ADSR envelope generator for sound synthesis.
In the previous blog we implemented a Direct Digital Frequency Synthesis module (DDFS ) that can generate an unmodulated audio-frequency tone. In this blog we will implement an ADSR (attack-decay-sustain-release) amplitude envelope module which is a widely used scheme in music synthesizers.
We will use the unmodulated DDFS module to generate a basic tone and with the ADSR module, we will modulate the signal to get closer to how musical instruments produce notes.
Table of Contents
- ADSR envelope generator for sound synthesis.
- ADSR Introduction
- ADSR Envelope Generator Circuit
- FSMD design of the ADSR Envelope Generator
- ADSR Envelope Generator SystemVerilog implementation
- Synthesized Circuit by Vivado
- Input values calculator designed with Excel
- ADSRCalculator.xlsx
- Testbench SystemVerilog Implementation
- Testbench Scope
- Test with the Digilent Arty S7 50
- Examples
- Conclusion
- SystemVerilog Study Notes Chapters
ADSR Introduction
The ADSR 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:
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 parametres to define an envelope.
ADSR Envelope Generator Circuit
The ADSR Envelope Generator is a circuit that generates an amplitude envelope following the ADSR scheme. Its output is connected to a DDFS module as an external amplitude modulation signal.
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 an 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 cancelled. We will start by designing a generator for automatic mode.
FSMD design of the ADSR Envelope Generator
We will construct the ADSR envelope generator using a FSMD with an amplitude counter and a sustain time counter. A FSMD is a kind of Finite State Machine with 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 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.
We'll represent the behavior of the ADSR envelope FSMD circuit with an Algorithmic State Machine Chart or ASM chart, a flow-chart-like description constructed from ASM blocks: sate boxes, decision boxes and conditional output boxes.
This is the graphical representation of a generic ASM block for a state.
The unconditional outputs corresponding to that state can be placed inside state box. Moore state machine outputs can also be placed inside state box.
In general, Mealy state machine outputs are represented inside conditional output box.
In addition to the attack, decay, sustain and release states we'll add and 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.
The FSM uses the 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 a specific amount every clock cycle.
The value used for increment or decrement 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 sustain 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: amount 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 start is asserted, the FSMD moves from the idle state to the launch state.
- Then the FSM moves to the attack state, in which 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.
ADSR Envelope Generator SystemVerilog implementation
Following the ASMD Chart we will convert the chart to an HDL (SystemVerilog) description.
This approach is simple and descriptive but we rely on the synthesis software (Vivado) for datapath construction and have less control than coding the data path explicitly.
`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_decay / 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_decay / 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
Graphic representation of input and output ADSR module signals
Synthesized Circuit by Vivado
Input values calculator designed with Excel
I have created a small spreadsheet to pre-calculate the input values to the envelope generator module by indicating the times in milliseconds and the amplitude level in the sustain segment as a percentage of the maximum amplitude.
ADSRCalculator.xlsx
Testbench SystemVerilog Implementation
`timescale 1ns / 1ps module adsr_tb; localparam T=20; localparam PHASE_ACC_WIDTH = 30; logic reset; logic clk; // Attack 100ms, Decay 50ms, Sustain 100ms, Release 50ms. Sustain amplitude level 90% logic [31:0] attack_step_value = 32'h0000_00d6; // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment logic [31:0] decay_step_value = 32'h0000_002a; // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment logic [31:0] sustain_level = 32'h7333_3332; // amplitude for the sustain segment logic [31:0] sustain_time = 32'h0098_9680; // tsustain / t_sys steps for the sustain logic [31:0] release_step_value = 32'h0000_0183; // precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment logic adsr_idle; logic start; logic [15:0] envelope; always begin clk <= 1'b1; #(T/2); clk <= 1'b0; #(T/2); end // instantiate adsr adsr adsr_uut( .clk(clk), .reset(reset), .start(start), .attack_step_value(attack_step_value), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment .decay_step_value(decay_step_value), // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment .sustain_level(sustain_level), // amplitude for the sustain segment .release_step_value(release_step_value), // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment .sustain_time(sustain_time), // tsustain / t_sys steps for the sustain .envelope(envelope), .adsr_idle(adsr_idle) ); initial begin reset = 1'b1; #40; reset = 1'b0; #20; start = 1'b1; #40; start = 1'b0; #999999999; $stop; end endmodule
Testbench Scope
I tried several values. A flute-like ADSR envelope
t-attack (ms) | t-decay (ms) | t-sustain (ms) | t-release (ms) | A-sustain (% over MAX) |
100 | 50 | 100 | 50 | 90% |
logic [31:0] attack_step_value = 32'h0000_00d6; // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
logic [31:0] decay_step_value = 32'h0000_002a; // precalculated (A_max-A_sus) / (t_decay / t_sys) steps for the decay segment
logic [31:0] sustain_level = 32'h7333_3332; // amplitude for the sustain segment
logic [31:0] sustain_time = 32'h0098_9680; // t sustain / t_sys steps for the sustain
logic [31:0] release_step_value = 32'h0000_0183; // precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
Let's set the attack segment to 0 ms to remove it.
t-attack (ms) | t-decay (ms) | t-sustain (ms) | t-release (ms) | A-sustain (% over MAX) |
0 | 100 | 200 | 50 | 80% |
logic [31:0] attack_step_value = 32'h7FFFFFFF; // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment
logic [31:0] decay_step_value = 32'h0000002B; // precalculated (A_max-A_sus) / (t_decay / t_sys) steps for the decay segment
logic [31:0] sustain_level = 32'h66666666; // amplitude for the sustain segment
logic [31:0] sustain_time = 32'h01312D00; // tsustain / t_sys steps for the sustain
logic [31:0] release_step_value = 32'h00000158; // precalculated (A_sus - 0)/(t_release - t_sys) steps for the release segment
Test with the Digilent Arty S7 50
Constraints
## Clock Signals set_property -dict {PACKAGE_PIN R2 IOSTANDARD SSTL135} [get_ports clk] create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports clk] ## Switches set_property -dict {PACKAGE_PIN H14 IOSTANDARD LVCMOS33} [get_ports {sw[0]}] set_property -dict {PACKAGE_PIN H18 IOSTANDARD LVCMOS33} [get_ports {sw[1]}] set_property -dict {PACKAGE_PIN G18 IOSTANDARD LVCMOS33} [get_ports {sw[2]}] set_property -dict {PACKAGE_PIN M5 IOSTANDARD SSTL135} [get_ports {sw[3]}] ## LEDs set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports {led[0]}] set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports {led[1]}] set_property -dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33} [get_ports {led[2]}] set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {led[3]}] ## Buttons set_property -dict {PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports {btn[0]}] set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports {btn[1]}] set_property -dict {PACKAGE_PIN J16 IOSTANDARD LVCMOS33} [get_ports {btn[2]}] set_property -dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports {btn[3]}] ## Pmod Header JD set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports audio_out] set_property DRIVE 16 [get_ports audio_out] set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports gain] set_property -dict {PACKAGE_PIN V13 IOSTANDARD LVCMOS33} [get_ports pulse_out] set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports neg_shutdown] #set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { jd[4] }]; #IO_L22N_T3_A04_D20_14 Sch=jd7/ck_io[29] #set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { jd[5] }]; #IO_L23P_T3_A03_D19_14 Sch=jd8/ck_io[28] #set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { jd[6] }]; #IO_L23N_T3_A02_D18_14 Sch=jd9/ck_io[27] #set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { jd[7] }]; #IO_L24P_T3_A01_D17_14 Sch=jd10/ck_io[26] ## Configuration options, can be used for all designs set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] set_property CONFIG_VOLTAGE 3.3 [current_design] set_property CFGBVS VCCO [current_design] set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] set_property CONFIG_MODE SPIx4 [current_design] ## SW3 is assigned to a pin M5 in the 1.35v bank. This pin can also be used as ## the VREF for BANK 34. To ensure that SW3 does not define the reference voltage ## and to be able to use this pin as an ordinary I/O the following property must ## be set to enable an internal VREF for BANK 34. Since a 1.35v supply is being ## used the internal reference is set to half that value (i.e. 0.675v). Note that ## this property must be set even if SW3 is not used in the design. set_property INTERNAL_VREF 0.675 [get_iobanks 34]
Arty S7 Test Module
- Four slide switches to change octave
- Four switch buttons to play a note modulated with the selected envelope.
`timescale 1ns / 1ps module checkDDFSModule( input clk, input logic [3:0] sw, input logic [3:0] btn, // PMOD 1 output audio_out, // audio out output gain, // Gain output pulse_out, output neg_shutdown, // ~SHUTDOWN output led[3:0] ); localparam T = 2; localparam W = 16; localparam PHASE_ACC_WIDTH = 30; localparam M1 = 30'h0AFD; // (262.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH); // carrier freq 262 Hz Freq sys 100 Mhz, M = fout/fsys * 2^N localparam M2 = 30'h0C54; // (277.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH); // carrier freq 277 Hz Freq sys 100 Mhz, M = fout/fsys * 2^N localparam M3 = 30'h0DD7; // (294.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH); // carrier freq 294 Hz Freq sys 100 Mhz, M = fout/fsys * 2^N localparam M4 = 30'hEAE; // (311.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH); // carrier freq 311 Hz Freq sys 100 Mhz, M = fout/fsys * 2^N logic [31:0] attack_step_value = 32'h0000_00d6; // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment logic [31:0] decay_step_value = 32'h0000_002a; // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment logic [31:0] sustain_level = 32'h7333_3332; // amplitude for the sustain segment logic [31:0] sustain_time = 32'h0098_9680; // tsustain / t_sys steps for the sustain logic [31:0] release_step_value = 32'h0000_0183; // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment logic adsr_idle; logic [PHASE_ACC_WIDTH-1:0] freq_carrier_ctrl_word = M1; logic [PHASE_ACC_WIDTH-1:0] freq_offset_ctrl_word = 30'h0; logic [PHASE_ACC_WIDTH-1:0] phase_offset = 30'h0; logic [15:0] amp; // = 16'h4000; // Q2.14 format -1.0 to 1.0 //logic [15:0] amp = 16'h2FFF; // Q2.14 format -1.0 to 1.0 logic reset; logic [PHASE_ACC_WIDTH-1:0] note; assign neg_shutdown = sw[0]; // control sound on off with switch B0 assign gain = sw[1]; //assign reset = btn[0]; logic [15:0] pcm; // pcm signal logic [32:0] counter; logic [32:0] counter_next; logic start; always_ff @(posedge clk) begin start <= btn[0] | btn[1] | btn[2] | btn[3]; case (btn) 4'b1000: note <= M1 << sw; 4'b0100: note <= M2 << sw; 4'b0010: note <= M3 << sw; 4'b0001: note <= M4 << sw; endcase end // registers always_ff @(posedge clk, posedge reset) begin if (reset) begin freq_carrier_ctrl_word <= 0; freq_offset_ctrl_word <= 0; phase_offset <= 0; //amp <= 16'h4000; // 1.00 counter <=0; end else begin freq_carrier_ctrl_word <= note; freq_offset_ctrl_word <= 0; phase_offset <= 0; //amp <= 16'h4000; counter <= counter_next; end end assign counter_next = counter +1; assign led[0] = counter[24]; assign led[1] = pcm; // instantiate adsr adsr adsr_uut( .clk(clk), .reset(reset), .start(start & adsr_idle), .attack_step_value(attack_step_value), // precalculated (Amax - 0)/(t_attack - t_sys) steps for the attack segment .decay_step_value(decay_step_value), // precalculated (A_max-A_sus) / (t_sustain / t_sys) steps for the decay segment .sustain_level(sustain_level), // amplitude for the sustain segment .release_step_value(release_step_value), // precalculated (A_sus - 0)/(t_release - t_sys) steps fot the release segment .sustain_time(sustain_time), // tsustain / t_sys steps for the sustain .envelope(amp), .adsr_idle(adsr_idle) ); // instantiate ddfs ddfs #( .PHASE_ACC_WIDTH(30)) ddfs_uut (.clk(clk), .reset(reset), .freq_carrier_ctrl_word(freq_carrier_ctrl_word), .freq_offset_ctrl_word(freq_offset_ctrl_word), .phase_offset(phase_offset), .envelope(amp), .pcm_out(pcm), .pulse_out(pulse_out) ); // instantiate ds 1bit dac ds_1bit_dac #(.W(16)) ds_1bit_uut( .clk(clk), .reset(reset), .pcm_in(pcm), .pdm_out(audio_out) ); endmodule
Examples
Waveform captured with the Digilent Analog Discovery 2 and WaveForms virtual instrument suite
Changing octave, higher frequency.
Envelop with long attack
Piano like envelope using a triangle wave DDFS
And using a sine wave DDFS
Conclusion
This is the second blog dedicated to generating sounds with the Spartan-7 FPGA within the SystemVerilog Study Notes series that I'm posting on element14.
I have learned to implement a circuit in SystemVerilog HDL that generates a configurable ADSR envelope starting from the design of a finite state machine with data path.
I have used the envelope signal generated by the circuit to amplitude modulate a waveform generated by the DDFS circuit that we built in the previous blog and I have experimented with the 1-bit delta sigma DAC also built in the previous blog.
This modulator circuit can be used not only to modulate the amplitude but also as a Cutoff Envelope to control the cutoff of a low-pass filter and as a Frequency Envelope to control the frequency of an oscillator. I will try to experiment with those uses in future posts.
Project sources and tools
The following zip file s7_ddfs_adsr_sources_tools.zip contains:
- Arty S7 constraints file
- SystemVerilog source files
- SystemVerilog Testbench source files
- ADSR values calculator spreadsheet
- Diagrams in draw.io format
SystemVerilog Study Notes Chapters
- Gate-Level Combinational Circuit
- RTL Combinational Circuit Operators
- RTL Combinational Circuit - Concurrent and Control Constructs
- Hex-Digit to Seven-Segment LED Decoder RTL Combinational Circuit
- Barrel Shifter RTL Combinational Circuit
- Simplified Floating Point Arithmetic. RTL Combinational Circuit
- BCD Number Format. RTL Combinational Circuit
- DDFS. Direct Digital Frequency Synthesis for Sound
- FPGA ADSR envelope generator for sound synthesis
- AMD Xilinx 7 series FPGAs XADC
- Building FPGA-Based Music Instrument Synthesis: A Simple Test Bench Solution
Top Comments