从零开始讲DDR(8)——AXI 接口MIG 使用(1)
一、前言
在之前的系列文章中,我们已经讨论过了MIG ip的接口内容,配置方式和modelsim独立仿真相关的内容,因此,本文对于之前已经讨论过的相关内容只做简单描述,着重介绍AXI 接口MIG使用上与普通ui接口的不同之处。感兴趣的读者可以自行阅读之前的相关系列文章。
二、MIG配置
在配置上,我们选择AXI4 Interface。
在AXI接口的选择上选择64bit,写优先模式。然后就是通过打开xilinx提供的example project,并导出modelsim独立仿真所需要的相关文件,构建自己的独立仿真工程。
三、AXI 接口MIG仿真文件解析
从example project导出的仿真文件中,我们可以看到如下的一系列测试平台列表
3.1 sim_tb_top.sv
3.1.1 注释说明
根据官方给出的注释说明,我们可以看到这部分的内容主要的功能是:(1)整个测试平台的顶层模块(2)例化了内存模型(3)生成了时钟和复位逻辑。
3.1.2 代码解析
sim_tb_top.sv中首先完成了一些必要信号的定义,然后就是进行了复位信号和时钟信号的产生,接下来就是例化了example_top模块,再之后就是例化的内存模型。这里我们并不需要关心内存模型是怎么实现的,我们只需要理解在sim_tb_top中核心功能就是例化了example_top模块。
而如果我们希望仿真我们自己独立的代码逻辑,有2种可行的方式:(1)把自己的逻辑替换example_top模块(2)注释掉example_top模块,把相关的数据线从内部逻辑转换成input/output/inout的形式,形成对外的接口,这样我们就可以把整个sim_tb_top模块当做一个例化了内存模型,能自主进行复位和时钟产生的DDR测试模型,这样把我们自己的代码逻辑接到此文件,就可以进行独立的DDR模型仿真了。
3.2 example_top
在编写我们自己独立的DDR读写逻辑之前,学习一下官方给出的示例工程还是非常必要的,我们知道在sim_tb_top中主要的功能是例化了example_top模块,因此,我们接下来来学习example_top模块。
3.2.1 注释说明
从注释中我们可以发现, example_top模块的主要功能是作为一个测试的顶层文件,允许用户自定义他们的设计。主要例化了DDR MIG(就是我们生成的IP)和一个可综合测试平台,用于建模用户的后端逻辑和传输不同的流量模式。
3.2.2 代码解析
从代码角度看,首先还是一些必要参数和信号定义,然后就是实例化了一个DDR MIG的ip模型,接下来例化了一个ddr4_v2_2_8_axi_tg_top模块。tg就是Traffic Generator的缩写。
首先可以看到的是对于 c0_init_calib_complete 信号的一个寄存,这里的主要目的是为了在c0_init_calib_complete信号拉高后,生成一个boot_mode_start的pulse信号。简单推测就是在c0_init_calib_complete拉高后,开始进行boot mode的流量测试。
接下来就是对于ddr4_v2_2_8_axi_tg_top的例化,ddr4_v2_2_8_axi_tg_top从接口上来看,主要包含3个部分:(1)AXI tg Input control signals (2)AXI MIG接口(3)Axi tg Error status signals。AXI MIG接口相对简单,就是用来和MIG进行连接的interface。Axi tg Error status signals看起来是和debug相关的,我们后面有需要的时候再详细展开,AXI tg Input control signal这里我们稍微关注一下:
从信号的名称来看,大概指的是这个 Traffic Generator 支持的3种模式,分别是boot,custom和prbs。这里的执行逻辑是 custom 模式暂时没有使用,首先在c0_init_calib_complete 拉高后进行boot模式,然后,在boot_mode_done之后,boot_mode_done被接到了prbs_mode_start接口上,意味着在完成boot模式后,执行prbs模式。
3.3 ddr4_v2_2_8_axi_tg_top
接下来我们来重点关注ddr4_v2_2_8_axi_tg_top模块
3.3.1 注释说明
这个模块实例化了boot/prbs/custom-mode_gen模块。 根据用户输入,这些模式中的任何一个都可以处于运行状态,运行模式将驱动axi_opcode_gen模块的输入,该模块为AXI 从接口测试生成符合AXI4协议的流量。
- *_mode_start->模式启动
- *_mode_stop->模式停止
- *_mode_done->为高表示用户停止模式或模式完成其表中的所有指令,可以通过断言任*_mode_start来清除
简单判断就是提供了3种不同的模式,每种模式有各自的一组控制信号start,stop和done
3.3.2 代码解析
首先还是进行一系列的参数和信号定义,然后我们关注下图中的代码逻辑,这里设置了running信号,用来表示当时正在执行的时什么模式。
接下来,以running信号为依据,将正在运行的模式的相关控制信号,接到axi_opcode_gen的信号组上。
然后就是一组模块的实例化,分别例化了boot_mode_gen,prbs_mode_gen,custom_mode_gen和axi_opcode_gen。功能分析起来也并不复杂,首先用户通过start,stop和done控制运行的模式(同一时间只能运行一种模式),然后boot_mode_gen,prbs_mode_gen,custom_mode_gen是各自模式的激励生成器,会生成对应模式的测试激励,然后根据running信号,选择对应的激励驱动到axi_opcode_gen的输入端接口,axi_opcode_gen会将激励转换成符合axi4协议规范的信号,驱动到MIG的AXI4从接口上。
梳理清楚了核心逻辑,我们还需要关注一下最后的一部分自动化测试逻辑。
首先是watch_dog ,它的功能是检测AXI总线是否卡死——如果超过C_TG_WATCH_DOG_MAX_CNT
个时钟周期没有读写活动,触发watch_dog_hang
信号,终止仿真并报错($finish
)。
localparam C_TG_WATCH_DOG_MAX_CNT = 16'hFFFF;
reg [15:0] watch_dog_cnt;
wire watch_dog_enable;
reg watch_dog_rst;
wire watch_dog_hang;assign watch_dog_enable = 1'b1; // 看门狗常启// 任何AXI读写活动都会复位计数器
always@(posedge clk) beginwatch_dog_rst <= #TCQ ((axi_awready && axi_awvalid) || // 写地址通道活动(axi_wready && axi_wvalid) || // 写数据通道活动(axi_bready && axi_bvalid) || // 写响应通道活动(axi_arready && axi_arvalid) || // 读地址通道活动(axi_rready && axi_rvalid)); // 读数据通道活动
end// 看门狗计数逻辑
always@(posedge clk) beginif (tg_rst | watch_dog_rst) begin // 全局复位或AXI活动时清零watch_dog_cnt <= #TCQ 'h0;endelse if (watch_dog_enable && ~watch_dog_hang && (vio_axi_tg_boot_mode_running || // 在TG运行模式下递增计数vio_axi_tg_custom_mode_running || vio_axi_tg_prbs_mode_running)) beginwatch_dog_cnt <= #TCQ watch_dog_cnt + 'h1;end
end
assign watch_dog_hang = (watch_dog_cnt == C_TG_WATCH_DOG_MAX_CNT); // 超时标志
然后是错误状态记录与VIO调试接口,它的作用是记录首次发生的数据不匹配错误,并通过VIO接口输出详细的错误上下文(地址、ID、数据差异等),方便硬件调试。类似地处理写响应错误(vio_axi_tg_write_resp_error
)和读响应错误(vio_axi_tg_read_resp_error
)。
// 同步错误状态到VIO(Virtual Input/Output)可观察信号
always @(posedge clk) beginif (tg_rst) begin // 全局复位时清零vio_axi_tg_mismatch_error <= #TCQ 0; // 数据不匹配错误vio_axi_tg_expected_bits <= #TCQ 0; // 预期数据值vio_axi_tg_actual_bits <= #TCQ 0; // 实际收到的数据值vio_axi_tg_error_bits <= #TCQ 0; // 出错的数据位掩码vio_axi_tg_error_status_id <= #TCQ 0; // 错误交易IDvio_axi_tg_error_status_addr <= #TCQ 0; // 错误地址vio_axi_tg_error_status_len <= #TCQ 0; // 错误交易长度vio_axi_tg_error_status_size <= #TCQ 0; // 错误数据大小(字节)vio_axi_tg_error_status_burst <= #TCQ 0; // 错误突发类型endelse if(axi_tg_mismatch_error && ~vio_axi_tg_mismatch_error) begin// 捕获第一次出现的错误(Sticky Error)vio_axi_tg_mismatch_error <= #TCQ 1;vio_axi_tg_expected_bits <= #TCQ axi_tg_expected_bits;vio_axi_tg_actual_bits <= #TCQ axi_tg_actual_bits;// ...其他信号赋值省略...end
end
接下来是仿真调试输出,用来在仿真中实时报告错误,并在测试完成后统计成功率。
// 仿真时打印错误信息
always @(posedge clk) beginif(axi_tg_mismatch_error) begin$display("ERROR::数据不匹配");$display("地址='h%h, 长度='d%0d, 数据大小='d%0d, 突发类型='b%2b",...);endelse if(axi_tg_write_resp_error) begin$display("ERROR:: 时间 %t 写响应错误", $time);end// ...读响应错误处理类似...
end// 看门狗超时终止仿真
always @(posedge clk) beginif(watch_dog_hang) begin$display("ERROR:: 看门狗超时,AXI总线无响应");$finish; // 强制结束仿真end
end// 测试结束总结
initial beginwait(prbs_mode_done == 1); // 等待测试模式完成if(mismatch_err_cnt == 0) $display("测试通过");else $display("测试失败,错误次数 = %0d", mismatch_err_cnt);$finish;
end
最后是窄突发(Narrow Burst)断言检查,作用是当DDR控制器不支持窄突发(Narrow Burst)时,强制检查所有AXI传输的awsize/arsize
是否等于全数据宽度(避免配置错误导致数据错位)。
generate
if((C_AXI_NBURST_SUPPORT == 0) && (APP_DATA_WIDTH == C_AXI_DATA_WIDTH)) begin// 检查写请求的突发大小是否符合限制property narrow_burst_disabled_check_size_for_writes;@(posedge clk) disable iff (tg_rst) (axi_awready && axi_awvalid) |-> (axi_awsize == C_WSTRB_WIDTH_LOG2);endpropertyassert property(narrow_burst_disabled) else $error("错误:控制器不支持窄突发时发送了不匹配的AXI写请求大小(h%0h)",axi_awsize);// 读请求也有类似检查
end
endgenerate
四、小结
至此,我们已经简单拆解了MIG AXI接口中测试文件的整体框架,首先在sim_tb_top.sv文件中例化了DDR模型和example_top。在example_top中,又例化了MIG IP和一个流量产生器ddr4_v2_2_8_axi_tg_top。在ddr4_v2_2_8_axi_tg_top中的主要功能是分别例化了boot_mode_gen,prbs_mode_gen,custom_mode_gen和axi_opcode_gen。用户通过start,stop和done控制运行的模式(同一时间只能运行一种模式),boot_mode_gen,prbs_mode_gen,custom_mode_gen是各自模式的激励生成器,会生成对应模式的测试激励,然后根据running信号,选择对应的激励驱动到axi_opcode_gen的输入端接口,axi_opcode_gen会将激励转换成符合axi4协议规范的信号,驱动到MIG的AXI4从接口上。在后面的文章中,我们将继续展开介绍boot_mode_gen,prbs_mode_gen,custom_mode_gen和axi_opcode_gen的原理。