Verilog系列:define的扩展用法(二)

宏除了可以进行简单的文本替换,还可以像函数和任务一样传递指定多个参数分别对文本进行对应的替换.

【示例】
`timescale 1 ns / 1 ps
`define DISP(d1,d2,d3) \
        initial begin \
            $display(`"data_``d1 + data_``d2 + data_``d3= %h`",(d1 + d2 + d3)); \
        end
module top_tb;
`DISP(1,2,3)
endmodule
【仿真结果】
# data_1 + data_2 + data_3= 00000006
示例首先告诉我们,当宏定义中宏文本内容比较多时,写在一行阅读起来比较麻烦,此时可以使用“\”进行换行.其次,宏名可以带有任意多个“形参”,各个参数之间通过“,”或者若干个空格进行分割(此处空格不会作为被替换的文本进行替换).这里还需要注意宏替换时存在优先匹配的情况,示例中虽然宏文本中“d3=”为一个连续的字符串,但是替换时首先对匹配的“d3”进行替换,而不是“d3=”.
既然宏定义时可以指定参数,那么这些参数是不是也可以指定默认值呢?在实际的应用过程中,也可以为宏定义指定的参数指定默认值,但是在使用时需要特别注意,不能随意使用(当然任何语法都不能随意使用).

【示例】
`timescale 1 ns / 1 ps
`define DISP(m1,m2="Middle",m3) \
        initial begin \
            $display("Start",m1,m2,m3,"End!"); \
        end
module top_tb;
`DISP("Before",,"After")          // StartBeforeMiddleAfterEnd!
`DISP("Before","Haha","After")    // StartBeforeHaHaAfterEnd!
`DISP(,,"After")                  // Start MiddleAfterEnd!
`DISP(" Before ",," After"  )     // Start Before Middle AfterEnd!
`DISP(,,)                         // Start Middle End!
`DISP(,,,)                        // illegal,more actual arguments
`DISP(,)                          // illegal,less actual arguments
endmodule
【仿真结果】
# StartBeforeMiddleAfterEnd!
# StartBeforeHahaAfterEnd!
# Start MiddleAfterEnd!
# Start Before Middle AfterEnd!
# Start Middle End!
# illegal!
# illegal!

从仿真结果可以观测到:
第7行中宏的第二个参数并没有给出,此时替换时将采用的是宏定义时指定的默认参数,并且各个字符串之间并没有空格出现;
第8行中宏的第二个参数显式给出,此时替换时将采用显式给出的参数进行替换而不是宏定义时指定的默认参数;
第9行中宏的第一个参数和第二个参数都没有显式给出,但是通过都好保留了参数的位置,其中第一个参数没有指定默认参数,所以进行替换后仅用一个空格空出了该参数的位置,第二个参数因为有指定的默认值,所以在实参没有指定时,宏定义将使用指定的默认参数进行替换,如果第二个参数没有指定默认值,那么此时第二个参数的位置也会有一个空格空出该参数的位置;
第10行双引号中第一个参数的首字母前增加一个空格,那么这个空格将会成为第一个参数的一部分替换到宏文本中.最后一个参数双引号外增加了多个空格,因为空格出现在字符串以外,所以不属于替换字符串的一部分,不会替换到宏文本中.
第11行三个参数均没有进行设置,因为第二个参数存在默认值,所以第二个参数将会使用默认值替换宏文本,宏文本被对应参数替换后,第一个和第三个参数所在位置将会有一个空格空出该参数的位置;
第12行虽然没有指定参数,但是实际留下参数的位置个数与宏定义中指定参数个数不同,多于宏定义指定的三个参数,编译错误;
第13行与第12行错误类似,这里只是参数个数少于指定的参数个数(这里因为三个参数仅指定了一个默认参数,如果三个参数都有默认值编译不会错误,后文将示例).所以,参数可以不指定,但是空余的位置还是需要与定义时匹配;

【示例】
`timescale 1 ns / 1 ps
`define DISP(m1="Before",m2="Middle",m3="After") \
        initial begin \
            $display("Start",m1,m2,m3,"End!"); \
        end
module top_tb;
`DISP()               // StartBeforeMiddleAfterEnd!
`DISP("Haha")    // StartHahaMiddleAfterEnd!
`DISP                  // illegal
endmodule
【仿真结果】
# StartBeforeMiddleAfterEnd!
# StartHahaMiddleAfterEnd!
# illegal!
第7行当三个宏参数都指定了默认值时,在调用宏时可以不指定参数,但是括号必须保存,否则会像第9行一样编译错误;
第8行没有显式的用逗号分隔参数,仅传递了一个参数“Haha”,那么这个参数将会作为第一个参数替换掉形参指定的默认值,其余参数仍然保持原默认值不变;

【示例】
`timescale 1 ns / 1 ps
`define DISP(m1="Before",m2,m3="After") \
        initial begin \
            $display("Start",m1,m2,m3,"End!"); \
        end
module top_tb;
`DISP(,)     // StartBefore AfterEnd!
`DISP(,,)    // StartBefore AfterEnd!
`DISP()      // illegal
endmodule
【仿真结果】
# StartBefore AfterEnd!
# StartBefore AfterEnd!
# illegal!
第7行因为第一个参数和第三个参数都有默认值,参数列表中仅使用一个逗号保留了两个参数的位置,这两个参数对应的是第一个参数和第二个参数,因为第一个参数有默认值文本替换时使用的是默认值替换,第二个参数相当于没有指定具体要替换的内容,所以宏文本中对应位置保留一个空各位.虽然在宏调用时没有使用都好分隔预留第三个参数的位置,但是第三个参数有默认值,文本替换时会使用默认值替换.
第8行用逗号分隔参数列表,把两个参数的位置都保留了出来,输出结果与第7行一致;
第9行编译错误,因为宏定义时参数列表中并不是所有参数都有默认值,此时参数列表不能为空;
宏除了像函数和任务那样存在参数列表外,还可以像函数和任务那样进行相互调用.

【示例】
`timescale 1 ns / 1 ps
`define FI first
`define TW "`FI two"
`define S1(x) "first two x"
`define S2(x) `"first two x`"
`define ADD(a,b) a+b
module top_tb;
int sum;
initial begin
    $display("`FI");
    $display(`TW);
    $display(`S1(third));    
    $display(`S2(third));     
    $display(`S2(`S2(forth))); // illegal
    sum = `ADD(`ADD(1,2),`ADD(3,4));
    $display("The sum is %h",sum);
end
endmodule
【仿真结果】
# `FI
# `FI two
# first two x
# first two third
# illegal!
# The sum is 0000000a
第10行字符串中的宏定义不会被替换掉;
第11行虽然宏文本中有宏`FI调用,但是处于字符串中的宏调用不会被调用;
第12行字符串中的内容不会被宏名后的参数替换;
第13行“`”对原字符串进行了处理,所以宏名后的参数可以替换文本中的内容;
第14行字符串中的宏调用不能进行,其调用实现过程如下:
第15行宏可以像函数和任务那样嵌套调用,其分析过程类似第14行;

综上所述,可以得到以下几点关于宏的使用的通用规律:
Ø  如果对应的宏参数指定了默认值,那么该参数在宏调用时可以不指定实参.如果所有的参数都指定了默认参数,那么在宏调用时可以仅保留括号不传递任何参数;
Ø  如果存在部分宏参数没有指定默认值,那么在调用宏时不能省略所有的实参,但是可以使用“,”空留出各个参数的位置;
Ø  如果宏参数中最后一个参数指定了默认值,那么如果期望使用该参数默认值时,在调用宏时可以不保留该参数的位置,即不使用逗号专门空留对应位置;
Ø  宏定义的宏名不能与编译命令名字相同,例如define的宏名不能是define等;
Ø  宏名可以作为一般的信号名,与宏名不冲突,例如定义的宏名为“VAR_V”,那么可以在使用该宏的代码中定义“reg VAR_V”变量,该变量与宏“VAR_V”不冲突,其实主要是使用宏时,在宏名前指定了“`”,将宏名与其他信号变量进行了区分;
Ø  可以重复定义宏,但是仅有最后一次定义的宏有效,也就是说前几次其实都被最后一次定义覆盖了;
Ø  如果宏调用出现在字符串文本中,那么该宏调用将不能进行;
在进行一些文本打印时,有时文本中部分内容需要包含双引号字符,但是文本本身就是以双引号作为限定的,即字符串会将第一个"到下一个"之间的内容作为字符串文本,如果在文本字符串中本身包含",那么会认为当前字符文本结束,问题就来了,这种情况如何实现文本中的双引号的输出呢?

【示例】
`timescale 1 ns / 1 ps   
module top_tb;
int data;
initial begin
    data = 'hA;
    $display("Variable \"v\" is %h",data);
end
endmodule
【仿真结果】
# Variable "v" is 0000000a
示例中可以看到,通过在需要插入引号的地方使用转义符“\”,既可以实现字符串中引号的嵌入.那么如果试图将宏文本中插入的双引号中的内容进行宏参数替换,可以按照如下示例方法进行.

【示例】
`timescale 1 ns / 1 ps   
`define DISP(v) $display(`"Variable `\`"v`\`" is %h`",v);
module top_tb;
int data;
initial begin
    data = 'hA;
    `DISP(data);
end
endmodule
【仿真结果】
# Variable "data" is 0000000a
其中首尾的`"确保了双引号中的内容也可以被宏参数进行文本替换,`\`"确保了其中的双引号可以被有效输出,同时该双引号中的内容也可以被宏参数替换.


全部评论
感谢大神的分享,学习到了
点赞 回复 分享
发布于 2022-09-14 16:10 河南

相关推荐

评论
点赞
收藏
分享
牛客网
牛客企业服务