基于FPGA的四则运算

在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

经过仿真验证没问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值