在FPGA设计中,一般的算数运算符都是按照无符号数进行的。那么需要有符号数计算的时候,该怎么办呢?

  很久很久以前也就是Verilog-2001还没有出现时,是手动操作的,也就是说,对于一个8位的无符号数,比如reg [7:0] a; 我们手动把最高位当做符号位,剩余的7位则是数值位,整个二进制以补码的形式表示。这个时候进入的二进制以补码的形式表示,出来的二进制也以补码的形式表示,在查看波形时,也以补码的形式查看。总之,里面涉及到的二进制、二进制运算,都是基于二进制补码的这个编码形式。这个时候,这个模块里面的变量、要处理的数都是有符号数,不要添上与无符号相关东西了,容易乱。无符号数的数据流想要进入这个模块,得先经过补码编码后再输进来。

  后来,Verilog-2001出现了,当我们想处理有符号数时,再也不用我们手动认最高位了,因为这个时候的Verilog被定义得很“智能”了。是有符号还是无符号,就要看变量有没有signed的声明了。意思就是说,一个数进来,比如说8位,如果是 reg [7:0] a; 那么a就会被识别为无符号的8位数。如果是reg signed [7:0] a; 那么a就会被识别为有符号数,并且是识别为8位的二进制补码。所谓的signed只有在参与运算的过程中才会有作用,再连线方面或是其他方面,没有任何作用,因为都是补码。

  在Verilog-2001出现之后,一个模块中出现有符号和无符号的处理问题也不大了,因为Verilog 2001提供了$signed()及$signed()机制,能够进行有符号与无符号之间的转换(然而我还是不建议一个模块里面同时处理有符号和无符号的数据)。  

  尽量不要使有符号数与无符号数进行混合计算。因为只要有一个无符号数的运算单元,整个算式将被将成无符号数进行计算。

  下面是例子:

例子1:

input signed [7:0] a, b;
output signed [15:0] o;
assign o = a * b;

 

上面输入两个有符号数a和b,这两个数都将被识别为二进制补码(我们在查看波形的时候,也应该以补码的形式查看)。这两个数进行有符号数的乘法运算,得到的自然也是有符号数的积,因此这里的输出也定义为有符号数。从这个例子中我们可以看到,操作数变量被定义为有符号数,那么操作的结果变量也应该被定义为有符号数。

 

例子2:

input [7:0] a, b;
output [15:0] o;
wire signed [15:0] o_sgn;
assugb o_sgn = $signed(a) * $signed(b);
assign o = $unsigned(o_sgn);

 

例子2中,输入的是两个无符号数,输出也定义为无符号数。然而,(假设)应用时,发现只有一个有符号的乘法器可以使用,这个时候就要用Verilog中的signed()及$signed()机制.首先使用$signed()把输入变成有符号数,同时定义一个有符号的中间结果,这样子就可以使用有符号的乘法器进行运算了。最后使用$unsigned()把有符号的中间结果转换为无符号输出。

 

例子3:

input signed [7:0] a, b;
input signed [8:0] o;
assign o = a + b; // Verilog会自动进行符号的扩展。

Verilog-2001会自动扩展符号位。意思是说,当我们进行有符号数进行运算的时候,我们不要手动转换符号位了,Verilog会自动识别最高位为符号位,同时在运算过程中会自动进行符号位的扩展。因此,正负号的扩展时,应多加利用Verilog的implicity signed extension,避免手动进行转换。

 

例子4:

input [7:0] a;
input signed [7:0] b;
output signed [15:0] o;

// Don't do this: assign o = a * b;
// The $signed({1'b0, a}) can convert the unsigned number to signed number.
assign o = $signed({1'b0, a}) * b;

 

上述代码中,输入a被识别为无符号数,而输入b则被识别为二进制补码,输出则被定义为二进制补码输出。在进行运算的时候,我们不能直接让a、b直接相乘,因为在一个verilog叙述中只要有一个无号数的​​运算元,整个算式将被当成无号数进行计算。这个时候,因为输出是有符号的,我们最好把输入也变成有符号的,方便运算。因此我们首先需要把无符号的输入手动添加一个符号位后,使用$signed()把输入转换称为与b一样的有符号形式;如果不使用$signed(),直接使用{1'b0,a}与b相乘,这样还是无符号*有符号,因此还是需要把{1'b0,a}转成有符号的形式。

 

同样地,当乘以一个常数时,也要用$signed()把常数转换一下:

input signed [7:0] a;
output signed [15:0] o;

// Don't do this: assign o = a * 8'b10111111;
// Use $signed() system task
assign o = a * $signed(8'b10111111);
// or sb keyword.
assign o = a * 8'sb10111111;

 

 

还有一个需要的是,部分选择(也就是进行位选的时候),位选运算过后的结果是无号数!!!就算是选择的范围包含整个register或wire,例子如下所示:

input signed [7:0] a;
input signed [7:0] b;
output signed [15:0] o1, o2;

// Don't do this: assign o1 = a[7:0];//a[7:0]是得到的是无符号数,将一个无符号赋值给一个有符号数的时候,结果肯定会出错
assign o1 = a;
// Don't do this: assign o2 = a[6:0] * b;
assign o2 = $signed(a[6:0]) + b

暂时先记录到这里!

 

posted on 2019-10-18 11:44  IC_learner  阅读(1113)  评论(0编辑  收藏  举报