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>);
- <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
`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
00abcdef 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
Modify this simulation file:
-
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:
- The only valid choices for memory_initialization_radix in the *.coe file are 2, 10 or 16.
- Note that numbers can be separated by a space, a comma, or a carriage return.
- 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