Verilog新手求教:流水线模块与普通模块测试平台的差异及示例
作为常年跟Verilog测试平台打交道的开发者,我可以明确告诉你:流水线模块的Test Bench和普通模块的确实存在明显差异,核心原因在于流水线本身的「延迟特性」和「并行处理能力」——这两点直接决定了测试逻辑的设计思路。
核心差异点
- 输出等待逻辑不同:普通单周期模块(组合逻辑或单周期时序逻辑)的输出通常在输入后的1个时钟周期内稳定(甚至组合逻辑是即时的),但流水线模块的输出需要等待等于流水线级数的时钟周期才能得到结果。比如3级流水线,输入数据后要等3个clk沿才能看到对应输出。
- 输入激励的连续性:流水线的优势是可以同时处理多组数据,所以测试平台可以连续不断地给输入(每周期一组),不用等前一组的输出返回;而普通单周期模块可能需要等前一组输出验证完,再给下一组输入(否则会覆盖数据)。
- 可选的中间节点验证:调试流水线时,你可能需要检查每一级流水线寄存器的输出是否正确,这在普通模块的TB里通常不需要(除非是复杂的单周期模块)。
示例对比:普通模块 vs 流水线模块的Test Bench
下面用一个单周期加法器(普通模块)和2级流水线加法器(被测流水线模块)的TB来直观展示差异。
1. 普通单周期加法器(被测模块)
module single_cycle_adder( input clk, input rst_n, input [7:0] a, input [7:0] b, output reg [8:0] sum ); always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sum <= 9'd0; end else begin sum <= a + b; // 单周期计算,下一个clk沿输出结果 end end endmodule
普通模块的Test Bench
module single_cycle_adder_tb; reg clk; reg rst_n; reg [7:0] a; reg [7:0] b; wire [8:0] sum; single_cycle_adder uut( .clk(clk), .rst_n(rst_n), .a(a), .b(b), .sum(sum) ); // 生成100MHz时钟 initial begin clk = 0; forever #5 clk = ~clk; end initial begin // 复位 rst_n = 0; #10 rst_n = 1; // 第一组测试:给输入后等1个clk周期验证输出 a = 8'd10; b = 8'd20; @(posedge clk); @(negedge clk); // 等clk下降沿确保输出稳定 $display("Test 1: a=%d, b=%d, sum=%d (expected 30)", a, b, sum); // 第二组测试:必须等第一组验证完再给输入 a = 8'd30; b = 8'd40; @(posedge clk); @(negedge clk); $display("Test 2: a=%d, b=%d, sum=%d (expected 70)", a, b, sum); #10 $finish; end endmodule
2. 2级流水线加法器(被测模块)
module pipelined_adder( input clk, input rst_n, input [7:0] a, input [7:0] b, output reg [8:0] sum ); reg [7:0] a_reg; reg [7:0] b_reg; // 第一级流水线:打拍存储输入 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin a_reg <= 8'd0; b_reg <= 8'd0; end else begin a_reg <= a; b_reg <= b; end end // 第二级流水线:计算加法 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sum <= 9'd0; end else begin sum <= a_reg + b_reg; // 输入后第2个clk沿输出结果 end end endmodule
流水线模块的Test Bench
module pipelined_adder_tb; reg clk; reg rst_n; reg [7:0] a; reg [7:0] b; wire [8:0] sum; pipelined_adder uut( .clk(clk), .rst_n(rst_n), .a(a), .b(b), .sum(sum) ); // 生成100MHz时钟 initial begin clk = 0; forever #5 clk = ~clk; end initial begin // 复位 rst_n = 0; #10 rst_n = 1; // 连续给输入:流水线支持并行处理,不用等输出返回 @(posedge clk); a = 8'd10; b = 8'd20; // 第1个clk沿输入,对应输出在第3个clk沿出现 @(posedge clk); a = 8'd30; b = 8'd40; // 第2个clk沿输入,对应输出在第4个clk沿出现 @(posedge clk); a = 8'd50; b = 8'd60; // 第3个clk沿输入,对应输出在第5个clk沿出现 // 等待流水线输出:需要跳过等于流水线级数的周期(这里是2级) @(posedge clk); // 第4个clk沿,第一个输入的结果刚输出 @(negedge clk); $display("Test 1: sum=%d (expected 30)", sum); @(posedge clk); // 第5个clk沿,第二个输入的结果输出 @(negedge clk); $display("Test 2: sum=%d (expected 70)", sum); @(posedge clk); // 第6个clk沿,第三个输入的结果输出 @(negedge clk); $display("Test 3: sum=%d (expected 110)", sum); #10 $finish; end // 可选:调试用——打印流水线中间寄存器状态 initial begin forever begin @(negedge clk); $display("Pipeline Stage 1: a_reg=%d, b_reg=%d", uut.a_reg, uut.b_reg); end end endmodule
差异总结
对比两个TB,核心区别一目了然:
- 输入方式:普通TB是「一组输入→验证→下一组输入」的串行模式,流水线TB可以连续给输入,利用流水线的并行性提高测试效率。
- 输出等待时长:普通TB只需要等1个clk周期,流水线TB需要等待与流水线级数一致的clk周期才能拿到对应输入的输出。
- 调试逻辑:流水线TB可以轻松添加中间节点的验证逻辑,这在普通模块TB里几乎不需要。
内容的提问来源于stack exchange,提问作者Salma El-Sokkary
相关产品推荐
相关产品推荐

