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

团队宏使用建议:

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

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

宏语法

宏命名规则

宏名称的命名规则很简单:可以使用任何名称,但不能使用编译器指令关键字,如 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 等)

参数传递技巧

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

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

debug1debug2 为例:在 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 库源代码,可以总结出以下规则:

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

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

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 等成熟库的宏设计模式

通过合理运用宏,可以显著提升代码复用性和开发效率,但切记平衡简洁性与可维护性。

posted @ 2025-09-23 15:37  LeslieQ  阅读(160)  评论(0)    收藏  举报