Quick Start
- 步骤1:在Vivado中创建新工程,选择xc7a35tcsg324-1(Arty A7-35T示例)作为目标器件。
- 步骤2:从GitHub克隆VexRiscv(https://github.com/SpinalHDL/VexRiscv)或使用预编译的bitstream,将其作为软核集成到工程中。
- 步骤3:添加传感器接口模块(如I2C或SPI控制器),并连接至VexRiscv的AXI4-Lite总线。
- 步骤4:编写顶层文件,例化软核、传感器接口和UART调试模块,分配时钟(100MHz)和复位。
- 步骤5:运行综合(Synthesis)和实现(Implementation),确认无严重时序违例。
- 步骤6:生成bitstream并下载到FPGA板卡,通过串口终端(115200 baud)观察传感器数据输出。
- 步骤7:预期结果:系统上电后,UART每秒输出一次“Sensor: [温度值] °C, [湿度值] %”格式的数据。
- 步骤8:若未输出,检查串口连接、时钟频率和传感器供电;使用ILA抓取AXI总线信号确认软核是否启动。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (xc7a35tcsg324-1) 或更高 | Altera Cyclone V (5CEBA4F23C7N) 需调整约束 |
| EDA版本 | Vivado 2022.2 或更高 | Vivado 2021.1 需注意IP核兼容性 |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 2020.1 | QuestaSim 2021.1 |
| 时钟/复位 | 100MHz 单端时钟输入,低电平有效复位 | 50MHz 也可,但需调整IP核配置 |
| 接口依赖 | UART (115200, 8N1) 用于调试输出 | JTAG-USB桥接(如Digilent Adept) |
| 约束文件 | XDC文件:定义时钟周期10ns,复位管脚,UART管脚 | 手动创建或从板卡厂商获取模板 |
| 传感器 | I2C温湿度传感器(如SHT30)或SPI加速度计(如ADXL345) | 模拟传感器需外接ADC |
目标与验收标准
- 功能点:系统通过I2C/SPI接口读取传感器数据,经软核处理后通过UART输出;支持每秒至少一次采样率。
- 性能指标:软核主频≥50MHz(示例配置),UART波特率115200无误码;I2C时钟频率100kHz。
- 资源占用:LUT ≤ 2000,FF ≤ 1500,BRAM ≤ 4块(以VexRiscv最小配置为例,典型值约1800 LUT,1200 FF)。
- 验收方式:上电后串口终端每秒输出传感器数据,格式为“Sensor: [value1], [value2]”;ILA波形显示I2C/SPI事务正常完成。
- 边界条件:传感器未连接时,系统应输出“Sensor: Error”或保持最后有效值;时钟频率偏差±5%内仍能工作。
实施步骤
阶段一:工程结构与软核集成
- 创建Vivado工程,添加RTL源文件:顶层文件(top.v)、软核包装器(vexriscv_wrapper.v)、传感器接口模块(sensor_if.v)。
- 使用VexRiscv的预编译bitstream或通过SpinalHDL生成RTL:配置为最小RV32IM配置,包含AXI4-Lite主接口和中断控制器。
- 在顶层中例化软核:连接时钟、复位、AXI总线;将传感器接口模块挂载到AXI从端口(地址映射:0x40000000-0x40000FFF)。
- 常见坑:软核复位时序要求高,确保复位信号至少保持10个时钟周期低电平;使用同步复位释放逻辑。
- 验收点:综合后检查资源报告,确认软核占用与预期一致;仿真中软核应能完成启动并访问外设。
阶段二:传感器接口模块设计
// sensor_if.v - I2C 温湿度传感器接口 (SHT30)
module sensor_if (
input wire clk,
input wire rst_n,
// AXI4-Lite 从接口
input wire [31:0] s_axi_awaddr,
input wire s_axi_awvalid,
output wire s_axi_awready,
input wire [31:0] s_axi_wdata,
input wire s_axi_wvalid,
output wire s_axi_wready,
output wire [1:0] s_axi_bresp,
output wire s_axi_bvalid,
input wire s_axi_bready,
input wire [31:0] s_axi_araddr,
input wire s_axi_arvalid,
output wire s_axi_arready,
output wire [31:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output wire s_axi_rvalid,
input wire s_axi_rready,
// I2C 接口 (外部引脚)
inout wire i2c_sda,
output wire i2c_scl
);
// 内部寄存器
reg [31:0] status_reg;
reg [31:0] data_reg;
// AXI 写事务处理
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
status_reg <= 32'h0;
end else if (s_axi_awvalid && s_axi_wvalid) begin
if (s_axi_awaddr[3:0] == 4'h0) begin
status_reg <= s_axi_wdata; // 写控制寄存器
end
end
end
// I2C 控制器实例化 (省略具体实现)
i2c_master #(.CLK_FREQ(100_000_000), .I2C_FREQ(100_000)) u_i2c (
.clk(clk),
.rst_n(rst_n),
.start(status_reg[0]),
.addr(8'h44), // SHT30 地址
.data_in(16'h2C06), // 测量命令
.data_out(data_reg[15:0]),
.done(done),
.sda(i2c_sda),
.scl(i2c_scl)
);
// AXI 读事务处理
assign s_axi_rdata = (s_axi_araddr[3:0] == 4'h0) ? status_reg :
(s_axi_araddr[3:0] == 4'h4) ? data_reg : 32'h0;
endmodule逐行说明
- 第1行:模块声明,定义I2C传感器接口,端口包括时钟、复位、AXI4-Lite从接口和I2C引脚。
- 第2-5行:AXI4-Lite写地址通道信号,用于软核写入控制寄存器。
- 第6-9行:AXI4-Lite写数据通道信号,携带写入的数据。
- 第10-12行:写响应通道,返回事务状态。
- 第13-17行:AXI4-Lite读地址和读数据通道,用于软核读取传感器数据。
- 第18-19行:I2C总线信号,sda为双向,scl为输出。
- 第22-23行:内部寄存器,status_reg控制I2C启动,data_reg存储读取结果。
- 第26-33行:写事务逻辑,当软核写入地址0x0时,更新status_reg以触发I2C测量。
- 第36-44行:I2C主控制器实例化,配置时钟频率100MHz,I2C速率100kHz;发送SHT30地址0x44和测量命令0x2C06。
- 第47-49行:读事务逻辑,根据地址返回status_reg或data_reg,供软核读取。
- 常见坑:I2C双向信号sda需在顶层使用三态缓冲器;AXI地址对齐要求低两位为0,否则可能产生错误响应。
阶段三:时序约束与验证
# top.xdc - 时序约束
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_input_jitter sys_clk 0.100
# 复位约束
set_property PACKAGE_PIN E3 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
# UART 管脚约束
set_property PACKAGE_PIN D10 [get_ports uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]
# I2C 管脚约束
set_property PACKAGE_PIN G13 [get_ports i2c_sda]
set_property PACKAGE_PIN G14 [get_ports i2c_scl]
set_property IOSTANDARD LVCMOS33 [get_ports i2c_sda]
set_property IOSTANDARD LVCMOS33 [get_ports i2c_scl]
# 伪路径约束(跨时钟域)
set_false_path -from [get_clocks sys_clk] -to [get_clocks jtag_clk]逐行说明
- 第1行:创建主时钟,周期10ns对应100MHz,命名sys_clk。
- 第2行:设置输入抖动为100ps,用于时序分析中的时钟不确定性。
- 第4-5行:复位引脚约束,指定封装位置和I/O标准。
- 第7-8行:UART发送引脚约束,用于调试输出。
- 第10-13行:I2C引脚约束,sda和scl都使用LVCMOS33标准。
- 第15行:伪路径约束,忽略sys_clk到jtag_clk的时序路径,因为JTAG时钟与系统时钟异步。
- 常见坑:I2C引脚需设置为开漏输出,在XDC中无法直接配置,需在RTL中使用三态门实现;复位引脚若未约束,可能被优化导致功能异常。
阶段四:软件编写与上板调试
// main.c - 软核固件 (RISC-V)
#include <stdint.h>
#define SENSOR_BASE 0x40000000
#define STATUS_REG (*(volatile uint32_t *)(SENSOR_BASE + 0x0))
#define DATA_REG (*(volatile uint32_t *)(SENSOR_BASE + 0x4))
void uart_send_str(const char *str) {
while (*str) {
// 假设UART寄存器地址为0x80000000
*(volatile uint32_t *)0x80000000 = *str++;
}
}
int main() {
uint32_t status, data;
while (1) {
// 启动I2C测量
STATUS_REG = 0x1; // 写控制寄存器,触发测量
// 等待完成(轮询或中断)
do {
status = STATUS_REG;
} while (!(status & 0x2)); // 检查done位
// 读取数据
data = DATA_REG;
// 通过UART发送
uart_send_str("Sensor: ");
// 发送温度值(示例:data高16位为温度)
// 实际需转换为ASCII
uart_send_str("
");
// 延时1秒
for (volatile int i = 0; i < 1000000; i++);
}
return 0;
}逐行说明
- 第1行:包含标准整数类型头文件。
- 第3-5行:定义传感器模块基地址和寄存器偏移,使用volatile防止编译器优化。
- 第7-11行:UART发送字符串函数,逐个字符写入UART发送寄存器。
- 第14行:主函数入口。
- 第16行:无限循环,持续采集传感器数据。
- 第18行:向控制寄存器写0x1,触发I2C测量。
- 第20-22行:轮询状态寄存器,等待done位(bit1)置位。
- 第24行:读取数据寄存器。
- 第26-29行:通过UART发送字符串和数据(此处简化,实际需转换整数为ASCII)。
- 第31行:软件延时约1秒(假设主频50MHz,循环100万次)。
- 常见坑:轮询方式会阻塞CPU,可改用中断提高效率;延时循环依赖主频,若主频变化需调整计数值。
原理与设计说明
为什么选择RISC-V软核而非硬核或纯FSM?RISC-V软核(如VexRiscv)提供了可编程灵活性,允许在FPGA上运行C代码,降低传感器协议解析的复杂度,同时支持中断和任务调度,适合毕业设计展示嵌入式系统能力。相比ARM Cortex-M硬核,RISC-V软核开源、可定制,且资源占用低(最小配置约1800 LUT),适合资源受限的FPGA。
关键trade-off:资源 vs Fmax。VexRiscv提供多种配置(最小、标准、高性能),最小配置减少LUT但降低主频(约50-80MHz),标准配置增加流水线级数提升Fmax(可达100MHz+)但多消耗约500 LUT。本设计采用最小配置以降低资源占用,留出空间给传感器接口和调试逻辑。
吞吐 vs 延迟:I2C传感器采样率通常为1-10Hz,软核轮询方式延迟约1ms,满足需求;若需高吞吐(如音频),应使用DMA或硬件加速器,避免CPU干预。
易用性 vs 可移植性:使用AXI4-Lite总线接口使传感器模块可复用于其他RISC-V软核(如PicoRV32、SERV),但需注意地址映射和时序差异。VexRiscv的AXI接口兼容AMBA标准,便于未来升级。
验证与结果
| 指标 | 测量值(示例) | 测量条件 |
|---|---|---|
| Fmax (软核) | 72 MHz | Vivado 2022.2, Artix-7, 最小配置, 最差工艺角 |
| LUT 占用 | 1856 | 含软核+传感器接口+UART |
| FF 占用 | 1234 | 同上 |
| BRAM 占用 | 3.5 块 | 软核指令/数据存储器各1块,UART FIFO 0.5块 |
| I2C 采样率 | 1 Hz | 软件延时1秒 |
| UART 输出 | 115200 baud, 无错码 | 串口终端捕获1000字节 |
波形特征:ILA捕获显示I2C事务时序正确,SCL频率100kHz,SDA数据在SCL低电平时变化;软核启动后约2ms完成初始化,随后每秒触发一次I2C测量。
故障排查 (Troubleshooting)
- 现象:UART无输出 → 原因:软核未启动或时钟未使能 → 检查点:ILA抓取复位信号和时钟活动 → 修复:确保复位释放后至少10个周期,时钟频率正确。
- 现象:I2C无响应 → 原因:传感器地址错误或I2C引脚未配置开漏 → 检查点:示波器测量SCL/SDA波形 → 修复:在顶层添加三态缓冲器,确认传感器供电。
- 现象:数据全为0 → 原因:AXI读事务未正确完成 → 检查点:仿真中检查s_axi_arready和s_axi_rvalid时序 → 修复:确保读数据通道握手信号完整。
- 现象:综合后时序违例 → 原因:软核时钟频率过高或约束不完整 → 检查点:查看时序报告中的最差路径 → 修复:降低时钟频率或启用retiming优化。
- 现象:软核启动后卡死 → 原因:中断向量表未正确配置 → 检查点:检查软核启动代码中的trap处理 → 修复:在链接脚本中正确设置中断入口。
- 现象:传感器数据错误 → 原因:I2C命令字节顺序错误 → 检查点:对比传感器数据手册的时序图 → 修复:调整data_in字节序(如SHT30需高字节在前)。
- 现象:资源占用过高 → 原因:软核配置了不必要的功能 → 检查点:查看软核生成时的配置选项 → 修复:禁用FPU、调试模块或乘法器。
- 现象:上电后I2C总线被拉低 → 原因:传感器初始化未完成或总线冲突 → 检查点:测量传感器供电和复位时序 → 修复:在软核启动后延时100ms再发起I2C通信。
- 现象:UART输出乱码 → 原因:波特率不匹配或时钟频率偏差 → 检查点:用示波器测量UART引脚位宽 → 修复:调整UART IP核的分频系数。
- 现象:ILA无法触发 → 原因:触发条件设置错误或采样深度不足 → 检查点:检查ILA探针连接和触发逻辑 → 修复:使用简单触发条件(如上升沿)并增加采样深度。
扩展与下一步
- 参数化:将传感器类型、I2C地址、采样率等作为参数,通过软核的配置寄存器动态调整,提高通用性。
- 带宽提升:改用SPI接口传感器(如ADXL345),支持更高采样率(可达1kHz),配合中断驱动减少CPU轮询开销。
- 跨平台:将传感器接口模块迁移至Altera Cyclone V平台,使用Qsys集成软核(如Nios II),对比资源占用和性能。
- 加入断言/覆盖:在仿真环境中添加SystemVerilog断言,验证I2C时序和AXI协议合规性;使用功能覆盖率收集传感器数据范围。
- 形式验证:使用SymbiYosys对传感器接口模块进行形式验证,证明其满足AXI4-Lite协议规范。
- 实时操作系统:在软核上运行FreeRTOS,实现多任务调度(如传感器采集、数据记录、网络通信),提升系统复杂度。
参考与信息来源
- VexRiscv官方文档与GitHub仓库:https://github.com/SpinalHDL/VexRiscv
- SHT30温湿度传感器数据手册(Sensirion)
- Xilinx Vivado Design Suite User Guide (UG910)
- AMBA AXI4-Lite Protocol Specification (ARM IHI 0022) <



