1. 课程导论与开发环境搭建
各位同学好,我是你们这门课的老周。在FPGA这行摸爬滚打了十几年,踩过的坑比走过的路还多。今天咱们开始第一讲,先把地基打牢。
这门课叫《FPGA时序约束与RISC-V软核优化性能调优实战》,名字挺长,但核心就两件事:让FPGA跑得快,让RISC-V软核跑得稳。说白了,就是教你如何把一块芯片的性能压榨到极致。
1.1 为什么时序约束是FPGA设计的“命门”?
很多新手觉得,代码写完了,仿真通过了,就万事大吉。结果一上板,要么不工作,要么偶尔抽风。为什么?
我刚开始做FPGA时也犯过这毛病。有一次做一个视频处理项目,仿真跑得完美,结果板子一跑,画面时不时花屏。查了三天,最后发现是时钟路径上有个组合逻辑延迟超标了。从那以后,我再也不敢轻视时序约束。
时序约束,说白了就是告诉工具:你的电路必须在多快的时间内完成计算。工具会根据这个要求,去优化布局布线,确保每条路径都不超时。
核心概念:静态时序分析(STA)是检查电路能否在目标频率下稳定工作的唯一手段。它不依赖仿真向量,而是穷举所有路径,计算最坏情况下的延迟。
没有约束,工具就不知道你的目标频率是多少。它会随便布,结果可能就是——你的设计只能跑50MHz,但你期望的是100MHz。
1.2 RISC-V软核:为什么选它?
RISC-V这几年火得不行。为什么?因为它开源、精简、可定制。你想想看,以前想用个CPU核,要么买ARM授权,要么用开源但复杂的OpenRISC。RISC-V的出现,让FPGA工程师也能轻松拥有一个属于自己的处理器。
咱们这门课选用的RISC-V软核,是经过我实际项目验证过的。它有几个特点:
- 五级流水线:取指、译码、执行、访存、写回,标准的RISC-V架构
- 支持RV32I指令集:基础整数指令,够用且高效
- 可配置:可以按需裁剪,比如去掉乘法器、调整缓存大小
- 面积小:在Xilinx Artix-7上只占约3000个LUT
但软核也有软核的烦恼。我在一个项目中,把RISC-V软核跑到了200MHz,结果发现访存路径成了瓶颈。这就是为什么咱们要把时序约束和软核优化结合起来学——两者是相辅相成的。
1.3 开发环境搭建:Vivado + Vitis
工欲善其事,必先利其器。咱们用Xilinx家的工具链:Vivado做FPGA综合布线,Vitis做RISC-V的软件开发。
安装过程我就不赘述了,网上教程很多。我提几个关键点:
| 工具 | 版本建议 | 用途 | 注意事项 |
|---|---|---|---|
| Vivado | 2022.2 或更新 | FPGA设计、综合、实现、时序分析 | 安装时勾选“Vivado HL WebPACK”即可,够用 |
| Vitis | 2022.2 或更新 | RISC-V交叉编译、调试、下载 | 需要单独安装,或者用Vivado自带的Vitis |
| RISC-V工具链 | GCC 10+ | 编译C代码为RISC-V机器码 | 推荐用SiFive提供的预编译包 |
我的习惯:Vivado和Vitis装在同一台机器上,但项目目录分开。Vivado工程放在 D:/FPGA_Projects/,Vitis工作空间放在 D:/Vitis_Workspace/。这样方便管理,也避免路径冲突。
1.4 第一个RISC-V软核工程
好,咱们动手。创建一个最简单的RISC-V软核工程,让它跑起来。
步骤一:创建Vivado工程
- 打开Vivado,点击“Create Project”
- 选择RTL Project,勾选“Do not specify sources at this time”
- 选择器件:我用的Xilinx Artix-7 xc7a35tcsg324-1
- 点击Finish
步骤二:添加RISC-V软核源码
咱们用的是一个精简版的RISC-V核,叫“PicoRV32”。它只有一个Verilog文件,非常干净。
// 在Vivado中,点击 Add Sources -> Add or create design sources
// 选择 picorv32.v 文件
// 然后创建一个顶层模块 top.v
步骤三:编写顶层模块
module top (
input wire clk,
input wire rst_n,
output wire [31:0] led
);
// 实例化PicoRV32
wire mem_valid;
wire mem_instr;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
reg [31:0] mem_rdata;
wire mem_ready;
picorv32 #(
.ENABLE_MUL(1),
.ENABLE_DIV(1)
) cpu (
.clk (clk),
.resetn (rst_n),
.mem_valid (mem_valid),
.mem_instr (mem_instr),
.mem_addr (mem_addr),
.mem_wdata (mem_wdata),
.mem_wstrb (mem_wstrb),
.mem_rdata (mem_rdata),
.mem_ready (mem_ready)
);
// 简单的内存模型:只读,地址0x0000_0000开始
reg [31:0] rom [0:255];
initial $readmemh("firmware.hex", rom);
always @(posedge clk) begin
if (mem_valid & mem_ready) begin
mem_rdata <= rom[mem_addr[9:2]];
end
end
assign mem_ready = mem_valid;
assign led = mem_rdata;
endmodule
注意:这里的 firmware.hex 是RISC-V的机器码文件,需要用Vitis编译生成。别急,下一步就讲。
步骤四:用Vitis编译固件
- 打开Vitis,创建一个新的Application Project
- 选择“RISC-V”平台(如果没有,需要先导入)
- 写一个简单的C程序:
// main.c
volatile unsigned int *led = (unsigned int *)0x00000000;
void main() {
while(1) {
*led = 0xAAAAAAAA; // 让LED交替闪烁
for(int i=0; i<100000; i++);
*led = 0x55555555;
for(int i=0; i<100000; i++);
}
}
- 编译生成
firmware.hex,复制到Vivado工程目录下
步骤五:综合、实现、生成比特流
在Vivado中点击“Generate Bitstream”。如果一切顺利,你会看到时序报告。嗯,第一次跑可能时序不满足,别慌,这正是咱们后面要解决的问题。
1.5 本章知识体系
为了让你更直观地理解本章内容,我画了一张图:
这张图把本章的知识点串起来了。你看,时序约束、RISC-V软核、开发环境,三者是并列关系。而最下面的“第一个RISC-V软核工程实践”,则是把前面所有知识落地的关键一步。
1.6 避坑指南
最后,分享几个我踩过的坑:
- 路径问题:Vivado工程路径不要有中文,不要有空格。我曾经因为路径里有个“&”符号,折腾了一下午。
- 版本匹配:Vivado和Vitis的版本必须一致。2022.1的Vivado配2022.2的Vitis,大概率会报错。
- 固件格式:
$readmemh读取的hex文件,必须是每行一个32位数据,且是十六进制格式。少个换行符都可能读错。 - 复位极性:PicoRV32的复位是低电平有效,别搞反了。我见过有人把
resetn接高电平,结果CPU一直复位,啥也跑不起来。
我的建议:第一次跑通后,别急着往下学。花点时间看看Vivado的时序报告,哪怕现在看不懂,也要知道它长什么样。后面咱们会反复跟它打交道。