element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Path to Programmable 3
  • Challenges & Projects
  • Design Challenges
  • Path to Programmable 3
  • More
  • Cancel
Path to Programmable 3
Blog AMD Zynq SoC MIDI Vintage Sound Synthesizer - Final
  • Blog
  • Forum
  • Documents
  • Leaderboard
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Path to Programmable 3 to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: javagoza
  • Date Created: 27 Aug 2023 9:39 PM Date Created
  • Views 5433 views
  • Likes 15 likes
  • Comments 20 comments
  • Zynq XC7Z007S SoC
  • multiplexer
  • avnet
  • fpga_projects
  • xilinx
  • winners
  • fpga
  • vivado
  • amd
  • Path to Programmable III
  • music
  • synthesizer
  • vitis
  • minized
Related
Recommended

AMD Zynq SoC MIDI Vintage Sound Synthesizer - Final

javagoza
javagoza
27 Aug 2023
Minized Synth I

1. Minized Synth I Introduction

image

In a music studio, a newbie synth designer is given a complex sound synthesizer and told to bring out the sound of a 'Grand Piano' using subtractive synthesis. After some tweaking, all that's left is a basic sine wave. The senior sound engineer asks, "Is it done?" The newbie grins and says, "Don't worry, if the 'Grand Piano' is in there, I'll bring it out with subtractive synthesis!"

Image generated with comicsmaker.ai

The joke is my adaptation of a joke in Spanish in which a cabinetmaker's apprentice is commissioned to try to extract a "San José" from a piece of wood and when he barely has a splinter left he exclaims that if the "San José" is inside from the billet of wood it will come out at any moment. Subtractive synthesis is about, sculpting a sound starting from a sound full of harmonics through filtering and envelopes.

One of the primary forms of synthesis is subtractive synthesis. This technique involves removing undesirable frequencies to create the desired sound. It operates under the assumption that a basic oscillator can produce waveforms with varying frequency spectrums that mimic the sound of an acoustic instrument. The signal then passes through a filter that compensates for the frequency-dependent losses and resonances in the body of the instrument. The amplifier section of the synthesizer shapes the filtered (or unfiltered) signal over time.

Minized Syntheziser

MiniZed Synth I

As part of my "Path to Programmable III Program" final project, I selected DSP from the Core Technology Path. My project involved creating a digital version of a classic analog synthesizer from the 1960s, which utilized the AVNET MiniZed development board. This board is powered by an AMD Zynq 7Z007S single-core development board, which contains an "All Programmable System-On-Chip" (APSoC) that integrates most computer components into one chip. Additionally, it includes FPGA technology that can be utilized.

image

AVNET MiniZed Development Board. The AMD Zynq 7Z007S (Formerly Xilinx Zynq) chip is in the center with the sticker on it.

During the project, several extension boards to the MiniZed board have been designed and built:

  • A sound editing panel with 17 analog potentiometers,
  • a board with an external analog multiplexer from 2 to 16 channels,
  • an extension board with user buttons and indicator LEDs. 
  • a low-pass filter board for digital-to-analog conversion with an analog audio amplifier
  • and a MIDI interface board, this last is designed but not yet built pending receipt of PCBs.

The majority of the work on this project was accomplished using SystemVerilog, a language for describing and verifying hardware used in the creation, design, simulation, testing, and implementation of electronic systems. The MiniZed development board features an AMD Zynq chip, which includes both a microcontroller and a network of Field Programmable Gate Arrays (FPGAs). These FPGAs are semiconductor devices made up of configurable logic blocks (CLBs) that are connected through programmable interconnects. As a result, FPGAs can be reprogrammed after manufacturing to meet specific application or functionality needs. AMD provides a development tool called VIVADO ML that allows the hardware description language to be translated into an interconnect configuration file for the FPGA fabric.

Throughout the past five blog posts, I have been documenting the necessary research and learning required for the successful completion of this project. The blogs covered various aspects such as hardware and software development flow, interfaces and ports of the AVNET Minized development board, and the development of IP blocks in Zynq's FPGA fabric programmable logic for the Vivado development tool. These IP blocks are like Lego pieces that allow bundling and reuse, and there was also a focus on the use of digital filters and hardware-accelerated conversion between Pulse Coded Modulation and Pulse Density Modulation formats. One of the blogs was dedicated to designing an external multiplexer for the Zynq XADC Analog Digital Converter, which is now also a part of the Minized Synth I. At the end of this blog post, you can find references to all the previous posts.


2. Index

Table of Contents

  • 1. Minized Synth I Introduction
  • 2. Index
  • 3. Minized Synth I: Overview and Features
    • 3.1. Signal Flow
    • 3.2. Basic Operation
    • 3.3. The components
      • Oscillators
      • Mixer (Source Levels)
      • Filter
      • Envelopes
      • Modulation (MOD)
      • Amplifier Volume
  • 4. High-Level Design
  • 5. Hardware Accelerated Synth Module IP
    • 5.1. Custom Synth Module AXI4 Lite Peripheral IP
      • Creation of a new AXI4 Peripheral IP
      • Standalone C Driver 
    • 5.2. What's under the hood of the Synth module?
    • 5.3. Oscillators
    • 5.4. Sine Wave Generator
    • 5.5. Noise Generator Module
    • 5.6. Square Wave Generator
    • 5.7. Triangle Wave Generator 
    • 5.8. Saw-Tooth Wave Generator Module
    • 5.9. Attack-Decay-Sustain-Release (ADSR) Envelope Modulation
    • 5.10. One-bit delta-sigma DAC
    • 5.11. Mixer Module
    • 5.12. Filter Module
    • 5.13. Amplifier Module
    • 5.14 Low Frequency Oscillator. Pitch Frequency Modulation
  • 6. Minized Microphone Input PDM to PCM via Decimation
    • 6.1. Interfacing the MiniZed built-in microphone
    • 6.2. Checking Microphone capture
    • 6.3. PDM to PCM using the VIVADO CIC Compiler IP
    • 6.4. Cascading Integrating Comb Filter
    • 6.5. Configuration of the AMD LogiCORE IP CIC Compiler
  • 7. Indicator LEDs and Control Switches Auxiliary Expansion Board
  • 8. Low Pass Filter and Sound Amplifier Expansion Boards
    • 8.1. Analog RC Low Pass Filter
    • 8.2 Sound Amplifier Expansion Board
  • 9. Multiplexer Expansion Board
    • 9.1. AMD XADC External Multiplexer Mode
    • 9.2. External Multiplexer mode in simultaneous sampling mode
    • 9.3. External Multiplexer Circuit Design
    • 9.4. 16:2 Multiplexer Expansion Board Bill of Materials
      • XADC Channels assignment
    • 9.5. XADC IP Configuration
  • 10. ARM 9 Bare Metal Software App. MIDI Control
  • 11. Creating the prototype
  • 12. Code Repository and References
  • 13. Conclusion and next steps
  • 14. Path to Programmable III Training Blog Series


3. Minized Synth I: Overview and Features

Back to index

The MiniZed Synth I is a Monophonic Digital Synthesizer. It features 2 digitally controlled oscillators, a pseudo-random noise generator, a microphone input, a low pass filter, 2 envelope generators, a mixer, and a modulation circuit. The MiniZed Synth has a classic one-knob-per-function design.

Although this first version is monophonic, thanks to the versatility of FPGAs the design can become polyphonic with very little effort by replicating the monophonic module of the synthesizer and creating a dispatcher that assigns the different voices and a new voice mixer module.

FRONT PANEL

image

MiniZed Synth I Front Panel. It has seventeen analog knobs, four-mode selection pushbuttons, and five-mode or action indicator LEDs.

OSCILLATORS

Two Digital Controlled Oscillators with selectable Sawtooth and Square waveshapes.

NOISE GENERATOR

Shapeable Pseudo Random Noise Generator.

MIC Input

The signal picked up by the MiniZed's microphone can be used as an alternate sound source.

MIX

Mixer for adjusting signal levels independently.

FILTER

12 dB/Octave Digital Low Pass Filter with Adjustable Resonance.

ENVELOPES

Two Attack-Decay-Sustain-Release (ADSR) envelope generators for modulating the filter and amplifier.

The Envelope Decay and Release segments are controlled by the DECAY knob, while the Release segments are enabled or disabled via the RELEASE switch

MOD

MIDI-syncable Low Frequency Oscillator (LFO) with Rate and individual control for the oscillators LFO amount and the filter LFO amount.

AUDIO OUT

1/8" Headphone Output

MIDI

DIN MIDI and USB UART/MIDI bridge 

3.1. Signal Flow

The below diagram shows how the MiniZed Synth generates sound. It shows the flow of Audio, Digital Control, and Modulation signals in the MiniZed Synth. Heavy lines indicate 16-bit Pulse Code Modulation (PCM) audio signals flowing from left to right. Lighter lines indicate Digital Control Signals which flow from the bottom. Dotted lines indicate Modulation routings. Spaced-dotted lines indicate 1-bit Pulse Density Modulation (PDM)  digital signals

image

MiniZed Synth Signal Flow Diagram. The diagram shows the different signals used by the synthesizer: control lines, PCM and PDM digital audio signal lines, the modulation routing of the signals, and the final analog sound signal.

The MiniZed Synth's source signals are created by two direct digital frequency synthesis oscillators and a digital Pseudo-Random Noise Generator which are mixed with the MiniZed built-in microphone 1-bit Pulse Density Modulation (PDM) signal previously converted to 16-bit Pulse Coded Modulation (PCM) via decimation.

The Mixer Output is routed to the Filter, where the tone is sculpted according to the Filter parameters and the Filter Attack-Decay-Sustain-Release  (ADSR) Envelope. 

The signal is then passed to the amplifier stage, where the Volume ADSR envelope shapes it. The Attack-Decay-Sustain-Release (ADSR) envelope modulation scheme is based on the observation that when a real instrument generates a note, the volume of the musical note changes over time. It rises rapidly from zero and then steadily decays. 

Finally, the PCM signal is routed to the 1-bit Delta Sigma Digital to Analog Converter and routed to an RC Low Pass Filter. The final level is set by the Analog Volume Control Knob.

Each time the MiniZed Synth receives a MIDI "Note On" command, it produces a Pitch control signal and Gate signal in response. The Pitch control signal sets the Pitch of the Oscillators, while the Gate signal triggers the Filter and Volume ADSR envelopes.

3.2. Basic Operation

The MiniZed Synth I responds to MIDI messages. In addition, Minized Synth has an LED MIDI indicator that indicates MIDI activity on either the DIN MIDI or the USB / UART connector. Additional parameters can be accessed via MIDI control.

3.3. The components

Oscillators

imageimage

The Oscillators together with the Pseudo-Random Noise Generator are the main source of the MiniZed Synth. They use Direct digital synthesis (DDS) a method employed for creating arbitrary waveforms from a single, fixed-frequency reference that can be tuned and amplified into sound that we can hear. The Mnized Synth can produce a total musical range of 8 octaves.

The OSCILLATOR 1 serves as a master Oscillator to which OSCILLATOR 2 is tuned. Two independent switches select the waveform for each Oscillator (Sawtooth or Square). The MiniZed is not limited to these two waveforms but it can also be programmed to generate sinusoidal, triangle, and PWM square waves.

Panel controls for the Oscillators

