vivado 仿真工程中$readmemh 使用

原文网站: vivado 仿真工程中$readmemh 使用 – 芯片天地

在verilog 仿真中,有时会使用$readmemh系统函数,帮助仿真者快速装载仿真所使用的数据。在一些简单的仿真中, 我们可以通过initial 来加载仿真的数据,但是当仿真数据比较多的情况下, 就需要使用系统函数加载数据了。$readmemh 系统函数就是用来帮助开发者加载仿真数据的。$readmemh系统函数本身具有文件操作功能,因此不需要$fopen等文件操作。通常来说 , $readmemh 也被归为文件操作类型的系统函数。

1.$readmemh 语法格式

$readmemh("<数据文件名>",<存储器名>);
$readmemh("<数据文件名>",<存储器名>,<起始地址>);
$readmemh("<数据文件名>",<存储器名>,<起始地址>,<终止地址>);

其中:

  • <数据文件名> 是指向一个文本文件,用来保存仿真的数据。每一行代表一个十六进制的数据。
  • <存储器名> 为仿真文件中例化的存储器的名称。
  • <起始地址>,<终止地址> 指示将文本文件中的数据存储到存储器的位置段。
  • 注:如果存储器的位宽是8-bit的, 那么使用$readmemh 将读取文本文件中每行的最后一个byte(8-bit)

2.$readmemh在Vivado下仿真的使用

  • 存储器类型变量初始化

以下举例说明:

reg [7:0] ram[0:127];
initial
begin
    $readmemh ("test.txt", ram);
end

例化一个 8-bit 宽的ram存储器,深度为128。 将文本文件test.txt 文件中每一行最后一个byte 数据,存储到ram中, 从ram[0] 开始, 直到test.txt 文件的最后一行结束,或者是读出的数据达到 ram 的最大深度。文本文件的格式需要注意只能使用0-9,a-f,每一行的末尾需要以回车换行结束。使用$readmemh的仿真文件如例1所示:

例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

其中仿真引入的文件led_sim.sim 为文本文件, 缺省的目录为仿真执行的目录:

图1

只要将文本文件led_sim.sim 放到xsim目录下,就可以打开了。在仿真文件中可以按如下方式引用,

localparam FILE_NAME = “led_sim.sim”;

使用xsim目录的缺点是每次仿真复位后(右键点击SIMULTAION,选择Reset,如图2所示),会删除led_sim.sim 文件,用户需要重新制作文件,再次放入的xsim 这个目录中,比较麻烦; 如果将led_sim.sim 放到其他的目录下,可以避免这个问题的出现。

图2

因此上例中,并没有将led_sim.sim 放到xsim 这个目录下,而是使用的../../../led_sim.sim, 这时使用重定向来指定文本文件的位置:

图3

led_sim.sim 文件的内容及格式如下:

00abcdef
01
02
03
04
05
06
07
08
09
0a
0b
0c
0d
0e
0f
10
11
12
13
14

仿真输出结果如下:

图4

图4中显示了读取16 个 ram 中数据的结果。

修改这个仿真文件:

将 $readmemh (FILE_NAME, ram); 替换为 $readmemh (FILE_NAME, ram, 2, 8);  表示将led_sim.sim 文本文件中的数据 写入ram中 (从ram[2] 开始写, 写到ram[8] 结束)

仿真结果如下:

图5

注意图5中显示ram[00],ram[01],ram[09]-ram[15]的值都是0xxx(未初始化的值),真正初始化的只有ram[02]- ram[08]。使用 $readmemh (FILE_NAME, ram, 2, 8);  这种格式可以在一定范围内对存储器进行局部修改。

  • 利用$readmemh初始化block memory IP核

reg [7:0] ram [0:127];  这样的定义既可以在仿真中使用,也可以在综合真实项目中使用。但在真实的Verilog 项目中, 更多的情况下是使用block memory 例化的IP 实现数据存储。 那么block memory IP 中的数据装载,我们可以使用*.coe 文件, 但是*.coe 文件不够灵活,每次修改*.coe 文件,都要重新生成IP ,然后才能仿真。 本文将使用$readmemh 系统函数,快速的加载数据到block memory IP 中, 方便仿真调试。使用$readmemh初始化block memory IP核的工程文件如例2所示。

例2:

使用环境:

Vivado 版本: Vivado 2018.2

开发板: FII-PRX100-D

代码如下:

