搭建RISC-V软核工程:在Vivado中创建RISC-V处理器工程

好,咱们正式开始动手。这一节,我带你走一遍在Vivado里搭建RISC-V软核工程的全流程。说白了,就是把一个开源的RISC-V处理器核(比如VexRiscv、PicoRV32这些)塞进FPGA里,让它能跑起来。

我个人习惯把这一步叫做“搭骨架”。骨架搭得稳,后面调试才不慌。你想想看,如果工程结构乱糟糟的,约束文件缺胳膊少腿,综合出来一堆时序违例,那后面仿真调试根本没法搞。

第一步:创建Vivado工程

打开Vivado,点“Create Project”。这里有个小细节——工程名字别用中文,路径也别带空格。我在项目中遇到过有人用中文路径,结果IP核生成时报了一堆莫名其妙的错误,排查了半天才发现是路径问题。

选RTL Project,勾上“Do not specify sources at this time”。为什么?因为咱们后面要加IP核,先建个空壳子更清爽。

器件型号怎么选?看你手里的板子。我用的是Xilinx Artix-7系列,具体型号XC7A35T。如果你用别的板子,比如Kintex或者Zynq,操作步骤完全一样,就是器件型号不同。

小技巧: 如果你不确定器件型号,可以在Vivado里搜“Part Number”,输入板子上的芯片编号。比如“xc7a35tcsg324-1”,后面的“-1”代表速度等级,别选错了。

第二步:添加RISC-V处理器IP核

RISC-V处理器核不是Vivado自带的,得自己加。常用的开源方案有几种:

  • VexRiscv:用SpinalHDL写的,可配置性极强,我比较推荐
  • PicoRV32:纯Verilog,简单粗暴,适合入门
  • Rocket Chip:Berkeley出品,功能全但有点重

我个人习惯用VexRiscv。为什么?因为它支持JTAG调试,而且能生成AXI总线接口,跟Vivado里的IP核对接特别方便。

添加IP核的步骤:

  1. 在Vivado里点“IP Catalog”
  2. 右键选“Add Repository”,指向你下载的RISC-V核文件夹
  3. 刷新一下,IP核就会出现在列表里
  4. 双击它,配置参数(比如指令缓存大小、乘法器类型等)

嗯,这里要注意:配置参数时,指令总线和数据总线宽度一定要匹配。我曾经帮一个同事排查问题,他配了32位指令总线但数据总线选了64位,结果访存时地址对齐出了问题,程序跑飞了。

第三步:添加其他必要IP核

光有CPU核不够,还得有内存和总线。我一般会加这几个:

IP核名称 作用 配置要点
Block Memory Generator 做程序存储器(ROM)和数据存储器(RAM) 深度至少4KB,宽度32位
AXI Interconnect 连接CPU核和各个外设 选“AXI4-Lite”模式,简单够用
Clocking Wizard 生成系统时钟 输入50MHz,输出设成CPU目标频率
Processor System Reset 提供复位信号 勾上“Ext Reset In”,连到板子按键

你想想看,如果没有AXI Interconnect,CPU核怎么访问内存?总不能直接连吧?那布线会乱成一锅粥。AXI总线的好处就是标准化,所有IP核都按这个协议来,省心。

第四步:编写顶层模块并连接

IP核都加好了,接下来写个顶层Verilog文件,把它们串起来。代码大概长这样:

module riscv_top (
    input  wire        clk_50m,
    input  wire        rst_n,
    output wire [3:0]  led
);

    // 时钟和复位
    wire        clk_cpu;
    wire        rst_cpu;
    
    clk_wiz_0 u_clk (
        .clk_in1  (clk_50m),
        .clk_out1 (clk_cpu)
    );
    
    proc_sys_reset_0 u_rst (
        .slowest_sync_clk (clk_cpu),
        .ext_reset_in     (rst_n),
        .peripheral_aresetn (rst_cpu)
    );
    
    // RISC-V处理器核
    vexriscv_wrapper u_cpu (
        .clk       (clk_cpu),
        .reset     (~rst_cpu),
        .axi_*     (/* 连到AXI Interconnect */)
    );
    
    // Block Memory
    blk_mem_gen_0 u_ram (
        .clka      (clk_cpu),
        .addra     (/* 地址总线 */),
        .dina      (/* 写数据 */),
        .douta     (/* 读数据 */)
    );
    
    // 简单外设:LED
    assign led = /* CPU写某个地址的值 */;

