4. 仿真验证入门

好,咱们今天来拆蜂鸟E203的RTL代码。说实话,我第一次拿到这套代码的时候,也被它的层次感惊到了——结构非常清晰,没有那种“屎山”的感觉。你想想看,一个开源RISC-V核能做到这种程度,确实值得好好学。

我个人习惯,拿到一个SoC项目,先看顶层,再往里钻。这样心里有个全局地图,不至于迷路。咱们今天就按这个思路来。

4.1 顶层模块:e203_soc_top

顶层模块叫 e203_soc_top,在 rtl/e203/soc 目录下。这个模块就是整个芯片的“外壳”,所有东西都挂在这下面。

核心要点:顶层只做三件事——例化核心、例化总线、例化外设。它不参与任何逻辑运算,纯粹是个“容器”。

我截一段它的端口定义给你看:

module e203_soc_top (
    // 系统时钟与复位
    input  clk,
    input  rst_n,
    
    // 外部中断
    input  ext_interrupt,
    
    // GPIO接口
    inout [31:0] gpio_io,
    
    // UART接口
    input  uart_rxd,
    output uart_txd,
    
    // QSPI Flash接口
    output qspi_cs,
    output qspi_clk,
    inout [3:0] qspi_dq
);

嗯,这里要注意:蜂鸟E203的顶层把所有外设接口都暴露出来了。你在FPGA上做调试的时候,这些引脚就是你跟芯片交互的窗口。我记得第一次在FPGA上跑E203,就是因为UART的引脚绑错了,折腾了半天才发现——这种低级错误,犯过一次就记住了。

4.2 处理器核心模块:e203_core

核心模块在 rtl/e203/core 目录下,文件名是 e203_core.v。这才是真正干活的地方。

说白了,CPU核心就是一条“指令流水线”。蜂鸟E203用的是2级流水线,比那些5级、7级的简单得多。但你别小看它,麻雀虽小五脏俱全。

子模块 功能 我踩过的坑
e203_ifu 指令取指单元 取指地址对齐问题,曾经导致指令错位
e203_exu 执行单元 分支预测失败时的流水线冲刷,时序容易出问题
e203_lsu 加载存储单元 非对齐访问会触发异常,调试时容易忽略
e203_csr 控制状态寄存器 CSR读写权限检查,漏掉会死锁

核心内部的数据流,我画了个图,你一看就明白:

蜂鸟E203处理器核心数据流 IFU 指令取指单元 EXU 执行单元 LSU 加载存储单元 CSR 控制状态寄存器 说明: 1. IFU从指令存储器取指令,送到EXU解码执行 2. EXU处理算术逻辑运算,LSU处理内存读写 3. CSR模块管理异常、中断、调试等控制状态 4. 所有模块通过内部总线与ICB总线桥接

调试小技巧:如果你在FPGA上发现程序跑飞了,先检查IFU的取指地址对不对。我曾经遇到一个案例,就是PC指针跳到了未初始化的内存区域,导致执行了非法指令。在Vivado里拉出IFU的波形,一眼就能看出来。

4.3 总线与外设模块

蜂鸟E203用的总线叫ICB(Internal Chip Bus),这是芯来科技自己定义的一套总线协议。说白了,它比AXI简单得多,但该有的功能都有。

总线模块在 rtl/e203/soc 目录下,叫 e203_soc_bus.v。它负责把核心和外设连起来。

4.3.1 ICB总线协议要点

  • 主从架构:核心是唯一的主设备,外设都是从设备
  • 地址映射:每个外设占用一段地址空间,通过地址译码选择
  • 握手机制:使用valid-ready握手,跟AXI的地址通道类似

我直接给你看地址映射表,这个在调试时特别有用:

外设 基地址 地址范围 说明
ITCM 0x0000_0000 64KB 指令紧耦合存储器
DTCM 0x8000_0000 64KB 数据紧耦合存储器
GPIO 0x1000_0000 4KB 通用输入输出
UART 0x1000_1000 4KB 串口通信
QSPI 0x1000_2000 4KB Flash控制器
CLINT 0x0200_0000 4KB 核心本地中断控制器
PLIC 0x0C00_0000 4MB 平台级中断控制器

注意:ITCM和DTCM的地址空间不连续!ITCM在低地址,DTCM在高地址。我曾经犯过一个错误,把数据段链接到了ITCM的地址空间,结果程序一跑就死。后来查了半天才发现是链接脚本写错了。

4.3.2 外设模块清单

蜂鸟E203的外设不多,但够用。每个外设的RTL代码都在 rtl/e203/soc 目录下:

  • GPIO:e203_gpio.v,32位双向IO,支持中断
  • UART:e203_uart.v,16550兼容,波特率可配
  • QSPI:e203_qspi.v,支持XIP(片上执行)
  • CLINT:e203_clint.v,处理软件中断和定时器中断
  • PLIC:e203_plic.v,管理所有外部中断的优先级和路由

我建议你重点看UART和GPIO的代码。为什么?因为这两个外设在调试时最常用。UART用来打印调试信息,GPIO可以用来点灯、测量时序。我记得有一次调试一个中断响应延迟的问题,就是用GPIO来测量——在中断服务函数里拉高一个GPIO,在逻辑分析仪上看波形,一目了然。

4.4 模块间的连接关系

最后,我总结一下整个SoC的模块连接关系。你把这个图记住,以后看代码就不会迷路:

蜂鸟E203 SoC模块连接关系 e203_core 处理器核心 e203_soc_bus ICB总线 GPIO 0x1000_0000 UART 0x1000_1000 QSPI 0x1000_2000 CLINT/PLIC 中断控制器 核心通过ICB总线访问所有外设,中断信号通过CLINT/PLIC直接连接到核心

嗯,看到这里,你应该对整个代码结构有个清晰的印象了。顶层模块负责“搭积木”,核心模块负责“算数”,总线模块负责“传数据”,外设模块负责“跟外界打交道”。

我个人觉得,蜂鸟E203的代码设计有一个很大的优点——模块划分非常干净。每个模块只做一件事,接口清晰,耦合度低。这种风格在调试时特别舒服,你定位问题的时候,可以快速锁定是哪个模块出了问题,不用在一堆纠缠不清的代码里翻来翻去。