Menu Close

$readmemh used in vivado simulation project

In Verilog simulation, the $readmemh system function is sometimes used to help the simulator quickly load the data used for the simulation. In some simple simulations, we can load the simulation data through the initial, but when the simulation data is large, we need to use the system function to load the data. The $readmemh system function is used to help developers load simulation data. The $readmemh system function itself has file operation functions, so file operations such as $fopen are not required. In general, $readmemh is also classified as a system function of the file operation type.

1. $readmemh syntax format

 $readmemh ("<data file name>",<memory name>);
 $readmemh ("<data file name>",<memory name>,<start address>);
 $readmemh ("<data file name>" ,<memory name>,<start address>,<end address>);
in:
  • <data file name> refers to a text file used to save the simulation data. Each row represents a hexadecimal data.
  • <memory name> is the name of the memory instantiated in the simulation file.
  • <start address>, <end address> Indicate the location segment where the data in the text file is stored to memory.
  • Note: If the bit width of the memory is 8-bit, then using $readmemh will read the last byte (8-bit) of each line in the text file

2. The use of $readmemh simulation under Vivado

  • memory type variable initialization

The following examples illustrate:

reg[7:0] ram[0:127];
initial
begin
    $readmemh ("test.txt", ram);
end
Instantiate an 8-bit wide ram memory with a depth of 128. Store the last byte data of each line in the text file test.txt file into ram, starting from ram[0], until the end of the last line of the test.txt file, or the read data reaches the maximum depth of ram. The format of the text file needs to be noted that only 0-9, af can be used, and the end of each line needs to be terminated with a carriage return and line feed. The simulation file using $readmemh is shown in Example 1:
example 1:
`timescale 1ns / 1ps
module sim_top(
);
    
reg clk = 0;
always clk = #10 ~clk;


reg [7:0] ram[0:127];

localparam FILE_NAME = "../../../led_sim.sim";
initial begin
    $readmemh (FILE_NAME, ram);
end

integer i;
initial
begin
    #20;
    for(i = 0; i < 16; i = i + 1)
        $display("ram[%02d] = 0x%h ", i, ram[ i ] );
    #8000;
    $stop;
end


    
endmodule

 

The file led_sim.sim introduced by the simulation is a text file, and the default directory is the directory where the simulation is executed:
figure 1
Just put the text file led_sim.sim in the xsim directory and it can be opened. In the simulation file it can be referenced as follows,
localparam FILE_NAME = “led_sim.sim”;
The disadvantage of using the xsim directory is that after each simulation reset (right-click SIMULATION, select Reset, as shown in Figure 2), the led_sim.sim file will be deleted, and the user needs to recreate the file and put it in the xsim directory again, which is troublesome; If you put led_sim.sim in another directory, this problem can be avoided.
figure 2
Therefore, in the above example, led_sim.sim is not placed in the xsim directory, but ../../../led_sim.sim is used, and redirection is used to specify the location of the text file:
image 3
The content and format of the led_sim.sim file are as follows:
00abcdef
01
02
03
04
05
06
07
08
09
0a
0b
0c
0d
0e
0f
10
11
12
13
14

 

The simulation output results are as follows:
Figure 4
Figure 4 shows the result of reading data in 16 rams.

Modify this simulation file:

Replace $readmemh (FILE_NAME, ram); with $readmemh (FILE_NAME, ram, 2, 8);  to write the data in the led_sim.sim text file into ram (start from ram[2], write to ram[ 8] end)
The simulation results are as follows:
Figure 5
Note that in Figure 5, the values ​​of ram[00], ram[01], ram[09]-ram[15] are all 0xxx (uninitialized values), and only ram[02]-ram[08] are really initialized. Use $readmemh (FILE_NAME, ram, 2, 8);  this format allows local modification of memory within a certain range.
  • Use $readmemh to initialize the block memory IP core

reg [7:0] ram [0:127]; Such a definition can be used both in simulation and in synthetic real projects. But in a real Verilog project, more often the data storage is implemented using block memory instantiated IP. Then, for data loading in block memory IP, we can use *.coe files, but *.coe files are not flexible enough. Every time you modify *.coe files, IP must be regenerated before simulation. This article will use the $readmemh system function to quickly load data into the block memory IP, which is convenient for simulation debugging. The project file that uses $readmemh to initialize the block memory IP core is shown in Example 2.

 

Example 2:

Use environment:

Vivado version: Vivado 2018.2

Development board: FII-PRX100-D

code shown as below:

`timescale 1ns / 1ps



module top #
(
    parameter SIM_DEBUG = "FALSE"
)
(
    input        OSC_CLK,
    output [7:0] LED
);

