零基础学FPGA(五):时序逻辑电路设计之计数器(附有呼吸灯实验、简单组合逻辑设计介绍)

日常·唠嗑

      第一次建立《零基础学FPGA》专栏,是在2021年2月2日,已经过去了一年了,目前只更新了4篇。总说要更新,却总是拖更,直到这两天有关注的朋友提起,才想起来,在这里跟各位关注我的朋友说声抱歉。
      新的一年,我的时间会比较充裕,会花更多时间在这个专栏上,初心不变,还是跟大家一起共进步,嘿嘿。
      最近会接触一些国产FPGA开发,如京微齐力的伏羲软件等,有兴趣的也可以一起交流哇~~
在这里插入图片描述

前言

      在零基础学FPGA系列中,我没有写组合逻辑电路设计,而是直接进入时序逻辑电路设计。因为组合逻辑比较简单,在这里我简单提一下就好。
      顺带提一下,有关非阻塞赋值、阻塞赋值、assign和always这些Verilog之类的语法,我放到下一篇再讲。

一、认清逻辑设计

      在数字电路中可以根据电路功能的不同分为,组合逻辑电路与时序逻辑电路。组合逻辑电路在逻辑功能上的特点是:任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关。而时序逻辑从电路特征上看来,其特点为任意时刻的输出不仅取决于该时刻的输入,而且还和电路原来的状态有关。通常是以assign语句为主。 组合逻辑电路在电路结构上,不涉及对信号跳变沿的处理,无存储电路,也没有反馈电路,通常可以通过真值表的形式表达出来。时序逻辑电路在电路结构上,不管输入如何变化,仅当时钟的沿(上升沿或下降沿)到达时,才有可能使输出发生变化。通常是以always语句为主。

对于组合逻辑,这里用一个3-8 译码器电路来介绍一下:
      译码器是一种多输入多输出的组合逻辑电路,负责将二进制代码翻译为特定的对象(如逻辑电平等),功能与编码器相反。译码器一般分为通用译码器和数字显示译码器两大类。
      比如:三八译码器,将 3 种输入状态翻译成 8 种输出状态,其真值表如下所示。其中 A,B,C 为数据输入,Out 为数据输出。在 MCU 应用中,如果需要保证一定的速度情况下实现此功能,一般选取外挂一片74HC38 或者 74LS38 等独立芯片,但 FPGA 提供了一个完整的想象以及实现空间,仅靠其自身即可实现设计要求。
                                                译码器真值表
在这里插入图片描述

module decoder( 
		a,
		b,
		c,
		out
	);

	input a;//输入端口A
	input b;//输入端口B
	input c;//输入端口C
	
	output [7:0]out;//输出端口
	reg [7:0]out;
	
	always@(a,b,c)//always@()括号内为敏感信号列表
	//always语句可以带时钟,也可以不带时钟。在always不带时钟时,逻辑功能和assign完全一致,都是只产生组合逻辑。
	begin
		case({
    
    a,b,c})//判断a,b,c状态值,可以用拨键开关做测试
			3'b000:out = 8'b0000_0001;
			3'b001:out = 8'b0000_0010;
			3'b010:out = 8'b0000_0100;
			3'b011:out = 8'b0000_1000;
			3'b100:out = 8'b0001_0000;
			3'b101:out = 8'b0010_0000;
			3'b110:out = 8'b0100_0000;
			3'b111:out = 8'b1000_0000;
		endcase	
	end

endmodule

二、时序逻辑电路设计

      时序逻辑电路是指电路任何时刻的稳态输出不仅取决于当前的输入,还与前一时刻输入形成的状态有关。这跟组合逻辑电路相反,组合逻辑的输出只会跟目前的输入成一种函数关系。换句话说,时序逻辑拥有储存元件来存储信息,而组合逻辑则没有。
重点:
      时序逻辑电路分为很多种,在这里我讲一下最常用的计数器,并比较与组合逻辑电路的区别。此处设计一个计数器,使开发板上的 LED 状态每 500ms 翻转一次。市面上开发板的晶振大多为 50MHz,也就是说时钟周期为 20ns(1÷500_000),这样可以计算得出 500ms = 500_000_000ns/20ns = 25_000_000,即需要计数器计数 25_000_000 次,也就是需要一个至少25 位的计数器(225>25_000_000>224)。且每当计数次数达到需要清零并重新计数。
      Verilog HDL 之所以被称为硬件电路描述语言,就是因为我们不是在类似 C 一样进行普通的编程,而是在编写一个实际的硬件电路。下面将设计一个计数器,通过计数器控制一个LED 闪。

module counter
(
input clk, //时钟信号50Mhz
input reset_n,//复位信号
output led
); 

reg led;

parameter MCNT = 24_999_999;

reg [24:0]period_cnt ;	//定义计数器寄存器

//计数器计数进程	
always@(posedge clk or posedge reset_n)
if(reset_n)
	period_cnt <= 25'd0;
