在这篇文章《从几个简单例子聊聊Verilog的参数化设计(parameter、localparam和`define)》中已经讨论过 `define 的一些用法,但不太深入,所以今天再说道说道。
在日常的编码过程中,常常碰到一个参数会被到处调用的情况,比如时钟的定义和调用:
//假设时钟为20ns
always # (20/2)clk1 = ~clk1; //场景1,使用到时钟20
always # (20/2)clk2 = ~clk2; //场景2,使用到时钟20
always # (20/2)clk3 = ~clk3; //场景3,使用到时钟20
always # (20/2)clk4 = ~clk4; //场景4,使用到时钟20
在调试过程中,如果时钟参数从20ns改成了10ns,则上面这段代码需要改动的地方就会多达5处。很明显,时钟参数被调用的情况越多,则改动代码越麻烦。
宏定义 `define 提供用一个相对简单的文字来表示一大段真正有意义的文字作用。换句话说,就是综合软件见到定义的宏,就用这个宏代表的文字替代这个宏的位置。之后,综合软件再按照替代之后的代码来综合出电路。
简单而言,宏定义就是起到一个替换的作用。它并不会使代码优化,但会使得代码的规模变小,并能够有效提高调试的效率和程序的可复用性。
这种情况下可以使用`define来定义一个时钟宏:
`define clk_period 20 //定义时钟宏clk_period
always # (`clk_period/2)clk1 = ~clk1; //场景1,使用到时钟20
always # (`clk_period/2)clk2 = ~clk2; //场景2,使用到时钟20
always # (`clk_period/2)clk3 = ~clk3; //场景3,使用到时钟20
always # (`clk_period/2)clk4 = ~clk4; //场景4,使用到时钟20
`define的一般格式:
`define macro_name(formal_argu_list) macro_text
- `define 是宏定义语法的标志,注意符号 `
- macro_name 是给这个宏定义起的名字,需符合verilog的起名规则
- macro_text 是宏定义被综合软件重新替代的内容
- formal_argu_list是宏定义可能的输入参数,里面可以有多个输入。这些变量可以取代macro_text里对应的内容,当不需要参数时,则可以省略这一项
比如用 clk_period 这个文本来替换时钟20ns:
`define clk_period 20 //定义时钟宏clk_period
这相当于告诉综合软件,在程序中遇到的 `clk_period 时均等同于 20。定义宏语句的最后是没有分号 ; 的,如果不小心加了分号,那么这个分号则会被视为“macro_text”的一部分而参与替代工作。
宏定义被调用的格式是:
`macro_name(formal_argu_list)
比如调用上面定义的宏clk_period:
always # (`clk_period/2)clk1 = ~clk1;
请注意符号 ` 。
`undef 的作用比较简单,就是取消先前定义的文本宏的定义,这意味着从宏定义开始到 `undef 这一句才是宏定义的作用域,这样可以用来精准地控制宏定义的适用范围,其一般格式:
`undef text_macro_identifier
比如解除上面定义的时钟宏:
`undef clk_period
通过上面这一句,就可以精准地把宏定义clk_period的作用范围控制在自己想实现的范围内,不然默认情况下该宏定义的范围则为定义开始的语句到文件尾。
`define用法的一些细节总结:
(1)宏名建议用大写字母表示,以便与变量名进行区分
此项仅为编码习惯,具体请以你的编码规范为准。
(2)'define定义既可以在模块内部定义,也可以在模块外部定义
定义的有效范围为定义之后到本文件结束或遇到 `undef 。通常,'define命令写在模块定义的外面,作为程序的一部分,在此代码内有效。
(3)在引用已定义的宏名时,必须在宏名前面加上符号 `
` 是英文输入法下ESC键下的那个键。
(4)宏定义不是verilog语句,不必在行末加分号
如果加了分号 ; ,则分号 ; 会被视为替代文本的一部分。
(5)宏名和宏内容尽量在同一行中进行声明。如果宏内容中包括注释行,注释行不会被置换
如果需要多行来指定文本,换行符必须在前加反斜杠(\)。第一个不带反斜杠的换行符将结束宏文本。
(6)只对那些确实需要全局定义的而且不会被其它设计更改的标识符才使用宏定义
尽量不要对那些只后在模块内使用的常量使用宏定义,这种情况更应该用localparam定义。
(7)为宏文本指定的文本不应被拆分为以下词法标记:注释、数字、字符串、标识符、关键词、操作符
比如这样的宏定义就是非法的,因为它把字符串拆分了。
`define first_half "start of string
$display(`first_half end of string");
(8)尽量把所有的宏定义放到一个宏定义文件(例如global_define.v),这样可以更高效地管理多个宏定义
在稍微复杂一点的FPGA工程设计中,难免会出现数个module,一般是推荐一个module使用一个文件管理,所以一般的FPGA工程会有数个文件。假设工程中存在大量参数是多个module都需要使用到的,比如VGA时序相关参数。这样我们就可以单独使用一个文件(命名为:global_define.v)来定义这些参数:
//行同步参数定义
`define H_SYNC 10'd96 //行同步
`define H_BACK 10'd40 //行时序后沿
`define H_LEFT 10'd8 //行时序左边框
`define H_VALID 10'd640 //行有效数据
`define H_RIGHT 10'd8 //行时序右边框
`define H_FRONT 10'd8 //行时序前沿
`define H_TOTAL 10'd800 //行扫描周期
1、把所有宏定义放到一个宏定义文件(global_define.v);
2、在某个文件需要使用这些参数时,需要使用该指令--`include "global_define.v" 来将该宏定义文件调用;
3、在需要使用到具体的宏时,直接使用,但是要记得加 ` ,比如: ` H_SYNC 。