Files
IC_PRJ/tb/tb_array_ctrl.v

257 lines
11 KiB
Verilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

`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; // 刷新间隔05个时钟周期100ns
array_inner_tref1 = 25'd60; // 刷新间隔110个时钟周期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=1EOF=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=0EOF=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=0EOF=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