OSCILLATOR 1 Switch (CC# 70):

Selects a Sawtooth (LED OFF) or Square wave (LED ON) for OSC 1.

OSCILLATOR 2 Switch (CC# 71):

Selects a Sawtooth (LED OFF) or Square wave (LED ON) for OSC 2.

VCO 2 FREQ (CC# 17):

Sets the frequency offset of OSC 2 from OSC 1.

The offset range is +/-1 octave.

Center position tunes OSC 2 in unison with OSC 1.

Mixer (Source Levels)

imageimage

Each Oscillator (OSC1 & OSC2) has a dedicated level knob that allows you to control the relative strength of each oscillator from 0 to 100%. Another knob allows you to control the relative strength of the other two sound sources, the pseudo-random noise generator, and the MiniZed's internal microphone. A switch selects the third sound source (Mic or Noise). The signal begins to clip the filter at about 2 o’clock creating more aggressive sounds.

Panel Controls for the Mixer

OSC1 LVL (CC# 15):

Sets the level of OSC 1.

OSC 2 LVL (CC# 16):

Sets the level of OSC 2.

NOISE / MIC LVL (CC# 17 / CC# 18)

Sets the level of NOISE or MIC.

Filter

image

The FILTER is a 12dB/Octave Low Pass Filter design with resonance. It has controls for CUTOFF frequency which determines the range of frequencies the filter will affect, as well as RESONANCE, which determines how much emphasis is applied to the harmonics near the Cutoff frequency.

image

Frequency response of a Low Pass Filter with Resonance

The FILTER provides either fixed or dynamic timbre modifications. Dynamic changes are provided by the Filter Envelope Generator (EG) and a Low Frequency Oscillator (LFO).

Panel Controls for the Filter

CUTOFF (CC# 19):

Adjusts the CUTOFF frequency of the Low Pass Filter from 20 Hz to 20 KHz. As the knob is rotated clockwise, the cutoff frequency is increased, allowing more harmonics to pass through the filter, resulting in a brighter sound. Conversely, as the knob is rotated counterclockwise, the sounds get darker.

RESONANCE (RES) (CC# 21):

Sets the amount of signal sent from the FILTER output to be fed back into its input. This creates a peak in the frequency that can be increased to self-oscillation.

EG AMOUNT (CC# 22):

Determines how much the Filter Envelope Generator (EG) adds to or subtracts from the Filter Cutoff control setting.

When the EG AMOUNT knob is set to positive (+), turn the FILTER CUTOFF knob left to hear the effect.

When the EG AMOUNT knob is set to negative (-), turn the FILTER CUTOFF knob right to hear the effect.

Envelopes

imageimage

ENVELOPE GENERATORS (EGs) add motion to a sound after a note is played. The MiniZed Synth has two separate Envelope Generators that affect the brightness and loudness of
the MiniZed Synth's sound by modulating the Filter Cutoff and Volume.

The EGs are started by a Gate or MIDI Note message. Once started, their shape in time is set by the ATTACK, DECAY/RELEASE, and SUSTAIN controls, as well as the Release switch and length of the Note played.

Panel Controls for the Envelopes

FILTER ATTACK (CC# 23):

Sets the time it takes for the Attack portion of the Filter EG to rise from zero to maximum. The Attack time ranges from 1 msec to 30 seconds.

FILTER DECAY/RELEASE (CC# 24):

Sets the time for the Decay and Release portion of the Filter EG. When a note is held, and the Attack time end is reached, the Decay portion of the EG starts. During the Decay portion, the EG moves to the Sustain level. When a note is released, the EG moves back to zero at the rate set by this control. This time ranges from 1 msec to 30 seconds. The Release segment of the
The envelope is determined by the state of the RELEASE switch (ON/OFF).

FILTER SUSTAIN (CC# 25):

Sets the Filter EG level after the Decay and before the Release portion. A note must be held longer than both the Attack and Decay time to reach the Sustain level. The level is adjustable from 0 to 100%.

AMPLIFIER ATTACK (CC# 28):

Sets the time it takes for the Attack portion of the Amplifier EG to rise from zero to maximum. The Attack time ranges from 1 msec to 30 seconds.

AMPLIFIER DECAY/RELEASE (CC# 29):

Sets the time for the Decay and Release portion of the Amplifier EG.

When a note is held, and the Attack time end is reached, the Decay portion of the EG starts. During the decay portion, the EG moves to the Sustain level.

When a note is released, the EG moves back to zero at the rate set by this control. The time ranges from 1 msec to 30 seconds.

The Release segment of the Envelope is determined by the state of the RELEASE switch (ON/OFF).

AMPLIFIER SUSTAIN (CC# 30):

Sets the Amplifier EG level after the Decay and before the Release portion. A note must be held longer than both the Attack and Decay time to reach the Sustain level. The level is adjustable from 0 to 100%.

RELEASE

image

The RELEASE switch enables or disables the Release segment of both Envelope Generators.

When enabled, the Envelope Release time is the same as the Envelope Decay time, and the DECAY control adjusts the time for both segments.

When disabled, the Release segment does not occur and the Envelope stops abruptly in response to a “Note Off” message (or when the Gate goes to zero).

Modulation (MOD)

image

The MiniZed Synth's MODULATION section provides an LFO with adjustable RATE and AMOUNT controls for the oscillators and the Filter. The Low-Frequency Oscillator (LFO) is a signal used to move the pitch of Oscillators and the Filter Cutoff up and down automatically. An LFO can be used to simulate vibrato, create wobbling filter sweeps, or make interesting synthesizer sounds.

Panel Controls for the Modulation

LFO RATE (CC# 3):

Sets the frequency of LFO Modulation. The range is from 0.1Hz to 100Hz.

OSC LFO AMOUNT (CC# 13):

Sets the maximum amount the LFO moves the OSCILLATORS pitch up and down, up to +/- 1 octave. Modulation affects both Oscillators.

If using a MIDI controller, the Mod Wheel (CC# 1) is used to fade the LFO Pitch Modulation in and out.

FILTER LFO AMOUNT (CC# 12):

Sets the maximum amount the LFO moves the Filter Cutoff up and down, up to +/- 5 octaves. Amounts above 20KHz or below 20 Hz are clipped.

If using a MIDI controller, the Mod Wheel (CC# 1) is used to fade the LFO Filter Modulation in and out.

Amplifier Volume

image

The MiniZed Synth features a monophonic Audio Output that is adjusted by the VOLUME control.

This is an external analog control to the MiniZed development board, intended to control an amplified output.

Panel Controls for the Amplifier Volume

VOLUME:

Adjusts the output of an amplified analog output.

Rotating the control fully clockwise produces the maximum output.

Rotating the control fully counterclockwise silences the MiniZed Synth.


4. High-Level Design

Back to index

image

High-level MiniZed Synth Block Diagram. Heavy Blocks are custom blocks

The core of the synthesizer is the AVNET MiniZed development board. The kernel is divided into two main parts. A software program that runs on the Processing System (PS) part, based on the ARM 9 microcontroller, and a design built on top of the FPGA fabric.
The software program is in charge of configuring the oscillators, responding to user inputs from the front panel, and processing the MIDI orders, it interfaces between the MIDI controller devices and the synthesizer module created in the FPGA fabric as an accelerated hardware module.

To expand the user interface there are three new modules external to the MiniZed development board: an additional board with a 16:2 analog multiplexer that allows reading 16 analog potentiometers, a sound editing panel with 16 user-controlled analog potentiometers control, and an expansion board with 4 push buttons and 5 indicator LEDs.

Finally, there is another expansion board that allows the connection of standard MIDI controllers via a 5-pin DIN connector and another one with a Low Pass Filter plus amplifier to convert the Pulse Density Modulated (PDM ) signal generated by a 1-bit Delta Sigma Digital filter module into an analog signal. and amplify the signal sent to the speaker.

Communication with MIDI controllers can be done either through the DIN connector or a USB UART bridge taking advantage of the MiniZed's mini USB connector.


5. Hardware Accelerated Synth Module IP

Back to index

image

The Synth Module IP is controlled from a bare metal application written in C that runs on Zynq's ARM 9 Microcontroller Processing System.

The system uses an AXI4 Lite Bus to communicate from the bare metal C app, without the support of a known operating system as FreeRTOS or Linux, to the Synth Module.

5.1. Custom Synth Module AXI4 Lite Peripheral IP

The Synth Module is packaged as a Vivado IP Block and is fully configured from the bare metal software application using AXI Lite 4 bus protocol.

It has one input for the 1-bit PDM built-in microphone and two outputs. One output with the 2.4 MHz generated microphone clock and the other is 1 bit PDM output sound signal.

If the reader is interested in the complete creation of an AXI4 Lite peripheral, I made a complete tutorial, see  Arty S7 50 ArtyBot Custom AXI4 Lite IP Peripheral for Sensing Motor Rotational Speed 

The Synth Module is coded in SystemVerilog:

`timescale 1ns / 1ps


module synth_module #(PHASE_ACC_WIDTH = 30)(
    input logic clk,
    input logic reset,
    // oscillator 1 configuration
    input logic [PHASE_ACC_WIDTH-1:0] osc1_fccw, // Pitch, oscillator 1 frequency carrier ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc1_focw, // Detune, oscillator 1 frequency offset ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc1_pho, // Phase shift, oscillator 1 phase offset ctrl word
    input logic [2:0] osc1_wt, // oscillator 1 wave type
    input logic [15:0] mix_lvl_osc1, // oscillator 1 volume level
   
    // oscillator 2 configuration
    input logic [PHASE_ACC_WIDTH-1:0] osc2_fccw, // Pitch, oscillator 1 frequency carrier ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc2_focw, // Detune, oscillator 1 frequency offset ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc2_pho, // Phase shift, oscillator 1 phase offset ctrl word
    input logic [2:0] osc2_wt, // oscillator 2 wave type
    input logic [15:0] mix_lvl_osc2, // oscillator 2 volume level
    
    // oscillator 3 / noise oscillator configuration
    input logic [PHASE_ACC_WIDTH-1:0] osc3_fccw, // Pitch, oscillator 1 frequency carrier ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc3_focw, // Detune, oscillator 1 frequency offset ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] osc3_pho, // Phase shift, oscillator 1 phase offset ctrl word
    input logic [2:0] osc3_wt, // oscillator 3 wave type
    input logic [15:0] mix_lvl_osc3, // oscillator 3 volume level
   
    // LFO oscillator configuration
    input logic [PHASE_ACC_WIDTH-1:0] lfoo_fccw, // LFO oscillator frequency carrier ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] lfoo_focw, // LFO oscillator frequency offset ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] lfoo_pho, // LFO oscillator phase offset ctrl word
    input logic [2:0] lfoo_wt, // LFO oscillator wave type
    input logic [15:0] lfoo_lvl, // Sets the maximum amount the LFO moves the VCOs pitch up and down, up to +/- 1 octave.
    
    // LFO filter configuration
    input logic [PHASE_ACC_WIDTH-1:0] lfof_fccw, // LFO filter frequency carrier ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] lfof_focw, // LFO filter frequency offset ctrl word
    input logic [PHASE_ACC_WIDTH-1:0] lfof_pho, // LFO filter phase offset ctrl word
    input logic [2:0] lfof_wt, // LFO filter wave type
    input logic [15:0] lfof_lvl, // Sets the maximum amount the LFO moves the Filter Cutoff up and down, up to +/- 5 octaves.
    
    // Amplifier ADSR configuration
    input logic adsra_start, // generate a pulse to start the envelope generation
    input logic [31:0] adsra_a, // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
    input logic [31:0] adsra_d,  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
    input logic [31:0] adsra_sl, // amplitude for the sustain segment
    input logic [31:0] adsra_r,  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
    input logic [31:0] adsra_st, // tsustain / t_sys steps for the sustain

    // Filter ADSR configuration
    input logic adsrf_start, // generate a pulse to start the envelope generation
    input logic [31:0] adsrf_a, // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
    input logic [31:0] adsrf_d,  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
    input logic [31:0] adsrf_sl, // amplitude for the sustain segment
    input logic [31:0] adsrf_r,  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
    input logic [31:0] adsrf_st, // tsustain / t_sys steps for the sustain
    
    // Filter configuration
    input logic [31:0] fcut, // filter cutoff frequency
    input logic [31:0] fres, // filter resonance level
    input logic [31:0] feg, // filter envelope generator amount


    // PDM Microphone 
    input logic mic_pdm_input, // Input external P1-bit DM MiniZed Signal AUDIO_DAT
    input logic [15:0] mix_lvl_mic, // oscillator 3 volume level
        
    output logic pdm_out, // 1-bit PDM sound output
    output logic mic_clk,  // generated microphone clock signal route to Minized external AUDIO_CLK
    
    output logic adsra_idle,
    output logic [15:0] adsra_amp


    );
    
    // signal declarations
    localparam BIAS = 2 ** (15); // {1'b1, (W-2){1'b0}};
 

    
    // constants for envelopes
    localparam MAX = 32'h8000_0000; // MAX volume
    localparam BYPASS = 32'hffff_ffff; // Bypass envelope
    localparam ZERO = 32'h0000_0000; // mute
    
    // constants for oscillators
    localparam SINE =     3'b000;                 
    localparam SQUARE =   3'b001;
    localparam SAW =      3'b010;
    localparam TRIANGLE = 3'b011;
    localparam NOISE =    3'b100; 
    
    // lfo oscillator outputs
    logic [15:0] lfoo_output; // LFO oscillator output
    logic lfoo_pulse_out; //  LFO Oscillator pulse output 
    
//    // lfo filter outputs
    logic [15:0] lfof_output; // lfo filter output
    logic lfof_pulse_out; //  Oscillator 1 pulse output 
    
    
    // Oscillator 1 outputs
    logic [15:0] osc1_pcm_out; // Oscillator 1 PCM output 
    logic osc1_pulse_out; //  Oscillator 1 pulse output 
    
//    // Oscillator 2 outputs
    logic [15:0] osc2_pcm_out; // Oscillator 2 PCM output 
    logic osc2_pulse_out; //  Oscillator 2 pulse output 
    
//    // Noise / Oscillator 3 outputs
    logic [15:0] osc3_pcm_out; // Oscillator 3 PCM output 
    logic osc3_pulse_out; //  Oscillator 3 pulse output 
    
    // Microphone Outputs
    logic [15:0] mic_pcm_out; // Microphone PCM output 
    logic mic_data_valid; //  Microphone PDM to PCM conversion data valid
    
    // Mixer outputs
    logic [15:0] mix_pcm_out; // Mixer PCM output 
    
    // amplifier ADSR amplitude output
     logic [15:0] adsra_amp;
     logic [15:0] adsra_rt_amp;
     logic [15:0] adsra_selected;
     
     // filter ADSR amplitude output
     logic [15:0] adsrf_amp;
     logic [15:0] adsrf_rt_amp;
     logic [15:0] adsrf_selected;
    
    // Amplifier outputs
    logic [15:0] amp_pcm_out;
    
//    filter outputs
    logic [15:0] filter_pcm_out;
    
    
    logic [PHASE_ACC_WIDTH-1:0] lfoo_extended;
    logic [PHASE_ACC_WIDTH-1:0] lfof_extended;
    
    logic adsrf_idle;
    logic adsrf_rt_idle;
    logic adsrf_nrt_idle;
    
    logic adsra_rt_idle;
    logic adsra_nrt_idle;
    
    
//    // Keep The range is from 0.01Hz to 100Hz
//    // instantiate lfo oscillator
    ddfs #(.PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) lfoo(.clk(clk),
        .reset(reset),
        .freq_carrier_ctrl_word(lfoo_fccw),
        .freq_offset_ctrl_word(lfoo_focw),
        .phase_offset(lfoo_pho),
        .envelope(lfoo_lvl), // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
        .pcm_out(lfoo_output),
        .pulse_out(lfoo_pulse_out),
        .wave_type( lfoo_wt)
        );
        
    // extend sign to 30 bits    
    assign lfoo_extended = {{ 14{lfoo_output[15]}},lfoo_output}; 
    
    // extend sign to 30 bits    
    assign lfof_extended = {{ 14{lfof_output[15]}},lfof_output}; 


    
    
//    // Keep The range is from up to +/- 5 octaves. Amounts above 20KHz or below 20 Hz are clipped
//    // instantiate lfo filter
    ddfs #(.PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) lfof(.clk(clk),
        .reset(reset),
        .freq_carrier_ctrl_word(lfof_fccw),
        .freq_offset_ctrl_word(lfof_focw),
        .phase_offset(lfof_pho),
        .envelope(lfof_lvl), // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
        .pcm_out(lfof_output),
        .pulse_out(lfof_pulse_out),
        .wave_type( lfof_wt)
        );
    
    // instantiate oscillator 1 osc_1
    ddfs #(.PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) osc1(.clk(clk),
        .reset(reset),
        .freq_carrier_ctrl_word(osc1_fccw),// 4.724 .freq_carrier_ctrl_word(osc1_fccw),
        .freq_offset_ctrl_word(lfoo_extended),  // .freq_offset_ctrl_word(lfoo_output), // LFO moves the VCOs pitch up and down, up to +/- 1 octave
        .phase_offset(osc1_pho), // .phase_offset(osc1_pho),
        .envelope(16'h4000), // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
        .pcm_out(osc1_pcm_out),
        .pulse_out(osc1_pulse_out),
        .wave_type(osc1_wt)//.wave_type( 3'b001)
        );
        
//   // instantiate oscillator 2 osc_2
    ddfs #(.PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) osc2(.clk(clk),
        .reset(reset),
        .freq_carrier_ctrl_word(osc2_fccw),
        .freq_offset_ctrl_word(lfoo_extended), // LFO moves the VCOs pitch up and down, up to +/- 1 octave
        .phase_offset(osc2_pho),
        .envelope(16'h4000), // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
        .pcm_out(osc2_pcm_out),
        .pulse_out(osc2_pulse_out),
        .wave_type( osc2_wt)
        );
        
    // instantiate oscillator 3 osc_3 usually noise
    ddfs #(.PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) osc3(.clk(clk),
        .reset(reset),
        .freq_carrier_ctrl_word(osc3_fccw),
        .freq_offset_ctrl_word(lfoo_extended),
        .phase_offset(osc3_pho),
        .envelope(16'h4000), // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
        .pcm_out(osc3_pcm_out),
        .pulse_out(osc3_pulse_out),
        .wave_type( osc3_wt)
        );
        
    // instantiate PDM to PCM converter for the microphone input
    pdm_microphone microphone(
        .clk(clk),
        .reset(reset),
        .mic_pdm_data(mic_pdm_input),
        .mic_pcm_data(mic_pcm_out),
        .mic_data_valid(mic_data_valid),
        .mic_clk(mic_clk)
    );    
        
    // instantiate mixer
    mixer mix(.clk(clk),
        .reset(reset),
        .input_1(osc1_pcm_out),
        .lvl_1(mix_lvl_osc1),
        .input_2(osc2_pcm_out),
        .lvl_2(mix_lvl_osc2),
        .input_3(osc3_pcm_out),// .input_3(osc3_pcm_out),
        .lvl_3(mix_lvl_osc3),
        .input_4(mic_pcm_out),
        .lvl_4(mix_lvl_mic),
        .pcm_out(mix_pcm_out)
        );
        
     // if sustain time is  32'hffff_fff select real time mode ADSR   
     always_comb begin 
       if (adsra_st == 32'hffff_ffff) begin
            adsra_selected = adsra_rt_amp;
            adsrf_selected = adsrf_rt_amp;
       end else begin
            adsra_selected = adsra_amp;
            adsrf_selected = adsrf_amp;
       end
     end   
     
     assign adsra_idle = adsra_nrt_idle & adsra_rt_idle & adsrf_nrt_idle & adsrf_rt_idle;

        
     // instantiate amplifier ADSR amplitude generator
      adsr adsra(
          .clk(clk),
          .reset(reset),
          .start(adsra_start), // generate a pulse to start the envelope generation
          .attack_step_value(adsra_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
          .decay_step_value(adsra_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
          .sustain_level(adsra_sl), // amplitude for the sustain segment
          .release_step_value(adsra_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
          .sustain_time(adsra_st), // tsustain / t_sys steps for the sustain
          .envelope(adsra_amp),
          .adsr_idle(adsra_nrt_idle)
          );
          
      // instantiate amplifier ADSR real time mode amplitude generator
      adsr_rt adsra_rt(
          .clk(clk),
          .reset(reset),
          .start(adsra_start), // g adsr sustain active when start asserted
          .attack_step_value(adsra_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
          .decay_step_value(adsra_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
          .sustain_level(adsra_sl), // amplitude for the sustain segment
          .release_step_value(adsra_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
          .envelope(adsra_rt_amp),
          .adsr_idle(adsra_rt_idle)
          );
          
//      // instantiate filter ADSR amplitude generator
      adsr adsrf(
          .clk(clk),
          .reset(reset),
          .start(adsrf_start), // generate a pulse to start the envelope generation
          .attack_step_value(adsrf_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
          .decay_step_value(adsrf_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
          .sustain_level(adsrf_sl), // amplitude for the sustain segment
          .release_step_value(adsrf_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
          .sustain_time(adsrf_st), // tsustain / t_sys steps for the sustain
          .envelope(adsrf_amp),
          .adsr_idle(adsrf_nrt_idle)
          );
          
     adsr_rt adsrf_rt(
          .clk(clk),
          .reset(reset),
          .start(adsrf_start), // adsr sustain active when start asserted
          .attack_step_value(adsrf_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
          .decay_step_value(adsrf_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
          .sustain_level(adsrf_sl), // amplitude for the sustain segment
          .release_step_value(adsrf_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
          .envelope(adsrf_rt_amp),
          .adsr_idle(adsrf_rt_idle)
          );
        
      // instantiate Amplifier
      amplifier amp(
          .clk(clk),
          .reset(reset),
          .pcm_input(mix_pcm_out),
          .envelope(adsra_selected),
          .pcm_out(amp_pcm_out));
          
//      // instantiate filter
      filter filter_unit(
          .clk(clk),
          .reset(reset),
          .fccw(osc1_fccw),
          .pcm_in(amp_pcm_out),
          .cutoff_freq(fcut),
          .resonance_lvl(fres),
          .eg_amount(feg), //EG AMOUNT Determines how much the Filter Envelope Generator (EG) adds to or subtracts from the Filter Cutoff control setting.
          .envelope(adsrf_rt_amp),
          .modulation(lfof_output),
          .pcm_out(filter_pcm_out)
      );
          
      // instantiate 1 bit delta sigma DAC
      ds_1bit_dac dac(
        .clk(clk),
        .reset(reset),
        .pcm_in(filter_pcm_out),
        .pdm_out(pdm_out)
        );
      
      
        
    
endmodule

SynthModuleAxi

`timescale 1 ns / 1 ps

	module SynthModule_v1_0_S00_AXI #
	(
		// Users to add parameters here

		// User parameters ends
		// Do not modify the parameters beyond this line

		// Width of S_AXI data bus
		parameter integer C_S_AXI_DATA_WIDTH	= 32,
		// Width of S_AXI address bus
		parameter integer C_S_AXI_ADDR_WIDTH	= 8
	)
	(
    
    	// Input external P1-bit DM MiniZed Signal AUDIO_DAT
        input wire mic_pdm_input,
        
        
         // 1-bit PDM sound output 
        output wire pdm_out, // 1-bit PDM sound output
        
        // generated microphone clock signal route to Minized external AUDIO_CLK
        output wire micclk,  
        
        output wire [31:0] osc1Freq,
        
		// User ports ends
		// Do not modify the ports beyond this line

		// Global Clock Signal
		input wire  S_AXI_ACLK,
		// Global Reset Signal. This Signal is Active LOW
		input wire  S_AXI_ARESETN,
		// Write address (issued by master, acceped by Slave)
		input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR,
		// Write channel Protection type. This signal indicates the
    		// privilege and security level of the transaction, and whether
    		// the transaction is a data access or an instruction access.
		input wire [2 : 0] S_AXI_AWPROT,
		// Write address valid. This signal indicates that the master signaling
    		// valid write address and control information.
		input wire  S_AXI_AWVALID,
		// Write address ready. This signal indicates that the slave is ready
    		// to accept an address and associated control signals.
		output wire  S_AXI_AWREADY,
		// Write data (issued by master, acceped by Slave) 
		input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA,
		// Write strobes. This signal indicates which byte lanes hold
    		// valid data. There is one write strobe bit for each eight
    		// bits of the write data bus.    
		input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB,
		// Write valid. This signal indicates that valid write
    		// data and strobes are available.
		input wire  S_AXI_WVALID,
		// Write ready. This signal indicates that the slave
    		// can accept the write data.
		output wire  S_AXI_WREADY,
		// Write response. This signal indicates the status
    		// of the write transaction.
		output wire [1 : 0] S_AXI_BRESP,
		// Write response valid. This signal indicates that the channel
    		// is signaling a valid write response.
		output wire  S_AXI_BVALID,
		// Response ready. This signal indicates that the master
    		// can accept a write response.
		input wire  S_AXI_BREADY,
		// Read address (issued by master, acceped by Slave)
		input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR,
		// Protection type. This signal indicates the privilege
    		// and security level of the transaction, and whether the
    		// transaction is a data access or an instruction access.
		input wire [2 : 0] S_AXI_ARPROT,
		// Read address valid. This signal indicates that the channel
    		// is signaling valid read address and control information.
		input wire  S_AXI_ARVALID,
		// Read address ready. This signal indicates that the slave is
    		// ready to accept an address and associated control signals.
		output wire  S_AXI_ARREADY,
		// Read data (issued by slave)
		output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA,
		// Read response. This signal indicates the status of the
    		// read transfer.
		output wire [1 : 0] S_AXI_RRESP,
		// Read valid. This signal indicates that the channel is
    		// signaling the required read data.
		output wire  S_AXI_RVALID,
		// Read ready. This signal indicates that the master can
    		// accept the read data and response information.
		input wire  S_AXI_RREADY
	);

	// AXI4LITE signals
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_awaddr;
	reg  	axi_awready;
	reg  	axi_wready;
	reg [1 : 0] 	axi_bresp;
	reg  	axi_bvalid;
	reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_araddr;
	reg  	axi_arready;
	reg [C_S_AXI_DATA_WIDTH-1 : 0] 	axi_rdata;
	reg [1 : 0] 	axi_rresp;
	reg  	axi_rvalid;

	// Example-specific design signals
	// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
	// ADDR_LSB is used for addressing 32/64 bit registers/memories
	// ADDR_LSB = 2 for 32 bits (n downto 2)
	// ADDR_LSB = 3 for 64 bits (n downto 3)
	localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;
	localparam integer OPT_MEM_ADDR_BITS = 5;
	//----------------------------------------------
	//-- Signals for user logic register space example
	//------------------------------------------------
	//-- Number of Slave Registers 34
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg0;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg1;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg2;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg3;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg4;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg5;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg6;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg7;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg8;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg9;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg10;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg11;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg12;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg13;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg14;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg15;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg16;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg17;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg18;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg19;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg20;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg21;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg22;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg23;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg24;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg25;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg26;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg27;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg28;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg29;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg30;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg31;
	reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg32;
	wire [C_S_AXI_DATA_WIDTH-1:0]	slv_reg33;
	wire	 slv_reg_rden;
	wire	 slv_reg_wren;
	reg [C_S_AXI_DATA_WIDTH-1:0]	 reg_data_out;
	integer	 byte_index;
	reg	 aw_en;

	// I/O Connections assignments

	assign S_AXI_AWREADY	= axi_awready;
	assign S_AXI_WREADY	= axi_wready;
	assign S_AXI_BRESP	= axi_bresp;
	assign S_AXI_BVALID	= axi_bvalid;
	assign S_AXI_ARREADY	= axi_arready;
	assign S_AXI_RDATA	= axi_rdata;
	assign S_AXI_RRESP	= axi_rresp;
	assign S_AXI_RVALID	= axi_rvalid;
	// Implement axi_awready generation
	// axi_awready is asserted for one S_AXI_ACLK clock cycle when both
	// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
	// de-asserted when reset is low.

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_awready <= 1'b0;
	      aw_en <= 1'b1;
	    end 
	  else
	    begin    
	      if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
	        begin
	          // slave is ready to accept write address when 
	          // there is a valid write address and write data
	          // on the write address and data bus. This design 
	          // expects no outstanding transactions. 
	          axi_awready <= 1'b1;
	          aw_en <= 1'b0;
	        end
	        else if (S_AXI_BREADY && axi_bvalid)
	            begin
	              aw_en <= 1'b1;
	              axi_awready <= 1'b0;
	            end
	      else           
	        begin
	          axi_awready <= 1'b0;
	        end
	    end 
	end       

	// Implement axi_awaddr latching
	// This process is used to latch the address when both 
	// S_AXI_AWVALID and S_AXI_WVALID are valid. 

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_awaddr <= 0;
	    end 
	  else
	    begin    
	      if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
	        begin
	          // Write Address latching 
	          axi_awaddr <= S_AXI_AWADDR;
	        end
	    end 
	end       

	// Implement axi_wready generation
	// axi_wready is asserted for one S_AXI_ACLK clock cycle when both
	// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is 
	// de-asserted when reset is low. 

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_wready <= 1'b0;
	    end 
	  else
	    begin    
	      if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
	        begin
	          // slave is ready to accept write data when 
	          // there is a valid write address and write data
	          // on the write address and data bus. This design 
	          // expects no outstanding transactions. 
	          axi_wready <= 1'b1;
	        end
	      else
	        begin
	          axi_wready <= 1'b0;
	        end
	    end 
	end       

	// Implement memory mapped register select and write logic generation
	// The write data is accepted and written to memory mapped registers when
	// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
	// select byte enables of slave registers while writing.
	// These registers are cleared when reset (active low) is applied.
	// Slave register write enable is asserted when valid address and data are available
	// and the slave is ready to accept the write address and write data.
	assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      slv_reg0 <= 0;
	      slv_reg1 <= 0;
	      slv_reg2 <= 0;
	      slv_reg3 <= 0;
	      slv_reg4 <= 0;
	      slv_reg5 <= 0;
	      slv_reg6 <= 0;
	      slv_reg7 <= 0;
	      slv_reg8 <= 0;
	      slv_reg9 <= 0;
	      slv_reg10 <= 0;
	      slv_reg11 <= 0;
	      slv_reg12 <= 0;
	      slv_reg13 <= 0;
	      slv_reg14 <= 0;
	      slv_reg15 <= 0;
	      slv_reg16 <= 0;
	      slv_reg17 <= 0;
	      slv_reg18 <= 0;
	      slv_reg19 <= 0;
	      slv_reg20 <= 0;
	      slv_reg21 <= 0;
	      slv_reg22 <= 0;
	      slv_reg23 <= 0;
	      slv_reg24 <= 0;
	      slv_reg25 <= 0;
	      slv_reg26 <= 0;
	      slv_reg27 <= 0;
	      slv_reg28 <= 0;
	      slv_reg29 <= 0;
	      slv_reg30 <= 0;
	      slv_reg31 <= 0;
	      slv_reg32 <= 0;
	      // slv_reg33 <= 0;  // assigned in user logic
	    end 
	  else begin
	    if (slv_reg_wren)
	      begin
	        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	          6'h00:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 0
	                slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h01:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 1
	                slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h02:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 2
	                slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h03:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 3
	                slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h04:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 4
	                slv_reg4[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h05:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 5
	                slv_reg5[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h06:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 6
	                slv_reg6[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h07:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 7
	                slv_reg7[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h08:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 8
	                slv_reg8[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h09:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 9
	                slv_reg9[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0A:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 10
	                slv_reg10[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0B:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 11
	                slv_reg11[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0C:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 12
	                slv_reg12[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0D:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 13
	                slv_reg13[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0E:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 14
	                slv_reg14[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h0F:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 15
	                slv_reg15[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h10:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 16
	                slv_reg16[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h11:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 17
	                slv_reg17[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h12:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 18
	                slv_reg18[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h13:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 19
	                slv_reg19[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h14:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 20
	                slv_reg20[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h15:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 21
	                slv_reg21[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h16:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 22
	                slv_reg22[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h17:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 23
	                slv_reg23[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h18:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 24
	                slv_reg24[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h19:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 25
	                slv_reg25[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1A:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 26
	                slv_reg26[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1B:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 27
	                slv_reg27[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1C:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 28
	                slv_reg28[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1D:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 29
	                slv_reg29[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1E:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 30
	                slv_reg30[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h1F:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 31
	                slv_reg31[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h20:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 32
	                slv_reg32[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
	              end  
	          6'h21:
	            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
	              if ( S_AXI_WSTRB[byte_index] == 1 ) begin
	                // Respective byte enables are asserted as per write strobes 
	                // Slave register 33
	                // slv_reg33[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; // assigned in user logic
	              end  
	          default : begin
	                      slv_reg0 <= slv_reg0;
	                      slv_reg1 <= slv_reg1;
	                      slv_reg2 <= slv_reg2;
	                      slv_reg3 <= slv_reg3;
	                      slv_reg4 <= slv_reg4;
	                      slv_reg5 <= slv_reg5;
	                      slv_reg6 <= slv_reg6;
	                      slv_reg7 <= slv_reg7;
	                      slv_reg8 <= slv_reg8;
	                      slv_reg9 <= slv_reg9;
	                      slv_reg10 <= slv_reg10;
	                      slv_reg11 <= slv_reg11;
	                      slv_reg12 <= slv_reg12;
	                      slv_reg13 <= slv_reg13;
	                      slv_reg14 <= slv_reg14;
	                      slv_reg15 <= slv_reg15;
	                      slv_reg16 <= slv_reg16;
	                      slv_reg17 <= slv_reg17;
	                      slv_reg18 <= slv_reg18;
	                      slv_reg19 <= slv_reg19;
	                      slv_reg20 <= slv_reg20;
	                      slv_reg21 <= slv_reg21;
	                      slv_reg22 <= slv_reg22;
	                      slv_reg23 <= slv_reg23;
	                      slv_reg24 <= slv_reg24;
	                      slv_reg25 <= slv_reg25;
	                      slv_reg26 <= slv_reg26;
	                      slv_reg27 <= slv_reg27;
	                      slv_reg28 <= slv_reg28;
	                      slv_reg29 <= slv_reg29;
	                      slv_reg30 <= slv_reg30;
	                      slv_reg31 <= slv_reg31;
	                      slv_reg32 <= slv_reg32;
	                      // slv_reg33 <= slv_reg33; // assigned in user logic
	                    end
	        endcase
	      end
	  end
	end    

	// Implement write response logic generation
	// The write response and response valid signals are asserted by the slave 
	// when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.  
	// This marks the acceptance of address and indicates the status of 
	// write transaction.

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_bvalid  <= 0;
	      axi_bresp   <= 2'b0;
	    end 
	  else
	    begin    
	      if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
	        begin
	          // indicates a valid write response is available
	          axi_bvalid <= 1'b1;
	          axi_bresp  <= 2'b0; // 'OKAY' response 
	        end                   // work error responses in future
	      else
	        begin
	          if (S_AXI_BREADY && axi_bvalid) 
	            //check if bready is asserted while bvalid is high) 
	            //(there is a possibility that bready is always asserted high)   
	            begin
	              axi_bvalid <= 1'b0; 
	            end  
	        end
	    end
	end   

	// Implement axi_arready generation
	// axi_arready is asserted for one S_AXI_ACLK clock cycle when
	// S_AXI_ARVALID is asserted. axi_awready is 
	// de-asserted when reset (active low) is asserted. 
	// The read address is also latched when S_AXI_ARVALID is 
	// asserted. axi_araddr is reset to zero on reset assertion.

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_arready <= 1'b0;
	      axi_araddr  <= 32'b0;
	    end 
	  else
	    begin    
	      if (~axi_arready && S_AXI_ARVALID)
	        begin
	          // indicates that the slave has acceped the valid read address
	          axi_arready <= 1'b1;
	          // Read address latching
	          axi_araddr  <= S_AXI_ARADDR;
	        end
	      else
	        begin
	          axi_arready <= 1'b0;
	        end
	    end 
	end       

	// Implement axi_arvalid generation
	// axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both 
	// S_AXI_ARVALID and axi_arready are asserted. The slave registers 
	// data are available on the axi_rdata bus at this instance. The 
	// assertion of axi_rvalid marks the validity of read data on the 
	// bus and axi_rresp indicates the status of read transaction.axi_rvalid 
	// is deasserted on reset (active low). axi_rresp and axi_rdata are 
	// cleared to zero on reset (active low).  
	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_rvalid <= 0;
	      axi_rresp  <= 0;
	    end 
	  else
	    begin    
	      if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
	        begin
	          // Valid read data is available at the read data bus
	          axi_rvalid <= 1'b1;
	          axi_rresp  <= 2'b0; // 'OKAY' response
	        end   
	      else if (axi_rvalid && S_AXI_RREADY)
	        begin
	          // Read data is accepted by the master
	          axi_rvalid <= 1'b0;
	        end                
	    end
	end    

	// Implement memory mapped register select and read logic generation
	// Slave register read enable is asserted when valid address is available
	// and the slave is ready to accept the read address.
	assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
	always @(*)
	begin
	      // Address decoding for reading registers
	      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
	        6'h00   : reg_data_out <= slv_reg0;
	        6'h01   : reg_data_out <= slv_reg1;
	        6'h02   : reg_data_out <= slv_reg2;
	        6'h03   : reg_data_out <= slv_reg3;
	        6'h04   : reg_data_out <= slv_reg4;
	        6'h05   : reg_data_out <= slv_reg5;
	        6'h06   : reg_data_out <= slv_reg6;
	        6'h07   : reg_data_out <= slv_reg7;
	        6'h08   : reg_data_out <= slv_reg8;
	        6'h09   : reg_data_out <= slv_reg9;
	        6'h0A   : reg_data_out <= slv_reg10;
	        6'h0B   : reg_data_out <= slv_reg11;
	        6'h0C   : reg_data_out <= slv_reg12;
	        6'h0D   : reg_data_out <= slv_reg13;
	        6'h0E   : reg_data_out <= slv_reg14;
	        6'h0F   : reg_data_out <= slv_reg15;
	        6'h10   : reg_data_out <= slv_reg16;
	        6'h11   : reg_data_out <= slv_reg17;
	        6'h12   : reg_data_out <= slv_reg18;
	        6'h13   : reg_data_out <= slv_reg19;
	        6'h14   : reg_data_out <= slv_reg20;
	        6'h15   : reg_data_out <= slv_reg21;
	        6'h16   : reg_data_out <= slv_reg22;
	        6'h17   : reg_data_out <= slv_reg23;
	        6'h18   : reg_data_out <= slv_reg24;
	        6'h19   : reg_data_out <= slv_reg25;
	        6'h1A   : reg_data_out <= slv_reg26;
	        6'h1B   : reg_data_out <= slv_reg27;
	        6'h1C   : reg_data_out <= slv_reg28;
	        6'h1D   : reg_data_out <= slv_reg29;
	        6'h1E   : reg_data_out <= slv_reg30;
	        6'h1F   : reg_data_out <= slv_reg31;
	        6'h20   : reg_data_out <= slv_reg32;
	        6'h21   : reg_data_out <= slv_reg33;
	        default : reg_data_out <= 0;
	      endcase
	end

	// Output register or memory read data
	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      axi_rdata  <= 0;
	    end 
	  else
	    begin    
	      // When there is a valid read address (S_AXI_ARVALID) with 
	      // acceptance of read address by the slave (axi_arready), 
	      // output the read dada 
	      if (slv_reg_rden)
	        begin
	          axi_rdata <= reg_data_out;     // register read data
	        end   
	    end
	end    

	// Add user logic here
	
	   
       
    //Register 0: Gates Register {30'b0, adsrf_start,adsra_start}
    //bit 0: adsra_start, generate a pulse to start the oscillator envelope generation
    wire adsra_start;
    assign adsra_start = slv_reg0[0];
    //bit 1: adsrf_start, generate a pulse to start the filter envelope generation
    wire adsrf_start;
    assign adsrf_start = slv_reg0[1];    
    
    //Register 1 :osc1_fccw
    //bits 29 to 0: osc1_fccw 30-bits Pitch, oscillator 1 frequency carrier ctrl word
    wire [29:0] osc1_fccw;
    assign osc1_fccw = slv_reg1[29:0];
    
    //Register 2 :osc1_focw
    //bits 29 to 0: osc1_focw 30-bits Detune, oscillator 1 frequency offset ctrl word
    wire [29:0] osc1_focw;
    assign osc1_focw = slv_reg2[29:0];
    
    //Register 3: osc1_pho:
    //bits 29 to 0: osc1_pho, Phase shift, oscillator 1 phase offset ctrl word
    wire [29:0] osc1_pho;
    assign osc1_pho = slv_reg3[29:0];
    
    //Register 4 :osc2_fccw:
    //bits 29 to 0: osc2_fccw 30-bits Pitch, oscillator 2 frequency carrier ctrl word
    wire [29:0] osc2_fccw;
    assign osc2_fccw = slv_reg4[29:0];
    
    //Register 5 :osc2_focw
    //bits 29 to 0: osc2_focw 30-bits Detune, oscillator 2 frequency offset ctrl word
    wire [29:0] osc2_focw;
    assign osc2_focw = slv_reg5[29:0];
    
    //Register 6: osc2_pho
    //bits 29 to 0: osc2_pho, Phase shift, oscillator 2 phase offset ctrl word
    wire [29:0] osc2_pho;
    assign osc2_pho = slv_reg6[29:0];
    
    //Register 7 :osc3_fccw
    //bits 29 to 0: osc3_fccw 30-bits Pitch, oscillator 3 frequency carrier ctrl word
    wire [29:0] osc3_fccw;
    assign osc3_fccw = slv_reg7[29:0];
    
    //Register 8 :osc3_focw
    //bits 29 to 0: osc3_focw 30-bits Detune, oscillator 3 frequency offset ctrl word
    wire [29:0] osc3_focw;
    assign osc3_focw = slv_reg8[29:0];
    
    //Register 9: osc3_pho
    //bits 29 to 0: osc3_pho, Phase shift, oscillator 3 phase offset ctrl word
    wire [29:0] osc3_pho;
    assign osc3_pho = slv_reg9[29:0];
    
    //Register 10 :lfoo_fccw
    //bits 29 to 0: lfoo_fccw 30-bits Pitch, LFO oscillator frequency carrier ctrl word
    wire [29:0] lfoo_fccw;
    assign lfoo_fccw = slv_reg10[29:0];
    
    //Register 11 :lfoo_focw
    //bits 29 to 0: lfoo_focw 30-bits Detune, LFO oscillator frequency offset ctrl word
    wire [29:0] lfoo_focw;
    assign lfoo_focw = slv_reg11[29:0];
    
    //Register 12: lfoo_pho
    //bits 29 to 0: lfoo_pho, Phase shift, LFO oscillator phase offset ctrl word
    wire [29:0] lfoo_pho;
    assign lfoo_pho = slv_reg12[29:0];
    
    //Register 13 :lfof_fccw
    //bits 29 to 0: lfof_fccw 30-bits Pitch, LFO filter frequency carrier ctrl word
    wire [29:0] lfof_fccw;
    assign lfof_fccw = slv_reg13[29:0];
    
    //Register 14 :lfof_focw
    //bits 29 to 0: lfof_focw 30-bits Detune, LFO filter frequency offset ctrl word
    wire [29:0] lfof_focw;
    assign lfof_focw = slv_reg14[29:0];
    
    //Register 15: lfof_pho
    //bits 29 to 0: lfof_pho, Phase shift, LFO filter phase offset ctrl word
    wire [29:0] lfof_pho;
    assign lfof_pho = slv_reg15[29:0];
    
    //Register 16: adsra_a, Amplifier ADSR configuration attack step
    //bits 31 to 0: adsra_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
    wire [31:0] adsra_a;
    assign adsra_a = slv_reg16[31:0];
    
    //Register 17: adsra_d, Amplifier ADSR configuration decay step
    //bits 31 to 0: adsra_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
    wire [31:0] adsra_d;
    assign adsra_d = slv_reg17[31:0];
    
    //Register 18: adsra_sl, Amplifier ADSR configuration sustain level
    //bits 31 to 0: adsra_sl, amplitude for the sustain segment
    wire [31:0] adsra_sl;
    assign adsra_sl = slv_reg18[31:0];
    
    //Register 19: adsra_r, Amplifier ADSR configuration, steps for the release segment
    //bits 31 to 0: adsra_r, precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
    wire [31:0] adsra_r;
    assign adsra_r = slv_reg19[31:0];
    
    //Register 20: adsra_st, Amplifier ADSR configuration, time for the sustain segment
    //bits 31 to 0: adsra_st, tsustain / t_sys steps for the sustain
    wire [31:0] adsra_st;
    assign adsra_st = slv_reg20[31:0];
    
    //Register 21: adsrf_a, Filter ADSR configuration attack step
    //bits 31 to 0: adsrf_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
    wire [31:0] adsrf_a;
    assign adsrf_a = slv_reg21[31:0];
    
    //Register 22: adsrf_d, Filter ADSR configuration decay step
    //bits 31 to 0: adsrf_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
    wire [31:0] adsrf_d;
    assign adsrf_d = slv_reg22[31:0];
    
    //Register 23: adsrf_sl, Filter ADSR configuration sustain level
    //bits 31 to 0: adsrf_sl, amplitude for the sustain segment
    wire [31:0] adsrf_sl;
    assign adsrf_sl = slv_reg23[31:0];
    
    //Register 24: adsrf_r, Filter ADSR configuration, steps for the release segment
    //bits 31 to 0: adsrf_r, precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
    wire [31:0] adsrf_r;
    assign adsrf_r = slv_reg24[31:0];
    
    //Register 25: adsrf_st, Filter ADSR configuration, time for the sustain segment
    //bits 31 to 0: adsrf_st, tsustain / t_sys steps for the sustain
    wire [31:0] adsrf_st;
    assign adsrf_st = slv_reg25[31:0];
    
    //Register 26: fcut filter cutoff frequency
    //bits 31 to 0: fcut, filter cutoff frequency
    wire [31:0] fcut;
    assign fcut = slv_reg26[31:0];
    
    //Register 27: fres, filter resonance level
    //bits 31 to 0: fres, filter resonance level
    wire [31:0] fres;
    assign fres = slv_reg27[31:0];
    
    //Register 28: feg, filter envelope generator amount
    //bits 31 to 0: feg, filter envelope generator amount
    wire [31:0] feg;
    assign feg = slv_reg28[31:0];
    
    //Register 29: wave types:
    //bits 2 to 0: osc1_wt, oscillator 1 wave type
    wire [2:0] osc1_wt;
    assign osc1_wt = slv_reg29[2:0];
    
    //bits 5 to 3: osc2_wt, oscillator 2 wave type
    wire [2:0] osc2_wt;
    assign osc2_wt = slv_reg29[5:3];
    
    //bits 8 to 6: osc3_wt, oscillator 3 wave type
    wire [2:0] osc3_wt;
    assign osc3_wt = slv_reg29[8:6];
    
    //bits 11 to 9: lfoo_wt, LFO oscillator wave type
    wire [2:0] lfoo_wt;
    assign lfoo_wt = slv_reg29[11:9];
    
    //bits 14 to 12: lfof_wt, LFO filter wave type
    wire [2:0] lfof_wt;
    assign lfof_wt = slv_reg29[14:12];
    
    //Register 30: Mix levels osc1 & osc2
    //bits 15 to 0: mix_lvl_osc1, oscillator 1 volume level
    wire [15:0] mix_lvl_osc1;
    assign mix_lvl_osc1 = slv_reg30[15:0];
    
    //bits 31 to 16: mix_lvl_osc2, oscillator 2 volume level
    wire [15:0] mix_lvl_osc2;
    assign mix_lvl_osc2 = slv_reg30[31:16];
    
    //Register 31: Mix levels osc3 & mic
    //bits 15 to 0: mix_lvl_osc3, oscillator 3 volume level
    wire [15:0] mix_lvl_osc3;
    assign mix_lvl_osc3 = slv_reg31[15:0];
    
    //bits 31 to 16: mix_lvl_mic, mic volume level
    wire [15:0] mix_lvl_mic;
    assign mix_lvl_mic = slv_reg31[31:16];
    
    //Register 32: LFO amounts configurations    
    //bits 15 to 0: lfoo_lvl, Sets the maximum amount the LFO moves the Oscillators pitch up and down, up to +/- 1 octave.
    wire [15:0] lfoo_lvl;
    assign lfoo_lvl = slv_reg32[15:0];
    
    //bits 31 to 16: lfof_lvl, Sets the maximum amount the LFO moves the Filter Cutoff up and down, up to +/- 5 octaves.
    wire [15:0] lfof_lvl;
    assign lfof_lvl = slv_reg32[31:16];
    
    //Read Registers
    //Register 33: idle status and amplifier envelope amplitude data
        
    //bits 31 to 16: 16 bit envelope data
    wire [15:0] adsra_amp;
    //bit 0: idle status
    wire adsra_idle;
    
    assign slv_reg33 = {adsra_amp, 15'b0, adsra_idle};
    
    assign osc1Freq = slv_reg1;
    
    // instantiate synth module
    
    synth_module synth_module_1 (
     .clk(S_AXI_ACLK),
     .reset(~S_AXI_ARESETN), // S_AXI_ARESETN is active low
     .osc1_fccw(slv_reg1), // Pitch(), oscillator 1 frequency carrier ctrl word
     .osc1_focw(osc1_focw), // Detune(), oscillator 1 frequency offset ctrl word
     .osc1_pho(osc1_pho), // Phase shift(), oscillator 1 phase offset ctrl word
     .osc1_wt(osc1_wt), // oscillator 1 wave type
     .mix_lvl_osc1(mix_lvl_osc1), // oscillator 1 volume level
     .osc2_fccw(slv_reg4), // Pitch(), oscillator 1 frequency carrier ctrl word
     .osc2_focw(osc2_focw), // Detune(), oscillator 1 frequency offset ctrl word
     .osc2_pho(osc2_pho), // Phase shift(), oscillator 1 phase offset ctrl word
     .osc2_wt(osc2_wt), // oscillator 2 wave type
     .mix_lvl_osc2(mix_lvl_osc2), // oscillator 2 volume level
     .osc3_fccw(osc3_fccw), // Pitch(), oscillator 1 frequency carrier ctrl word
     .osc3_focw(osc3_focw), // Detune(), oscillator 1 frequency offset ctrl word
     .osc3_pho(osc3_pho), // Phase shift(), oscillator 1 phase offset ctrl word
     .osc3_wt(osc3_wt), // oscillator 3 wave type
     .mix_lvl_osc3(mix_lvl_osc3), // oscillator 3 volume level
     .lfoo_fccw(lfoo_fccw), // LFO oscillator frequency carrier ctrl word
     .lfoo_focw(lfoo_focw), // LFO oscillator frequency offset ctrl word
     .lfoo_pho(lfoo_pho), // LFO oscillator phase offset ctrl word
     .lfoo_wt(lfoo_wt), // LFO oscillator wave type
     .lfoo_lvl(lfoo_lvl), // Sets the maximum amount the LFO moves the VCOs pitch up and down(), up to +/- 1 octave.
     .lfof_fccw(lfof_fccw), // LFO filter frequency carrier ctrl word
     .lfof_focw(lfof_focw), // LFO filter frequency offset ctrl word
     .lfof_pho(lfof_pho), // LFO filter phase offset ctrl word
     .lfof_wt(lfof_wt), // LFO filter wave type
     .lfof_lvl(lfof_lvl), // Sets the maximum amount the LFO moves the Filter Cutoff up and down(), up to +/- 5 octaves.
     .adsra_start(adsra_start), // generate a pulse to start the envelope generation
     .adsra_a(adsra_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
     .adsra_d(adsra_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
     .adsra_sl(adsra_sl), // amplitude for the sustain segment
     .adsra_r(adsra_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
     .adsra_st(adsra_st), // tsustain / t_sys steps for the sustain
     .adsrf_start(adsrf_start), // generate a pulse to start the envelope generation
     .adsrf_a(adsrf_a), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
     .adsrf_d(adsrf_d),  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
     .adsrf_sl(adsrf_sl), // amplitude for the sustain segment
     .adsrf_r(adsrf_r),  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
     .adsrf_st(adsrf_st), // tsustain / t_sys steps for the sustain
     .fcut(fcut), // filter cutoff frequency
     .fres(fres), // filter resonance level
     .feg(feg), // filter envelope generator amount
     .mic_pdm_input(mic_pdm_input), // Input external P1-bit DM MiniZed Signal AUDIO_DAT
     .mix_lvl_mic(mix_lvl_mic), // oscillator 3 volume level
     .pdm_out(pdm_out), // 1-bit PDM sound output
     .mic_clk(micclk),  // generated microphone clock signal route to Minized external AUDIO_CLK
     .adsra_idle(adsra_idle), 
     .adsra_amp(adsra_amp)
    );




	// User logic ends

	endmodule

SynthModule IP Wrapper

`timescale 1 ns / 1 ps

	module SynthModule_v1_0 #
	(
		// Users to add parameters here

		// User parameters ends
		// Do not modify the parameters beyond this line


		// Parameters of Axi Slave Bus Interface S00_AXI
		parameter integer C_S00_AXI_DATA_WIDTH	= 32,
		parameter integer C_S00_AXI_ADDR_WIDTH	= 8
	)
	(
		// Users to add ports here

		// Input external P1-bit DM MiniZed Signal AUDIO_DAT
        input wire mic_pdm_input,
        
         // 1-bit PDM sound output 
        output wire pdm_out, // 1-bit PDM sound output
        
        // generated microphone clock signal route to Minized external AUDIO_CLK
        output wire micclk,  
        
        output wire [31:0] osc1Freq,
        
		// User ports ends
		// Do not modify the ports beyond this line


		// Ports of Axi Slave Bus Interface S00_AXI
		input wire  s00_axi_aclk,
		input wire  s00_axi_aresetn,
		input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,
		input wire [2 : 0] s00_axi_awprot,
		input wire  s00_axi_awvalid,
		output wire  s00_axi_awready,
		input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,
		input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,
		input wire  s00_axi_wvalid,
		output wire  s00_axi_wready,
		output wire [1 : 0] s00_axi_bresp,
		output wire  s00_axi_bvalid,
		input wire  s00_axi_bready,
		input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,
		input wire [2 : 0] s00_axi_arprot,
		input wire  s00_axi_arvalid,
		output wire  s00_axi_arready,
		output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,
		output wire [1 : 0] s00_axi_rresp,
		output wire  s00_axi_rvalid,
		input wire  s00_axi_rready
	);
// Instantiation of Axi Bus Interface S00_AXI
	SynthModule_v1_0_S00_AXI # ( 
		.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
		.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
	) SynthModule_v1_0_S00_AXI_inst (
	    .mic_pdm_input(mic_pdm_input),
	    .pdm_out(pdm_out),
	    .micclk(micclk),
	    .osc1Freq(osc1Freq),
		.S_AXI_ACLK(s00_axi_aclk),
		.S_AXI_ARESETN(s00_axi_aresetn),
		.S_AXI_AWADDR(s00_axi_awaddr),
		.S_AXI_AWPROT(s00_axi_awprot),
		.S_AXI_AWVALID(s00_axi_awvalid),
		.S_AXI_AWREADY(s00_axi_awready),
		.S_AXI_WDATA(s00_axi_wdata),
		.S_AXI_WSTRB(s00_axi_wstrb),
		.S_AXI_WVALID(s00_axi_wvalid),
		.S_AXI_WREADY(s00_axi_wready),
		.S_AXI_BRESP(s00_axi_bresp),
		.S_AXI_BVALID(s00_axi_bvalid),
		.S_AXI_BREADY(s00_axi_bready),
		.S_AXI_ARADDR(s00_axi_araddr),
		.S_AXI_ARPROT(s00_axi_arprot),
		.S_AXI_ARVALID(s00_axi_arvalid),
		.S_AXI_ARREADY(s00_axi_arready),
		.S_AXI_RDATA(s00_axi_rdata),
		.S_AXI_RRESP(s00_axi_rresp),
		.S_AXI_RVALID(s00_axi_rvalid),
		.S_AXI_RREADY(s00_axi_rready)
	);

	// Add user logic here

	// User logic ends

	endmodule

The following schematic is the result of the Vivado Elaborated Design after the RTL Analysis. 

image

To set or read the status of the synth module, the C software application writes or reads to the registers of the Synth module.

Register map

The different registers controlled by the C application are grouped to save MiniZed resources.

The processor interacts with the Synth Module as follows:

  • Generate (i.e., write) a pulse to start the amplifier envelope ADSR generation.
  • Generate (i.e., write) a pulse to start the filter envelope ADSR generation.
  • set (i.e., write) the value of the Oscillator 1 frequency carrier control word osc1_fccw
  • set (i.e., write) the value of the Oscillator 1 frequency offset control word osc1_focw
  • set (i.e., write) the value of the Oscillator 1 phase offset control word osc1_pho
  • set (i.e., write) the value of the Oscillator 2 frequency carrier control word osc2_fccw
  • set (i.e., write) the value of the Oscillator 2 frequency offset control word osc2_focw
  • set (i.e., write) the value of the Oscillator 3 phase offset control word osc2_pho
  • set (i.e., write) the value of the Oscillator 3 frequency carrier control word osc3_fccw
  • set (i.e., write) the value of the Oscillator 3 frequency offset control word osc3_focw
  • set (i.e., write) the value of the Oscillator 3 phase offset control word osc3_pho
  • set (i.e., write) the value of the Oscillators Low-Frequency Oscillator (LFO) frequency carrier control word lfoo_fccw
  • set (i.e., write) the value of the Oscillators Low-Frequency Oscillator (LFO) frequency offset control word lfoo_focw
  • set (i.e., write) the value of the Oscillators Low-Frequency Oscillator (LFO) phase offset control word lfoo_pho
  • set (i.e., write) the value of the Filter Low-Frequency Oscillator (LFO) frequency carrier control word lfof_fccw
  • set (i.e., write) the value of the Filter Low-Frequency Oscillator (LFO) frequency offset control word lfof_focw
  • set (i.e., write) the value of the Filter Low-Frequency Oscillator (LFO) phase offset control word lfof_pho
  • set (i.e., write) the value of the Amplifier ADSR envelope generator configuration attack step adsra_a
  • set (i.e., write) the value of the Amplifier ADSR envelope generator configuration decay step adsra_d
  • set (i.e., write) the value of the Amplifier ADSR envelope generator configuration sustain level adsra_sl
  • set (i.e., write) the value of the Amplifier ADSR envelope generator configuration, steps for the release segment, adsra_r
  • set (i.e., write) the value of the Amplifier ADSR envelope generator configuration, time for the sustain segment, adsra_st
  • set (i.e., write) the value of the Filter ADSR envelope generator configuration attack step adsrf_a
  • set (i.e., write) the value of the Filter ADSR envelope generator configuration decay step adsrf_d
  • set (i.e., write) the value of the Filter ADSR envelope generator configuration sustain level adsrf_sl
  • set (i.e., write) the value of the Filter ADSR envelope generator configuration, steps for the release segment, adsrf_r
  • set (i.e., write) the value of the Filter ADSR envelope generator configuration, time for the sustain segment, adsrf_st
  • set (i.e., write) the value of the Filter cutoff frequency, fcut
  • set (i.e., write) the value of the Filter resonance amount, fcres
  • set (i.e., write) the value of the Filter resonance amount, fcres
  • set (i.e., write) the value of the Filter Envelope Generator amount, fcres
  • set (i.e., write) the wave type for the Oscillator 1, osc1_wt
  • set (i.e., write) the wave type for the Oscillator 2, osc2_wt
  • set (i.e., write) the wave type for the Oscillator 3, osc3_wt
  • set (i.e., write) the wave type for the Oscillators Low-Frequency Oscillator (LFO), lfoo_wt
  • set (i.e., write) the wave type for the Filter Low-Frequency Oscillator (LFO), lfoo_wt
  • set (i.e., write) the mixer level, volume, for the Oscillator 1, mix_lvl_osc1
  • set (i.e., write) the mixer level, volume, for the Oscillator 2, mix_lvl_osc2
  • set (i.e., write) the mixer level, volume, for the Oscillator 3, mix_lvl_osc3
  • set (i.e., write) the mixer level, volume, for the Microphone input  mix_lvl_mic
  • set (i.e., write) the maximum amount the Oscillators LFO moves the Oscillators pitch up and down
  • set (i.e., write) sets the maximum amount the Filter LFO moves the Filter Cutoff up and down,
  • check (i.e., read) the idle status and retrieve the envelope amplitude data

Write registers

  • Register 0: Gates Register {30'b0, adsrf_start,adsra_start}
    • bit 0: adsra_start, generate a pulse to start the oscillator envelope generation
    • bit 1: adsrf_start, generate a pulse to start the filter envelope generation
  • Register 1:osc1_fccw
    • bits 29 to 0: osc1_fccw 30-bits Pitch, oscillator 1 frequency carrier ctrl word
  • Register 2 :osc1_focw
    • bits 29 to 0: osc1_focw 30-bits Detune, oscillator 1 frequency offset ctrl word
  • Register 3: osc1_pho:
    • bits 29 to 0: osc1_pho, Phase shift, oscillator 1 phase offset ctrl word
  • Register 4 :osc2_fccw:
    • bits 29 to 0: osc2_fccw 30-bits Pitch, oscillator 2 frequency carrier ctrl word
  • Register 5 :osc2_focw
    • bits 29 to 0: osc2_focw 30-bits Detune, oscillator 2 frequency offset ctrl word
  • Register 6: osc2_pho
    • bits 29 to 0: osc2_pho, Phase shift, oscillator 2 phase offset ctrl word
  • Register 7:osc3_fccw
    • bits 29 to 0: osc3_fccw 30-bits Pitch, oscillator 3 frequency carrier ctrl word
  • Register 8 :osc3_focw
    • bits 29 to 0: osc3_focw 30-bits Detune, oscillator 3 frequency offset ctrl word
  • Register 9: osc3_pho
    • bits 29 to 0: osc3_pho, Phase shift, oscillator 3 phase offset ctrl word
  • Register 10:lfoo_fccw
    • bits 29 to 0: lfoo_fccw 30-bits Pitch, LFO oscillator frequency carrier ctrl word
  • Register 11 :lfoo_focw
    • bits 29 to 0: lfoo_focw 30-bits Detune, LFO oscillator frequency offset ctrl word
  • Register 12: lfoo_pho
    • bits 29 to 0: lfoo_pho, Phase shift, LFO oscillator phase offset ctrl word
  • Register 13:lfof_fccw
    • bits 29 to 0: lfof_fccw 30-bits Pitch, LFO filter frequency carrier ctrl word
  • Register 14:lfof_focw
    • bits 29 to 0: lfof_focw 30-bits Detune, LFO filter frequency offset ctrl word
  • Register 15: lfof_pho
    • bits 29 to 0: lfof_pho, Phase shift, LFO filter phase offset ctrl word
  • Register 16: adsra_a, Amplifier ADSR configuration attack step
    • bits 31 to 0: adsra_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
  • Register 17: adsra_d, Amplifier ADSR configuration decay step
    • bits 31 to 0: adsra_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
  • Register 18: adsra_sl, Amplifier ADSR configuration sustain level
    • bits 31 to 0: adsra_sl, amplitude for the sustain segment
  • Register 19: adsra_r, Amplifier ADSR configuration, steps for the release segment
    • bits 31 to 0: adsra_r, precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
  • Register 20: adsra_st, Amplifier ADSR configuration, time for the sustain segment
    • bits 31 to 0: adsra_st, tsustain / t_sys steps for the sustain
  • Register 21: adsrf_a, Filter ADSR configuration attack step
    • bits 31 to 0: adsrf_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
  • Register 22: adsrf_d, Filter ADSR configuration decay step
    • bits 31 to 0: adsrf_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
  • Register 23: adsrf_sl, Filter ADSR configuration sustain level
    • bits 31 to 0: adsrf_sl, amplitude for the sustain segment
  • Register 24: adsrf_r, Filter ADSR configuration, steps for the release segment
    • bits 31 to 0: adsrf_r, precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
  • Register 25: adsrf_st, Filter ADSR configuration, time for the sustain segment
    • bits 31 to 0: adsrf_st, tsustain / t_sys steps for the sustain
  • Register 26: fcut filter cutoff frequency
    • bits 31 to 0: fcut, filter cutoff frequency
  • Register 27: fres, filter resonance level
    • bits 31 to 0: fres, filter resonance level
  • Register 28: feg, filter envelope generator amount
    • bits 31 to 0: feg, filter envelope generator amount
  • Register 29: wave types:
    • bits 2 to 0: osc1_wt, oscillator 1 wave type
    • bits 5 to 3: osc2_wt, oscillator 2 wave type
    • bits 8 to 6: osc3_wt, oscillator 3 wave type
    • bits 11 to 9: lfoo_wt, LFO oscillator wave type
    • bits 14 to 12: lfof_wt, LFO filter wave type
  • Register 30: Mix levels osc1 & osc2
    • bits 15 to 0: mix_lvl_osc1, oscillator 1 volume level
    • bits 31 to 16: mix_lvl_osc2, oscillator 2 volume level
  • Register 31: Mix levels osc3 & mic
    • bits 15 to 0: mix_lvl_osc3, oscillator 3 volume level
    • bits 31 to 16: mix_lvl_mic, mic volume level
  • Register 32: LFO amounts configurations
    • bits 15 to 0: lfoo_lvl, Sets the maximum amount the LFO moves the oscillator pitch up and down, up to +/- 1 octave.
    • bits 31 to 16: lfof_lvl, Sets the maximum amount the LFO moves the Filter Cutoff up and down, up to +/- 5 octaves.

Read Registers

  • Register 33: idle status and amplifier envelope amplitude data
    • bits 31 to 16: 16-bit envelope data
    • bit 0: idle status

Creation of a new AXI4 Peripheral IP

Based on the previous configuration a a new AXI4 Peripheral IP is created with the Vivado IP Packager

{gallery}New AXI4 Peripheral

image

Create and package new IP: Create a new AXI4 Peripheral

image

Add Interfaces: Configure 34 registers

User ports

  • Input external P1-bit DM MiniZed Signal AUDIO_DAT
  • 1-bit PDM sound output
  • generated microphone clock signal route to Minized external AUDIO_CLK

image

All the files used by the Synth Module IP are added to the project and modified if needed.

image

IP project files

File Type
hdl/SynthModule_v1_0_S00_AXI.v verilogSource
src/adsr.sv systemVerilogSource
src/amplifier.sv systemVerilogSource
src/ddfs.sv systemVerilogSource
src/ds_1bit_dac.sv systemVerilogSource
src/filter.sv systemVerilogSource
src/lfsr.sv systemVerilogSource
src/mixer.sv systemVerilogSource
src/pdm_clk_gen.sv systemVerilogSource
src/pdm_microphone.sv systemVerilogSource
src/saw_tooth.sv systemVerilogSource
src/sin_rom.sv systemVerilogSource
src/square_wave.sv systemVerilogSource
src/synth_module.sv systemVerilogSource
src/triangle_wave.sv systemVerilogSource
src/sin_table.mem mem
hdl/SynthModule_v1_0.v verilogSource

Standalone C Driver 

To facilitate communication with the Synthesizer Module, a C driver has been developed so that the C application does not have to worry about the implementation details of the modules in HDL.

The drivers are also included in the IP module project so that they are included in the .xsa file that Vivado will export when we export the hardware definition for use with the VITIS integrated development environment (IDE).

Driver header file

#ifndef SYNTHMODULE1_H
#define SYNTHMODULE1_H


/****************** Include Files ********************/
#include <stdio.h>
#include "xil_types.h"
#include "xstatus.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xil_io.h"


#define PHA_WIDTH 30 // bits in DDFS phase register
#define SYS_CLK_FREQ 100
// MAX is 0x80000000
#define SYNTHMODULE1_MAX            0x7fffffff
#define SYNTHMODULE1_BYPASS_PATTERN 0xffffffff
#define SYNTHMODULE1_STOP_PATTERN   0x00000000

#define SINE     0x00
#define SQUARE   0x01
#define SAW      0x02
#define TRIANGLE 0x03
#define NOISE    0x04

#define SYNTHMODULE_S00_AXI_SLV_REG0_OFFSET 0
#define SYNTHMODULE_S00_AXI_SLV_REG1_OFFSET 4
#define SYNTHMODULE_S00_AXI_SLV_REG2_OFFSET 8
#define SYNTHMODULE_S00_AXI_SLV_REG3_OFFSET 12
#define SYNTHMODULE_S00_AXI_SLV_REG4_OFFSET 16
#define SYNTHMODULE_S00_AXI_SLV_REG5_OFFSET 20
#define SYNTHMODULE_S00_AXI_SLV_REG6_OFFSET 24
#define SYNTHMODULE_S00_AXI_SLV_REG7_OFFSET 28
#define SYNTHMODULE_S00_AXI_SLV_REG8_OFFSET 32
#define SYNTHMODULE_S00_AXI_SLV_REG9_OFFSET 36
#define SYNTHMODULE_S00_AXI_SLV_REG10_OFFSET 40
#define SYNTHMODULE_S00_AXI_SLV_REG11_OFFSET 44
#define SYNTHMODULE_S00_AXI_SLV_REG12_OFFSET 48
#define SYNTHMODULE_S00_AXI_SLV_REG13_OFFSET 52
#define SYNTHMODULE_S00_AXI_SLV_REG14_OFFSET 56
#define SYNTHMODULE_S00_AXI_SLV_REG15_OFFSET 60
#define SYNTHMODULE_S00_AXI_SLV_REG16_OFFSET 64
#define SYNTHMODULE_S00_AXI_SLV_REG17_OFFSET 68
#define SYNTHMODULE_S00_AXI_SLV_REG18_OFFSET 72
#define SYNTHMODULE_S00_AXI_SLV_REG19_OFFSET 76
#define SYNTHMODULE_S00_AXI_SLV_REG20_OFFSET 80
#define SYNTHMODULE_S00_AXI_SLV_REG21_OFFSET 84
#define SYNTHMODULE_S00_AXI_SLV_REG22_OFFSET 88
#define SYNTHMODULE_S00_AXI_SLV_REG23_OFFSET 92
#define SYNTHMODULE_S00_AXI_SLV_REG24_OFFSET 96
#define SYNTHMODULE_S00_AXI_SLV_REG25_OFFSET 100
#define SYNTHMODULE_S00_AXI_SLV_REG26_OFFSET 104
#define SYNTHMODULE_S00_AXI_SLV_REG27_OFFSET 108
#define SYNTHMODULE_S00_AXI_SLV_REG28_OFFSET 112
#define SYNTHMODULE_S00_AXI_SLV_REG29_OFFSET 116
#define SYNTHMODULE_S00_AXI_SLV_REG30_OFFSET 120
#define SYNTHMODULE_S00_AXI_SLV_REG31_OFFSET 124
#define SYNTHMODULE_S00_AXI_SLV_REG32_OFFSET 128
#define SYNTHMODULE_S00_AXI_SLV_REG33_OFFSET 132

/**
* Register 0: Gates Register {30'b0, adsrf_start,adsra_start}
* bit 0: adsra_start, generate a pulse to start the oscillator envelope generation
* bit 1: adsrf_start, generate a pulse to start the filter envelope generation
*/
#define ADSR_START_GATES_OFFSET  SYNTHMODULE_S00_AXI_SLV_REG0_OFFSET

/**
* Register 1 :osc1_fccw
* bits 29 to 0: osc1_fccw 30-bits Pitch, oscillator 1 frequency carrier ctrl word
*/
#define OSC1_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG1_OFFSET

/**
* Register 2 :osc1_focw
* bits 29 to 0: osc1_focw 30-bits Detune, oscillator 1 frequency offset ctrl word
*/
#define OSC1_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG2_OFFSET

/**
* Register 3: osc1_pho:
* bits 29 to 0: osc1_pho, Phase shift, oscillator 1 phase offset ctrl word
*/
#define OSC1_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG3_OFFSET

/**
* Register 4 :osc2_fccw:
* bits 29 to 0: osc2_fccw 30-bits Pitch, oscillator 2 frequency carrier ctrl word
*/
#define OSC2_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG4_OFFSET

/**
* Register 5 :osc2_focw
* bits 29 to 0: osc2_focw 30-bits Detune, oscillator 2 frequency offset ctrl word
*/
#define OSC2_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG5_OFFSET

/**
* Register 6: osc2_pho
* bits 29 to 0: osc2_pho, Phase shift, oscillator 2 phase offset ctrl word
*/
#define OSC2_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG6_OFFSET

/**
* Register 7 :osc3_fccw
* bits 29 to 0: osc3_fccw 30-bits Pitch, oscillator 3 frequency carrier ctrl word
*/
#define OSC3_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG7_OFFSET

/**
* Register 8 :osc3_focw
* bits 29 to 0: osc3_focw 30-bits Detune, oscillator 3 frequency offset ctrl word
*/
#define OSC3_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG8_OFFSET

/**
* Register 9: osc3_pho
* bits 29 to 0: osc3_pho, Phase shift, oscillator 3 phase offset ctrl word
*/
#define OSC3_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG9_OFFSET

/**
* Register 10 :lfoo_fccw
* bits 29 to 0: lfoo_fccw 30-bits Pitch, LFO oscillator frequency carrier ctrl word
*/
#define LFOO_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG10_OFFSET

/**
* Register 11 :lfoo_focw
* bits 29 to 0: lfoo_focw 30-bits Detune, LFO oscillator frequency offset ctrl word
*/
#define LFOO_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG11_OFFSET

/**
* Register 12: lfoo_pho
* bits 29 to 0: lfoo_pho, Phase shift, LFO oscillator phase offset ctrl word
*/
#define LFOO_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG12_OFFSET

/**
* Register 13 :lfof_fccw
* bits 29 to 0: lfof_fccw 30-bits Pitch, LFO filter frequency carrier ctrl word
*/
#define LFOF_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG13_OFFSET

/**
* Register 14 :lfof_focw
* bits 29 to 0: lfof_focw 30-bits Detune, LFO filter frequency offset ctrl word
*/
#define LFOF_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG14_OFFSET

/**
* Register 15: lfof_pho
* bits 29 to 0: lfof_pho, Phase shift, LFO filter phase offset ctrl word
*/
#define LFOF_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG15_OFFSET

/**
* Register 16: adsra_a, Amplifier ADSR configuration attack step
* bits 31 to 0: adsra_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
*/
#define ADSRA_A_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG16_OFFSET

/**
* Register 17: adsra_d, Amplifier ADSR configuration decay step
* bits 31 to 0: adsra_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
*/
#define ADSRA_D_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG17_OFFSET

/**
* Register 18: adsra_sl, Amplifier ADSR configuration sustain level
* bits 31 to 0: adsra_sl, amplitude for the sustain segment
*/
#define ADSRA_SL_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG18_OFFSET

/**
* Register 19: adsra_r, Amplifier ADSR configuration, steps for the release segment
* bits 31 to 0: adsra_r, precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
*/
#define ADSRA_R_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG19_OFFSET

/**
* Register 20: adsra_st, Amplifier ADSR configuration, time for the sustain segment
* bits 31 to 0: adsra_st, tsustain / t_sys steps for the sustain
*/
#define ADSRA_ST_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG20_OFFSET

/**
* Register 21: adsrf_a, Filter ADSR configuration attack step
* bits 31 to 0: adsrf_a, precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
*/
#define ADSRF_A_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG21_OFFSET

/**
* Register 22: adsrf_d, Filter ADSR configuration decay step
* bits 31 to 0: adsrf_d, precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
*/
#define ADSRF_D_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG22_OFFSET

/**
* Register 23: adsrf_sl, Filter ADSR configuration sustain level
* bits 31 to 0: adsrf_sl, amplitude for the sustain segment
*/
#define ADSRF_SL_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG23_OFFSET

/**
* Register 24: adsrf_r, Filter ADSR configuration, steps for the release segment
* bits 31 to 0: adsrf_r, precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
*/
#define ADSRF_R_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG24_OFFSET

/**
* Register 25: adsrf_st, Filter ADSR configuration, time for the sustain segment
* bits 31 to 0: adsrf_st, tsustain / t_sys steps for the sustain
*/
#define ADSRF_ST_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG25_OFFSET

/**
* Register 26: fcut filter cutoff frequency
* bits 31 to 0: fcut, filter cutoff frequency
*/
#define FCUT_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG26_OFFSET

/**
* Register 27: fres, filter resonance level
* bits 31 to 0: fres, filter resonance level
*/
#define FRES_OFFSET 	         SYNTHMODULE_S00_AXI_SLV_REG27_OFFSET

/**
* Register 28: feg, filter envelope generator amount
* bits 31 to 0: feg, filter envelope generator amount
*/
#define FEG_OFFSET 	             SYNTHMODULE_S00_AXI_SLV_REG28_OFFSET

/**
* Register 29: wave_types:
* bits 2 to 0: osc1_wt, oscillator 1 wave type
* bits 5 to 3: osc2_wt, oscillator 2 wave type
* bits 8 to 6: osc3_wt, oscillator 3 wave type
* bits 11 to 9: lfoo_wt, LFO oscillator wave type
* bits 14 to 12: lfof_wt, LFO filter wave type
*/
#define WAVE_TYPES_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG29_OFFSET


/**
* Register 30: Mix_levels_osc1_osc2
* bits 15 to 0: mix_lvl_osc1, oscillator 1 volume level
* bits 31 to 16: mix_lvl_osc2, oscillator 2 volume level
*/
#define MIX_LVL_OSC1_OSC2_OFFSET SYNTHMODULE_S00_AXI_SLV_REG30_OFFSET

/**
* Register 31: Mix_levels_osc3_mic
* bits 15 to 0: mix_lvl_osc3, oscillator 3 volume level
* bits 31 to 16: mix_lvl_mic, mic volume level
*/
#define MIX_LVL_OSC3_MIC_OFFSET  SYNTHMODULE_S00_AXI_SLV_REG31_OFFSET

/**
* Register 32: LFO amounts configurations
* bits 15 to 0: lfoo_lvl, Sets the maximum amount the LFO moves the Oscillators pitch up and down, up to +/- 1 octave.
* bits 31 to 16: lfof_lvl, Sets the maximum amount the LFO moves the Filter Cutoff up and down, up to +/- 5 octaves.
*/
#define LFO_AMOUNTS_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG32_OFFSET

/**
* Read Registers
* Register 33: idle status and amplifier envelope amplitude data
* bits 31 to 16: 16 bit envelope data
* bit 0: idle status
*/
#define AMP_ADSR_STATUS_OFFSET 	 SYNTHMODULE_S00_AXI_SLV_REG33_OFFSET



/**
* Register 1 :osc1_fccw
* bits 29 to 0: osc1_fccw 30-bits Pitch, oscillator 1 frequency carrier ctrl word
*/
#define OSC1_FCCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG1_OFFSET

/**
* Register 2 :osc1_focw
* bits 29 to 0: osc1_focw 30-bits Detune, oscillator 1 frequency offset ctrl word
*/
#define OSC1_FOCW_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG2_OFFSET

/**
* Register 3: osc1_pho:
* bits 29 to 0: osc1_pho, Phase shift, oscillator 1 phase offset ctrl word
*/
#define OSC1_PHO_OFFSET 	     SYNTHMODULE_S00_AXI_SLV_REG3_OFFSET


typedef struct OscillatorConfig {
   u32 fccwOffset;
   int fccw;  // Pitch, oscillator frequency carrier ctrl word

   u32 focwOffset;
   int focw;  // Detune, oscillator frequency offset ctrl word

   u32 phoOffset;
   int pho;  // Phase shift, oscillator phase offset ctrl word

   u32 waveTypeOffset; //
   u8  waveTypeIndex;
   u8  waveType;

   u32 amplitudeOffset;
   u8  amplitudeIndex;
   // mix level
   float amplitude;
} OscillatorConfig;

typedef struct AdsrConfig {
	u32 atackStepsOffset;
	u32 ams;

	u32 decayStepsOffset;
	u32 dms;

	u32 sustainLevelOffset;
	float slevel;

	u32 sustainTimeOffset;
	u32 sms;

	u32 releaseStepsOffset;
	u32 rms;

	u32 gateOffset;

	u32 statusOffset;
	u32 status;
} AdsrConfig;

typedef struct MicConfig {
	u32 micLevelOffset;
	u32 amplitudeIndex;
	float amplitude;
} MicConfig;

typedef struct Synth{
	u32 			 baseAddr;
	OscillatorConfig osc_1;
	OscillatorConfig osc_2;
	OscillatorConfig osc_3;
	OscillatorConfig osc_lfo;
	OscillatorConfig filter_lfo;
    MicConfig        mic;
	AdsrConfig       amplifier_adsr;
	AdsrConfig       filter_adsr;
} Synth;



/**************************** Type Definitions *****************************/
/**
 *
 * Write a value to a SYNTHMODULE register. A 32 bit write is performed.
 * If the component is implemented in a smaller width, only the least
 * significant data is written.
 *
 * @param   BaseAddress is the base address of the SYNTHMODULEdevice.
 * @param   RegOffset is the register offset from the base to write to.
 * @param   Data is the data written to the register.
 *
 * @return  None.
 *
 * @note
 * C-style signature:
 * 	void SYNTHMODULE_mWriteReg(u32 BaseAddress, unsigned RegOffset, u32 Data)
 *
 */
#define SYNTHMODULE_mWriteReg(BaseAddress, RegOffset, Data) \
  	Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))

/**
 *
 * Read a value from a SYNTHMODULE register. A 32 bit read is performed.
 * If the component is implemented in a smaller width, only the least
 * significant data is read from the register. The most significant data
 * will be read as 0.
 *
 * @param   BaseAddress is the base address of the SYNTHMODULE device.
 * @param   RegOffset is the register offset from the base to write to.
 *
 * @return  Data is the data from the register.
 *
 * @note
 * C-style signature:
 * 	u32 SYNTHMODULE_mReadReg(u32 BaseAddress, unsigned RegOffset)
 *
 */
#define SYNTHMODULE_mReadReg(BaseAddress, RegOffset) \
    Xil_In32((BaseAddress) + (RegOffset))




/************************** Function Prototypes ****************************/
/**
 *
 * Run a self-test on the driver/device. Note this may be a destructive test if
 * resets of the device are performed.
 *
 * If the hardware system is not built correctly, this function may never
 * return to the caller.
 *
 * @param   baseaddr_p is the base address of the SYNTHMODULE instance to be worked on.
 *
 * @return
 *
 *    - XST_SUCCESS   if all self-test code passed
 *    - XST_FAILURE   if any self-test code failed
 *
 * @note    Caching must be turned off for this function to work.
 * @note    Self test may fail if data memory and device are not on the same bus.
 *
 */
XStatus SYNTHMODULE1_Reg_SelfTest(u32 baseaddr_p);


void SYNTHMODULE1_init(Synth * synth, u32 baseAddr);

void SYNTHMODULE1_osc1_set_carrier_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc1_set_offset_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc1_set_phase_degree(Synth * synth, int phase);
void SYNTHMODULE1_osc1_set_amp(Synth * synth, float amp);
void SYNTHMODULE1_osc1_set_wave_type(Synth * synth, int waveType);

void SYNTHMODULE1_osc2_set_carrier_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc2_set_offset_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc2_set_phase_degree(Synth * synth, int phase);
void SYNTHMODULE1_osc2_set_amp(Synth * synth, float amp);
void SYNTHMODULE1_osc2_set_wave_type(Synth * synth, int waveType);

void SYNTHMODULE1_osc3_set_carrier_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc3_set_offset_freq(Synth * synth, int freq);
void SYNTHMODULE1_osc3_set_phase_degree(Synth * synth, int phase);
void SYNTHMODULE1_osc3_set_amp(Synth * synth, float amp);
void SYNTHMODULE1_osc3_set_wave_type(Synth * synth, int waveType);

void SYNTHMODULE1_lfoo_set_carrier_freq(Synth * synth, int freq);
void SYNTHMODULE1_lfoo_set_offset_freq(Synth * synth, int freq);
void SYNTHMODULE1_lfoo_set_phase_degree(Synth * synth, int phase);
void SYNTHMODULE1_lfoo_set_amp(Synth * synth, float amp);
void SYNTHMODULE1_lfoo_set_wave_type(Synth * synth, int waveType);

void SYNTHMODULE1_lfof_set_carrier_freq(Synth * synth, int freq);
void SYNTHMODULE1_lfof_set_offset_freq(Synth * synth, int freq);
void SYNTHMODULE1_lfof_set_phase_degree(Synth * synth, int phase);
void SYNTHMODULE1_lfof_set_amp(Synth * synth, float amp);
void SYNTHMODULE1_lfof_set_wave_type(Synth * synth, int waveType);



int SYNTHMODULE1_amplifier_adsr_idle(Synth * synth);
void SYNTHMODULE1_amplifier_adsr_start(Synth * synth);
void SYNTHMODULE1_amplifier_adsr_abort(Synth * synth);
void SYNTHMODULE1_amplifier_adsr_bypass(Synth * synth);
void SYNTHMODULE1_amplifier_adsr_set_env(Synth * synth, int attack_ms, int decay_ms, int sustain_ms, int release_ms, float sus_level);
void SYNTHMODULE1_amplifier_adsr_select_env(Synth * synth, int n);

int SYNTHMODULE1_filter_adsr_idle(Synth * synth);
void SYNTHMODULE1_filter_adsr_start(Synth * synth);
void SYNTHMODULE1_filter_adsr_abort(Synth * synth);
void SYNTHMODULE1_filter_adsr_bypass(Synth * synth);
void SYNTHMODULE1_filter_adsr_set_env(Synth * synth, int attack_ms, int decay_ms, int sustain_ms, int release_ms, float sus_level);
void SYNTHMODULE1_filter_adsr_select_env(Synth * synth, int n);

void SYNTHMODULE1_amplifier_adsr_write_reg(Synth * synth);

int SYNTHMODULE1_calc_note_freq(int octave, int noteIndex);
void SYNTHMODULE1_play_note(Synth * synth, int note, int octave, int duration);

u32 SYNTHMODULE1_calc_phase_control_word(int phase);

u32 SYNTHMODULE1_convert_amp_to_q214(float amp) ;

void SYNTHMODULE1_set_wave_types(Synth* synth);

void SYNTHMODULE1_set_mix_lvl_osc1_osc2(Synth* synth);


void SYNTHMODULE1_set_mix_lvl_osc3_mic(Synth* synth);

u32 SYNTHMODULE1_calc_lfo_freq_control_word(float freq);

void SYNTHMODULE1_mic_set_amp(Synth* synth, float amp);

void setBaseConfiguration(Synth * synth);


#endif // SYNTHMODULE1_H

Driver Implementation:

/***************************** Include Files *******************************/
#include "SynthModule1.h"

/************************** Function Definitions ***************************/

/************************** Constant Definitions ***************************/
#define READ_WRITE_MUL_FACTOR 0x10


/**
 *
 * Initialize the modulecalculates write and read register addresses
 * based on SynthModule base address. SYNTHMODULE1_init() must be call once
 * before any other API call
 *
 * @param   baseAddr is the base address of the SYNTHMODULE instance to be worked on.
 *
 * @return
 *
 *    - void
 *
 */
void SYNTHMODULE1_init(Synth * synth, u32 baseAddr) {

	synth->baseAddr = baseAddr;

	// calculate oscillator 1 write register addresses
   synth->osc_1.fccwOffset       =  OSC1_FCCW_OFFSET;
   synth->osc_1.focwOffset       =  OSC1_FOCW_OFFSET;
   synth->osc_1.phoOffset        =  OSC1_PHO_OFFSET;
   synth->osc_1.waveTypeOffset   =  WAVE_TYPES_OFFSET;
   synth->osc_1.waveTypeIndex  = 0;
   synth->osc_1.amplitudeOffset  =  MIX_LVL_OSC1_OSC2_OFFSET;
   synth->osc_1.amplitudeIndex = 0;

   // calculate oscillator 2 write register addresses
   synth->osc_2.fccwOffset       =  OSC2_FCCW_OFFSET;
   synth->osc_2.focwOffset       =  OSC2_FOCW_OFFSET;
   synth->osc_2.phoOffset        =  OSC2_PHO_OFFSET;
   synth->osc_2.waveTypeOffset   =  WAVE_TYPES_OFFSET;
   synth->osc_2.waveTypeIndex  = 1;
   synth->osc_2.amplitudeOffset  =  MIX_LVL_OSC1_OSC2_OFFSET;
   synth->osc_2.amplitudeIndex = 1;

   // calculate oscillator 3 write register addresses
   synth->osc_3.fccwOffset       =  OSC3_FCCW_OFFSET;
   synth->osc_3.focwOffset       =  OSC3_FOCW_OFFSET;
   synth->osc_3.phoOffset        =  OSC3_PHO_OFFSET;
   synth->osc_3.waveTypeOffset   =  WAVE_TYPES_OFFSET;
   synth->osc_3.waveTypeIndex  = 2;
   synth->osc_3.amplitudeOffset  =  MIX_LVL_OSC3_MIC_OFFSET;
   synth->osc_3.amplitudeIndex = 0;

   // calculate oscillators lfo write register addresses
   synth->osc_lfo.fccwOffset       =  LFOO_FCCW_OFFSET;
   synth->osc_lfo.focwOffset       =  LFOO_FOCW_OFFSET;
   synth->osc_lfo.phoOffset        =  LFOO_PHO_OFFSET;
   synth->osc_lfo.waveTypeOffset   =  WAVE_TYPES_OFFSET;
   synth->osc_lfo.waveTypeIndex  = 3;
   synth->osc_lfo.amplitudeOffset  =  LFO_AMOUNTS_OFFSET;
   synth->osc_lfo.amplitudeIndex = 0;

   // calculate filter lfo write register addresses
   synth->filter_lfo.fccwOffset       =  LFOF_FCCW_OFFSET;
   synth->filter_lfo.focwOffset       =  LFOF_FOCW_OFFSET;
   synth->filter_lfo.phoOffset        =  LFOF_PHO_OFFSET;
   synth->filter_lfo.waveTypeOffset   =  WAVE_TYPES_OFFSET;
   synth->filter_lfo.waveTypeIndex  = 4;
   synth->filter_lfo.amplitudeOffset  =  LFO_AMOUNTS_OFFSET;
   synth->filter_lfo.amplitudeIndex = 1;

   // calculate microphone write register addresses
   synth->mic.micLevelOffset          =  MIX_LVL_OSC3_MIC_OFFSET;
   synth->mic.amplitudeIndex        = 1;

   // calculate amplifier adsr write register addresses
   synth->amplifier_adsr.atackStepsOffset   =  ADSRA_A_OFFSET;
   synth->amplifier_adsr.decayStepsOffset   =  ADSRA_D_OFFSET;
   synth->amplifier_adsr.sustainLevelOffset =  ADSRA_SL_OFFSET;
   synth->amplifier_adsr.sustainTimeOffset  =  ADSRA_ST_OFFSET;
   synth->amplifier_adsr.releaseStepsOffset =  ADSRA_R_OFFSET;

   // calculate filter adsr write register addresses
   synth->filter_adsr.atackStepsOffset   =  ADSRF_A_OFFSET;
   synth->filter_adsr.decayStepsOffset   =  ADSRF_D_OFFSET;
   synth->filter_adsr.sustainLevelOffset =  ADSRF_SL_OFFSET;
   synth->filter_adsr.sustainTimeOffset  =  ADSRF_ST_OFFSET;
   synth->filter_adsr.releaseStepsOffset =  ADSRF_R_OFFSET;

   // calculate amplifier gate addresses
   synth->amplifier_adsr.gateOffset =  ADSR_START_GATES_OFFSET;

   // calculate amplifier adsr read register address
   synth->amplifier_adsr.statusOffset =  AMP_ADSR_STATUS_OFFSET;


}

/**
 * Configure System as:
 * 	- two main sawtooth wave oscillators
 * 	- two sinusoidal waves lfo
 * 	- 1 noise input
 * 	- 1 microphone input
 *
 *
 */
void setBaseConfiguration(Synth * synth) {

	SYNTHMODULE1_osc1_set_carrier_freq(synth, 262);
	SYNTHMODULE1_osc1_set_amp(synth, 0.9);
	SYNTHMODULE1_osc1_set_phase_degree(synth, 0);
	SYNTHMODULE1_osc1_set_wave_type(synth, SINE);

	SYNTHMODULE1_amplifier_adsr_bypass(synth);

	SYNTHMODULE1_osc2_set_carrier_freq(synth, 262);
	SYNTHMODULE1_osc2_set_wave_type(synth, SAW);

	SYNTHMODULE1_osc3_set_wave_type(synth, NOISE);
	SYNTHMODULE1_osc2_set_amp(synth, 0.0);
	SYNTHMODULE1_osc3_set_amp(synth, 0.0);
	SYNTHMODULE1_mic_set_amp(synth, 0.0);


	SYNTHMODULE1_lfoo_set_amp(synth, 0.0);
	SYNTHMODULE1_lfof_set_amp(synth, 0.0);

	SYNTHMODULE1_amplifier_adsr_set_env(synth, 100, 50, 100, 50, 0.9);

	SYNTHMODULE1_amplifier_adsr_start(synth);



}

/************************** Function Definitions ***************************/




/**************************  Support functions ***************************/

/**
 * Calculates frequency control word in steps
 * Only for audio frequencies above 10 Hz
 *
 * @param freq frequency in hertz
 */
u32 SYNTHMODULE1_calc_freq_control_word(int freq) {
	u32 fcw, p2n;
	float tmp;
	p2n = 1 << PHA_WIDTH;
	tmp = ((float) p2n) / (float) (SYS_CLK_FREQ * 1000000.0);
	fcw = (u32) (freq * tmp);
	return fcw;
}

/**
 * Calculate the frequency for lfo oscillators
 * lfo range is from 0.1Hz to 100Hz.
 *
 * @param freq frequency in hertz
 */
u32 SYNTHMODULE1_calc_lfo_freq_control_word(float freq) {
	u32 fcw, p2n;
	float tmp;
	p2n = 1 << PHA_WIDTH;
	tmp = ((float) p2n) / (float) (SYS_CLK_FREQ * 1000000.0);
	fcw = (u32) (freq * tmp);
	return fcw;
}

u32 SYNTHMODULE1_calc_phase_control_word(int phase) {
	u32 pha;
	pha = (SYS_CLK_FREQ * 1000000) * phase / 360;
	return pha;
}

u32 SYNTHMODULE1_convert_amp_to_q214(float amp) {
	// convert floating point to fixed-point Q2.14 format
	u32 q214;
	float max_amp;
	max_amp = (float) (0x4000); // amp * 2^15
	q214 = (u32) (amp * max_amp);

	return q214 & 0x0000ffff;
}

/**********************************************************
 * Setters
 **********************************************************/

/*********************************************************
 * Oscillator 1 setters
 *********************************************************/

void SYNTHMODULE1_osc1_set_carrier_freq(Synth * synth, int freq) {
	synth->osc_1.fccw = freq;
	SYNTHMODULE_mWriteReg (synth->baseAddr, synth->osc_1.fccwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}

void SYNTHMODULE1_osc1_set_offset_freq(Synth * synth, int freq) {
	synth->osc_1.focw = freq;
	SYNTHMODULE_mWriteReg (synth->baseAddr, synth->osc_1.focwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}

void SYNTHMODULE1_osc1_set_phase_degree(Synth * synth, int phase) {
	synth->osc_1.pho = phase;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_1.phoOffset, 	SYNTHMODULE1_calc_phase_control_word(phase));
}

void SYNTHMODULE1_osc1_set_amp(Synth * synth, float amp) {
	synth->osc_1.amplitude = amp;
	SYNTHMODULE1_set_mix_lvl_osc1_osc2(synth);
}

void SYNTHMODULE1_osc1_set_wave_type(Synth * synth, int waveType) {
	synth->osc_1.waveType = waveType;
	SYNTHMODULE1_set_wave_types( synth);
}

/*********************************************************
 * Oscillator 2 setters
 *********************************************************/

void SYNTHMODULE1_osc2_set_carrier_freq(Synth * synth, int freq){
	synth->osc_2.fccw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_2.fccwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_osc2_set_offset_freq(Synth * synth, int freq) {
	synth->osc_2.focw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_2.focwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_osc2_set_phase_degree(Synth * synth, int phase) {
	synth->osc_2.pho = phase;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_2.phoOffset, SYNTHMODULE1_calc_phase_control_word(phase));
}
void SYNTHMODULE1_osc2_set_amp(Synth * synth, float amp) {
	synth->osc_2.amplitude = amp;
	SYNTHMODULE1_set_mix_lvl_osc1_osc2(synth);
}

void SYNTHMODULE1_osc2_set_wave_type(Synth * synth, int waveType) {
	synth->osc_2.waveType = waveType;
	SYNTHMODULE1_set_wave_types( synth);
}


/*********************************************************
 * Oscillator 3 setters
 *********************************************************/

void SYNTHMODULE1_osc3_set_carrier_freq(Synth * synth, int freq) {
	synth->osc_3.fccw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_3.fccwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_osc3_set_offset_freq(Synth * synth, int freq) {
	synth->osc_3.focw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_3.focwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_osc3_set_phase_degree(Synth * synth, int phase) {
	synth->osc_3.pho = phase;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_3.phoOffset, 	SYNTHMODULE1_calc_phase_control_word(phase));
}
void SYNTHMODULE1_osc3_set_amp(Synth * synth, float amp) {
	synth->osc_3.amplitude = amp;
	SYNTHMODULE1_set_mix_lvl_osc3_mic(synth);
}

void SYNTHMODULE1_osc3_set_wave_type(Synth * synth, int waveType) {
	synth->osc_3.waveType = waveType;
	SYNTHMODULE1_set_wave_types(synth);
}

/*********************************************************
 * Oscillators LFO setters
 *********************************************************/

void SYNTHMODULE1_lfoo_set_carrier_freq(Synth * synth, int freq) {
	synth->osc_lfo.fccw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_lfo.fccwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_lfoo_set_offset_freq(Synth * synth, int freq) {
	synth->osc_lfo.focw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_lfo.focwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_lfoo_set_phase_degree(Synth * synth, int phase) {
	synth->osc_lfo.pho = phase;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_lfo.phoOffset, 	SYNTHMODULE1_calc_phase_control_word(phase));
}
void SYNTHMODULE1_lfoo_set_amp(Synth * synth, float amp) {
	synth->osc_lfo.amplitude = amp;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_lfo.amplitudeOffset, 	SYNTHMODULE1_convert_amp_to_q214(amp));
}

void SYNTHMODULE1_lfoo_set_wave_type(Synth * synth, int waveType) {
	synth->osc_lfo.waveType = waveType;
	SYNTHMODULE1_set_wave_types(synth);
}

/*********************************************************
 * Filter LFO setters
 *********************************************************/

void SYNTHMODULE1_lfof_set_carrier_freq(Synth * synth, int freq) {
	synth->filter_lfo.fccw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->filter_lfo.fccwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_lfof_set_offset_freq(Synth * synth, int freq) {
	synth->filter_lfo.focw = freq;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->filter_lfo.focwOffset, SYNTHMODULE1_calc_freq_control_word(freq));
}
void SYNTHMODULE1_lfof_set_phase_degree(Synth * synth, int phase) {
	synth->filter_lfo.pho = phase;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->filter_lfo.phoOffset, 	SYNTHMODULE1_calc_phase_control_word(phase));
}
void SYNTHMODULE1_lfof_set_amp(Synth * synth, float amp) {
	synth->filter_lfo.amplitude = amp;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->filter_lfo.amplitudeOffset, 	SYNTHMODULE1_convert_amp_to_q214(amp));
}

void SYNTHMODULE1_lfof_set_wave_type(Synth * synth, int waveType) {
	synth->filter_lfo.waveType = waveType;
	SYNTHMODULE1_set_wave_types(synth);
}



/*********************************************************
 * Wave type register LFO setters
 *********************************************************/

void SYNTHMODULE1_set_wave_types(Synth* synth) {
	u32 waveType;
	waveType = (synth->osc_1.waveType & 7)
			| (synth->osc_2.waveType & 7) << 3
			| (synth->osc_3.waveType & 7) << 6
			| (synth->osc_lfo.waveType & 7) << 9
			| (synth->filter_lfo.waveType & 7) << 12;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_1.waveTypeOffset, 	waveType);
}

/*********************************************************
 * Microphone input setters
 *********************************************************/

void SYNTHMODULE1_mic_set_amp(Synth * synth, float amp) {
	synth->mic.amplitude = amp;
	SYNTHMODULE1_set_mix_lvl_osc3_mic(synth);
}

/*********************************************************
 * Amplifier ADSR setters
 *********************************************************/

/**
 * Put amplifier ADSR to IDLE status
 *
 * @param   synth is the SynthModule configuration instance
 *
 * @return
 *
 *    - void
 *
 */
int SYNTHMODULE1_amplifier_adsr_idle(Synth * synth){
	return Xil_In32(synth->amplifier_adsr.statusOffset);
}

/**
 * Send an start signal to amplifier ADSR
 *
 * @param   synth is the SynthModule configuration instance
 *
 * @return
 *
 *    - void
 *
 */
void SYNTHMODULE1_amplifier_adsr_start(Synth * synth) {
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.gateOffset, (u32) 3);
}

/**
 * Send an stop signal to amplifier ADSR
 *
 * @param   synth is the SynthModule configuration instance
 *
 * @return
 *
 *    - void
 *
 */
void SYNTHMODULE1_amplifier_adsr_abort(Synth * synth) {
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.atackStepsOffset, (u32) SYNTHMODULE1_STOP_PATTERN);
}

/**
 * Bypass amplifier ADSR, output is not modulated
 *
 * @param   synth is the SynthModule configuration instance
 *
 * @return
 *
 *    - void
 *
 */
void SYNTHMODULE1_amplifier_adsr_bypass(Synth * synth) {
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.atackStepsOffset, (u32) SYNTHMODULE1_BYPASS_PATTERN);
}


/**
 * Configure amplifier ADSR
 *
 * @param   synth is the SynthModule configuration instance
 * @param   attack_ms attack time in milliseconds
 * @param   decay_ms decay time in milliseconds
 * @param   sustain_ms sustain time in milliseconds
 * @param   release_ms release time in milliseconds
 * @param   sus_level in percentage of MAX value
 *
 * @return
 *
 *    - void
 *
 */
void SYNTHMODULE1_amplifier_adsr_set_env(Synth * synth, int attack_ms, int decay_ms, int sustain_ms, int release_ms, float sus_level) {
	synth->amplifier_adsr.ams = attack_ms;
	synth->amplifier_adsr.dms = decay_ms;
	synth->amplifier_adsr.sms = sustain_ms;
	synth->amplifier_adsr.rms = release_ms;
	synth->amplifier_adsr.slevel = sus_level;
	SYNTHMODULE1_amplifier_adsr_write_reg(synth);
}

/**
 * Sets a preset Amplifier ADSR configuration
 */
void SYNTHMODULE1_amplifier_adsr_select_env(Synth * synth, int n) {

}

/**
 * upload amplifier ADSR configuration writing the ADSR registers
 */
void SYNTHMODULE1_amplifier_adsr_write_reg(Synth * synth) {
	u32 nc, step, sus_abs;
	// clocks per ms = 0.001 / (1 / ( SYS_CLK_FREQ * 1000000))
	const u32 clks = SYS_CLK_FREQ * 1000;

	if (synth->amplifier_adsr.ams == SYNTHMODULE1_BYPASS_PATTERN) {
		SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.atackStepsOffset, (u32) SYNTHMODULE1_BYPASS_PATTERN);
		return;
	}

	if (synth->amplifier_adsr.ams == SYNTHMODULE1_STOP_PATTERN) {
		SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.atackStepsOffset, (u32) SYNTHMODULE1_STOP_PATTERN);
		return;
	}

	// convert sustain value in absolute value
	sus_abs = (u32) SYNTHMODULE1_MAX * synth->amplifier_adsr.slevel;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.sustainLevelOffset, (u32) sus_abs);

	// convert attack time (in ms) into envelope increment steps
	nc = synth->amplifier_adsr.ams * clks;
	step = SYNTHMODULE1_MAX / nc;
	if (step == 0) {
		step = 1;
	}
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.atackStepsOffset, (u32) step);

	// convert decay time ( in ms) into envelope decrement step
	nc = synth->amplifier_adsr.dms * clks;
	step = (SYNTHMODULE1_MAX - sus_abs) / nc;
	if (step == 0) {
		step = 1;
	}
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.decayStepsOffset, (u32) step);

	// convert sustain time (in ms)  into clks
	nc = synth->amplifier_adsr.sms * clks;
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.sustainTimeOffset, (u32) nc);

	// convert release time (in ms) into envelope decrement step
	nc = synth->amplifier_adsr.rms * clks;
	step = sus_abs / nc;
	if (step == 0) {
		step = 1;
	}
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->amplifier_adsr.releaseStepsOffset, (u32) step);

}


/************************ Mixer Functions ********   ***********************/

/**
 * Save mix level for oscillators 1 and 2
 */
void SYNTHMODULE1_set_mix_lvl_osc1_osc2(Synth* synth) {

	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_1.amplitudeOffset,
			(u32) (	SYNTHMODULE1_convert_amp_to_q214(synth->osc_2.amplitude) << 16
			| SYNTHMODULE1_convert_amp_to_q214(synth->osc_1.amplitude) ) );

}

/**
 * Save mix level for oscillators 3 and microphone input
 */
void SYNTHMODULE1_set_mix_lvl_osc3_mic(Synth* synth) {
	SYNTHMODULE_mWriteReg(synth->baseAddr, synth->osc_3.amplitudeOffset,
			(u32) (	SYNTHMODULE1_convert_amp_to_q214(synth->osc_2.amplitude) << 16
			| SYNTHMODULE1_convert_amp_to_q214(synth->osc_1.amplitude)  ) );

}

/************************ Music Related Functions **************************/

int SYNTHMODULE1_calc_note_freq(int octave, int noteIndex) {
	// frequency table for octave 0
	const float NOTES[]={
			16.3516,	// 0 C
			17.3239,	// 1 C#
			18.3541, 	// 2 D
			19.4454,	// 3 D#
			20.6017,	// 4 E
			21.8268,	// 5 F
			23.1247,	// 6 F#
			24.4997,	// 7 G
			25.9565,	// 8 G#
			27.5000, 	// 9 A
			29.1352, 	// 10 A#
			30.8677		// 11 B
	};

	int freq;

	freq = (u32) NOTES[noteIndex] * (1 << octave);
	return freq;
}

void SYNTHMODULE1_play_note(Synth * synth, int note, int octave, int duration){
	int sus_tmp;
	int freq;

	freq = SYNTHMODULE1_calc_note_freq(octave, note);
	SYNTHMODULE1_osc1_set_carrier_freq(synth, freq);

	sus_tmp = duration - (synth->amplifier_adsr.ams + synth->amplifier_adsr.dms + synth->amplifier_adsr.rms);
	if (sus_tmp <=0) {
		sus_tmp = 10;
	}

	SYNTHMODULE1_amplifier_adsr_set_env(synth,
			synth->amplifier_adsr.ams,
			synth->amplifier_adsr.dms,
			sus_tmp,
			synth->amplifier_adsr.rms,
			synth->amplifier_adsr.slevel);

	SYNTHMODULE1_amplifier_adsr_start(synth);

}

/**
 *
 * Run a self-test on the driver/device. Note this may be a destructive test if
 * resets of the device are performed.
 *
 * If the hardware system is not built correctly, this function may never
 * return to the caller.
 *
 * @param   baseaddr_p is the base address of the SYNTHMODULEinstance to be worked on.
 *
 * @return
 *
 *    - XST_SUCCESS   if all self-test code passed
 *    - XST_FAILURE   if any self-test code failed
 *
 * @note    Caching must be turned off for this function to work.
 * @note    Self test may fail if data memory and device are not on the same bus.
 *
 */
XStatus SYNTHMODULE1_Reg_SelfTest(u32 baseaddr_p)
{
	u32 baseaddr;
	int write_loop_index;
	int read_loop_index;

	baseaddr = (u32) baseaddr_p;

	xil_printf("******************************\n\r");
	xil_printf("* User Peripheral Self Test\n\r");
	xil_printf("******************************\n\n\r");

	/*
	 * Write to user logic slave module register(s) and read back
	 */
	xil_printf("User logic slave module test...\n\r");

	for (write_loop_index = 0 ; write_loop_index < 4; write_loop_index++)
	  SYNTHMODULE_mWriteReg (baseaddr, write_loop_index*4, (write_loop_index+1)*READ_WRITE_MUL_FACTOR);
	for (read_loop_index = 0 ; read_loop_index < 4; read_loop_index++)
	  if ( SYNTHMODULE_mReadReg (baseaddr, read_loop_index*4) != (read_loop_index+1)*READ_WRITE_MUL_FACTOR){
	    xil_printf ("Error reading register value at address %x\n", (int)baseaddr + read_loop_index*4);
	    return XST_FAILURE;
	  }

	xil_printf("   - slave register write/read passed\n\n\r");

	return XST_SUCCESS;
}

Peripheral self-check

Connected to COM9 at 115200
---Entering main---
 Running SYNTHMODULE_Reg_SelfTest() ...
******************************
* User Peripheral Self Test
******************************
User logic slave module test...
   - slave register write/read passed
SYNTHMODULE_Reg_SelfTest PASSED
---Exiting main---

5.2. What's under the hood of the Synth module?

Many are the modules and many are the interconnections between them. It may seem somewhat complex but the design has been simplified to a few very configurable basic modules:

  • DDFS module: Creates different types of configurable oscillators.
  • ADSR module: Creates an envelope signal that changes amplitude over time
  • Mixer module: Combines several input signals into one
  • Filter module: Applies a digital filter over a signal.
  • Amplifier module: Applies an envelope to a signal.
  • 1-bit Delta Sigma DAC: It is a Single Bit Sigma-Delta Digital to Analog converter that uses oversampling.

5.3. Oscillators

Oscillators are the heart of sound generation. To give more design flexibility the MiniZed Synth works with 5 independent oscillators. Two are reserved to be the two main oscillators. Another, third auxiliary can provide another additional wave or can be reserved to generate different types of noise, and the last two are used for the generation of low-frequency oscillations below the audible frequency to model the sound in time, both in frequency as in filtering.

For the generation and modulation of the waveform of these oscillators, a direct digital synthesis technique is used. 

The designed oscillators can produce sine waves, square waves, triangle waves, saw waves, and white noise. The wave type is user selectable and several methods of modulation can be applied. Modulation is the process of modifying a carrier signal by a message signal. We can modulate the analog signal in amplitude, frequency, and phase modulation.

Direct synthesis of a modulated analog waveform

If the carrier signal is sin(2*PI*f*t) the modulated signals become the following:

  • Amplitude modulation: A(t) * sin(2*PI*f*t)
  • Frequency modulation: sin(2*PI*(f + Δf(t) * t)
  • Phase modulation:   sin(2*PI*f + Δp(t) )

The A(t), Δf(t) and Δp(t) are slow time-varying signals that embed the message

The DDFS system incorporates the three modulation schemes by inserting additional adders or multipliers:

image

The above diagram shows a conceptual diagram of a DDFS system that supports all three modulation schemes.

Instead of sin(2* PI * f * t) the extended system generates:

A(t) * sin(2*PI*(f + Δf(t) * t )  + Δp(t))

  • Frequency Control Word: Frequency control word (fccw) to generate the carrier frequency
  • Frequency Offset Word: The frequency control word (focw) to generate the offset frequency Δf(t) 
  • Phase Offset: The phase value corresponding to the desired phase offset, Δp(t)
  • Envelope: The digitized value of A(t)

DDFS module construction

The MiniZed Synth uses the following parameters in the design:

  • fsys: 100 MHz
  • N: 30 bits
  • Width of the lookup table 8 bits (256 entries)
  • sine wave amplitude resolution: 16 bits in signed format

The DDFS module can produce five different types of waveforms: Sinusoidal, Triangle, Square, Saw-Tooth, Noise, and any other wave based on the Phase to Amplitude Lookup Table as waveforms captured by a microphone.

image

5.4. Sine Wave Generator

A sine wave, sinusoidal wave, or just sinusoid is a mathematical curve defined in terms of the sine trigonometric function, of which it is the graph. It is a type of continuous wave and also a smooth periodic function. By adding different sine waves we get a different waveform, which changes the timbre of the sound. When the sound is made up of more than one sinusoidal then it has more harmonics that are perceptible to the human ear.

image

The presence of higher harmonics in addition to the fundamental causes variation in the timbre, which is the reason why the same musical note (the same frequency) played on different instruments sounds different. The actual design can use the 3 oscillators with different variations in frequency and mix them.

Here the three main oscillators generate three different sinusoidal signals and an attenuated noise source. The fourth signal is the mixed signal.

image

To generate the sinusoidal wave we can use algorithms like CORDIC or use a stored table with determined values ​​and interpolation for intermediate values. For this project, the latter solution has been chosen.

Synchronous ROM Unit - Table Lookup

The size of the lookup table is 2^8 by 16 (4K bits)and can be implemented by a synchronous ROM.

The actual design will take advantage of the possibility to define the initial values of FPGA's internal memory modules. When an SRAM-based FPGA device is programmed, the configuration file is loaded into the device's configuration module. When the configuration is completed the memory modules are initialized as well. If the content of a memory is not updated during the operation, it maintains its original values and behaves like a ROM. This is an efficient way to implement large lookup tables or to store read-only data.

The read operation of a BRAM is controlled and synchronized by a clock signal. The ROM must include a clock signal as in the diagram.

After an address change, it takes one clock cycle to output new data. The ROM readout is buffered via a register.

The synchronous ROM is constructed with FPGA's BRAM module and its content is loaded into the BRAM's initial values when the device is configured.

The module uses an initial block to load the values using $readmemb directive.

Sine lookup table SystemVerilog Implementation

`timescale 1ns / 10ps

module sin_rom
#( parameter DATA_WIDTH = 16,  // number of bits
             ADDR_WIDTH = 8    // number od address bits

)
(
    input logic clk,
    input logic [ADDR_WIDTH-1:0] addr_r,
    output logic [DATA_WIDTH-1:0] dout
    );
    
    // signal declaration
    logic [DATA_WIDTH-1:0] ram [0:2**ADDR_WIDTH-1];  // ascending range
    logic [DATA_WIDTH-1:0] data_reg;
    
    initial
        $readmemh("sin_table.mem", ram);
    
    // read operation
    always_ff @(posedge clk)
    begin
        data_reg <= ram[addr_r];
    end
    
    assign dout = data_reg;    
endmodule

DDFS module implementation in SystemVerilog

The DDFS module is built with two outputs pulse out that is a square wave and the Pulse Code Modulation (PCM) output with the selected waveform.  The lookup table output (amp) and envelope are 16 bits wide. After multiplication, it needs to trim the 32-bit multiplication result back to 16 bits.

It uses Q2.14 format in which -1.0 and 1.0 are represented as 1100 0000 000 0000 and 0100 0000 0000 0000 respectively.

The multiplication result, modulation,

assign modulation = $signed(envelope) * $signed(amp);

is in the Q18.14 format. Finally, selects the appropriate portion of the modulation signal and trims it back to the Q16.0 format, a 16-bit signed integer.

`timescale 1ns / 1ps


module ddfs
#(parameter PHASE_ACC_WIDTH = 30) // width of phase accumulator
( 
    input   logic               clk,
    input   logic               reset,
    input   logic   [PHASE_ACC_WIDTH-1:0]    freq_carrier_ctrl_word,     // frequency control word to generate carrier frequency
                                                                         // fccw = fout/fsys * 2^N ; (N = PHASE_ACC_WIDTH)
    input   logic   [PHASE_ACC_WIDTH-1:0]    freq_offset_ctrl_word,      // frequency offset control word
                                                                         // focw = fout/fsys * 2^N  ; (N = PHASE_ACC_WIDTH)
    input   logic   [PHASE_ACC_WIDTH-1:0]    phase_offset,               // phase offset
                                                                         // offset/360 * 2^N ; (N = PHASE_ACC_WIDTH)
    input   logic   [15:0]      envelope,                                // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
    input   logic   [2:0]       wave_type,
        
    output  logic   [15:0]      pcm_out,                                 // pcm signal 
    output  logic               pulse_out                                // pulse out
    );
    
    localparam SINE =     3'b000;                 
    localparam SQUARE =   3'b001;
    localparam SAW =      3'b010;
    localparam TRIANGLE = 3'b011;
    localparam NOISE =    3'b100; 
    
    // signal declaration
    logic   [PHASE_ACC_WIDTH-1:0]    freq_control_word;  // frequency modulation control word
    logic   [PHASE_ACC_WIDTH-1:0]    phase_next;         // next phase
    logic   [PHASE_ACC_WIDTH-1:0]    phase_control_word; // phase control word
    logic   [PHASE_ACC_WIDTH-1:0]    phase_reg;          // actual phase
    logic   [7:0]       p2a_raddr;
    logic   [15:0]      amp;
    logic   signed  [31:0] modulation;
    logic   [15:0] pcm_reg;  // multi-bit PCM (pulse code modulation) t    
    logic   [15:0] pcm_next;  // multi-bit PCM (pulse code modulation) t

    
    logic   [15:0] sine_amp;
    logic   [15:0] square_amp;
    logic   [15:0] triangle_amp;
    logic   [15:0] saw_tooth_amp;
    logic   [15:0] noise_amp;
    
    // body
    // instanciate sin() ROM
    sin_rom rom_unit
        (   .clk(clk),
            .addr_r(p2a_raddr),
            .dout(sine_amp));
            
            
   // instantiate square_wave unit
    square_wave square_unit
    (   .clk(clk),
        .addr_r(p2a_raddr),
        .dout(square_amp));
        
    // instantiate triangle wave unit
    triangle_wave triangle_unit
    (   .clk(clk),
        .addr_r(p2a_raddr),
        .dout(triangle_amp));
        
    // instantiate saw tooth wave unit
    saw_tooth saw_tooth_unit
    (   .clk(clk),
        .addr_r(p2a_raddr),
        .dout(saw_tooth_amp));
        
    // instantiate lsfr noise
    lfsr lfsr_unit(.clk(clk),
             .enable(1'b1),
             .lfsr_data(noise_amp),
             .reset(reset));
                  
    // phase register and output buffer
    // use an output buffer (to shorten crtical path since the o/p feeds dac) 
    // always_ff @(posedge clk)
    //    pcm_reg <= modu[29:14];
    always_ff @(posedge clk, posedge reset)
    begin
        if (reset) 
            begin
                phase_reg <= 0;
                pcm_reg <= 0;
            end
        else
            begin
                phase_reg <= phase_next;
                pcm_reg <= pcm_next;
            end
    end
    
    // frequency modulation
    assign freq_control_word = freq_carrier_ctrl_word + freq_offset_ctrl_word;
    
    // phase accumulation
    assign phase_next = phase_reg + freq_control_word;
    
    assign pcm_next = modulation[29:14];
    
    // phase modulation
    assign phase_control_word = phase_reg + phase_offset;
    
    // phase to amplitude mapping address
    assign p2a_raddr = phase_control_word[PHASE_ACC_WIDTH-1:PHASE_ACC_WIDTH-8]; // 8 bits
    
    // amplitude modulation envelop in Q2.14 
    //    * -1 < env < +1  (between 1100...00 and 0100...00) 
    //    * Q16.0 * Q2.14 => modu is Q18.14
    //    * convert modu back to Q16.0  
    // assign modulation = $signed(envelope) * $signed(amp);
    
    always_comb begin
      case (wave_type)
          SINE    : amp = sine_amp;                 
          SQUARE  : amp = square_amp;
          SAW     : amp = saw_tooth_amp;
          TRIANGLE: amp = triangle_amp;
          NOISE   : amp = noise_amp;  
          default:  amp = sine_amp;       
       endcase 
       modulation = $signed(envelope) * $signed(amp);
    end
    
    assign pcm_out = pcm_reg;
    assign pulse_out = phase_reg[PHASE_ACC_WIDTH-1];    
    
endmodule

Test Bench

  • T = 4,545.96µs - 2,273.01µs = 2,273 μs (microseconds) 
  • Frequency ν from ν = 1/T = 439.956884 Hz ≈ 440 Hz (hertz) 
  • Jitter 440 - 439.956884 = 0.043116 s 

image

5.5. Noise Generator Module

Noise is characterized as being aperiodic or having a non-repetitive pattern. White noise is a type of sound that contains all audible frequencies in equal amounts. It sounds like a hiss or a static noise. In sound synthesizers, white noise is used as a starting point for creating other sounds. By adding filters or modulating the sound, the synthesizer can create different tones or textures.

image

A few examples of sounds that can be created using white noise as a starting point are the sound of a snare drum and the sound of the wind. the sound of ocean waves, the sound of cymbals...

image

For noise generation, this module uses a linear-feedback shift register (LFSR). An LFSR is a shift register whose input bit is a linear function of its previous state. The most commonly used linear function of single bits is exclusive-or (XOR). Thus, an LFSR is most often a shift register whose input bit is driven by the XOR of some bits of the overall shift register value. Any long LFSR counter generates a long pseudo-random sequence of zeros and ones. The sequence is not exactly random since it repeats eventually, and it also follows a mathematically predictable sequence.

Vivado synthesis for the SystemVerilog module

 The appropriate taps for Maximum-Length LFSR Counters are extracted from this Xilinx Technical Note:

 xapp052.pdf • Viewer • AMD Adaptive Computing Documentation Portal (xilinx.com)

SystemVerilog implementation of the Noise module using a Linear Feedback Shift Register

`timescale 1ns / 1ps

// Description: 
//  This module generates an 16-bit random number. A LFSR or Linear Feedback Shift Register
//  is a quick and easy way to generate pseudo-random data inside of an FPGA.

module lfsr #(parameter NUM_BITS = 31)
  (
   input logic clk,
   input logic reset,
   input logic enable,
   output logic [NUM_BITS-1:0] lfsr_data
   );
 
  logic [NUM_BITS:1] lfsr_reg = 0;
  logic polinomial; 

  // run LFSR when enabled.
  always @(posedge clk, posedge reset)
    begin
      if (enable == 1'b1)
        begin
            if (reset == 1'b1) begin
                lfsr_reg <= 1'b0;
            end else begin
                lfsr_reg <= {lfsr_reg[NUM_BITS-1:1], polinomial};
            end
        end else begin
            lfsr_reg <= lfsr_reg;
        end
    end
 
  // Feedback Polynomial based on Xilinx Application Note:
  // http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
  // for 16 bits
  // assign  polinomial = lfsr_reg[16] ^~ lfsr_reg[15] ^~ lfsr_reg[13] ^~ lfsr_reg[4];
  // for 31 bits
   assign  polinomial = lfsr_reg[31] ^~ lfsr_reg[28];
 
  assign lfsr_data = lfsr_reg[16:1];
 
endmodule

image

5.6. Square Wave Generator

A square wave is a non-sinusoidal periodic waveform in which the amplitude alternates at a steady frequency between fixed minimum and maximum values, with the same duration at minimum and maximum. In an ideal square wave, the transitions between minimum and maximum are instantaneous, Square waves contain a wide range of harmonics. The ideal square wave contains only components of odd-integer harmonic frequencies.

image

In music, they are described as hollow sounds. They can be used as the basis for wind instrument sounds created using subtractive synthesis.
Also for performing distortion effects by clipping the outermost regions of the waveform, making it more and more like a square wave as more distortion is applied.

SystemVerilog implementation for the square wave generator.

`timescale 1ns / 1ps

/**
A square wave is a non-sinusoidal periodic waveform 
in which the amplitude alternates at a steady frequency between
fixed minimum and maximum values, with the same duration at minimum and maximum. 
n musical terms, they are often described as sounding hollow, and are therefore 
used as the basis for wind instrument sounds created using subtractive synthesis.
Additionally, the distortion effect used on electric guitars clips the outermost
regions of the waveform, causing it to increasingly resemble 
a square wave as more distortion is applied.
*/
module square_wave
#( parameter DATA_WIDTH = 16,  // number of bits
             ADDR_WIDTH = 8    // number od address bits

)
(
    input logic clk,
    input logic [ADDR_WIDTH-1:0] addr_r,
    output logic [DATA_WIDTH-1:0] dout
    );
    
    // signal declaration
    logic [DATA_WIDTH-1:0] data_reg;
    logic [DATA_WIDTH-1:0] data;
    
    
    // read operation
    always_ff @(posedge clk)
    begin
        data_reg <= data;
    end
    
    always_comb
    begin
       data = addr_r[7]?16'b1100_0000_0000_0000:16'b0100_0000_0000_0000;
    end
    
    assign dout = data_reg;    
endmodule

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

5.7. Triangle Wave Generator 

A triangular wave or triangle wave is a non-sinusoidal waveform named for its triangular shape. It is a periodic, piecewise linear, continuous real function. Like a square wave, the triangle wave contains only odd harmonics. However, the higher harmonics roll off much faster than in a square wave (proportional to the inverse square of the harmonic number as opposed to just the inverse).

image

Triangle Wave Generator - SystemVerilog implementation

`timescale 1ns / 10ps

/**
A triangular wave or triangle wave is a non-sinusoidal waveform 
named for its triangular shape. It is a periodic, piecewise linear, 
continuous real function. Like a square wave, the triangle wave
contains only odd harmonics. 
The higher harmonics roll off much faster than in a square wave.
*/

module triangle
#( parameter DATA_WIDTH = 16,  // number of bits
             ADDR_WIDTH = 8    // number od address bits

)
(
    input logic clk,
    input logic [ADDR_WIDTH-1:0] addr_r,
    output logic [DATA_WIDTH-1:0] dout
    );
    
    // signal declaration
    logic [DATA_WIDTH-1:0] data_reg;
    logic [DATA_WIDTH-1:0] data;    
    
    // read operation
    always_ff @(posedge clk)
    begin
        data_reg <= data;
    end
    
    always_comb
    begin
      if (addr_r > 8'h7F) begin
           data = {8'h00, 8'hFE - addr_r}; // decaying
      end else begin
           data = {8'h00, addr_r}; // raising
      end    
    end   
    
    assign dout =  $signed(data << 9) - $signed(16'h8000);  // offset biass  
endmodule

5.8. Saw-Tooth Wave Generator Module

Sawtooth waves and real-world signals contain all integer harmonics.

`timescale 1ns / 10ps


module saw_tooth
#( parameter DATA_WIDTH = 16,  // number of bits
             ADDR_WIDTH = 8    // number od address bits

)
(
    input logic clk,
    input logic [ADDR_WIDTH-1:0] addr_r,
    output logic [DATA_WIDTH-1:0] dout
    );
    
    // signal declaration
    logic [DATA_WIDTH-1:0] data_reg;
    logic [DATA_WIDTH-1:0] data;
    logic [DATA_WIDTH-1:0] amp;
    
    
    // read operation
    always_ff @(posedge clk)
    begin
        data_reg <= data;
    end
    
    
    always_comb
    begin
      amp = {8'h00, addr_r} << 8;
      data = $signed(amp) - $signed(16'h8000);   
    end  

    
    assign dout = data_reg;    
endmodule

5.9. Attack-Decay-Sustain-Release (ADSR) Envelope Modulation

The ATTACK-DECAY-SUSTAIN-RELEASE  (ADSR) envelope modulation scheme is based on the observation that when a real instrument generates a note, the volume of the musical note changes over time. It rises rapidly from zero and then steadily decays. To model this effect we can multiply the constant tone produced by the DDFS module by an amplitude ADSR envelope that contains the segments, attack, decay, sustain, and release. As can be seen in the figure below:

ADSR envelope

The ADRS envelope can be specified by two amplitude parameters, A max and Asus, and four time parameters, Attack time, Decay time, Sustain time, and Release time.

  • A max: maximum amplitude level the envelope can reach.
  • A sus: the amplitude of the sustain segment.
  • Attack time: the time interval of the Attack segment.
  • Decay time: the time interval of the Decay segment.
  • Sustain time: the time interval of the Sustain segment.
  • Release time: the time interval of the Release segment.

The maximum level will be defined as a constant and the sustain level will be defined as a percentage of the maximum amplitude. So we only need 6 parameters to define an envelope.

ADSR Envelope Generator Circuit

The ADSR Envelope Generator is a circuit that generates an amplitude envelope following the ADSR scheme.

The ADSR circuit models the sound generation of a musical instrument and its output does not run continuously.

A single envelope is generated at a time and the generation is controlled by a start trigger signal.

The trigger signal can be triggered automatically or in real-time mode:

  • The automatic mode uses the trigger signal as a note start.
  • The real-time mode mimics the operation of a key on the piano keyboard.

In the real-time mode, when the trigger signal is asserted, the envelope circuit will start operation and remain in the sustain segment until the trigger signal is canceled.

Finite State Machine with Data Path (FSMD) design of the ADSR Envelope Generator

The ADSR envelope generator is created using an FSMD with an amplitude counter and a sustained time counter. An FSMD is a kind of finite-state machine with a Data Path. 

A Finite-State Machine (FSM) is a sequential circuit that can be in exactly one of a finite number of states at any given time.

It contains five elements:

  • symbolic state register
  • input signal
  • output signal
  • current state
  • next state computed by a function

The symbolic state register contains the state. The FSM has a function to computer the next state and depending on the current state has an output (Moore FSM), and maybe on the input and the current state (Mealy FSM). 

A traditional FSM cannot represent storage elements (register) except the state registers. We can use instead an FSMD. An FSM with a Datapath (FSMD) is an extension of a traditional FSM. The storage and signals can be declared. and within a state expression, comparison, arithmetic, or logic operations on these signals can be performed. 

The behavior of the ADSR envelope is represented by this FSMD circuit with an Algorithmic State Machine Chart or ASM chart, a flow-chart-like description constructed from ASM blocks: state boxes, decision boxes, and conditional output boxes.

This is the graphical representation of a generic ASM block for a state.

ASM Block Legend

The unconditional outputs corresponding to that state can be placed inside the state box. Moore state machine outputs can also be placed inside the state box.

In general, Mealy state machine outputs are represented inside a conditional output box.

In addition to the attack, decay, sustain, and release states we'll add an idle state for the idle condition and a launch state for the retriggering.

The start signal is the trigger to initiate the amplitude envelope generation.

Once asserted the FSMD goes through the entire envelope generation process unless the start signal is asserted before completion, in that case, the FSMD aborts the current operation and starts a new envelope generation. The start signal is checked in every state.

The FSMD uses two registers, amplitude and t_sus, for the amplitude counter and the sustain counter.

ASMD Chart of the Automatic ADSR Envelope Circuit

The FSM uses the attack, decay, sustain, and release states to represent the four segments of the ADS envelope.

The amplitude counter is used to generate the envelope amplitude. It is also used to determine the time spent in the attack, decay, and release states. In each state, the amplitude is decremented or incremented by a specific amount every clock cycle.

The value used for increment or decrement of the counter in those states is passed pre-calculated to the ADSR envelope generator module: attack step, decay_step, and release_step.

The ADRS envelope can be specified by:

  • Attack_ms: The time interval (t_attack ) between activation and full loudness (MAX).
  • Decay_ms: The time interval (t_decay) for Vmax to drop to the sustain level (Sustain_level).
  • Sustain_level: The constant sound volume (Sustain_level) for the note until it is released.
  • Release_ms: The time interval (t_release ) for the sound to fade from Vs to 0 when a note ends.

Attack_step: amount the amplitude counter must be incremented each clock cycle to reach MAX from zero in t_attack time

Attack_step = (MAX- 0)/(t_attack / t_sys)

  • MAX: the  maximum amplitude as MAX, the sustained amplitude as sustain_level 
  • t_attack: time in the attack segment
  • t_sys: system time, depends on system clock frequency

Decay_step: amount the amplitude counter must be decremented each clock cycle to reach the sustain level amplitude from MAX in t_decay time

 Decay_step = (MAX-sustain_level) / (t_decay / t_sys)

  • sustain_level: amplitude in the sustain segment
  • t_decay: time in the decay segment
  • t_sys: system time, depends on system clock frequency

Release_step: amount the amplitude counter must be decremented each clock cycle to reach the zero level amplitude from the sustain_level in t_release time

Release_step  = (Sustain_level-0) / (t_release / t_sys)

  • sustain_level: amplitude in the sustain segment
  • t_release: time in the release segment
  • t_sys: system time, depends on system clock frequency

Sustain_time: the number of clock cycles in the sustain segment. The amplitude is constant in the sustain segment and its interval is controlled by the sustain time.

sustain_time = t_sustain / t_sys

  • t_sustain: time in the sustain segment
  • t_sys: system time, depends on system clock frequency

The incrementing and decrementing steps appear as external circuit signals, Attack_step, Decay_step, Decay_step. The Sustain_level also appears as an external circuit signal.

  • After the start is asserted, the FSMD moves from the idle state to the launch state.
  • Then the FSM moves to the attack state, where the amplitude register is incremented with the Attack_step until it reaches the MAX amplitude.
  • Then the FSMD transits to the decay state in which the amplitude register is decremented with the Decay_step until the amplitude reaches the Sustain_level.
  • Then the FSMD transits to the sustain state in which the t_sus register is incremented until reaching Sustain_time.
  • Then the FSMD moves to the release state in which the amplitude register is decremented until it becomes 0.
  • Then the FSMD returns to the idle state.

 

image

ADSR envelope applied to a Saw-Tooth Wave

ADSR Envelope Generator SystemVerilog implementation

`timescale 1ns / 1ps


// fsm to generate ADSR envelop

// start (trigger) signal:
//     - starts the "attack" when asserted
//     - restarts the epoch if aseerted before the current epoch ends 

// amplitudes:
//     - 32-bit unsigned   
//     - use 32 bits to accommodate the needed resolution for "step"
//     - intepreted as Q1.31 format:
//     - range artificially limited between 0.0 and 1.0
//     - i.e., 0.0...0 to 1.0...0 (1.0)
//     - 1.1xx...x not allowed

// output: Q2.14 for range (-1.0 to 1.0)

// special atk_step values
//     - atk_step = 11..11: bypass adsr; i.e., envelop=1.0
//     - atk_step = 00..00: output 0; i.e., envelop = 0.0

// Width selection: 
//   max attack time = 2^31 * clock period = 2^31 *(1/100e6) = 21,47483648 seconds

// Attack_time
// t_attack desired attack time
// t_sys sytem clock period
// maximum amplitude Amax
// need t_attack / t_sys clock cycles in the attact segment
// the counter must be incremented (Amax - 0)/(t_attack - t_sys) each clock cycle to reach A_max from zero in t_attack

// Decrementing amount in decay segment for t_decay
// t_decay and A_sus.  (A_max-A_sus) / (t_sustain / t_sys)
// 

// Decrementing amount in release segment
// (A_sus - 0)/(t_release - t_sys)

// The amplitude is constant in the sustain segment
// There are are tsustain / t_sys cycles in the sustain segment

// Amax  |    /\
//       |   /  \
//       |  /    \
// Asus  | /      ------------
//       |/                   \
//       ---------------------------
//        | attack
//             | decay
//                | sustain
//                            | release


 module adsr(
input logic clk,
input logic reset,
input logic start, // generate a pulse to start the envelope generation
input logic [31:0] attack_step_value, // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
input logic [31:0] decay_step_value,  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
input logic [31:0] sustain_level, // amplitude for the sustain segment
input logic [31:0] release_step_value,  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
input logic [31:0] sustain_time, // tsustain / t_sys steps for the sustain
output logic [15:0] envelope,
output logic adsr_idle

    );
    
    // constants
    localparam MAX = 32'h8000_0000;
    localparam BYPASS = 32'hffff_ffff;
    localparam ZERO = 32'h0000_0000;
    
    // fsm state type
    typedef enum {idle, launch, attack, decay, sustain, rel} state_type;
    
    // declaration
    state_type state_reg;
    state_type state_next;
    logic [31:0] amplitude_counter_reg;
    logic [31:0] amplitude_counter_next;
    logic [31:0] sustain_time_reg;
    logic [31:0] sustain_time_next;
    logic [31:0] n_tmp;
    logic fsm_idle;
    logic [31:0] envelope_i;
    
    // state and data registers
    always_ff @(posedge clk, posedge reset)
    begin
        if(reset) 
          begin
            state_reg <= idle;
            amplitude_counter_reg <= 32'h0;
            sustain_time_reg <= 32'h0;
          end
         else
         begin
            state_reg <= state_next;
            amplitude_counter_reg <= amplitude_counter_next;
            sustain_time_reg <= sustain_time_next;
         end
    end
    
    // fsmd next-state logic and data path logic
    always_comb
    begin
        state_next = state_reg;
        amplitude_counter_next = amplitude_counter_reg;
        sustain_time_next = sustain_time_reg;
        fsm_idle = 1'b0;        
        case (state_reg)         
            idle: begin
                fsm_idle = 1'b1;
                if (start) begin
                    state_next = launch;
                  end
              end
            launch: begin
                state_next = attack;
                amplitude_counter_next = 32'b0;
              end  
            attack: begin
                if(start) begin
                    state_next = launch;
                end else begin
                    n_tmp = amplitude_counter_reg + attack_step_value;
                    if (n_tmp < MAX) begin
                        amplitude_counter_next = n_tmp;
                    end else begin
                        state_next = decay;
                    end
                 end
              end
            decay: begin
                if (start) begin
                    state_next = launch;
                end else begin
                    n_tmp = amplitude_counter_reg - decay_step_value;
                    if(n_tmp > sustain_level) begin
                        amplitude_counter_next = n_tmp;
                    end else begin
                        amplitude_counter_next = sustain_level;
                        state_next = sustain;
                        sustain_time_next = 32'b0;  // start timer
                    end
                 end
              end              
            sustain: begin
                if (start) begin
                    state_next = launch;
                end else begin
                    if(sustain_time_reg < sustain_time) begin
                        sustain_time_next = sustain_time_next + 1;
                    end else begin
                        state_next = rel;
                    end
                 end
              end              
             default: begin
                if (start) begin
                    state_next = launch;
                  end else begin
                    if(amplitude_counter_reg > release_step_value) begin
                        amplitude_counter_next = amplitude_counter_reg - release_step_value;
                    end else begin
                        state_next = idle;
                    end
                  end
               end    
        endcase

    end
    
    assign adsr_idle = fsm_idle;
    
    assign envelope_i = (attack_step_value == BYPASS) ? MAX :
                   (attack_step_value == ZERO) ? 32'b0 :
                   amplitude_counter_reg;
                   
    assign envelope = {1'b0, envelope_i[31:17]};
endmodule

Direct capture of the MiniZed Synth. Two triangular waves added out of phase, with frequency modulation using an LFO, and sculpted with the ADSR envelope of the final amplifier.

image

Direct capture of the MiniZed Synth with the Digilent Analog Discovery 2.

In the real-time mode, when the trigger signal is asserted, the envelope circuit will start operation and remain in the sustain segment until the trigger signal is canceled.

Here is an implementation in SystemVerilog. Please note that the hold time is no longer indicated.

`timescale 1ns / 1ps
// adsr with real time option
// sus_time is controlled by the sart signal instead of sustain_time

// fsm to generate ADSR envelop

// start (trigger) signal:
//     - starts the "attack" when asserted
//     - restarts the epoch if aseerted before the current epoch ends 

// amplitudes:
//     - 32-bit unsigned   
//     - use 32 bits to accommodate the needed resolution for "step"
//     - intepreted as Q1.31 format:
//     - range artificially limited between 0.0 and 1.0
//     - i.e., 0.0...0 to 1.0...0 (1.0)
//     - 1.1xx...x not allowed

// output: Q2.14 for range (-1.0 to 1.0)

// special atk_step values
//     - atk_step = 11..11: bypass adsr; i.e., envelop=1.0
//     - atk_step = 00..00: output 0; i.e., envelop = 0.0

// Width selection: 
//   max attack time = 2^31 * clock period = 2^31 *(1/100e6) = 21,47483648 seconds

// Attack_time
// t_attack desired attack time
// t_sys sytem clock period
// maximum amplitude Amax
// need t_attack / t_sys clock cycles in the attact segment
// the counter must be incremented (Amax - 0)/(t_attack - t_sys) each clock cycle to reach A_max from zero in t_attack

// Decrementing amount in decay segment for t_decay
// t_decay and A_sus.  (A_max-A_sus) / (t_sustain / t_sys)
// 

// Decrementing amount in release segment
// (A_sus - 0)/(t_release - t_sys)

// The amplitude is constant in the sustain segment
// Keeps in the sustain segment while start is asserted

// Amax  |    /\
//       |   /  \
//       |  /    \
// Asus  | /      ------------
//       |/                   \
//       ---------------------------
//        | attack
//             | decay
//                | sustain
//                            | release


 module adsr_rt(
input logic clk,
input logic reset,
input logic start, // start the envelop and sustain signal
input logic [31:0] attack_step_value, // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
input logic [31:0] decay_step_value,  // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment
input logic [31:0] sustain_level, // amplitude for the sustain segment
input logic [31:0] release_step_value,  // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment
output logic [15:0] envelope,
output logic adsr_idle

    );
    
    // constants
    localparam MAX = 32'h8000_0000;
    localparam BYPASS = 32'hffff_ffff;
    localparam ZERO = 32'h0000_0000;
    
    // fsm state type
    typedef enum {idle, launch, attack, decay, sustain, rel} state_type;
    
    // declaration
    state_type state_reg;
    state_type state_next;
    logic [31:0] amplitude_counter_reg;
    logic [31:0] amplitude_counter_next;
    logic [31:0] n_tmp;
    logic fsm_idle;
    logic [31:0] envelope_i;
    
    // state and data registers
    always_ff @(posedge clk, posedge reset)
    begin
        if(reset) 
          begin
            state_reg <= idle;
            amplitude_counter_reg <= 32'h0;
          end
         else
         begin
            state_reg <= state_next;
            amplitude_counter_reg <= amplitude_counter_next;
         end
    end
    
    // fsmd next-state logic and data path logic
    always_comb
    begin
        state_next = state_reg;
        amplitude_counter_next = amplitude_counter_reg;
        fsm_idle = 1'b0;        
        case (state_reg)         
            idle: begin
                fsm_idle = 1'b1;
                if (start) begin
                    state_next = launch;
                  end
              end
            launch: begin
                state_next = attack;
                amplitude_counter_next = 32'b0;
              end  
            attack: begin
                if(!start) begin
                    state_next = rel;
                end else
                 begin
                    n_tmp = amplitude_counter_reg + attack_step_value;
                    if (n_tmp < MAX) begin
                        amplitude_counter_next = n_tmp;
                    end else begin
                        state_next = decay;
                    end
                 end
              end
            decay: begin
                if (!start) begin
                    state_next = rel;
                end else 
                begin
                    n_tmp = amplitude_counter_reg - decay_step_value;
                    if(n_tmp > sustain_level) begin
                        amplitude_counter_next = n_tmp;
                    end else begin
                        amplitude_counter_next = sustain_level;
                        state_next = sustain;
                    end
                 end
              end              
            sustain: begin
                if (!start) begin
                    state_next = rel;
                end else begin
                    state_next = sustain;
                 end   
              end              
             default: begin
                if (start) begin
                    state_next = launch;
                  end else begin
                    if(amplitude_counter_reg > release_step_value) begin
                        amplitude_counter_next = amplitude_counter_reg - release_step_value;
                    end else begin
                        state_next = idle;
                    end
                  end
               end    
        endcase

    end
    
    assign adsr_idle = fsm_idle;
    
    assign envelope_i = (attack_step_value == BYPASS) ? MAX :
                   (attack_step_value == ZERO) ? 32'b0 :
                   amplitude_counter_reg;
                   
    assign envelope = {1'b0, envelope_i[31:17]};
endmodule

A test bench checking correct operation.

image

5.10. One-bit delta-sigma DAC

To convert the PCM signal to a true analog signal it is needed a DAC (digital-to-analog converter) and a low-pass filter.

The system uses a one-bit-delta-sigma DAC, which generates PDM (pulse modulation) output. This DAC can be realized in pure digital logic without any analog component. See Analog-Sigma-Delta ADCs and DACs

The frequency of the system clock is 100MHz and the frequency of the generated audio signal is around 20kHz.

Delta-Sigma DACs are high-speed single-bit DACs. Using digital feedback, a string of pulses is generated. The average duty cycle of the pulse string is proportional to the value of the binary input. The analog signal is created by passing the pulse string through an analog low-pass filter.

Conceptual Design

Conceptual block diagram of a one-bit delta-sigma DAC.

image

It consists of a one-bit delta-sigma modulation circuit and a one-bit ADC. The term, Delta-Sigma, refers to the arithmetic difference and sum, respectively.

The PCM input is in the 16-bit unsigned integer format. from 0x0000 to 0xffff. Data is expanded to 17 bits internally.

The Sigma Accumulator is the main part of the DAC, composed of an adder and a register. It continuously adds the input PCM data samples.

If the accumulation exceeds the maximum value of 0xffff, the PDM pulse becomes '1' and the amount of 0x1_0000 is subtracted from the accumulation.

The one-bit ADC converts logic '0' and '1' into two PC values of 0x0_0000 and 0x1_0000.

More high pulses will be generated if the PCM amplitude is larger.

image

Different sound waves generated mixing different waves and converted to analog by the 1-bit delta sigma DAC after the RC low-pass filter

One-bit Delta Sigma SystemVerilog Implementation

The comparator can be eliminated, we can use the MSB as the output. The subtractor also can be eliminated. We can simply append a 0 to the 16 LSBs and use it as the feedback value. The output of the amplifier converted from a 16-bit signed format to a 17-bit unsigned format. It is first sign-extended to 17 bits and then added a bias of 0x0_ 8000

`timescale 1ns / 10ps

module ds_1bit_dac
#(parameter W = 16)  // input width
(
input logic clk,
input logic reset,
input logic [W-1:0] pcm_in,
output logic pdm_out
    );
    
    // signal declarations
    localparam BIAS = 2 ** (W-1); // {1'b1, (W-2){1'b0}};
    logic [W:0] pcm_biased;
    logic [W:0] acc_next;
    logic [W:0] acc_reg;
    
    // shift the range from [-2^(W-1) -1 , 2^(W-1)-1] to [0, 2^W-1]
    assign pcm_biased = {pcm_in[W - 1], pcm_in} + BIAS;
    
    // signal treated as unsigned number in delta-sigma modulation
    assign acc_next = {1'b0, acc_reg[W-1:0]} + pcm_biased;
    
    // accumulation register
    always_ff @(posedge clk, posedge reset)
    begin
        if (reset)
        begin
            acc_reg <= 0;
        end
        else
        begin
            acc_reg <= acc_next;
        end
    end
    
    assign pdm_out = acc_reg[W];
    
    
endmodule

5.11. Mixer Module

The mixer module is very simple.  It takes the input audio signals, mixes them, and provides one output signal.

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 08/19/2023 11:21:59 AM
// Design Name: 
// Module Name: mixer
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module mixer(
    input logic clk,
    input logic reset,
    input logic [15:0] input_1, // input signal 1
    input logic [15:0] lvl_1, // volume level 1
    input logic [15:0] input_2, // input signal 2
    input logic [15:0] lvl_2, // volume level 2
    input logic [15:0] input_3, // input signal 3
    input logic [15:0] lvl_3, // volume level 3
    input logic [15:0] input_4, // input signal 4
    input logic [15:0] lvl_4, // volume level 4
    output logic [15:0] pcm_out
    );
    
    logic   signed  [31:0] modulation1;
    logic   signed  [31:0] modulation2;
    logic   signed  [31:0] modulation3;
    logic   signed  [31:0] modulation4;
    logic   signed  [31:0] sum; 
    
    // amplitude modulation in Q2.14 
    //    * -1 < env < +1  (between 1100...00 and 0100...00) 
    //    * Q16.0 * Q2.14 => modu is Q18.14
    //    * convert modu back to Q16.0  
    // assign modulation = $signed(envelope) * $signed(amp);   
       
    assign modulation1 = $signed(lvl_1) * $signed(input_1);
    assign modulation2 = $signed(lvl_2) * $signed(input_2);
    assign modulation3 = $signed(lvl_3) * $signed(input_3);
    assign modulation4 = $signed(lvl_4) * $signed(input_4);    
    
    // Q18.14 + Q18.14 = Q18.14
    assign sum = $signed( modulation1 + modulation2 + modulation3 + modulation4);
    // TODO overflow treatment with clipping
    
    // convert back to Q16.0
    assign pcm_out = sum[29:14];
 
endmodule

In the next image, the mixer combines a triangle wave, a square wave, and a sinusoidal wave.

image

5.12. Filter Module

The filters have the function of altering the spectral content of the signal generated by the oscillators, increasing, attenuating, or even filtering certain harmonics. To do this, resonant filters are used, with a cut-off frequency and quality factor (Q, hereinafter, resonance) that can be parameterized by the user interactively. The filter should be a variable filter that allows adjustment of the cutoff frequency and resonance level, since depending on the frequency and type of signal generated by the oscillator, these parameters will be adjusted differently to obtain the desired timbre. The parameter setting can be done manually, by the user, or using modulations with ADSR envelopes, which provides a high degree of dynamism to the generated sound.

A low-pass filter based on a moving average has been developed.

`timescale 1ns / 1ps
///////////////////////////////////////////////////////////////////////


module moving_average_lpf(
    input logic clk,
    input logic reset,
    input logic [7:0] order,
    input logic  [15:0] signal_in,
    output logic  [15:0] signal_out
);


    // signal declarations
    localparam BIAS = 2 ** 15; // {1'b1, (W-2){1'b0}};

    logic [31:0] ma_new, ma_new_reg;
    logic [31:0] ma_old, ma_old_reg;
    logic [31:0] signal_n;
    logic [16:0] unbiased_signal_out;
    logic [16:0] biased_signal;
    logic [16:0] biased_trucated_output;
    
        
    // shift the range from [-2^(15) -1 , 2^(15)-1] to [0, 2^32-1]
    assign biased_signal = {signal_in[15], signal_in} + BIAS;
    
    assign signal_n = {biased_signal, 15'b0};
    
    always_ff @(posedge clk, posedge reset) begin
        if (reset) begin
            ma_new_reg <=0;
            ma_old_reg <=0;
        end else begin
            ma_new_reg <= ma_new;
            ma_old_reg <= ma_old;
        end
    end
    

    assign ma_old = ma_new_reg;
    assign ma_new = $signed(signal_n  >> order) + $signed(ma_old_reg) - $signed(ma_old_reg >> order) ;
    
    assign biased_trucated_output = ma_new_reg[31:15];
    
    

    assign unbiased_signal_out = $signed(biased_trucated_output - BIAS) ;
    
    assign signal_out = unbiased_signal_out[15:0];

endmodule

A noise signal after filtered and unfiltered with order = 6

image

And with order = 12

image

The final design will have a version of the Chamberlin state-variable filter. I haven't finished implementing yet.

The Chamberlin provides multiple outputs with different filtering characteristics.

image

Controlling the feedback path, you can change the filter's resonance and emphasize specific frequency ranges.

5.13. Amplifier Module

The function of the amplifier module is to shape the amplitude in time, for this it multiplies the output of the ADSR module by the output of the mixer to then pass the signal to the final filter.

Again, fixed point encoding numbers are used to avoid floating point operations.

module amplifier(
    input logic clk,
    input logic reset,
    input logic [15:0] pcm_input,
    input logic [15:0] envelope, // Amplitude modulation: envelope, digitized value of A(t) in Q2.14 format
    output logic [15:0] pcm_out
    );
    
    logic   signed  [31:0] modulation;
        
    // amplitude modulation in Q2.14 
    //    * -1 < env < +1  (between 1100...00 and 0100...00) 
    //    * Q16.0 * Q2.14 => modu is Q18.14
    //    * convert modu back to Q16.0  
    // assign modulation = $signed(envelope) * $signed(amp);   
       
    assign modulation = $signed(envelope) * $signed(pcm_input);
    
    // convert back to Q16.0
    assign pcm_out = modulation[29:14];

endmodule

5.14 Low-Frequency Oscillator. Pitch Frequency Modulation

The implementation features two low-frequency oscillators (LFOs) which modulate the frequency or pitch of the three main oscillators, as well as the cutoff frequency of the final filter over time.

In the image, you can see the signal from the oscillator generating a sine wave at the top. Below that, the low-frequency modulating signal is displayed, and in the last graph, the signal is already frequency-modulated.

image


6. Minized Microphone Input PDM to PCM via Decimation

Back to index

image

The MEMS microphone on the Minized development board captures sound and generates a 1-bit Pulse Density Modulated (PDM) signal. The signal is passed to a FIFO memory that will produce a small delay, the full flag signal is connected to the ready flag for reading. The output of the FIFO is still a PDM signal that is connected to a very simple DAC consisting of a simple low-pass RC filter.

6.1. Interfacing the MiniZed built-in microphone

The  Avnet Minized development board has as its sound sensor an ST MP45DT02 MEMS microphone that generates a 1-bit pulse density modulated (PDM) signal. Pulse Density Modulation (PDM), is used in the representation and conversion of analog signals to the digital domain (and vice versa). The amplitude of the signals is represented by the relative density or number of pulses as a function of time. A one-bit stream is unacceptably noisy, but very high sampling rates and noise-shaping techniques are used to greatly reduce the noise in the audio spectrum. This noise energy is moved from the audio baseband into the area of the spectrum above 20 kHz, where it is inaudible.

The Pulse Code Modulation (PCM) is a modulation procedure used to transform an analog signal into a sequence of bits (digital signal), It is the standard form of digital audio in computers, compact discs, digital telephony, and other similar applications. In a PCM stream, the amplitude of an analog signal is sampled regularly at uniform intervals, and each sample is quantized to the nearest value within a range of digital steps.

The sound sensor on the MiniZed is hard configured as data valid on clock low.

image

It is needed a slow clock for the MEMS microphone, in the range between 1.2 MHz and 3.25 MHz

The Vivado LogicCore Clock Wizard IP cannot generate clocks under 5MHz so it implemented a modulo-M counter with M equal to (input frequency / desired frequency)/2 to generate a 2.400 MHz clock signal from a 100 MHz clock signal.

Microphone clock generation

module pdm_clk_gen
  #(
    parameter INPUT_FREQ = 100000000,
    parameter OUTPUT_FREQ = 2400000

    )
   (
    input  clk,
    input  reset,
    output mic_clk,
    output clk_rising
    );

   logic   clk_rising_reg;
   logic   mic_clk_reg;

   localparam CLK_DIVIDER = INPUT_FREQ/OUTPUT_FREQ;

   // M-bit counter  M = LOG2(CLK_DIVIDER)
   logic [$clog2(CLK_DIVIDER)-1:0] clk_counter_reg;

   always_ff@(posedge clk) begin
      if (reset) begin
         clk_counter_reg    <= 0;
         mic_clk_reg    <= 0;
         clk_rising_reg <= 0;
      end
      else begin
         clk_rising_reg <= 0;
         if (clk_counter_reg < (CLK_DIVIDER/2)-1) begin
            clk_counter_reg <= clk_counter_reg + 1;
         end
         else begin
            clk_counter_reg    <= 0;
            mic_clk_reg        <= ~mic_clk_reg;
            clk_rising_reg     <= ~mic_clk_reg;

         end
      end
   end

   assign mic_clk = mic_clk_reg; // Microphone clock signal
   assign clk_rising = clk_rising_reg; //  output pulse on the rising edge of the clock for s_axis_data_tvalid


endmodule

In the Zynq hardware integrated logic analyzer (ILA IP) image capture we can see the 1-bit PDM output signal of the MEMS microphone and the signal output delayed by the FIFO.

image

6.2. Checking Microphone capture

The video showcases the MiniZed development board's microphone, which produces a 1-bit PDM pulse density modulated signal. This signal is saved in a FIFO queue and transmitted to an output on the MiniZed, which is connected to a board with a low pass filter and amplifier. The data was captured using the Digilent waveforms program. The Digilent Analog Discovery 2 USB Oscilloscope was used for data acquisition.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

6.3. PDM to PCM using the VIVADO CIC Compiler IP

A signal that is coded as PDM can be converted to PCM by sampling it at a lower rate (decimating) and increasing the word length. The ratio of the PDM bit rate to the decimated PCM sample rate is called the oversampling ratio. The decimation (also called "down-sampling") operation, means discarding all but every Rth sample.

Vivado includes a Cascaded Integrator Comb (CIC) Compiler. The Cascaded Integrator Comb (CIC) Compiler provides the ability to design and implement AXI4-Stream-compliant cascaded
integrator-comb (CIC) filters. https://docs.xilinx.com/v/u/en-US/pg140-cic-compiler

To make the PDM to PCM conversion we need to establish our design requirements. Our PDM input sample rate is 2.400 MHz and we want a PCM output sample rate of 48kHz.That is a decimation rate of 50.

image

The frequency response of the MP34DT05-A microphone of the MiniZed is only flat between 100Hz and 4.5kHz, after which it starts going up. So set the pass band filter specification from 0 to 6 kHz and the stop band to 10kHz.

Other requirements can be the passband ripple and the stop band attenuation for that we need to study the acoustic characteristics of our microphone.

6.4. Cascading Integrating Comb Filter

Cascaded Integrator-Comb (CIC) filters, also known as Hogenauer filters, are implementations of narrowband lowpass filters. These multi-rate filters are typically employed in applications where the system sample rate is much larger than the bandwidth occupied by the processed signal as in digital down converters (DDC). Compared to standard FIR filters, they are more economical since they do not include multipliers and use limited storage components. CIC uses only adders, subtracters, and delay elements.

In a decimating CIC, the input signal is fed through one or more cascaded integrators, then a down-sampler, followed by one or more comb sections (equal in number to the number of integrators). CIC is well suited for antialiasing filtering before decimation or sample rate reduction.

6.5. Configuration of the AMD LogiCORE IP CIC Compiler

The AMD LogiCORE IP CIC Compiler core provides the ability to design and implement AXI4-Stream-compliant cascaded integrator-comb (CIC) filters.

The Minized Synth uses a Decimation Filter with 5 stages, one channel, and a 50 rate change factor. 

{gallery}CIC COMPILER CONFIGURATION FOR PDM TO PCM VIA DECIMATION

image

FILTER OPTIONS: Decimation, 5 stages, 50 Fixed Sample Rate, 100MHz Oversampling sample clock frequency, 2.4MHz input Sample Frequency

image

IMPLEMENTATION OPTIONS: 16 bits output via truncation, input data width 2

image

CIC COMPILER: SUMMARY

CIC Compiler Configuration


7. Indicator LEDs and Control Switches Auxiliary Expansion Board

Back to index

image

This extension board with user buttons and indicator LEDs is configured as follows:

  • The first button selects a Sawtooth (LED OFF) or Square wave (LED ON) for OSC 1.
  • The second button selects a Sawtooth (LED OFF) or Square wave (LED ON) for OSC 2.
  • The third button sets the input source level of NOISE (LED OFF) or MIC LED ON)
  • Forth button enables or disables the Release segment of both Envelope Generators. When enabled, the Envelope Release time is the same as the Envelope Decay time, and the DECAY control adjusts the time for both segments. When disabled, the Release segment does not occur and the Envelope stops abruptly in response to a “Note Off” message (or when the Gate goes to zero).

This board has been built by soldering the components on a protoboard. The switches work in negative logic and have 1 KOhm SMD pull-up resistors, and the LEDs work with positive logic and have 10 KOhm SMD resistors to limit the current and therefore the intensity of the LED light.

image  image

Auxiliary board for the support of control switches and indicator LEDs.

The schematic is made with Easy EDA, so far I have not created a specific PCB for this module.

image

Schematic for the button panel of the control module and indicators made with EasyEDA.

The module needs 13 ports for inputs and outputs. 12 of the ports are taken from one of the PMOD connectors. The xdc configuration file allows you to use any of the two PMOD connectors on the MiniZed board. The thirteenth connects to one of the free ports on the Arduino connectors.

PMOD 1

FUNCTION PMOD1 PIN DIRECTION LOGIC ZYNQ PACKAGE PIN ZYNQ PACKAGE PIN LOGIC DIRECTION PMOD1 PIN Function
LED5 7 OUTPUT POSITIVE K13 | L15 NEGATIVE INPUT 1 SW1
LED4 8 OUTPUT POSITIVE L13 M15 NEGATIVE INPUT 2 SW2
LED3 9 OUTPUT POSITIVE N13 L14 NEGATIVE INPUT 3 SW3
LED2 10 OUTPUT POSITIVE N14 M14 NEGATIVE INPUT 4 SW1
GND 11 GND GND 5 GND
VCC 12 3V3 3V3 6 VCC

ARDUINO HEADERS

FUNCTION ARDUINO PIN DIRECTION LOGIC ZYNQ PACKAGE PIN
LED1 IO11 OUTPUT POSITIVE M11

Constraints File

# Analog input VAUX0
set_property IOSTANDARD LVCMOS33 [get_ports Vaux0_0_v_n]
set_property IOSTANDARD LVCMOS33 [get_ports Vaux0_0_v_p]
# Analog input
set_property PACKAGE_PIN E13 [get_ports Vaux0_0_v_n]

# Analog input VAUX8
set_property IOSTANDARD LVCMOS33 [get_ports Vaux8_0_v_n]
set_property IOSTANDARD LVCMOS33 [get_ports Vaux8_0_v_p]


## ARDUINO IO10
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[2]}]
set_property PACKAGE_PIN M10 [get_ports {muxaddr_out_0[2]}]


## ARDUINO IO9
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[1]}]
set_property PACKAGE_PIN N9 [get_ports {muxaddr_out_0[1]}]

## ARDUINO IO8
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[0]}]
set_property PACKAGE_PIN M9 [get_ports {muxaddr_out_0[0]}]


########################################################################
## Pmod #1 LEDs & SWITCHES
########################################################################

## PIN 1 - SW4 - SYNTH_SWITCHES_tri_i[3]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[3]}]
set_property PACKAGE_PIN L15 [get_ports {SYNTH_SWITCHES_tri_i[3]}]

## PIN 2 - SW3 - SYNTH_SWITCHES_tri_i[2]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[2]}]
set_property PACKAGE_PIN M15 [get_ports {SYNTH_SWITCHES_tri_i[2]}]

## PIN 3 - SW2 - SYNTH_SWITCHES_tri_i[1]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[1]}]
set_property PACKAGE_PIN L14 [get_ports {SYNTH_SWITCHES_tri_i[1]}]

## PIN 4 - SW1 -  SYNTH_SWITCHES_tri_i[0]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[0]}]
set_property PACKAGE_PIN M14 [get_ports {SYNTH_SWITCHES_tri_i[0]}]

## PIN 7 - LED2 - SYNTH_LEDS_tri_o[1]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[1]}]
set_property PACKAGE_PIN K13 [get_ports {SYNTH_LEDS_tri_o[1]}]

## PIN 8 - LED3 - SYNTH_LEDS_tri_o[2]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[2]}]
set_property PACKAGE_PIN L13 [get_ports {SYNTH_LEDS_tri_o[2]}]

## PIN 9 - LED4 - SYNTH_LEDS_tri_o[3]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[3]}]
set_property PACKAGE_PIN N13 [get_ports {SYNTH_LEDS_tri_o[3]}]

## PIN 9 - LED5 - SYNTH_LEDS_tri_o[4]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[4]}]
set_property PACKAGE_PIN N14 [get_ports {SYNTH_LEDS_tri_o[4]}]

########################################################################
### ARDUINO IO11  - LED1 - SYNTH_LEDS_tri_o[0]
########################################################################


set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[0]}]
set_property PACKAGE_PIN M11 [get_ports {SYNTH_LEDS_tri_o[0]}]

set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets clk]


8. Low Pass Filter and Sound Amplifier Expansion Boards

Back to index

image

8.1. Analog RC Low Pass Filter

A simple passive RC low-pass filter is used as a digital-to-analog converter. 

  • Resistance 3.3 kΩ
  • Capacitance 0.0047 µF
  • -3dB Cutoff Frequency 10 kHz

image

8.2 Sound Amplifier Expansion Board

The audio output connects to a commercially available LM386 audio amplifier.

image


9. Multiplexer Expansion Board

Back to index

image

The MiniZed Synth needs to read the values of 16 analog potentiometers but the Avnet Minized board only exposes 4 channels of the Zynq's integrated analog-to-digital converter (XADC). This section presents the board that I have designed and built to obtain the reading of the 16 analog potentiometers that are included in the control panel for sound editing.

Zynq's XADC allows the use of an external analog multiplexer to implement several external analog inputs in situations where FPGA I/O resources are limited and auxiliary analog inputs are not available as is our case. A 16:2 Multiplexer Expansion Board is designed and built for the project. 

image

Custom-made XADC 16:2 External Multiplexer addon board for the MiniZed

An analog-to-digital converter (ADC) is a circuit that digitizes a continuous analog signal by converting its voltage level to a discrete digital quantity. The AMD Zynq-700 SoC and the AMD Xilinx FPGA 7 series have an integrated analog-to-digital converter (XADC). The XADC has two 12-bit 1 mega samples per second (MSPS) ADCs with separate track and hold amplifiers, an analog multiplexer (up to 17 external analog input channels), and on-chip thermal and on-chip voltage sensors. The two ADCs can be configured to simultaneously sample two external-input analog channels.

image

The XADC can read up to 17 analog channels in sequence but the Avnet MiniZed board only exposes 4 channels of the integrated analog-to-digital converter (XADC).

9.1. AMD XADC External Multiplexer Mode

Fortunately, the XADC supports the use of an external analog multiplexer to implement several external analog inputs in situations where FPGA I/O resources are limited and auxiliary analog inputs are not available.

The XADC track/hold amplifiers return to track mode as soon as a conversion starts. Therefore, the acquisition on the next channel can start during the current conversion cycle. An output bus called MUXADDR[4:0] allows the XADC to control an external multiplexer.

The address on this bus reflects the channel currently being acquired, and it changes state as soon as the XADC enters acquisition mode. Users can also nominate the channel to be used with an external multiplexer.

The designed board takes advantage of the external multiplexer mode in simultaneous sampling mode.

9.2. External Multiplexer mode in simultaneous sampling mode

In the case of simultaneous sampling mode, two channels must be allocated to two external multiplexers to support simultaneous sampling.

The next figure illustrates how the external multiplexer mode is implemented for simultaneous sampling mode. The channels selected for connection are also selected by writing to CH4 to CH0 but are allocated in pairs. For example, writing 16 ( 10000 ) to CH4 to CH0 would select auxiliary channels 0 and 8 for connection to external multiplexers as shown in the figure.

image

When placed in simultaneous sampling mode, the sequencer automatically sequences through eight pairs of auxiliary analog input channels for simultaneous sampling and conversion.

This is the mode I have chosen for the design of the multiplexer. The 16:2 multiplexer takes advantage of the simultaneous reading performed by the two ADC converters of the XADC module.

image

XADC Channels assignment

9.3. External Multiplexer Circuit Design

DISCLAIMER: I am not an electrical engineer, just a hobbyist, use at your own risk. The design is working correctly for me and it's doing its job.

The expansion board use two 74HC4051D,653-Analog Multiplexer / Demultiplexer, 8:1

The 74HC4051 is a single-pole octal-throw analog switch (SP8T) suitable for use in analog or digital 8:1 multiplexer/demultiplexer applications. The switch features three digital select inputs (S0, S1, and S2), eight independent inputs/outputs (Yn), a common input/output (Z), and a digital enable input (E). When E is HIGH, the switches are turned off.

imageimage

74HC4051,single-pole octal-throw analog switch (SP8T)

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

The addresses for the external multiplexer go from 0 to 7, and three  XADC multiplexer output signals (D0, D1, D2) address the 8 analog inputs for both independent 8:1 multiplexers.

The board limits the analog signal to 1V using a buffered voltage divider.

image

Those headers are for connecting up to 16x10K potentiometers.

image

The double-sided PCB is designed with easyEDA. I had never ordered a circuit from an online service before, it has been a new experience for me.

16:2 Multiplexer Expansion Board PCB

{gallery}XADC 16:2 Multiplexer PCB

image

image

image

image

 

The project in easyEDA json format:

PCB_PCB_ADCMultiplexerV3_2023-07-02.zip

 Gerber_PCB_ADCMultiplexerV3.zip

9.4. 16:2 Multiplexer Expansion Board Bill of Materials

Product Name Manufacturer Quantity Buy Kit
M20-9740642-Pin Header, Board-to-Board, 2.54 mm, 2 Rows, 12 Contacts, Through Hole Right Angle, M20 HARWIN 1 Buy Now
C0603C104K5RACAUTO-SMD Multilayer Ceramic Capacitor, AEC-Q200, 0.1 µF, 50 V, 0603 [1608 Metric], ± 10%, X7R KEMET 2 Buy Now
B3B-XH-A (LF)(SN)-Pin Header, Vertical, Wire-to-Board, 2.5 mm, 1 Rows, 3 Contacts, Through Hole Straight, XH JST (JAPAN SOLDERLESS TERMINALS) 16 Buy Now
CRCW060322K0FKEA-SMD Chip Resistor, 22 kohm, ± 1%, 100 mW, 0603 [1608 Metric], Thick Film, General Purpose VISHAY 1 Buy Now
CRCW080510K0FKEA-SMD Chip Resistor, 10 kohm, ± 1%, 125 mW, 0805 [2012 Metric], Thick Film, General Purpose VISHAY 1 Buy Now
CRCW0805220KFKEA-SMD Chip Resistor, 220 kohm, ± 1%, 125 mW, 0805 [2012 Metric], Thick Film, General Purpose VISHAY 1 Buy Now
74HC4051D,653-Analog Multiplexer / Demultiplexer, 8:1, 1 Circuit, 2V to 10V, SOIC-16 NEXPERIA 2 Buy Now
LMV321SN3T1G-Operational Amplifier, RR O/P, 1 Amplifier, 1 MHz, 1 V/µs, 2.7V to 5V, TSOP, 5 Pins ONSEMI 1 Buy Now
 
{gallery}XADC 16:2 External Multiplexer Expansion Board

image

image

image

The form factor allows the board to be used in a PMOD connector.

XADC Channels assignment

The following diagram shows the XADC channel assignment to each of the analog potentiometers on the front panel.

image

Function Assignment to XADC Channels Diagram

Function Assignment to XADC Channels

XADC Channel Function
A00 OSC1 LVL
A01 LFO RATE
A02 OSC LFO
A03 LFO FILTER
A04 OSC2 FREQ
A05 OSC2 LVL
A06 FILTER RES
A07 FILTER CUTOFF
A08 AMP ENVELOPE SUSTAIN
A09 FILTER ENVELOPE SUSTAIN
A10 AMP ENVELOPE DECAY/RELEASE
A11 AMP ENVELOPE ATTACK
A12 FILTER ENVELOPE DECAY
A13 FILTER EG AMOUNT
A14 FILTER ENVELOPE ATTACK
A15 NOISE / MIC LVL

9.5. XADC IP Configuration

The XADC is configured for simultaneous sampling with an external multiplexer, using the AXI4Lite interface for control in continuous mode and using channel averaging as a low pass filter.

image

VIVADO XADC Wizard configuration summary


VIVADO XADC IP CONNECTION

The Vivado block diagram shows how the XADC IP module is connected to the Zynq processor.
Two Integrated Logic Analyzers have been added, one to sample the output of the multiplexer router and another to monitor the communication on the AXI bus.

image

Constraints file

# Analog input VAUX0
set_property IOSTANDARD LVCMOS33 [get_ports Vaux0_0_v_n]
set_property IOSTANDARD LVCMOS33 [get_ports Vaux0_0_v_p]
# Analog input
set_property PACKAGE_PIN E13 [get_ports Vaux0_0_v_n]

# Analog input VAUX8
set_property IOSTANDARD LVCMOS33 [get_ports Vaux8_0_v_n]
set_property IOSTANDARD LVCMOS33 [get_ports Vaux8_0_v_p]


## ARDUINO IO10
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[2]}]
set_property PACKAGE_PIN M10 [get_ports {muxaddr_out_0[2]}]


## ARDUINO IO9
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[1]}]
set_property PACKAGE_PIN N9 [get_ports {muxaddr_out_0[1]}]

## ARDUINO IO8
set_property IOSTANDARD LVCMOS33 [get_ports {muxaddr_out_0[0]}]
set_property PACKAGE_PIN M9 [get_ports {muxaddr_out_0[0]}]


########################################################################
## Pmod #1 LEDs & SWITCHES
########################################################################

### PIN 1 - SW4 - SYNTH_SWITCHES_tri_i[3]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[3]}]
#set_property PACKAGE_PIN L15 [get_ports {SYNTH_SWITCHES_tri_i[3]}]

### PIN 2 - SW3 - SYNTH_SWITCHES_tri_i[2]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[2]}]
#set_property PACKAGE_PIN M15 [get_ports {SYNTH_SWITCHES_tri_i[2]}]

### PIN 3 - SW2 - SYNTH_SWITCHES_tri_i[1]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[1]}]
#set_property PACKAGE_PIN L14 [get_ports {SYNTH_SWITCHES_tri_i[1]}]

### PIN 4 - SW1 -  SYNTH_SWITCHES_tri_i[0]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[0]}]
#set_property PACKAGE_PIN M14 [get_ports {SYNTH_SWITCHES_tri_i[0]}]

### PIN 7 - LED2 - SYNTH_LEDS_tri_o[1]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[1]}]
#set_property PACKAGE_PIN K13 [get_ports {SYNTH_LEDS_tri_o[1]}]

### PIN 8 - LED3 - SYNTH_LEDS_tri_o[2]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[2]}]
#set_property PACKAGE_PIN L13 [get_ports {SYNTH_LEDS_tri_o[2]}]

### PIN 9 - LED4 - SYNTH_LEDS_tri_o[3]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[3]}]
#set_property PACKAGE_PIN N13 [get_ports {SYNTH_LEDS_tri_o[3]}]

### PIN 9 - LED5 - SYNTH_LEDS_tri_o[4]
#set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[4]}]
#set_property PACKAGE_PIN N14 [get_ports {SYNTH_LEDS_tri_o[4]}]


########################################################################
## Pmod #2 LEDs & SWITCHES
########################################################################

## PIN 1 - SW4 - SYNTH_SWITCHES_tri_i[3]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[3]}]
set_property PACKAGE_PIN P13 [get_ports {SYNTH_SWITCHES_tri_i[3]}]

## PIN 2 - SW3 - SYNTH_SWITCHES_tri_i[2]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[2]}]
set_property PACKAGE_PIN P14 [get_ports {SYNTH_SWITCHES_tri_i[2]}]

## PIN 3 - SW2 - SYNTH_SWITCHES_tri_i[1]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[1]}]
set_property PACKAGE_PIN N11 [get_ports {SYNTH_SWITCHES_tri_i[1]}]

## PIN 4 - SW1 -  SYNTH_SWITCHES_tri_i[0]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_SWITCHES_tri_i[0]}]
set_property PACKAGE_PIN N12 [get_ports {SYNTH_SWITCHES_tri_i[0]}]

## PIN 7 - LED2 - SYNTH_LEDS_tri_o[1]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[1]}]
set_property PACKAGE_PIN P15 [get_ports {SYNTH_LEDS_tri_o[1]}]

## PIN 8 - LED3 - SYNTH_LEDS_tri_o[2]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[2]}]
set_property PACKAGE_PIN R15 [get_ports {SYNTH_LEDS_tri_o[2]}]

## PIN 9 - LED4 - SYNTH_LEDS_tri_o[3]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[3]}]
set_property PACKAGE_PIN R12 [get_ports {SYNTH_LEDS_tri_o[3]}]

## PIN 9 - LED5 - SYNTH_LEDS_tri_o[4]
set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[4]}]
set_property PACKAGE_PIN R13 [get_ports {SYNTH_LEDS_tri_o[4]}]

########################################################################
### ARDUINO IO11  - LED1 - SYNTH_LEDS_tri_o[0]
########################################################################


set_property IOSTANDARD LVCMOS33 [get_ports {SYNTH_LEDS_tri_o[0]}]
set_property PACKAGE_PIN M11 [get_ports {SYNTH_LEDS_tri_o[0]}]

########################################################################
### PDM MICROPHONE
########################################################################

### MiniZed Microphone CLK
set_property PACKAGE_PIN L12 [get_ports AUDIO_CLK]
set_property IOSTANDARD LVCMOS33 [get_ports AUDIO_CLK]

### MiniZed Microphone DAT
set_property PACKAGE_PIN M12 [get_ports AUDIO_DAT]
set_property IOSTANDARD LVCMOS33 [get_ports AUDIO_DAT]

## AUDIO OUT ARDUINO ARDUINO_IO12
set_property IOSTANDARD LVCMOS33 [get_ports {AUDIO_OUT}]
set_property PACKAGE_PIN R11 [get_ports {AUDIO_OUT}]

In the image, the MinZed ports are used for the multiplexed reading of the 16 potentiometers according to the XDC constraints configuration file.

image

In the following video, you can see the data capture of 16 potentiometers made by multiplexing.
A small C program was made that runs in the Zynq processing system on the ARM A9 processor and that is polling the registers of the XADC module and sending the reading of the 16 channels through the serial port in CSV format. Reading the serial port is done using the Windows Serial Plot program.

The XADC module is configured to take an average of 256 readings, working this average as a very suitable low-pass filter for this type of reading with so much noise. It is also observed that the two channels are not symmetrical, this is because the Minized shares some of the ports with the output for the Programmable Logic LEDs and there is a slight voltage drop on one of the channels. This can be solved by cutting a track from the MiniZed but the current design solves it in software by adjusting the values using a map function.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image


10. ARM 9 Bare Metal Software App. MIDI Control

Back to index

The full firmware is still unfinished. Below you can see a video with the main functions implemented that allow you to control the two main oscillators, the noise source and the detune.
Two of the buttons allow you to change the wave type of the two oscillators. Three potentiometers allow you to adjust the three input levels of the mixer. The third button changes the octave of the oscillators and adjusts the frequency of a note, and the fourth button releases the ADSR so you can continuously hear the pitch.
You can also adjust the rate of the low-frequency oscillator which affects the modulation of the frequency of the oscillators and the desired level of modulation. The output filter settings and MIDI communication are still pending development.

ATTENTION: The video is quite long, more than four minutes, and has very loud sounds, Turn down the volume and try not to use headphones.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Testing the Attack-Decay-Sustain-Releases envelops and the Low-Frequency Oscillators. MiniZed Synths plays a 4 octaves musical scale in different configurations of the oscillators, noise source, ADSR envelopes and LFOs.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

The current version implements a simple variable cutoff frequency low-pass filter based on a moving average. At the moment it does not have a feedback loop for resonance. In the video you can see a frequency analysis of how the filter affects different waveform frequencies. The video has no sound.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Popcorn Melody

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

11. Creating the prototype

Back to index

From idea to prototype. It has been many hours of design and discarding solutions. The most difficult thing has been defining the scope to be able to carry out the project in one month.

In the images some moments of the development of the project.

{gallery}From idea to prototype

image

MiniZed Synth Design: Cardboard prototype

image

Learning communication between programmable logic and the processing system

Analog 8:1 multiplexer

Analog multiplexer and adapter
image
image

image

Checking audio output section: RC Low-Pass Filter and LM386 sound amplifier.

image

MiniZed Synth Design: Checking cable lengths

image

MiniZed Synth 1: Components

image

Control panel front: switches, LEDs, and pots

image

image

Main boards connected: Exploded view

image

image

image

Top view

image

Checking the arrangement of modules.

12. Code Repository and References

Back to index

Github repository

https://github.com/javagoza/e14PathToProgrammableIII

References

Software Synthesis (basicsynth.com)

TFilter - Free online FIR filter design (engineerjs.com)


13. Conclusion and next steps

Back to index

Throughout this project, I utilized all the skills that I acquired during my three-year time as a member of Element14. These abilities consist of composing technical blog posts in English, designing marquetry crafts, programming microcontrollers, creating digital circuits using HDL, constructing a PCB, and handling SMD components. While the latter two skills were unfamiliar to me, I have found them both enjoyable and challenging due to my limited experience and the brief length of this program.

Initially, I lacked knowledge about musical synthesizers and their functioning. However, I have managed to gain a better understanding through this project. I hope that the reader can also benefit from the information I have gathered.

I am extremely pleased with the results of this project, and I hope that it inspires others who, like me, have pursued electronics as a hobby despite coming from a different professional background.

The project is far from over as I still have numerous hours ahead to experiment and continue learning. Rest assured, I will keep sharing any new developments on Element14.

I want to express my gratitude to the amazing team at Element14 and sponsor AMD for allowing me to take part in the free training course on AMD's APSoC development tools, Vivado and Vitis IDE. I also want to give a shout-out to all the other participants in the training program for sharing their questions and insights in the forums. Thank you all!


14. Path to Programmable III Training Blog Series

Back to index

You can access the entire series of blogs I have written for the "Path to Programmable III" program:

  1. BLOG 1: P2P3 Getting Started. Clockless Hardware Blinky on the Avnet Minized 
  2. BLOG 2:  P2P3 AMD Vitis portability and reuse. Migrating a Microblaze bare metal environmental monitor App to the Zynq architecture. 
  3. BLOG 3:  P2P3 AMD Zynq-7000 SoC XADC. External Multiplexer Mode. 
  4. BLOG 4:  P2P3 AMD Vivado Cascaded Integrator Comb (CIC) Compiler. PDM Microphone to PCM Decimation 
  5. BLOG 5:  P2P3 Wireless sensors on the Avnet Minized. Getting Started with PetaLinux 
  6. FINAL PROJECT:  AMD Zynq SoC MIDI Vintage Sound Synthesizer - Final 
  • Sign in to reply
  • Boneoh
    Boneoh 11 months ago

    Outstanding! This is an awesome project, congrats!  I've bookmarked this and will be coming back to learn how you've created this.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 1 year ago in reply to rsc

    Thanks! The final version will definitely be polyphonic.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • rsc
    rsc over 1 year ago

    That is very cool.  (next you should make it polyphonic)

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 1 year ago in reply to saadtiwana_int

    Thanks for your kind words. I'm really thankful to element14 for these great opportunities. Even though it's all for fun, I think that if we make content that the sponsor likes, it can help keep these programs going or even make them better.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • saadtiwana_int
    saadtiwana_int over 1 year ago

    I've been following your projects over the past year or two and by now I have come to expect exceptional projects from you every time...and this one is no different!

    You chose a very unique project idea, tackled every problem in the way in a methodical manner and produced a very professional looking (and sounding) end product. Bravo for that!! I am definitely rooting for you to win!

    P.s I'm always amazed by how fast you learn! Slight smile. Keep it up!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube