`timescale 1ns/1ps module tb_array_ctrl; // 时钟与复位信号 reg clk; reg rst_n; // AXI到array的帧信号 reg axi2array_frame_valid; reg [152:0] axi2array_frame_data; wire axi2array_frame_ready; // array到AXI的读数据信号 wire array2axi_rdata_valid; wire [127:0] array2axi_rdata; // array接口信号 wire array_csn; wire [15:0] array_raddr; wire array_caddr_vld_wr; wire [5:0] array_caddr_wr; wire array_wdata_vld; wire [127:0] array_wdata; wire array_caddr_vld_rd; wire [5:0] array_caddr_rd; reg array_rdata_vld; reg [127:0] array_rdata; // APB配置信号(内存控制器时序参数) reg mc_work_en; reg [7:0] array_inner_tras; // 行预充电时间 reg [7:0] array_inner_trp; // 预充电到激活时间 reg [7:0] array_inner_trcd_wr;// 激活到写命令时间 reg [7:0] array_inner_twr; // 写恢复时间 reg [7:0] array_inner_trcd_rd;// 激活到读命令时间 reg [7:0] array_inner_trtp; // 读预充电时间 reg array_ref_en; // 刷新使能 reg [24:0] array_inner_tref0; // 刷新间隔0 reg [24:0] array_inner_tref1; // 刷新间隔1 reg array_inner_ref_sel;// 刷新间隔选择 // 实例化被测模块 array_ctrl u_array_ctrl ( .clk (clk), .rst_n (rst_n), .axi2array_frame_valid (axi2array_frame_valid), .axi2array_frame_data (axi2array_frame_data), .axi2array_frame_ready (axi2array_frame_ready), .array2axi_rdata_valid (array2axi_rdata_valid), .array2axi_rdata (array2axi_rdata), .array_csn (array_csn), .array_raddr (array_raddr), .array_caddr_vld_wr (array_caddr_vld_wr), .array_caddr_wr (array_caddr_wr), .array_wdata_vld (array_wdata_vld), .array_wdata (array_wdata), .array_caddr_vld_rd (array_caddr_vld_rd), .array_caddr_rd (array_caddr_rd), .array_rdata_vld (array_rdata_vld), .array_rdata (array_rdata), .mc_work_en (mc_work_en), .array_inner_tras (array_inner_tras), .array_inner_trp (array_inner_trp), .array_inner_trcd_wr (array_inner_trcd_wr), .array_inner_twr (array_inner_twr), .array_inner_trcd_rd (array_inner_trcd_rd), .array_inner_trtp (array_inner_trtp), .array_ref_en (array_ref_en), .array_inner_tref0 (array_inner_tref0), .array_inner_tref1 (array_inner_tref1), .array_inner_ref_sel (array_inner_ref_sel) ); // 时钟生成:50MHz(周期20ns) initial begin clk = 1'b0; forever #1.25 clk = ~clk; end // 复位与初始化流程 initial begin // 初始复位状态 rst_n = 1'b0; axi2array_frame_valid = 1'b0; axi2array_frame_data = 153'd0; array_rdata_vld = 1'b0; array_rdata = 128'd0; mc_work_en = 1'b0; // 初始化时序参数(示例值,模拟真实内存时序) array_inner_tras = 8'd16; // 行预充电时间:2个时钟周期 array_inner_trp = 8'd6; // 预充电到激活时间:1个时钟周期 array_inner_trcd_wr = 8'd7; // 激活到写命令时间:1个时钟周期 array_inner_twr = 8'd6; // 写恢复时间:3个时钟周期 array_inner_trcd_rd = 8'd7; // 激活到读命令时间:1个时钟周期 array_inner_trtp = 8'd3; // 读预充电时间:1个时钟周期 array_ref_en = 1'b0; // 初始关闭刷新 array_inner_tref0 = 25'd100; // 刷新间隔0:5个时钟周期(100ns) array_inner_tref1 = 25'd60; // 刷新间隔1:10个时钟周期(200ns) array_inner_ref_sel = 1'b0; // 默认选择刷新间隔0 // 释放复位并启动控制器 #100 rst_n = 1'b1; // 100ns后释放复位 #50 mc_work_en = 1'b1; // 50ns后使能控制器工作 // 测试场景1:单周期写操作(完整帧:含SOF和EOF) @(posedge clk); // 帧数据格式:[152]操作类型(1=写), [151]SOF, [150]EOF, [149:134]行地址, [133:128]列地址, [127:0]数据 axi2array_frame_data = {1'b1, 1'b1, 1'b1, 16'h1234, 6'h0A, 128'h5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A}; axi2array_frame_valid = 1'b1; // 发送写请求 @(posedge axi2array_frame_ready); // 等待控制器接收 axi2array_frame_valid = 1'b0; // 撤销请求 #200; // 等待写操作完成(含时序延迟) // 测试场景2:多周期写操作(分帧传输,SOF=首帧,EOF=尾帧) @(posedge clk); // 首帧:SOF=1,EOF=0 axi2array_frame_data = {1'b1, 1'b1, 1'b0, 16'h5678, 6'h0B, 128'hA5A5_A5A5_A5A5_A5A5_A5A5_A5A5_A5A5_A5A5}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); // 次帧:SOF=0,EOF=0 axi2array_frame_data = {1'b1, 1'b0, 1'b0, 16'h5678, 6'h0C, 128'hAA55_AA55_AA55_AA55_AA55_AA55_AA55_AA55}; @(posedge axi2array_frame_ready); // 尾帧:SOF=0,EOF=1 axi2array_frame_data = {1'b1, 1'b0, 1'b1, 16'h5678, 6'h0D, 128'h55AA_55AA_55AA_55AA_55AA_55AA_55AA_55AA}; @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; #300; // 等待多帧写完成 // 测试场景3:单周期读操作(读取场景1写入的数据) @(posedge clk); // 帧数据格式:[152]操作类型(0=读), [151]SOF, [150]EOF, [149:134]行地址, [133:128]列地址, [127:0]无效(读数据由array返回) axi2array_frame_data = {1'b0, 1'b1, 1'b1, 16'h1234, 6'h0A, 128'd0}; axi2array_frame_valid = 1'b1; // 发送读请求 @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; // 模拟array返回读数据(延迟1个时钟周期,符合时序) #20; // 等待控制器发起读命令 array_rdata = 128'h5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A; // 预期数据(与场景1写入一致) array_rdata_vld = 1'b1; @(posedge clk); array_rdata_vld = 1'b0; // 撤销读数据有效 #200; // 等待读响应返回AXI // 测试场景4:读写混合操作(写后立即读,验证数据一致性) @(posedge clk); // 先写 axi2array_frame_data = {1'b1, 1'b1, 1'b1, 16'h9ABC, 6'h0E, 128'h1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; #100; // 等待写完成 // 立即读 axi2array_frame_data = {1'b0, 1'b1, 1'b1, 16'h9ABC, 6'h0E, 128'd0}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; #20; array_rdata = 128'h1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0; // 验证写后读一致性 array_rdata_vld = 1'b1; @(posedge clk); array_rdata_vld = 1'b0; #200; // 测试场景5:刷新操作(使能刷新,验证控制器响应) @(posedge clk); array_ref_en = 1'b1; // 开启刷新 #(2.5*array_inner_tref0); // 等待至少1个刷新周期(根据tref0=5个时钟周期) array_ref_en = 1'b0; // 关闭刷新 #100; // 测试场景6:刷新与读写冲突(刷新期间发起读写,验证优先级) @(posedge clk); array_ref_en = 1'b1; // 开启刷新 #50; // 确保刷新已启动 // 尝试写入(预期被阻塞,直到刷新完成) axi2array_frame_data = {1'b1, 1'b1, 1'b1, 16'hDEF0, 6'h0F, 128'hFEDC_BA98_7654_3210_FEDC_BA98_7654_3210}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); // 可能延迟,因刷新优先级更高 axi2array_frame_valid = 1'b0; array_ref_en = 1'b0; // 关闭刷新 #200; // 测试场景7:边界地址读写(最大行/列地址) @(posedge clk); // 写最大地址 axi2array_frame_data = {1'b1, 1'b1, 1'b1, 16'hFFFF, 6'h3F, 128'hFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; #200; // 读最大地址 axi2array_frame_data = {1'b0, 1'b1, 1'b1, 16'hFFFF, 6'h3F, 128'd0}; axi2array_frame_valid = 1'b1; @(posedge axi2array_frame_ready); axi2array_frame_valid = 1'b0; #20; array_rdata = 128'hFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; // 验证边界地址数据 array_rdata_vld = 1'b1; @(posedge clk); array_rdata_vld = 1'b0; #200; // 测试结束 #1000; $display("所有测试场景完成!"); $finish; end // 波形记录(用于仿真后分析) initial begin $fsdbDumpfile("tb.fsdb"); // 记录FSDB格式波形 $fsdbDumpvars(0, tb_array_ctrl, "+all"); // 记录所有信号 $vcdpluson; // 记录VCD+格式波形 $vcdplusmemon; // 记录内存信号 end // 信号监控与断言(验证关键功能) initial begin $monitor( "Time: %0t, 写有效: %b, 读有效: %b, 刷新使能: %b, CSN: %b, 行地址: %h, 列地址(写): %h, 列地址(读): %h", $time, array_wdata_vld, array_caddr_vld_rd, array_ref_en, array_csn, array_raddr, array_caddr_wr, array_caddr_rd ); // 断言1:复位期间片选信号CSN应为高(无效) @(negedge rst_n); if (array_csn !== 1'b1) begin $error("复位期间CSN应为高!Time: %0t", $time); end // 断言2:写操作时CSN应为低(有效),且写数据有效 @(posedge array_wdata_vld); if (array_csn !== 1'b0) begin $error("写操作时CSN应为低!Time: %0t", $time); end // 断言3:读操作时CSN应为低(有效),且读地址有效 @(posedge array_caddr_vld_rd); if (array_csn !== 1'b0) begin $error("读操作时CSN应为低!Time: %0t", $time); end // 断言4:读响应数据应与写入数据一致(场景3验证) @(posedge array2axi_rdata_valid); if (array2axi_rdata !== 128'h5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A) begin $error("读数据与写入数据不一致!Time: %0t", $time); end // 断言5:刷新期间不应有读写操作(CSN保持高或读写信号无效) @(posedge array_ref_en); #10; // 等待刷新启动 if (array_wdata_vld === 1'b1 || array_caddr_vld_rd === 1'b1) begin $error("刷新期间不应有读写操作!Time: %0t", $time); end end endmodule