wire clk_100m;
wire locked;
SYS_MMCM  SYS_MMCM_inst
(
    // Clock in ports
    .clk_in1    (OSC_CLK),  // input clk_in1
    // Clock out ports
    .clk_out1   (clk_100m), // output clk_out1
    // Status and control signals
    .reset      (1'b0),     // input reset
    .locked     (locked)    // output locked
);

wire reset = ~locked;

reg  [7:0] led_val_in = 0;
reg  [9:0] led_addr = 0;
reg        led_wea = 0;

wire [7:0] led_val;
RAM_LED_VAL  RAM_LED_VAL_inst
(
    .clka   (clk_100m),     // input wire clka
    .wea    (led_wea),      // input wire [0 : 0] wea
    .addra  (led_addr),     // input wire [9 : 0] addra
    .dina   (led_val_in),   // input wire [7 : 0] dina
    .douta  (led_val)       // output wire [7 : 0] douta
);

reg [31:0] cnt = 0;
always @ (posedge clk_100m)
if(reset) cnt <= 0;
else cnt <= cnt + 1;

wire s_p = (SIM_DEBUG == "FALSE" ) ? cnt[24] : cnt[3];


reg [2:0] led_st = 0;
always @ (posedge clk_100m)
if(reset)
begin
    led_wea <= 0;
    led_st <= 0;
end
else case (led_st)
0:
begin
    led_addr <= 0;
    led_wea <= 0;
    led_st <= 1;
end
1:
begin
    if(s_p)
    begin
        led_addr <= led_addr + 1;
        led_st <= 2;
    end
end
2:
begin
    if(~s_p)
        led_st <= 1;
end
default: led_st <= 0;
endcase

assign LED = led_val;

endmodule

 

The main function of the project file in Example 2 is to read the data in the block memory (RAM_LED_VAL) about every 330 ms, and display the read data on the LED. When instantiating RAM_LED_VAL, the *.coe file is used as the initialized data.

The content and format of the *.coe file are as follows:

memory_initialization_radix = 16;
memory_initialization_vector = 
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00
ff 
00;

 

Notice:

  1. The only valid choices for memory_initialization_radix in the *.coe file are 2, 10 or 16.
  2. Note that numbers can be separated by a space, a comma, or a carriage return.
  3. A semicolon is required at the end of a memory_initialization_radix line or memory_initialization_vector line.

 

RAM_LED_VAL instantiation steps are as follows:

 

Image 6

Figure 7

Figure 8

The simulation file of the current project is shown in Example 3:

Example 3:

`timescale 1ns / 1ps



module sim_top(

    );
    
reg clk = 0;
always clk = #10 ~clk;


localparam FILE_NAME = "../../../led_sim.sim";
initial begin
    $readmemh (FILE_NAME, top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory);
end

integer i;
initial
begin
    #20;
    for(i = 0; i < 16; i = i + 1)
        $display("ram[%02d] = 0x%h ", i, ram[ i ] );
    #8000;
    $stop;
end

wire [7:0] LED;
top #
(
    .SIM_DEBUG ( "TRUE" )
)
top_inst
(
    .OSC_CLK    (clk),
    .LED        (LED)
);

    
endmodule

 

How does $readmemh (FILE_NAME, top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory); find this path? Proceed as follows:

  • Comment this line of file first, and then execute the simulation project.
  • On the Simulation Scope screen (see Figure 9) observe where the user is currently instantiating the IP. Use.  Connect upper and lower modules, such as top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst
  • Add memory at the end of the path, the final full path is top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst .memory

Figure 9

 

Here, we can know where our IP is, and then reopen the line $readmemh (FILE_NAME, top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory); The content in sim is stored in RAM_LED_VAL_inst, thus replacing the original *.coe file in RAM_LED_VAL_inst in simulation. Due to the modification of the led_sim.sim file, the execution speed of the simulation file is relatively fast, so this method is much more efficient than modifying the *.coe file and regenerating the IP core.

 

The simulation results are as follows:

Figure 10

 

So far, without modifying the Verilog project, just using the simulation system task, you can easily replace the *.coe file in the IP and simulate quickly.

3. Use of $readmemh in Vivado synthesis

The $readmemh system function can also be used in synthesis (Synthesis), the following example 3 is to initialize the memory type variable. ( Note: The block memory generated by Vivado does not support using the $readmemh system function to initialize data during synthesis, but no error will be reported during synthesis. )

When using $readmemh to initialize a memory type variable, it should be noted that the reference to the memory unit must use the variable index, not the constant index, as shown in the following example:

wrong usage:

reg[7:0] coeff_array[0:7];
reg[7:0] out = 0;

always@(posedge clk)begin
    out <= coeff_array[ 0 ];
end

use correctly:

reg[7:0] coeff_array[0:7];
reg[7:0] out = 0;
reg[3:0] addr = 0;

always@(posedge clk)begin
    out <= coeff_array[ addr ];
end

Example 3:

`timescale 1ns / 1ps



module test #
(
    parameter SIM_DEBUG = "FALSE"
)
(
    input        OSC_CLK,
    output [7:0] LED
);

wire clk_100m;
wire locked;
SYS_MMCM  SYS_MMCM_inst
(
    // Clock in ports
    .clk_in1    (OSC_CLK),  // input clk_in1
    // Clock out ports
    .clk_out1   (clk_100m), // output clk_out1
    // Status and control signals
    .reset      (1'b0),     // input reset
    .locked     (locked)    // output locked
);

wire reset = ~locked;

reg  [7:0] led_val_in = 0;
reg  [9:0] led_addr = 0;
reg        led_wea = 0;


reg [31:0] cnt = 0;
always @ (posedge clk_100m)
if(reset) cnt <= 0;
else      cnt <= cnt + 1;

wire s_p = (SIM_DEBUG == "FALSE" ) ? cnt[24] : cnt[3];

reg [7:0] ram[0:127];
reg [7:0] led_reg;
reg [2:0] led_st = 0;
always @ (posedge clk_100m)
if(reset)
begin
    led_addr <= 0;
    led_wea <= 0;
    led_st <= 0;
end
else case (led_st)
0:
begin
    led_addr <= 0;
    led_reg <= ram[led_addr];
    led_wea <= 0;
    led_st <= 1;
end
1:
begin
    if(s_p)
    begin
        led_addr <= led_addr + 1;
        led_st <= 2;
    end
end
2:
begin
    if(~s_p)
    begin
        led_reg <= ram[led_addr]; 
        led_st <= 1;
    end
        
end
default: led_st <= 0;
endcase


localparam FILE_NAME = "D:/PRJ/led_sim.sim";

initial begin
   $readmemh (FILE_NAME, ram);
end


assign LED = led_reg;


endmodule

 

The experimental phenomenon of the *.bit file generated on the FII-PRX100-D development board after synthesis is inconsistent with the simulation results. After writing 21 data, from address (led_addr) 22-31, the output led_reg result is indeed 0. However, starting with led_addr equal to 32, the previous 21 data are repeated, as shown in Figure 11 (ILA output).

 

Figure 11

solution:

Fill in the led_sim.sim text file with 128 lines of data to correspond to 128 different addresses. In conclusion, using the $readmemh system function for synthesis under Vivado recommends keeping the line count and memory depth of the text file consistent.

4. $readmemh is used under Quartus and ModelSim

Considering the differences, the above example is simply modified and tested under Quartus and Modelsim. The project file and simulation file are shown in Example 4.

Example 4:

Project files under Quartus

`timescale 1ns / 1ps



module readmemh_prj #
(
    parameter SIM_DEBUG = "FALSE"
)
(
    input        clk_50m,
    output [7:0] LED
);



wire reset = 1'b0;

reg  [7:0] led_val_in = 0;
reg  [6:0] led_addr = 0;
reg        led_wea = 0;


reg [31:0] cnt = 0;
always @ (posedge clk_50m)
if(reset) cnt <= 0;
else      cnt <= cnt + 1;

wire s_p = (SIM_DEBUG == "FALSE" ) ? cnt[23] : cnt[3];

reg [7:0] ram[0:127];
reg [7:0] led_reg;
reg [2:0] led_st = 0;
always @ (posedge clk_50m)
if(reset)
begin
    led_addr <= 0;
    led_wea <= 0;
    led_st <= 0;
end
else case (led_st)
0:
begin
    led_addr <= 0;
//    led_reg <= ram[led_addr];
    led_reg <= ram[0];       //测试综合后,是否正常运行
    led_wea <= 0;
    led_st <= 1;
end
1:
begin
    if(s_p)
    begin
        led_addr <= led_addr + 1;
        led_st <= 2;
    end
end
2:
begin
    if(~s_p)
    begin
        led_reg <= ram[led_addr]; 
        led_st <= 1;
    end
        
end
default: led_st <= 0;
endcase


localparam FILE_NAME = "D:/PRJ/led_sim.sim";

initial begin

   $readmemh (FILE_NAME, ram);
end


assign LED = led_reg;


endmodule

 

Simulation files under Quartus

`timescale 1ns / 1ps



module sim_top(

    );
    
reg clk = 0;
always clk = #10 ~clk;


integer i;
initial
begin
    #20;
    for(i = 0; i < 16; i = i + 1)
        $display("ram[%02d] = 0x%h ", i, top_inst.ram[ i ] );
    #8000;
    $stop;
end

wire [7:0] LED;
readmemh_prj #
(
    .SIM_DEBUG ( "TRUE" )
)
top_inst
(
    .clk_50m    (clk),
    .LED        (LED)
);

    
endmodule

 

  • Simulation under ModelSim

Use $readmemh to simulate the memory variable under Modelsim, there is a warning as shown in Figure 12, that the value of ram is not initialized according to the value given in the file. ModelSim requires that the data width of each line of the text file read by $readmemh must be less than or equal to the memory definition width.

 

Figure 12

Modification scheme: Modify the first line in the led_sim.sim text file from 00abcdef to ef . The modified simulation results are shown in Figure 13.

Figure 13

As can be seen from Figure 13, the simulation result of using $readmemh for memory variables under ModelSim is correct.

  • Comprehensive testing using $readmemh under Quartus

In example 4, after the project is synthesized and tested on the FII-PRA006 (Altera FPGA) development board, the results are consistent with the simulation and can be run correctly. At the same time, it can be seen from Example 4 that it is feasible to use the Quartus constant index memory or use the variable index memory.

It can be seen that the $readmemh system function is used more in simulation, and some restrictions need to be paid attention to in actual synthesis.

vivado project file download 

coe_prj.zip

 

Posted in FPGA, RISC-V, RISC-V Textbook, Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!