宋桓公

【技术分享】Verilog打造除法器驱动数码管(上)

0
阅读(3414)

记得很久以前,用单片机玩数码管的时候,我们用“%”和“/”将一个长长的数字一一分离出来逐个显示在数码管上。如今已不玩单片机很多年,想用Verilog打造一个数码管接口,用来实时显示一个“较长的”数字。比如前段时间,和一个学长“比赛”看谁用到较少的逻辑先驱动DS18B20,就需要将温度这个数字显示在数码管上。

先看看C语言是如何实现分离一个数字的比如我要将一个三位数分别显示在三个数码管上,那么操作过程如下:

假设这个三位数叫做Temp;

百位 = Temp/100;

十位 = Temp%100/10;

个位 = Temp%10;

这样的话就能将Temp 拆分开来,并且分别显示在三个数码管上。但是对于FPGA而言,“%”和“/”是消耗逻辑的恶魔,要想用较少的逻辑门,那么这种方法直接被抛弃~~

很久以前我看到一篇笔记,叫《Verilog HDL那些事儿_建模篇》里面介绍了一个用Verilog打造的除法器。这个除法器,有以下特点:

1、这个除法器有两个输入端口,一个是除数输入,一个是被除数输入;输出端口也有两个,一个输出商,一个输出余数;

2、不管除数被除数如何变化(在变化范围内),所消耗的时间是一样的。

3、它是一个带符号的除法器。

这个除法器,确实十分强大,它的第一个特点就完成了“%”和“/”这两个功能,不是吗?呵呵~~~所以这个强大的除法器就是我们的不二人选。

       接下来,就来看看这个除法器,不过恕笔者愚笨,至今它的原理我还是不甚明白,不知道BT的创始人是怎么想出来的,不过用确实好用,所以我只会用而已,代码如下:

