一、RISC-V 的核心构成:如何成为硬件与软件的桥梁?
RISC-V 处理器本质是用 Verilog 代码 "翻译" 了 RISC-V 指令集的每一条指令的执行逻辑,同时提供了软件(C 语言)可访问的 "接口"。具体来说,其核心模块的作用是:
1. 指令集的硬件实现:让 C 代码的操作有对应的硬件动作
C 语言中的每一个操作(如a + b
、if (x > 0)
、memcpy()
),最终都会被编译为 RISC-V 指令集中的一条或多条指令。而 RISC-V 处理器的硬件模块,就是为这些指令 "定制" 了执行逻辑:
- 算术逻辑单元(ALU,通常在
ex
模块中实现) :对应 C 语言中的+
、-
、*
、&
、|
等运算。例如,C 代码的c = a + b
会被编译为add rd, rs1, rs2
指令,ex
模块中的加法器硬件会执行具体的加法操作,并将结果写入目标寄存器(由regs
模块存储)。 - 分支判断单元(在
ex
模块中实现) :对应 C 语言中的if-else
、for
、while
等流程控制。例如,if (a > b)
会被编译为bgt rs1, rs2, label
指令,ex
模块会比较寄存器rs1
和rs2
的值,若满足条件则通过ex_jump_flag_o
信号通知pc_reg
模块跳转至label
对应的地址。 - 内存访问单元(在
ex
模块和内存接口中实现) :对应 C 语言中的变量读写、数组操作、函数调用时的栈操作等。例如,int x = arr[2]
会被编译为lw rd, 8(rs1)
(从基地址rs1
偏移 8 字节处读取数据),ex
模块通过rib_ex_addr_o
输出内存地址,由rib_ex_data_i
获取数据并写入寄存器。
这些硬件模块的逻辑,完全按照 RISC-V ISA 对每条指令的定义来实现(如操作码、操作数、结果存储方式等),确保软件编译出的指令能被硬件正确 "理解"。
2. 寄存器与存储器:软件操作的 "硬件载体"
C 语言中的变量、函数参数等数据,最终需要存储在硬件中,RISC-V 通过以下模块提供存储载体:
- 通用寄存器(
regs
模块) :C 语言中的局部变量、函数参数等临时数据,会被编译器分配到通用寄存器中。例如,int a = 10
可能被编译为li x5, 10
(将 10 写入寄存器x5
),regs
模块通过we_i
(写使能)、waddr_i
(地址)、wdata_i
(数据)信号接收并存储这个值,后续运算时再通过raddr1_i
读取。 - 控制状态寄存器(
csr_reg
模块) :C 语言中涉及系统级操作的代码(如中断处理、特权级切换),会通过特殊指令(如csrrw
)访问 CSR 寄存器。例如,开启中断的 C 代码asm("csrrsi mstatus, 0x8");
(设置mstatus
寄存器的中断使能位),csr_reg
模块会根据指令修改对应寄存器的值,硬件层面则通过csr_global_int_en_o
信号实际开启中断响应。 - 外部存储器接口(
rib_ex_*
和rib_pc_*
信号) :C 语言中的全局变量、数组、程序代码本身,会被存储在外部存储器(FPGA 上的 BRAM 或 DDR)中。处理器通过地址信号(rib_pc_addr_o
取指令地址、rib_ex_addr_o
取数据地址)访问这些存储单元,实现软件中 "变量读写" 与硬件中 "存储器访问" 的映射。
3. 控制与交互模块:软件流程的硬件调度
C 语言的执行流程(如函数调用、中断响应)需要硬件提供控制机制,RISC-V 通过以下模块实现:
- 程序计数器(
pc_reg
模块) :C 程序的顺序执行、跳转、函数调用等流程,本质是程序计数器(PC)的不断更新。例如,函数调用func()
会被编译为jal ra, func
指令,ex
模块计算跳转地址后,通过ctrl_jump_addr_o
通知pc_reg
模块更新 PC 值,实现程序流程的切换。 - 中断控制器(
clint
模块) :C 语言中的中断服务函数(如void irq_handler()
),需要硬件触发执行。当外部事件(如 FPGA 上的按键按下)产生中断信号int_i
,clint
模块会暂停当前程序,将 PC 跳转至中断服务函数的入口地址(由clint_int_addr_o
输出),执行完后再返回原程序,这一过程完全由硬件协调,但对应的中断处理逻辑由 C 语言编写。 - 调试接口(JTAG 相关逻辑) :开发者通过 C 语言调试工具(如 GDB)调试程序时,JTAG 接口允许软件直接访问硬件寄存器(
jtag_reg_addr_i
、jtag_reg_data_i
),实现 "单步执行""查看变量值" 等功能,本质是软件通过硬件接口直接操控处理器状态。
二、C 语言生成的 bin 文件在 FPGA 上的 RISC-V 中如何工作?
C 程序生成的 bin 文件在 FPGA 上的运行过程,本质是 "二进制指令加载→逐条执行" 的过程,具体步骤如下:
-
bin 文件的准备
C 代码通过 RISC-V 交叉工具链编译、链接,生成包含 RISC-V 指令的 ELF 文件。
2.bin 文件加载到 FPGA
- 固化存储:bin 文件可预先烧录到 FPGA 外部的 Flash 芯片中,FPGA 上电后自动将其加载到内部 BRAM 或外部 DDR 中。
- 在线加载:通过 JTAG 或 UART 接口,将 bin 文件实时传输到 FPGA 的内存地址空间(用openocd)
三、 C 程序在 RISC-V 中编写提前要想好干完的事?
要做的配置本质是让软件(C 代码)与 RISC-V 硬件的 "能力" 和 "规则" 对齐:
- 指令集对齐:通过 RISC-V 编译器,将 C 代码转换为硬件能执行的 RISC-V 指令(而非其他架构指令)。
- 地址空间对齐:通过链接脚本,确保程序加载到硬件可访问的内存地址(匹配 FPGA 上的存储器布局)。
- 运行环境对齐:通过启动代码,初始化硬件状态以满足 C 语言的运行需求(如栈、全局变量)。
- 外设交互对齐:通过内存映射 IO,让 C 代码能通过硬件支持的地址访问方式控制 FPGA 外设。