---
title: FPGA毕业设计：基于RISC-V软核的SoC搭建全流程实施指南
url: "https://www.fpgalearn.com/51179.html"
kind: article
id: 51179
excerpt: Quick Start：快速上手安装Vivado 2024.2（或更新版本），并确保已添加Spartan-7 / Artix-7器件支持。从GitHub克隆Ve…
published_at: 2026-06-13
author: FPGA小白
tags: fpga, RISC-V, SoC
word_count: 6308
source: "https://www.fpgalearn.com"
---

# FPGA毕业设计：基于RISC-V软核的SoC搭建全流程实施指南

> 标签：fpga、RISC-V、SoC
> Quick Start：快速上手安装Vivado 2024.2（或更新版本），并确保已添加Spartan-7 / Artix-7器件支持。从GitHub克隆Ve…

> Quick Start：快速上手安装Vivado 2024.2（或更新版本），并确保已添加Spartan-7 / Artix-7器件支持。从GitHub克隆Ve…

## Quick Start：快速上手
1. 安装Vivado 2024.2（或更新版本），并确保已添加Spartan-7 / Artix-7器件支持。
1. 从GitHub克隆VexRiscv或PicoRV32的RTL源码，或使用芯来科技NMSL库。
1. 在Vivado中创建新工程，选择目标器件（如xc7a35tcsg324-1）。
1. 将RISC-V核的Verilog源文件、SoC顶层文件及Memory控制器IP加入工程。
1. 编写顶层模块，例化RISC-V核、Block RAM（作为指令/数据存储器）、GPIO与UART外设。
1. 添加XDC约束（时钟、复位、UART引脚），运行综合与实现。
1. 生成比特流并下载到FPGA开发板；打开串口终端（115200 8N1），观察“Hello World”或LED闪烁。
1. 若未出现预期输出，检查时钟频率（建议50MHz）、复位极性及UART波特率配置。
## 前置条件与环境
| 项目 | 推荐值/说明 | 替代方案 |
| --- | --- | --- |
| FPGA开发板 | Nexys A7-50T / Arty A7-35T | 其他Artix-7/Spartan-7板卡，需调整约束 |
| EDA版本 | Vivado 2024.2（或2023.2） | ISE不支持RISC-V软核综合（推荐Vivado） |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 2020.1 | Verilator（仅支持Verilog） |
| 时钟/复位 | 100MHz板载时钟（经MMCM降至50MHz）；低有效异步复位 | 可直接使用50MHz晶振，但需调整PLL |
| 接口依赖 | UART-USB桥（CP2102/FTDI）用于串口通信 | VGA/HDMI输出（需额外IP） |
| 约束文件 | XDC：主时钟约束、复位、UART RX/TX引脚 | SDC格式（Vivado自动转换） |
| RISC-V核选择 | VexRiscv（RV32IM，支持M/U模式） | PicoRV32（更小，但无M扩展） |
## 目标与验收标准
**功能点：**RISC-V核能够正确执行编译后的C程序（如LED闪烁、UART打印“Hello World”）。
**性能指标：**系统时钟频率≥50MHz（典型值），CoreMark分数≥1.5/MHz（示例）。
**资源占用：**LUT ≤ 4000，FF ≤ 3000，BRAM ≤ 8块（18K），DSP ≤ 4（典型配置）。
**验收方式：**上电后串口输出“SoC Boot OK”，LED按0.5Hz闪烁；仿真波形显示指令取指与写回正确。
## 实施步骤
### 阶段1：工程结构与RTL整合
创建Vivado工程，设置器件为xc7a35tcsg324-1。
添加RISC-V核源码（VexRiscv）：将`src/main/scala/vexriscv/`目录下的所有Verilog文件添加至工程。
创建SoC顶层模块，例化CPU核、指令ROM（BRAM）、数据RAM（BRAM）、GPIO与UART。
使用Vivado IP Catalog生成Block Memory Generator，配置为单端口ROM（指令）和双端口RAM（数据）。
#### 常见坑与排查
**坑1：**VexRiscv默认使用AXI4接口，若直接连BRAM需添加AXI4-Lite桥接器（Vivado IP：AXI Interconnect）。
**坑2：**指令ROM初始化文件（.coe或.mif）需与编译器生成的二进制对齐；使用`riscv32-unknown-elf-objcopy`转换ELF为hex。
### 阶段2：关键模块——RISC-V核与存储器接口
```
// SoC顶层模块（简化版）
module soc_top (
    input wire clk_100m,
    input wire rst_n,
    output wire [3:0] led,
    output wire uart_tx,
    input wire uart_rx
);

// 内部信号
wire clk_cpu;
wire pll_locked;
wire [31:0] instr_addr, instr_rdata;
wire [31:0] data_addr, data_wdata, data_rdata;
wire data_we;
wire [3:0] data_be;

// PLL实例：100MHz -> 50MHz
clk_wiz_0 clk_gen (
    .clk_in1(clk_100m),
    .clk_out1(clk_cpu),
    .locked(pll_locked)
);

// CPU核实例（VexRiscv）
vexriscv_wrapper cpu (
    .clk(clk_cpu),
    .reset(~rst_n & pll_locked),
    .iBusWishbone_ADR(instr_addr),
    .iBusWishbone_DAT_MISO(instr_rdata),
    .dBusWishbone_ADR(data_addr),
    .dBusWishbone_DAT_MISO(data_rdata),
    .dBusWishbone_DAT_MOSI(data_wdata),
    .dBusWishbone_WE(data_we),
    .dBusWishbone_SEL(data_be)
);

// 指令ROM（BRAM）
blk_mem_gen_0 instr_mem (
    .clka(clk_cpu),
    .addra(instr_addr[15:2]),
    .douta(instr_rdata)
);

// 数据RAM（BRAM）
blk_mem_gen_1 data_mem (
    .clka(clk_cpu),
    .wea(data_we),
    .addra(data_addr[15:2]),
    .dina(data_wdata),
    .douta(data_rdata)
);

// GPIO与UART（略）
endmodule
```
### 逐行说明
**第1行：**模块声明，输入100MHz时钟和低有效复位，输出4位LED和UART信号。
**第2-3行：**声明内部线网，包括CPU时钟、PLL锁定信号、指令/数据总线信号。
**第4行：**PLL实例化，将100MHz输入转换为50MHz输出；locked信号用于复位同步。
**第5行：**CPU核实例化，使用Wishbone总线接口（VexRiscv默认）。
**第6行：**复位信号为外部复位取反后与PLL锁定信号相与，确保复位在时钟稳定后释放。
**第7行：**指令ROM实例化，地址位宽16位（实际取高14位，因为BRAM字宽32位，地址按字对齐）。
**第8行：**数据RAM实例化，支持写使能；地址同样按字对齐。
**第9行：**GPIO和UART模块未在此展示，但需在完整SoC中例化。
### 阶段3：时序约束与CDC处理
```
# 主时钟约束
create_clock -name clk_100m -period 10.000 [get_ports clk_100m]

# 生成时钟（PLL输出）
create_generated_clock -name clk_cpu -source [get_ports clk_100m]
    -divide_by 2 [get_pins clk_gen/inst/mmcm_adv_inst/CLKOUT0]

# 异步复位约束
set_property ASYNC_REG TRUE [get_cells {rst_sync_reg[*]}]

# UART引脚约束
set_property PACKAGE_PIN U18 [get_ports uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]
```
### 逐行说明
**第1行：**定义输入时钟100MHz，周期10ns。
**第2行：**定义PLL生成的50MHz时钟，源时钟为输入时钟，分频系数2。
**第3行：**将复位同步器寄存器标记为异步寄存器，避免CDC误报。
**第4行：**UART TX引脚约束，指定封装位置和电平标准。
### 阶段4：验证（仿真）
```
// testbench片段
initial begin
    $readmemh("firmware.hex", uut.instr_mem.mem);
    #1000;
    $display("PC = %h, Instr = %h", uut.cpu.pc, uut.cpu.ibus_instr);
    if (uut.cpu.pc == 32'h00000010)
        $display("SUCCESS");
    else
        $display("FAIL");
    #2000 $finish;
end
```
### 逐行说明
**第1行：**使用`$readmemh`将hex文件加载到指令ROM内部数组。
**第2行：**等待1000个时间单位，让CPU完成初始取指。
**第3行：**打印当前PC和指令码，用于调试。
**第4行：**检查PC是否到达地址0x10（假设是程序结束点），并输出测试结果。
### 阶段5：上板测试
生成比特流后，使用Vivado Hardware Manager下载到FPGA。
打开串口终端（如PuTTY），设置波特率115200、8数据位、1停止位、无校验。
观察LED是否按预期闪烁；串口是否输出“Hello World”字符串。
## 原理与设计说明
### 为什么选择VexRiscv？
VexRiscv是可配置的RV32IM核，支持M模式（乘除法）和U模式（用户态），且提供Wishbone/AXI接口，易于集成。相比PicoRV32，它支持更多指令集扩展，适合毕业设计展示。
### 资源 vs Fmax权衡
使用BRAM作为指令/数据存储器可大幅减少LUT占用，但会限制最大频率（BRAM延迟固定，但组合逻辑路径变长）。若追求高Fmax，可改用分布式RAM（LUTRAM），但资源消耗增加2-3倍。
### 吞吐 vs 延迟
RISC-V核采用单周期或两级流水线设计（VexRiscv默认两级），吞吐量较低但延迟确定。若需更高性能，可启用五级流水线配置（需修改VexRiscv参数），但会引入结构冒险，需增加转发逻辑。
### 易用性 vs 可移植性
使用Vivado IP（如Block Memory Generator）简化了BRAM配置，但将设计绑定到Xilinx平台。若需移植到Lattice/Intel，需替换为通用RTL BRAM模型。
## 验证与结果
| 指标 | 测量值（示例） | 条件 |
| --- | --- | --- |
| Fmax | 65 MHz | Vivado 2024.2，时序约束宽松 |
| LUT资源 | 3,842 | VexRiscv默认配置，含GPIO/UART |
| FF资源 | 2,910 | 同上 |
| BRAM (18K) | 6块 | 指令ROM 4KB，数据RAM 4KB |
| UART波特率误差 | 0.16% | 50MHz时钟，115200波特率 |
**测量条件：**Nexys A7-50T，Vivado 2024.2，默认综合策略，实现后静态时序分析。
## 故障排查（Troubleshooting）
**现象：**串口无输出 → **原因：**UART波特率不匹配 → **检查：**时钟频率与分频系数 → **修复：**调整UART IP的时钟分频参数。
**现象：**LED不亮 → **原因：**复位未释放 → **检查：**复位信号电平与PLL锁定状态 → **修复：**确保rst_n为低且pll_locked为高。
**现象：**仿真中PC卡在0x0 → **原因：**指令ROM未正确初始化 → **检查：**hex文件路径与格式 → **修复：**使用`$readmemh`加载正确文件。
**现象：**综合后时序违规 → **原因：**时钟频率过高或路径过长 → **检查：**时序报告中的关键路径 → **修复：**降低时钟频率或增加流水线级数。
**现象：**上板后程序跑飞 → **原因：**存储器地址映射错误 → **检查：**链接脚本中ROM/RAM基地址 → **修复：**修改ld文件与SoC地址译码一致。
**现象：**UART数据乱码 → **原因：**波特率误差过大 → **检查：**时钟分频计算 → **修复：**使用整数分频或调整时钟频率。
**现象：**BRAM输出全X → **原因：**未使能BRAM的复位或初始化 → **检查：**IP配置中的初始化选项 → **修复：**在IP中勾选“Load Init File”。
**现象：**综合报错“undefined macro” → **原因：**VexRiscv参数未正确定义 → **检查：**Verilog宏定义文件 → **修复：**添加`\`define RV32 1`等。
## 扩展与下一步
**参数化：**将SoC设计参数化（如存储器大小、外设数量），通过Vivado Tcl脚本自动生成。
**带宽提升：**将指令总线改为AXI4-Full，支持突发传输，提高指令吞吐。
**跨平台：**将RTL移植到Lattice iCE40或Intel Cyclone V，使用通用BRAM模型。
**加入断言：**在仿真中添加SVA断言，验证总线协议正确性。
**覆盖分析：**使用Vivado Coverage工具分析代码覆盖率，确保测试充分。
**形式验证：**使用SymbiYosys对RISC-V核与存储器接口进行形式化验证。
## 参考与信息来源
VexRiscv GitHub仓库：[https://github.com/SpinalHDL/VexRiscv](https://github.com/SpinalHDL/VexRiscv)
PicoRV32 GitHub仓库：[https://github.com/YosysHQ/picorv32](https://github.com/YosysHQ/picorv32)
Xilinx Vivado Design Suite User Guide (UG910)
RISC-V指令集手册（非特权架构）v2.2
芯来科技NMSL库文档：[https://www.nucleisys.com/](https://www.nucleisys.com/)
## 技术附录
### 术语表
**RV32IM：**32位RISC-V基础整数指令集加乘除法扩展。
**BRAM：**Block RAM，Xilinx FPGA中的专用存储块。
**CDC：**时钟域交叉，处理不同时钟域信号同步。
**Wishbone：**开源片上总线标准，常用于SoC互连。
### 检查清单
□ 确认RISC-V核与BRAM接口时序匹配（地址/数据建立时间）。
□ 确认复位同步器已正确实现。
□ 确认UART波特率分频系数计算无误。
□ 确认链接脚本中ROM/RAM基地址与SoC译码一致。
### 关键约束速查
| 约束类型 | 示例命令 | 说明 |
| --- | --- | --- |
| 主时钟 | `create_clock -period 10.000 [get_ports clk_100m]` | 定义输入时钟周期 |
| 生成时钟 | `create_generated_clock -source [get_ports clk_100m] -divide_by 2 [get_pins ...]` | 定义PLL输出时钟 |
| 异步复位 | `set_property ASYNC_REG TRUE [get_cells {rst_sync_reg[*]}]` | 标记复位同步器 |
| 引脚约束 | `set_property PACKAGE_PIN U18 [get_ports uart_tx]` | 指定物理引脚 |
