verilog – Generate a sine wave

This ip core simply generates a sine wave according a .mem file. It is required to specify rom depth equal to number of the sine points, the init file and the data size contained in the file. The phase offset and frequency are used as control signals.

It also houses the ROM module as well as the ip core and ROM testbenches. The tcl scripts allow you to run Modelsim from the console due to the bash scripts.

https://www.daycounter.com/Calculators/Sine-Generator-Calculator.phtml – points of the sine LUT calculator.

dds.v

`timescale 1ns / 1ps

module dds #
(
    parameter integer DATA_WIDTH         = 16                    ,
    parameter integer ROM_DEPTH          = 1024                  ,
    parameter         INIT_FILE          = ""                    ,
   
    parameter integer FCW_WIDTH          = $clog2(ROM_DEPTH) + 14,
    parameter integer PHASE_OFFSET_WIDTH = 10
)
(
    input  wire                              clk_i            ,
    input  wire                              s_rst_n_i        ,
    input  wire                              en_i             ,
    
    input  wire (FCW_WIDTH - 1 : 0)          fcw_i            ,
    input  wire (PHASE_OFFSET_WIDTH - 1 : 0) phase_offset_i   , //for instance: ROM_DEPTH / 2 == (2 * pi) / 2 == pi
    input  wire                              phase_offset_wr_i,
    
    output wire (DATA_WIDTH - 1 : 0)        sinus_o
);
    localparam integer LOOKUP_TABLE_INDEX_WIDTH = $clog2(ROM_DEPTH);
    localparam integer ACCUMULATOR_WIDTH        = FCW_WIDTH;

    wire (LOOKUP_TABLE_INDEX_WIDTH - 1 : 0) lookup_table_index;
    
    rom_distributed #
    (
        .DATA_WIDTH    (DATA_WIDTH              ),
        .ROM_DEPTH     (ROM_DEPTH               ),
        .INIT_FILE     (INIT_FILE               ),
        .ADDRESS_WIDTH (LOOKUP_TABLE_INDEX_WIDTH)
    ) 
    rom_distributed_inst
    (
        .address_i (lookup_table_index),
        .data_o    (sinus_o           )
    );
    
    dds_generator # 
    (
        .LOOKUP_TABLE_INDEX_WIDTH (LOOKUP_TABLE_INDEX_WIDTH),
        .ACCUMULATOR_WIDTH        (ACCUMULATOR_WIDTH       ),
        .PHASE_OFFSET_WIDTH       (PHASE_OFFSET_WIDTH      ),
        .FCW_WIDTH                (FCW_WIDTH               )
    )
    dds_generator_inst
    (
        .clk_i                (clk_i             ),
        .s_rst_n_i            (s_rst_n_i         ),
        .en_i                 (en_i              ),
             
        .phase_offset_wr_i    (phase_offset_wr_i ),                      
        .phase_offset_i       (phase_offset_i    ),
        .fcw_i                (fcw_i             ),
                              
        .lookup_table_index_o (lookup_table_index)
     ); 
endmodule

dds_tb.v

`timescale 1ns / 1ps

//The paths need to be changed
`define MEM_FILE_0   "../dds_0.mem"
`define MEM_FILE_2   "../dds_2.mem"

