学习FPGA(四)复位端

前言

        前几天在开源的FPGA论坛上看到FPGA工程师前辈说的FPGA工程师的职业病,其中有一条是看见代码就想着重构;之后又刷到一篇博客专门讲写VerilogHDL代码的规范问题,我这些看一下,我大致处于有一定基础的新手与工程师之间。

        在我看过的工程师写的VerilogHDL代码中,我发现一个普遍的规律,就是always块的触发条件中,与clk(时钟信号)的上升沿一定与rst(复位信号)的下降沿进行或运算,且这类alway块内的内容中,一定要先判断复位信号rst的状态再进行接下来的主要操作,其伪代码大致如下:

always @(posedge clk or negedge rst_n)
begin
    if (!rst_n)
    begin
        //寄存器复位
    end
    else
    begin
        //时序逻辑电路
    end
end

        接下来还是结合着昆明理工大学数字电子技术实验A(2024-2025下学期版),来对复位端进行一个深入的理解和应用。

一、设计目标与设计思路

        数字电子技术实验A实验三分成四个子任务,简单说一下就是:1.四位拨动开关控制数码管显示0-9,显示在第一个数码管上;2.四位拨动开关控制数码管显示0-9,三位拨动开关控制字符显示在对应数码管上;3.设计分频器,分出0.5Hz、1Hz、100Hz、500Hz、1000Hz的频率,通过三位拨动开关控制输出特定频率方波,由led显示;4.利用三位拨动开关,在数码管上显示专属参数对应字符,并用分频器的输出方波对数码管的位选进行扫描,同时led进行1Hz的闪烁。

        通过任务目标可以发现,前两个任务就是之前的seglight.v子模块的实例化,是组合逻辑电路。根据我的理解,组合逻辑电路的复位端一般来说就是使能端,使能端一断,组合逻辑电路就停止工作。

        后两个任务是简单的时序电路的设计,对于时序逻辑电路,复位端的主要功能是清楚寄存器里面的内容,使整个电路回到初始状态。分频的原理就不细说了,极端例子就是数时间:数60个1秒就是1分钟,数3600个1秒就是1小时……

        任务4有个细节的问题:分频器对数码管位选进行扫频,任务要求无明显闪烁,为实现目标可以将6位数码管全部打通,就无明显闪烁了。(这可能是实验编者的失误,也可能是我理解错误,这里还是按照要求来编写)

二、主模块

        由于有四个任务,需要四个子模块的实例化,这里考虑使用按键作为不同任务的选择,也用按键来充当组合逻辑电路的使能端与时序逻辑电路的复位端,具体程序如下:

module assignment3(clk, sw, key, seg, dig, led);

input				clk;
input	[7:0]		sw;
input	[7:0]		key;
output	[7:0]		seg;
output	[5:0]		dig;
output	[15:0]	    led;

wire	[15:0]	led1;
wire	[15:0]	led2;
reg		[3:0]	en;
wire			rst;

assign rst = key[7];

always @(key)
begin
	case(key)
		8'b11111110:	en	<=	4'b0001;	
		8'b11111101:	en	<=	4'b0010;	
		8'b11111011:	en	<=	4'b0100;	
		8'b11110111:	en	<=	4'b1000;
	endcase
end

assignment3_1 u_ass1(
	.enable(en[0] & rst),
	.sw(sw),
	.seg(seg),
	.dig(dig)
);

assignment3_2 u_ass2(
	.enable(en[1] & rst),
	.sw(sw),
	.seg(seg),
	.dig(dig)
);

assignment3_3 u_ass3(
	.clk(clk),
	.rst(en[2] & rst),
	.sw(sw),
	.led(led1)
);

assignment3_4 u_ass4(
	.clk(clk),
	.rst(en[3] & rst),
	.sw(sw),
	.dig(dig),
	.seg(seg),
	.led(led2)
);

assign led[0] = led1 | led2;

endmodule 

        由于任务三和任务四都要用到同一个led灯,直接将两个模块的输出led引脚接在led[0]上会出现引脚复用的错误,因为系统认为我的两个子模块的led引脚同时接在led[0]上,状态不明确,但是我知道一个在运行、另一个不会运行,因此我可以大胆地将两值求或输出。

        将key[7]作为组合逻辑电路的使能端与时序逻辑电路的复位端,与任务选择端求与输入到子模块。由于我的FPGA开发板外电路图中,按键通过上拉电阻接Vcc,因此常态高电平。

三、任务一与任务二

        类似7448的组合逻辑电路的设计,用之前早已编写好的seglight.v子模块就行,这里就不展示了,具体的可以翻找之前的文章,大多都有seglight.v子模块的使用。

四、任务三

        分频的原理在前面已经简单举例了,接下来演示一下1Hz的分频,代码如下:

module CLK_1Hz(clk, rst, clkout);

input			clk;
input			rst;
output        	clkout;

reg		[27:0]	counter;
reg				state;

parameter f = 25_000_000;

