Menu Close

Verilog 算术运算符

Verilog 算术运算符是Verilog中数值运算的主要运算符,配合可综合的功能强大IPcore 可以完成功能强大的复杂运算,是数字信号处理,科学计算加速器,人工智能等领域FPGA或IC应用的主要途径之一。算术操作符包括单目操作符和双目操作符。

Verilog算数运算符包含以下运算符号:

单目运算符 正(+),负();

双目操作符对 2 个操作数进行算术运算,包括加(+)、减()、乘(*)、除(/)、求幂(**)、取模(%),对数(log)。

在Verilog设计中,目前一般综合工具支持正(+)、负(),加(+),减(),乘(*),除(/),取模(%)等操作符。求幂(**)只支持常量运算或特殊规定幂运算,对于常数也就是parameter 、localparam或宏定义的常量进行运算。对于变量,在如下规定后可以支持,其它情况下不能综合,当然仿真程序不受限制。

(1) 如果底为变量,幂必须为0或正整数。

(2)底为2的幂次方,幂可以变量。

 

举例

module test
(
    input  [3:0] x1,
    input  [3:0] x2,
    output [3:0] x3,
)

assign x3 = x1 ** x2 ; // 不能被综合
assign x3 = 2 ** x2 ;  // 能被综合
assign x3 = x1 ** 2 ;  // 能被综合 wire [3:0] sw_sel = SW ** (5);// 能被综合 wire [6:0] bbb = 4 ** SW; // 能被综合

endmodule

 

合理利用可综合的算数运算符,可以简化设计。把复杂的综合问题交给综合工具,使设计者从繁杂的底层设计中解放出来。

例:四位全加器的设计

逻辑设计步骤:

  1. 一位全加器设计
    1. 真值表
    2. 逻辑表达式
    3. 逻辑化简
    4. 逻辑电路
  2. 利用层次电路设计四位全加器

设计过程参见四位全加器设计

利用算数运算符设计,RTL实现过程非常简单。

module fadd4
(
    input        ci,
    input  [3:0] a,
    input  [3:0] b,
 
    output [3:0] y,
    output       co
);
 
assign  {co, y} = a + b + ci;
 
endmodule

 

算数运算符在计算中按照最长向量宽度扩展,如a+b+ci, 由于a,b的宽度都是4位,ci是一位,因此ci自动左边填0补齐,即{3’b0, ci}。

在执行算术运算时要注意变量的正、负极性,一般算术运算支持有符号运算。

负数:

是通过2进制的补码实现的。因此在做减法计算时往往是通过2的补码然后通过加法实现的。

2进制补码:原码取反+1.

例: a[3:0] == 4’b0110; 求取 -a;

  1. 计算a[3:0]的反码, 得4’b1001;
  2. 补码: 反码加一,得 4’b1010;
  3. 二进制补码4’b1010,即是十进制的-6;因此在做减法时如b – 4’b0110 , 也可以写成 b+ 4’b1010;

思考题:为什么用补码可以将减法转换成加法呢?

2进制中有符号数的表示方法

设a[3:0]是有符号数,最高位 a[3]为符号位,a[3]=0,表示0或正数,a[3]=1 表示负数。下表是2进制有符号数与十进制有符号数对应表。

2进制 有符号数 十进制 有符号数
4’b0000 0
4’b0001 1
4’b0010 2
4’b0011 3
4’b0100 4
4’b0101 5
4’b0110 6
4’b0111 7
以下是负数
4’b1000 -8(-0)
4’b1001 -7
4’b1010 -6
4’b1011 -5
4’b1100 -4
4’b1101 -3
4’b1110 -2
4’b1111 -1

在Verilog计算中,如果没有特别得设定,一般运算结果都是按无符号数处理的。如 c[3:0]=a[3:0]+b[3:0] 的结果按无符号处理。我们看一下有符号无符号有何不同, 假设a[3:0]==7,b[3:0]==7;

无符号数的结果为14,4’b1110;

有符号数的结果则需要位扩展十进制14,5’b01110。如果没有符号位扩展,4’b1110 应解释为-2,这显然与预期的结果不符。

在Verilog中专门有关键字 signed 来标识有符号无符号,该关键字一般wire,reg, input, output 之后。

同时算数运算在赋值的时候,要注意接收变量的位宽匹配,如加法,减法运算由于有进位,借位,符号位扩展等原因,一般会在赋值语句的左端的变量多一位到两位。乘法运算,赋值语句的左端是相乘两变量位宽之和,如 assign c[6:0]=a[3:0]*b[2:0];

signed 运算符的使用

例:

有符号数的加、减、乘

module sign_test
(
    input  signed [3:0] a,
    input  signed [3:0] b,
    output signed [5:0] sum,
    output signed [5:0] diff,
    output signed [7:0] p
);
 
assign sum  = a + b;
assign diff = a - b;
assign p    = a * b;
 
 
endmodule

 

testbench 代码 tb.v

`timescale 1 ns/1 ps
`define PW 4
 
module tb
(
 
);
 
parameter PERIOD = 10 ;
 
reg CLK;
 
initial 
begin
    CLK = 1'b0;
    #(PERIOD/2);
    forever
        #(PERIOD/2) CLK = ~CLK;
end
 
reg [`PW-1:0] a, b;
 
wire [5:0] sum;
wire [5:0] diff;
wire [`PW*2-1:0] p;
 
initial 
begin
    a = `PW'b0000;
    b = `PW'b0000;
end
 
always @(posedge CLK)
begin
    a <= a + 1;
    if(a == 15)
        b <= b + 1;
end
 
sign_test sign_test_dut
(
    .a    (a),
    .b    (b),
    .sum  (sum),
    .diff (diff),
    .p    (p)
);
 
endmodule

 

modelsim 仿真波形

%title插图%num

 

 

求幂(**)操作符,目前广泛的使用对常量操作,并且只能是2的幂。

例:

设计文件  mul8.v

`define PW 8 //用宏定义符号常量
 
module mul8
(
    input [`PW-1:0] a,
    input [`PW-1:0] b,
 
    output [`PW*2-1:0] p
);
 
assign p = a * b;
 
endmodule

 

 

仿真文件 tb.v

`timescale 1 ns/1 ps
 
`define PW 8
 
module tb
(
);
parameter PERIOD = 10 ;
 
reg CLK;
 
initial 
begin
    CLK = 1'b0;
    #(PERIOD/2);
    forever
        #(PERIOD/2) CLK = ~CLK;
end
 
reg  [`PW-1:0]   a, b;
 
wire [`PW*2-1:0] p;
 
initial 
begin
    a = `PW'b0;
    b = `PW'b0;
end
 
always @(posedge CLK)
begin
    a <= a + 1;
    if(a == 2**(`PW)-1)   
        b <= b + 1;
end
mul8 mul8_dut
(
    .a (a),
    .b (b),
    .p (p)
);
 
endmodule

 

 

 

先发布,后续内容很快补充。

 

Posted in IC

发表评论

相关链接