4. SystemVerilog基础回顾(二):过程块、任务与函数、事件、断言基础
好,咱们接着聊SystemVerilog。上一章我们把数据类型、操作符这些基础过了一遍。这一章,我挑几个在验证环境里天天打交道的核心语法来讲:过程块、任务函数、事件同步,还有断言。
这些东西,说白了就是验证代码的骨架和血肉。你写testbench,写driver,写monitor,哪哪都离不开它们。我个人习惯是,先把这些基础搞扎实了,后面学UVM才不至于一头雾水。
4.1 过程块:always、initial、always_comb、always_ff
过程块是Verilog时代就有的老伙计了。SystemVerilog在它基础上加了几把新刀,用起来更顺手。
4.1.1 initial 与 always
initial 块只执行一次,适合做初始化。 always 块循环执行,适合描述时序逻辑或组合逻辑。
module tb_basic;
logic clk = 0;
logic rst_n;
// initial块:只跑一次
initial begin
rst_n = 0;
#20 rst_n = 1;
#100 $finish;
end
// always块:一直翻转
always #5 clk = ~clk;
endmodule
嗯,这里要注意:always 太通用了,容易写出 latch。我见过不少新手用 always @(*) 写组合逻辑,漏了敏感列表,仿真和综合结果不一致。所以SystemVerilog给了我们更明确的替代品。
4.1.2 always_comb 与 always_ff
always_comb 专门用来描述组合逻辑。它自动推导敏感列表,仿真器还会在0时刻自动执行一次,避免初始值X态问题。
always_ff 专门用来描述时序逻辑。你必须在括号里指定时钟沿和复位沿,写错了编译器直接报错。
logic [3:0] cnt;
logic clk, rst_n, en;
// 组合逻辑:用 always_comb
always_comb begin
if (rst_n)
cnt_next = cnt + 1;
else
cnt_next = 0;
end
// 时序逻辑:用 always_ff
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (en)
cnt <= cnt_next;
end
always @(*) 了。全部换成 always_comb 和 always_ff。代码意图一目了然,工具检查也更严格。我在一个项目里吃过亏——综合前仿真全绿,综合后多出一堆 latch,就是因为 always @(*) 的敏感列表没写全。从那以后,我就彻底改了这个习惯。
4.2 任务与函数
任务和函数,是验证代码模块化的基础。SystemVerilog对它们做了不少增强,尤其是 void function 和 automatic 关键字,用起来比Verilog舒服多了。
4.2.1 function 与 void function
传统Verilog的函数必须有返回值。SystemVerilog允许你定义 void function,没有返回值,纯粹执行操作。这在写验证组件时特别常用。
// 传统函数:必须有返回值
function automatic bit is_valid_packet(byte data[]);
return (data.size() > 0) && (data[0] == 8'hAA);
endfunction
// void function:没有返回值,适合做操作
function automatic void print_packet(byte data[]);
$display("Packet: %p", data);
endfunction
为什么用 automatic?因为Verilog默认函数里的变量是静态的,递归调用会出问题。SystemVerilog里,automatic 让每次调用都有独立的变量空间。我建议你写函数时,一律加上 automatic,省心。
4.2.2 task 与带参数传递
任务可以消耗仿真时间(带时序控制),函数不行。这是最根本的区别。
task automatic spi_write(input bit [7:0] addr, data,
output bit ack);
// 模拟SPI写操作
cs_n = 0;
#100;
sdi = addr;
#100;
sdi = data;
#100;
cs_n = 1;
ack = 1;
#10;
ack = 0;
endtask
调用时,你可以用 ref 关键字传递引用,避免大数组的拷贝开销。这在UVM的sequence里经常用到。
task automatic process_data(ref byte data_q[$]);
// 直接操作原队列,不拷贝
foreach (data_q[i])
data_q[i] = data_q[i] ^ 8'hFF;
endtask
input 传了一个大数组,仿真速度慢得离谱。后来改成 ref,速度快了10倍。但注意:ref 意味着你可能会修改原始数据,如果不想被改,加个 const ref。
4.3 事件:triggered 与 wait_order
事件是线程间同步的老办法。SystemVerilog在Verilog的 @(event) 基础上,加了 triggered() 函数,解决了事件竞争的问题。
4.3.1 传统事件 vs triggered()
传统写法:@(evt); 如果事件在 @ 之前就触发了,你就永远等不到了。这叫“事件丢失”。
triggered() 函数会返回一个布尔值,告诉你事件是否在 当前仿真时间片 内被触发过。即使你晚了一步检查,也能知道事件发生过。
event data_ready;
// 生产者线程
task automatic producer();
#10;
-> data_ready; // 触发事件
endtask
// 消费者线程
task automatic consumer();
#5;
// 用 triggered() 检查:事件是否在0时刻内触发过?
if (data_ready.triggered())
$display("事件已触发,开始处理数据");
else
$display("事件未触发,等待中...");
endtask
我个人习惯是:新代码一律用 triggered(),不再用 @(event)。因为事件竞争在验证环境里太常见了,尤其是多个driver同时驱动同一个事件时。
4.3.2 wait_order
wait_order 用来等待多个事件按指定顺序发生。如果顺序错了,它会报错。这在检查协议时序时特别有用。
event evt1, evt2, evt3;
task automatic check_sequence();
wait_order(evt1, evt2, evt3)
$display("事件顺序正确");
else
$error("事件顺序错误!期望 evt1->evt2->evt3");
endtask
嗯,这里要注意:wait_order 是阻塞的,它会一直等下去,直到所有事件按序发生,或者超时(如果你加了超时控制)。
4.4 断言基础:立即断言与并发断言
断言是验证的“眼睛”。没有断言,你就像在黑夜里开车——出了错也不知道。SystemVerilog提供了两种断言:立即断言和并发断言。
4.4.1 立即断言
立即断言是过程性的,写在过程块里,执行到这一行时立即检查。语法是 assert(expression)。
always_ff @(posedge clk) begin
// 检查:复位后第一个时钟,计数器必须为0
if (!rst_n)
cnt <= 0;
else begin
cnt <= cnt + 1;
// 立即断言:cnt 不能超过 15
assert (cnt <= 15) else
$error("cnt 溢出!当前值 = %0d", cnt);
end
end
立即断言的好处是简单直接。坏处是它只能检查当前时刻的值,不能检查跨时钟周期的时序关系。
4.4.2 并发断言
并发断言用 property 和 sequence 来描述,可以检查跨越多个时钟周期的行为。它基于时钟沿采样,不是过程性的。
// 定义一个序列:req 拉高后,2个时钟内 ack 必须拉高
sequence req_ack_seq;
@(posedge clk) req ##[1:2] ack;
endsequence
// 定义属性:每次 req 拉高,序列必须成立
property req_ack_prop;
@(posedge clk) $rose(req) |-> req_ack_seq;
endproperty
// 实例化断言
assert property (req_ack_prop)
else $error("req-ack 协议违例!");
并发断言的核心符号:
##n:延迟 n 个时钟周期##[m:n]:延迟 m 到 n 个时钟周期|->:蕴含操作符,左边条件成立时,右边必须成立$rose、$fell、$stable:边沿检测函数
4.5 本章知识体系
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个快速索引。
好了,这一章的内容就这些。过程块、任务函数、事件、断言——这四个东西,你在后续搭建UVM验证环境时会反复用到。尤其是断言,我建议你从现在开始就养成习惯:每写一个接口,至少加一个并发断言。相信我,这会帮你省下大量调试时间。
公众号:蓝海资料掘金营,微信deep3321