Quick Start
- 注册GitHub账号,安装Git并配置SSH密钥,克隆一个开源FPGA项目(如PicoRV32或LiteX)。
- 安装Vivado或Quartus Prime Lite(免费版),下载对应厂商的器件库(如Xilinx Artix-7或Intel Cyclone V)。
- 在IDE中创建一个空工程,添加一个简单的LED闪烁模块(分频+计数器),综合并实现,生成比特流。
- 连接开发板(如Nexys A7或DE10-Lite),下载比特流,观察LED按预期频率闪烁。
- 编写一个简单的Testbench(使用Verilog或SystemVerilog),用ModelSim或Vivado Simulator仿真LED闪烁模块,验证时序。
- 阅读Xilinx或Intel官方文档《ug901 Vivado Synthesis》或《Quartus Prime Handbook》中关于时序约束的章节,为一个寄存器到寄存器的路径添加create_clock约束。
- 在GitHub上提交一个Pull Request,修复一个开源FPGA项目的文档或代码小bug(如注释错误或边界条件遗漏)。
- 整理一份个人技能清单(本文可作为模板),对照招聘网站(如Boss直聘、猎聘)上的FPGA实习生JD,标记已掌握和待学习项。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7(如Nexys A7-100T)或Intel Cyclone V(如DE10-Lite) | Lattice iCE40(开源工具链)、Gowin GW1N(国产低成本) |
| EDA版本 | Vivado 2024.2 / Quartus Prime Lite 24.1 | ISE 14.7(仅支持老器件)、Yosys+nextpnr(开源) |
| 仿真器 | Vivado Simulator / ModelSim SE-64 2024.2 | Verilator(开源,仅支持Verilog/SystemVerilog)、Icarus Verilog |
| 时钟/复位 | 板载100MHz晶振(差分或单端),低电平异步复位 | PLL生成内部时钟,同步复位(需额外逻辑) |
| 接口依赖 | UART(USB转串口)、GPIO(LED/按键)、PMOD(扩展) | SPI Flash、HDMI输出(需IP核) |
| 约束文件 | XDC(Vivado)或SDC(Quartus),包含主时钟约束、I/O标准、位置 | Tcl脚本自动生成,或手动编辑 |
| 操作系统 | Windows 10/11 64位 或 Ubuntu 22.04 LTS | macOS(通过Docker运行EDA) |
| 版本控制 | Git + GitHub/GitLab | SVN(少数公司仍用) |
目标与验收标准
- 功能点:独立完成一个中等复杂度数字系统(如UART收发器、SPI控制器、简易CPU)的RTL设计、仿真、综合与上板验证。
- 性能指标:设计在目标器件上达到Fmax ≥ 100MHz(典型值,以实际时序报告为准),资源利用率不超过器件总量的70%。
- 资源/Fmax:综合后LUT+FF占用 ≤ 器件总量的50%(示例),WNS ≥ 0(无时序违例)。
- 关键波形/日志:仿真波形显示接口协议正确(如UART波特率误差 < 2%),上板后串口打印"Hello FPGA"。
实施步骤
工程结构
推荐使用标准目录结构,便于团队协作与版本管理:
project/
├── rtl/ # 所有RTL源文件(.v/.sv)
├── sim/ # Testbench与仿真脚本
├── constr/ # 约束文件(.xdc/.sdc)
├── ip/ # IP核(如PLL、BRAM)
├── scripts/ # Tcl自动化脚本
├── docs/ # 设计文档与笔记
└── output/ # 综合/实现产物(bit、rpt)逐行说明
- 第1行:项目根目录,名称与设计主题一致(如uart_tx)。
- 第2行:rtl/存放所有可综合的Verilog/SystemVerilog源文件,按模块命名(如uart_tx.v)。
- 第3行:sim/存放Testbench文件(如tb_uart_tx.v)和仿真脚本(如run.do)。
- 第4行:constr/存放时序与物理约束,Vivado用.xdc,Quartus用.sdc。
- 第5行:ip/放置厂商IP核(如Clocking Wizard),避免与RTL混放。
- 第6行:scripts/存放自动化Tcl脚本,用于批量运行综合/实现/仿真。
- 第7行:docs/存放设计文档、时序分析笔记、面试准备材料。
- 第8行:output/存放综合报告(.rpt)、实现报告、比特流文件,便于回溯。
关键模块:UART发送器
以下是一个可综合的UART发送器RTL(8位数据,1位起始位,1位停止位,无校验):
module uart_tx #(
parameter CLK_FREQ = 100_000_000, // 输入时钟频率 (Hz)
parameter BAUD_RATE = 115200 // 目标波特率
) (
input wire clk,
input wire rst_n,
input wire tx_start,
input wire [7:0] tx_data,
output reg tx_busy,
output reg txd
);
localparam BIT_PERIOD = CLK_FREQ / BAUD_RATE; // 每比特时钟周期数
localparam CNT_W = $clog2(BIT_PERIOD); // 计数器位宽
reg [CNT_W-1:0] baud_cnt;
reg [3:0] bit_cnt;
reg sending;
// 状态机与计数逻辑
// ... (完整代码见附录)
endmodule逐行说明
- 第1行:模块定义,使用parameter实现参数化设计,便于复用。
- 第2行:CLK_FREQ参数,典型值100MHz,需与板载时钟一致。
- 第3行:BAUD_RATE参数,标准值115200,也可设为9600或460800。
- 第4-5行:输入端口,clk为系统时钟,rst_n为低电平异步复位。
- 第6行:tx_start为发送启动信号,高电平有效,至少保持一个时钟周期。
- 第7行:tx_data为待发送的8位并行数据。
- 第8行:tx_busy输出,高电平表示正在发送,发送完成后自动拉低。
- 第9行:txd输出,串行数据线,空闲时为高电平。
- 第11行:localparam BIT_PERIOD,通过除法计算每比特所需时钟周期数,需确保除尽或误差在可接受范围(< 2%)。
- 第12行:localparam CNT_W,使用$clog2函数自动计算计数器位宽,避免手动计算错误。
- 第14-16行:内部寄存器定义,baud_cnt为波特率计数器,bit_cnt为已发送比特数计数器,sending为发送状态标志。
- 第18-19行:状态机与计数逻辑的占位注释,完整实现需包含状态转移、波特率时钟生成、串行移位输出等逻辑。
- 第21行:endmodule结束模块定义。
验证结果
完成上述步骤后,应验证以下内容:
- 仿真波形中txd信号按UART协议输出起始位、8位数据(LSB first)、停止位,波特率误差 < 2%。
- 综合后时序报告显示WNS ≥ 0,无建立时间违例。
- 上板后通过串口助手(如Putty)接收数据,与发送数据一致。
排障
- 问题:仿真波形无输出或数据错误——检查复位时序是否正确(rst_n低电平有效),tx_start脉冲宽度是否足够。
- 问题:综合时报错"cannot evaluate non-constant parameter"——确认BIT_PERIOD和CNT_W在编译时可计算,避免使用运行期变量。
- 问题:上板后串口无输出——检查约束文件中I/O标准(如LVCMOS33)和引脚分配是否正确,开发板跳线是否设置正确。
扩展
- 为UART发送器添加FIFO缓冲,支持连续发送多字节。
- 实现UART接收器,并与发送器组成回环测试。
- 使用PLL生成不同时钟域,练习跨时钟域同步(CDC)处理。
- 参考开源项目(如ZipCPU、Serv)实现一个简单RISC-V CPU。
参考
- Xilinx UG901: Vivado Design Suite User Guide - Synthesis
- Intel Quartus Prime Handbook, Volume 1: Design and Synthesis
- PicoRV32: A Size-Optimized RISC-V CPU (GitHub)
- LiteX: A Migen-based SoC builder (GitHub)
附录
完整UART发送器RTL代码(含状态机与计数逻辑)可参考以下实现:
module uart_tx #(
parameter CLK_FREQ = 100_000_000,
parameter BAUD_RATE = 115200
) (
input wire clk,
input wire rst_n,
input wire tx_start,
input wire [7:0] tx_data,
output reg tx_busy,
output reg txd
);
localparam BIT_PERIOD = CLK_FREQ / BAUD_RATE;
localparam CNT_W = $clog2(BIT_PERIOD);
reg [CNT_W-1:0] baud_cnt;
reg [3:0] bit_cnt;
reg sending;
reg [7:0] data_reg;
// 波特率时钟生成与状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_cnt <= 0;
bit_cnt <= 0;
sending <= 0;
txd <= 1'b1;
tx_busy <= 1'b0;
data_reg <= 0;
end else begin
if (!sending && tx_start) begin
// 启动发送
sending <= 1'b1;
tx_busy <= 1'b1;
data_reg <= tx_data;
baud_cnt <= 0;
bit_cnt <= 0;
txd <= 1'b0; // 起始位
end else if (sending) begin
if (baud_cnt == BIT_PERIOD - 1) begin
baud_cnt <= 0;
if (bit_cnt < 8) begin
// 发送数据位 (LSB first)
txd <= data_reg[bit_cnt];
bit_cnt <= bit_cnt + 1;
end else if (bit_cnt == 8) begin
// 发送停止位
txd <= 1'b1;
bit_cnt <= bit_cnt + 1;
end else begin
// 发送完成
sending <= 1'b0;
tx_busy <= 1'b0;
end
end else begin
baud_cnt <= baud_cnt + 1;
end
end
end
end
endmodule逐行说明
- 第1-3行:模块定义与参数声明,同前。
- 第4-9行:端口声明,同前。
- 第11-12行:localparam定义,同前。
- 第14-18行:内部寄存器,新增data_reg用于暂存待发送数据。
- 第20行:always块,敏感列表包含clk上升沿和rst_n下降沿(异步复位)。
- 第21-28行:复位逻辑,将所有寄存器清零,txd置为高电平(空闲态),tx_busy置低。
- 第29-36行:检测到tx_start且当前未发送时,启动发送:设置sending和tx_busy,锁存数据,计数器清零,输出起始位(低电平)。
- 第37-56行:发送状态下的逻辑:当baud_cnt计数到BIT_PERIOD-1时,产生一个波特率时钟脉冲;根据bit_cnt的值依次发送数据位(LSB first)、停止位,完成后清除sending和tx_busy。
- 第57行:endmodule结束模块。




