系统架构基础:SoC架构概览、总线协议、主从设备模型与中断机制

各位同学,今天我们聊聊系统架构基础。这部分内容,说白了就是芯片的“骨架”和“血管”。没有它,再牛的硬件加速引擎也跑不起来。

我个人习惯,在开始设计任何加速引擎之前,先花半天时间把SoC架构图画清楚。为什么?因为总线协议、地址映射这些,一旦定错,后面改起来就是伤筋动骨。

SoC架构概览:一个芯片就是一个“小社会”

SoC,System on Chip,片上系统。你可以把它想象成一个微型城市。

  • 处理器核心(CPU):市长,负责决策和调度。
  • 硬件加速引擎(我们设计的):特种部队,专门干脏活累活(比如加解密、编解码)。
  • 内存控制器:城市粮仓,存储数据。
  • 外设接口(UART、SPI、I2C):城门,与外界通信。
  • 总线系统:城市道路,连接所有模块。

我在项目中遇到过,有人把加速引擎直接挂在CPU的私有总线上,结果CPU跑一个中断,总线就堵死了。嗯,这里要注意:加速引擎通常挂在高速系统总线上,比如AXI总线。

核心原则: 加速引擎要“离数据近,离CPU远”。数据通路要宽,控制通路要简单。

CPU 核心 系统总线 (AXI/AHB) 硬件加速引擎 内存控制器 外设 (UART/SPI) APB 桥接 (低速外设总线) GPIO 定时器 看门狗 图:典型 SoC 架构(加速引擎挂载于高速总线)

总线协议:AXI / AHB / APB 怎么选?

ARM的AMBA总线是事实标准。我建议你记住一个口诀:AXI跑高速,AHB跑中速,APB跑低速

特性 AXI AHB APB
数据宽度 8~1024 bits 8~256 bits 8~32 bits
流水线 支持(分离地址/数据) 支持(简单流水) 不支持
突发传输 支持(INCR/WRAP/FIXED) 支持(INCR/WRAP) 不支持
典型应用 DDR、PCIe、加速引擎 SRAM、DMA GPIO、UART、定时器

为什么加速引擎要用AXI?因为AXI支持乱序传输分离事务。你想想看,加速引擎在处理数据时,CPU可以同时去读另一块数据,互不阻塞。我在项目中遇到过,用AHB总线挂一个加密引擎,结果每次加密都要等总线空闲,性能直接腰斩。

避坑指南: 我曾经在AXI总线上忘记处理WLAST信号,结果加速引擎写数据时,总线一直等最后一个数据,死锁了。嗯,AXI的握手信号(VALID/READY)一定要检查全。

主从设备模型:谁发起,谁响应?

总线上的设备分两种:主设备(Master)从设备(Slave)

  • Master:发起读写请求。比如CPU、DMA、我们的加速引擎(当它主动搬运数据时)。
  • Slave:响应请求。比如内存、寄存器、外设。

我建议你在设计加速引擎时,尽量让它做Slave。为什么?因为Master要处理总线仲裁、重试、超时,逻辑复杂度翻倍。除非你的引擎需要主动搬运大量数据(比如视频编解码),否则让CPU或DMA来做Master更稳妥。

举个例子,一个简单的加速引擎寄存器接口:

// AHB Slave 接口示例
module ahb_slave (
    input         HCLK,
    input         HRESETn,
    input         HSEL,
    input  [31:0] HADDR,
    input         HWRITE,
    input  [31:0] HWDATA,
    output [31:0] HRDATA,
    output        HREADY,
    output        HRESP
);
    // 寄存器地址映射
    localparam CTRL_ADDR = 8'h00;  // 控制寄存器
    localparam STATUS_ADDR = 8'h04; // 状态寄存器
    localparam DATA_ADDR = 8'h08;   // 数据寄存器

    reg [31:0] ctrl_reg, status_reg, data_reg;

    always @(posedge HCLK or negedge HRESETn) begin
        if (!HRESETn) begin
            ctrl_reg <= 32'h0;
            status_reg <= 32'h0;
            data_reg <= 32'h0;
        end else if (HSEL && HREADY) begin
            if (HWRITE) begin
                case (HADDR[7:0])
                    CTRL_ADDR: ctrl_reg <= HWDATA;
                    DATA_ADDR: data_reg <= HWDATA;
                endcase
            end else begin
                case (HADDR[7:0])
                    CTRL_ADDR: HRDATA <= ctrl_reg;
                    STATUS_ADDR: HRDATA <= status_reg;
                    DATA_ADDR: HRDATA <= data_reg;
                    default: HRDATA <= 32'h0;
                endcase
            end
        end
    end
endmodule

注意: 从设备必须在一个时钟周期内准备好数据(HREADY拉高),否则会插入等待状态。我曾经见过有人把从设备响应时间拖到10个周期,结果整个总线性能崩了。

中断与轮询机制:别让CPU空转

CPU怎么知道加速引擎干完活了?两种方式:轮询中断

  • 轮询:CPU不断读状态寄存器。简单,但浪费CPU。
  • 中断:引擎干完活主动通知CPU。高效,但需要中断控制器。

我个人习惯:控制通路用中断,数据通路用轮询。什么意思?比如你让引擎加密一块数据,完成后发中断通知CPU。但如果你要检查引擎是否空闲,轮询状态寄存器更快,因为中断有延迟。

我曾经在项目中,把中断信号直接连到CPU的IRQ引脚,没有经过中断控制器。结果多个中断源同时触发,CPU根本不知道谁发的。嗯,这里要注意:多中断源必须用中断控制器(GIC/PLIC)

地址映射与寄存器空间:给每个模块发“门牌号”

SoC中每个模块都有一段地址空间。比如:

起始地址 结束地址 模块 大小
0x4000_0000 0x4000_0FFF 加速引擎控制寄存器 4KB
0x4000_1000 0x4000_1FFF 加速引擎数据缓冲区 4KB
0x4000_2000 0x4000_2FFF 状态/中断寄存器 4KB

我建议你给每个加速引擎分配至少4KB地址空间。为什么?因为页表映射通常以4KB为单位。如果你只分配1KB,操作系统可能无法映射。

寄存器空间设计有个原则:读操作不能有副作用。什么意思?你读一个状态寄存器,不能改变它的值。我曾经见过有人把清除中断的操作用读实现,结果调试时一读就把中断清了,bug查了两天。

核心总结: 地址映射要连续,寄存器访问要幂等,中断信号要同步。

好了,系统架构基础就讲到这里。这些概念,你会在后续的加速引擎设计中反复用到。记住:架构设计决定了性能上限,代码实现只是把上限变成现实

个人经验: 我每次开始新项目,都会先画一张SoC架构图,标清楚每个模块的地址、中断号、总线接口。这张图,比任何文档都管用。


专注资料整理