element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Achievement Levels
    • Benefits of Membership
    • Feedback and Support
    • Members Area
    • Personal Blogs
    • What's New on element14
  • Learn
    Learn
    • eBooks
    • Learning Center
    • Learning Groups
    • STEM Academy
    • Webinars, Training and Events
  • Technologies
    Technologies
    • 3D Printing
    • Experts & Guidance
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Arduino Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Project Groups
    • Raspberry Pi Projects
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Or 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
FPGA
  • Technologies
  • More
FPGA
Blog SystemVerilog Study Notes. DDFS. Direct Digital Frequency Synthesis for Sound
  • Blog
  • Forum
  • Documents
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
FPGA requires membership for participation - click to join
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: javagoza
  • Date Created: 19 Feb 2023 10:00 PM Date Created
  • Views 2210 views
  • Likes 16 likes
  • Comments 15 comments
  • waveforms
  • musictimech
  • AMD XILINX
  • DDFS
  • fpga
  • vivado
  • digilent
  • sound
  • amd
  • spartan-7
  • analog discovery 2
  • music
  • systemverilog
Related
Recommended

SystemVerilog Study Notes. DDFS. Direct Digital Frequency Synthesis for Sound

javagoza
javagoza
19 Feb 2023
DDFS Direct Digital Frequency Synthesis for Sound

DDFS - Direct Digital Frequency Synthesis

DDFS is a digitally-controlled method of generating multiple frequencies from a reference frequency source. DDFS is a method of producing a tunable digital or analog waveform. First the data points of the waveform are generated in digital format and the converted to analog format with a Digital to Analog Converter (DAC) and a Low Pass Filter (LPF)

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

Next we examine the synthesis and implementation of three types of signals:

  • Digital waves: square wave of constant amplitude
  • Unmodulated analog sine wave
  • Modulated sine wave, with phase, frequency, and amplitude controlled by other signals.

image

Table of Contents

  • DDFS - Direct Digital Frequency Synthesis
    • Direct synthesis of a square wave
    • Direct synthesis of an unmodulated analog waveform
    • Direct synthesis of a modulated analog waveform
  • DDFS module construction
    • Synchronous ROM Unit - Table Lookup
      • Sine lookup table SystemVerilog Implementation
      • Elaborated design by VIvado 2022.2
      • Synthesized Design With BRAM module
      • SystemVerilog Test Bench Implementation
      • Test bench scope
      • Resources Utilization
    • DDFS module implementation in SystemVerilog
      • Test Bench SystemVerilog Code
    • Conceptual Design
    • One-bit Delta Sigma SystemVerilog Implementation
      • Elaborated design by VIvado 2022.2
      • SystemVerilog Test Bench Implementation
      • Test bench scope
    • Low Pass Filter
  • Testing with the Spartan 7 FPGA  with the Digilent Arty S7
    • System test module in SystemVerilog
  • Generating a Triangle Wave
  • Next steps
  • SystemVerilog Study Notes Chapters

Direct synthesis of a square wave

It uses a fixed clock, an N-bit adder, and a phase register or phase accumulator. The frequency of the signal is controlled by a digital word (fcw) used to increment the phase of the signal. With each clock pulse, the phase register samples the sum of the value in the previous clock cycle and the frequency control word (fcw) to produce the output sequence for that cycle. The phase accumulator continuously accumulates the phase increment, and when it reaches its maximum value, an overflow occurs and the accumulator is reset to zero, completing one cycle of the frequency.

DDFS offers fast switching between output frequencies, fine frequency resolution, and operation over a wide range of frequencies.

Block diagram for synthesizing a digital waveform or square wave with constant amplitude:image

To synthesize the digital waveform the DDFS requires a register, the Phase Register or Phase Accumulator in the diagram, and an adder.

The output of the circuit is the MSB of the register output Q. It is a square wave with a duty cycle close to 50%.

The output frequency is controlled by the M frequency N-bits word, known as frequency control word. This value is added to the phase register in every clock cycle.

Main parameters of the digital waveform generator.

  • N: word width of the register and the adder.
  • fsys and tsys: frequency and period of the system clock
  • fout and tout: frequency and period of the output signal.
  • M: frequency control word

 The Phase Accumulator starts with value 0 and gradually increments to 2^N-1 and then wraps around.

The MSB of Q starts as 0 and then changes to 1 when the Phase Accumulator reaches halfway of 2^N-1. tout, the output signal period, is the duration of incrementing the phase accumulator from 0 to 2^N-1,

