RISC-V软核GPIO(内存映射)仅输出首个值后续失败问题排查
问题描述
基于PICO-SoC搭建的运行在PYNQ FPGA板上的RISC-V软核SoC,实现了地址为0x00000090的内存映射输出端口。通过软核的Python笔记本编译执行C程序时,仅第一个值7能输出到端口,后续9、15无输出,且程序返回值错误,测试失败;注释GPIO写入代码后程序正常执行。相关代码如下:
C程序代码
%%riscvc test overlay.tutorialProcessor #include <stdint.h> #include <stdbool.h> #include <unistd.h> #include <time.h> #define reg_outp (*(volatile int*)0x00000090) int main(int argc, char ** argv) { unsigned int * arr = (unsigned int *)argv[1]; reg_outp = 7; reg_outp = 9; reg_outp = 15; return arr[2]; }
Python执行代码
import numpy as np arg1 = np.array([4,2,3], np.uint32) retval = overlay.tutorialProcessor.run(test, arg1) if (retval != arg1[2]): print("Test failed!") else: print("Test passed!")
Verilog实现代码
always @(posedge clk) begin if (!resetn) begin gpio <= 0; end else begin rmem_ready <= 0; if (mem_valid && !mem_ready && mem_addr[31:0] == 32'h 00000090) begin rmem_ready <= 1; if (mem_wstrb[0]) gpio[ 7: 0] <= mem_wdata[ 7: 0]; if (mem_wstrb[1]) gpio[15: 8] <= mem_wdata[15: 8]; if (mem_wstrb[2]) gpio[23:16] <= mem_wdata[23:16]; if (mem_wstrb[3]) gpio[31:24] <= mem_wdata[31:24]; end end end
疑问点
- 是否因程序触发异常提前退出导致后续值无法输出?
- 可能的故障原因是什么?
0x00000090是否属于代码区域?- 该到哪里查看内存映射?
- 输出端口应映射到哪个内存区域?
问题解答
1. 是否因异常提前退出?
是。从现象看,第一次写端口后程序大概率触发了总线错误、地址非法之类的异常,导致后续代码完全没执行,返回值也出错——毕竟注释GPIO写入逻辑后程序能正常跑,说明问题根源就在端口访问操作上。
2. 可能的故障原因
- 地址冲突:
0x00000090属于低地址段,大概率和SoC的代码/数据内存区域重叠,直接写这个地址会覆盖程序指令或触发内存访问保护,引发异常。 - 总线响应不完整:Verilog代码只处理了对
0x00000090的写请求并拉rmem_ready,但如果CPU因异常处理或其他操作发起对该地址的读请求,模块没有返回rmem_ready,会导致总线挂死或超时异常。 - 内存属性不匹配:该地址未被配置为IO专用区域,CPU默认按普通内存访问,可能存在缓存、访问顺序等问题,而IO端口需要非缓存、强顺序的访问属性(虽然C代码加了
volatile,但硬件端的内存映射配置也要对应)。
3. 0x00000090是否属于代码区域?
大概率是。PICO-SoC的默认内存映射中,起始地址0x0开始的区域是ROM/RAM,用于存放程序代码和数据。0x00000090处于低地址段,肯定在代码/数据内存的范围内,把IO端口映射到这里必然会和程序的指令、数据重叠,访问时触发异常。
4. 查看内存映射的位置
- 查阅PICO-SoC官方文档或你搭建SoC时的内存规划表,里面会明确ROM、RAM、外设IO等各个区域的地址范围。
- 查看PYNQ软核的链接脚本(如
linker.ld),脚本里会定义代码段、数据段、外设段的具体地址分配。 - 检查FPGA工程中的地址解码模块,看外设地址的分配规则和映射范围。
5. 输出端口应映射到的内存区域
必须映射到外设专用的MMIO(内存映射IO)地址段,也就是SoC规划中专门留给外设的区域,一般是高地址段(比如0x80000000及以上,具体以PICO-SoC的配置为准)。这个区域不会和代码、数据内存重叠,且硬件会配置对应的非缓存、允许IO访问的总线属性。
比如可以把端口地址改成0x80000000,同时修改Verilog中的地址判断条件,确保CPU访问该地址时能正确响应。
内容的提问来源于stack exchange,提问作者karthik
相关产品推荐
相关产品推荐

