2. Verilog基础回顾
在进入CORDIC与FFT这类复杂算法实现之前,必须夯实Verilog的核心基础。复杂算法的硬件化并非代码的简单堆砌,而是对硬件结构、数据流和控制逻辑的精确描述。本章将系统梳理模块化设计方法、核心数据类型、组合与时序逻辑的本质区别、状态机设计范式,以及仿真与综合的标准流程。这些内容构成了后续所有算法实现的地基,理解其底层硬件含义远比记住语法更重要。
模块化设计:复杂算法的分解与封装
模块化是Verilog设计的灵魂。对于CORDIC和FFT这类计算密集型算法,直接编写一个超大模块必然导致代码难以维护、仿真调试困难、综合时序恶化。正确的做法是将算法拆分为多个功能单一、接口清晰的子模块,通过层次化实例化构建完整的硬件系统。
核心原理:端口与参数化
每个模块通过端口(Port)与外部通信,端口方向分为input、output和inout。参数化设计使用parameter定义常量,使得同一模块可配置为不同位宽或不同迭代次数。例如,CORDIC模块的迭代次数、FFT的点数都可以通过参数传递,极大提升代码复用性。
module cordic_pipeline #(
parameter DATA_WIDTH = 16,
parameter ITER_NUM = 12
)(
input wire clk,
input wire rst_n,
input wire signed [DATA_WIDTH-1:0] x_in,
input wire signed [DATA_WIDTH-1:0] y_in,
input wire signed [DATA_WIDTH-1:0] angle_in,
output reg signed [DATA_WIDTH-1:0] x_out,
output reg signed [DATA_WIDTH-1:0] y_out
);
// 内部逻辑
endmodule
操作流程:自顶向下设计
- 算法分解:将CORDIC旋转分解为移位、加法、查找表三个子功能。
- 接口定义:明确每个子模块的输入输出信号、位宽、时序要求。
- 子模块实现:分别编写并验证每个子模块。
- 顶层集成:在顶层模块中实例化所有子模块并完成信号连接。
- 联合仿真:对完整系统进行功能验证。
数据类型:位宽、符号与硬件映射
Verilog的数据类型直接映射到硬件寄存器或连线。wire类型对应组合逻辑输出,reg类型对应变量存储,但reg并不一定代表寄存器——在组合逻辑always块中,reg只是变量。理解这一点对后续算法实现至关重要。
| 数据类型 | 硬件含义 | 典型应用 | 注意事项 |
|---|---|---|---|
| wire | 连线 | 模块间信号连接 | 不能存储值 |
| reg | 变量 | always块中赋值 | 可综合为寄存器或连线 |
| integer | 32位有符号 | 循环变量、计数器 | 综合时可能消耗较多资源 |
| signed reg | 有符号数 | CORDIC坐标运算 | 需配合$signed()使用 |
| parameter | 常量 | 位宽、迭代次数 | 编译时确定 |
位宽与符号的工程决策
在FFT实现中,输入数据通常为定点数格式。假设输入为16位有符号数,经过蝶形运算后数据位宽会增长。若不进行截位或饱和处理,位宽将无限膨胀。常见策略是每级蝶形运算后右移一位(除以2)以保持总增益不变,但会引入量化误差。设计者需要在精度与资源之间权衡。
reg signed [15:0] data; 而非 reg [15:0] data;
组合逻辑与时序逻辑:硬件本质的区分
组合逻辑的输出仅取决于当前输入,无记忆功能;时序逻辑的输出还取决于历史状态,由时钟沿触发更新。在CORDIC流水线中,每一级迭代既包含组合逻辑(移位、加法),也包含时序逻辑(寄存器暂存中间结果)。
阻塞赋值与非阻塞赋值
- 阻塞赋值(=):在always块中顺序执行,用于组合逻辑建模。
- 非阻塞赋值(<=):在时钟沿同时更新,用于时序逻辑建模。
// 组合逻辑示例
always @(*) begin
sum = a + b; // 阻塞赋值
end
// 时序逻辑示例
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
sum_reg <= 16'd0;
else
sum_reg <= a + b; // 非阻塞赋值
end
同步复位与异步复位
同步复位仅在时钟上升沿检查复位信号,资源利用率高但抗干扰能力弱;异步复位立即生效,适合需要快速恢复的场景。在FPGA设计中,推荐使用异步复位、同步释放的方式,避免复位释放时的亚稳态问题。
状态机基础:控制通路的骨架
FFT的地址生成、CORDIC的模式切换都需要状态机来管理。三段式状态机将状态跳转、次态逻辑、输出逻辑分离,代码清晰且易于维护。
三段式状态机模板
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
// 第二段:次态组合逻辑
always @(*) begin
case (current_state)
IDLE: next_state = START ? CALC : IDLE;
CALC: next_state = DONE ? IDLE : CALC;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
always @(*) begin
case (current_state)
CALC: calc_en = 1'b1;
default: calc_en = 1'b0;
endcase
end
| 编码方式 | 资源消耗 | 速度 | 适用场景 |
|---|---|---|---|
| 二进制编码 | 最少 | 较慢 | 状态数多 |
| 格雷编码 | 中等 | 中等 | 相邻状态切换多 |
| 独热编码 | 最多 | 最快 | 状态数少(<16) |
仿真与综合流程:从RTL到比特流
仿真验证功能正确性,综合将RTL映射为门级网表。两者是迭代过程,任何一步发现问题都需要返回修改代码。
标准流程
- 编写Testbench:生成时钟、复位、输入激励,监控输出。
- 功能仿真:使用Vivado Simulator或ModelSim验证逻辑功能。
- 综合:设置时钟约束、IO约束,将RTL综合为网表。
- 时序仿真:加入门延迟后仿真,检查时序是否满足。
- 布局布线:FPGA工具完成物理实现。
- 时序分析:检查建立/保持时间是否满足。