基于FPGA的高效除法器

news2024/11/17 7:45:38

  FPGA可以通过除号直接实现除法,但是当除数或被除数位宽较大时,计算会变得缓慢,导致时序约束不能通过。此时可以通过在除法IP中加入流水线来提高最大时钟频率,这种方式提高时钟频率也很有限。如果还不能达到要求,就只能把除法器拆分,来提高系统时钟频率。

  其实最简单的方式是使用计数器对除数进行累加,并且把累加的次数寄存,当累加结果大于等于被除数时,此时寄存的累加次数就是商,而被除数减去累加结果就得到余数。

  但这种方式存在一种弊端,当除数很小的时候,被除数特别大时,需要经过很多个时钟周期才能计算除结果。比如被除数为100,除数为1,就需要100个时钟左右才能计算出结果,效率无疑是低下的。因此一般不会使用这种方式实现除法器。

1、除法器原理

  首先观察图1中二进制数10011001除以1010的计算过程,除数与被除数位宽相差4,4位除数(1010)大于被除数的高4位(1001),小于被除数的高5位(10011),所以首先是被除数的高5位减去除数(如图紫色数字),得到01001。然后被除数的第2位到6位数据依旧大于除数,则在次减去除数,就这样依次相减,最后得到商为1111,余数为3。

在这里插入图片描述

图1 二进制除法

  其实通过上面讲述,就可以根据这个规律实现除法器了,每次从被除数高位取除数相同数据位宽的数据与除数进行比较,大于等于时直接做减法,商加1。小于时被除数数据位宽向低位移动,被除数每右移动1位,商左移1位,直到被除数移动到最低位时结束运算。

  以上描述的方法在FPGA上不太好实现,最好是改进一下,将被除数和除数比较的位进行固定,那样就会好实现很多。

  如图2所示,还是前文的8位10011001除以4位1010,通过一个9位的寄存器存储8位被除数,始终比较被除数寄存器的第4到8位数据与除数的大小,如果大于除数,则进行减法运算,否则将被除数左移1位,然后在次进行判断。

  首先是5’b01001小于除数4’b1010,则将被除数左移1位。然后被除数高五位数据5’b10010大于除数,则商1,两者相减得到01001。得到数据作为被除数运算位,此时被除数高5位还是比除数小,将被除数左移1位,相当于运算被除数的低位数据,同时也要把商左移1位。

在这里插入图片描述

图2 除法运算

  就如同上图循环判断被除数高5位数据与除数的大小关系,然后对被除数进行移位和减法运算,就能够计算出商和余数。当被除数的最低位数据被移入高5位时,在通过判断被除数高5位数据与除数大小并进行相应减法后,运算结束。也就是说,这种情况下,被除数位宽为8,除数位宽为4,则被除数左移4次后计算结束,余数等于最后被除数的数值右移4位(被除数总共左移的次数)。

  这种运算FPGA实现就较为简单,例如被除数位宽为M,除数位宽为N,满足M>=N,只需要比较被除数[M:M-N]与除数的大小,被除数左移M-N次后运算结束,将最终的被除数右移M-N位得到余数。

  上述只适用于一般情况,还会出现图3这种,被除数依旧是8位10000001,4位除数为0010,此时如果还拿被除数高5位减去除数,得到的结果仍然大于除数,会导致计算错误。

在这里插入图片描述

图3 除法运算

  此时可以把除数进行左移,直到除数的最高位为1或者除数左移后大于被除数的高5位为止,需要考虑除数左移1位,相当于增大2倍,要保持最终结果不变,被除数左移次数需要增加除数左移的次数,并且最后余数右移的次数也会相应增加。所以上述计算变为图4所示。

在这里插入图片描述

图4 除法运算

  首先除数根据条件需要左移2位都不会大于被除数的高5位,且不会溢出,所以除数就先左移2位,然后就是判断被除数高5位是否大于除数,大于除数就进行减法运算且商加1,减法运算结果小于除数取代被除数参与运算的数据位。如果被除数高5位小于除数,则被除数和商一起左移,然后在次判断。最终当被除数左移次数达到除数左移次数加被除数与除数位宽之差时计算结束。

