在FPGA设计中,所有的算术运算符都是按照无符号数进行的。如果要完成有符号数计算,对于加、减操作通过操作通过补码处理即可用无符号加法完成。对于乘法操作,无符号数直接采用“*” 运算符,有符号数可提高定义输出为signed来处理。
尽量不要使用有符号数与无符号数进行混合计算。因为只要有一个无符号数的运算单元,整个算法将成为无符号数计算。
正数和负数处理时都是按照补码的形式处理,把这些补码理解为符号型还是无符号型,具体看reg signed的声明。如果声明了就把这些数据当做符号型操作,如果没有声明,当做无符号型操作(即都是正数);
机器数和真值
一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1。
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。
原码、反码、补码
原码、反码、补码是机器存储一个具体数字的编码方式。
原码就是符号位加上真值的绝对值,即第一位表示符号位,其余位表示值。
反码的表示方法是:正数的反码是其本身;
负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
如果一个反码表示是负数,通常将其转换成原码再计算。
补码的表示方法有:正数的补码就是其本身;
负数的补码就是在其原码的基础上,符号位不变,其余各位取反,最后加1.(在反码的基础上加1)
一、基于FPGA的简单除法
计算机能完成的基本元操作是:+,-,左移、右移等指令
加法运算:
[x+y]补=[x]补+[y]补
可直接相加。
减法运算:
[x-y]补=[x]补+(-[y]补)
计算机里,加法与减法是统一的,能够形成统一是由于补码。须知道,两个正整数想减,可以看成是一个正整数加一个负整数,进一步,俩个正整数相减是用一个正整数的补码加上一个负整数的补码来得到的。
溢出判断:首先一个正数和一个负数相加,结果一定不会溢出(因为结果的绝对值一定小于两个加数的绝对值),所以溢出都是符号相同的两个数相加。
正+正:符号位0,数位相加,如果结果的符号位变成1了,那就是两个加数的最高位相加进位来的,发生溢出。
负+负:符号位都是1,所以符号位一定会进位。数位相加,如果最后符号位是0,说明结果变成正的了,一定发生溢出了。
负数补码最高位相加,发生进位不是溢出,反而不进位是溢出?
在补码发生溢出的情况中,正数是因为太大发生溢出,但是负数是因为它太小发生的溢出。有进位说明两个负数较大。
原码移位:符号位不参与移位。
补码移位:符号位参与移位。左移时符号位左移。右移时符号位不变,最高位补符号位。
//基于FPGA的除法
module c (
input i_clk,
input i_rst_n,
input i_data_valid,
input [7:0] i_data_a;
input [7:0] i_data_b,
output reg o_data_valid,
output reg [7:0] o_data_shang,
output reg [7:0] o_data_yushu
);
reg [7:0] tempa;
reg [7:0] tempb;
reg [15:0] temp_a;
reg [15:0] temp_b;
reg div_start;
reg div_start_d1;
wire div_satrt_neg;
reg [4:0] div_cnt;
always @ (posedge i_clk or i_rst_n) begin
if(!i_rst_n) begin
tempa <= 8'b0;
tempb <= 8'b0;
end
else if(i_data_valid) begin
tempa <= i_data_a;
tempb <= i_data_b;
end
else begin
tempa <= tempa;
tempb <= tempb;
end
end
always @ (posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n) begin
div_start <= 1'b0;
end
else if(i_data_valid && div_start==1'b0) begin
div_start <= 1'b1;
end
else if(div_cnt==4'd16) begin
div_start <= 1'b0;
end
else
div_start <= div_start;
end
//----div_cnt计数器---
always @ (posedge i_clk or negedge i_rst) begin
if(!i_rst_n) begin
div_cnt <= 5'd0;
end
else if(div_start) begin
div_cnt <= div_cnt + 1'b1;
end
else begin
div_cnt <= 5'd0;
end
end
always @ (posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n) begin
temp_a <= 16'b0;
temp_b <= 16'b0;
end
else if(div_start) begin
if(div_cnt==5'd0) begin
temp_a <= {8'h0,tempa};
temp_b <= {tempb,8'h0};
end
else if(div_cnt[0]==1'b1) begin
temp_a <= {temp_a[14:0],1'b0}; //相当于乘2,或者左移一位
end
else begin
temp_a <= temp_a[15:8] >= temp_b[15:8] ? (temp_a - temp_b + 1) : temp_a;
//判断temp_a乘2之后取高8位与输入的除数比较大小 8次移动完temp_a[15:8] < temp_b[15:0]
//结果就是坐边高temp_a[15:8]是余数,右边低temp_a[7:0]是商
end
end
else begin
temp_a <= 16'b0;
temp_b <= 16'b0;
end
end
//除法结束后产生的下降沿
always @ (posedge i_clk) begin
div_start_d1 <= div_start;
end
assign div_start_neg = div_start_d1 & (~div_start); //产生一个时钟的高
always @ (posedge i_clk or negedge i_rst_n) begin
if(!rst) begin
o_data_valid <= 1'b0;
o_data_shang <= 8'b0;
o_data_yushu <= 8'b0;
end
else if(div_start_neg) begin
o_data_valid <= 1'b1;
o_data_shang <= temp_a[7:0];
o_data_yushu <= temp_a[15:8];
end
else begin
o_data_valid <= 1'b0;
o_data_shang <= 8'b0;
o_data_yushu <= 8'b0;
end
end
endmodule
用一段式状态机写:
module DIV_INT_TYPE #
( parameter E = 16, //Extension of bits
parameter D = 8 //The bits of dividend and divisor
)(
input clk,//50MHz
input rst_n,
input start, //除法开始的使能标志
output reg busy, //start开启后,busy=1代表除法器在忙,除法器被占用。当除法器忙的时候,start就要为0;
input [D-1:0] dividend, //[ˈdɪvɪdend] 被除数
input [D-1:0] divisor, // [dɪˈvaɪzər] 除数
output wire [D-1:0] quotient, // [ˈkwoʊʃnt] 商
output wire [D-1:0] remainder, // [rɪˈmeɪndər] 余数
output reg finish //除法完成
);
/
///以下是一段式状态机来写的整数除法器内核
reg [1:0] state;//状态机
reg [D-1:0] count;
reg [E-1:0] data_next;
always @ ( posedge clk or negedge rst_n )begin
if( !rst_n )begin
count <= D;
state <= 2'd0;
end
else begin
case( state )
2'd0:begin
finish <= 1'b0;
busy <= 1'b0;
if( start == 1'b1 )begin
data_next <= {{E-D{1'b0}},dividend};
state <= 2'd1;
end
else begin
data_next <= 0;
state <= 2'd0;
end
end
2'd1:begin
if( data_next[E-1:D] >= divisor )begin//如果余数大于等于除数
//data_next[0] = 1'b1;
//data_next[E-1:D] = data_next[E-1:D] - divisor;
//如果余数大于除数,那就对data_next做相应运算,可是我们接着要对运算完的data_next进行移位操作,这样才不会吃时钟,所以把这两步操作合为一体91行即是
if( count == 0 )begin
state <= 2'd0;
finish <= 1'b1;
busy <= 1'b0;
data_next <= data_next;
count <= D;
end
else begin
state <= state;
finish <= 1'b0;
busy <= 1'b1;
data_next[E-1:0] <= {{data_next[E-1:D] - divisor},data_next[D-1:1],1'b1} << 1'b1;
count <= count - 1'b1;
end
end
else begin
if( count == 0 )begin
state <= 2'd0;
finish <= 1'b1;
busy <= 1'b0;
data_next <= data_next;
count <= D;
end
else begin
state <= state;
finish <= 1'b0;
busy <= 1'b1;
data_next <= data_next << 1'b1;
count <= count - 1'b1;
end
end
end
default:begin
count <= D;
state <= 2'd0;
end
endcase
end
end
assign quotient = finish?data_next[D-1:0] : quotient;
assign remainder = finish?data_next[E-1:D] : remainder;
endmodule
可以计算出小数的小数位:
module ad_div_mult(
input clk,
input rst_n,
input start,
input [12:0] dividend,
input [12:0] divisor,
output reg div_done,
output reg [21:0] quotient,
output reg [7:0] decimals
);
parameter ANGLE = 9'd360;
reg [4:0] current_state,next_state;
parameter IDLE = 5'b00001; //1
parameter START = 5'b00010;//2
parameter MULT = 5'b00100; //4
parameter QUOTIENT = 5'b01000;//8
parameter DICIMALS = 5'b10000;//16
reg [7:0] decimals_reg;
reg [21:0] quotient_reg;
//第一段
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end
//第二段
reg mult_done;
reg div_en;
reg mult_en;
always @ (/*current_state or dividend or divisor or start*/*) begin
case(current_state)
IDLE : begin
if(start) begin
next_state = START;
end
else begin
next_state = IDLE;
end
end
START : if(mult_en)
next_state = MULT;
else
next_state = START;
MULT : begin
if(mult_done) begin
next_state = QUOTIENT;
end
else begin
next_state = MULT;
end
end
QUOTIENT : begin
if(div_en) begin
next_state = DICIMALS;
end
else begin
next_state = QUOTIENT;
end
end
DICIMALS : begin
if(div_done) begin
next_state = IDLE;
end
else begin
next_state = DICIMALS;
end
end
default : next_state = IDLE;
endcase
end
//第三段
reg [12:0] dividend_reg;
reg [21:0] dividend_reg1; //当被除数大于除数时,余数为新的被除数
reg [12:0] divisor_reg;
reg [21:0] mult_temp;
reg [3:0] cnt;
wire [21:0] dividend_minus_divisor1;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_done <= 1'b0;
quotient_reg <= 22'b0;
decimals_reg <= 8'b0;
dividend_reg <= 13'b0;
divisor_reg <= 13'b0;
dividend_reg1 <= 22'b0;
div_en <= 1'b0;
mult_temp <= 22'b0;
cnt <= 4'd0;
end
else begin
case(next_state)
IDLE : begin
quotient_reg <= 22'b0;
decimals_reg <= 8'b0;
dividend_reg <= 13'b0;
divisor_reg <= 13'b0;
dividend_reg1 <= 22'b0;
div_en <= 1'b0;
div_done <= 1'b0;
mult_temp <= 22'b0;
cnt <= 4'd0;
end
START : begin
mult_en <= 1'b1;
dividend_reg <= dividend;
divisor_reg <= divisor;
dividend_reg1 <= 22'b0;
end
MULT : begin
mult_en <= 1'b0;
mult_temp <= ANGLE * dividend_reg;
mult_done <= 1'b1;
end
QUOTIENT : begin
if(mult_temp >= divisor_reg) begin
mult_temp <= mult_temp - divisor_reg;
divisor_reg <= divisor_reg;
dividend_reg1 <= 22'b0;
quotient_reg <= quotient_reg + 1'b1;
decimals_reg <= 8'b0;
mult_done <= 1'b0;
//div_en <= 1'b0;
end
else begin
div_en <= 1'b1;
mult_temp <= mult_temp;
divisor_reg <= divisor_reg;
dividend_reg1 <= mult_temp;
decimals_reg <= 8'b0;
mult_done <= 1'b0;
end
end
DICIMALS : begin
div_en <= 1'b0;
if(dividend_reg1 > divisor_reg) begin
dividend_reg1 <= {dividend_minus_divisor1[20:0],1'b0}; //减法进位左乘2
decimals_reg <= {decimals_reg[6:0],1'b1};
if(cnt==4'd8) begin
div_done <= 1'b1;
cnt <= 4'd0;
end
else begin
div_done <= 1'b0;
cnt <= cnt + 1'b1;
end
end
else begin
dividend_reg1 <= {dividend_reg1[20:0],1'b0};
decimals_reg <= {decimals_reg[6:0],1'b0};
if(cnt==4'd8) begin
div_done <= 1'b1;
cnt <= 4'd0;
end
else begin
div_done <= 1'b0;
cnt <= cnt + 1'b1;
end
end
end
endcase
end
end
assign dividend_minus_divisor1 = (dividend_reg1 > divisor_reg) ? dividend_reg1-divisor_reg : 22'b0;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
decimals <= 8'd0;
quotient <= 22'b0;
end
else if(div_done) begin
decimals <= decimals_reg;
quotient <= quotient_reg;
end
end
endmodule
测试代码
`timescale 1ps/1ps
module tb_ad_div_mult();
reg clk;
reg rst_n;
reg start;
reg [13:0]dividend;
reg [13:0]divisor;
wire div_end;
wire [21:0]quotient;
wire [7:0]decimals;
initial begin
rst_n = 0;
clk = 0;
#100;
rst_n = 1;
dividend = 70;
divisor = 2900;
start = 0;
#70;
start =1;
#40;
start = 0;
end
always #20 clk = ~clk;
ad_div_mult u1 (
.clk(clk),
.rst_n(rst_n),
.start(start),
.dividend(dividend),
.divisor(divisor),
.div_done(div_done),
.quotient(quotient),
.decimals(decimals)
);
endmodule
通过使用移位相加的操作,可以替代乘法器,具有节省电路资源的优点;计数精度与预期的一致,且计数速度快,消耗时钟不多。
关于有符号数除法的设计思路还是跟之前上面的整数除法部分一样,只不过是先将两个数的符号位给单独取出来,并判断符号位决定是否对剩余部分数据进行取反,然后将剩余部分当做整数除法运算,结果只取了整数部分,余数部分并未取
module div_signed(
input clk,
input rst_n,
input signed [3:0] in_a,
input signed [3:0] in_b,
input start,
output reg done_flag,
output reg signed [3:0] o_c
);
reg [2:0] a;
reg [2:0] b;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
a <= 'b0;
end
else if(start) begin
if(in_a[3]) begin
a <= ~in_a[2:0] + 1'b1;
end
else begin
a <= in_a[2:0];
end
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
b <= 'b0;
end
else if(start) begin
if(in_b[3]) begin
b <= ~in_b[2:0] + 1'b1;
end
else begin
b <= in_b[2:0];
end
end
end
reg signed_flag;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
signed_flag <= 'b0;
end
else if(start) begin
if((in_a[3]&&in_b[3]) | (in_a[3]==1'b0 && in_b[3]==1'b0)) begin
signed_flag <= 1'b0;
end
else begin
signed_flag <= 1'b1;
end
end
end
reg div_start;
reg [2:0] div_cnt;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_start <= 1'b0;
end
else if(start && div_start==1'b0) begin
div_start <= 1'b1;
end
else if(div_cnt==3'd6) begin
div_start <= 1'b0;
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_cnt <= 'b0;
end
else if(div_start) begin
div_cnt <= div_cnt + 1'b1;
end
else begin
div_cnt <= 'b0;
end
end
reg [5:0] temp_a;
reg [5:0] temp_b;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
temp_a <= 'b0;
temp_b <= 'b0;
end
else if(div_start) begin
if(div_cnt==3'b0) begin
temp_a <= {3'b0,a};
temp_b <= {b,3'b0};
end
else if(div_cnt[0]==1'b1) begin
temp_a <= {temp_a[4:0],1'b0};
end
else begin
temp_a <= temp_a[5:3] >= temp_b[5:3] ? (temp_a - temp_b + 1) : temp_a;
end
end
else begin
temp_a <= 'b0;
temp_b <= 'b0;
end
end
reg div_start_d1;
wire div_start_neg;
always @(posedge clk) begin
div_start_d1 <= div_start;
end
assign div_start_neg = div_start_d1 & (~div_start);
reg [5:0] done_data;
reg done_flag_r1;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
done_data <= 'b0;
done_flag_r1 <= 'b0;
end
else if(div_start_neg) begin
done_flag_r1 <= 1'b1;
if(signed_flag) begin
done_data <= ~temp_a + 1'b1;
end
else begin
done_data <= temp_a;
end
end
else begin
done_flag_r1 <= 1'b0;
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
done_flag <= 1'b0;
o_c <= 'sd0;
end
else if(done_flag_r1) begin
done_flag <= 1'b1;
o_c <= {signed_flag,done_data[2:0]};
end
else begin
done_flag <= 1'b0;
o_c <= 'sd0;
end
end
endmodule
测试模块
`timescale 1ns/1ps
module div_signed_tb();
reg clk;
reg rst_n;
reg signed [3:0] in_a;
reg signed [3:0] in_b;
reg start;
wire done_flag;
wire signed [3:0] o_c;
initial begin
rst_n = 0;
clk = 0;
#14;
rst_n = 1;
end
always #5 clk = ~clk;
initial begin
in_a = 3'd0;
in_b = 3'd0;
start = 1'b0;
#23;
start = 1'b1;
in_a = -3'd6;
in_b = 3'd4;
#10;
start = 1'b0;
in_a = 3'd0;
in_b = 3'd0;
#200;
start = 1'b1;
in_a = 3'd4;
in_b = -3'd2;
#10;
start = 1'b0;
in_a = 3'd0;
in_b = 3'd0;
end
div_signed div_signed_inst(
.clk(clk),
.rst_n(rst_n),
.in_a(in_a),
.in_b(in_b),
.start(start),
.done_flag(done_flag),
.o_c(o_c)
);
endmodule
经过仿真验证没问题