ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RISC-V MCU子系统设计:ITCM、DTCM与Slave-AHB接口的工程实践与避坑指南

RISC-V MCU子系统设计:ITCM、DTCM与Slave-AHB接口的工程实践与避坑指南 RISC-V核越做越多真正让系统跑得稳、跑得快的那几个模块往往不是流水线本身而是紧耦合存储和从机总线接口这类“配角”。我最近在整理一个基于RISC-V的MCU子系统时重新把ITCM、DTCM和Slave-AHB这三块翻出来啃了一遍发现很多刚接触RISC-V IP集成的朋友对它们的理解还停留在“一块RAM加一个总线口”的层面结果一到实际跑代码就出问题中断响应忽快忽慢、DMA搬数据搬出脏数据、多主机访问直接死锁。这篇就把我踩过的坑和验证过的设计思路完整摊开讲一遍从存储映射、接口时序到仲裁逻辑尽量说到能直接照着改RTL的程度。1. 先把ITCM和DTCM的定位掰清楚1.1 它们不是普通SRAM别按Cache的思路去理解很多人第一次看到ITCMInstruction Tightly Coupled Memory和DTCMData Tightly Coupled Memory会下意识把它当成“小容量Cache”或者“带地址映射的SRAM”。这个理解偏差是后面一系列设计问题的根源。TCM的本质是紧耦合存储它直接挂在CPU核的存储访问通路上不经过Cache的标签比对、不参与替换算法访问延迟是确定的、可预测的。这一点和Cache有本质区别Cache命中与否是概率事件而TCM的访问周期在综合后基本是固定值通常是1到2个时钟周期。为什么这个区别重要因为实时性要求高的场景——比如电机控制里的电流环中断、通信协议栈的收发缓冲——最怕的就是“最坏执行时间”不可控。Cache的miss会导致执行时间抖动而TCM不会。所以ITCM通常用来放中断向量表、关键中断服务程序、实时性要求极高的循环体DTCM则放频繁访问的栈、全局变量、DMA描述符这类数据。从微架构角度看ITCM一般接在取指通路上和I-Cache并列或者替代I-CacheDTCM接在访存通路上和D-Cache并列。有些低功耗核干脆不做Cache只用TCM这样面积小、功耗低、时序确定代价是容量受限、需要软件手动管理。1.2 地址映射与容量规划的实际取舍TCM的地址映射通常有两种做法一种是固定在核的地址空间某一段比如0x0000_0000起始放ITCM0x2000_0000起始放DTCM另一种是通过一个基址寄存器可配置让SoC集成者灵活摆放。我倾向于固定基址加可配置容量的方案原因是固定基址能让链接脚本和启动代码稳定可配置容量则方便不同型号复用同一套RTL。容量规划上有个经验值ITCM一般4KB到64KBDTCM一般4KB到128KB。为什么DTCM往往更大因为栈、堆、频繁读写的全局变量都堆在DTCM里尤其是带RTOS的系统每个任务的栈加起来很容易超过ITCM的代码量。我做过一个带Modbus协议栈的项目ITCM只用了8KB就放下了全部中断处理和协议解析但DTCM用了32KB还紧张因为光任务栈就占了16KB。这里有个容易忽略的点TCM的容量必须是2的幂次且地址要自然对齐。如果你规划了12KB的DTCM实际综合工具会给你按16KB处理多出来的4KB要么浪费要么映射到别的区域容易出诡异问题。所以规划时直接按2的幂次来别给自己找麻烦。1.3 访问时序与流水线的配合TCM和流水线的配合是设计里最微妙的部分。以经典的五级流水线取指、译码、执行、访存、写回为例ITCM的取指要在取指级完成DTCM的访存要在访存级完成。如果TCM是单周期访问那流水线可以全速跑如果TCM需要两个周期就要在流水线里插入等待状态或者用预取机制掩盖。我实测过一个坑某核的ITCM设计成两周期访问但取指级没有做预取结果每条指令都要等一个周期IPC直接掉到0.5。后来在ITCM输出加了一级预取寄存器把下一个取指地址的数据提前读出来IPC才恢复到接近1。这个预取逻辑不复杂就是一个地址加4的预读加一个多路选择但没有它性能差一倍。DTCM这边要注意的是读写冲突。如果一条指令在访存级读DTCM同时另一条指令在写回级写DTCM单端口TCM就会冲突。解决办法要么用双端口TCM面积翻倍要么做仲裁让一个先等一个周期。我的做法是给DTCM加一个写缓冲写操作先入缓冲读操作优先这样大部分情况下读不会被写阻塞。写缓冲深度4到8就够太深了反而增加时序压力。2. Slave-AHB接口到底在系统里扮演什么角色2.1 它是CPU之外所有主设备访问TCM和寄存器的入口Slave-AHB接口顾名思义是RISC-V核作为一个AHB从设备时对外暴露的接口。但这里有个常见误解很多人以为这个接口只是给DMA用的。实际上在一个典型的MCU子系统里通过Slave-AHB访问核内资源的主设备可能包括DMA控制器、以太网MAC、USB控制器、另一个CPU核多核场景、调试模块。这些主设备都要通过Slave-AHB来读写DTCM里的数据缓冲区、配置核内的控制寄存器、甚至往ITCM里加载代码。所以Slave-AHB不是一个“可选接口”而是核与系统交互的关键通路。它的设计质量直接决定了DMA效率、多核通信延迟、调试体验。我见过一个设计Slave-AHB的写响应要等16个周期才返回结果DMA搬一块数据要反复等响应吞吐量只有理论值的四分之一。后来把写响应改成缓冲后立即返回吞吐量直接上去了。2.2 AHB-Lite与AHB5的选型差异Slave-AHB接口可以基于AHB-Lite或AHB5实现。AHB-Lite是单主机协议但作为从设备接口它其实可以接在多主机系统的互连矩阵上由矩阵来仲裁。AHB5增加了TrustZone、安全属性、独占传输等特性。对于大多数RISC-V MCU场景AHB-Lite足够用因为安全隔离通常由外部的MPU或总线矩阵来做核内的Slave-AHB不需要自己处理。但如果你的系统要做安全启动、安全调试AHB5的安全信号就有价值了。选型时问自己三个问题系统里有没有多主机需要访问核内资源有没有安全隔离需求总线矩阵支持哪种协议三个问题的答案基本就决定了选AHB-Lite还是AHB5。2.3 接口信号的最小集合一个能工作的Slave-AHB接口信号可以分成几组。地址和控制组包括HADDR、HTRANS、HWRITE、HSIZE、HBURST、HPROT数据组包括HWDATA、HRDATA响应组包括HREADY、HRESP还有选择信号HSEL。时钟复位就是HCLK、HRESETn。这里要强调的是HSEL的处理。HSEL来自总线矩阵的地址译码只有HSEL有效时接口才响应。很多bug出在HSEL和HTRANS的配合上HTRANS为NONSEQ或SEQ时才是有效传输IDLE和BUSY时不应该产生任何动作。我调试过一个死锁就是因为接口在HTRANS为BUSY时错误地拉低了HREADY导致主机一直等。记住一个原则只有在有效传输且HSEL有效时才去操作TCM或寄存器其他情况HREADY保持高、HRESP保持OKAY。3. 地址译码与存储映射的落地细节3.1 一张地址映射表要覆盖哪些区域Slave-AHB接口收到的地址需要译码到不同的目标DTCM、ITCM如果允许外部写入、控制寄存器组、调试寄存器。我习惯先画一张完整的地址映射表把每个区域的基址、大小、访问属性列清楚。下面是一个典型例子区域基址大小可读可写说明DTCM0x2000_000064KB是是数据紧耦合存储ITCM0x0000_000032KB是条件外部加载时允许写控制寄存器0xE000_00004KB是是核配置、状态调试寄存器0xE000_10004KB是是断点、观察点这张表要同步给软件团队链接脚本、启动代码、驱动都依赖它。我踩过的坑是RTL改了地址映射但没同步文档软件按旧地址访问读回来全是0查了两天才发现是映射对不上。3.2 译码逻辑的组合路径与时序地址译码本身是纯组合逻辑比较地址高位就能得出片选。但要注意组合路径不能太长。如果地址位宽32位比较器级数多了会拖慢时序。我的做法是分段译码先比较高8位确定大区域再比较中间位确定子区域最后用低位做偏移。这样每级比较器位宽小路径短。另一个细节是默认从设备的响应。如果地址落在没有映射的区域接口不能挂死要返回一个错误响应HRESP为ERROR。很多设计忘了这个结果软件访问了非法地址总线一直等HREADY整个系统卡死。加一个默认从设备检测到无匹配时返回ERROR能让软件通过异常快速定位问题。3.3 字节使能与非对齐访问的处理AHB支持字节写通过HSIZE和地址低位组合出字节使能。比如HSIZE为WORD32位但地址低两位不为0就是非对齐访问。RISC-V核通常不支持非对齐访问但Slave-AHB作为从设备可能收到主设备发来的非对齐传输。这时候有两种处理一是拆分成多次对齐访问二是返回ERROR。我倾向于返回ERROR因为拆分逻辑复杂且容易出错而且非对齐访问在规范里本来就是低效的。但要注意有些DMA控制器会发非对齐传输如果直接返回ERRORDMA就报错了。所以更稳妥的做法是在接口里做对齐检查非对齐时返回ERROR并在状态寄存器里记录让软件知道是哪个主设备发的。字节使能的生成逻辑要仔细HSIZE为BYTE时只有对应地址的那一个字节使能HALFWORD时两个字节WORD时四个字节。写DTCM时字节使能直接接到SRAM的写掩码读的时候要根据HSIZE和地址低位把数据对齐到正确的字节通道。这里容易出bug的是读数据的对齐比如读一个BYTE但地址是0x2000_0003返回的数据应该在最低字节其他字节补0或保持。我见过一个设计读BYTE时把数据放在最高字节软件读出来全是0查了半天。4. 读写通路与仲裁逻辑的设计4.1 单端口TCM如何应对多主机并发DTCM通常是单端口SRAM但Slave-AHB和CPU核都会访问它。这就需要一个仲裁器来决定每个周期谁访问。仲裁策略直接影响性能如果CPU核优先级高DMA访问就会被频繁打断吞吐量下降如果DMA优先级高CPU的实时性就受影响。我的做法是动态优先级加时间片。CPU核的访问请求如果连续被阻塞超过一定周期数比如8个周期就提升它的优先级DMA的突发传输则保证一个burst内不被CPU打断避免DMA频繁重试。这样既保证了CPU的实时性又让DMA能高效搬数据。仲裁器的实现要注意请求和授权的时序。CPU的访存请求在访存级发出Slave-AHB的请求在HREADY为高时采样。仲裁器要在每个周期开始前决定授权授权信号要在一个周期内稳定。如果仲裁逻辑太复杂组合路径长会拖慢整个TCM的访问频率。我一般把仲裁逻辑做成寄存器输出虽然多一个周期延迟但时序好收敛。4.2 写缓冲与读优先策略前面提到DTCM加写缓冲这里展开说。写缓冲的作用是让CPU或DMA的写操作快速完成不用等SRAM实际写入。写缓冲的深度和宽度要匹配宽度就是数据位宽通常32位深度4到8。写缓冲满的时候新的写请求要等这时候HREADY拉低主机等待。读优先策略是指当读和写同时请求时优先处理读。因为读通常是阻塞的——CPU读不到数据就没法继续执行而写可以缓冲。但要注意读后写和写后读的顺序问题。如果软件先写一个地址再读同一个地址写缓冲还没落盘读就会读到旧数据。解决办法是读的时候检查写缓冲里有没有同地址的未完成写有的话要么等写完成要么直接从写缓冲转发数据。这个转发逻辑叫store-to-load forwarding能显著提升性能但增加设计复杂度。我的经验是如果软件不依赖这种紧耦合的读写顺序可以不做转发但要在文档里写清楚让软件避免这种模式。4.3 突发传输的支持与边界处理AHB支持突发传输包括INCR、WRAP4、INCR4、INCR8、INCR16等。Slave-AHB接口要不要支持突发如果DMA要高效搬数据支持INCR突发是必须的。WRAP突发主要用于Cache行填充TCM场景下用得少可以不支持。支持突发时要注意边界不能跨。AHB规范规定1KB边界是突发的自然边界突发传输不能跨越1KB边界。接口里要检查地址加传输长度是否跨边界跨了就拆分或者返回ERROR。我见过一个DMA配置成INCR16但起始地址在1KB边界附近结果传输跨了边界接口没处理数据写到了错误的地址。后来加了边界检查跨边界时把突发拆成两个问题解决。突发传输的地址递增逻辑也要注意INCR突发的地址每个beat递增递增的步长由HSIZE决定。WRAP突发的地址在回绕点要回到起始地址。这些逻辑用状态机实现比较清晰状态机里记录当前beat计数、起始地址、回绕地址。5. 中断、调试与低功耗场景下的接口行为5.1 中断向量从ITCM取指的路径优化中断响应速度是实时系统的关键指标。RISC-V核的中断入口通常由mtvec寄存器指定如果mtvec指向ITCM中断发生时取指直接从ITCM走不经过Cache延迟确定。但这里有个优化点中断向量表可以放在ITCM的最前面且每个向量项只放一条跳转指令这样取指一次就能跳到实际的中断服务程序。如果向量项里放的是完整的中断服务程序取指要多次访问ITCM响应就慢了。我实测过向量表放ITCM且用跳转指令的方式中断响应延迟比向量表放Flash的方式快3到5倍。对于电机控制这种要求微秒级响应的场景这个差距是决定性的。5.2 调试模块通过Slave-AHB访问TCM的注意事项调试模块比如JTAG调试器通常也通过Slave-AHB来读写TCM实现断点设置、变量观察、代码下载。这里要注意调试访问和CPU访问的冲突。调试器写TCM时CPU可能正在执行同一块代码或读写同一块数据。如果调试器改了代码CPU的流水线里可能已经有预取的旧指令需要冲刷流水线。我的做法是调试写ITCM后产生一个流水线冲刷信号让CPU重新取指调试写DTCM后如果CPU有Cache有些核D-Cache和DTCM并存要无效化对应的Cache行。这些信号在Slave-AHB接口里生成通过核内部的调试接口传给流水线控制逻辑。另一个坑是调试访问的字节序。调试器通常按字节访问但TCM是32位宽字节使能要正确。我见过调试器写一个字节结果整个字都被改了就是因为字节使能没接对。5.3 低功耗模式下TCM的保持与Slave-AHB的响应低功耗模式下CPU核可能断电或时钟门控但DTCM里的数据可能需要保持。这时候Slave-AHB接口的行为要明确如果核断电了Slave-AHB还能不能响应如果DMA还要访问DTCMDTCM的电源就不能断。我的设计里DTCM通常放在一个可保持电源域Slave-AHB接口的逻辑放在常开域这样即使CPU核断电DMA仍能访问DTCM。但要注意跨电源域的握手。Slave-AHB的请求从常开域传到CPU域的TCM控制器需要同步器响应从CPU域传回常开域也需要同步器。同步器的宽度和深度要匹配否则会丢请求或死锁。低功耗下还有一个细节时钟门控时的HREADY。如果Slave-AHB的时钟被门控了HREADY要保持高还是低保持高的话主机以为传输完成了但实际没完成保持低的话主机一直等。正确做法是在进入低功耗前确保没有未完成的传输然后HREADY保持高主机的新请求会被忽略或返回ERROR。这个握手协议要在系统层面约定好。6. 实测中容易翻车的几个设计细节6.1 HREADY与HRESP的配合时序HREADY和HRESP的配合是AHB从设备设计里最容易出错的地方。规范要求HRESP在传输的第一个周期给出HREADY在最后一个周期拉高表示完成。对于OKAY响应HRESP为0HREADY通常一直为高零等待或拉低若干周期有等待。对于ERROR响应HRESP为1需要两个周期第一个周期HRESP为1、HREADY为0第二个周期HRESP为1、HREADY为1。我调试过一个bugERROR响应只给了一个周期结果主机没识别到错误继续发下一个传输状态机乱了。后来严格按两周期实现问题解决。还有OKAY响应时HREADY拉低的周期数要和实际等待周期一致多一个少一个都会导致数据采样错位。6.2 地址相位与数据相位的区分AHB的传输分地址相位和数据相位。地址相位里主机给出HADDR、HTRANS、HWRITE、HSIZE等数据相位里进行数据传输。对于写操作HWDATA在数据相位给出对于读操作HRDATA在数据相位返回。从设备要在地址相位采样控制信号在数据相位处理数据。很多新手把地址相位和数据相位搞混导致写数据时用了错误的地址或者读数据时返回了错误的数据。我的做法是在接口里用两个状态IDLE和DATA。IDLE状态采样地址和控制如果HSEL和HTRANS有效进入DATA状态DATA状态处理数据根据HREADY决定是否完成。这样地址和数据自然分开。6.3 复位后的默认状态与安全值复位后Slave-AHB接口的输出信号要有默认值HREADY为高HRDATA为0HRESP为OKAY。这样主机在复位后发请求不会挂死。TCM的内容复位后是不确定的但控制寄存器要有复位值比如基址寄存器复位为默认映射地址使能寄存器复位为关闭状态。我见过一个设计复位后HREADY为低结果主机复位后发的第一个请求就挂住了系统起不来。查了半天发现是HREADY的复位值写错了。这种低级错误在RTL里很常见建议在接口模块里显式写出所有输出信号的复位值并在验证时专门测复位后的行为。6.4 综合时的时序约束与面积权衡Slave-AHB接口和TCM控制器的时序约束要写清楚。HCLK的周期、输入输出的建立保持时间、跨时钟域的false path和multicycle path都要约束。TCM的SRAM通常有读延迟如果SRAM读延迟是一个周期那读数据要在下一个周期返回接口里要加一级寄存器。面积上双端口TCM比单端口大差不多一倍仲裁器和写缓冲也会增加面积。如果面积紧张可以先用单端口加仲裁实测性能不够再考虑双端口。我的经验是对于主频100MHz以内的MCU单端口加写缓冲基本够用超过200MHz双端口或者多bank TCM更稳妥。7. 从RTL到验证怎么确认接口真的没问题7.1 用形式验证检查协议合规性AHB协议有很多规则比如HTRANS为IDLE时HREADY必须为高、ERROR响应必须两周期、突发不能跨1KB边界等。这些规则用形式验证formal verification来检查最有效。把Slave-AHB接口的RTL和AHB协议断言一起跑形式验证能穷举所有输入组合找出协议违规。我常用的断言包括HSEL有效且HTRANS为NONSEQ/SEQ时HREADY在有限周期内必须拉高HRESP为ERROR时HREADY必须遵循两周期模式突发传输的地址递增必须符合HSIZE和HBURST。这些断言写一次后续改RTL都能复用。7.2 用定向测试覆盖边界场景形式验证覆盖协议规则定向测试覆盖功能场景。我列的定向测试包括单次读写、突发读写、非对齐访问、非法地址访问、CPU和DMA同时访问DTCM、调试器写ITCM后CPU取指、低功耗模式下的访问。每个场景都要检查数据正确性和时序符合性。特别是CPU和DMA同时访问DTCM这个场景要构造CPU连续读、DMA连续写、两者交替等各种组合观察仲裁器是否公平、数据是否一致。我写过一个测试CPU读地址A的同时DMA写地址A结果读到了旧数据这就是store-to-load forwarding没做导致的。后来在测试里加了这种场景每次回归都跑。7.3 性能计数器与实测数据验证阶段可以加一些性能计数器统计TCM的访问次数、仲裁冲突次数、写缓冲满的次数、Slave-AHB的等待周期数。这些数据能帮你判断设计是否达到预期。比如仲裁冲突次数高说明CPU和DMA的访问模式冲突严重可能需要调整仲裁策略或增加TCM bank。实测数据方面我会在FPGA上跑一个真实的负载比如用DMA搬一块图像数据到DTCM同时CPU跑一个中断密集的任务测量DMA吞吐量和中断响应延迟。这两个指标能综合反映TCM和Slave-AHB的设计质量。我最近一次实测DMA搬64KB数据用了约1300个周期中断响应延迟稳定在12个周期这个数据对于100MHz的MCU来说是可以接受的。7.4 常见bug的排查清单最后列一个我实际遇到过的bug清单供排查时参考现象可能原因排查方法系统复位后挂死HREADY复位值错误检查复位后HREADY是否为高DMA搬数据错位字节使能或地址递增错误抓波形看HADDR和HWDATA中断响应慢向量表不在ITCM或向量项太长检查mtvec和向量表布局读写数据不一致写缓冲未转发或仲裁冲突检查store-to-load forwarding非法地址访问挂死无默认从设备响应加默认从设备返回ERROR低功耗唤醒后访问失败跨电源域握手错误检查同步器和电源域约束这些坑我基本都踩过一遍每一个都花了不少时间定位。希望这份整理能让你在设计RISC-V IP的TCM和Slave-AHB接口时少走点弯路。接口设计这件事规范是底线实测是真理多跑几个真实负载比看一百遍手册都管用。
返回列表