介绍

什么是宏?

宏是使用 `define 编译器指令创建的代码片段。它们基本上有 3 个部分 - 名称、一些文本和可选参数。

1
`define macroname(ARGS) macrotext

在编译时,代码中出现的 macroname 都会被替换为 macrotext 字符串,并且 ARGS 是可以在 macrotext 中使用的变量。

为什么要使用宏?

在编写测试平台和用例时,经常会遇到代码重复的问题。比如,需要在多个地方打印字节数组,会出现类似这样的重复代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
//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 行调用,不仅减少了代码量,还提升了可读性。更重要的是,如果需要修改打印格式,只需在宏定义处修改一次即可!

1
2
3
4
5
6
7
8
9
10
`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 强大,但对于简单场景已经足够。如果团队成员都使用统一的宏,就能获得一致的输出格式,让所有人都能更轻松地阅读仿真日志。

团队宏使用建议:

  1. 建立团队宏库,统一管理常用宏定义
  2. 制定宏命名规范,例如使用 *_utils 后缀(如 print_byte_utils)
  3. 将宏定义集中放在 macro_utils.sv 文件中,并在基础包中引用
  4. 在合适的场景下优先使用宏,减少重复代码,提升开发效率

通过以上方式,宏可以成为团队设计/验证方法论的重要组成部分。

宏语法

宏命名规则

宏名称的命名规则很简单:可以使用任何名称,但不能使用编译器指令关键字,如 define、ifdef、endif、else、elseif、include 等。如果错误地使用了这些关键字,编译器会报错:

1
`define include(ARG) `"ARG.master`"

Mentor Graphics Questa:

1
2
** Error: macros_one.sv(4): (vlog-2264) Cannot redefine compiler directives:
`include.

Synopsys VCS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
/* 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

运行结果

1
2
# MOD.master
# clock1.master

分析:

  • append_front_bad - 接受参数 MOD,期望将其与”.master”拼接。但由于使用了普通双引号,参数不会被替换,输出结果为字符串”MOD.master”
  • append_front_good - 正确的版本。通过在双引号前使用反引号,告诉编译器在宏展开时替换参数 MOD

2. 标记拼接操作符 ``

`` (双反引号)是标记分隔符,帮助编译器明确区分宏参数和字符串的其他部分。当参数需要与其他字符紧密拼接时特别有用。

示例 1.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 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

运行结果

1
2
3
4
5
6
7
# 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

1
2
3
4
5
6
7
8
9
/* 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

运行结果

1
# This "BlueBeast" is Really "Black"

通过上述示例可以看出,复杂的字符串拼接和转义可以通过这些操作符灵活实现。

补充说明

  • 宏可以嵌套调用其他宏
  • 允许在宏中使用注释
  • 宏定义中可以使用条件编译指令(ifdef 等)

参数传递技巧

关于宏参数的几个重要特性:

  1. 默认参数值 - 参数可以设置默认值
  2. 可选参数 - 可以通过留空位置跳过参数,即使该参数没有默认值(如示例中的 test2(,,))
  3. 字符串参数处理 - 需要根据参数在宏文本中的位置来决定是否添加引号

以 debug1 和 debug2 为例:在 debug1 中,MODNAME 位于引号内,因此调用时不需要加引号;而 debug2 中的 MODNAME 位于引号外,因此需要传入带引号的字符串。

示例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 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
2
3
4
5
6
7
8
# 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 库源代码,可以总结出以下规则:

  1. 函数/任务宏 - 使用 UPPERCASE 宏名称和 lowercase 参数名称
  2. 类/代码片段宏 - 使用 lowercase 宏名称和 UPPERCASE 参数名称
  3. 单词分隔 - 使用下划线分隔,避免使用驼峰命名

这些规范经过大量实践验证,有助于提升代码的可读性和维护性。

UVM 宏实践示例

以下是从 UVM 库中精选的宏定义,展示了标准的编码规范和实际应用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//> 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