endmodule

这段代码里,我故意省略了一些连线细节。为什么?因为不同RISC-V核的接口名字不一样,你得看它的数据手册。VexRiscv的AXI接口叫“io_axi_awready”这种,PicoRV32的叫“mem_axi_awready”,别搞混了。

避坑指南: 我曾经在连接复位信号时犯过傻——CPU核是高电平复位,但板子按键是低电平有效。结果一上电CPU就复位,程序根本跑不起来。后来加了个反相器才搞定。所以,复位极性一定要确认清楚

第五步:添加约束文件

约束文件(XDC)是告诉Vivado你的信号对应板子上哪个引脚。比如:

# 时钟引脚
set_property PACKAGE_PIN E3 [get_ports clk_50m]
set_property IOSTANDARD LVCMOS33 [get_ports clk_50m]

# 复位引脚
set_property PACKAGE_PIN C12 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]

# LED引脚
set_property PACKAGE_PIN H5 [get_ports {led[0]}]
set_property PACKAGE_PIN H6 [get_ports {led[1]}]
set_property PACKAGE_PIN G5 [get_ports {led[2]}]
set_property PACKAGE_PIN G6 [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[*]}]

这里有个坑:时钟约束别忘了加。如果不加,Vivado会默认时钟周期是无穷大,综合出来的结果可能时序不收敛。加一句:

create_clock -period 20.000 [get_ports clk_50m]

20ns对应50MHz,如果你的板子时钟频率不同,自己换算一下。

第六步:综合与实现

点“Run Synthesis”,等几分钟。综合完成后,打开“Schematic”看看你的RISC-V核是不是正确连接了。我每次都会看一眼,确认IP核的接口没接错。

综合通过后,点“Run Implementation”。这一步会把你的设计映射到FPGA的查找表和触发器上。如果报时序违例,别慌,先看看是哪个路径。一般RISC-V核跑50MHz是没问题的,如果跑100MHz以上,可能需要优化代码或者降低频率。

实现完成后,点“Generate Bitstream”,生成比特流文件。下载到板子上,如果LED亮了或者串口打印了“Hello World”,恭喜你,RISC-V软核跑起来了!

核心要点: 搭建RISC-V软核工程,本质上就是“IP核拼积木”。CPU核、内存、总线、时钟、复位,这几块拼对了,剩下的就是调试。别怕出错,我刚开始搭的时候也烧了好几次板子(当然,烧板子是因为电源接反了,跟Vivado没关系)。

知识体系图:RISC-V软核工程搭建流程

RISC-V软核工程搭建流程 创建Vivado工程 选择器件、设置路径 添加RISC-V IP核 VexRiscv/PicoRV32 添加其他IP核 BRAM、AXI、时钟 编写顶层模块 连接所有IP核 添加约束文件 引脚分配、时钟约束 综合与实现 生成比特流 下载到FPGA板 验证LED/串口输出 关键点:IP核配置匹配、复位极性确认、时钟约束完整 常见问题:路径含中文、总线位宽不匹配、引脚约束遗漏

这张图把整个流程串起来了。你从左边开始,一步步走到右边,最后下载到板子。每一步都有坑,但踩过一次就记住了。

好了,这一节的内容就到这儿。你按照这个步骤走一遍,RISC-V软核工程就能搭起来。下一节咱们聊聊怎么往里面烧程序,以及如何用Vivado的仿真工具调试它。


公众号:蓝海资料掘金营,微信deep3321