Verilog实战:从零开始手把手教你实现寄存器、触发器和锁存器
如果你刚开始接触数字电路设计,面对Verilog里那些听起来差不多的“存储单元”——寄存器、触发器、锁存器,是不是感觉有点懵?它们好像都能存数据,但写起代码来又各有各的规矩。别担心,这篇文章就是为你准备的。我们不谈枯燥的理论区别,直接打开编辑器,一行行代码敲下去,在仿真波形里亲眼看看它们到底是怎么“干活”的。我会假设你有一个基础的FPGA开发环境(比如Vivado、Quartus,甚至是开源的Icarus Verilog + GTKWave),跟着步骤走,你不仅能写出正确的代码,更能理解背后的“为什么”,以及如何避开那些新手常踩的坑。准备好了吗?让我们从最基础的触发器开始,一步步构建起你的存储单元知识体系。
1. 基石:从D触发器开始理解时序逻辑
在数字世界的记忆体里,D触发器是最小、最核心的存储单元。你可以把它想象成一个极其守时的哨兵:只有在时钟信号发出明确指令(比如上升沿)的瞬间,它才会看一眼当前的输入数据D,并将其牢牢记住,直到下一个指令到来。这种“边沿触发”的特性,是构建一切稳定时序系统的根基。
1.1 编写你的第一个D触发器
让我们先抛开所有高级功能,实现一个最纯净的、带异步复位的D触发器。所谓异步复位,意味着无论时钟在做什么,只要复位信号有效,输出就必须立刻被清零,这为系统提供了一个确定的初始状态。
module d_flip_flop_basic (
input wire clk, // 时钟信号
input wire rst_n, // 低电平有效的异步复位信号
input wire d, // 数据输入
output reg q // 数据输出
);
// 核心逻辑:always块对时钟上升沿和复位下降沿敏感
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 复位有效时,无条件将输出清零
q <= 1'b0;
end else begin
// 在时钟上升沿,将输入d的值锁存到输出q
q <= d;
end
end
endmodule
注意:这里使用了
<=非阻塞赋值。在描述时序逻辑时,务必使用非阻塞赋值,这能确保在时钟沿触发的瞬间,所有触发器并行更新,模拟真实的硬件行为。使用阻塞赋值=会导致不可预测的仿真结果和综合后的电路功能错误。
这个模块虽然简单,但包含了时序逻辑描述的所有关键要素:敏感列表、边沿检测、条件判断。在仿真中,你会看到 q 只在 clk 的上升沿且 rst_n 为高时,才变化为 d 的值。
1.2 添加同步使能信号
在实际项目中,我们经常需要控制触发器是否在某个时钟周期更新数据,这就需要引入同步使能信号。它不像复位那样具有“最高优先级”,其生效完全依赖于时钟边沿。
module d_flip_flop_with_en (
input wire clk,
input wire rst_n,
input wire en, // 同步使能信号,高有效
input wire d,
output reg q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end else if (en) begin // 使能信号有效时,才更新数据
q <= d;
end
// 如果en为0,则q保持原值,无需显式写出
end
endm


960

被折叠的 条评论
为什么被折叠?



