特权同学

Verilog中宏定义位宽带来的问题

0
阅读(3379)

宏定义在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]};