always @(posedge clk or negedge rst)
begin
	if (!rst)
	begin
		counter    	<= 0;
        state       <= 0;
	end
	else
	begin
		counter		<=	counter + 1;
		if (counter >= f)
		begin
			counter	<=	0;
			state	<=	!state;
		end
	end
end

assign clkout = state;

endmodule 

        我的开发板比较老,50MHz的主频,因此每跳动25'000'000次,就是0.5s;每0.5s高电平,0.5s低电平,就是周期为1s,频率为1Hz的方波。

        这里首次出现的rst复位端的使用,即复位端被按下,寄存器清零,这段程序的寄存器只有计数器和状态,将这两寄存器清零,之后的操作中,这两个寄存器就重新开始运作。复位键没有被按下时,就遵循分频器的工作,计数到指定值后就进行翻转。

        在应用中,clk引脚接入主频时钟信号,rst引脚接复位信号,clkout引脚输出1Hz的方波信号,应用到任务三中,代码如下:

module assignment3_3(clk, rst, sw, led);

input			clk;
input			rst;
input	[3:0]	sw;
output	[15:0]	led;

wire			clk_5e1Hz;
wire			clk_1Hz;
wire			clk_100Hz;
wire			clk_500Hz;
wire			clk_1kHz;
reg				ledstate;

assign led[15:1] = 16'o00000;

CLK_5e1Hz u_CLK_5e1Hz(
	.clk(clk),
	.rst(rst),
	.clkout(clk_5e1Hz)
);

CLK_1Hz u_CLK_1Hz(
	.clk(clk),
	.rst(rst),
	.clkout(clk_1Hz)
);

CLK_100Hz u_CLK_100Hz(
	.clk(clk),
	.rst(rst),
	.clkout(clk_100Hz)
);

CLK_500Hz u_CLK_500Hz(
	.clk(clk),
	.rst(rst),
	.clkout(clk_500Hz)
);

CLK_1kHz u_CLK_1kHz(
	.clk(clk),
	.rst(rst),
	.clkout(clk_1kHz)
);

always @(sw)
begin
	case(sw)
		0:	ledstate	<=	clk_5e1Hz;
		1:	ledstate	<=	clk_1Hz;
		2:	ledstate	<=	clk_100Hz;
		3:	ledstate	<=	clk_500Hz;
		4:	ledstate	<=	clk_1kHz;
	endcase
end

assign led[0] = rst & ledstate;

endmodule 

五、任务四

        任务四是任务一和任务三的组合,led以1Hz的频率闪烁和数码管的显示这里就不多说了,主要来看一下今天的主体——复位端,先上代码:

module assignment3_4(clk, rst, sw, dig, seg, led);

input			clk;
input			rst;
input	[2:0]	sw;
output	[5:0]	dig;
output	[7:0]	seg;
output	[15:0]	led;

reg		[8:0]	segnum;
reg		[4:0]	dignum;
wire			clkout1;
wire			clkout2;

assign led[15:1] = 16'o00000;

CLK_1kHz u_CLK1(
	.clk(clk),
	.rst(rst),
	.clkout(clkout1)
);

CLK_1Hz u_CLK2(
	.clk(clk),
	.rst(rst),
	.clkout(clkout2)
);

seglight u_seg(
	.enable(rst),
	.seg(seg),
	.dig(dig),
	.segnum(segnum),
	.dignum(dignum),
	.point(0)
);

always @(posedge clkout1 or negedge rst)
begin
	if (!rst)
	begin
		segnum	<= 8'h00;
		dignum	<= 6'o00;
	end
	else
	begin
		dignum	<= dignum + 1;
		case (sw)
			1:		segnum	<= 1;
			2:		segnum	<= 2;
			3:		segnum	<= 3;
			4:		segnum	<= 4;
			5:		segnum	<= 5;
			6:		segnum	<= 6;
		default:	segnum	<= 10;
	endcase
	end
end

assign led[0] = rst & clkout2;

endmodule 

        任务三和任务四的末尾都将复位端rst和led状态作于输给led,这个复位端的作用不仅是将时钟信号进行复位,也将led的输出置低电平。其实更好的写法应该是(ledstate与clkout2作用相同):

assign led[0] = rst ? ledstate : 1'bz;

        这样的写法能保证复位键按下时,led[0]输出保持高组态,极大可能地防止led[0]输出到此模块以外的地方之后出现混淆的情况。同样在clk1kHz的时钟时序下,rst复位端的下降沿及其后续的低电平使数码管位选、段选寄存器置初始状态。

总结

        复位端的作用是将时序电路的寄存器置初始状态,这一操作就像给计算机加入“重启”功能。在时序电路出现没能预期的错误或是卡顿后,利用复位端将电路重置回初始,方便找到问题,也能更加清晰地重新演示时序逻辑的状态。

        当然要说不加复位端可以吗?可以!小程序可以,大程序绝对不可以!应该说,如果能写到大程序,应该就不是普通的Verilog入门者了,工程师一般都会在时序电路中加入复位端,保证时序电路可以正常的调试。

        以上的程序需要修改专属参数的地方只有任务四时序主电路的地方,自行修改。我和工程师之间的最大差距还有就是代码中的注释。(我是一点没写……)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值