systemverilog macros 宏使用
SystemVerilog 宏使用指南(中文版)
介绍
什么是宏?
宏是使用 `define 编译器指令创建的代码片段。它们基本上有 3 个部分 - 名称、一些文本和可选参数。
`define macroname(ARGS) macrotext
在编译时,代码中出现的 macroname 都会被替换为 macrotext 字符串,并且 ARGS 是可以在 macrotext 中使用的变量。
为什么要使用宏?
在编写测试平台和用例时,经常会遇到代码重复的问题。比如,需要在多个地方打印字节数组,会出现类似这样的重复代码:
//1
for (int ii=0; ii<numbytes; ii++) begin
if ((ii !=0) && (ii % 16 == 0))
$display("\n");
$display("0x%x ", bytearray[ii]);
end
//2
for (int ii=20; ii<100; ii++) begin
if (ii % 16 == 0)
$display("\n");
$display("0x%x ", pkt[ii]);
end
通过定义宏,可以将 5 行重复代码简化为 1 行调用,不仅减少了代码量,还提升了可读性。更重要的是,如果需要修改打印格式,只需在宏定义处修改一次即可!
`define print_bytes(ARR, STARTBYTE, NUMBYTES) \
for (int ii=STARTBYTE; ii<STARTBYTE+NUMBYTES; ii++) begin \
if ((ii != 0) && (ii % 16 == 0)) \
$display("\n"); \
$display("0x%x ", ARR[ii]); \
end
// 使用宏后,代码的意图更加明确
`print_bytes(bytearray, 0, numbytes)
`print_bytes(pkt, 20, 80)
宏特别适用于专用的打印功能等场景。虽然功能上不如 uvm_info 强大,但对于简单场景已经足够。如果团队成员都使用统一的宏,就能获得一致的输出格式,让所有人都能更轻松地阅读仿真日志。
团队宏使用建议:
- 建立团队宏库,统一管理常用宏定义
- 制定宏命名规范,例如使用
*_utils后缀(如print_byte_utils) - 将宏定义集中放在
macro_utils.sv文件中,并在基础包中引用 - 在合适的场景下优先使用宏,减少重复代码,提升开发效率
通过以上方式,宏可以成为团队设计/验证方法论的重要组成部分。
宏语法
宏命名规则
宏名称的命名规则很简单:可以使用任何名称,但不能使用编译器指令关键字,如 define、ifdef、endif、else、elseif、include 等。如果错误地使用了这些关键字,编译器会报错:
`define include(ARG) `"ARG.master`"
Mentor Graphics Questa:
** Error: macros_one.sv(4): (vlog-2264) Cannot redefine compiler directives:
`include.
Synopsys VCS:
Error-[IUCD] Illegal use of compiler directive
Illegal use of compiler directive: `include is illegal in this context.
"macros_one.sv", 28
Source info: $display(`include(clock));
重要提醒: 宏的命名空间是全局的,编译顺序决定了宏的有效性。无论宏定义在类内还是类外,一旦被编译器识别,就可以在任何地方使用。如果重新定义宏,编译器会发出警告,需要特别注意这些日志信息:
** Warning: macros2.sv(7): (vlog-2263) Redefinition of macro:
'debug' (previously defined near macros2.sv(3)) .
Parsing design file 'macros4.sv'
Warning-[TMR] Text macro redefined
macros2.sv, 7
Text macro (debug) is redefined. The last definition will override previous
ones.
In macros2.sv, 3, it was defined as $display("%s, %0d: %s", `__FILE__,
`__LINE__, msg)
反引号特殊用法详解
SystemVerilog 中有 3 个特殊的反引号操作符需要重点掌握:
1. 字符串化操作符 `"
`" 操作符用于将宏参数转换为字符串。如果宏文本用普通双引号(")括起来,参数不会被替换,会被当作普通字符串处理。而 `"(双引号前的反引号)则告诉编译器:
- 保留双引号字符
- 替换双引号内的参数
- 展开嵌入的其他宏
让我们通过示例来理解:
示例 1.1
/* Example 1.1 */
`define append_front_bad(MOD) "MOD.master"
`define append_front_good(MOD) `"MOD.master`"
program automatic test;
initial begin
$display(`append_front_bad(clock1));
$display(`append_front_good(clock1));
end
endprogram: test
运行结果
# MOD.master
# clock1.master
分析:
append_front_bad- 接受参数 MOD,期望将其与".master"拼接。但由于使用了普通双引号,参数不会被替换,输出结果为字符串"MOD.master"append_front_good- 正确的版本。通过在双引号前使用反引号,告诉编译器在宏展开时替换参数 MOD
2. 标记拼接操作符 ``
`` (双反引号)是标记分隔符,帮助编译器明确区分宏参数和字符串的其他部分。当参数需要与其他字符紧密拼接时特别有用。
示例 1.2
/* Example 1.2 */
`define append_front_2a(MOD) `"MOD.master`"
`define append_front_2b(MOD) `"MOD master`"
`define append_front_2c_bad(MOD) `"MOD_master`"
`define append_front_2c_good(MOD) `"MOD``_master`"
`define append_middle(MOD) `"top_``MOD``_master`"
`define append_end(MOD) `"top_``MOD`"
`define append_front_3(MOD) `"MOD``.master`"
program automatic test;
initial begin
/* Example 1.2 */
$display(`append_front_2a(clock2a));
$display(`append_front_2b(clock2b));
$display(`append_front_2c_bad(clock2c));
$display(`append_front_2c_good(clock2c));
$display(`append_front_3(clock3));
$display(`append_middle(clock4));
$display(`append_end(clock5));
end
endprogram: test
运行结果
# clock2a.master
# clock2b master
# MOD_master
# clock2c_master
# clock3.master
# top_clock4_master
# top_clock5
分析:
append_front_2a/2b- 空格和句点是天然的标记分隔符,编译器可以清楚识别宏参数。2a 使用句点分隔,2b 使用空格分隔append_front_2c_bad- 当参数后直接跟下划线等字符时,编译器无法确定参数的边界append_front_2c_good- 通过双反引号明确分隔参数和后续字符,确保正确替换append_middle/append_end- 展示了在字符串中间和末尾使用双反引号的效果append_front_3- 在天然分隔符处使用双反引号不会产生影响
3. 转义双引号操作符 `\`"
`\`" - 当宏被扩展时,会生成文字双引号 "。如果需要在宏展开后的文本中包含双引号字符,请使用此操作符。
示例 1.3
/* Example 1.3 */
`define complex_string(ARG1, ARG2) \
`"This `\`"Blue``ARG1`\`" is Really `\`"ARG2`\`"`"
program automatic test;
initial begin
$display(`complex_string(Beast, Black));
end
endprogram: test
运行结果
# This "BlueBeast" is Really "Black"
通过上述示例可以看出,复杂的字符串拼接和转义可以通过这些操作符灵活实现。
补充说明
- 宏可以嵌套调用其他宏
- 允许在宏中使用注释
- 宏定义中可以使用条件编译指令(ifdef 等)
参数传递技巧
关于宏参数的几个重要特性:
- 默认参数值 - 参数可以设置默认值
- 可选参数 - 可以通过留空位置跳过参数,即使该参数没有默认值(如示例中的
test2(,,)) - 字符串参数处理 - 需要根据参数在宏文本中的位置来决定是否添加引号
以 debug1 和 debug2 为例:在 debug1 中,MODNAME 位于引号内,因此调用时不需要加引号;而 debug2 中的 MODNAME 位于引号外,因此需要传入带引号的字符串。
示例 2
/* Example 2 */
`define test1(A) $display(`"1 Color A`")
`define test2(A=blue, B, C=green) $display(`"3 Colors A, B, C`")
`define test3(A=str1, B, C=green) \
if (A == "orange")$display("Oooo .. I received an Orange!"); \
else $display(`"3 Colors %s, B, C`", A);
`define debug1(MODNAME, MSG) \
$display(`" ``MODNAME`` >> %s, %0d: %s`", `__FILE__, `__LINE__, MSG)
`define debug2(MODNAME, MSG) \
$display(`"%s >> %s, %0d: %s`", MODNAME, `__FILE__, `__LINE__, MSG)
program automatic test;
initial begin
string str1, str2;
str1 = "gray";
str2 = "orange";
// No args provided even without default value
`test1();
`test2(,,);
// Passing some args
`test1(black);
`test2(,pink,);
// Passing a var (str1, str2) as an arg
`test3(str1,mistygreen,babypink);
`test3(str2,mistygreen,babypink);
`debug1(program-block, "this is a debug message");
`debug2("program-block", "this is a debug message");
/* The following will result in an Error
* `test2(); // no args provided
* `test2(,); // insufficient args provided
*
* // arg1 is used as a var in an if statement,
* // This will compile fail because the macro
* // will expand to *if ( == "orange")*
* `test(, blue, pink)
*/
end
endprogram: test
运行结果
# 1 Color
# 3 Colors blue, , green
# 1 Color black
# 3 Colors blue, pink, green
# 3 Colors gray, mistygreen, babypink
# Oooo .. I received an Orange!
# program-block >> macros2.sv, 31: this is a debug message
# program-block >> macros2.sv, 32: this is a debug message
编码规范建议
为了确保团队开发的一致性,建议遵循 UVM 的宏命名规范。通过分析 UVM 库源代码,可以总结出以下规则:
- 函数/任务宏 - 使用 UPPERCASE 宏名称和 lowercase 参数名称
- 类/代码片段宏 - 使用 lowercase 宏名称和 UPPERCASE 参数名称
- 单词分隔 - 使用下划线分隔,避免使用驼峰命名
这些规范经过大量实践验证,有助于提升代码的可读性和维护性。
UVM 宏实践示例
以下是从 UVM 库中精选的宏定义,展示了标准的编码规范和实际应用场景:
//> src/base/uvm_phase.svh
`define UVM_PH_TRACE(ID,MSG,PH,VERB) \
`uvm_info(ID, {$sformatf("Phase '%0s' (id=%0d) ", \
PH.get_full_name(), PH.get_inst_id()),MSG}, VERB);
//> src/macros/uvm_tlm_defines.svh
// Check out the MacroStyleGuide in use here
// [ 1.] If the Macro defines a function or a task,
// use UPPERCASE for macro name and lower case for args
`define UVM_BLOCKING_TRANSPORT_IMP_SFX(SFX, imp, REQ, RSP, req_arg, rsp_arg) \
task transport( input REQ req_arg, output RSP rsp_arg); \
imp.transport``SFX(req_arg, rsp_arg); \
endtask
// [ 2a.] For everything else (i.e., if Macro defines
// a snippet of code, class or a convenience definition),
// use lowercase with UPPERCASE args
`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
`UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
function void write( input T t); \
m_imp.write``SFX( t); \
endfunction \
\
endclass
// [ 2b.] Examples of lowercase+uppercase with snippets
`define uvm_error(ID, MSG) \
begin \
if (uvm_report_enabled(UVM_NONE,UVM_ERROR,ID)) \
uvm_report_error (ID, MSG, UVM_NONE, `uvm_file, `uvm_line, "", 1); \
end
// [ 3.] Separate words with underscores, don't use camelCase
`define uvm_non_blocking_transport_imp_decl(SFX) \
`uvm_nonblocking_transport_imp_decl(SFX)
//> src/macros/uvm_sequence_defines.svh
`define uvm_do(SEQ_OR_ITEM) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, {})
`define uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS) \
`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, CONSTRAINTS)
`define uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS) \
begin \
uvm_sequence_base __seq; \
`uvm_create_on(SEQ_OR_ITEM, SEQR) \
if (!$cast(__seq,SEQ_OR_ITEM)) start_item(SEQ_OR_ITEM, PRIORITY);\
if ((__seq == null || !__seq.do_not_randomize) && !SEQ_OR_ITEM.randomize() with CONSTRAINTS ) begin \
`uvm_warning("RNDFLD", "Randomization failed in uvm_do_with action") \
end\
if (!$cast(__seq,SEQ_OR_ITEM)) finish_item(SEQ_OR_ITEM, PRIORITY); \
else __seq.start(SEQR, this, PRIORITY, 0); \
end
`define uvm_create_on(SEQ_OR_ITEM, SEQR) \
begin \
uvm_object_wrapper w_; \
w_ = SEQ_OR_ITEM.get_type(); \
$cast(SEQ_OR_ITEM , create_item(w_, SEQR, `"SEQ_OR_ITEM`"));\
end
核心要点总结
为了确保宏的正确使用和团队协作的顺畅,请牢记以下核心要点:
编码规范
- 遵循 UVM 编码风格:保证团队代码的一致性
- 函数/任务宏:使用 UPPERCASE 宏名 + lowercase 参数
- 类/代码片段宏:使用 lowercase 宏名 + UPPERCASE 参数
- 命名方式:用下划线分隔单词,避免驼峰命名法
技术要点
- 掌握反引号用法:理解 `"、``、`\`" 的不同作用
- 注意全局作用域:宏定义位置不影响作用域,编译顺序才是关键
- 关注编译警告:及时发现和解决宏重定义问题
使用建议
- 适度使用:过度依赖宏会降低代码可读性
- 统一管理:建立团队宏库,集中维护常用定义
- 参考实践:学习 UVM 等成熟库的宏设计模式
通过合理运用宏,可以显著提升代码复用性和开发效率,但切记平衡简洁性与可维护性。

浙公网安备 33010602011771号