4、激励生成:时钟信号生成、复位信号生成、数据激励生成、随机激励生成
做仿真验证,说白了就是给设计喂数据,看它怎么反应。而喂什么数据、怎么喂,这就是激励生成要解决的问题。我刚开始带项目那会儿,有个同事写了个复杂的测试用例,结果仿真跑了三天三夜没跑完——后来发现是时钟生成写了个死循环。嗯,这种坑咱们得避开。
4.1 时钟信号生成
时钟是数字电路的心跳。没有时钟,一切归零。生成时钟看起来简单,但实际项目中容易出幺蛾子。
最基本的时钟生成,用 Verilog 写就是:
reg clk;
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns周期,100MHz
end
我个人习惯用 always 块来写,更直观:
reg clk = 0;
always #5 clk = ~clk;
这里有个细节——reg clk = 0 这种初始化写法,在有些仿真器里可能不认。我建议用 initial 块给初值,兼容性更好。
带使能的时钟,在低功耗验证中很常见:
reg clk_gated;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
clk_gated <= 1'b0;
else if (clk_en)
clk_gated <= #1 ~clk_gated;
end
注意那个 #1 延迟——不加的话,仿真波形上会出现奇怪的毛刺。我曾经因为这个被折腾了一整天,后来发现是门控时钟的仿真模型没加延迟。
4.2 复位信号生成
复位信号,说白了就是让设计回到初始状态。但怎么复位、什么时候释放,这里头有讲究。
异步复位,最常用的写法:
reg rst_n;
initial begin
rst_n = 1'b0;
#100; // 保持复位100ns
rst_n = 1'b1; // 释放复位
#200;
rst_n = 1'b0; // 再来一次复位
#50;
rst_n = 1'b1;
end
你想想看,如果设计里有多个时钟域,每个时钟域的复位释放时间应该错开。我见过一个案例,就是因为所有复位同时释放,导致电源瞬间电流过大,仿真直接崩了。
@(posedge clk); #1; 来释放,这样可以避免复位释放和时钟上升沿同时发生,导致仿真出现亚稳态问题。
同步复位的生成方式不同:
reg rst_sync;
always @(posedge clk) begin
if (rst_n)
rst_sync <= 1'b0;
else
rst_sync <= 1'b1;
end
嗯,这里要注意,同步复位需要时钟才能工作。如果时钟停了,复位就失效了。所以芯片上电时,一般先用异步复位把关键路径稳住,再切到同步复位。
4.3 数据激励生成
数据激励,就是给设计喂的各种输入数据。最简单的做法是直接赋值,但实际项目中,数据往往有特定的协议和时序要求。
固定数据序列,适合做基本功能测试:
reg [7:0] data_in;
initial begin
data_in = 8'h00;
#10;
data_in = 8'hA5;
#10;
data_in = 8'h5A;
#10;
data_in = 8'hFF;
end
循环数据,适合做压力测试:
reg [31:0] addr;
initial begin
addr = 32'h0000_0000;
repeat (1024) begin
@(posedge clk);
addr = addr + 4;
end
end
我在项目中遇到过一个问题:用 repeat 循环生成地址时,忘了加 @(posedge clk),结果地址在一个时钟周期内跳了1024次。仿真波形看起来就像一根毛刺,根本没法分析。
@(posedge clk) 同步,要么用非阻塞赋值 <=。否则仿真结果和实际芯片行为会差十万八千里。
4.4 随机激励生成
随机激励,是验证的杀手锏。它能覆盖你没想到的边界情况。SystemVerilog 里用 $random 或者 std::randomize 来实现。
简单的随机数:
reg [7:0] rand_data;
initial begin
repeat (100) begin
@(posedge clk);
rand_data = $random % 256; // 0~255的随机数
end
end
但 $random 有个问题——它是均匀分布的。你想想看,如果地址范围是0~1023,但实际设计中只有地址0~15和1000~1023有特殊逻辑,均匀随机就很难覆盖到这些边界。
带约束的随机,才是正道:
class Packet;
rand bit [7:0] length;
rand bit [31:0] address;
constraint addr_range {
address inside {[0:15], [1000:1023]};
}
constraint len_range {
length > 0;
length < 64;
}
endclass
initial begin
Packet pkt = new();
repeat (1000) begin
assert(pkt.randomize());
@(posedge clk);
// 把 pkt.length 和 pkt.address 送给设计
end
end
我曾经犯过一个错:随机约束写得太松,结果生成了大量无效数据,仿真跑了两天没发现问题。后来加了覆盖率分析才发现,关键路径只被覆盖了30%。
4.5 知识体系总览
下面这张图,是我自己总结的激励生成知识体系。你可以把它当作一个检查清单,写测试用例时对照着看,不容易漏东西。
这张图把激励生成的四个核心模块串起来了。时钟是基础,复位是起点,数据是主体,随机是补充。四者缺一不可。
最后说一句:写激励的时候,别总想着一次搞定所有情况。先写简单的、固定的,跑通了再上随机。我见过太多人一上来就写复杂的随机约束,结果仿真跑不动,debug 也找不到问题在哪。循序渐进,才是正道。
可可精品资料库