目录
本文主体内容翻译自https://www.systemverilog.io/verification/macros/,并作了一些增删。
摘要:
- 正确地使用宏可以让可以让代码更简洁。如果你对
`
,`"
,`\`"
这些符号不是很熟悉,那么可能会对一个较多宏的代码感到困惑甚至望而生畏。 文中将通过示例解释这些符号的含义,并展示如何使用它们。 - 我们还将探索一些UVM源代码的宏,并建立编写宏的样式指南。
- 在开始之前,需要提醒–过度使用宏可能会降低代码的可读性,因此要谨慎使用。有时候增加额外的抽象层次并不是最好的选择。
1 介绍
1.1 什么是宏
宏是使用`define
编译器指令创建的代码片段。它们基本上由三部分组成–名称、文本和可选参数。
`define macroname(ARGS) macrotext
在编译时,代码中的每个`macroname
都会被替换为字符串macrotext
,而ARGS
是可以在macrotext
中使用的变量。
1.2 为什么要使用宏
宏的使用一般可以归结为两种:编译宏和替换宏。编译宏一般是作为开关或者隔离选项,比如我们一致在文件前面开头和结尾加如下所示代码,目的就是避免重复编译。
`ifndef PKT_IF_SV
`define PKT_IF_SV
interface pkt_if(input clk, rst_n);
...
`endif
编译宏还可以在编译选项中采用 +define xxx
的形式。
我们接下来重点看下替换宏的使用。
在编写平台和测试用例的时候,我发现自己在几个地方都打印出了一个字节数组。 在代码的不同地方都出现类似的代码片段:
//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
作为替代,我可以定义如下的一个宏,用来替代1处的5行重复的代码,节省一些编码并使得代码可读性更强。此外,如果我需要更改打印格式,只需在一个地方进行修改。
`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
// When someone reads this code, they'll know
// it prints a formatted array of bytes
`print_bytes(bytearray, 0, numbytes)
`print_bytes(pkt, 20, 80)
我喜欢使用宏,特别是对于特殊的打印函数,这是`uvm_info
不足以实现的。如果你的团队成员都是用这个宏,那么你会得到一致的打印消息,这会让仿真日志对于每个人都更容易阅读。
以下是使用宏的一种可能方式:
- 建立一个宏库
- 为这个库的宏使用约定命名,例如
<*>_utils
(print_byte_utils
等)。 - 将其放在名为
macro_utils .sv
的文件中并将其包含在基础package中。 - 将其作为设计/验证方法学中的一部分,代替重复代码。
2 宏的语法
2.1 宏的名字
宏的命名规则是除了编译器指令,即诸如 `define
,`ifdef
,`endif
,`else
, `elseif
,`include
等关键字之外,可以使用任何名称。如果不小心使用了编译器指令,将会出现以下错误:
`define include(ARG) `"ARG.master`"
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)
此外需要注意,宏定义的替换实在编译完成时为不是在执行时,也就是说不会因为某一分支未执行而不进行宏定义。如下的代码执行是并不会根据a的值将DEF替换成不同的值,而是后面的宏定义会覆盖前面的定义,即DEF会被固定替换为`h20.
if(a==1)
`define DEF `h10
else
`define DEF `h20
2.2 宏符号 backtick- ``
,`"
,`\`"
有3种符号需要做一下解释。
2.2.1 反引号和引号(backtick and quotes)
`"
–如果宏文本被包含在普通引号(")中,则它就变成了一个字符串。双引号内的参数不会被替换,如果宏文本种嵌入了其他宏,则它们不会被展开。`"
(双引号之前的反引号)覆盖了"
的含义,并指示宏展开应该:
- 包含双引号
- 双引号内的参数应该被替换
- 内嵌的宏应该被展开
看以下的示例:
/* 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
CONSOLE OUTPUT
MOD.master
clock1.master
`append_front_bad
- 接收一个参数MOD
,预期的结果是宏将两个字符串连接起来-MOD
和.master
。但是由于双引号的存在,输出结果变成了字符串MOD.master
,参数没有被替换。`append_front_good
- 这是正确的写法。通过在双引号前加反引号`
,告诉编译器,参数MOD
需要被替换当宏展开的时候。
2.2.2 双重反引号(Double Backtick)
``
-双反引号实际上是一个分隔符。它有助于编译器清楚地区分宏文本中的参数和字符串的其余部分。通过下面这个例子解释:
/* 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
CONSOLE OUTPUT
# clock2a.master
# clock2b master
# MOD_master
# clock2c_master
# clock3.master
# top_clock4_master
# top_clock5
`append_front_2a and 2b
-空格和句点是天然的标记分隔符,即编译器可以清楚地识别宏文本种的参数。_2a
在参数MOD
和master
种使用句点,而_2b
使用了空格。`append_front_2c_bad
-如果参数附加了非空格或者非句点字符,如MOD_master
中的下划线,那么编译器将无法确定参数字符串MOD的结束位置。`append_front_2c_good
-双反引号``
在MOD
和_master
之间显式地创建了一个分割符,这就是为什么MOD
可以被正确地替换。`append_middle
和`append_end
是另外两个例子。`append_front_3
-在天然分隔符(句点或者空格)之前或者之后加上双反引号``
也是可以的,但是不起任何效果。
2.2.3 斜杠,撇号和引号(Slashes, Ticks and Quotes)
`\`"
-当宏展开时,将产生一个字面上的"
.当你再展开的宏中需要一个双引号时可以使用这个符号。
/* 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
CONSOLE OUTPUT
# This “BlueBeast” is Really “Black”
如果对以上概念仍然感到疑惑,后文提供了更多的例子,将会帮助你理解它。
2.3 杂项
- 可以再宏中调用另一个宏
- 在宏中使用注释是可以的,没有任何不同
- 在宏中使用
ifdefs
也是可以的,没有任何不同
3 参数传递
宏的参数需要记住这几点:
- 参数可以有默认值
- 即使没有默认值,也可以将位置空着来跳过参数值。参见example2中
`test(, ,)
打印的结果。 - 检查
`debug1
和`debug2
,如果你的参数是一个字符串,那么是否能需要将参数用双引号括起来取决于参数在宏文本中被替换的位置。在`debug1
中,宏文本中的MODNAME
在引号之中,因此在调用宏的时候没有将program-block
放在引号中。而在``debug2
中,传递的参数为"program-block"
,因为宏文本中的MODNAME
出现在引号之外。
/* 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
CONSOLE OUTPUT
# 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
4 宏风格指南
在团队中遵循一致的宏命名风格非常有用。由于UVM现在被广泛用作验证方法学,我们可以从他们的实践中借鉴,并使用与他们用于宏的编码风格相同的风格。如果您查看UVM库源代码,您将看到以下内容:
- 如果定义一个宏的task或者function,那么使用大写的宏名称和小写的参数名。
- 如果宏定义了类或者代码段之类的,请使用小写的宏名称和大写的参数名。
- 用下划线分割宏名称中的单词。
下一节将会挑选一些UVM中的宏来展示上述命名规范。
5 参考示例
有时候,你只需要一堆示例来刷新一下如何使用宏的记忆。所以,这里有一些我从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
6 总结
- 遵循编码风格指南,以追求团队一致性
- 知道
`"
,``
,`\
的用法 - 宏的作用域为全局命名空间。在一个类内定义的宏,不代表仅对这个类可见
- 在编译日志中留意重复定义宏的警告