M is added to the Phase Accumulator each system clock cycle, it then requires (2^N)/M cycles to complete one cycle, and its duration is: tout = ((2^N)*/M) * tsys

Rewritten in term of frequencies: fout = M * (fsys/2^N)

As N increases the finer frequency can be obtained. In the experiments of this blog we will use a value of 30 bits.

To obtain the target frequency we need to calculate the approximate M value: M = (fout / fsys)*2^N

The value of M must be rounded to a whole integer number. The variation in frequency produced by the rounding error is known as jitter.

Direct synthesis of an unmodulated analog waveform

We can generate an unmodulated analog waveform mapping phases to digitized amplitude points and then converting the values to the analog format by a DAC.

The conceptual diagram of the direct synthesis of an unmodulated analog waveform.

image

The previous digital waveform scheme uses the MSB of the Phase/Accumulator Register as the output. The Most Significant Bit, MSB, divides tout in two equal regions. Using mor bits we can divide tout to smaller regions or phases of a period. Then we can map the phases to digitized amplitude points.

The Phase to Amplitude Lookup Table performs the mapping. I can be implemented by a ROM or RAM. We will use a synchronous ROM.

The DAC converts the digitized amplitude value to an analog value and the Low Pass Filter removes unwanted high-frequency signals.

The "shape" of the analog waveform is determined by the values stored in the lookup table. We can generate any type of analog waveform. In this blog we will play with sine and triangular waves.

We don't use all the N bits for the lookup table. We'll use 8 MSBs from the N-bit phase register output, S-width in the diagram. A larger S increases the size of the lookup table. A small S puts more constraints on the low pass filter.

Direct synthesis of a modulated analog waveform

Modulation is the process of modifying a carrier signal in accordance with a message signal. We can modulate the analog signal in amplitude, frequency and phase modulation.

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

We can then expand the DDFS system incorporating the desired modulation scheme by inserting additional adders or multipliers in its path:

image

The above diagram shows a conceptual diagram of a DDFS system that supports all the 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 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

We use 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

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.

We will take advantage of the possibility to define initial values of FPGA's internal memory modules. When an SRAM-based FPGA device is programmed, the configuration file is loaded to 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.

We will use 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.txt", ram);
    
    // read operation
    always_ff @(posedge clk)
    begin
        data_reg <= ram[addr_r];
    end
    
    assign dout = data_reg;    
endmodule

Elaborated design by VIvado 2022.2

image

Synthesized Design With BRAM module

image

SystemVerilog Test Bench Implementation

