1 丶应用场景:
当我们 interface 中有几组信号的驱动或采样方式相同时,我们可以把 interface 中的信号作为参数来声明宏定义,将这些相同的操作以带参数的宏定义方式抽象出来。
2 丶举例:
前段时间写 axi master bfm 时,axi 有 5 个通道,每个通道都有一个握手机制;
当 a 信号拉高后,进入等待握手状态,开启一个无限循环进程,开始每拍进行计数,并判断是否超时;
当 b 信号拉高后,计数器清零,跳出循环进程,并将 a 信号拉低复位。
同时设置一个开关 c,来控制是否开启超时检查。
`define wait_handshake(a, b, c) \
forever begin \
@(posedge clk); \
cnt++; \
if(``a``=== 1'b1 && ``b`` === 1'b1) begin \
cnt = 0; \
break; \
end else if(cnt > cfg.cnt && ``c``) begin \
$display("out time."); \
end \
end \
``b`` <= 1'b0; \
3 丶缺陷:
由于宏定义的特性,我们定义的带参数的宏定义表示的是一行代码;
如果我们定义的带参数的宏定义过长,出现了错误,编译器无法给出精准的出错位置,只能指出是哪行的宏定义有问题,而无法指到宏定义内部,这无疑是给 DEBUG 增加了难度。
4 丶其他:
那我们为什么没有将它抽象成 task?
因为声明 task 要指明参数的类型及位宽,而宏定义的参数则不需要;
往往我们 interface 中几组信号的驱动或采样方式相同时,但他们的位宽并不相同。
还有一个好处,task 的作用域只局限于当前 object,而宏定义则是全局;
但这也有个坏处,如果我们不熟悉环境,可能找不到宏定义是在哪声明的,使代码的可读性变弱了。