赞
踩
在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
经过仿真验证没问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。