`timescale 1ns / 1ps
`define ADDR_WIDTH 8
`define DATA_WIDTH 16

// sin table rom test bench
module sin_table_tb;
    localparam T=20;
    logic clk;
    logic [`ADDR_WIDTH-1:0] addr_r;
    logic [`DATA_WIDTH-1:0] dout;

    sin_rom uut( .clk(clk), 
            .addr_r(addr_r),
            .dout(dout));
            
                    
    always
    begin
        clk = 1'b1;
        #(T/2);
        clk = 1'b0;
        #(T/2);
    end
    
    initial begin
        addr_r = 16'h00; #40;
        addr_r = 16'h02; #40;
        addr_r = 16'h04; #40;
        addr_r = 16'h08; #40;
        addr_r = 16'h0f; #40;
        addr_r = 16'hf0; #40;
        addr_r = 16'hf2; #40;
        addr_r = 16'hf4; #40;
        addr_r = 16'hf8; #40;
        addr_r = 16'hff; #40;
    
    $stop;
    end
endmodule

Test bench scope

image

Resources Utilization

imageCreating the sine wave table with Excel.

ROM Sine Wave Table

0000 0324 0648 096b 0c8c 0fab 12c8 15e2 18f9 1c0c 1f1a 2224 2528 2827 2b1f 2e11 30fc 33df 36ba 398d 3c57 3f17 41ce 447b 471d 49b4 4c40 4ec0 5134 539b 55f6 5843 5a82 5cb4 5ed7 60ec 62f2 64e9 66d0 68a7 6a6e 6c24 6dca 6f5f 70e3 7255 73b6 7505 7642 776c 7885 798a 7a7d 7b5d 7c2a 7ce4 7d8a 7e1e 7e9d 7f0a 7f62 7fa7 7fd9 7ff6 7fff 7ff6 7fd9 7fa7 7f62 7f0a 7e9d 7e1e 7d8a 7ce4 7c2a 7b5d 7a7d 798a 7885 776c 7642 7505 73b6 7255 70e3 6f5f 6dca 6c24 6a6e 68a7 66d0 64e9 62f2 60ec 5ed7 5cb4 5a82 5843 55f6 539b 5134 4ec0 4c40 49b4 471d 447b 41ce 3f17 3c57 398d 36ba 33df 30fc 2e11 2b1f 2827 2528 2224 1f1a 1c0c 18f9 15e2 12c8 0fab 0c8c 096b 0648 0324 0000 fcdc f9b8 f695 f374 f055 ed38 ea1e e707 e3f4 e0e6 dddc dad8 d7d9 d4e1 d1ef cf04 cc21 c946 c673 c3a9 c0e9 be32 bb85 b8e3 b64c b3c0 b140 aecc ac65 aa0a a7bd a57e a34c a129 9f14 9d0e 9b17 9930 9759 9592 93dc 9236 90a1 8f1d 8dab 8c4a 8afb 89be 8894 877b 8676 8583 84a3 83d6 831c 8276 81e2 8163 80f6 809e 8059 8027 800a 8001 800a 8027 8059 809e 80f6 8163 81e2 8276 831c 83d6 84a3 8583 8676 877b 8894 89be 8afb 8c4a 8dab 8f1d 90a1 9236 93dc 9592 9759 9930 9b17 9d0e 9f14 a129 a34c a57e a7bd aa0a ac65 aecc b140 b3c0 b64c b8e3 bb85 be32 c0e9 c3a9 c673 c946 cc21 cf04 d1ef d4e1 d7d9 dad8 dddc e0e6 e3f4 e707 ea1e ed38 f055 f374 f695 f9b8 fcdc

Graphic representation of the sine wave table. Values in 16 bits signed format

image

DDFS module implementation in SystemVerilog

We will build the DDFS module with two outputs pulse_out that is a square wave and the Pulse Code Modulation (PCM) output. This is the digitized sin wave.

The lookup table output (amp) and envelope are 16 bits wide. After multiplication we need to trim the 32-bit multiplication result back to 16 bits.

We use 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. We need to select the appropriate portion of the modulation signal and trim it back to the Q16.0 format, 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
    output  logic   [15:0]      pcm_out,                                 // pcm signal 
    output  logic               pulse_out                                // pulse out
    );
    
    // 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
    
    // body
    // instanciate sin() ROM
    sin_rom rom_unit
        (   .clk(clk),
            .addr_r(p2a_raddr),
            .dout(amp));
                  
    // 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);
    assign pcm_out = pcm_reg;
    assign pulse_out = phase_reg[PHASE_ACC_WIDTH-1];    
    
endmodule


Elaborated design by VIvado 2022.2

image

Test Bench SystemVerilog Code

`timescale 1ns / 1ps

module ddfs_tb;
    localparam T=20;
    localparam PHASE_ACC_WIDTH = 30;
    logic reset;
    logic clk;    
    logic   [PHASE_ACC_WIDTH-1:0]    freq_carrier_ctrl_word;            // frequency control word to generate carrier frequency
    logic   [PHASE_ACC_WIDTH-1:0]    freq_offset_ctrl_word;     // frequency offset control word
    logic   [PHASE_ACC_WIDTH-1:0]    phase_offset;               // phase offset
    logic   [15:0]      envelope;                       // Amplitude modulation: envelope, digitized value of A(t)
    logic   [15:0]      pcm_out;                    // pcm signal 
    logic               pulse_out;
    
    localparam M = (262.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH); // carrier freq 262 Hz Freq sys 100 Mhz, M = fout/fsys * 2^N
    // clk signal generation
    always 
    begin
        clk <= 1'b1;
        #(T/2);
        clk <= 1'b0;
        #(T/2); 
    end
    
    // instanciate ddfs
    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(envelope),
    .pcm_out(pcm_out),
    .pulse_out(pulse_out)
    );
    
    
    initial
    begin
        reset = 1'b1;   #40;
        reset = 1'b0;  freq_carrier_ctrl_word =M; freq_offset_ctrl_word = 0; phase_offset =0; envelope= 16'd1; #1000000000;
    end
endmodule

Test Bench Scope

imageOne-bit delta-sigma DAC

In order to convert the PCM signal to a true analog signal we need a DAC (digital-to-analog converter) and a low-pass-filter.

We will use 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.

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

