【IC】NoC设计入门 -- 验证

太棒了!我们正式进入了第四阶段 ,这一步是"工程师"区别于"理论家"的分水岭:验证 (Verification)

你已经"建造"了一座完美的"智能十字路口" (router.v)。但是...

你怎么知道它能用?

如果你把它直接"复制"16个来搭建你的"城市路网 (Mesh)",结果它内部有一个小小的Bug(比如"交警"的priority_ptr写错了),你的整个"城市"就会瘫痪。

在芯片行业,有一句名言:"你花 10% 的时间设计,就得花 90% 的时间验证。"

我们现在要做的,就是**"点亮"**你设计的路由器,证明它能工作。


1. 什么是"测试平台" (Testbench)?

Testbench(我们简称tb)是一个**"虚拟实验室"。它是一段 独立的Verilog代码**,其唯一的目的就是:

  1. "模拟" 你设计的router模块(我们称之为 DUT, Device Under Test,待测设备)的**"外部世界"**。
  2. 扮演"上游" :向router输入i_flit, i_valid)"喂"数据。
  3. 扮演"下游""监听" router输出o_flit, o_valid),并检查结果是否"符合预期"。
  4. 提供"动力" :它必须生成"时钟 (clk)"和"复位 (rst_n)"信号。

2. 我们的第一个"测试用例" (Test Case)

  • 目标: 测试 XY 路由 是否正确。
  • 场景:
    1. 我们将"实例化"1个 router
    2. 我们给这个router一个"身份":你是 (0, 0) 号路口。
    3. 我们将从它的**"本地"**输入口 (i_flit_L)"注入"一个Flit。
    4. 这个Flit的"目的地"是 (1, 1)
    5. 我们**"假装"**所有5个"下游"路口都"准备好了"(i_ready_N/S/E/W/L 全部设为 1)。
  • 预期结果 (Expected Result):
    • 根据 XY 路由 (先X后Y),一个在 (0, 0) 的Flit,如果要去 (1, 1)...
    • 它的X坐标 0 < 1
    • 必须 首先被发送到**"东出口" (o_flit_E)**!
  • 我们要"检查": Flit是不是真的从o_valid_E出来了?

3. 编写 tb_router.v(简易版)

我们将创建第二个 Verilog 文件,名为 tb_router.v

verilog 复制代码
// `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] = Payload
    parameter 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 begin
        clk = 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; // 等待 50ns
        rst_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; 
        
        // 构造我们的 Flit
        reg [FLIT_WIDTH-1:0] test_flit;
        test_flit[DEST_Y_MSB:DEST_Y_LSB] = 4'd1; // 目的地 Y=1
        test_flit[DEST_X_MSB:DEST_X_LSB] = 4'd1; // 目的地 X=1
        test_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 ---");
            end
        end else begin
            $display("FAILURE: No Flit seen on EAST output after 20 cycles.");
            $display("--- TEST 1 FAILED ---");
        end

        // 3. 结束仿真
        #100;
        $display("--- Testbench End ---");
        $finish;
    end

endmodule

4. 如何"运行"这个虚拟实验室

你现在有了两个文件:router.v (设计) 和 tb_router.v (测试)。

用我们之前提到的Icarus VerilogGTKWave

  1. 编译 (Compile):

    bash 复制代码
    iverilog -o my_router_sim -s tb_router -g2012 router.v tb_router.v
    • iverilog:调用Icarus Verilog。
    • -o my_router_sim:输出一个叫 my_router_sim 的可执行文件。
    • -s tb_router:告诉编译器,tb_router 是"顶层"模块。
    • -g2012:使用现代Verilog标准。
    • router.v tb_router.v:你所有的源文件。
  2. 运行 (Run):

    bash 复制代码
    vvp my_router_sim
    • vvp:运行编译好的仿真文件。

    • 你会**在终端(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 ---

  3. 查看波形 (Debug):

    • 如果你取消了 $dumpfile$dumpvars 的注释,你的vvp命令还会生成一个 waves.vcd 文件。
    • 在终端输入:gtkwave waves.vcd
    • 你就可以"亲眼"看到clk如何跳变,i_valid_L如何拉高,以及几个周期后o_valid_E如何也跟着拉高!

恭喜!你已经亲手设计并验证了你自己的NoC路由器。你已经完成了从"理论"到"工程实现"的整个闭环。

相关推荐
Olafur_zbj1 天前
【IC】NoC设计入门 -- Router输入输出端口
noc
newyork major1 年前
On-Chip-Network之router微架构的物理实现
架构·noc·topology
网络研究院1 年前
安全中心 (SOC) 与 网络运营中心 (NOC)
网络·安全·soc·安全运营中心·noc·区别·网络运营中心
农民真快落2 年前
【异常处理】sbt构建Chisel库时出现extracting structure failed:build status:error的解决办法
scala·ic设计·chisel·noc·一生一芯
apple_ttt2 年前
片上网络NoC(1)——导论
网络·fpga开发·fpga·芯片设计·noc·片上网络
农民真快落2 年前
【IC设计】Vivado单口RAM的使用和时序分析
fpga开发·ic设计·noc
CodingCos2 年前
【ARM CoreLink 系列 1 -- SoC 片上互联介绍】
片上互联·共享总线结构·noc·crossbar 架构·mesh 总线·noc 总线·noc qos