module Divider				
(
       input CLK,
       input RSTn,
       input Start_Sig,
       input [15:0] X1,  //被除数   
       input [15:0] X2,  //除数     
       output reg[15:0] Quotient,
       output reg[15:0] Reminder,
		 output reg Done
);
		reg [4:0] i;
		reg [31:0] Diff;
		reg [31:0] p;   // 操作空间为操作数宽度的两倍
		reg [16:0] S;
    
	always @(posedge CLK or negedge RSTn)
		if(!RSTn) 
		begin 
		  Diff <= 32'd0;
		  p <= 32'd0;
		  S <= 17'd0;
		  i <= 5'd0;
		  Done <= 1'b0;
		end
		else if(Start_Sig)
			case (i)
			0:
			  begin
				  Diff <= 16'd0;
				  S <= X2[15] ? { 1'b1, X2 } : { 1'b1, ~X2 + 1'b1 };
				  p <= X1[15] ? { 16'd0, ~X1 + 1'b1  } : { 16'd0,  X1};
				  i <= i + 1'b1;
			  end
			1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16:
			begin 
				Diff = p + { S, 15'd0 };
				if( Diff[31] )  p <= { p[30:0], 1'b0 };
				else p <=  { Diff[30:0], 1'b1 };
				i <= i + 1'b1;
			end
			17:
			begin
				Quotient <=  ( X1[15] ^ X2[15] ) ? ( ~p[15:0] + 1'b1 ) : p[15:0]; 
				Reminder <= p[31:16];
				i <= i + 1'b1;
			end
			18:	
			begin
				Done <= 1'b1;
				i <= i + 1'b1;
			end
			19:
			begin
				Done <= 1'b0;
				i <= 5'd0;
			end
		endcase	

endmodule 

以上就是一个,16位的除法器,至于如何打造其他位数的除法器,照葫芦画瓢吧,会用就行呵呵~~若特别想理解,那就去看《Verilog HDL那些事儿_建模篇》这本笔记吧。

简单的分析一下这个除法器,如你所见他的被除数和除数都是16位的,且他是带符号位的,就是说除数,被除数,商都可以是负数,很显然,我们用不到这么多,应为对于除数而言,只可能是10,100,1000之类的数。此外我们还注意到这个模块有一个Start_Sig信号,它是用来启动或是关闭,除法器的。Done信号是表示一个除法的完成。有了这两个信号,那么这个除法器就可以复用了。嘿嘿,这个是《Verilog HDL那些事儿_建模篇》里面的仿顺序操作写法,个人十分喜欢。

好了,准备工作已经做好了,现在开始驱动数码管~~

我的6个数码管是底电平驱动的,于是乎声明参数如下,也就是所谓的段选和为选:

parameter 
	_0 = 8'b1100_0000, _1 = 8'b1111_1001, _2 = 8'b1010_0100, 
	_3 = 8'b1011_0000, _4 = 8'b1001_1001, _5 = 8'b1001_0010, 
	_6 = 8'b1000_0010, _7 = 8'b1111_1000, _8 = 8'b1000_0000,
	_9 = 8'b1001_0000;
  
    parameter 
	wei_1 = 6'b111110, wei_2 = 6'b111101, wei_3 = 6'b111011,
	wei_4 = 6'b110111, wei_5 = 6'b101111, wei_6 = 6'b011111;

好我们来声明一个“数组”来存储将要分离的数:

以为分离出的数最大为9,最小为0,且一共6个数码管,所以“数组”大小如下:

reg [3:0]Num [0:5]; //内存的宽度4,内存的深度6;

假设长长的数字已经完美的分离出来了,那把它显示出来就好啦:

/**************加码模块*************************/
	reg [7:0]SegNum[5:0];
	always @(posedge CLK)
		case(Num[0])
			0:SegNum[0] <= _0;
			1:SegNum[0] <= _1;
			2:SegNum[0] <= _2;
			3:SegNum[0] <= _3;
			4:SegNum[0] <= _4;
			5:SegNum[0] <= _5;
			6:SegNum[0] <= _6;
			7:SegNum[0] <= _7;
			8:SegNum[0] <= _8;
			9:SegNum[0] <= _9;
		endcase
		
	always @(posedge CLK)
		case(Num[1])
			0:SegNum[1] <= _0;
			1:SegNum[1] <= _1;
			2:SegNum[1] <= _2;
			3:SegNum[1] <= _3;
			4:SegNum[1] <= _4;
			5:SegNum[1] <= _5;
			6:SegNum[1] <= _6;
			7:SegNum[1] <= _7;
			8:SegNum[1] <= _8;
			9:SegNum[1] <= _9;
		endcase
	
	always @(posedge CLK)
		case(Num[2])
			0:SegNum[2] <= _0;
			1:SegNum[2] <= _1;
			2:SegNum[2] <= _2;
			3:SegNum[2] <= _3;
			4:SegNum[2] <= _4;
			5:SegNum[2] <= _5;
			6:SegNum[2] <= _6;
			7:SegNum[2] <= _7;
			8:SegNum[2] <= _8;
			9:SegNum[2] <= _9;
		endcase

多的我就不写了~~~~待会源码贴在下面。

虽然,FPGA是并行的,但是为了节约引脚,数码管还是需要扫描的~~

//--------------------数码管扫描----------------------------	
	reg [19:0] C0 = 20'd0;
	reg [5:0]Control = 6'b111110;
	always @(posedge CLK)
		if(C0 == 20'h1ffff)
		begin
			C0 <= 20'd0;
			Control <= {Control[4:0],Control[5]};
		end
		else 	
		begin
			C0 <= C0 + 1'b1;
		end	
		
//------------将每一位数显示在对应的数码管上-----------------------		
	reg [7:0]Seg = 8'hff;
	always @(posedge CLK)
		case(Control)
			wei_1: Seg <= SegNum[0];
			wei_2: Seg <= SegNum[1];	
			wei_3: Seg <= SegNum[2];
			wei_4: Seg <= SegNum[3];
			wei_5: Seg <= SegNum[4];
			wei_6: Seg <= SegNum[5];
		endcase

由于FPGA的速度不是单片机可以比的,所以扫描的时候加个延时,才能更好的显示。

接下来,把我们之前的写的除法器例化出来备用:

//-------------------------除法器例化------------------------
	reg[15:0] X1;			//被除数   
	reg[15:0] X2;  		//除数     
	wire[15:0] Quotient;	//商	
	wire[15:0] Reminder;	//余数
	reg Start_Sig = 1'b0;
	wire Done;
	Divider 		Divi	
	(
		.CLK(CLK),
		.RSTn(RSTn),
		.Start_Sig(Start_Sig),
		.X1(X1),  //被除数   
		.X2(X2),  //除数     
		.Quotient(Quotient),
		.Reminder(Reminder),
		.Done(Done)
	);

之前都是铺垫,最后的关键是如何将数字逐个分离出来~~

//-------------------------利用除法器将数字分离出来----------------------------	
	reg [15:0]Display_Num;		//要显示的一个数字
	reg [3:0]i = 4'd0;
	reg [3:0]Num [0:5];			//内存的宽度4,内存的深度6;
	reg [2:0]Num_i = 3'd0;
	always @(posedge CLK)
		case(i)
			0:
			begin
				Display_Num <= 16'd4321;
				i <= i + 1'b1;
			end
			1:
			begin
				Start_Sig <= 1'b1;
				X1 <= Display_Num;
				X2 <= 16'd10;
				i <= i + 1'b1;
			end
			2:
			begin
				if(Done) 
				begin
					Start_Sig <= 1'b0;
					X1 <= Quotient;
					X2 <= 16'd10;
					Num[Num_i] <= Reminder[3:0];
					i <= i + 1'b1;
				end	
				else begin Start_Sig <= 1'b1; i <= i; end
			end
			3:
			begin
					if(Num_i < 5)begin Num_i <= Num_i + 1'b1; i <= i - 1'b1;end
					else	i <= i + 1'b1;
			end
			4:
			begin
				i <= i;
			end
		endcase

Display_Num,这个数是用来做测试的,看看我写的这个数是不是能反映到数码管上~~

接下来,分析这段程序:

步骤0:对Display_Num任意赋一个值。(接下来就是分离Display_Num)

步骤1:打开除法器,并将Display_Num作为除法器的被除数,10作为除法器的除数。

步骤2:当除法器一轮完成之时,很明显此时而余数Reminder = 1,

也正是我们要分离的第一个数,于是把它存到了NUM[0],而商Quotient 此时等于432,将继续作为下一轮除法的被除数;

步骤3:判断是否分离出所以的数。

步骤4:分离完成,卡死于此。

要注意的是,这用的是一个16位的带符号的除法器,所以显示数的范围,是0到32768而已,当然稍加修改,就能显示负数啦~

到目前为止,这个程序也只是测试程序而已,检测是否能完成共能而已,而不是一个完整的接口。下次再来完成它吧~~

未完待续~~

完整的Digital代码如下:

module Digital 
(
	input CLK,
	input RSTn,
	//--
	output [7:0]Row_Scan_Sig,
	output [5:0]Column_Scan_Sig
);

/***************************************/
parameter 
	_0 = 8'b1100_0000, _1 = 8'b1111_1001, _2 = 8'b1010_0100, 
	_3 = 8'b1011_0000, _4 = 8'b1001_1001, _5 = 8'b1001_0010, 
	_6 = 8'b1000_0010, _7 = 8'b1111_1000, _8 = 8'b1000_0000,
	_9 = 8'b1001_0000;
  
parameter 
	wei_1 = 6'b111110, wei_2 = 6'b111101, wei_3 = 6'b111011,
	wei_4 = 6'b110111, wei_5 = 6'b101111, wei_6 = 6'b011111;
//-------------------------除法器例化------------------------
	reg[15:0] X1;			//被除数   
	reg[15:0] X2;  		//除数     
	wire[15:0] Quotient;	//商	
	wire[15:0] Reminder;	//余数
	reg Start_Sig = 1'b0;
	wire Done;
	Divider 		Divi	
	(
		.CLK(CLK),
		.RSTn(RSTn),
		.Start_Sig(Start_Sig),
		.X1(X1),  //被除数   
		.X2(X2),  //除数     
		.Quotient(Quotient),
		.Reminder(Reminder),
		.Done(Done)
	);
//-------------------------利用除法器将数字分离出来----------------------------	
	reg [15:0]Display_Num;		//要显示的一个数字
	reg [3:0]i = 4'd0;
	reg [3:0]Num [0:5];			//内存的宽度4,内存的深度6;
	reg [2:0]Num_i = 3'd0;
	always @(posedge CLK)
		case(i)
			0:
			begin
				Display_Num <= 16'd4321;
				i <= i + 1'b1;
			end
			1:
			begin
				Start_Sig <= 1'b1;
				X1 <= Display_Num;
				X2 <= 16'd10;
				i <= i + 1'b1;
			end
			2:
			begin
				if(Done) 
				begin
					Start_Sig <= 1'b0;
					X1 <= Quotient;
					X2 <= 16'd10;
					Num[Num_i] <= Reminder[3:0];
					i <= i + 1'b1;
				end	
				else begin Start_Sig <= 1'b1; i <= i; end
			end
			3:
			begin
					if(Num_i < 5)begin Num_i <= Num_i + 1'b1; i <= i - 1'b1;end
					else	i <= i + 1'b1;
			end
			4:
			begin
				i <= i;
			end
		endcase
	
	 
/**************加码模块*************************/
	reg [7:0]SegNum[5:0];
	always @(posedge CLK)
		case(Num[0])
			0:SegNum[0] <= _0;
			1:SegNum[0] <= _1;
			2:SegNum[0] <= _2;
			3:SegNum[0] <= _3;
			4:SegNum[0] <= _4;
			5:SegNum[0] <= _5;
			6:SegNum[0] <= _6;
			7:SegNum[0] <= _7;
			8:SegNum[0] <= _8;
			9:SegNum[0] <= _9;
		endcase
		
	always @(posedge CLK)
		case(Num[1])
			0:SegNum[1] <= _0;
			1:SegNum[1] <= _1;
			2:SegNum[1] <= _2;
			3:SegNum[1] <= _3;
			4:SegNum[1] <= _4;
			5:SegNum[1] <= _5;
			6:SegNum[1] <= _6;
			7:SegNum[1] <= _7;
			8:SegNum[1] <= _8;
			9:SegNum[1] <= _9;
		endcase
	
	always @(posedge CLK)
		case(Num[2])
			0:SegNum[2] <= _0;
			1:SegNum[2] <= _1;
			2:SegNum[2] <= _2;
			3:SegNum[2] <= _3;
			4:SegNum[2] <= _4;
			5:SegNum[2] <= _5;
			6:SegNum[2] <= _6;
			7:SegNum[2] <= _7;
			8:SegNum[2] <= _8;
			9:SegNum[2] <= _9;
		endcase
		
		always @(posedge CLK)
		case(Num[3])
			0:SegNum[3] <= _0;
			1:SegNum[3] <= _1;
			2:SegNum[3] <= _2;
			3:SegNum[3] <= _3;
			4:SegNum[3] <= _4;
			5:SegNum[3] <= _5;
			6:SegNum[3] <= _6;
			7:SegNum[3] <= _7;
			8:SegNum[3] <= _8;
			9:SegNum[3] <= _9;
		endcase
	
	always @(posedge CLK)
		case(Num[4])
			0:SegNum[4] <= _0;
			1:SegNum[4] <= _1;
			2:SegNum[4] <= _2;
			3:SegNum[4] <= _3;
			4:SegNum[4] <= _4;
			5:SegNum[4] <= _5;
			6:SegNum[4] <= _6;
			7:SegNum[4] <= _7;
			8:SegNum[4] <= _8;
			9:SegNum[4] <= _9;
		endcase
	
	always @(posedge CLK)
		case(Num[5])
			0:SegNum[5] <= _0;
			1:SegNum[5] <= _1;
			2:SegNum[5] <= _2;
			3:SegNum[5] <= _3;
			4:SegNum[5] <= _4;
			5:SegNum[5] <= _5;
			6:SegNum[5] <= _6;
			7:SegNum[5] <= _7;
			8:SegNum[5] <= _8;
			9:SegNum[5] <= _9;
		endcase
	
//--------------------数码管扫描----------------------------	
	reg [19:0] C0 = 20'd0;
	reg [5:0]Control = 6'b111110;
	always @(posedge CLK)
		if(C0 == 20'h1ffff)
		begin
			C0 <= 20'd0;
			Control <= {Control[4:0],Control[5]};
		end
		else 	
		begin
			C0 <= C0 + 1'b1;
		end	
		
//------------将每一位数显示在对应的数码管上-----------------------		
	reg [7:0]Seg = 8'hff;
	always @(posedge CLK)
		case(Control)
			wei_1: Seg <= SegNum[0];
			wei_2: Seg <= SegNum[1];	
			wei_3: Seg <= SegNum[2];
			wei_4: Seg <= SegNum[3];
			wei_5: Seg <= SegNum[4];
			wei_6: Seg <= SegNum[5];
		endcase


	

	assign Column_Scan_Sig = Control;
	assign Row_Scan_Sig = Seg;






endmodule