仍旧是最近持续更新的交付总结输出系列,今天的讨论源于前几天和leader的一次对话,大体内容如下:
HE:”parameter没用,都换成define就行了。“
ME:”对对对,但是define也不能滥用。“
于是借这篇文章说说我对宏定义和parameter在设计领域的使用的一些思考吧。关于之前有关几篇关于parameter和define的文章参见“阅读原文”。
宏使用场景
相对于芯片验证领域,宏在芯片设计领域的使用场景和复杂度方面是远远不及的。我自己看到过的RTL和设计交付中使用宏的场景,大概就以下这些。
1. 文件宏隔离。这个场景也是宏定义的专属场景,parameter不可替代。在交付filelist过程中如果确实难以避免一些文件重复编译,那么使用宏定义隔离也未尝不可(虽然个人不支持这样做)。
`ifndef XXX_MODULE
`define XXX_MODULE
...
`endif
`ifdef __THREADS__
RESMODE <= RES ? -1 : RESMODE ? RESMODE-1 : 0;
XRES <= |RESMODE;
`else
XRES <= RES;
`endif
`ifdef __3STAGE__
FLUSH <= XRES ? 2 : HLT ? FLUSH : // reset and halt
FLUSH ? FLUSH-1 :
`ifdef__INTERRUPT__
MRET ? 2 :
`endif
JREQ ? 2 : 0; // flush the pipeline!
`else
FLUSH <= XRES ? 1 : HLT ? FLUSH : // reset and halt
JREQ; // flush the pipeline!
`endif
`ifdef QMTECH_KINTEX7_K325
`define BOARD_ID 12
`define BOARD_CK_REF 50000000
`define BOARD_CK_MUL 20
`define BOARD_CK_DIV 4
`define XILINX7CLK 1
`define INVRES 1
`endif
5. 替代某些字符串或固定操作,后者甚至有点像我比较推崇的寄存器例化风格。
`define CLK core_clk
`define RSTN core_rst_n
always @(posedge `CLK or negedge `RST_N)...
//====================================================================================
//====================================================================================
`define ALWAYS_CLK(clk, rst_n) \
always @(posedge clkor negedge rst_n)
//====================================================================================
//====================================================================================
`define DFFE(D, Q, EN) \
always @(posedge `CLK) \
if(EN == 1'b1) Q <= D;
`DFFE(sig_a_d, sig_a, sig_a_en)
参数传递和模块化设计:宏定义可以用于定义参数,实现参数的模块化设计。例如,在进行外部芯片初始化时,需要定义大量参数,通过`define宏定义可以避免直接在module中通过localparam或parameter进行参数定义带来的代码冗长和不易修改的问题。
防止文件重复编译:在芯片验证过程中,使用宏定义可以防止同一个文件在编译时被重复编译,引发多重定义的问题。通过`ifndef、`define和`endif配合使用,可以实现条件编译,确保代码块只被编译一次。
提高代码复用性和可维护性:宏定义可以定义一些常用的代码片段或参数,使得在多处使用时,只需修改宏定义而不需要在每个使用点进行修改,提高了代码的复用性和可维护性。
跨文件传递参数:与localparam和parameter不同,宏定义可以跨文件传递参数,而parameter只能在模块间传递参数,localparam则只能在其所在的module中起作用。
简化代码和提高调试效率:通过宏定义,可以用一个简单的标识符来代替复杂的表达式或代码段,简化代码的规模,并有效提高调试的效率。
条件编译:在某些特定的编译条件下,可能需要包含或排除某些代码段,`define配合`ifdef和`ifndef可以实现条件编译,根据编译时的条件决定是否包含特定的代码块。
文本替换:`define宏定义在预处理阶段会被替换为其定义的值,这种文本替换功能可以用于各种场景,如定义常量、代码片段等,以减少代码重复和提高代码清晰度。
宏定义的风险
上面列举的这些优势实际上也就对应了使用parameter的劣势如非跨文件作用、不具备全局性、灵活性差、无法直接替换等。但是我仍旧秉承”非必要不定义宏“和”宏定义项目组统一管理“两个宏定义的使用原则,不建议太多的使用宏。宏最大的问题在我看来还是将内部结构暴露于不可控的外部环境下,这一点在《综合的harden block顶层为什么不能有参数》也有所讨论(请见“阅读原文”)。
简单而言就是更上层可以通过修改宏定义来改变所交付的RTL代码行为,当然这不一定是问题或许恰好就要用这个特性来选择分支,只是说会有这个风险存在。而我们交付的代码如果是前端交付(即交付HDL代码),当然是希望其在映射为后端网表的过程中行为不要有任何的变化,”所见即所得“嘛。在之前的验证文章里我称宏定义、force和callback为饮鸩止渴也是因为这三种操作的本质都是将环境和RTL内部的操作暴露于更上层,进而引入风险。例如子系统定义了一个位宽宏,芯片层恰好也定义了一个同名位宽宏,那最后到底哪个宏起了作用完全就看文件的编译顺序了,很有可能互相干扰了RTL实现。所以宏定义项目组统一管理我觉得是比较合理的策略,这也必然决定了每个子系统不能随意定义自己的宏,即非必要不使用,使用时确保互不干扰。
此外宏定义的一些其他缺点比如隐蔽性强、可读性差、调试定位困难等,也是个人不太推荐频繁使用宏的原因。那么在明确了”非必要不定义宏“和”宏定义项目组统一管理“这两个大原则之后,再回过头来看看怎么用parameter填补一部分宏定义的场景。parameter最为大家所诟病的两点就是使用不方便以及需要按层传递不便于统一管理。使用不方便的例子比如ADDR_W'd5这个写法就不行,要对齐位宽只能写成{{(ADDR_W-3){1'b0}, 3'd5}这种繁琐的方式。这个问题好像挺挺无解的,除非以后IEEEE改语法(即使改语法也是作用到SystemVerilog,还在坚持verilog的老古董享受不到啊!)。
需要按层传递不便于统一管理这个倒是有一些办法,比如个人现在采用的 `include "xxx_param.v"的方式。因为一个子系统内各种参数大概率是一致和配套的,可以将所有的参数或基础参数定义到同一个文件中,每个模块都include该文件。如果在某个文件内有单独的parameter需求可以在本文件内以parameter或localparam的方式增加,推荐localparam毕竟parameter说明上层可改配那么更接近一个通用的参数可以考虑放在xxx_param.v中了。这种方式也不用担心子系统和芯片定义装车,毕竟只有该子系统内的模块才会include该参数文件(且独立交付的顶层尽可能不漏parameter出去)。
以上是个人对宏定义和parameter在芯片设计领域使用场景的一些思考,难免有疏漏和错误,请不吝指正讨论。
系列文章入口——
【芯片验证】sva_assertion: 15道助力飞升的断言练习 |
【芯片验证】可能是RTL定向验证的巅峰之作 |
【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起 |
【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析 |
【芯片设计】verilog中有符号数和无符号数的本质探究 |
【芯片设计】论RTL中always语法的消失术 |
【芯片设计】代码即注释,注释即代码 |
【芯片设计】700行代码的risc处理器你确实不能要求太多了 |
入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢 |
如何计算系统的outstanding 和 burst length? |
芯片搬砖日常·逼死强迫症的关键词不对齐事件 |
熟人社会里,一群没有社会价值的局外人 |