3. Verilog HDL基础与CertusPro-NX设计入口
CertusPro-NX 属于 Lattice Nexus 平台的低功耗 FPGA,逻辑单元规模、嵌入式存储和 DSP 资源都围绕低功耗与高可靠性做了优化。用 Verilog HDL 描述设计时,代码最终要映射到它内部的 PFU(可编程功能单元)、EBR(嵌入式块 RAM)、DSP 以及时钟网络等真实资源上。因此,本章不孤立地讲语法,而是把“模块化设计、时序逻辑与组合逻辑、可综合代码风格、面向 Lattice 器件的编码建议”放在同一条设计入口链路上理解:从模块划分开始,到代码被综合成什么结构,再到如何写出对 CertusPro-NX 友好的可综合代码。
模块化设计:先定接口,再填内部
模块化设计的核心是“一个模块只做一件事,并通过清晰的端口与外界交互”。在 Verilog 中,模块由 module 声明、端口列表、内部信号声明和功能描述组成。对 CertusPro-NX 而言,模块划分直接影响综合后的层次结构和时序收敛难度。建议按功能边界划分,例如时钟分频、数据采样、状态机、数据缓存、输出驱动各成模块,而不是把所有逻辑塞进一个顶层文件。
端口方向要明确:input、output、inout。对于跨模块信号,推荐使用命名端口连接,而不是按位置连接,避免端口顺序变化导致隐蔽错误。下面是一个面向 CertusPro-NX 的简单模块示例,用于把 50 MHz 输入时钟分频为 1 Hz 使能脉冲,并驱动一个计数器:
module clk_en_1hz #(
parameter integer CLK_FREQ_HZ = 50_000_000
)(
input wire clk,
input wire rst_n,
output reg en_1hz
);
localparam integer CNT_MAX = CLK_FREQ_HZ - 1;
reg [25:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 26'd0;
en_1hz <= 1'b0;
end else if (cnt == CNT_MAX) begin
cnt <= 26'd0;
en_1hz <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
en_1hz <= 1'b0;
end
end
endmodule
这个模块只负责产生使能脉冲,不直接驱动 LED 或外设,后续模块可以复用该使能信号。这种“接口清晰、职责单一”的划分方式,在 CertusPro-NX 上更容易做资源估算和时序约束。
组合逻辑与时序逻辑:分清“立即”与“节拍”
组合逻辑的输出只取决于当前输入,常用 assign 或 always @(*) 描述;时序逻辑的输出取决于当前输入和当前状态,通常由时钟边沿触发,用 always @(posedge clk) 描述。两者混用是初学者最容易犯的错误,例如在时序 always 块中同时写组合赋值和寄存器赋值,会导致综合结果与预期不一致。
在 CertusPro-NX 中,PFU 内部包含查找表、进位链和触发器。组合逻辑主要占用 LUT,时序逻辑主要占用触发器。一个设计如果组合路径过长,会限制最高工作频率;如果触发器使用过多,则增加功耗和资源占用。因此,组合逻辑适合做译码、比较、多路选择,时序逻辑适合做计数、状态机、流水线寄存器。
下面是一个典型的“组合逻辑 + 时序逻辑”配合示例:组合逻辑产生下一状态,时序逻辑在时钟边沿更新状态。
localparam S_IDLE = 2'b00, S_RUN = 2'b01, S_DONE = 2'b10;
reg [1:0] state, next_state;
always @(*) begin
case (state)
S_IDLE: next_state = start ? S_RUN : S_IDLE;
S_RUN : next_state = finish ? S_DONE : S_RUN;
S_DONE: next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= S_IDLE;
else state <= next_state;
end
这种写法把“判断条件”和“状态更新”分开,综合工具更容易识别出标准状态机结构,也便于在 CertusPro-NX 上做时序分析。
可综合代码风格:写给综合器看,而不是写给仿真器看
可综合代码必须满足综合工具能映射为真实硬件结构的要求。关键原则包括:使用 always @(posedge clk) 描述时序逻辑,使用非阻塞赋值 <=;使用 always @(*) 描述组合逻辑,使用阻塞赋值 =;避免在可综合代码中使用 initial、#delay、while 等不可综合或依赖仿真语义的语句。复位策略要统一:CertusPro-NX 支持异步复位和同步复位,建议同一时钟域内统一使用一种复位方式,避免复位释放时的亚稳态问题。
另一个重点是避免锁存器。组合逻辑中如果 if 或 case 没有覆盖所有分支,综合工具会推断出锁存器,带来毛刺和时序问题。例如:
always @(*) begin
if (sel)
y = a;
// 缺少 else,y 在 sel=0 时保持原值,会推断锁存器
end
正确做法是补全 else 或给默认赋值。对于 case 语句,建议加上 default 分支。
面向 Lattice 器件的编码建议
CertusPro-NX 的时钟资源有限,建议尽量使用全局时钟网络驱动时钟信号,避免用普通逻辑资源分频后直接当时钟使用。如果确实需要分频,优先产生使能脉冲,而不是产生新的时钟信号。这样可以让时序分析更简单,也降低时钟树功耗。
在资源使用上,CertusPro-NX 的 EBR 适合做 FIFO、行缓存和查找表;DSP 模块适合做乘加运算。写代码时,如果乘法器能被综合工具识别为 DSP 原语,就不要手写移位加法去替代,否则会浪费 LUT 和寄存器资源。对于存储器,建议使用 reg [7:0] mem [0:1023] 这样的数组描述,并配合同步读写,综合工具更容易推断出 EBR。
此外,Lattice 的综合工具对代码风格有一定偏好:模块端口建议显式声明位宽;状态机建议使用 localparam 定义状态编码;跨时钟域信号必须做同步处理,不能直接打两拍就当作稳定信号使用。对于低功耗设计,可以在不需要工作的模块上使用时钟使能,而不是频繁开关时钟。
把上述内容串起来看,CertusPro-NX 的 Verilog 设计入口可以概括为:先用模块化划分把系统拆成可管理的功能块,再用组合逻辑和时序逻辑分别描述“判断”和“状态”,然后通过可综合代码风格保证代码能被正确映射,最后结合 Lattice 器件的时钟、EBR、DSP 和低功耗特性做针对性优化。这样写出的代码,既能在仿真中行为正确,也能在 CertusPro-NX 上获得合理的资源占用和时序表现。