在数字逻辑设计与FPGA开发中,时序控制是永恒的核心主题。我们经常遇到这样的需求:当一个外部激励信号(如按键按下、中断请求)到来时,我们并不希望立即执行操作,而是希望“等一等”——比如等待外部芯片电源稳定、等待数据建立时间结束,或者是为了规避毛刺信号。
这时候,一个精准、可配置的脉冲延迟模块就显得尤为重要。今天我们就通过一个经典的Verilog实战案例,来深入讲解如何实现一个“可编程脉冲延迟器”。
一、 模块功能与接口定义
我们设计的模块名为 pulse_delay,其核心功能非常直观:将输入的脉冲信号 i_single 延迟 i_delay_cnt+1 个时钟周期后输出。
1. 接口说明
i_clk:系统时钟,模块的心脏。
i_single:输入脉冲信号,通常持续一个时钟周期。
i_delay_cnt:延迟周期数,这是一个配置端口,决定了输出“慢几拍”。
o_delay_single:延迟后的输出脉冲。
2. 核心设计亮点
该模块具备两个非常重要的特性:
延迟值为0的处理:代码中明确判断了 if (i_delay_cnt == 0)。这意味着如果配置为0,输入脉冲会延迟同一时钟周期直接输出。
防抖动/防重叠机制:代码中引入了一个 busy 状态信号。当模块正在执行延迟计数时,如果此时又来了一个新的输入脉冲,模块会直接忽略。这保证了每一次延迟过程的完整性,避免了计数器逻辑混乱。
二、 设计原理解析
模块内部维护了一个计数器 cnt 和一个忙标志位 busy。整个状态机可以简化为三个阶段:
监测阶段:利用 single_d 寄存器对 i_single 进行打拍操作,通过 i_single && !single_d 精准捕捉输入脉冲的上升沿。
计数阶段:一旦捕捉到脉冲,若 i_delay_cnt 不为0,则加载计数器并拉高 busy信号。在接下来的时钟周期里,计数器开始倒数。
输出阶段:当计数器倒数到1时,模块拉高 o_delay_single 一个周期,同时清零 busy 状态,等待下一次触发。 这种设计利用了“时间换空间”的思想,仅用少量的寄存器资源就实现了任意长度的延迟功能。
三、 典型应用场景
这个模块虽小,但在实际工程中却有着广泛的应用价值:
1. 复位延迟启动
在多芯片级联系统中,上电复位往往需要遵循特定顺序。例如,主电源建立后,需要等待100ms才能释放FPGA的逻辑复位。我们可以将 i_single 接至上电完成信号,i_delay_cnt 设为目标计数值,即可生成一个精准的延时复位脉冲。
2. 规避亚稳态与毛刺
在按键消抖或处理异步信号时,延迟是常用的手段。如果已知干扰信号只会持续几个周期,我们可以通过延迟输出,避过这段“危险期”,确保输出信号的稳定性。
3. 总线时序适配
在与SRAM、ADC等外设通信时,往往需要等待若干个时钟周期的建立时间。通过此模块,可以灵活地产生读/写使能信号的延迟版本,从而满足芯片手册的时序要求。
四、 仿真验证与代码分析
为了验证模块的正确性,测试平台编写了六个典型的测试用例。
1. 延迟为0的极限测试
// Test 1: delay_cnt = 0
i_delay_cnt = 0;
send_pulse();
在 tb_pulse_delay 中,测试1将延迟值设为0。仿真结果显示,当 i_single 拉高时,o_delay_single 在同一时钟上升沿立即跟随拉高。这验证了模块对边界条件的处理能力,非常关键。
2. 常规延迟功能测试
测试3和测试4分别设置了延迟5和10个周期。波形清晰显示,当输入脉冲到来后,计数器 cnt 开始倒数,当计数器归零(或到1)时,输出脉冲准确出现。这证明了计数器逻辑的准确性。
3. “忙”状态下的冲突测试
// Test 5: busy 期间再来脉冲(应被忽略)
i_delay_cnt = 5;
send_pulse(); // 触发第一次延迟
repeat(2) @(posedge clk);
i_single = 1'b1; // 此时计数器还在工作
在测试5中,我们在模块正在执行“延迟5个周期”的任务中途,故意发送了一个干扰脉冲。查看 $monitor 监控输出,我们可以发现:虽然 i_single 出现了跳变,但由于内部 busy 标志位为高,模块拒绝响应这一新脉冲。这种“原子性”保护机制,防止了在实际工程中因信号抖动导致的逻辑错误,极大地提高了系统的鲁棒性。
五、源码获取
通过网盘分享的文件:pulse_delay.rar
链接: https://pan.baidu.com/s/1YtmxQvV16ESLxTpfqKXFyg?pwd=8heg 提取码: 8heg
--来自百度网盘超级会员v4的分享
我是小灰灰的FPGA,关注我,带你从入门到精通数字逻辑设计。
195