Delta-Sigma DACs are actually 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 art 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.

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 DDFS lookup table is 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

Elaborated design by VIvado 2022.2

image

SystemVerilog Test Bench Implementation

`timescale 1ns / 10ps

module ds_1bit_dac_tb(

    );
    localparam T = 20;
    localparam W = 16;
    localparam PHASE_ACC_WIDTH = 30;
    //localparam M = 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 M = 30'((262.0 / 100_000_000.0) * (1 <<PHASE_ACC_WIDTH)); 
     localparam M = 30'h0AFD; 
    
    // signal declaration
    logic clk;
    logic reset;
    logic pdm_out;
 
    logic   [PHASE_ACC_WIDTH-1:0]    freq_carrier_ctrl_word;    // frequency control word to generate carrier frequency
    logic   [PHASE_ACC_WIDTH-1:0]    freq_offset_ctrl_word;     // frequency offset control word
    logic   [PHASE_ACC_WIDTH-1:0]    phase_offset;              // phase offset
    logic   [15:0]      amp;                                    // Amplitude modulation: envelope, digitized value of A(t)
    logic   [15:0]      pcm;                                    // pcm signal 
    logic               pulse_out;


    
    // instantiate ds 1bit dac    
    ds_1bit_dac #(.W(W)) ds_1bit_uut(
        .clk(clk),
        .reset(reset),
        .pcm_in(pcm),
        .pdm_out(pdm_out)
    );
    
        // instanciate ddfs
    ddfs #( .PHASE_ACC_WIDTH(PHASE_ACC_WIDTH)) 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)
    );
    
    
   
   // registers
   always_ff @(posedge clk, posedge reset)
      if (reset) begin
         freq_carrier_ctrl_word <= 0;
         freq_offset_ctrl_word <= 0;
         phase_offset <= 0;
         amp <= 16'h4000;    // 1.00
      end 
      else begin
            freq_carrier_ctrl_word <= M;
            freq_offset_ctrl_word <= 0;
            phase_offset <= 0;
            amp = 16'h4000;
      end

    
    // clock 20 ns clock running for ever
    always 
    begin
        clk <= 1'b1;
        #(T/2);
        clk <= 1'b0;  
        #(T/2);   
   
    end
        
    // reset for the first cycle
    initial 
    begin
        reset = 1'b1;
        #(T/2);
        reset = 1'b0;
    end
        
    initial
    begin
   
       // @(negedge reset);   // wait reset to deasssert    
        @(negedge clk); // wait for one clock
        #200;
        reset = 1'b0;
        #(T/2);
        reset = 1'b1;
        #(T/2);
        reset = 1'b0;
                #(T/2);
        reset = 1'b1;
        #(T/2);
        reset = 1'b0;
                #(T/2);
        reset = 1'b1;
        #(T/2);
        reset = 1'b0;
                #(T/2);
        reset = 1'b1;
        #(T/2);
        reset = 1'b0;
        #200;
        
        $stop;
    end
endmodule

Test bench scope

image

More information on One-bit delta-sigma DAC: https://china.xilinx.com/content/dam/xilinx/support/documents/ip_documentation/xps_deltasigma_dac.pdf

Low Pass Filter

A simple passive RC low-pass filter is adequate for most applications. A 16mA LVTTL output buffer is used to provide maximum current drive.

image


There are three primary considerations in choosing values for the resistor and capacitor:

  • Output Source and Sink Current: Unlike normal digital applications, it is important that signal DACout always switch the entire voltage range from 0 V to VCCO (rail-to-rail). If the value of R is too low and signal DACout can not switch rail-to-rail, the analog output is non-linear; i.e., the absolute output voltage change resulting from incrementing or decrementing DACin is not constant.
  • Load Impedance: Keep the value of R low relative to the impedance of the load so that the current change through the capacitor due to loading becomes negligible.
  • Time Constant: The filter time constant (τ = RC) must be high enough to greatly attenuate the individual pulses in the pulse string. On the other hand, a high time constant may also attenuate the desired low-frequency output signal.

Testing with the Spartan 7 FPGA  with the Digilent Arty S7

Constraints file

https://github.com/Digilent/digilent-xdc/blob/master/Arty-S7-50-Master.xdc

## This file is a general .xdc for the Arty S7-50 Rev. E

## 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 }]; #IO_L20N_T3_A07_D23_14 Sch=jd1/ck_io[33]
set_property DRIVE 16 [get_ports audio_out]
set_property -dict { PACKAGE_PIN U12   IOSTANDARD LVCMOS33 } [get_ports { gain }]; #IO_L21P_T3_DQS_14 Sch=jd2/ck_io[32]
set_property -dict { PACKAGE_PIN V13   IOSTANDARD LVCMOS33 } [get_ports { pulse_out }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jd3/ck_io[31]
set_property -dict { PACKAGE_PIN T12   IOSTANDARD LVCMOS33 } [get_ports { neg_shutdown }]; #IO_L22P_T3_A05_D21_14 Sch=jd4/ck_io[30]
#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]


We will connect the RC LPF to the JD Pmod pin1 and the pulse out signal to the JD Pmod pin 3

Warning: Since the Pmod pins are connected to Spartan-7 FPGA pins using a 3.3V logic standard, care should be taken not to drive these pins over 3.4V.

Pmod JA iand Pmod JB are a High-Speed Pmod: The High-speed Pmods use the standard Pmod connector, but have their data signals routed as impedance matched differential pairs for maximum switching speeds. They have pads for loading resistors for added protection, but the Arty S7 ships with these loaded as 0-Ohm shunts. With the series resistors shunted, these Pmods offer no protection against short circuits, but allow for much faster switching speeds. The signals are paired to the adjacent signals in the same row: pins 1 and 2, pins 3 and 4, pins 7 and 8, and pins 9 and 10. Traces are routed 100 ohm (+/- 10%) differential.

These connectors should be used only when high speed differential signaling is required or the other Pmods are all occupied. If used as single-ended, coupled pairs may have significant crosstalk. In applications where this is a concern, the standard Pmod connector shall be used. Another option would be to ground one of the signals (drive it low from the FPGA) and use its pair for the signal-ended signal.

Since the High-Speed Pmods have 0-ohm shunts instead of protection resistors, the operator must take precaution to ensure that they do not cause any shorts.

System test module in SystemVerilog

`timescale 1ns / 10ps