2、除法器实现

  经过上述分析,首先当输入数据有效时,需要判断除数和被除数是否为0,其中一个为0,则输出均为0,且除数为0时,应该报错。该模块采用一个状态机作为架构,包括空闲状态,左移除数,除法运算三个状态。假设除数位宽L_DIVR,被除数位宽L_DIVN,且L_DIVN>=L_DIVR。

  模块初始处于空闲状态,只有上游模块把开始计算信号拉高且被除数与除数均不为0时,状态机跳转到左移除数状态。此时需要把除数和被除数存入对应寄存器,把除数左移次数计数器、被除数左移次数计数器、商和余数暂存器清零。

  开始信号只有在状态机处于空闲状态下才会有效,其余时间不会接收上游模块的除数、被除数等数据。

  在左移除数的状态下,如果除数最高位为0,且除数左移1位比被除数寄存器的高L_DIVR+1位小,那么将除数左移1位,除数左移次数计数器加1。否则状态机跳转到除法运算状态。

  状态机处于除法运算状态时,需要计算被除数高L_DIVR+1减去除数是否大于0。如果大于等于0,则将减法结果作为被除数的高L_DIVR+1位,商的最低位赋值为1,也就是加1。如果小于0且被除数左移次数小于除数左移次数加L_DIVN-L_DIVR时,把被除数和商均左移1位,被除数左移次数计数器加1。

  如果被除数左移次数等于除数左移次数加L_DIVN-L_DIVR,则状态机跳转到空闲状态,将被除数右移除数左移次数加L_DIVN-L_DIVR得到余数。

  状态机从除法状态跳转到空闲状态时,把商、余数进行输出。

  以下代码是模块端口信号:

