有限状态机设计基础

有限状态机可以实现对外部激励和当前状态的有效相应机制 ,在数字通信、自动化控制、指令 集设计等方面均有重要作用,应用广泛。当前的程序化设计方法使得有限状态机的设计得以极 大简化,从过去数字电路教学中较复杂的设计内容演变为基础教学内容,提升了设计效率,这 也符合行业对于数字电路系统设计的需求

目录

有限状态机定义

有限状态机优点

有限状态机分类

Moore型状态机

Mealy型状态机

有限状态机的表示方法

1.状态转移图

2.算法状态机(ASM)图

有限状态机代码实现

状态机设计实例

序列检测器设计

实现对输入序列数"1101"的检测

Moore状态机序列检测器

[序列检测器核心模块 moore_seq_detector.v](#序列检测器核心模块 moore_seq_detector.v)

[Testbench 仿真文件 tb_moore_seq_detector.v](#Testbench 仿真文件 tb_moore_seq_detector.v)

Mealy状态机序列检测器

[Mealy 型序列检测器核心模块 mealy_seq_detector.v](#Mealy 型序列检测器核心模块 mealy_seq_detector.v)

[Testbench 仿真文件 tb_mealy_seq_detector.v](#Testbench 仿真文件 tb_mealy_seq_detector.v)

总结

[Moore 型特性](#Moore 型特性)

[Mealy 型特性](#Mealy 型特性)

ADC采样控制电路设计

[ADC0809 核心特性](#ADC0809 核心特性)

[ADC0809 驱动核心模块 adc0809_ctrl.v](#ADC0809 驱动核心模块 adc0809_ctrl.v)

顶层模块(含数码管显示,方便上板验证)adc0809_top.v

[数码管扫描驱动模块 scan_led_hex_disp.v(复用优化版)](#数码管扫描驱动模块 scan_led_hex_disp.v(复用优化版))

按键消抖电路设计

核心功能

状态定义

[10ms 定时器模块 timer_10ms.v](#10ms 定时器模块 timer_10ms.v)

[FSM 按键消抖核心模块 debounce_fsm.v](#FSM 按键消抖核心模块 debounce_fsm.v)

[顶层整合模块 debounce_top.v](#顶层整合模块 debounce_top.v)

[zero → one 路径](#zero → one 路径)

[one → zero 路径](#one → zero 路径)

关键特性

tb_debounce.v

序列检测器进阶

Moore型

Mealy型

消抖电路的替换方案

[10ms 定时器模块(复用原模块,无修改)](#10ms 定时器模块(复用原模块,无修改))

[替换方案 FSM 消抖核心模块(边沿触发 + 20ms 防抖)](#替换方案 FSM 消抖核心模块(边沿触发 + 20ms 防抖))

顶层整合模块(替换原消抖电路)

[Testbench 仿真代码](#Testbench 仿真代码)

停车场计数器

功能验证


有限状态机定义

有限状态机(Finite State Machine,FSM)简称状态机,是用来表示系统中的有限个状态及这些状态之间的转移和动作的模型

这些转移和动作依赖于当前状态和外部输入,它下一步的状态逻辑通常是重新建立的,也称之为随机逻辑

有限状态机优点

1.高效的顺序控制模型

克服了纯硬件数字系统顺序方式控制不灵活的缺点,在其运行方式上类似于控制灵活和方便的CPU,是高速高效控制的首选

2.容易利用现成的EDA工具进行优化设计

状态机构建简单,设计方案相对固定,使用HDL综合器可以发挥其强大的优化功能;

性能良好的综合器都具备许多可控或自动优化状态机的功能

3.稳定性能

状态机容易构成良好的同步时序逻辑模块,可用于解决大规模逻辑电路设计中的竞争和冒险现象

4.高速性能

在高速通信和高速控制方面,状态机更具有其巨大的优势,一个状态机的功能类似于CPU的功能

5.高可靠性能

状态机是由纯硬件电路构成,不存在CPU运行软件过程中许多固有的缺陷;

状态机的设计中能使用各种容错技术

当状态机进入非法状态并从中跳出,进入正常状态的时间短暂,对系统的危害不大

有限状态机分类

状态机主要分为两种类型:

Moore型状态机:

下一状态只由当前状态决定,

即 次态=f(现状,输入),输出=f(现状);

Mealy型状态机:

下一状态不但与当前状态有关,还与当前输入值有关,

即 次态=f(现状,输入),输出=f(现状,输入);

Moore型状态机

Moore状态机的输出只与当前的状态有关,也就是由当前的状态决定输出,而与此时的输入无关,输入之决定状态机的状态改变,不影响电路的最终输出

Mealy型状态机

Mealy状态机的输出不仅与当前的状态有关,还与当前的输出有关,即当前的输入和当前的状态共同决定当前的输入

有限状态机的表示方法

相同点:这两种表示方法包含了相同的信息,都包含了状态机的输入、输出、状态和转换

1.状态转移图

状态转移图表示法更为紧凑,更适合描述较为简单的系统

状态转移图组成:

  • 带有标示状态的节点
  • 带有注释的有向弧线

上图中的逻辑表达式由输入信号决定,放在每个转移弧线上,表示状态转移的特定条件

Moore型输出值放置在节点内,只由当前状态决定;

Mealy型输出放置在转换弧线上,由当前状态和外部输出决定

图中这个有限状态机包含三个状态、两个信号输入、一个Moore输出和一个Mealy输出

当有限状态机在S0或S1状态时,y1输出高电平;

当状态机处于S0状态且a、b均为1的时候,y0也为高电平;

2.算法状态机(ASM)图

算法状态机图则更像是流程图,能较好地描述复杂系统中状态的转换和动作

  • 由ASM的一些状态框、判断框和条件输出框相互连接构成,其中判断框时可选的
  • 状态框用于测试输入条件和根据测试的结果来决定选择输出通道
  • 条件输出框通常放置Mealy型的有效输出,一般情况下放置在判断框的后面,表示控制器某些状态只有在特定条件下才能输出
  • 状态图可以转换为ASM图,ASM图也可以转换为状态图

有限状态机代码实现

有限状态机和常规时序电路的代码编写对比:

相同点:均是先将状态寄存器拿出,然后将次态逻辑和输出逻辑结合起来并书写相应的代码

不同点:次态逻辑的代码较为不同,对于有限状态机,次态逻辑单元的代码要遵循状态图或者ASM图的逻辑转换流向

考虑到简便性和灵活性,用符号常量来表示有限状态机的状态

复制代码
localparam[1:0] s0=2'b00,

                s1=2'b01,

                s2=2'b10;

综合的时候,软件会根据有限状态机的结构将符号常量映射为对应的二进制字符(如独热码),这就是状态分配

复制代码
//次态逻辑
always@(*)
    case(state_reg)
        s0:
            if(a)
                if(b)
                    state_next=s2;
                else
                    state_next=s1;
            else
                state_next=s0;
        s1:    
            if(a)
                state_next=s0;
            else
                state_next=s1;
        s2:
            state_next=s0;
        default:state_next=s0;
    endcase

状态机设计实例

序列检测器设计

序列检测器可用于检测一组或多组由二进制码组成的脉冲序列信号,当序列检测器连续收到一组串行二进制码后,如果这组码与检测器中预先设置的码相同,则输出1,否则输出0

关键步骤:正确码的接收必须是连续的,要求检测器必须记住前一次的正确码及正确序列,直到在连续的检测中所收到的每一位码都与预置数的对应码相同

实现对输入序列数"1101"的检测

Moore状态机序列检测器

定义以下状态:

|--------------|
| s0:未检测到'1'输入 |
| s1:收到'1' |
| s2:收到'11' |
| s3:收到'110' |
| s4:收到'1101' |

如果现态是s0,输入为0,那么下一状态还是停留在s0;如果输入1,则转移到状态s1

在s1状态,如果 输入为0,则回到状态s0;如果输入为1,那么久转移到s2

在s2状态,如果输入为1,则停留在状态s2;如果输入为0,那么下一状态为s3

在s3状态,如果输入为1,则转移到状态s4,输出1;如果输入为0,则分会状态0

在s4状态,如果输入为0,回到初始状态s0;如果输入为1,下一状态为s1

  • 状态定义 :5 个状态(3bit 编码)
    • s0:初始状态,未检测到有效序列
    • s1:检测到1
    • s2:检测到11
    • s3:检测到110
    • s4:检测到1101(目标序列终点)
  • 目标序列1101,Moore 型状态机输出仅由当前状态决定
  • 仅在s4状态输出1,其余状态输出0,完美匹配 Moore 状态机定义
序列检测器核心模块 moore_seq_detector.v
复制代码
module moore_seq_detector
(
    input clk,              // 系统时钟
    input rst_n,            // 异步复位,低电平有效
    input din,              // 串行输入数据
    output reg dout         // 检测输出:检测到1101时输出1,否则0
);

// ====================== 状态定义 ======================
localparam [2:0]
    s0 = 3'b000,  // 初始状态
    s1 = 3'b001,  // 检测到1
    s2 = 3'b010,  // 检测到11
    s3 = 3'b011,  // 检测到110
    s4 = 3'b100;  // 检测到1101(目标状态)

reg [2:0] current_state;  // 当前状态
reg [2:0] next_state;     // 次态

// ====================== 状态寄存器:时序逻辑 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        current_state <= s0;
    else
        current_state <= next_state;
end

// ====================== 次态逻辑:组合逻辑 ======================
always @(*) begin
    case(current_state)
        s0: begin
            if(din == 1'b1)
                next_state = s1;
            else
                next_state = s0;
        end

        s1: begin
            if(din == 1'b1)
                next_state = s2;
            else
                next_state = s0;
        end

        s2: begin
            if(din == 1'b0)
                next_state = s3;
            else
                next_state = s2;  // 输入1,保持s2(11→111,仍匹配11前缀)
        end

        s3: begin
            if(din == 1'b1)
                next_state = s4;
            else
                next_state = s0;
        end

        s4: begin
            if(din == 1'b0)
                next_state = s1;
            else
                next_state = s0;
        end

        default: next_state = s0;  // 异常状态回到初始态
    endcase
end

// ====================== 输出逻辑:Moore型,仅由当前状态决定 ======================
always @(*) begin
    case(current_state)
        s4: dout = 1'b1;  // 仅在s4状态输出1,检测到1101
        default: dout = 1'b0;
    endcase
end

endmodule
Testbench 仿真文件 tb_moore_seq_detector.v
复制代码
module tb_moore_seq_detector();

reg clk;
reg rst_n;
reg din;
wire dout;

// 实例化DUT
moore_seq_detector uut
(
    .clk(clk),
    .rst_n(rst_n),
    .din(din),
    .dout(dout)
);

// 生成50MHz时钟(周期20ns)
initial begin
    clk = 1'b0;
    forever #10 clk = ~clk;
end

// 仿真激励:输入序列 1101 1101 01101,验证检测
initial begin
    // 初始化
    rst_n = 1'b0;
    din = 1'b0;
    #100;
    rst_n = 1'b1;
    #20;

    // 输入序列:1 1 0 1 → 检测到1101,dout=1
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    // 再次输入1101
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    // 输入干扰序列0 1 1 0 1
    din = 1'b0; #20;
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    #100;
    $finish;
end

endmodule

Mealy状态机序列检测器

对比Mealy状态机与Moore状态机的状态图可知:

Moore状态机的检测结果输出是与时钟同步的;而Mealy状态机的检测结果输出是异步的,当输入发生变化时,输出就立即变化。因此Mealy状态机的输出比Moore状态机状态的输出提前一个周期

  • 状态定义 :4 个状态(2bit 编码)
    • s0:初始状态,未检测到有效序列
    • s1:检测到1
    • s2:检测到11
    • s3:检测到110
  • 目标序列110,Mealy 型状态机输出由当前状态 + 输入共同决定
  • 输出规则 :仅当s3状态输入1时,输出1(检测到1101),其余情况输出0
Mealy 型序列检测器核心模块 mealy_seq_detector.v
复制代码
module mealy_seq_detector
(
    input clk,              // 系统时钟
    input rst_n,            // 异步复位,低电平有效
    input din,              // 串行输入数据
    output reg dout         // 检测输出:检测到1101时输出1,否则0
);

// ====================== 状态定义 ======================
localparam [1:0]
    s0 = 2'b00,  // 初始状态
    s1 = 2'b01,  // 检测到1
    s2 = 2'b10,  // 检测到11
    s3 = 2'b11;  // 检测到110

reg [1:0] current_state;  // 当前状态
reg [1:0] next_state;     // 次态

// ====================== 状态寄存器:时序逻辑 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        current_state <= s0;
    else
        current_state <= next_state;
end

// ====================== 次态逻辑:组合逻辑 ======================
always @(*) begin
    case(current_state)
        s0: begin
            if(din == 1'b1)
                next_state = s1;
            else
                next_state = s0;
        end

        s1: begin
            if(din == 1'b1)
                next_state = s2;
            else
                next_state = s0;
        end

        s2: begin
            if(din == 1'b0)
                next_state = s3;
            else
                next_state = s2;  // 输入1,保持s2(11→111,仍匹配11前缀)
        end

        s3: begin
            next_state = s0;  // 无论输入0/1,都回到s0
        end

        default: next_state = s0;  // 异常状态回到初始态
    endcase
end

// ====================== 输出逻辑:Mealy型,由当前状态+输入决定 ======================
always @(*) begin
    case(current_state)
        s3: begin
            // s3状态输入1时,输出1(检测到110→1,即1101序列)
            if(din == 1'b1)
                dout = 1'b1;
            else
                dout = 1'b0;
        end
        default: dout = 1'b0;  // 其余状态/输入,输出0
    endcase
end

endmodule
Testbench 仿真文件 tb_mealy_seq_detector.v
复制代码
module tb_mealy_seq_detector();

reg clk;
reg rst_n;
reg din;
wire dout;

// 实例化DUT
mealy_seq_detector uut
(
    .clk(clk),
    .rst_n(rst_n),
    .din(din),
    .dout(dout)
);

// 生成50MHz时钟(周期20ns)
initial begin
    clk = 1'b0;
    forever #10 clk = ~clk;
end

// 仿真激励:输入序列 1101 1101 01101,验证检测
initial begin
    // 初始化
    rst_n = 1'b0;
    din = 1'b0;
    #100;
    rst_n = 1'b1;
    #20;

    // 输入序列:1 1 0 1 → 检测到1101,dout=1
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    // 再次输入1101
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    // 输入干扰序列0 1 1 0 1
    din = 1'b0; #20;
    din = 1'b1; #20;
    din = 1'b1; #20;
    din = 1'b0; #20;
    din = 1'b1; #20;  // dout=1

    #100;
    $finish;
end

endmodule
当前状态 输入 次态 输出 说明
s0 0 s0 0 初始态,输入 0 保持
s0 1 s1 0 检测到第一个 1
s1 0 s0 0 输入 0,序列中断,回到初始态
s1 1 s2 0 检测到 11
s2 1 s2 0 输入 1,保持 11 状态(111 仍匹配 11 前缀)
s2 0 s3 0 检测到 110
s3 0 s0 0 输入 0,回到初始态
s3 1 s0 1 输入 1,检测到 1101,输出 1,回到初始态

总结

Moore 型特性
  • 输出dout仅由当前状态决定,与输入无关
  • 仅在s4状态输出1,其余状态输出0,完美匹配 Moore 状态机定义
Mealy 型特性
  • 输出dout当前状态 + 输入共同决定,而非仅由状态决定
  • 仅在s3状态输入1时输出1,完美匹配 Mealy 状态机定义
  • 输出与输入同步,无时钟延迟(Moore 型输出延迟一个周期)

ADC采样控制电路设计

8通道输入的ADC

1.ADDA、ADDB、ADDC是8路输入IN0~IN7的选择信号;

2.端口ALE为模拟信号输入选通端口地址锁存信号,上升沿有效

3.START为转换启动信号,高电平有效;当START有效后,转换状态信号,EOC立即变为低电平,转换结束后,EOC为高电平,控制器可以根据此信号了解转换状态

4.此后控制器可以通过控制输出使能端OE,通过8位并行数据D7:0来读取转换结果

ADC转换控制状态机共有4个状态:

  • 初始化状态s0
  • 启动ADC状态s1
  • 等待ADC转换结束状态s2
  • 转换数据读取状态s3

ADC0809控制的状态从s0到s1,s1到s2,s3到s0的状态转换都是在时钟上升沿直接变化,只有在s2状态时,根据输入信号EOC来判断状态转移的下一状态。ADC在状态控制下,依次在这4个状态切换,完成AD转换功能

ADC0809 核心特性

  • 8 通道 8 位逐次逼近型 ADC,输入IN0~IN7ADDA/ADDB/ADDC(3 位地址)选择
  • 控制信号:
    • ALE:地址锁存,上升沿有效,锁存通道选择信号
    • START:转换启动,高电平有效,启动后EOC变低
    • EOC:转换结束标志,转换中为低,结束后变高
    • OE:输出使能,高电平有效,使能后D[7:0]输出转换结果
  • 转换时间:典型 100μs,需状态机等待转换完成

状态机设计(4 状态)

状态 功能 转移条件
s0 初始化 / 空闲 时钟上升沿 → s1
s1 启动 ADC(拉高ALE+START 时钟上升沿 → s2
s2 等待转换结束(检测EOC高电平) EOC=1s3,否则保持s2
s3 读取转换结果(拉高OE,锁存D[7:0] 时钟上升沿 → s0,循环下一次转换

ADC0809 驱动核心模块 adc0809_ctrl.v

复制代码
module adc0809_ctrl
(
    input clk,              // 系统时钟(推荐50MHz)
    input rst_n,            // 异步复位,低电平有效
    input eoc,              // ADC转换结束信号(高电平有效)
    input [7:0] d,          // ADC 8位并行数据输入
    output reg ale,         // 地址锁存信号(上升沿有效)
    output reg start,       // 转换启动信号(高电平有效)
    output reg oe,          // 输出使能信号(高电平有效)
    output reg [2:0] addr,  // 通道选择地址(ADDA/ADDB/ADDC)
    output reg [7:0] ad_data// 转换结果锁存输出
);

// ====================== 状态定义 ======================
localparam [1:0]
    s0 = 2'b00,  // 初始化/空闲状态
    s1 = 2'b01,  // 启动ADC状态
    s2 = 2'b10,  // 等待转换结束状态
    s3 = 2'b11;  // 读取转换结果状态

reg [1:0] current_state;  // 当前状态
reg [1:0] next_state;     // 次态

// ====================== 状态寄存器:时序逻辑 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        current_state <= s0;
    else
        current_state <= next_state;
end

// ====================== 次态逻辑:组合逻辑 ======================
always @(*) begin
    case(current_state)
        s0: next_state = s1;          // s0 → s1(时钟上升沿直接跳转)
        s1: next_state = s2;          // s1 → s2(时钟上升沿直接跳转)
        s2: next_state = (eoc == 1'b1) ? s3 : s2;  // s2:EOC=1→s3,否则保持
        s3: next_state = s0;          // s3 → s0(时钟上升沿直接跳转)
        default: next_state = s0;     // 异常状态回到s0
    endcase
end

// ====================== 输出逻辑:Moore型,由当前状态决定 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        ale <= 1'b0;
        start <= 1'b0;
        oe <= 1'b0;
        addr <= 3'b000;  // 默认选择IN0通道,可修改为循环扫描
        ad_data <= 8'd0;
    end
    else begin
        case(current_state)
            s0: begin
                // 初始化状态:关闭所有控制信号,准备下一次转换
                ale <= 1'b0;
                start <= 1'b0;
                oe <= 1'b0;
                // 可选:通道循环扫描(每次转换切换通道)
                // if(addr == 3'b111) addr <= 3'b000;
                // else addr <= addr + 1'b1;
            end

            s1: begin
                // 启动ADC状态:拉高ALE(锁存地址)和START(启动转换)
                ale <= 1'b1;
                start <= 1'b1;
                oe <= 1'b0;
            end

            s2: begin
                // 等待转换状态:拉低ALE/START,等待EOC变高
                ale <= 1'b0;
                start <= 1'b0;
                oe <= 1'b0;
            end

            s3: begin
                // 读取结果状态:拉高OE,锁存转换结果
                ale <= 1'b0;
                start <= 1'b0;
                oe <= 1'b1;
                ad_data <= d;  // 锁存ADC输出数据
            end

            default: begin
                ale <= 1'b0;
                start <= 1'b0;
                oe <= 1'b0;
                ad_data <= 8'd0;
            end
        endcase
    end
end

endmodule

顶层模块(含数码管显示,方便上板验证)adc0809_top.v

复制代码
module adc0809_top
(
    input clk,              // 50MHz系统时钟
    input rst_n,            // 复位按键,低电平有效
    input eoc,              // ADC EOC信号
    input [7:0] d,          // ADC 8位数据
    output ale,             // ALE信号
    output start,           // START信号
    output oe,              // OE信号
    output [2:0] addr,      // 通道地址
    output [3:0] an,        // 4位数码管位选
    output [7:0] sseg       // 数码管段码
);

// 内部信号
wire [7:0] ad_data;
wire [3:0] hex3, hex2, hex1, hex0;

// 实例化ADC0809驱动
adc0809_ctrl u_adc_ctrl
(
    .clk(clk),
    .rst_n(rst_n),
    .eoc(eoc),
    .d(d),
    .ale(ale),
    .start(start),
    .oe(oe),
    .addr(addr),
    .ad_data(ad_data)
);

// 8位ADC结果转4位BCD(0~255,显示格式:XX.X,单位V,参考电压5V)
wire [7:0] ad_volt = (ad_data * 50) / 256;  // 5V参考电压,放大10倍,0~50对应0.0~5.0V
assign hex3 = 4'd0;
assign hex2 = {3'b000, ad_volt[7]};         // 十位
assign hex1 = ad_volt[6:3];                 // 个位
assign hex0 = ad_volt[2:0];                 // 0.1V位

// 实例化数码管扫描驱动(复用之前项目模块)
scan_led_hex_disp u_disp
(
    .clk(clk),
    .reset(~rst_n),
    .hex3(hex3),
    .hex2(hex2),
    .hex1(hex1),
    .hex0(hex0),
    .dp_in(4'b1011),  // 个位后点亮小数点,显示X.XV
    .an(an),
    .sseg(sseg)
);

endmodule

数码管扫描驱动模块 scan_led_hex_disp.v(复用优化版)

复制代码
module scan_led_hex_disp
(
    input clk,reset,
    input[3:0] hex3,hex2,hex1,hex0,
    input[3:0] dp_in,
    output reg[3:0] an,
    output reg[7:0] sseg
);

localparam N=18;    // 50MHz分频,扫描频率约190Hz,无闪烁
reg[N-1:0] regN;
reg[3:0] hex_in;
reg dp;

// 分频计数器
always@(posedge clk or posedge reset)
    if(reset)
        regN <= 0;
    else
        regN <= regN + 1;

// 数码管扫描选通
always@(*)
    case(regN[N-1:N-2])
        2'b00: begin an=4'b1110; hex_in=hex0; dp=dp_in[0]; end
        2'b01: begin an=4'b1101; hex_in=hex1; dp=dp_in[1]; end
        2'b10: begin an=4'b1011; hex_in=hex2; dp=dp_in[2]; end
        default: begin an=4'b0111; hex_in=hex3; dp=dp_in[3]; end
    endcase

// 七段译码(共阴数码管,低电平点亮)
always@(*)
begin
    case(hex_in)
        4'h0: sseg[6:0] = 7'b0000001;
        4'h1: sseg[6:0] = 7'b1001111;
        4'h2: sseg[6:0] = 7'b0010010;
        4'h3: sseg[6:0] = 7'b0000110;
        4'h4: sseg[6:0] = 7'b1001100;
        4'h5: sseg[6:0] = 7'b0100100;
        4'h6: sseg[6:0] = 7'b0100000;
        4'h7: sseg[6:0] = 7'b0001111;
        4'h8: sseg[6:0] = 7'b0000000;
        4'h9: sseg[6:0] = 7'b0000100;
        default: sseg[6:0] = 7'b1111111;  // 灭灯
    endcase
    sseg[7] = ~dp;  // 低电平点亮小数点
end

endmodule

状态机时序

  • s0→s1:时钟上升沿直接跳转,初始化完成
  • s1→s2:时钟上升沿直接跳转,完成 ALE/START 拉高,启动转换
  • s2→s3 :仅当EOC=1(转换结束)时跳转,否则保持等待
  • s3→s0:时钟上升沿直接跳转,完成数据读取,回到空闲,循环下一次转换
状态 ALE START OE 功能
s0 0 0 0 空闲,准备通道地址
s1 1 1 0 锁存地址,启动转换
s2 0 0 0 等待转换完成(EOC 变高)
s3 0 0 1 使能输出,锁存转换结果

通道选择

  • 默认固定选择IN0addr=3'b000),可在s0状态添加通道循环逻辑,实现 8 通道自动扫描
  • 通道对应关系:addr=3'b000→IN0,3'b001→IN1,...,3'b111→IN7
FPGA 引脚 ADC0809 引脚 功能
clk - 50MHz 系统时钟
rst_n - 复位按键
eoc EOC 转换结束信号输入
d7:0 D7:0 8 位数据输入
ale ALE 地址锁存输出
start START 转换启动输出
oe OE 输出使能输出
addr2:0 ADDA/ADDB/ADDC 通道地址输出
an3:0/sseg7:0 数码管 显示转换结果(电压值)
  • 给 ADC0809 供电(+5V),参考电压接 + 5V
  • IN0输入 0~5V 模拟电压,下载程序到 FPGA
  • 观察数码管显示:0.0V~5.0V,随输入电压线性变化
  • 验证状态机时序:用示波器观测ALE/START/EOC/OE的时序关系

关键优化与扩展

  1. 时序优化
  • 状态机采用三段式设计,时序收敛性好,无亚稳态风险

  • 适配 50MHz 时钟,转换周期约 400μs(4 个时钟周期 + 100μs 转换时间),满足 ADC 时序要求

  1. 功能扩展
  • 8 通道循环扫描 :在s0状态添加addr自增逻辑,实现 8 通道自动轮流转换

  • 按键切换通道:添加按键输入,手动选择转换通道

  • 串口输出:添加 UART 模块,将 ADC 结果上传到 PC

  • 滤波处理:添加数字滤波(如滑动平均),提高采样稳定性

  1. 常见问题排查
  • 转换结果错误 :检查ALE/START时序,确保地址锁存后再启动转换

  • EOC 检测失败:检查 ADC 供电与参考电压,确保转换正常

  • 数码管显示乱码:检查共阴 / 共阳极性,段码是否正确

按键消抖电路设计

基于FSM设计消抖电路,利用一个10ms的非同步定时器和有限状态机,计时器每10ms产生一个滴答使能周期信号,有限状态机利用此信号来确定输入信号是否稳定

有限状态机将消除时间较短的抖动,当输入信号稳定20ms以后才改变去抖动以后的输出值

|---------------|
| zero态:sw稳定在0值 |
| one态:sw稳定在1值 |

假设系统的起始态时zero(one)态,当sw变为1(0)时,系统转换为wait1_1态

当处于wait1_1态时,有限状态机处于等待状态并将m_tick置为有效电平态。若sw变为0则表示1值所持续的时间过短,有限状态机返回zero态

这个工作在wait1_2态也将再重复2次

核心功能

  • 消除机械按键的 20ms 以内抖动,仅当输入稳定 20ms 后才更新输出
  • 采用10ms 定时器m_tick)作为状态机的使能周期,每 10ms 产生一个脉冲
  • 8 状态 FSM 结构,分两路验证:
    • zero 态:sw 稳定为 0,等待 3 次 10ms 采样(共 20ms)确认跳变
    • one 态:sw 稳定为 1,等待 3 次 10ms 采样(共 20ms)确认跳变
    • wait1_1/wait1_2/wait1_3:从 zero 到 one 的等待状态
    • wait0_1/wait0_2/wait0_3:从 one 到 zero 的等待状态

状态定义

状态 编码 功能
zero 3'b000 sw 稳定为 0,输出 db=0
wait1_1 3'b001 检测到 sw=1,第 1 次 10ms 等待
wait1_2 3'b010 第 2 次 10ms 等待,累计 20ms
wait1_3 3'b011 第 3 次 10ms 等待,累计 30ms,确认跳变到 one
one 3'b100 sw 稳定为 1,输出 db=1
wait0_1 3'b101 检测到 sw=0,第 1 次 10ms 等待
wait0_2 3'b110 第 2 次 10ms 等待,累计 20ms
wait0_3 3'b111 第 3 次 10ms 等待,累计 30ms,确认跳变到 zero

10ms 定时器模块 timer_10ms.v

复制代码
module timer_10ms
#(
    parameter CLK_FREQ = 50_000_000  // 系统时钟50MHz
)(
    input clk,
    input rst_n,
    output reg m_tick  // 10ms脉冲,高电平1个时钟周期
);

localparam CNT_MAX = CLK_FREQ / 100 - 1;  // 50MHz → 10ms = 500_000个周期
reg [18:0] cnt;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt <= 19'd0;
        m_tick <= 1'b0;
    end
    else begin
        if(cnt == CNT_MAX) begin
            cnt <= 19'd0;
            m_tick <= 1'b1;
        end
        else begin
            cnt <= cnt + 1'b1;
            m_tick <= 1'b0;
        end
    end
end

endmodule

FSM 按键消抖核心模块 debounce_fsm.v

复制代码
module debounce_fsm
(
    input clk,
    input rst_n,
    input sw,          // 原始按键输入
    input m_tick,      // 10ms定时器脉冲
    output reg db     // 消抖后输出
);

// ====================== 状态定义(完全匹配题目) ======================
localparam [2:0]
    zero    = 3'b000,
    wait1_1 = 3'b001,
    wait1_2 = 3'b010,
    wait1_3 = 3'b011,
    one     = 3'b100,
    wait0_1 = 3'b101,
    wait0_2 = 3'b110,
    wait0_3 = 3'b111;

reg [2:0] state_reg, state_next;

// ====================== 状态寄存器:时序逻辑 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        state_reg <= zero;
    else
        state_reg <= state_next;
end

// ====================== 次态+输出逻辑:组合逻辑(完全匹配题目代码) ======================
always @(*) begin
    state_next = state_reg;
    db = 1'b0;  // 默认输出

    case(state_reg)
        zero: begin
            db = 1'b0;
            if(sw)
                state_next = wait1_1;
        end

        wait1_1: begin
            db = 1'b0;
            if(~sw)
                state_next = zero;
            else if(m_tick)
                state_next = wait1_2;
        end

        wait1_2: begin
            db = 1'b0;
            if(~sw)
                state_next = zero;
            else if(m_tick)
                state_next = wait1_3;
        end

        wait1_3: begin
            db = 1'b0;
            if(~sw)
                state_next = zero;
            else if(m_tick)
                state_next = one;
        end

        one: begin
            db = 1'b1;
            if(~sw)
                state_next = wait0_1;
        end

        wait0_1: begin
            db = 1'b1;
            if(sw)
                state_next = one;
            else if(m_tick)
                state_next = wait0_2;
        end

        wait0_2: begin
            db = 1'b1;
            if(sw)
                state_next = one;
            else if(m_tick)
                state_next = wait0_3;
        end

        wait0_3: begin
            db = 1'b1;
            if(sw)
                state_next = one;
            else if(m_tick)
                state_next = zero;
        end

        default: begin
            state_next = zero;
            db = 1'b0;
        end
    endcase
end

endmodule

顶层整合模块 debounce_top.v

复制代码
module debounce_top
(
    input clk,          // 50MHz系统时钟
    input rst_n,        // 异步复位,低电平有效
    input sw,           // 原始按键输入
    output db           // 消抖后输出
);

wire m_tick;

// 实例化10ms定时器
timer_10ms u_timer
(
    .clk(clk),
    .rst_n(rst_n),
    .m_tick(m_tick)
);

// 实例化FSM消抖模块
debounce_fsm u_fsm
(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw),
    .m_tick(m_tick),
    .db(db)
);

endmodule

状态转移逻辑

zero → one 路径
  • zero:sw=0,若检测到 sw=1 → 进入wait1_1
  • wait1_1:等待 10ms 脉冲,若 sw 保持 1 → wait1_2;若 sw 变回 0 → 回到zero(抖动滤除)
  • wait1_2:再等 10ms,累计 20ms,sw 保持 1 → wait1_3;否则回zero
  • wait1_3:再等 10ms,累计 30ms,sw 保持 1 → 进入one,输出 db=1
one → zero 路径
  • one:sw=1,若检测到 sw=0 → 进入wait0_1
  • wait0_1:等待 10ms 脉冲,若 sw 保持 0 → wait0_2;若 sw 变回 1 → 回到one(抖动滤除)
  • wait0_2:再等 10ms,累计 20ms,sw 保持 0 → wait0_3;否则回one
  • wait0_3:再等 10ms,累计 30ms,sw 保持 0 → 进入zero,输出 db=0

关键特性

  • 抖动滤除:仅当输入连续 3 次 10ms 采样稳定,才更新输出,彻底消除 20ms 以内抖动
  • 输出同步:db 输出由状态机直接决定,无亚稳态,同步于系统时钟

tb_debounce.v

复制代码
module tb_debounce();

reg clk;
reg rst_n;
reg sw;
wire db;

// 实例化顶层
debounce_top uut
(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw),
    .db(db)
);

// 生成50MHz时钟(周期20ns)
initial begin
    clk = 1'b0;
    forever #10 clk = ~clk;
end

// 仿真激励:模拟按键抖动
initial begin
    // 初始化
    rst_n = 1'b0;
    sw = 1'b0;
    #100;
    rst_n = 1'b1;
    #1_000_000;  // 等待1ms

    // 模拟按键按下(带抖动:0→1→0→1,持续<20ms)
    sw = 1'b1; #5_000_000;  // 5ms抖动
    sw = 1'b0; #3_000_000;  // 3ms抖动
    sw = 1'b1;  // 稳定为1

    // 等待30ms,观察db从0→1
    #40_000_000;

    // 模拟按键松开(带抖动:1→0→1→0,持续<20ms)
    sw = 1'b0; #5_000_000;
    sw = 1'b1; #3_000_000;
    sw = 1'b0;  // 稳定为0

    // 等待30ms,观察db从1→0
    #40_000_000;

    $finish;
end

endmodule

仿真预期结果

  • 按键按下的抖动期间,db 保持 0,直到 sw 稳定 30ms 后,db 才跳变为 1

  • 按键松开的抖动期间,db 保持 1,直到 sw 稳定 30ms 后,db 才跳变为 0

  • 彻底滤除 20ms 以内的机械抖动,输出无毛刺

信号 引脚连接
clk 板载 50MHz 时钟
rst_n 复位按键(低电平有效)
sw 机械按键输入
db LED 输出(观察消抖效果)

验证步骤

  1. 下载程序到 FPGA,按下机械按键,观察 LED:按键按下后,LED 延迟约 20ms 点亮,无闪烁

  2. 松开按键,LED 延迟约 20ms 熄灭,无闪烁

  3. 用示波器观测swdb的波形,验证抖动被滤除,输出稳定

关键优化

  1. 三段式状态机:状态寄存器、次态逻辑、输出逻辑分离,时序收敛性好

  2. 定时器参数化 :可通过修改CLK_FREQ适配不同时钟频率,无需修改核心逻辑

  3. 鲁棒性 :添加default分支,避免锁存器,异常状态自动回到zero

  4. 可扩展性:可修改等待次数(如 2 次 10ms=20ms),调整消抖时间

序列检测器进阶

在序列检测器的基础上,当收到的脉冲序列数据为0时,则记录下后八位数据

  1. 设计一个基于Moore型的电路并画出状态图和ASM图。

  2. 依据状态图和ASM图写出HDL代码。

  3. 写出testbench并且对代码进行仿真验证。

  4. 设计一个基于Mealy型,重复第一第四步。

  • 输入 :串行数据流 din
  • 同时做两件事
    1. 持续检测序列 1101 ,检测到则 detect = 1
    2. 持续检测起始位 0 ,一旦检测到 0,立即采集紧接着的 8 位数据
  • 输出
    • detect:1101 检测标志
    • data_out[7:0]:8 位采集数据
    • valid:采集完成有效标志

Moore型

复制代码
module seq_start_detector_moore (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       din,
    output reg        seq_detected,
    output reg [7:0]  data_out,
    output reg        data_valid
);

    // ---------- 序列检测状态机(检测 "1101")----------
    localparam SEQ_S0 = 3'd0,
               SEQ_S1 = 3'd1,
               SEQ_S2 = 3'd2,
               SEQ_S3 = 3'd3,
               SEQ_S4 = 3'd4;

    reg [2:0] seq_state, seq_next;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            seq_state <= SEQ_S0;
        else
            seq_state <= seq_next;
    end

    always @(*) begin
        seq_next = seq_state;
        case (seq_state)
            SEQ_S0: seq_next = din ? SEQ_S1 : SEQ_S0;
            SEQ_S1: seq_next = din ? SEQ_S2 : SEQ_S0;
            SEQ_S2: seq_next = din ? SEQ_S2 : SEQ_S3;
            SEQ_S3: seq_next = din ? SEQ_S4 : SEQ_S0;
            SEQ_S4: seq_next = SEQ_S0;
            default: seq_next = SEQ_S0;
        endcase
    end

    // Moore 输出:序列检测信号
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            seq_detected <= 1'b0;
        else
            seq_detected <= (seq_state == SEQ_S4);
    end

    // ---------- 起始位检测+数据捕获状态机 ----------
    localparam CAP_IDLE  = 2'd0,
               CAP_START = 2'd1,
               CAP_SHIFT = 2'd2,
               CAP_DONE  = 2'd3;

    reg [1:0] cap_state, cap_next;
    reg [2:0] bit_cnt;
    reg [7:0] shift_reg;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cap_state <= CAP_IDLE;
        else
            cap_state <= cap_next;
    end

    always @(*) begin
        cap_next = cap_state;
        case (cap_state)
            CAP_IDLE : cap_next = din ? CAP_IDLE : CAP_START;
            CAP_START: cap_next = CAP_SHIFT;
            CAP_SHIFT: cap_next = (bit_cnt == 3'd7) ? CAP_DONE : CAP_SHIFT;
            CAP_DONE : cap_next = CAP_IDLE;
            default  : cap_next = CAP_IDLE;
        endcase
    end

    // 计数器与移位寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bit_cnt   <= 3'd0;
            shift_reg <= 8'd0;
        end else begin
            case (cap_state)
                CAP_START: begin
                    bit_cnt   <= 3'd0;
                    shift_reg <= 8'd0;   // 起始位本身不计入数据
                end
                CAP_SHIFT: begin
                    bit_cnt   <= bit_cnt + 1'b1;
                    shift_reg <= {shift_reg[6:0], din};
                end
                default: bit_cnt <= bit_cnt; // 保持
            endcase
        end
    end

    // 数据有效输出(Moore型)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_valid <= 1'b0;
            data_out   <= 8'd0;
        end else begin
            data_valid <= (cap_state == CAP_DONE);
            if (cap_state == CAP_DONE)
                data_out <= shift_reg;
        end
    end

endmodule

`timescale 1ns/1ps
module tb_moore();

reg clk, rst_n, din;
wire seq_detected;
wire [7:0] data_out;
wire data_valid;

seq_start_detector_moore uut (
    .clk(clk),
    .rst_n(rst_n),
    .din(din),
    .seq_detected(seq_detected),
    .data_out(data_out),
    .data_valid(data_valid)
);

// 时钟生成
always #5 clk = ~clk;   // 100MHz

initial begin
    clk = 0;
    rst_n = 0;
    din = 1;
    #15 rst_n = 1;
    #10;

    // ---------- 测试序列检测 "1101" ----------
    // 输入序列:1 1 0 1 (中间插入无关位)
    din = 1; #10;
    din = 1; #10;
    din = 0; #10;
    din = 1; #10;   // 应在此后输出 seq_detected=1

    // 测试重叠检测: 1 1 0 1 1 0 1
    din = 1; #10;
    din = 1; #10;
    din = 0; #10;
    din = 1; #10;   // seq_detected
    din = 1; #10;
    din = 0; #10;
    din = 1; #10;   // 再次检测到

    // ---------- 测试起始位捕获 ----------
    // 先送几个1,然后起始位0,再送8位数据 10101010
    din = 1; #20;
    din = 0; #10;   // 起始位
    din = 1; #10;   // bit0
    din = 0; #10;   // bit1
    din = 1; #10;   // bit2
    din = 0; #10;   // bit3
    din = 1; #10;   // bit4
    din = 0; #10;   // bit5
    din = 1; #10;   // bit6
    din = 0; #10;   // bit7
    // 之后 data_valid 应拉高,data_out=8'b10101010

    // 测试同时发生的情况(数据接收中遇到序列)
    din = 0; #10;   // 起始位
    din = 1; #10;   // bit0
    din = 1; #10;   // bit1
    din = 0; #10;   // bit2
    din = 1; #10;   // bit3  (序列1101出现在bit0~3)
    din = 0; #10;   // bit4
    din = 1; #10;   // bit5
    din = 1; #10;   // bit6
    din = 0; #10;   // bit7
    // 数据接收完成,同时序列检测也应捕获到(两个状态机独立)
    #50;
    $finish;
end

// 波形监视
initial begin
    $monitor("Time=%0t: din=%b, seq_detected=%b, data_valid=%b, data_out=%h",
             $time, din, seq_detected, data_valid, data_out);
end

endmodule

Mealy型

复制代码
module seq_start_detector_mealy (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       din,
    output reg        seq_detected,
    output reg [7:0]  data_out,
    output reg        data_valid
);

    // ---------- 序列检测 Mealy 状态机 ----------
    localparam M_S0 = 2'd0,
               M_S1 = 2'd1,
               M_S2 = 2'd2,
               M_S3 = 2'd3;

    reg [1:0] m_state, m_next;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            m_state <= M_S0;
        else
            m_state <= m_next;
    end

    always @(*) begin
        m_next = m_state;
        case (m_state)
            M_S0: m_next = din ? M_S1 : M_S0;
            M_S1: m_next = din ? M_S2 : M_S0;
            M_S2: m_next = din ? M_S2 : M_S3;
            M_S3: m_next = din ? M_S0 : M_S0; // 不论din为何都回S0
            default: m_next = M_S0;
        endcase
    end

    // Mealy 输出:序列检测(组合逻辑)
    always @(*) begin
        seq_detected = 1'b0;
        if (m_state == M_S3 && din == 1'b1)
            seq_detected = 1'b1;
    end

    // ---------- 数据捕获(采用与 Moore 相同状态机,但输出改为 Mealy)----------
    localparam C_IDLE  = 2'd0,
               C_START = 2'd1,
               C_SHIFT = 2'd2;

    reg [1:0] c_state, c_next;
    reg [2:0] bit_cnt;
    reg [7:0] shift_reg;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            c_state <= C_IDLE;
        else
            c_state <= c_next;
    end

    always @(*) begin
        c_next = c_state;
        case (c_state)
            C_IDLE : c_next = din ? C_IDLE : C_START;
            C_START: c_next = C_SHIFT;
            C_SHIFT: c_next = (bit_cnt == 3'd7) ? C_IDLE : C_SHIFT;
            default: c_next = C_IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bit_cnt   <= 3'd0;
            shift_reg <= 8'd0;
        end else begin
            case (c_state)
                C_START: begin
                    bit_cnt   <= 3'd0;
                    shift_reg <= 8'd0;
                end
                C_SHIFT: begin
                    bit_cnt   <= bit_cnt + 1'b1;
                    shift_reg <= {shift_reg[6:0], din};
                end
                default: ; // keep
            endcase
        end
    end

    // Mealy 输出:data_valid 在移位完成且下一状态将回 IDLE 时有效
    always @(*) begin
        data_valid = 1'b0;
        data_out   = 8'd0;
        if (c_state == C_SHIFT && bit_cnt == 3'd7) begin
            data_valid = 1'b1;
            data_out   = {shift_reg[6:0], din}; // 最后一个位也要移入
        end
    end

endmodule

消抖电路的替换方案

案例中设计的消抖有一个缺陷,当开关转换状态的时候会有一个反应延迟的问题。替代方 案要实现在转换的第一个边沿即作出反应,在等待一个很小的时间段后(至少20ms)和输 入信号进行计算。替换方案要求当输入信号由0变为1时,有限状态机立即作出反应并根据 20ms时间内的输入消除抖动,在这个过程之后系统开始检查输入信号的下降沿。根据案例 中的设计步骤设计一个替代方案。

  1. 根据电路画出状态图和ASM图。

  2. 写出HDL代码。

  3. 依据状态图和ASM图写出HDL代码。

  4. 写出testbench并对代码仿真验证。

  5. 将代替方案替换原来的消抖电路并验证。

针对原消抖电路的响应延迟缺陷,设计替换方案:

  1. 边沿即时响应 :检测到输入sw的上升沿(0→1)时,FSM 立即进入等待状态
  2. 20ms 防抖验证:在 20ms 内持续采样输入,确认稳定后更新输出
  3. 下降沿检测:稳定后自动检测下降沿,实现双向消抖
  4. 复用 10ms 定时器:保持原 10ms 滴答脉冲作为采样使能
  5. Moore 型 FSM:输出仅由状态决定,时序稳定无毛刺
状态 编码 功能说明
ZERO 3'b000 输入稳定为 0,等待上升沿触发
WAIT1_1 3'b001 检测到上升沿,等待第 1 个 10ms 采样
WAIT1_2 3'b010 第 1 次采样稳定为 1,等待第 2 个 10ms 采样(累计 20ms)
ONE 3'b011 输入稳定为 1,等待下降沿触发
WAIT0_1 3'b100 检测到下降沿,等待第 1 个 10ms 采样
WAIT0_2 3'b101 第 1 次采样稳定为 0,等待第 2 个 10ms 采样(累计 20ms)
  • ZERO(稳定 0)
    • 检测到sw=1(上升沿)→ 立即跳转到WAIT1_1
    • sw=0 → 保持ZERO
  • WAIT1_1(等待第 1 次采样)
    • sw=0 → 跳回ZERO(抖动,取消触发)
    • sw=1m_tick=1 → 跳转到WAIT1_2
  • WAIT1_2(等待第 2 次采样,累计 20ms)
    • sw=0 → 跳回ZERO
    • sw=1m_tick=1 → 跳转到ONE(确认稳定,输出 1)
  • ONE(稳定 1)
    • 检测到sw=0(下降沿)→ 立即跳转到WAIT0_1
    • sw=1 → 保持ONE
  • WAIT0_1(等待第 1 次采样)
    • sw=1 → 跳回ONE(抖动,取消触发)
    • sw=0m_tick=1 → 跳转到WAIT0_2
  • WAIT0_2(等待第 2 次采样,累计 20ms)
    • sw=1 → 跳回ONE
    • sw=0m_tick=1 → 跳转到ZERO(确认稳定,输出 0)

10ms 定时器模块(复用原模块,无修改)

复制代码
module timer_10ms
#(
    parameter CLK_FREQ = 50_000_000  // 系统时钟50MHz
)(
    input clk,
    input rst_n,
    output reg m_tick  // 10ms脉冲,高电平1个时钟周期
);

localparam CNT_MAX = CLK_FREQ / 100 - 1;  // 50MHz → 10ms = 500_000个周期
reg [18:0] cnt;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt <= 19'd0;
        m_tick <= 1'b0;
    end
    else begin
        if(cnt == CNT_MAX) begin
            cnt <= 19'd0;
            m_tick <= 1'b1;
        end
        else begin
            cnt <= cnt + 1'b1;
            m_tick <= 1'b0;
        end
    end
end

endmodule

替换方案 FSM 消抖核心模块(边沿触发 + 20ms 防抖)

复制代码
module debounce_fsm_adv
(
    input clk,
    input rst_n,
    input sw,          // 原始按键输入
    input m_tick,      // 10ms定时器脉冲
    output reg db     // 消抖后输出
);

// ====================== 状态定义(替换方案:6状态Moore型) ======================
localparam [2:0]
    ZERO    = 3'b000,  // 输入稳定为0
    WAIT1_1 = 3'b001,  // 检测到上升沿,等待第1次10ms采样
    WAIT1_2 = 3'b010,  // 第1次采样稳定,等待第2次10ms采样(累计20ms)
    ONE     = 3'b011,  // 输入稳定为1
    WAIT0_1 = 3'b100,  // 检测到下降沿,等待第1次10ms采样
    WAIT0_2 = 3'b101;  // 第1次采样稳定,等待第2次10ms采样(累计20ms)

reg [2:0] state_reg, state_next;

// ====================== 状态寄存器:时序逻辑 ======================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        state_reg <= ZERO;
    else
        state_reg <= state_next;
end

// ====================== 次态+输出逻辑:组合逻辑(Moore型,输出仅由状态决定) ======================
always @(*) begin
    state_next = state_reg;
    db = 1'b0;  // 默认输出

    case(state_reg)
        // 稳定0态:检测到上升沿立即响应
        ZERO: begin
            db = 1'b0;
            if(sw)
                state_next = WAIT1_1;  // 0→1边沿,立即进入等待
            else
                state_next = ZERO;
        end

        // 等待第1次10ms采样
        WAIT1_1: begin
            db = 1'b0;
            if(!sw)
                state_next = ZERO;     // 抖动,返回0态
            else if(m_tick)
                state_next = WAIT1_2;  // 10ms采样稳定,进入下一级
            else
                state_next = WAIT1_1;
        end

        // 等待第2次10ms采样(累计20ms,确认稳定)
        WAIT1_2: begin
            db = 1'b0;
            if(!sw)
                state_next = ZERO;     // 抖动,返回0态
            else if(m_tick)
                state_next = ONE;      // 20ms稳定,进入1态
            else
                state_next = WAIT1_2;
        end

        // 稳定1态:检测到下降沿立即响应
        ONE: begin
            db = 1'b1;
            if(!sw)
                state_next = WAIT0_1;  // 1→0边沿,立即进入等待
            else
                state_next = ONE;
        end

        // 等待第1次10ms采样
        WAIT0_1: begin
            db = 1'b1;
            if(sw)
                state_next = ONE;      // 抖动,返回1态
            else if(m_tick)
                state_next = WAIT0_2;  // 10ms采样稳定,进入下一级
            else
                state_next = WAIT0_1;
        end

        // 等待第2次10ms采样(累计20ms,确认稳定)
        WAIT0_2: begin
            db = 1'b1;
            if(sw)
                state_next = ONE;      // 抖动,返回1态
            else if(m_tick)
                state_next = ZERO;     // 20ms稳定,进入0态
            else
                state_next = WAIT0_2;
        end

        // 异常状态:返回初始0态
        default: begin
            state_next = ZERO;
            db = 1'b0;
        end
    endcase
end

endmodule

顶层整合模块(替换原消抖电路)

复制代码
module debounce_adv_top
(
    input clk,          // 50MHz系统时钟
    input rst_n,        // 异步复位,低电平有效
    input sw,           // 原始按键输入
    output db           // 消抖后输出
);

wire m_tick;

// 实例化10ms定时器
timer_10ms u_timer
(
    .clk(clk),
    .rst_n(rst_n),
    .m_tick(m_tick)
);

// 实例化改进型FSM消抖模块
debounce_fsm_adv u_fsm
(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw),
    .m_tick(m_tick),
    .db(db)
);

endmodule

Testbench 仿真代码

复制代码
`timescale 1ns / 1ps
module tb_debounce_adv();

reg clk;
reg rst_n;
reg sw;
wire db;

// 实例化顶层模块
debounce_adv_top uut
(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw),
    .db(db)
);

// 生成50MHz时钟(周期20ns)
initial begin
    clk = 1'b0;
    forever #10 clk = ~clk;
end

// 仿真激励:模拟按键抖动(20ms内抖动,20ms后稳定)
initial begin
    // 初始化
    rst_n = 1'b0;
    sw = 1'b0;
    #100;
    rst_n = 1'b1;
    #100;

    // 1. 模拟按键按下(0→1),带抖动(10ms内抖动3次)
    sw = 1'b1; #5_000_000;  // 5ms高电平
    sw = 1'b0; #1_000_000;  // 1ms低电平(抖动)
    sw = 1'b1; #4_000_000;  // 4ms高电平
    // 此时累计10ms,sw稳定为1,进入WAIT1_2
    #10_000_000;  // 再等待10ms(累计20ms),db输出1

    // 2. 模拟按键松开(1→0),带抖动
    sw = 1'b0; #5_000_000;  // 5ms低电平
    sw = 1'b1; #1_000_000;  // 1ms高电平(抖动)
    sw = 1'b0; #4_000_000;  // 4ms低电平
    // 此时累计10ms,sw稳定为0,进入WAIT0_2
    #10_000_000;  // 再等待10ms(累计20ms),db输出0

    #100_000_000;
    $finish;
end

endmodule
特性 原消抖电路 替换方案
边沿响应 需等待 3 次 10ms 采样,响应延迟 30ms 检测到边沿立即进入等待,无初始延迟
防抖时间 30ms(3 次 10ms) 20ms(2 次 10ms),满足题目要求
状态数量 8 个 6 个,资源占用更少
输出逻辑 Moore 型,稳定 Moore 型,稳定无毛刺
适用场景 对响应速度要求不高 对按键响应速度要求高的场景

停车场计数器

  • 车辆检测逻辑
    • 车辆进入:00 → 10 → 11 → 01 → 00,产生enter脉冲(1 个周期高电平)
    • 车辆离开:00 → 01 → 11 → 10 → 00,产生exit脉冲(1 个周期高电平)
  • FSM 模块 :2 输入a/b,2 输出enter/exit
  • 计数器模块 :2 输入inc/dec,输出当前停车数,支持加减计数
  • 消抖模块:用按键代替光电传感器,消除机械抖动
  • LED / 数码管显示模块:复用显示当前停车数

车辆检测 FSM 模块(park_fsm.v

复制代码
module park_fsm(
    input clk,
    input rst_n,
    input a,          // 传感器a输入(消抖后)
    input b,          // 传感器b输入(消抖后)
    output reg enter, // 车辆进入脉冲(1周期高电平)
    output reg exit   // 车辆离开脉冲(1周期高电平)
);

// 状态定义
localparam [2:0]
    S_IDLE = 3'b000,  // 初始态:ab=00
    S_A    = 3'b001,  // 进入态1:ab=10(仅a被遮挡)
    S_AB   = 3'b010,  // 进入态2:ab=11(a、b均被遮挡)
    S_B    = 3'b011,  // 进入态3:ab=01(仅b被遮挡)
    S_B_EX = 3'b100,  // 离开态1:ab=01(仅b被遮挡)
    S_AB_EX= 3'b101,  // 离开态2:ab=11(a、b均被遮挡)
    S_A_EX = 3'b110;  // 离开态3:ab=10(仅a被遮挡)

reg [2:0] state_reg, state_next;

// 状态寄存器(时序逻辑)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        state_reg <= S_IDLE;
    else
        state_reg <= state_next;
end

// 次态+输出逻辑(组合逻辑)
always @(*) begin
    state_next = state_reg;
    enter = 1'b0;
    exit  = 1'b0;

    case(state_reg)
        // 初始态00
        S_IDLE: begin
            case({a,b})
                2'b10: state_next = S_A;     // 进入序列:10
                2'b01: state_next = S_B_EX;   // 离开序列:01
                default: state_next = S_IDLE;
            endcase
        end

        // 进入序列:10 → 11
        S_A: begin
            case({a,b})
                2'b11: state_next = S_AB;
                2'b00: state_next = S_IDLE;  // 非法,回初始
                default: state_next = S_A;
            endcase
        end

        // 进入序列:11 → 01
        S_AB: begin
            case({a,b})
                2'b01: state_next = S_B;
                2'b10: state_next = S_A;     // 回退
                default: state_next = S_AB;
            endcase
        end

        // 进入序列:01 → 00(完整进入,产生enter脉冲)
        S_B: begin
            case({a,b})
                2'b00: begin
                    state_next = S_IDLE;
                    enter = 1'b1;  // 1周期高电平
                end
                2'b11: state_next = S_AB;     // 回退
                default: state_next = S_B;
            endcase
        end

        // 离开序列:01 → 11
        S_B_EX: begin
            case({a,b})
                2'b11: state_next = S_AB_EX;
                2'b00: state_next = S_IDLE;  // 非法,回初始
                default: state_next = S_B_EX;
            endcase
        end

        // 离开序列:11 → 10
        S_AB_EX: begin
            case({a,b})
                2'b10: state_next = S_A_EX;
                2'b01: state_next = S_B_EX;   // 回退
                default: state_next = S_AB_EX;
            endcase
        end

        // 离开序列:10 → 00(完整离开,产生exit脉冲)
        S_A_EX: begin
            case({a,b})
                2'b00: begin
                    state_next = S_IDLE;
                    exit = 1'b1;  // 1周期高电平
                end
                2'b11: state_next = S_AB_EX;   // 回退
                default: state_next = S_A_EX;
            endcase
        end

        default: state_next = S_IDLE;
    endcase
end

endmodule

停车场计数器模块(park_counter.v

复制代码
module park_counter(
    input clk,
    input rst_n,
    input inc,       // 计数+1(enter脉冲)
    input dec,       // 计数-1(exit脉冲)
    output reg [3:0] cnt  // 4位计数(0-15,可扩展)
);

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 4'd0;
    else begin
        // 仅enter有效,+1
        if(inc && !dec)
            cnt <= cnt + 1'b1;
        // 仅exit有效,-1(防0下溢)
        else if(dec && !inc)
            cnt <= (cnt == 4'd0) ? 4'd0 : (cnt - 1'b1);
        // inc和dec同时有效,不计数
    end
end

endmodule

10ms 定时器模块(timer_10ms.v,消抖用)

复制代码
module timer_10ms
#(
    parameter CLK_FREQ = 50_000_000  // 系统时钟50MHz
)(
    input clk,
    input rst_n,
    output reg m_tick  // 10ms脉冲,高电平1个时钟周期
);

localparam CNT_MAX = CLK_FREQ / 100 - 1;  // 50MHz → 10ms = 500_000个周期
reg [18:0] cnt;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt <= 19'd0;
        m_tick <= 1'b0;
    end
    else begin
        if(cnt == CNT_MAX) begin
            cnt <= 19'd0;
            m_tick <= 1'b1;
        end
        else begin
            cnt <= cnt + 1'b1;
            m_tick <= 1'b0;
        end
    end
end

endmodule

按键消抖 FSM 模块(debounce_fsm.v

复制代码
module debounce_fsm(
    input clk,
    input rst_n,
    input sw,          // 原始按键输入
    input m_tick,      // 10ms定时器脉冲
    output reg db     // 消抖后输出
);

localparam [2:0]
    zero    = 3'b000,
    wait1_1 = 3'b001,
    wait1_2 = 3'b010,
    wait1_3 = 3'b011,
    one     = 3'b100,
    wait0_1 = 3'b101,
    wait0_2 = 3'b110,
    wait0_3 = 3'b111;

reg [2:0] state_reg, state_next;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        state_reg <= zero;
    else
        state_reg <= state_next;
end

always @(*) begin
    state_next = state_reg;
    db = 1'b0;

    case(state_reg)
        zero: begin db=1'b0; if(sw) state_next=wait1_1; end
        wait1_1: begin db=1'b0; if(~sw) state_next=zero; else if(m_tick) state_next=wait1_2; end
        wait1_2: begin db=1'b0; if(~sw) state_next=zero; else if(m_tick) state_next=wait1_3; end
        wait1_3: begin db=1'b0; if(~sw) state_next=zero; else if(m_tick) state_next=one; end
        one: begin db=1'b1; if(~sw) state_next=wait0_1; end
        wait0_1: begin db=1'b1; if(sw) state_next=one; else if(m_tick) state_next=wait0_2; end
        wait0_2: begin db=1'b1; if(sw) state_next=one; else if(m_tick) state_next=wait0_3; end
        wait0_3: begin db=1'b1; if(sw) state_next=one; else if(m_tick) state_next=zero; end
        default: begin state_next=zero; db=1'b0; end
    endcase
end

endmodule

数码管显示模块(seg7_display.v,LED 复用显示)

复制代码
module seg7_display(
    input clk,
    input rst_n,
    input [3:0] data,  // 4位计数输入(0-15)
    output reg [7:0] seg  // 8段数码管输出(a~g+小数点,共阳极)
);

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        seg <= 8'hc0;  // 0的共阳极编码
    else begin
        case(data)
            4'h0: seg <= 8'hc0;
            4'h1: seg <= 8'hf9;
            4'h2: seg <= 8'ha4;
            4'h3: seg <= 8'hb0;
            4'h4: seg <= 8'h99;
            4'h5: seg <= 8'h92;
            4'h6: seg <= 8'h82;
            4'h7: seg <= 8'hf8;
            4'h8: seg <= 8'h80;
            4'h9: seg <= 8'h90;
            4'ha: seg <= 8'h88;
            4'hb: seg <= 8'h83;
            4'hc: seg <= 8'hc6;
            4'hd: seg <= 8'ha1;
            4'he: seg <= 8'h86;
            4'hf: seg <= 8'h8e;
        endcase
    end
end

endmodule

顶层整合模块(park_top.v,完整系统)

复制代码
module park_top(
    input clk,          // 50MHz系统时钟
    input rst_n,        // 异步复位,低电平有效
    input sw_a,         // 按键a(代替传感器a)
    input sw_b,         // 按键b(代替传感器b)
    output [7:0] seg,   // 数码管输出
    output enter_led,   // 进入指示灯
    output exit_led     // 离开指示灯
);

// 内部信号
wire m_tick;
wire a_db, b_db;
wire enter, exit;
wire [3:0] cnt;

// 1. 10ms定时器
timer_10ms u_timer(
    .clk(clk),
    .rst_n(rst_n),
    .m_tick(m_tick)
);

// 2. 按键a消抖
debounce_fsm u_deb_a(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw_a),
    .m_tick(m_tick),
    .db(a_db)
);

// 3. 按键b消抖
debounce_fsm u_deb_b(
    .clk(clk),
    .rst_n(rst_n),
    .sw(sw_b),
    .m_tick(m_tick),
    .db(b_db)
);

// 4. 车辆检测FSM
park_fsm u_fsm(
    .clk(clk),
    .rst_n(rst_n),
    .a(a_db),
    .b(b_db),
    .enter(enter),
    .exit(exit)
);

// 5. 停车场计数器
park_counter u_counter(
    .clk(clk),
    .rst_n(rst_n),
    .inc(enter),
    .dec(exit),
    .cnt(cnt)
);

// 6. 数码管显示
seg7_display u_seg(
    .clk(clk),
    .rst_n(rst_n),
    .data(cnt),
    .seg(seg)
);

// 7. 指示灯输出
assign enter_led = enter;
assign exit_led  = exit;

endmodule

Testbench 仿真代码(tb_park_top.v

复制代码
`timescale 1ns / 1ps
module tb_park_top();

reg clk;
reg rst_n;
reg sw_a, sw_b;
wire [7:0] seg;
wire enter_led, exit_led;

// 实例化顶层模块
park_top uut(
    .clk(clk),
    .rst_n(rst_n),
    .sw_a(sw_a),
    .sw_b(sw_b),
    .seg(seg),
    .enter_led(enter_led),
    .exit_led(exit_led)
);

// 生成50MHz时钟(周期20ns)
initial begin
    clk = 1'b0;
    forever #10 clk = ~clk;
end

// 仿真激励:模拟车辆进出
initial begin
    // 初始化
    rst_n = 1'b0;
    sw_a = 1'b0;
    sw_b = 1'b0;
    #100;
    rst_n = 1'b1;
    #100;

    // 1. 模拟车辆进入:00→10→11→01→00
    #10_000_000;  // 等待消抖稳定
    sw_a = 1'b1; #10_000_000;  // 10
    sw_b = 1'b1; #10_000_000;  // 11
    sw_a = 1'b0; #10_000_000;  // 01
    sw_b = 1'b0; #10_000_000;  // 00(enter脉冲产生,计数+1)

    // 2. 模拟车辆离开:00→01→11→10→00
    #10_000_000;
    sw_b = 1'b1; #10_000_000;  // 01
    sw_a = 1'b1; #10_000_000;  // 11
    sw_b = 1'b0; #10_000_000;  // 10
    sw_a = 1'b0; #10_000_000;  // 00(exit脉冲产生,计数-1)

    #100_000_000;
    $finish;
end

endmodule
模块 功能 对应题目要求
park_fsm.v 车辆进出检测 FSM 步骤 1:2 输入 2 输出 FSM
park_counter.v 停车场计数器 步骤 3:inc/dec 控制的加减计数器
timer_10ms.v + debounce_fsm.v 按键消抖 替换光电传感器,消除抖动
seg7_display.v 数码管显示 LED 复用显示当前停车数
park_top.v 顶层整合 完整系统集成

功能验证

  • 车辆进入 :按a→b顺序按下按键,enter_led点亮,计数 + 1,数码管显示对应数值
  • 车辆离开 :按b→a顺序按下按键,exit_led点亮,计数 - 1,数码管显示对应数值
  • 消抖功能:按键抖动不影响计数,仅稳定输入才会触发状态跳转
  • 防下溢:计数为 0 时,多次按离开键,计数保持 0,不会出现负数
相关推荐
Terasic友晶科技16 分钟前
【教程】Agilex 5 SoC 的两种启动模式的开发演示(一)(FPGA Configuration First)
fpga开发·agilex 5 soc
ALINX技术博客20 分钟前
【黑金云课堂】FPGA技术教程Vitis开发:SD卡WAV文件读取音频播放实验
fpga开发·音视频
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner3 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20203 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子4 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi