4. 软核处理器总线接口与系统互联

在 Lattice FPGA 中搭建软核处理器系统,真正决定系统能否稳定工作的往往不是 CPU 内核本身,而是总线接口与互联结构。软核处理器(如 Lattice 的 RISC-V 核、Mico32 或用户自研核)通过片上总线连接指令存储器、数据存储器、外设寄存器与中断控制器。若总线协议、主从角色、地址译码或时序参数设置不当,常见现象是 CPU 能取指但读写外设失败、外设响应错位,或系统在综合后时序无法收敛。本节围绕 Wishbone、AXI、APB 三类典型片上总线,说明它们在 Lattice FPGA 中的实现方式,分析软核的主/从接口,并给出一个基于总线的 LED 与按键外设互联系统设计示例。

一、三类总线在 Lattice FPGA 中的定位

Wishbone 是开放、轻量的同步总线,信号少、握手简单,非常适合软核处理器与片上外设互联。它采用“主设备发起 STB/CYC,从设备以 ACK 应答”的握手方式,读写在同一个时钟域内完成,逻辑资源占用小,是 Lattice FPGA 中软核系统最常用的互联总线。AXI 是面向高性能的突发传输总线,具有独立的读地址、读数据、写地址、写数据、写响应五通道,支持乱序与 outstanding 传输,适合连接 DDR 控制器、高速数据通路等带宽敏感模块;在 Lattice 器件中通常用于较复杂的 SoC 或与硬核 IP 对接。APB 是 AXI 体系中的低带宽外设总线,协议最简单,只有 PSEL、PENABLE、PWRITE、PADDR、PWDATA、PRDATA、PREADY 等信号,适合挂接 UART、GPIO、定时器等慢速外设。

三者的典型分工是:软核处理器通过 Wishbone 或 AXI 作为主接口访问存储器和总线桥;总线桥将高速总线转换为 APB,再连接低速外设。这样既保证 CPU 取指与数据访问的效率,又让外设接口保持简单。需要强调的是,无论使用哪种协议,地址位宽、数据位宽、字节使能和时钟域必须与软核配置一致,否则会出现读写错位。

Lattice FPGA 软核系统总线互联结构 软核处理器 RISC-V / Mico32 主接口:取指 + 数据 指令/数据存储器 BRAM,从设备 0x0000_0000 起 Wishbone 互联 CYC / STB / ACK 地址译码 + 仲裁 单周期握手 AXI 高速通道 读/写地址、数据、响应 突发传输,接 DDR/高速 IP APB 外设总线 PSEL/PENABLE/PREADY GPIO、UART、定时器 总线桥 Wishbone → APB 地址重映射 位宽/时序适配 LED 与按键外设 GPIO 寄存器,从设备 0x8000_0000 起

二、软核处理器的主接口与从接口

软核处理器通常具有两类总线接口。主接口(Master)由 CPU 主动发起读写,用于取指令和访问数据存储器、外设寄存器。以 Wishbone 主接口为例,关键信号包括:wb_cyc_o 表示总线周期有效,wb_stb_o 表示选通,wb_we_o 表示写使能,wb_adr_o 为地址,wb_dat_o 为写数据,wb_sel_o 为字节选择,从设备通过 wb_ack_i 应答。CPU 发出地址后,必须等待 ACK 有效才能结束本次传输;若从设备未及时应答,CPU 会插入等待周期。

从接口(Slave)则出现在存储器与外设一侧。它接收主设备发来的地址、数据和选通信号,在地址命中时驱动读数据并返回 ACK。设计从接口时,地址译码是核心:每个从设备被分配一段唯一地址区间,只有落在该区间内的访问才被响应。例如指令 BRAM 分配 0x0000_0000–0x0000_3FFF,数据 BRAM 分配 0x1000_0000–0x1000_1FFF,GPIO 外设分配 0x8000_0000–0x8000_000F。若两个从设备地址重叠,总线会出现多设备同时驱动,导致读数据冲突。

在 Lattice 工具链中,主从接口通常以 IP 或参数化 HDL 模块形式提供。使用 Radiant 或 Diamond 的 IP Catalog 生成 Wishbone 互联时,需要设置数据位宽(常见 32 位)、地址位宽(常见 30 位或 32 位)、从设备数量与各自基地址。若软核配置为 32 位数据、字节寻址,则地址最低两位不参与译码,译码器应比较高位地址。

三、基于总线的 LED 与按键外设互联系统设计

下面给出一个可综合的互联示例。系统包含:软核 CPU(Wishbone 主接口)、指令 BRAM、数据 BRAM、GPIO 外设(含 LED 输出寄存器与按键输入寄存器)、Wishbone 互联与地址译码。地址分配如下表所示。

从设备基地址地址范围数据位宽说明
指令 BRAM0x0000_00000x0000_0000–0x0000_3FFF32 bit存放程序,只读为主
数据 BRAM0x1000_00000x1000_0000–0x1000_1FFF32 bit堆栈与变量
GPIO(LED/按键)0x8000_00000x8000_0000–0x8000_000F32 bit偏移 0x0 为 LED,0x4 为按键

GPIO 从接口的寄存器映射为:偏移 0x0 写 LED 输出寄存器,偏移 0x4 读按键输入寄存器。按键输入需要同步与消抖,建议在 GPIO 内部用两级触发器同步按键信号,再用计数器做约 10 ms 消抖(若时钟为 50 MHz,计数阈值约 500000)。LED 输出寄存器直接驱动引脚,注意 Lattice FPGA 的 IO 电平标准要与开发板一致,例如 3.3 V LVCMOS33。

地址译码逻辑可用组合逻辑实现,核心判断如下:

// 地址译码(示例,32 位地址,字节寻址)
wire sel_bram_i = (wb_adr[31:14] == 18'h00000); // 0x0000_0000–0x0000_3FFF
wire sel_bram_d = (wb_adr[31:13] == 19'h08000); // 0x1000_0000–0x1000_1FFF
wire sel_gpio   = (wb_adr[31:4]  == 28'h8000000); // 0x8000_0000–0x8000_000F

// 读数据多路选择
always @(*) begin
  case (1'b1)
    sel_bram_i: wb_dat_i = bram_i_dout;
    sel_bram_d: wb_dat_i = bram_d_dout;
    sel_gpio:   wb_dat_i = gpio_dout;
    default:    wb_dat_i = 32'h0000_0000;
  endcase
end

// ACK 生成:任一从设备命中即应答
assign wb_ack_i = sel_bram_i | sel_bram_d | sel_gpio;

软件侧通过指针访问外设,例如:

#define GPIO_BASE 0x80000000u
#define LED_REG   (*(volatile unsigned int *)(GPIO_BASE + 0x0))
#define KEY_REG   (*(volatile unsigned int *)(GPIO_BASE + 0x4))

int main(void) {
    while (1) {
        unsigned int key = KEY_REG & 0x1; // 读取按键
        if (key) LED_REG = 0x01;          // 按下点亮 LED0
        else     LED_REG = 0x00;
    }
}

验证时,先确认 CPU 能正确取指(观察指令 BRAM 读数据是否与编译结果一致),再通过写 LED 寄存器观察引脚电平变化,最后读取按键寄存器确认输入通路。若 LED 不亮,依次检查:地址译码是否命中、写使能与字节选择是否正确、IO 约束是否分配、按键消抖是否导致读值恒为 0。若读按键始终为全 1 或全 0,应检查同步触发器与消抖计数器是否复位正确。

在 Lattice FPGA 中,Wishbone 互联的 ACK 路径是组合逻辑关键路径之一。若时序不收敛,可在从设备读数据与 ACK 之间插入一级寄存器,把 ACK 改为寄存输出,代价是增加一个等待周期,但能显著改善建立时间。

综上,软核系统的总线互联应围绕“主接口发起、从接口应答、地址译码唯一、时序参数匹配”四个要点展开。Wishbone 适合轻量外设互联,AXI 用于高带宽通道,APB 用于低速外设;在 Lattice FPGA 中通过 IP 或参数化 HDL 实现时,务必核对数据位宽、地址位宽、字节使能与时钟域,才能让 LED、按键等外设稳定工作。