第4章:AHB总线详解
4.1 AHB总线架构与组件
AHB,全称Advanced High-performance Bus。说白了,它是ARM公司AMBA总线体系里的「高速公路」。我刚开始接触SoC设计时,总觉得总线这东西不就是连来连去嘛。后来踩了坑才明白——AHB的设计哲学,其实是在性能和复杂度之间找平衡。
一个典型的AHB系统,由这么几个核心组件构成:
- 主机(Master):发起传输的模块。比如CPU、DMA控制器。我习惯把主机想象成「发号施令的人」。
- 从机(Slave):响应传输的模块。比如SRAM、外设寄存器。它们是被动方,听主机指挥。
- 仲裁器(Arbiter):决定哪个主机能占用总线。嗯,这里有个坑——仲裁策略选不好,系统性能直接腰斩。
- 译码器(Decoder):根据地址判断该访问哪个从机。说白了就是「指路牌」。
- 多路选择器(Mux):把多个主机的信号和多个从机的响应,正确地连到一起。
你想想看,如果总线上只有一个主机,那仲裁器和Mux都可以省掉。但现实SoC里,CPU、GPU、DSP、DMA抢着用总线,没有仲裁器就乱套了。
4.2 AHB传输操作
4.2.1 单次传输
单次传输是最基本的操作。一次传一个数据,一拍地址,一拍数据。时序上分两个阶段:
- 地址阶段:主机把地址、控制信号(比如读写方向、传输大小)放到总线上。
- 数据阶段:从机响应,数据在总线上传输。如果从机来不及处理,可以插入等待周期(HREADY拉低)。
我在项目中遇到过一个问题:某个从机响应特别慢,每次都要等好几个周期。结果整个总线都被拖慢了。后来我加了个FIFO做缓冲,才把问题解决。
4.2.2 突发传输
突发传输,说白了就是一次地址,连续传多笔数据。AHB支持4拍、8拍、16拍,以及未定长度的增量突发。
突发类型有几种:
- INCR4/INCR8/INCR16:固定长度的增量突发。地址依次递增。
- WRAP4/WRAP8/WRAP16:回环突发。地址递增到边界后折回。常用于Cache line fill。
- INCR:未定长度突发。从机不知道什么时候结束,直到主机发LAST信号。
举个例子,CPU做Cache line fill时,通常用WRAP4突发。一次读4个字,地址从某个对齐边界开始,到边界后折回。这样做的好处是——地址计算简单,硬件实现也省资源。
关键点:突发传输中,地址只发一次,后续数据按固定规则递增。这能大幅减少地址阶段的占用,提升总线利用率。
4.2.3 拆分传输
拆分传输是AHB里一个比较高级的特性。当从机需要很长时间准备数据时,它可以把当前传输「拆开」,让总线先去服务其他主机。
具体流程是这样的:
- 主机发起传输,从机回复SPLIT响应。
- 仲裁器把总线让给其他主机。
- 从机准备好后,通知仲裁器。
- 仲裁器重新把总线交给原主机,继续未完成的传输。
我曾经在一个多主机的视频处理芯片里用过SPLIT。当时有个DMA要从DDR读数据,DDR延迟很大。如果不做SPLIT,CPU就得一直等着。用了SPLIT后,CPU可以在DMA等数据的时候继续干活,系统吞吐量提升了将近30%。
注意:SPLIT虽然好用,但实现起来比较复杂。仲裁器需要记住哪个主机被拆分了,从机也要有额外的逻辑来通知仲裁器。小系统不建议用,性价比不高。
4.3 AHB仲裁与优先级
仲裁,说白了就是「谁先上」的问题。AHB支持多种仲裁策略:
- 固定优先级:每个主机有固定优先级。高优先级的主机永远先获得总线。简单,但不公平。
- 轮询(Round-Robin):主机轮流使用总线。公平,但高优先级任务可能被延迟。
- 混合策略:比如高优先级用固定,低优先级用轮询。我比较喜欢这种,灵活。
仲裁的时机也很关键。AHB规定,仲裁结果在地址阶段决定。也就是说,当前传输的数据阶段还没结束,仲裁器就已经在决定下一个传输归谁了。这叫「流水线仲裁」,能提高总线利用率。
嗯,这里要注意——仲裁器不能频繁切换主机。如果每个传输都换人,总线效率反而会下降。因为每次切换都有「气泡」(空闲周期)。我建议在仲裁器里加个「保持计数器」,让同一个主机连续做几次传输再切换。
4.4 AHB从机与主机接口设计
4.4.1 主机接口
设计AHB主机接口,核心是处理好这几个信号:
| 信号 | 方向 | 说明 |
|---|---|---|
| HADDR | 输出 | 地址总线,32位 |
| HWDATA | 输出 | 写数据总线 |
| HRDATA | 输入 | 读数据总线 |
| HWRITE | 输出 | 1表示写,0表示读 |
| HSIZE | 输出 | 传输大小(字节、半字、字) |
| HBURST | 输出 | 突发类型 |
| HREADY | 输入 | 从机准备好信号 |
| HRESP | 输入 | 从机响应(OKAY、ERROR、RETRY、SPLIT) |
设计主机接口时,我习惯用状态机来控制。状态机至少要有这几个状态:
- IDLE:空闲,没有传输请求。
- ADDR:发送地址和控制信号。
- DATA:等待数据阶段完成。
- WAIT:从机插入等待周期。
代码示例(简化版):
// AHB Master FSM - 简化版
always @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn) begin
state <= IDLE;
end else begin
case (state)
IDLE: if (req) state <= ADDR;
ADDR: state <= DATA;
DATA: if (HREADY) begin
if (burst_done) state <= IDLE;
else state <= ADDR; // 继续下一拍突发
end
endcase
end
end
个人经验:设计主机接口时,别忘了处理从机的ERROR响应。我曾经有个项目,从机返回ERROR后主机没做任何处理,结果数据写错了都不知道。建议至少加个中断或者状态寄存器,记录错误信息。
4.4.2 从机接口
从机接口相对简单一些。核心是响应主机的请求,返回数据和状态。
设计从机接口时,有几个要点:
- 地址译码:判断当前访问是否在自己的地址范围内。
- 等待机制:如果来不及响应,拉低HREADY插入等待周期。
- 响应生成:正常返回OKAY,出错返回ERROR。
- 数据对齐:根据HSIZE和HADDR,正确选择数据字节。
举个例子,一个简单的SRAM从机接口:
// AHB Slave - SRAM接口简化版
assign HSEL = (HADDR >= BASE_ADDR) && (HADDR < BASE_ADDR + SIZE);
assign HREADY = 1'b1; // 假设SRAM可以零等待
assign HRESP = OKAY;
// 写操作
always @(posedge HCLK) begin
if (HSEL && HWRITE && HREADY)
mem[HADDR[ADDR_WIDTH-1:2]] <= HWDATA;
end
// 读操作
assign HRDATA = (HSEL && !HWRITE) ? mem[HADDR[ADDR_WIDTH-1:2]] : 32'b0;
避坑指南:我曾经设计过一个从机,忘了处理HSIZE信号。结果主机发半字写操作时,从机把整个字都写了。相邻的寄存器被意外修改,查了两天才找到原因。记住——一定要根据HSIZE和HADDR的低位,正确屏蔽不需要的字节。
好了,AHB总线的核心内容就这些。下一章我们会讲AHB的时序分析和常见问题排查。到时候我会分享一个实际项目中的总线死锁案例,挺有意思的。