SoC设计
边沿检测器
入门级数字设计练习:边沿检测器
SOC 入门 边沿检测
边沿检测器 完整练习
1. 模块功能说明
边沿检测器检测信号的跳变瞬间,产生一个时钟周期的脉冲。
应用场景:
- 按键按下检测(只响应一次)
- 协议起始位检测
- 数据有效信号捕获
- 定时器溢出检测
2. 原理图解
输入信号: ────┐ ┌────
└──────────────┘
↑ 下降沿 ↑ 上升沿
寄存器延迟: ──────────┐ ┌────
(d1) └──────────────┘
↑ 延迟1周期
再延迟: ────────────┐ ┌──
(d2) └──────────────┘
上升沿脉冲: ──────┐ ┌─────────────────────
└──┘
↑ d1高 & d2低 = 上升沿
下降沿脉冲: ──────────────────┐ ┌──────
└──┘
↑ d1低 & d2高 = 下降沿
3. 完整代码
edge_detector.sv
//=============================================================================
// Module: edge_detector
// Description: 边沿检测器 - 检测信号上升沿、下降沿、任意沿
pubDate: 2025-01-01
?n// Author: 学习笔记
// Date: 2026-07-13
//=============================================================================
module edge_detector (
input logic clk, // 系统时钟
input logic rst_n, // 异步复位(低有效)
input logic signal_in, // 输入信号
output logic pos_edge, // 上升沿脉冲(1个时钟周期)
output logic neg_edge, // 下降沿脉冲(1个时钟周期)
output logic any_edge // 任意沿脉冲(1个时钟周期)
);
//=========================================================================
// 信号声明
//=========================================================================
logic signal_d1; // 第一级寄存器(同步)
logic signal_d2; // 第二级寄存器(边沿检测)
//=========================================================================
// 两级寄存器同步 + 边沿检测
//=========================================================================
// 为什么需要两级?
// 1. 第一级:将异步信号同步到本地时钟域(可能产生亚稳态)
// 2. 第二级:提供稳定的信号用于边沿检测
//=========================================================================
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
signal_d1 <= 1'b0; // 复位时清零
signal_d2 <= 1'b0;
end else begin
signal_d1 <= signal_in; // 第一级:采样输入
signal_d2 <= signal_d1; // 第二级:延迟一拍
end
end
//=========================================================================
// 边沿检测组合逻辑
//=========================================================================
// 上升沿:当前为1,上一拍为0 → 0→1的跳变
// 下降沿:当前为0,上一拍为1 → 1→0的跳变
// 任意沿:上升沿 或 下降沿
//=========================================================================
assign pos_edge = signal_d1 & ~signal_d2; // 1 & ~0 = 上升沿
assign neg_edge = ~signal_d1 & signal_d2; // ~1 & 1 = 下降沿
assign any_edge = pos_edge | neg_edge; // 任意沿
endmodule
4. Testbench
tb_edge_detector.sv
//=============================================================================
// Testbench: tb_edge_detector
// Description: 边沿检测器测试平台
pubDate: 2025-01-01
?n//=============================================================================
`timescale 1ns / 1ps
module tb_edge_detector;
//=========================================================================
// 参数
//=========================================================================
parameter CLK_PERIOD = 10; // 时钟周期 10ns = 100MHz
//=========================================================================
// 信号
//=========================================================================
logic clk;
logic rst_n;
logic signal_in;
logic pos_edge;
logic neg_edge;
logic any_edge;
//=========================================================================
// 时钟生成
//=========================================================================
initial clk = 0;
always #(CLK_PERIOD/2) clk = ~clk; // 50% 占空比
//=========================================================================
// 实例化被测模块
//=========================================================================
edge_detector uut (
.clk (clk),
.rst_n (rst_n),
.signal_in (signal_in),
.pos_edge (pos_edge),
.neg_edge (neg_edge),
.any_edge (any_edge)
);
//=========================================================================
// 测试激励
//=========================================================================
initial begin
// 初始化
rst_n = 0;
signal_in = 0;
// 等待复位释放
#25;
rst_n = 1;
#10;
$display("=== 边沿检测器测试开始 ===");
$display("时间=%0t: 初始状态 signal_in=0", $time);
// 测试 1: 单次上升沿
#20;
signal_in = 1;
$display("时间=%0t: signal_in → 1 (上升沿)", $time);
#10;
if (pos_edge) $display(" ✓ 检测到上升沿");
else $display(" ✗ 未检测到上升沿");
// 测试 2: 单次下降沿
#20;
signal_in = 0;
$display("时间=%0t: signal_in → 0 (下降沿)", $time);
#10;
if (neg_edge) $display(" ✓ 检测到下降沿");
else $display(" ✗ 未检测到下降沿");
// 测试 3: 快速脉冲
#20;
signal_in = 1;
$display("时间=%0t: 快速脉冲开始", $time);
#10;
signal_in = 0;
#10;
signal_in = 1;
#10;
signal_in = 0;
#10;
$display("时间=%0t: 快速脉冲结束", $time);
// 测试 4: 长保持
#30;
signal_in = 1;
$display("时间=%0t: 长保持开始", $time);
#100;
signal_in = 0;
$display("时间=%0t: 长保持结束", $time);
#10;
// 测试 5: 连续变化
repeat(10) begin
#15;
signal_in = ~signal_in;
end
#50;
$display("\n=== 边沿检测器测试结束 ===");
$finish;
end
//=========================================================================
// 波形输出
//=========================================================================
initial begin
$dumpfile("wave_edge_detector.vcd");
$dumpvars(0, tb_edge_detector);
end
//=========================================================================
// 自动检测边沿并打印
//=========================================================================
always @(posedge clk) begin
if (pos_edge)
$display("时间=%0t: >>> 上升沿检测到 <<<", $time);
if (neg_edge)
$display("时间=%0t: >>> 下降沿检测到 <<<", $time);
end
endmodule
5. 仿真波形分析
运行仿真后,观察以下关键点:
理想波形分析:
clk ──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──
└──┘ └──┘ └──┘ └──┘ └──┘ └──┘
signal_in ──────────┐ ┌───────
└────────────────────┘
↑ 上升沿 ↑ 下降沿
signal_d1 ────────────┐ ┌─────
(延迟1拍) └────────────────────┘
signal_d2 ──────────────┐ ┌───
(延迟2拍) └────────────────────┘
pos_edge ────────┐ ┌────────────────────────────
└──┘
↑ 上升沿脉冲(1个时钟周期)
neg_edge ────────────────────────────┐ ┌───────
└──┘
↑ 下降沿脉冲
6. 练习任务
任务 1:基础验证(必做)
# 使用 Icarus Verilog 仿真
iverilog -o tb_edge_detector tb_edge_detector.sv edge_detector.sv
vvp tb_edge_detector
gtkwave wave_edge_detector.vcd
观察并记录:
- 上升沿脉冲是否为 1 个时钟周期?
- 下降沿脉冲是否为 1 个时钟周期?
- 脉冲出现的时机是否正确?
任务 2:功能扩展(选做)
修改代码,添加以下功能:
// 1. 添加使能端
input logic enable, // 使能信号
// 2. 添加去抖功能
parameter DEBOUNCE_EN = 0, // 是否启用去抖
parameter DEBOUNCE_CNT = 3 // 去抖计数
任务 3:多信号检测(挑战)
创建一个多通道边沿检测器:
module edge_detector_multi #(
parameter WIDTH = 4 // 检测 4 路信号
)(
input logic [WIDTH-1:0] signal_in,
output logic [WIDTH-1:0] pos_edge,
output logic [WIDTH-1:0] neg_edge
);
任务 4:写一个完整的 Testbench(必做)
要求:
- 至少 10 个测试用例
- 覆盖所有功能
- 使用
$display打印测试结果 - 添加
$dumpfile波形输出
7. 常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
| 边沿检测不到 | 忘记复位 | 确保 rst_n 正确释放 |
| 脉冲宽度不对 | 寄存器级数错误 | 确保两级寄存器 |
| 亚稳态 | 直接采样异步信号 | 使用两级同步器 |
8. 知识点总结
为什么是两级寄存器?
一级寄存器(不推荐):
signal_in ──→ [DFF] ──→ signal_d1
↓
边沿检测
问题:如果 signal_in 在时钟沿附近变化,
signal_d1 可能进入亚稳态(输出不稳定)
两级寄存器(推荐):
signal_in ──→ [DFF] ──→ [DFF] ──→ signal_d2
↑ ↑
第一级 第二级
(可能亚稳) (稳定)
即使第一级进入亚稳态,第二级有整个时钟周期
来稳定输出,大大降低亚稳态传播概率
亚稳态 MTBF
MTBF (Mean Time Between Failures) = T / (f_clk × f_data)
T: 稳定时间(通常 > 1 个时钟周期)
f_clk: 采样时钟频率
f_data: 数据变化频率
两级同步器 MTBF 可达数年,足够可靠
9. 扩展阅读
- CDC (Clock Domain Crossing) - 跨时钟域设计
- Metastability - 亚稳态原理
- MTBF Calculation - 平均无故障时间计算
完成本练习后,你应该掌握:
- 寄存器延迟原理
- 边沿检测电路设计
- Testbench 编写方法
- 仿真工具使用