Verilog中宏定义位宽带来的问题
0赞宏定义在C语言程序中的使用司空见惯,他的好处就在于可以大大提高代码的可读性和可移植性。而在verilog中,也支持这个语法,在很多开源代码 中也都能看到`define的身影。但是它的使用和C语言可不完全一样,很多时候需要非常小心和谨慎。其中最可能让设计者犯错的就是它的位宽问题。特权同 学就吃过这个亏,因此有必要在此专门撰文讨论一下,不仅给自己提个醒,它希望读者您少走弯路。
先简单的复习一下define在verilog基本语法书中的一些定义和简单的使用说明。
宏定义 `define:用一个指定的标识符(即名字)来代表一个字符串,它的一般形式为:
`define 标识符(宏名)字符串(宏内容)
如:`define signal string,它的作用是指定用标识符signal来代替string这个字符串,在编译预处理时,把代码中在该命令以后所有的signal都替换成 string。这种方法使用户能以一个简单的名字代替一个长的字符串,也可以用一个有含义的名字来代替没有含义的数字和符号,因此把这个标识符(名字)称 为“宏名”,在编译预处理时将宏名替换成字符串的过程称为“宏展开”。`define是宏定义命令。
例:
`define SIZE 8
module reg[`SIZE:1] db; //相当于定义寄存器reg[8:1] db;
那么下面特权同学拿一个简单的实例来说明define使用时容易遇到的位宽问题,其实也是特权同学自己遇到的,感觉很有典型性。
input clk; //外部输入时钟,25MHz
input rst_n; //外部输入复位信号,低电平有效
input[9:0] mcu_wr_addr; //原始地址数据
output[9:0] mcu_wr_ab; //译码后地址数据
`define LCDX_DIS 800
`define LCDSD_PAGE `LCDX_DIS/4
`define LCDSD_2PAGE `LCDSD_PAGE*2
`define LCDSD_3PAGE `LCDSD_PAGE*3
reg[11:0] mcu_wr_abr;
always @(posedge clk or negedge rst_n)
if(!rst_n) mcu_wr_abr <= 12'd0;
else if((mcu_wr_addr[9:0] >= 10'd0) && (mcu_wr_addr[9:0] < `LCDSD_PAGE)) mcu_wr_abr <= {2'b00,mcu_wr_addr[9:0]};
else if((mcu_wr_addr[9:0] >= `LCDSD_PAGE) && (mcu_wr_addr[9:0] < `LCDSD_2PAGE)) mcu_wr_abr <= {2'b01,mcu_wr_addr[9:0]-`LCDSD_PAGE};
else if((mcu_wr_addr[9:0] >= `LCDSD_2PAGE) && (mcu_wr_addr[9:0] < `LCDSD_3PAGE)) mcu_wr_abr <= {2'b10,mcu_wr_addr[9:0]-`LCDSD_2PAGE};
else mcu_wr_abr <= {2'b11,mcu_wr_addr[9:0]-`LCDSD_3PAGE};
assign mcu_wr_ab = {mcu_wr_abr[11:10],mcu_wr_abr[7:0]};
上面一段代码希望实现的功能是这样的:对输入地址总线mcu_wr_addr[9:0]进行译码,当他的值比`LCDX_DIS/4小(肯定比 256小),则mcu_wr_addr[9:0]直接赋值mcu_wr_ab[9:0],并且此时的mcu_wr_ab[9:8] = 2’b00;当他的值大等于`LCDX_DIS/4且小于(`LCDX_DIS/4)*2,则mcu_wr_addr[9:0]-`LCDX_DIS/4 的值(肯定比256小)赋给mcu_wr_ab[7:0],并且此时的mcu_wr_ab[9:8]==2’b01;依此类推。
写了一段简单的测试脚本,分别依次输入mcu_wr_addr = 55/255/455/655,预计的输出应该是55/(55+256)/(55+512)/(55+768),即55/311/567/823。而我们 查看波形如图1所示,得到的结果mcu_wr_ab却都是55,很显然没有达到既定的期望。
图1
那么是谁在作怪?很显然,是位宽!我们再返回源代码,其实在综合的时候,Quartus II给出了三条警告:
Warning (10230): Verilog HDL assignment warning at cy3mcutest.v(39): truncated value with size 34 to match size of target (12)
Warning (10230): Verilog HDL assignment warning at cy3mcutest.v(40): truncated value with size 34 to match size of target (12)
Warning (10230): Verilog HDL assignment warning at cy3mcutest.v(41): truncated value with size 34 to match size of target (12)
分别就是指前面always中的三个else if/else if/else语句。问题也就是出在这里,因为通常定义的宏参数都是32位的常量(也可能是64位的,和运行的PC有关),哪怕是定义了这个宏参数的位 宽。LCDX_DIS是32位的常量,而后面的几个宏参数都是由LCDX_DIS进行运算得到,也是32位常量。运算式 (mcu_wr_addr[9:0]-`LCDSD_PAGE)的结果当然也就是32位宽,它前面再补2位,那么整个运算结果就是34位宽,因此给出了 Warning是说式子左侧的12位寄存器和右边的34位结果位宽不符合。那么,这也就容易明白为什么仿真得出的结果中,原本译码的最高两位赋值始终为0 不变了。
简单的,单对这个例子,可以对源代码的always块做如下修改:
always @(posedge clk or negedge rst_n)
if(!rst_n) mcu_wr_abr[9:0] <= 10'd0;
else if((mcu_wr_addr[9:0] >= 10'd0) && (mcu_wr_addr[9:0] < `LCDSD_PAGE)) mcu_wr_abr[9:0] <= mcu_wr_addr[9:0];
else if((mcu_wr_addr[9:0] >= `LCDSD_PAGE) && (mcu_wr_addr[9:0] < `LCDSD_2PAGE)) mcu_wr_abr[9:0] <= (mcu_wr_addr[9:0]-`LCDSD_PAGE);
else if((mcu_wr_addr[9:0] >= `LCDSD_2PAGE) && (mcu_wr_addr[9:0] < `LCDSD_3PAGE)) mcu_wr_abr[9:0] <= (mcu_wr_addr[9:0]-`LCDSD_2PAGE);
else mcu_wr_abr[9:0] <= (mcu_wr_addr[9:0]-`LCDSD_3PAGE);
always @(posedge clk or negedge rst_n)
if(!rst_n) mcu_wr_abr[11:10] <= 2'd0;
else if((mcu_wr_addr[9:0] >= 10'd0) && (mcu_wr_addr[9:0] < `LCDSD_PAGE)) mcu_wr_abr[11:10] <= 2'b00;
else if((mcu_wr_addr[9:0] >= `LCDSD_PAGE) && (mcu_wr_addr[9:0] < `LCDSD_2PAGE)) mcu_wr_abr[11:10] <= 2'b01;
else if((mcu_wr_addr[9:0] >= `LCDSD_2PAGE) && (mcu_wr_addr[9:0] < `LCDSD_3PAGE)) mcu_wr_abr[11:10] <= 2'b10;
else mcu_wr_abr[11:10] <= 2'b11;
当然,重新修改后的代码虽然没有解决位宽不匹配的问题(第一个always块仍会有Warning),但是对输出的高位独立处理,避免了无法正常赋值输出的问题,重新仿真后的结果如图2所示,达到了设计要求。
图2
个人认为,一种比较稳妥的做法应该如下代码所示:
wire[31:0] mcu_abreg1 = mcu_wr_addr[9:0]-`LCDSD_PAGE;
wire[31:0] mcu_abreg2 = mcu_wr_addr[9:0]-`LCDSD_2PAGE;
wire[31:0] mcu_abreg3 = mcu_wr_addr[9:0]-`LCDSD_3PAGE;
always @(posedge clk or negedge rst_n)
if(!rst_n) mcu_wr_abr <= 12'd0;
else if((mcu_wr_addr[9:0] >= 10'd0) && (mcu_wr_addr[9:0] < `LCDSD_PAGE)) mcu_wr_abr <= {2'b00,mcu_wr_addr[9:0]};
else if((mcu_wr_addr[9:0] >= `LCDSD_PAGE) && (mcu_wr_addr[9:0] < `LCDSD_2PAGE)) mcu_wr_abr <= {2'b01,mcu_abreg1[9:0]};
else if((mcu_wr_addr[9:0] >= `LCDSD_2PAGE) && (mcu_wr_addr[9:0] < `LCDSD_3PAGE)) mcu_wr_abr <= {2'b10,mcu_abreg2[9:0]};
else mcu_wr_abr <= {2'b11,mcu_abreg3[9:0]};
这样做不会产生任何Warning,也能够从某种程度上看出设计者非常重视位宽的问题,甚至于在也不是很确定需要在always块里取多大位宽的时候,也有宏定义来什么这个最高的位宽值。
另外,这里提一下,其实很多时候使用parameter能够达到和`define一样的效果。这个实例如果做如下的修改,实现的功能也是一样的。
parameter LCDX_DIS = 800;
parameter LCDSD_PAGE = LCDX_DIS/4;
parameter LCDSD_2PAGE = LCDSD_PAGE*2;
parameter LCDSD_3PAGE = LCDSD_PAGE*3;
reg[11:0] mcu_wr_abr;
always @(posedge clk or negedge rst_n)
if(!rst_n) mcu_wr_abr <= 12'd0;
else if((mcu_wr_addr[9:0] >= 10'd0) && (mcu_wr_addr[9:0] < LCDSD_PAGE[9:0])) mcu_wr_abr <= {2'b00,mcu_wr_addr[9:0]};
else if((mcu_wr_addr[9:0] >= LCDSD_PAGE[9:0]) && (mcu_wr_addr[9:0] < LCDSD_2PAGE[9:0])) mcu_wr_abr <= {2'b01,mcu_wr_addr[9:0]-LCDSD_PAGE[9:0]};
else if((mcu_wr_addr[9:0] >= LCDSD_2PAGE[9:0]) && (mcu_wr_addr[9:0] < LCDSD_3PAGE[9:0])) mcu_wr_abr <= {2'b10,mcu_wr_addr[9:0]-LCDSD_2PAGE[9:0]};
else mcu_wr_abr <= {2'b11,mcu_wr_addr[9:0]-LCDSD_3PAGE[9:0]};