else if(period_cnt == MCNT)
	period_cnt <= 25'd0;
else
	period_cnt <= period_cnt + 1'b1;

//led输出控制进程
always@(posedge clk or posedge reset_n)
if(reset_n)
	led <= 1'b1;
else if(period_cnt == MCNT)
	led <= ~led;
else
	led <= led;

endmodule

      因为计数器是从 0 开始计数而不是 1,所以在计数值计数到 25’d24_999_999 时清零,而不是计数到25’d25_000_000 时清零,这里计数器最大值使用 parameter 进行参数化定义表示,使用参数化定义的好处是通过修改顶层parameter参数,从而完成整体变量参数修改。
      当计数器计数到预设的值后就让 led 取反一次,来达到亮灭翻转的目的。
      在这个实验基础上,可以通过PWM做呼吸灯实验。

三、扩展:呼吸灯实验

      呼吸灯采用 PWM 的方式,在固定的频率下,通过调整占空比的方式来控制 LED 灯亮度的变化。PWM,即脉冲宽度调制,在由计数器产生的固定周期的 PWM 信号下,如果其占空比为 0,则 LED 灯不亮;如果其占空比为 100%,则 LED 灯最亮。所以将占空比从 0 到 100%,再从 100%到 0 不断变化,就可以实现 LED 灯的“呼吸”效果。PWM 占空比调节示意图如下图所示:
在这里插入图片描述
      由上图可知,LED 高电平的时间由长渐渐变短,再由短渐渐变长,如果 LED 灯是高电平点亮,则 LED灯会呈现出亮度由亮到暗,再由暗到亮的过程。
在这里插入图片描述
      周期信号计数器用于产生驱动 LED 的脉冲信号,本次实验的周期信号频率为 1Khz,其占空比由后级逻辑在每个周期之后进行递增或递减,最后再对当前计数值和占空比计数值进行比较,以输出占空比可调的脉冲信号。

module breath_led(
    input   clk   ,  //时钟信号50Mhz
    input   reset_n ,  //复位信号

    output  led          //LED
);

//reg define
reg  [15:0]  period_cnt ;   //周期计数器频率:1khz 周期:1ms  计数值:1ms/20ns=50000
reg  [15:0]  duty_cycle ;   //占空比数值
reg          inc_dec_flag ; //0 递增  1 递减

//根据占空比和计数值之间的大小关系来输出LED
assign   led = (period_cnt >= duty_cycle) ?  1'b1  :  1'b0;

//周期计数器
always @(posedge clk or negedge reset_n) begin
    if(!reset_n)
        period_cnt <= 16'd0;
    else if(period_cnt == 16'd50000)
        period_cnt <= 16'd0;
    else
        period_cnt <= period_cnt + 1'b1;
end

//在周期计数器的节拍下递增或递减占空比
always @(posedge clk or negedge reset_n) begin
    if(!reset_n) begin
        duty_cycle   <= 16'd0;
        inc_dec_flag <= 1'b0;
    end
    else begin
        if(period_cnt == 16'd50000) begin    //计满1ms
            if(inc_dec_flag == 1'b0) begin   //占空比递增状态
                if(duty_cycle == 16'd50000)  //如果占空比已递增至最大
                    inc_dec_flag <= 1'b1;    //则占空比开始递减
                else                         //否则占空比以25为单位递增
                    duty_cycle <= duty_cycle + 16'd25;
            end
            else begin                       //占空比递减状态
                if(duty_cycle == 16'd0)      //如果占空比已递减至0
                    inc_dec_flag <= 1'b0;    //则占空比开始递增
                else                         //否则占空比以25为单位递减
                    duty_cycle <= duty_cycle - 16'd25;
            end
        end
    end
end

endmodule

      第 21-28 行是 1KHz 周期信号的计数器,用于产生 1KHz 的 LED 驱动信号。第 31-52 行的 always 块为占空比设定模块,每次计数完了一个周期,就根据递增/递减标志来对占空比计数值(duty_cycle)进行递增/递减 25 个计数值,这个递增或者递减的数值大小可以用来控制呼吸灯的呼吸频率。
      如果占空比计数值(duty_cycle)已经递增到了最大,则呼吸灯已经处于最亮的状态,接下来开始递减;反之,如果占空比计数至已经递减到了最小,即 0,则呼吸灯处于熄灭的状态,接下来开始递增;如此循环往复,最终实现了流水灯的效果。在代码的第 18 行通过组合逻辑把当前的周期计数值和占空比计数值进行比较,来判断 LED 的输出电平。在一个周期内,如果当前的周期计数值小于等于占空比计数值,则 LED 输出高电平,即点亮;如果当前的周期计数值大于占空比计数值,则 LED 输出低电平,即熄灭。

猜你喜欢

转载自blog.csdn.net/weixin_46423500/article/details/123022152