`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

例2中工程文件的主要功能是大约每330 ms,读取block memory (RAM_LED_VAL ) 中的数据,并将读出的数据显示到LED 上。其中在例化RAM_LED_VAL时,使用了*.coe文件,作为初始化的数据。

*.coe 文件内容及格式如下:

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;

注意:

  1. *.coe文件里的memory_initialization_radix有效的选择只有 2,10或是16。
  2. 注意数字之间可以用一个空格,一个逗号,或者回车隔开。
  3. 在memory_initialization_radix行或者memory_initialization_vector行结尾时需要使用分号。

RAM_LED_VAL  例化步骤如下:

图6

图7

图8

当前工程的仿真文件如例3所示:

例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

其中$readmemh (FILE_NAME, top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory); 是如何找到这个路径的呢?步骤如下:

  • 先注释这一行文件,然后执行仿真工程。
  • 在仿真Scope界面上(见图9)观察用户当前例化IP的位置。使用 . 连接上下级模块,例如top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst
  • 在路径的末尾添加memory,最终完整的路径为top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory

图9

在这里,就可以知道我们的IP 在什么地方了, 再将 $readmemh (FILE_NAME, top_inst.RAM_LED_VAL_inst.inst.native_mem_module.blk_mem_gen_v8_4_1_inst.memory); 这一行重新打开(不再注释掉),就可以将led_sim.sim 中的内容存储到RAM_LED_VAL_inst中,从而在仿真中替换了RAM_LED_VAL_inst 中原有的*.coe文件。 由于led_sim.sim 文件修改, 仿真文件执行速度都是比较快的, 所以这种方法要比修改*.coe文件, 重新生成IP 核,工作效率要高很多。

仿真结果如下:

图10

至此, 在不修改verilog 工程的情况下, 只是使用仿真系统任务,就可以轻松替换 IP 中的*.coe文件,快速的进行仿真了。

3.$readmemh在Vivado综合中的使用

$readmemh系统函数也可以在综合(Synthesis)时使用,下面的例3就是对存储器类型变量初始化。(注:Vivado生成的block memory暂时不支持在综合中使用$readmemh系统函数初始化数据,但是综合时不会报错。)

使用$readmemh对存储器类型变量初始化时,应注意存储器单元的引用必须使用变量索引,不能使用常数索引,如下例所示:

错误使用:

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

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

正确使用:

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

例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

综合后生成*.bit文件在FII-PRX100-D开发板上的实验现象与仿真结果不符合。在写完21个数据后,从地址(led_addr )22-31,输出的led_reg结果确实为0。但是,从led_addr 等于32开始,又重复出现了前面的21个数据,如图11所示(ILA 输出结果)。

图11

解决方案:

将led_sim.sim文本文件写满128行数据,以对应128位不同的地址。总结来说,在Vivado下使用$readmemh系统函数进行综合建议保持文本文件的行数和存储器的深度一致。

4.$readmemh在Quartus及ModelSim下使用

考虑到差异性,将上面的例子做简单修改,在Quartus及Modelsim下进行测试,工程文件及仿真文件如例4所示。

例4:

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;
(* mark_debug = "true" *)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;

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

reg [7:0] ram[0:127];
(* mark_debug = "true" *)reg [7:0] led_reg;
(* mark_debug = "true" *)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

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
  • ModelSim下仿真

对存储器型变量中使用$readmemh在modelsim下进行仿真,有如图12所示警告,ram的值没有按照文件中给定的值初始化。ModelSim 要求$readmemh读取的文本文件每行的数据宽度必须小于等于存储器定义宽度。

 图12

修改方案: 将led_sim.sim 文本文件中的第一行由00abcdef 修改为 ef 即可。修改后的仿真结果如图13所示。

图13

从图13中可以看出,ModelSim下对存储器型变量使用$readmemh的仿真结果是正确的。

  • Quartus 下利用$readmemh进行综合测试

例4中工程综合后并在FII-PRA006(Altera FPGA)开发板上进行测试,结果与仿真一致,可以正确运行。同时从例4也可以看出,在Quartus常量索引存储器或者使用变量索引存储器都是可行的。

由此可见,$readmemh系统函数在仿真中使用较多,在实际综合中使用还是需要注意一些限制条件。

  • 13
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Vivado JESD仿真工程是基于Xilinx Vivado设计套件的一个仿真工程,主要用于测试和验证JESD协议接口的功能和性能。该工程包含了仿真模型、测试向量、仿真脚本和波形分析工具,可以模拟JESD接口在各种不同工作条件下的行为,以验证设计性能、功能和一致性。 Vivado JESD仿真工程涵盖了系统级仿真和硬件级仿真两个层面。在系统级仿真,模拟的是JESD接口在应用层的行为,包括数据打包、传输和解包等过程;在硬件级仿真,模拟的是JESD接口在硬件电路上的实现,包括时钟同步、应答信号和状态机等电路设计。 为了保证仿真的可靠性和准确性,Vivado JESD仿真工程需要进行严格的测试和验证。在仿真前,需要根据具体设计要求,制定合适的测试方案和仿真测试用例;在仿真过程,需要监控仿真实验结果,分析调试错误,同时还要根据具体情况进行仿真时钟和仿真时序的优化。 总之,通过Vivado JESD仿真工程的设计和使用可以提高JESD接口设计的可靠性和性能,并帮助设计人员更好地理解和掌握JESD协议接口的相关知识。 ### 回答2: Vivado是一款Xilinx公司开发的工具软件,可以用于FPGA芯片的设计和仿真。其,JESD是一种串行数据接口协议,用于高速数据传输。因此,Vivado Jesd仿真工程是通过Vivado工具和JESD接口协议进行数据传输的仿真工程。 在建立仿真工程之前,需要首先创建一个Vivado项目,并将需要仿真的设计文件添加到该项目。随后,在仿真设置,需要在仿真波形配置添加实例化的JESD模型和信号属性,以便Vivado可以模拟接收和发送数据。此外,还需要对仿真时钟进行配置,以确保仿真时钟与JESD接口时钟同步。 在进行仿真时,可以通过不同的测试模式和测试用例来验证JESD接口的性能和功能。在仿真数据分析,可以通过观察波形图,查看时序、波特率以及误码率等数据。如果在仿真过程发现问题,可以通过对仿真设置和仿真时钟进行调试,寻找问题原因,并进行修复。 总之,Vivado Jesd仿真工程可以帮助工程师验证并优化JESD接口设计,提高数据传输的可靠性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值