module checkDDFSModule(
    input clk,        
    input logic [3:0] sw,
    input logic [3:0] btn,
    
    // PMOD 1
    output audio_out, // PMOD1_PIN1_R, audio out
    output gain,     // PMOD1_PIN2_R,  // 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   [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;  
    
    
    always_comb
    begin
        case (btn[3:2])
          2'b00: note = M1;
          2'b01: note = M2;
          2'b10: note = M3;
          2'b11: note = M4;
        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;
      


    // instanciate 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

Checking the signals generated with the oscilloscope.
I don't have an oscilloscope with enough bandwidth to view the digital signals from the DDFS but we can view the analog signals.

Sine waveform and the pulse out analog signals.

DDFS Pulse Out Sine PDM RC LF

FFT

image

Logic Analyzer- PDM output

The logic analyzer does not have the necessary bandwidth but something is intuited.

image

Generation of some notes in the 4th octave. I really don't really know what that means. My musical knowledge is something non-existent.

4th octave-  C note 261.6 Hz

image

4th octave-  D note 293.7 Hz

image

4th octave-  E note 329.6 Hz

image

4th octave-  F note 349.2 Hz

image

Combining the 4 waves

image

Generating a Triangle Wave

The good thing about this DDFS is that we can generate any analog waveform just by changing the table loaded in the ROM.
Later in other blogs we will generate waveforms of various instruments.

ROM Triangle Wave Table

0000 0200 0400 0600 0800 0A00 0C00 0E00 1000 1200 1400 1600 1800 19FF 1BFF 1DFF 1FFF 21FF 23FF 25FF 27FF 29FF 2BFF 2DFF 2FFF 31FF 33FF 35FF 37FF 39FF 3BFF 3DFF 3FFF 41FF 43FF 45FF 47FF 49FE 4BFE 4DFE 4FFE 51FE 53FE 55FE 57FE 59FE 5BFE 5DFE 5FFE 61FE 63FE 65FE 67FE 69FE 6BFE 6DFE 6FFE 71FE 73FE 75FE 77FE 79FD 7BFD 7DFD 7FFD 7DFD 7BFD 79FD 77FE 75FE 73FE 71FE 6FFE 6DFE 6BFE 69FE 67FE 65FE 63FE 61FE 5FFE 5DFE 5BFE 59FE 57FE 55FE 53FE 51FE 4FFE 4DFE 4BFE 49FE 47FF 45FF 43FF 41FF 3FFF 3DFF 3BFF 39FF 37FF 35FF 33FF 31FF 2FFF 2DFF 2BFF 29FF 27FF 25FF 23FF 21FF 1FFF 1DFF 1BFF 19FF 1800 1600 1400 1200 1000 0E00 0C00 0A00 0800 0600 0400 0200 0000 FE00 FC00 FA00 F800 F600 F400 F200 F000 EE00 EC00 EA00 E800 E601 E401 E201 E001 DE01 DC01 DA01 D801 D601 D401 D201 D001 CE01 CC01 CA01 C801 C601 C401 C201 C001 BE01 BC01 BA01 B801 B602 B402 B202 B002 AE02 AC02 AA02 A802 A602 A402 A202 A002 9E02 9C02 9A02 9802 9602 9402 9202 9002 8E02 8C02 8A02 8802 8603 8403 8203 8003 8203 8403 8603 8802 8A02 8C02 8E02 9002 9202 9402 9602 9802 9A02 9C02 9E02 A002 A202 A402 A602 A802 AA02 AC02 AE02 B002 B202 B402 B602 B801 BA01 BC01 BE01 C001 C201 C401 C601 C801 CA01 CC01 CE01 D001 D201 D401 D601 D801 DA01 DC01 DE01 E001 E201 E401 E601 E800 EA00 EC00 EE00 F000 F200 F400 F600 F800 FA00 FC00 FE00

Graphic representation of the data table values:

image

Triangle waveforms on the scope

Pulse out and triangle waveform and FFT

image

Filtered and unfiltered DAC out

Here we can see the effect of the RC LFP

image

and finally we connect the system with an amplified speaker. 

The two push buttons on the right change the frequency to another note. 

See the video at the beginning of the blog.


DDFS ARTY SY 50 triangle wave

Next steps

This is the first blog dedicated to generating sounds with the Spartan-7 FPGA within the SystemVerilog Study Notes series that I'm posting on element14.
The next steps will be to create a driver for Microblaze and design an ADSR attack-delay-sustain-release module.


SystemVerilog Study Notes Chapters

  1.  Gate-Level Combinational Circuit 
  2.  RTL Combinational Circuit Operators 
  3.  RTL Combinational Circuit - Concurrent and Control Constructs 
  4.  Hex-Digit to Seven-Segment LED Decoder RTL Combinational Circuit 
  5.  Barrel Shifter RTL Combinational Circuit 
  6.  Simplified Floating Point Arithmetic. RTL Combinational Circuit 
  7.  BCD Number Format. RTL Combinational Circuit 
  8.  DDFS. Direct Digital Frequency Synthesis for Sound 
  9.  FPGA ADSR envelope generator for sound synthesis 
  10.  AMD Xilinx 7 series FPGAs XADC 
  11.  Building FPGA-Based Music Instrument Synthesis: A Simple Test Bench Solution 
  • Sign in to reply

Top Comments

  • genebren
    genebren 9 months ago +1
    Very nice blog. I look forward to seeing your ADSR design. Thanks for sharing!
  • jc2048
    jc2048 9 months ago +1
    "Generation of some notes in the 4th octave. I really don't really know what that means." Numbering octaves like that is scientific pitch notation. This Wikipedia page tells you most of what you'd need…
  • shabaz
    shabaz 9 months ago +1
    Great blog! In case you're researching ideas, there's a classic "operator" style system that might be worth investigating, since you already have the basic mechanism for the DDS oscillator to work at…
  • javagoza
    javagoza 8 months ago

    First attempts with the ADSR module using a triangle wave.

    Attack 100ms, Decay 50ms, Sustain 100ms, Release 50ms. Frequency 262Hz. Sustain amplitude level 90%

    image

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • dang74
    dang74 9 months ago

    Very informative blog.  It had never occurred to me to use a triangle pattern in the memory instead of a sine wave.  I also like how you used a 1 bit DAC.  This general approach can be very helpful to me in one of my audio projects.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza 9 months ago in reply to DAB

    Thanks DAB !

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB 9 months ago

    Very good blog.

    You did a great job of walking through the design and explaining each section.

    Well done.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza 9 months ago in reply to shabaz

    Excellent. Great introduction to music synthesizers history and thanks for sharing the code. I will review it. The DDFS module allows both amplitude and frequency modulation and I will be able to use a couple of instances of the ADSR module as soon as it has it implemented, one to modulate in amplitude and the other to modulate in frequency. Let's see what comes out of all this, I first have to heal from the flu that does not let me work in the usual rhythm.

    • 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 © 2023 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