//当输入除数为0时,error信号拉高,且商和余数为0;
//当ready信号为低电平时,不能将开始信号start拉高,此时拉高start信号会被忽略。
module div #(
	parameter 			L_DIVN			= 	8				,//被除数的位宽;
	parameter 			L_DIVR			= 	4				 //除数的位宽;
)(
	input									clk 			,//时钟信号;
	input 									rst_n			,//复位信号,低电平有效;

	input 									start 			,//开始计算信号,高电平有效,必须在ready信号为高电平时输入才有效。
	input				[L_DIVN - 1 : 0]	dividend		,//被除数输入;
	input				[L_DIVR - 1 : 0]	divisor			,//除数输入;

	output 	reg 							ready			,//高电平表示此模块空闲。
	output 	reg 							error			,//高电平表示输入除数为0,输入数据错误。
	output  reg 		 					quotient_vld	,//商和余数输出有效指示信号,高电平有效;
	output	reg 		[L_DIVR - 1 : 0]	remainder		,//余数,余数的大小不会超过除数大小。
	output	reg			[L_DIVN - 1 : 0]	quotient 		 //商。
);

  以下是状态机状态编码和中间信号的定义,以及通过自动计算位宽函数取计算计数器的位宽。

	localparam          L_CNT   		= 	clogb2(L_DIVN)	;//利用函数自动计算移位次数计数器的位宽。
	localparam 			IDLE 			= 	3'b001  		;//状态机空闲状态的编码;
	localparam 			ADIVR			= 	3'b010  		;//状态机移动除数状态的编码;
	localparam 			DIV				= 	3'b100  		;//状态机进行减法计算和移动被除数状态的编码;

	reg 									vld				;//
	reg  				[2 : 0]				state_c			;//状态机的现态;
	reg  				[2 : 0]				state_n			;//状态机的次态;
	reg					[L_DIVN : 0]		dividend_r		;//保存被除数;
	reg					[L_DIVR - 1 : 0]	divisor_r   	;//保存除数。
	reg					[L_DIVN - 1 : 0]	quotient_r 		;//保存商。
	reg					[L_CNT - 1 : 0]		shift_dividend	;//用于记录被除数左移的次数。
	reg					[L_CNT - 1 : 0]		shift_divisor	;//用于记录除数左移的次数。
	
	wire 				[L_DIVR : 0] 		comparison		;//被除数的高位减去除数。
	wire   									max				;//高电平表示被除数左移次数已经用完,除法运算基本结束,可能还需要进行一次减法运算。
	
	//自动计算计数器位宽函数。
	function integer clogb2(input integer depth);begin
		if(depth == 0)
			clogb2 = 1;
		else if(depth != 0)
			for(clogb2=0 ; depth>0 ; clogb2=clogb2+1)
				depth=depth >> 1;
		end
	endfunction

  max用来判断被除数左移次数是否等于除数于被除数位宽差加除数左移次数。而comparison在除数左移状态时,需要计算被除数高位与除数左移后相减的结果,在其余状态下,计算被除数高位与除数相减的结果。

	//max为高电平表示被除数左移的次数等于除数左移次数加上被除数与除数的位宽差;
	assign max = (shift_dividend == (L_DIVN - L_DIVR) + shift_divisor);

	//用来判断除数和被除数第一次做减法的高位两者的大小,当被除数高位大于等于除数时,comparison最高位为0,反之为1。
	//comparison的计算结果还能表示被除数高位与除数减法运算的结果。
	//在移动除数时,判断的是除数左移一位后与被除数高位的大小关系,进而判断能不能把除数进行左移。
	assign comparison = ((divisor[L_DIVR-1] == 0) && ((state_c == ADIVR))) ? 
				dividend_r[L_DIVN : L_DIVN - L_DIVR] - {divisor_r[L_DIVR-2 : 0],1'b0} : 
				dividend_r[L_DIVN : L_DIVN - L_DIVR] - divisor_r;//计算被除数高位减去除数,如果计算结果最高位为0,表示被除数高位大于等于除数,如果等于1表示被除数高位小于除数。

  下面是状态机跳转代码。

	//状态机次态到现态的转换;
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为空闲状态;
			state_c <= IDLE;
		end
		else begin//状态机次态到现态的转换;
			state_c <= state_n;
		end
	end

	//状态机的次态变化。
	always@(*)begin
		case(state_c)
			IDLE : begin//如果开始计算信号为高电平且除数和被除数均不等于0。
				if(start & (dividend != 0) & (divisor != 0))begin
					state_n = ADIVR;
				end
				else begin//如果开始条件无效或者除数、被除数为0,则继续处于空闲状态。
					state_n = state_c;
				end
			end
			ADIVR : begin//如果除数的最高位为高电平或者除数左移一位大于被除数的高位,则跳转到除法运算状态;
				if(divisor_r[L_DIVR-1] | comparison[L_DIVR])begin
					state_n = DIV;
				end
				else begin
					state_n = state_c;
				end
			end
			DIV : begin
				if(max)begin//如果被除数移动次数达到最大值,则状态机回到空闲状态,计算完成。
					state_n = IDLE;
				end
				else begin
					state_n = state_c;
				end
			end
			default : begin//状态机跳转到空闲状态;
				state_n = IDLE;
			end
		endcase
	end

  下面是除数和被除数、商的计算过程,与前文描述一致,不再赘述。

	//对被除数进行移位或进行减法运算。
	//初始时需要加载除数和被除数,然后需要判断除数和被除数的高位,确定除数是否需要移位。
	//然后根据除数和被除数高位的大小,确认被除数是移位还是与除数进行减法运算,注意被除数移动时,为了保证结果不变,商也会左移一位。
	//如果被除数高位与除数进行减法运算,则商的最低位变为1,好比此时商1进行的减法运算。经减法结果赋值到被除数对应位。
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为0;
			divisor_r <= 0;
			dividend_r <= 0;
			quotient_r <= 0;
			shift_divisor <= 0;
			shift_dividend <= 0;
		end//状态机处于加载状态时,将除数和被除数加载到对应寄存器,开始计算;
		else if(state_c == IDLE && start && (dividend != 0) & (divisor != 0))begin
			dividend_r <= dividend;//加载被除数到寄存器;
			divisor_r <= divisor;//加载除数到寄存器;
			quotient_r <= 0;//将商清零;
			shift_dividend <= 0;//将移位的被除数寄存器清零;
			shift_divisor <= 0; //将移位的除数寄存器清零;
		end//状态机处于除数左移状态,且除数左移后小于等于被除数高位且除数最高位为0。
		else if(state_c == ADIVR && (~comparison[L_DIVR]) && (~divisor_r[L_DIVR-1]))begin
			divisor_r <= divisor_r << 1;//将除数左移1位;
			shift_divisor <= shift_divisor + 1;//除数总共被左移的次数加1;
		end
		else if(state_c == DIV)begin//该状态需要完成被除数移位和减法运算。
			if(comparison[L_DIVR] && (~max))begin//当除数大于被除数高位时,被除数需要移位。
				dividend_r <= dividend_r << 1;//将被除数左移1位;
				quotient_r <= quotient_r << 1;//同时把商左移1位;
				shift_dividend <= shift_dividend + 1;//被除数总共被左移的次数加1;
			end
			else if(~comparison[L_DIVR])begin//当除数小于等于被除数高位时,被除数高位减去除数作为新的被除数高位。
				dividend_r[L_DIVN : L_DIVN - L_DIVR] <= comparison;//减法结果赋值给被除数进行减法运算的相应位。
				quotient_r[0] <= 1;//因为做了一次减法,则商加1。
			end
		end
	end

  参考代码如下所示:

//注意此模块默认被除数的位宽大于等于除数的位宽。
//当quotient_vld信号为高电平且error为低电平时,输出的数据是除法计算的正确结果。
//当输入除数为0时,error信号拉高,且商和余数为0;
//当ready信号为低电平时,不能将开始信号start拉高,此时拉高start信号会被忽略。
module div #(
	parameter 			L_DIVN			= 	8				,//被除数的位宽;
	parameter 			L_DIVR			= 	4				 //除数的位宽;
)(
	input									clk 			,//时钟信号;
	input 									rst_n			,//复位信号,低电平有效;

	input 									start 			,//开始计算信号,高电平有效,必须在ready信号为高电平时输入才有效。
	input				[L_DIVN - 1 : 0]	dividend		,//被除数输入;
	input				[L_DIVR - 1 : 0]	divisor			,//除数输入;

	output 	reg 							ready			,//高电平表示此模块空闲。
	output 	reg 							error			,//高电平表示输入除数为0,输入数据错误。
	output  reg 		 					quotient_vld	,//商和余数输出有效指示信号,高电平有效;
	output	reg 		[L_DIVR - 1 : 0]	remainder		,//余数,余数的大小不会超过除数大小。
	output	reg			[L_DIVN - 1 : 0]	quotient 		 //商。
);	
	localparam          L_CNT   		= 	clogb2(L_DIVN)	;//利用函数自动计算移位次数计数器的位宽。
	localparam 			IDLE 			= 	3'b001  		;//状态机空闲状态的编码;
	localparam 			ADIVR			= 	3'b010  		;//状态机移动除数状态的编码;
	localparam 			DIV				= 	3'b100  		;//状态机进行减法计算和移动被除数状态的编码;

	reg 									vld				;//
	reg  				[2 : 0]				state_c			;//状态机的现态;
	reg  				[2 : 0]				state_n			;//状态机的次态;
	reg					[L_DIVN : 0]		dividend_r		;//保存被除数;
	reg					[L_DIVR - 1 : 0]	divisor_r   	;//保存除数。
	reg					[L_DIVN - 1 : 0]	quotient_r 		;//保存商。
	reg					[L_CNT - 1 : 0]		shift_dividend	;//用于记录被除数左移的次数。
	reg					[L_CNT - 1 : 0]		shift_divisor	;//用于记录除数左移的次数。
	
	wire 				[L_DIVR : 0] 		comparison		;//被除数的高位减去除数。
	wire   									max				;//高电平表示被除数左移次数已经用完,除法运算基本结束,可能还需要进行一次减法运算。
	
	//自动计算计数器位宽函数。
	function integer clogb2(input integer depth);begin
		if(depth == 0)
			clogb2 = 1;
		else if(depth != 0)
			for(clogb2=0 ; depth>0 ; clogb2=clogb2+1)
				depth=depth >> 1;
		end
	endfunction

	//max为高电平表示被除数左移的次数等于除数左移次数加上被除数与除数的位宽差;
	assign max = (shift_dividend == (L_DIVN - L_DIVR) + shift_divisor);

	//用来判断除数和被除数第一次做减法的高位两者的大小,当被除数高位大于等于除数时,comparison最高位为0,反之为1。
	//comparison的计算结果还能表示被除数高位与除数减法运算的结果。
	//在移动除数时,判断的是除数左移一位后与被除数高位的大小关系,进而判断能不能把除数进行左移。
	assign comparison = ((divisor[L_DIVR-1] == 0) && ((state_c == ADIVR))) ? 
				dividend_r[L_DIVN : L_DIVN - L_DIVR] - {divisor_r[L_DIVR-2 : 0],1'b0} : 
				dividend_r[L_DIVN : L_DIVN - L_DIVR] - divisor_r;//计算被除数高位减去除数,如果计算结果最高位为0,表示被除数高位大于等于除数,如果等于1表示被除数高位小于除数。
	
	//状态机次态到现态的转换;
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为空闲状态;
			state_c <= IDLE;
		end
		else begin//状态机次态到现态的转换;
			state_c <= state_n;
		end
	end

	//状态机的次态变化。
	always@(*)begin
		case(state_c)
			IDLE : begin//如果开始计算信号为高电平且除数和被除数均不等于0。
				if(start & (dividend != 0) & (divisor != 0))begin
					state_n = ADIVR;
				end
				else begin//如果开始条件无效或者除数、被除数为0,则继续处于空闲状态。
					state_n = state_c;
				end
			end
			ADIVR : begin//如果除数的最高位为高电平或者除数左移一位大于被除数的高位,则跳转到除法运算状态;
				if(divisor_r[L_DIVR-1] | comparison[L_DIVR])begin
					state_n = DIV;
				end
				else begin
					state_n = state_c;
				end
			end
			DIV : begin
				if(max)begin//如果被除数移动次数达到最大值,则状态机回到空闲状态,计算完成。
					state_n = IDLE;
				end
				else begin
					state_n = state_c;
				end
			end
			default : begin//状态机跳转到空闲状态;
				state_n = IDLE;
			end
		endcase
	end

	//对被除数进行移位或进行减法运算。
	//初始时需要加载除数和被除数,然后需要判断除数和被除数的高位,确定除数是否需要移位。
	//然后根据除数和被除数高位的大小,确认被除数是移位还是与除数进行减法运算,注意被除数移动时,为了保证结果不变,商也会左移一位。
	//如果被除数高位与除数进行减法运算,则商的最低位变为1,好比此时商1进行的减法运算。经减法结果赋值到被除数对应位。
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为0;
			divisor_r <= 0;
			dividend_r <= 0;
			quotient_r <= 0;
			shift_divisor <= 0;
			shift_dividend <= 0;
		end//状态机处于加载状态时,将除数和被除数加载到对应寄存器,开始计算;
		else if(state_c == IDLE && start && (dividend != 0) & (divisor != 0))begin
			dividend_r <= dividend;//加载被除数到寄存器;
			divisor_r <= divisor;//加载除数到寄存器;
			quotient_r <= 0;//将商清零;
			shift_dividend <= 0;//将移位的被除数寄存器清零;
			shift_divisor <= 0; //将移位的除数寄存器清零;
		end//状态机处于除数左移状态,且除数左移后小于等于被除数高位且除数最高位为0。
		else if(state_c == ADIVR && (~comparison[L_DIVR]) && (~divisor_r[L_DIVR-1]))begin
			divisor_r <= divisor_r << 1;//将除数左移1位;
			shift_divisor <= shift_divisor + 1;//除数总共被左移的次数加1;
		end
		else if(state_c == DIV)begin//该状态需要完成被除数移位和减法运算。
			if(comparison[L_DIVR] && (~max))begin//当除数大于被除数高位时,被除数需要移位。
				dividend_r <= dividend_r << 1;//将被除数左移1位;
				quotient_r <= quotient_r << 1;//同时把商左移1位;
				shift_dividend <= shift_dividend + 1;//被除数总共被左移的次数加1;
			end
			else if(~comparison[L_DIVR])begin//当除数小于等于被除数高位时,被除数高位减去除数作为新的被除数高位。
				dividend_r[L_DIVN : L_DIVN - L_DIVR] <= comparison;//减法结果赋值给被除数进行减法运算的相应位。
				quotient_r[0] <= 1;//因为做了一次减法,则商加1。
			end
		end
	end
	
	//生成状态机从计算除结果的状态跳转到空闲状态的指示信号,用于辅助设计输出有效指示信号。
	always@(posedge clk)begin
		vld <= (state_c == DIV) && (state_n == IDLE);
	end

	//生成商、余数及有效指示信号;
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为0;
			quotient <= 0;
			remainder <= 0;
			quotient_vld <= 1'b0;
		end//如果开始计算时,发现除数或者被除数为0,则商和余数均输出0,且将输出有效信号拉高。
		else if(state_c == IDLE && start && ((dividend== 0) || (divisor==0)))begin
			quotient <= 0;
			remainder <= 0;
			quotient_vld <= 1'b1;
		end
		else if(vld)begin//当计算完成时。
			quotient <= quotient_r;//把计算得到的商输出。
			quotient_vld <= 1'b1;//把商有效是指信号拉高。
			//移动剩余部分以补偿对齐变化,计算得到余数;
			remainder <= (dividend_r[L_DIVN - 1 : 0]) >> shift_dividend;
		end
		else begin
			quotient_vld <= 1'b0;
		end
	end

	//当输入除数为0时,将错误指示信号拉高,其余时间均为低电平。
	always@(posedge clk or negedge rst_n)begin
		if(rst_n==1'b0)begin//初始值为0;
			error <= 1'b0;
		end
		else if(state_c == IDLE && start)begin
			if(divisor==0)//开始计算时,如果除数为0,把错误指示信号拉高。
				error <= 1'b1;
			else//开始计算时,如果除数不为0,把错误指示信号拉低。
				error <= 1'b0;
		end
	end
	
	//状态机处于空闲且不处于复位状态;
	always@(*)begin
		if(start || state_c != IDLE || vld)
			ready = 1'b0;
		else 
			ready = 1'b1;
	end

endmodule

3、除法器仿真

  对应的TestBench文件如下所示:

`timescale 1ns/1ns
module test();
    localparam  L_DIVN          =   8              ;//被除数的位宽;
    localparam  L_DIVR          =   4               ;//除数的位宽;
    localparam  CYCLE           =   20              ;//时钟周期;
    
    reg                             clk             ;//时钟信号;
    reg                             rst_n           ;//复位信号,低电平有效;
    reg                             start           ;//开始计算信号,高电平有效,
    reg         [L_DIVN - 1 : 0]    dividend        ;//被除数输入;
    reg         [L_DIVR - 1 : 0]    divisor         ;//除数输入;
    
    wire        [L_DIVN - 1 : 0]    quotient        ;//商。
    wire        [L_DIVR - 1 : 0]    remainder       ;//余数,余数的大小不会超过除数大小。
    wire                            quotient_vld    ;//商和余数输出有效指示信号,高电平有效;
    wire                            ready           ;//高电平表示此模块空闲。
    wire                            error           ;//高电平表示输入除数为0,输入数据错误。

    reg                             quotient_error  ;
    reg                             rem_error       ;

    //例化待测试的除法器模块;
    div #(
        .L_DIVN         ( L_DIVN        ),//被除数的位宽;
        .L_DIVR         ( L_DIVR        ) //除数的位宽;
    )
    u_div(
        .clk            ( clk           ),//时钟信号;
        .rst_n          ( rst_n         ),//复位信号,低电平有效;
        .start          ( start         ),//开始计算信号,高电平有效,
        .dividend       ( dividend      ),//被除数输入;
        .divisor        ( divisor       ),//除数输入;
        .quotient       ( quotient      ),//商。
        .remainder      ( remainder     ),//余数,余数的大小不会超过除数大小。
        .ready          ( ready         ),//高电平表示此模块空闲。
        .error          ( error         ),//高电平表示输入除数为0,输入数据错误。
        .quotient_vld   ( quotient_vld  ) //商和余数输出有效指示信号,高电平有效;
    );

    //生成时钟信号;
    initial begin 
        clk = 0; 
        forever #(CYCLE/2) clk = ~clk;
    end

    //生成复位信号;
    initial begin
        rst_n = 1;
        #2;
        rst_n = 0;//开始时复位5个时钟;
        #(5*CYCLE);
        rst_n = 1;
    end

    initial begin
        #1;
        dividend = 121; divisor = 13;start=0;
        #(8*CYCLE);
        repeat(20)begin//循环进行20次运算;
            #(5*CYCLE);
            dividend = {$random};//产生随机数作为被除数;
            divisor = {$random} ;//产生随机数作为除数;
            start = 1;
            #(CYCLE);
            start = 0;
            @(negedge quotient_vld);
            #1;
            #(3*CYCLE);
        end
        #(5*CYCLE);
        $stop;//停止仿真;
    end

    //对模块输出的数据进行判断。
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            quotient_error <= 1'b0;
            rem_error <= 1'b0;
        end
        else if(quotient_vld && (divisor!=0))begin
            quotient_error <= ((dividend / divisor) != quotient);
            rem_error <= ((dividend % divisor) != remainder);
        end
    end
    
endmodule

  仿真结果如下所示,商和余数的错误指示信号均为低电平,表示除法运算的结果没有什么问题。

在这里插入图片描述

图5 仿真结果

  模块内部信号的详细仿真如图6、7所示,具体含义与前文介绍一致,就不再赘述了。

在这里插入图片描述

图6 除法模块详细仿真

在这里插入图片描述

图7 除法模块详细仿真

4、总结

  使用该模块需要注意几点:

  1. 被除数位宽必须大于等于除数位宽。
  2. 当error为高电平时,表示输入除数为0,此时输出的商和余数均为0且无效。
  3. 只有当quotient_vld为高电平时,模块输出的商和余数才是有效的。
  4. 只有当模块处于空闲(ready为高电平)时,外部输入的start和除数、被除数才能有效。

   如果需要源文件,在公众号后台回复“除法器”(不包括引号)即可。由于我只使用vscode和modelsim对工程进行仿真,所以没有vivado和quartus工程。下面视频就是通过vscode调用modelsim进行仿真的视频。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1409577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客刷题之列表

文章目录 生成数字列表列表的长度添加列表元素append() 列表尾添加insert(index,elem) 在任意位置插入 删除列表元素pop(index) 删除下标为index 的元素并返回remove(x) 删除指定元素 生成数字列表 方法一&#xff08;普通方法&#xff09; num input() newnum num.split() a…

Flutter中实现中国省份地图

效果展示(这里只展示局部&#xff0c;完全展示违规)&#xff1a; 可以点击省份改变颜色&#xff0c;更多功能可以自行拓展。 注&#xff1a;非完整中国地图&#xff01;&#xff01;&#xff01; 本文用于记录在Flutter项目中安卓端实现中国地图&#xff0c;因为实现过程是通过…

vue项目中debugger不生效问题解决

Vue中使用debugger在chrome谷歌浏览器中失效问题&#xff08;已解决&#xff09;_vue debugger不生效-CSDN博客 卡了半天,最后解决了

Python中安全删除列表元素的实用技巧详解

概要 在 Python 中&#xff0c;列表是一种常用的数据结构&#xff0c;用于存储一组有序的元素。然而&#xff0c;有时候需要从列表中删除特定的元素&#xff0c;以满足需求。本文将介绍一些安全删除列表元素的实用技巧&#xff0c;以及如何处理各种情况下可能出现的异常。 使用…

​第20课 在Android Native开发中加入新的C++类

​这节课我们开始利用ffmpeg和opencv在Android环境下来实现一个rtmp播放器&#xff0c;与第2课在PC端实现播放器的思路类似&#xff0c;只不过在处理音视频显示和播放的细节略有不同。 1.压缩备份上节课工程文件夹并修改工程文件夹为demo20&#xff0c;将demo20导入到Eclipse或…

代码随想录算法训练营29期|day30 任务以及具体安排

332.重新安排行程 class Solution {private LinkedList<String> res;private LinkedList<String> path new LinkedList<>();public List<String> findItinerary(List<List<String>> tickets) {Collections.sort(tickets, (a, b) -> a.…

Spring基于dynamic-datasource实现MySQL多数据源

目录 多数据源实现 引入依赖 yml配置文件 业务代码 案例演示 多数据源实现 引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>dynamicdatasourcespringbootstarter</artifactId><version>3.5.0</version> &…

Java玩转《啊哈算法》排序之桶排序

过去心不可得&#xff0c;现在心不可得&#xff0c;未来心不可得 目录在这里 楔子代码地址桶排序代码核心部分优缺点 完整代码演示 升级版核心代码完整代码演示 楔子 大家好&#xff01;本人最近看了下《啊哈算法》&#xff0c;写的确实不错&#xff0c;生动形象又有趣&#x…

AR 自回归模型

文章目录 总的代码ADF 检验(是否平稳)差分操作拟合AR 模型预测可视化总的代码 import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.ar_model import AutoReg from statsmodels.tsa.stattools import adfuller# 生成一个示例时间序…

【开源】基于JAVA语言的假日旅社管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统介绍2.2 QA 问答 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿评论4.3 查询民宿新闻4.4 新建民宿预订单4.5 查询我的民宿预订单 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的假日旅社…

Java-HashMap如何找落点

顾名思义&#xff0c;HashMap采用的是哈希方式来找落点&#xff0c;通过数据的某些特征&#xff0c;计算出一个哈希值&#xff0c;然后用哈希值与节点建立映射关系&#xff0c;从而确定这个数据应该在哪个节点上&#xff0c;下图是一个具有16个节点的分布式集群&#xff0c;本文…

JVM篇--垃圾回收器高频面试题

1 你知道哪几种垃圾收集器&#xff0c;各自的优缺点是啥&#xff0c;重点讲下cms和G1&#xff0c;包括原理&#xff0c;流程&#xff0c;优缺点&#xff1f; 1&#xff09;首先简单介绍下 有以下这些垃圾回收器 Serial收集器&#xff1a; 单线程的收集器&#xff0c;收集垃圾时…

【K8S 云原生】K8S的图形化工具——Rancher

目录 一、rancher概述 1、rancher概念 2、rancher和K8S的区别&#xff1a; 二、实验 1、安装部署 2、给集群添加监控&#xff1a; 3、创建命名空间&#xff1a; 4、创建deployment&#xff1a; 5、创建service&#xff1a; 6、创建ingress&#xff1a; 7、创建hpa 8…

redis—Set集合

目录 前言 1.常见命令 2.使用场景 前言 集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;集合中1)元素之间是无序的2)元素不允许重复&#xff0c;如图2-24所示。一个集合中最多可以存储22 - 1个元素。Redis 除了支持集合内的增删查改操…

Android SeekBar 进度条圆角

先看下效果图&#xff1a; 之前&#xff1a; 优化后&#xff1a; 之前的不是圆角是clip切割导致的 全代码&#xff1a; <SeekBarandroid:layout_width"188dp"android:layout_height"wrap_content"android:background"null"android:focusa…

微认证 openEuler社区开源贡献实践

文章目录 1. 开源与开源社区2. openEuler 社区概述3.参与openEuler社区贡献4.openEuler软件包开发Linux软件管理——源码编译 1. 开源与开源社区 Richard Matthew Stallman&#xff0c;1983年9月推出GNU项目&#xff0c;并发起自由软件运动(free software movement或free/open…

【并发】什么是 Future?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 关键特性和操作包括&#xff1a; 提交任务&#xff1a; 查询完成状态&#xff1a; 等待结果&#xff1a; 取消任务&#xff1a…

Vue实现图片预览,侧边栏懒加载,不用任何插件,简单好用

实现样式 需求 实现PDF上传预览&#xff0c;并且不能下载 第一次实现&#xff1a;用vue-pdf&#xff0c;将上传的文件用base64传给前端展示 问题&#xff1a; 水印第一次加载有后面又没有了。当上传大的pdf文件后&#xff0c;前端获取和渲染又长又慢&#xff0c;甚至不能用 修…

Windows下网络编程(win32API+VS2022)

一、开发环境 我这里介绍下我用的环境安装过程。 所有版本的VS都可以的。 我当前环境是在Windows下&#xff0c;IDE用的是地表最强IDE VS2022。 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/downloads/ 因为我这里只需要用到C和C语言编程&#xff0c;那…

SRC实战 | 小白SRC找到的第一个SQL注入

本文由掌控安全学院 - zbs投稿 一、漏洞说明 xxxxx公司后台存在SQL注入&#xff0c;后端数据库为Mysql 【显错位2&#xff0c;4&#xff0c;6】 漏洞已提交平台&#xff0c;后台的开发商提供给了很多公司&#xff0c;搜一下资产就有很多公司都没有修复该漏洞。 二、漏洞挖掘…