有限状态机可以实现对外部激励和当前状态的有效相应机制 ,在数字通信、自动化控制、指令 集设计等方面均有重要作用,应用广泛。当前的程序化设计方法使得有限状态机的设计得以极 大简化,从过去数字电路教学中较复杂的设计内容演变为基础教学内容,提升了设计效率,这 也符合行业对于数字电路系统设计的需求
目录
[序列检测器核心模块 moore_seq_detector.v](#序列检测器核心模块 moore_seq_detector.v)
[Testbench 仿真文件 tb_moore_seq_detector.v](#Testbench 仿真文件 tb_moore_seq_detector.v)
[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 型特性)
[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 路径)
[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:检测到1s2:检测到11s3:检测到110s4:检测到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:检测到1s2:检测到11s3:检测到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位并行数据D[7: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~IN7由ADDA/ADDB/ADDC(3 位地址)选择- 控制信号:
ALE:地址锁存,上升沿有效,锁存通道选择信号START:转换启动,高电平有效,启动后EOC变低EOC:转换结束标志,转换中为低,结束后变高OE:输出使能,高电平有效,使能后D[7:0]输出转换结果- 转换时间:典型 100μs,需状态机等待转换完成
状态机设计(4 状态)
状态 功能 转移条件 s0初始化 / 空闲 时钟上升沿 → s1s1启动 ADC(拉高 ALE+START)时钟上升沿 → s2s2等待转换结束(检测 EOC高电平)EOC=1→s3,否则保持s2s3读取转换结果(拉高 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 | 使能输出,锁存转换结果 |
通道选择
- 默认固定选择
IN0(addr=3'b000),可在s0状态添加通道循环逻辑,实现 8 通道自动扫描 - 通道对应关系:
addr=3'b000→IN0,3'b001→IN1,...,3'b111→IN7
| FPGA 引脚 | ADC0809 引脚 | 功能 |
|---|---|---|
| clk | - | 50MHz 系统时钟 |
| rst_n | - | 复位按键 |
| eoc | EOC | 转换结束信号输入 |
| d[7:0] | D[7:0] | 8 位数据输入 |
| ale | ALE | 地址锁存输出 |
| start | START | 转换启动输出 |
| oe | OE | 输出使能输出 |
| addr[2:0] | ADDA/ADDB/ADDC | 通道地址输出 |
| an[3:0]/sseg[7:0] | 数码管 | 显示转换结果(电压值) |
- 给 ADC0809 供电(+5V),参考电压接 + 5V
- 在
IN0输入 0~5V 模拟电压,下载程序到 FPGA - 观察数码管显示:
0.0V~5.0V,随输入电压线性变化 - 验证状态机时序:用示波器观测
ALE/START/EOC/OE的时序关系
关键优化与扩展
- 时序优化
状态机采用三段式设计,时序收敛性好,无亚稳态风险
适配 50MHz 时钟,转换周期约 400μs(4 个时钟周期 + 100μs 转换时间),满足 ADC 时序要求
- 功能扩展
8 通道循环扫描 :在
s0状态添加addr自增逻辑,实现 8 通道自动轮流转换按键切换通道:添加按键输入,手动选择转换通道
串口输出:添加 UART 模块,将 ADC 结果上传到 PC
滤波处理:添加数字滤波(如滑动平均),提高采样稳定性
- 常见问题排查
转换结果错误 :检查
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_1wait1_1:等待 10ms 脉冲,若 sw 保持 1 →wait1_2;若 sw 变回 0 → 回到zero(抖动滤除)wait1_2:再等 10ms,累计 20ms,sw 保持 1 →wait1_3;否则回zerowait1_3:再等 10ms,累计 30ms,sw 保持 1 → 进入one,输出 db=1
one → zero 路径
one:sw=1,若检测到 sw=0 → 进入wait0_1wait0_1:等待 10ms 脉冲,若 sw 保持 0 →wait0_2;若 sw 变回 1 → 回到one(抖动滤除)wait0_2:再等 10ms,累计 20ms,sw 保持 0 →wait0_3;否则回onewait0_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 输出(观察消抖效果) |
验证步骤
-
下载程序到 FPGA,按下机械按键,观察 LED:按键按下后,LED 延迟约 20ms 点亮,无闪烁
-
松开按键,LED 延迟约 20ms 熄灭,无闪烁
-
用示波器观测
sw和db的波形,验证抖动被滤除,输出稳定
关键优化
三段式状态机:状态寄存器、次态逻辑、输出逻辑分离,时序收敛性好
定时器参数化 :可通过修改
CLK_FREQ适配不同时钟频率,无需修改核心逻辑鲁棒性 :添加
default分支,避免锁存器,异常状态自动回到zero可扩展性:可修改等待次数(如 2 次 10ms=20ms),调整消抖时间
序列检测器进阶
在序列检测器的基础上,当收到的脉冲序列数据为0时,则记录下后八位数据
-
设计一个基于Moore型的电路并画出状态图和ASM图。
-
依据状态图和ASM图写出HDL代码。
-
写出testbench并且对代码进行仿真验证。
-
设计一个基于Mealy型,重复第一第四步。
- 输入 :串行数据流
din- 同时做两件事 :
- 持续检测序列
1101,检测到则detect = 1- 持续检测起始位
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时间内的输入消除抖动,在这个过程之后系统开始检查输入信号的下降沿。根据案例 中的设计步骤设计一个替代方案。
-
根据电路画出状态图和ASM图。
-
写出HDL代码。
-
依据状态图和ASM图写出HDL代码。
-
写出testbench并对代码仿真验证。
-
将代替方案替换原来的消抖电路并验证。
针对原消抖电路的响应延迟缺陷,设计替换方案:
- 边沿即时响应 :检测到输入
sw的上升沿(0→1)时,FSM 立即进入等待状态- 20ms 防抖验证:在 20ms 内持续采样输入,确认稳定后更新输出
- 下降沿检测:稳定后自动检测下降沿,实现双向消抖
- 复用 10ms 定时器:保持原 10ms 滴答脉冲作为采样使能
- 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=1且m_tick=1→ 跳转到WAIT1_2
- WAIT1_2(等待第 2 次采样,累计 20ms) :
sw=0→ 跳回ZEROsw=1且m_tick=1→ 跳转到ONE(确认稳定,输出 1)
- ONE(稳定 1) :
- 检测到
sw=0(下降沿)→ 立即跳转到WAIT0_1 sw=1→ 保持ONE
- 检测到
- WAIT0_1(等待第 1 次采样) :
sw=1→ 跳回ONE(抖动,取消触发)sw=0且m_tick=1→ 跳转到WAIT0_2
- WAIT0_2(等待第 2 次采样,累计 20ms) :
sw=1→ 跳回ONEsw=0且m_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,不会出现负数