module dds_tb;

    localparam integer                        CLOCK_PERIOD         = 100                                  ;
    localparam integer                        ITERATION_NUM        = 4 * 1000000                          ;

    localparam integer                        DATA_WIDTH           = 16                                   ;
    localparam integer                        LUT_INDEX_REST       = 14                                   ;
   
    localparam integer                        ROM_DEPTH_0          = 1024                                 ;
    localparam integer                        PHASE_OFFSET_WIDTH_0 = 10                                   ;
    localparam integer                        FCW_WIDTH_0          = PHASE_OFFSET_WIDTH_0 + LUT_INDEX_REST;
    localparam (FCW_WIDTH_0 - 1 : 0)          PI_0                 = 10'h200                              ;
    localparam (FCW_WIDTH_0 - 1 : 0)          SINE_FREQ_0          = {FCW_WIDTH_0{1'h0}} + 9'h100         ;
    localparam (PHASE_OFFSET_WIDTH_0 - 1 : 0) PHASE_OFFSET_0       = {PHASE_OFFSET_WIDTH_0{1'h0}}         ;
    localparam (PHASE_OFFSET_WIDTH_0 - 1 : 0) PHASE_OFFSET_1       = {PHASE_OFFSET_WIDTH_0{1'h0}} + PI_0  ;       
   
    localparam integer                        ROM_DEPTH_2          = 512                                  ;
    localparam integer                        PHASE_OFFSET_WIDTH_2 = 9                                    ;
    localparam integer                        FCW_WIDTH_2          = PHASE_OFFSET_WIDTH_2 + LUT_INDEX_REST;
    localparam (FCW_WIDTH_0 - 1 : 0)          PI_2                 = 9'h100                               ;
    localparam (FCW_WIDTH_0 - 1 : 0)          SINE_FREQ_2          = {FCW_WIDTH_0{1'h0}} + 13'h1000       ;
    localparam (PHASE_OFFSET_WIDTH_0 - 1 : 0) PHASE_OFFSET_2       = {PHASE_OFFSET_WIDTH_2{1'h0}}         ; 
    localparam (PHASE_OFFSET_WIDTH_0 - 1 : 0) PHASE_OFFSET_3       = {PHASE_OFFSET_WIDTH_2{1'h0}} + PI_2  ; 
    
    wire (DATA_WIDTH - 1 : 0) sinus_0;
    wire (DATA_WIDTH - 1 : 0) sinus_1;
    wire (DATA_WIDTH - 1 : 0) sinus_2;
    wire (DATA_WIDTH - 1 : 0) sinus_3;

    reg                       clk               = 1'h0;
    reg                       s_rst_n           = 1'h0;
    reg                       en                = 1'h0;
    reg                       phase_offset_wr_0 = 1'h0;
    reg                       phase_offset_wr_1 = 1'h0;
    reg                       phase_offset_wr_2 = 1'h0;
    reg                       phase_offset_wr_3 = 1'h0;
    reg (FCW_WIDTH_0 - 1 : 0) fcw_0             = SINE_FREQ_0;
    reg (FCW_WIDTH_0 - 1 : 0) fcw_1             = SINE_FREQ_0;
    reg (FCW_WIDTH_2 - 1 : 0) fcw_2             = SINE_FREQ_2;
    reg (FCW_WIDTH_2 - 1 : 0) fcw_3             = SINE_FREQ_2;
    
    reg (PHASE_OFFSET_WIDTH_0 - 1 : 0) phase_offset_0 = PHASE_OFFSET_0; 
    reg (PHASE_OFFSET_WIDTH_0 - 1 : 0) phase_offset_1 = PHASE_OFFSET_1; 
    reg (PHASE_OFFSET_WIDTH_2 - 1 : 0) phase_offset_2 = PHASE_OFFSET_2; 
    reg (PHASE_OFFSET_WIDTH_2 - 1 : 0) phase_offset_3 = PHASE_OFFSET_3;

    dds # 
    ( 
        .DATA_WIDTH         (DATA_WIDTH          ),
        .ROM_DEPTH          (ROM_DEPTH_0         ),
        .INIT_FILE          (`MEM_FILE_0         ),
        .FCW_WIDTH          (FCW_WIDTH_0         ),
        .PHASE_OFFSET_WIDTH (PHASE_OFFSET_WIDTH_0)
    )
    sinus_generator_dut_0
    (
        .clk_i             (clk              ),
        .s_rst_n_i         (s_rst_n          ),
        .en_i              (en               ),
       
        .fcw_i             (fcw_0            ),
        .phase_offset_i    (phase_offset_0   ),
        .phase_offset_wr_i (phase_offset_wr_0),
        .sinus_o           (sinus_0          )
    );
    
    dds # 
    ( 
        .DATA_WIDTH         (DATA_WIDTH          ),
        .ROM_DEPTH          (ROM_DEPTH_0         ),
        .INIT_FILE          (`MEM_FILE_0         ),
        .FCW_WIDTH          (FCW_WIDTH_0         ),
        .PHASE_OFFSET_WIDTH (PHASE_OFFSET_WIDTH_0)
    )
    sinus_generator_dut_1
    (
        .clk_i             (clk              ),
        .s_rst_n_i         (s_rst_n          ),
        .en_i              (en               ),
     
        .fcw_i             (fcw_1            ),
        .phase_offset_i    (phase_offset_1   ),
        .phase_offset_wr_i (phase_offset_wr_1),
        .sinus_o           (sinus_1          )    
    );    
    
    dds # 
    ( 
        .DATA_WIDTH         (DATA_WIDTH          ),
        .ROM_DEPTH          (ROM_DEPTH_2         ),
        .INIT_FILE          (`MEM_FILE_2         ),
        .FCW_WIDTH          (FCW_WIDTH_2         ),
        .PHASE_OFFSET_WIDTH (PHASE_OFFSET_WIDTH_2)
    )
    sinus_generator_dut_2
    (
        .clk_i             (clk              ),
        .s_rst_n_i         (s_rst_n          ),
        .en_i              (en               ),
     
        .fcw_i             (fcw_2            ),
        .phase_offset_i    (phase_offset_2   ),
        .phase_offset_wr_i (phase_offset_wr_2),
        .sinus_o           (sinus_2          )    
    );                                   
    
    dds # 
    ( 
        .DATA_WIDTH         (DATA_WIDTH          ),
        .ROM_DEPTH          (ROM_DEPTH_2         ),
        .INIT_FILE          (`MEM_FILE_2         ),
        .FCW_WIDTH          (FCW_WIDTH_2         ),
        .PHASE_OFFSET_WIDTH (PHASE_OFFSET_WIDTH_2)
    )
    sinus_generator_dut_3
    (
        .clk_i             (clk              ),
        .s_rst_n_i         (s_rst_n          ),
        .en_i              (en               ),
     
        .fcw_i             (fcw_3            ),
        .phase_offset_i    (phase_offset_3   ),
        .phase_offset_wr_i (phase_offset_wr_3),
        .sinus_o           (sinus_3          )    
    );
    
    initial begin
        forever begin
            #(CLOCK_PERIOD / 2) clk = !clk;
        end 
    end

    initial begin
        @(posedge clk);
        
        s_rst_n <= 1'h1;
        en      <= 1'h1;
        @(posedge clk);
        
       repeat(ITERATION_NUM) begin
           @(posedge clk);  
       end
       
       phase_offset_wr_0 <= 1'h1;
       phase_offset_0    <= PHASE_OFFSET_1;
       phase_offset_wr_2 <= 1'h1;
       phase_offset_2    <= PHASE_OFFSET_3;
       @(posedge clk);
       phase_offset_wr_0 <= 1'h0;
       phase_offset_wr_2 <= 1'h0;
       
       repeat(ITERATION_NUM) begin
           @(posedge clk);  
       end

        $stop();
    end

endmodule

rom_distributed.v

`timescale 1ns / 1ps

module rom_distributed #
(
    parameter integer DATA_WIDTH    = 8                ,
    parameter integer ROM_DEPTH     = 256              ,
    parameter         INIT_FILE     = "",
    parameter integer ADDRESS_WIDTH = $clog2(ROM_DEPTH)
) 
(
    input  wire (ADDRESS_WIDTH - 1 : 0) address_i,
    output wire (DATA_WIDTH - 1 : 0)    data_o
);
    reg (DATA_WIDTH - 1 : 0) rom_memory (ROM_DEPTH - 1 : 0);

    initial begin
        if ("" != INIT_FILE) begin
            $readmemh(INIT_FILE, rom_memory);
        end
        else begin
            $error("A rom init file is not specified.");
        end
    end

    assign data_o = rom_memory(address_i);
endmodule 

rom_distributed_tb.v

`timescale 1ns / 1ps

 //The path needs to be changed
`define INIT_FILE      "../rom_async.mem"

module rom_distributed_tb;
    localparam integer DATA_WIDTH = 16                   ;
    localparam integer ROM_DEPTH  = 1024                 ;
    localparam integer ADDRESS_WIDTH  = $clog2(ROM_DEPTH);   
     
    wire (DATA_WIDTH - 1 : 0) dut_value;
    
    reg (DATA_WIDTH - 1 : 0)    file_value;
    reg (ADDRESS_WIDTH - 1 : 0) address   ;
    
    integer file   = 0;
    integer errors = 0;
 
    rom_distributed #
    (
        .DATA_WIDTH    (DATA_WIDTH   ),
        .ROM_DEPTH     (ROM_DEPTH    ),
        .INIT_FILE     (`INIT_FILE   ),
        .ADDRESS_WIDTH (ADDRESS_WIDTH)
    ) 
    rom_distributed_dut
    (
        .address_i (address  ),
        .data_o    (dut_value)
    );
    
    initial begin
        file_value = 0;
        address    = 0;
        
        file = $fopen(`INIT_FILE, "r");
        
        if (0 != file) begin
            repeat(ROM_DEPTH) begin
                $fscanf(file, "%h", file_value); 
                 
                #100 if (file_value !== dut_value) begin
                    errors = errors + 1;
                end
                 
                 address = address + 1;   
            end
            
            if (0 == errors) begin
                $display("The test passed.n");
            end
            else begin
                $display("The test failed with %d errors.n", errors);
            end
        end
        else begin
            $display("A descripter of a '%s' file is 0.n", `INIT_FILE);
        end
        
        $stop();
    end
endmodule

dds_generator.v

`timescale 1ns / 1ps

module dds_generator # 
(
    parameter integer LOOKUP_TABLE_INDEX_WIDTH = 10,
    parameter integer ACCUMULATOR_WIDTH        = 24,
    parameter integer PHASE_OFFSET_WIDTH       = 10,
    parameter integer FCW_WIDTH                = 8        
)
(
    input  wire                                    clk_i               ,
    input  wire                                    s_rst_n_i           ,
    input  wire                                    en_i                ,
    
    input  wire                                    phase_offset_wr_i   ,
    input  wire (PHASE_OFFSET_WIDTH - 1 : 0)       phase_offset_i      ,
    input  wire (FCW_WIDTH - 1 : 0)                fcw_i               ,
    
    output wire (LOOKUP_TABLE_INDEX_WIDTH - 1 : 0) lookup_table_index_o
);
    localparam integer ACCUMULATOR_WIDTH_LSB = ACCUMULATOR_WIDTH - LOOKUP_TABLE_INDEX_WIDTH;
    
    reg (ACCUMULATOR_WIDTH - 1:0) accumulator;

    always @ (posedge clk_i) begin
        if(1'h0 == s_rst_n_i ) begin
            accumulator(ACCUMULATOR_WIDTH_LSB - 1 : 0)             <= 0;
            accumulator(ACCUMULATOR_WIDTH : ACCUMULATOR_WIDTH_LSB) <=  phase_offset_i;              
        end
        else if (1'h1 ==  phase_offset_wr_i) begin
            accumulator(ACCUMULATOR_WIDTH_LSB - 1 : 0)             <= 0;
            accumulator(ACCUMULATOR_WIDTH : ACCUMULATOR_WIDTH_LSB) <=  phase_offset_i;
        end else if (1'h1 == en_i) begin
            if (({10{1'h1}} - 2) == accumulator(ACCUMULATOR_WIDTH : ACCUMULATOR_WIDTH_LSB)) begin
                accumulator(ACCUMULATOR_WIDTH : ACCUMULATOR_WIDTH_LSB) <= 0;
            end
            else begin
                accumulator <= accumulator + fcw_i;
            end
        end
    end
    
    assign lookup_table_index_o = accumulator(ACCUMULATOR_WIDTH : ACCUMULATOR_WIDTH_LSB);
endmodule

dds_tb

#!/bin/bash

#change the vivado_path to yours if required.
modelsim_path=/opt/modelsim/modelsim_ase/bin
tb_tcl_file_name=dds_tb.tcl

$modelsim_path/vsim -do $tb_tcl_file_name

dds_tb.tcl

transcript on
vlib work
vmap work work

# variables---------
set dut   ../../hdl/dds.v
set var_1 ../../hdl/dds_generator.v
set var_2 ../../hdl/rom_distributed.v
set tb    dds_tb.v
# ------------------

vlog $dut $tb $var_1 $var_2

 
vsim -t 100ns -voptargs="+acc" dds_tb

# waves    ---------
add wave /dds_tb/clk


add wave -radix hex -format  analog-step -max 65535 -min -65535 -height 100 /dds_tb/sinus_0
add wave -radix hex -format  analog-step -max 65535 -min -65535 -height 100 /dds_tb/sinus_1
add wave -radix hex -format  analog-step -max 32767 -min -32767 -height 100 /dds_tb/sinus_2
add wave -radix hex -format  analog-step -max 32767 -min -32767 -height 100 /dds_tb/sinus_3

add wave -radix hex /dds_tb/fcw_0
add wave -radix hex /dds_tb/phase_offset_0
add wave -radix hex /dds_tb/phase_offset_wr_0

add wave -radix hex /dds_tb/fcw_1
add wave -radix hex /dds_tb/phase_offset_1
add wave -radix hex /dds_tb/phase_offset_wr_1

add wave -radix hex /dds_tb/fcw_2
add wave -radix hex /dds_tb/phase_offset_2
add wave -radix hex /dds_tb/phase_offset_wr_2

add wave -radix hex /dds_tb/fcw_3
add wave -radix hex /dds_tb/phase_offset_3
add wave -radix hex /dds_tb/phase_offset_wr_3
# ------------------

configure wave -timelineunits us

run -all 
wave zoom full

rom_distributed_tb

#!/bin/bash

#change the vivado_path to yours if required.
modelsim_path=/opt/modelsim/modelsim_ase/bin
tb_tcl_file_name=rom_distributed_tb.tcl

$modelsim_path/vsim modelsim.ini -do $tb_tcl_file_name

rom_distributed_tb.tcl

transcript on
vlib work
vmap work work

# variables---------
set dut ../../hdl/rom_distributed.v
set tb rom_distributed_tb.v
# ------------------

vlog $dut $tb
 
vsim -t 100ns -voptargs="+acc" rom_distributed_tb

# waves    ---------
add wave -radix hex /rom_distributed_tb/dut_value
add wave -radix hex /rom_distributed_tb/file_value
add wave -radix hex /rom_distributed_tb/errors
# ------------------

configure wave -timelineunits us

run -all 
wave zoom full

clear

#!/bin/bash

rm transcript modelsim.ini *vsim.wlf -r work