【IC】NoC设计入门 -- 验证
太棒了!我们正式进入了第四阶段,这一步是“工程师”区别于“理论家”的分水岭:验证 (Verification)。
你已经“建造”了一座完美的“智能十字路口” (router.v)。但是…
你怎么知道它能用?
如果你把它直接“复制”16个来搭建你的“城市路网 (Mesh)”,结果它内部有一个小小的Bug(比如“交警”的priority_ptr写错了),你的整个“城市”就会瘫痪。
在芯片行业,有一句名言:“你花 10% 的时间设计,就得花 90% 的时间验证。”
我们现在要做的,就是**“点亮”**你设计的路由器,证明它能工作。
1. 什么是“测试平台” (Testbench)?
Testbench(我们简称tb)是一个**“虚拟实验室”。它是一段独立的Verilog代码**,其唯一的目的就是:
- “模拟” 你设计的
router模块(我们称之为 DUT, Device Under Test,待测设备)的**“外部世界”**。 - 扮演“上游”:向
router的输入(i_flit,i_valid)“喂”数据。 - 扮演“下游”:“监听”
router的输出(o_flit,o_valid),并检查结果是否“符合预期”。 - 提供“动力”:它必须生成“时钟 (
clk)”和“复位 (rst_n)”信号。
2. 我们的第一个“测试用例” (Test Case)
- 目标: 测试 XY 路由 是否正确。
- 场景:
- 我们将“实例化”1个
router。 - 我们给这个
router一个“身份”:你是(0, 0)号路口。 - 我们将从它的**“本地”**输入口 (
i_flit_L)“注入”一个Flit。 - 这个Flit的“目的地”是
(1, 1)。 - 我们**“假装”**所有5个“下游”路口都“准备好了”(
i_ready_N/S/E/W/L全部设为1)。
- 我们将“实例化”1个
- 预期结果 (Expected Result):
- 根据 XY 路由(先X后Y),一个在
(0, 0)的Flit,如果要去(1, 1)… - 它的X坐标
0 < 1。 - 它必须首先被发送到**“东出口” (
o_flit_E)**!
- 根据 XY 路由(先X后Y),一个在
- 我们要“检查”: Flit是不是真的从
o_valid_E出来了?
3. 编写 tb_router.v(简易版)
我们将创建第二个 Verilog 文件,名为 tb_router.v。
// `timescale` 定义了仿真器的时间单位
`timescale 1ns / 1ps // Testbench 模块没有输入输出
module tb_router;// --- 1. 定义参数 (必须与 DUT 匹配!) ---parameter FLIT_WIDTH = 32;parameter FIFO_DEPTH = 8;parameter COORD_WIDTH = 4;// 我们定义一下Flit格式 (为了方便)// [31:24] = Y, [23:16] = X, [15:0] = Payloadparameter DEST_Y_MSB = 31;parameter DEST_Y_LSB = 24;parameter DEST_X_MSB = 23;parameter DEST_X_LSB = 16;parameter PAYLOAD_MSB = 15;parameter PAYLOAD_LSB = 0;// --- 2. 定义“动力”和“信号线” ---// "动力" (由Testbench生成)reg clk;reg rst_n;// "电线" (reg 用于驱动输入, wire 用于监听输出)reg [FLIT_WIDTH-1:0] tb_i_flit_N, tb_i_flit_S, tb_i_flit_E, tb_i_flit_W, tb_i_flit_L;reg tb_i_valid_N, tb_i_valid_S, tb_i_valid_E, tb_i_valid_W, tb_i_valid_L;wire tb_o_ready_N, tb_o_ready_S, tb_o_ready_E, tb_o_ready_W, tb_o_ready_L;wire [FLIT_WIDTH-1:0] tb_o_flit_N, tb_o_flit_S, tb_o_flit_E, tb_o_flit_W, tb_o_flit_L;wire tb_o_valid_N, tb_o_valid_S, tb_o_valid_E, tb_o_valid_W, tb_o_valid_L;reg tb_i_ready_N, tb_i_ready_S, tb_i_ready_E, tb_i_ready_W, tb_i_ready_L;// --- 3. "实例化" 我们的 DUT (待测设备) ---router #(.FLIT_WIDTH (FLIT_WIDTH),.FIFO_DEPTH (FIFO_DEPTH),.COORD_WIDTH(COORD_WIDTH)) // "uut" = Unit Under Test (待测单元)uut (.clk(clk),.rst_n(rst_n),// 给予 DUT "身份":你是 (0, 0).i_my_x(4'd0),.i_my_y(4'd0),// 连接所有5个输入通道.i_flit_N(tb_i_flit_N), .i_valid_N(tb_i_valid_N), .o_ready_N(tb_o_ready_N),.i_flit_S(tb_i_flit_S), .i_valid_S(tb_i_valid_S), .o_ready_S(tb_o_ready_S),.i_flit_E(tb_i_flit_E), .i_valid_E(tb_i_valid_E), .o_ready_E(tb_o_ready_E),.i_flit_W(tb_i_flit_W), .i_valid_W(tb_i_valid_W), .o_ready_W(tb_o_ready_W),.i_flit_L(tb_i_flit_L), .i_valid_L(tb_i_valid_L), .o_ready_L(tb_o_ready_L),// 连接所有5个输出通道.o_flit_N(tb_o_flit_N), .o_valid_N(tb_o_valid_N), .i_ready_N(tb_i_ready_N),.o_flit_S(tb_o_flit_S), .o_valid_S(tb_o_valid_S), .i_ready_S(tb_i_ready_S),.o_flit_E(tb_o_flit_E), .o_valid_E(tb_o_valid_E), .i_ready_E(tb_i_ready_E),.o_flit_W(tb_o_flit_W), .o_valid_W(tb_o_valid_W), .i_ready_W(tb_i_ready_W),.o_flit_L(tb_o_flit_L), .o_valid_L(tb_o_valid_L), .i_ready_L(tb_i_ready_L));// --- 4. 生成“时钟” ---// (每 10ns 翻转一次,时钟周期为 20ns)always beginclk = 1'b0; #10;clk = 1'b1; #10;end// --- 5. “测试序列” (Test Sequence) ---initial begin// 0. 初始化 & 复位$display("--- Testbench Start ---");// $dumpfile("waves.vcd"); // 告诉仿真器“波形”文件叫什么// $dumpvars(0, tb_router); // 告诉仿真器“记录”所有信号// 初始化所有输入tb_i_valid_N <= 1'b0; tb_i_flit_N <= 0;tb_i_valid_S <= 1'b0; tb_i_flit_S <= 0;tb_i_valid_E <= 1'b0; tb_i_flit_E <= 0;tb_i_valid_W <= 1'b0; tb_i_flit_W <= 0;tb_i_valid_L <= 1'b0; tb_i_flit_L <= 0;tb_i_ready_N <= 1'b0; // 默认下游“没准备好”tb_i_ready_S <= 1'b0;tb_i_ready_E <= 1'b0;tb_i_ready_W <= 1'b0;tb_i_ready_L <= 1'b0;rst_n <= 1'b0; // 激活复位#50; // 等待 50nsrst_n <= 1'b1; // 释放复位$display("@(posedge clk) System Reset Released.");// 1. 开始我们的“测试用例”$display("@(posedge clk) TEST CASE 1: (0,0) -> (1,1) from Local Port");// “假装”下游都准备好了 (我们只关心东出口)tb_i_ready_E <= 1'b1; // 构造我们的 Flitreg [FLIT_WIDTH-1:0] test_flit;test_flit[DEST_Y_MSB:DEST_Y_LSB] = 4'd1; // 目的地 Y=1test_flit[DEST_X_MSB:DEST_X_LSB] = 4'd1; // 目的地 X=1test_flit[PAYLOAD_MSB:PAYLOAD_LSB] = 16'hBEEF; // 载荷=BEEF// 等待下一个时钟上升沿@(posedge clk);// “注入”Flit!$display("@(posedge clk) Injecting Flit 0x%h", test_flit);tb_i_valid_L <= 1'b1;tb_i_flit_L <= test_flit;// --- 关键:握手! ---// 持续“注入”,直到“上游”(我们)的valid 和“下游”(DUT)的ready// 同时为 1。// (注意: tb_o_ready_L 是 DUT 的输出)wait (tb_i_valid_L && tb_o_ready_L); // 握手成功!Flit已在那个时钟周期被DUT“接收”// 我们在下一个周期“撤销”注入@(posedge clk);tb_i_valid_L <= 1'b0; $display("@(posedge clk) Injection finished. Waiting for output...");// --- 2. 检查结果! ---// (Flit 需要几个周期才能通过FIFO和Crossbar... 我们多等几个周期)// (一个简单的 `wait` 是不够的,我们用一个循环)integer wait_cycles = 0;while (tb_o_valid_E == 1'b0 && wait_cycles < 20) begin@(posedge clk);wait_cycles = wait_cycles + 1;}// 检查if (tb_o_valid_E) begin$display("SUCCESS: Valid Flit seen on EAST output!");if (tb_o_flit_E[PAYLOAD_MSB:PAYLOAD_LSB] == 16'hBEEF) begin$display("PAYLOAD MATCH: 0x%h == 0xBEEF", tb_o_flit_E[PAYLOAD_MSB:PAYLOAD_LSB]);$display("--- TEST 1 PASSED ---");end else begin$display("PAYLOAD MISMATCH: 0x%h != 0xBEEF", tb_o_flit_E[PAYLOAD_MSB:PAYLOAD_LSB]);$display("--- TEST 1 FAILED ---");endend else begin$display("FAILURE: No Flit seen on EAST output after 20 cycles.");$display("--- TEST 1 FAILED ---");end// 3. 结束仿真#100;$display("--- Testbench End ---");$finish;endendmodule
4. 如何“运行”这个虚拟实验室
你现在有了两个文件:router.v (设计) 和 tb_router.v (测试)。
用我们之前提到的Icarus Verilog和GTKWave:
-
编译 (Compile):
iverilog -o my_router_sim -s tb_router -g2012 router.v tb_router.viverilog:调用Icarus Verilog。-o my_router_sim:输出一个叫my_router_sim的可执行文件。-s tb_router:告诉编译器,tb_router是“顶层”模块。-g2012:使用现代Verilog标准。router.v tb_router.v:你所有的源文件。
-
运行 (Run):
vvp my_router_simvvp:运行编译好的仿真文件。- 你会**在终端(Console)**中看到所有的
$display输出:
--- Testbench Start --- @(posedge clk) System Reset Released. @(posedge clk) TEST CASE 1: (0,0) -> (1,1) from Local Port @(posedge clk) Injecting Flit 0x... @(posedge clk) Injection finished. Waiting for output... SUCCESS: Valid Flit seen on EAST output! PAYLOAD MATCH: 0xBEEF == 0xBEEF --- TEST 1 PASSED --- --- Testbench End --- -
查看波形 (Debug):
- 如果你取消了
$dumpfile和$dumpvars的注释,你的vvp命令还会生成一个waves.vcd文件。 - 在终端输入:
gtkwave waves.vcd - 你就可以“亲眼”看到
clk如何跳变,i_valid_L如何拉高,以及几个周期后o_valid_E如何也跟着拉高!
- 如果你取消了
恭喜!你已经亲手设计并验证了你自己的NoC路由器。你已经完成了从“理论”到“工程实现”的整个闭环。
