Quick Start
- 打开 Vivado(或 Quartus),创建新工程,选择目标器件(如 XC7A35T)。
- 新建一个 Verilog 源文件,命名为
top.v。 - 编写一个简单的计数器模块:包含时钟输入
clk、复位rst_n和输出cnt[3:0]。 - 在
always @(posedge clk or negedge rst_n)块中实现时序逻辑:if (!rst_n) cnt <= 4'd0; else cnt <= cnt + 1'b1;。
前置条件
- 已安装 Vivado 2020.1 及以上版本(或 Quartus Prime 18.0 及以上版本)。
- 具备 Verilog 基础语法知识,熟悉
always块与赋值语句。 - 了解 FPGA 基本架构(查找表、触发器、布线资源)。
目标与验收标准
- 掌握组合逻辑与时序逻辑的划分原则,能独立判断何时使用
assign、always @(*)与always @(posedge clk)。 - 完成一个可综合的计数器设计,并通过仿真验证其功能正确性。
- 验收标准:仿真波形中
cnt在每个时钟上升沿递增,复位时清零,无毛刺或竞争风险。
实施步骤
步骤 1:理解组合逻辑与时序逻辑的本质区别
组合逻辑的输出仅取决于当前输入,无记忆特性,典型实现方式为 assign 连续赋值或 always @(*) 过程赋值(使用阻塞赋值 =)。时序逻辑依赖时钟边沿触发,具有状态保持能力,必须使用 always @(posedge clk) 或 always @(negedge clk) 块,且内部赋值采用非阻塞赋值 <=。
原因与机制分析:组合逻辑在硬件上映射为查找表(LUT)和门电路,信号传播延迟由路径决定;时序逻辑则通过触发器(FF)在时钟边沿采样并锁存数据,避免了组合逻辑中常见的毛刺与竞争问题。正确划分二者是保证设计时序收敛与功能稳定的基础。
步骤 2:设计计数器模块
创建一个新的 Verilog 源文件 top.v,编写如下代码:
module top (
input wire clk,
input wire rst_n,
output reg [3:0] cnt
);
// 时序逻辑:计数器递增
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
endmodule落地路径:上述代码中,always 块的敏感列表包含时钟上升沿和异步复位下降沿,确保复位立即生效,计数行为仅在时钟边沿更新。这是典型的时序逻辑写法,非阻塞赋值 <= 保证了多个赋值在同一时钟周期内正确同步。
步骤 3:添加组合逻辑示例(可选)
为了对比,可以在同一模块中添加一个组合逻辑输出,例如 even_flag 指示计数器是否为偶数:
wire even_flag;
assign even_flag = (cnt[0] == 1'b0);这里 assign 语句直接根据当前 cnt 的最低比特位计算输出,不依赖时钟,属于纯组合逻辑。
步骤 4:编写测试激励并仿真
新建一个仿真文件 tb_top.v:
module tb_top;
reg clk;
reg rst_n;
wire [3:0] cnt;
top uut (
.clk (clk),
.rst_n (rst_n),
.cnt (cnt)
);
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns 时钟周期
end
initial begin
rst_n = 0;
#20 rst_n = 1;
#200 $finish;
end
initial begin
$monitor("Time=%0t, cnt=%d", $time, cnt);
end
endmodule在 Vivado 或 ModelSim 中运行仿真,观察波形。确认复位期间 cnt 保持为 0,释放后每个时钟上升沿递增 1。
验证结果
仿真通过后,应得到如下波形特征:
- 复位信号
rst_n为低电平时,cnt立即清零。 - 复位释放后,
cnt在clk的每个上升沿加 1。 even_flag(如果添加)在cnt为偶数时输出高电平,无延迟。
若出现 cnt 变化与时钟不同步、或复位后未清零,请检查敏感列表与赋值类型。
排障指南
- 问题:仿真中 cnt 不递增 —— 可能原因:时钟未翻转或复位未释放。检查
clk生成逻辑与rst_n时序。 - 问题:综合后出现锁存器(latch)警告 —— 通常是因为组合逻辑块中未覆盖所有分支。确保
always @(*)中每个条件都有赋值,或使用default语句。 - 问题:时序违例 —— 检查时钟频率是否过高,或组合逻辑路径过长。可插入流水线寄存器拆分关键路径。
扩展:复杂场景下的划分策略
在实际工程中,组合逻辑与时序逻辑的边界往往模糊。例如,状态机中的下一状态计算属于组合逻辑,而状态寄存属于时序逻辑。推荐采用“三段式”状态机写法,将组合逻辑(次态与输出)与时序逻辑(现态更新)分离,提高可读性与综合质量。
风险边界:切勿在同一个 always 块中混用阻塞与非阻塞赋值,或将组合逻辑写入时钟敏感列表中,这会导致仿真与综合结果不一致。始终遵循“组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的黄金法则。
参考
- IEEE Std 1364-2001 Verilog HDL 标准
- Vivado Design Suite User Guide: Synthesis (UG901)
- Clifford E. Cummings, “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!”
附录:完整代码清单
// top.v
module top (
input wire clk,
input wire rst_n,
output reg [3:0] cnt,
output wire even_flag
);
// 时序逻辑:计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
// 组合逻辑:偶数指示
assign even_flag = (cnt[0] == 1'b0);
endmodule


