4.5 RamTst——硅片的体检医生
场景:上电那一刻,0.3 秒的决定
ECU 上电。12V 车载电源流过稳压器,产生 3.3V 和 1.8V 的 SoC 供电。PMU 释放复位信号,Cortex-R5 从 BootROM 开始执行。FSBL(First Stage Bootloader)初始化 PLL、DDR 控制器、加载应用。然后,在所有应用代码之前,调用 RamTst_Init(),接着 RamTst_RunFullTest() 开始遍历 SRAM 的每一个存储单元。
如果任何一个 bit 卡死在 0 或卡死在 1(stuck-at fault),如果任何两个相邻 cell 之间存在耦合(coupling fault),如果任何地址线解码器出错,ECU 不会进入正常运行状态。RamTst 就是硅片的体检医生,它的诊断结果直接决定 ECU 能否“上路“。
一、RamTst.c:架构总览
memory/RamTst/src/RamTst.c,561 行。内部状态结构体:
typedef struct {
RamTst_TestResultType
RamTst_BlockResultBuffer[RAMTST_NUM_BLOCKS];
RamTst_ExecutionStatusType ExecStatus;
#if (RAMTST_STOP_API == STD_ON)
RamTst_ExecutionStatusType OrderedStatus;
#endif
RamTst_AlgParamsIdType ActiveAlgParamID;
RamTstInitInitStatus InitStatus;
RamTst_TestResultType CurrentTestResult;
} RamTstInternalType;
static RamTstInternalType RamTst_Internal = {
.ExecStatus = RAMTST_EXECUTION_UNINIT,
.ActiveAlgParamID = 0,
.InitStatus = RAMTST_UNINIT,
.CurrentTestResult = RAMTST_RESULT_NOT_TESTED
};
内部状态机有三个维度:
- ExecutionStatus:UNINIT → STOPPED → RUNNING(→ STOPPED)
- InitStatus:UNINIT / INIT(独立的初始化标记)
- CurrentTestResult:NOT_TESTED / OK / NOT_OK / UNDEFINED
这个三层状态机确保错误的 API 调用时序一定会被拦住:
#if ( RAMTST_DEV_ERROR_DETECT == STD_ON )
#define VALIDATE(_exp,_api,_err ) \
if( !(_exp) ) { \
(void)Det_ReportError(RAMTST_MODULE_ID,0,_api,_err); \
return; \
}
#define VALIDATE_W_RV(_exp,_api,_err,_rv ) \
if( !(_exp) ) { \
(void)Det_ReportError(RAMTST_MODULE_ID,0,_api,_err); \
return (_rv); \
}
#else
#define VALIDATE(_exp,_api,_err )
#define VALIDATE_W_RV(_exp,_api,_err,_rv )
#endif
VALIDATE 宏,与 WdgM 的 CHECK 宏同族,在 DET 开启时报告错误,在 DET 关闭时退化为空宏。但安全检查始终执行(_exp 条件判断不消失)。
二、Init:准备体检器材
void RamTst_Init(void)
{
RamTst_Internal.InitStatus = RAMTST_INIT;
VALIDATE( ( RAMTST_EXECUTION_UNINIT
== RamTst_Internal.ExecStatus ),
RAMTST_INIT_SERVICE_ID, RAMTST_E_STATUS_FAILURE);
RamTst_Internal.ExecStatus = RAMTST_EXECUTION_STOPPED;
RamTst_Internal.ActiveAlgParamID =
RamTst_ConfigParams.RamTstDefaultAlgParamsId;
for( RamTst_NumberOfBlocksType i = 0;
i < RAMTST_NUM_BLOCKS; i++ ) {
RamTst_Internal.RamTst_BlockResultBuffer[i]
= RAMTST_RESULT_NOT_TESTED;
}
RamTst_Internal.CurrentTestResult = RAMTST_RESULT_NOT_TESTED;
}
Init 的执行前提:ExecStatus == UNINIT,只能从未初始化状态进入。Init 后进入 STOPPED(准备就绪但未执行测试)。RamTstDefaultAlgParamsId 从配置中选取默认的算法参数组。
一个细节:InitStatus 在函数开头就设为 INIT。这不是 bug。如果 VALIDATE 失败(ExecStatus 不匹配),函数 return 了,但 InitStatus == RAMTST_INIT。后续 API 调用会因为 InitStatus 已设定而尝试执行。但 ExecStatus 不匹配会失败。这种“先改状态再验证条件“看似危险,实际上用 DET 检查提供了安全网。
三、RunFullTest:执行引擎
这是整个模块的核心,约 110 行的测试调度器:
void RamTst_RunFullTest( void )
{
VALIDATE( ( RamTst_Internal.InitStatus != RAMTST_UNINIT ),
RAMTST_INIT_SERVICE_ID, RAMTST_E_UNINIT);
VALIDATE( ( (RAMTST_EXECUTION_STOPPED)
== RamTst_Internal.ExecStatus ),
RAMTST_RUNFULLTEST_SERVICE_ID, RAMTST_E_STATUS_FAILURE);
const RamTst_AlgParamsType *algParams = NULL;
/* Find config for the active algorithm */
for(RamTst_AlgParamsIdType i = 0;
(i < RamTst_ConfigParams.RamTstNumberOfAlgParamSets)
&& (NULL == algParams); i++) {
if( RamTst_ConfigParams.RamTstAlgParams[i].RamTstAlgParamsId
== RamTst_Internal.ActiveAlgParamID ) {
algParams = &RamTst_ConfigParams.RamTstAlgParams[i];
}
}
if( NULL != algParams ) {
RamTst_Internal.ExecStatus = RAMTST_EXECUTION_RUNNING;
RamTst_Internal.CurrentTestResult = RAMTST_RESULT_UNDEFINED;
/* Initialize all block results to NOT_TESTED */
for(RamTst_NumberOfBlocksType i = 0;
i<algParams->RamTstNumberOfBlocks;i++)
{
RamTst_Internal.RamTst_BlockResultBuffer[
algParams->RamTstBlockParams[i].RamTstBlockIndex]
= RAMTST_RESULT_NOT_TESTED;
}
/* Run test for each block */
for(RamTst_NumberOfBlocksType i = 0;
i < algParams->RamTstNumberOfBlocks; i++)
{
RamTst_TestResultType res;
switch(algParams->RamTstAlgorithm) {
#if (RAMTST_MARCH_TEST_SELECTED == STD_ON)
case RAMTST_MARCH_TEST:
res = ramTstMarchX(
algParams->RamTstBlockParams[i]
.RamTstStartAddress.dataPtr,
algParams->RamTstBlockParams[i]
.RamTstEndAddress.dataPtr);
break;
#endif
default:
res = RAMTST_RESULT_NOT_OK;
break;
}
if(res != RAMTST_RESULT_OK){
#if defined(USE_DEM)
if(DEM_EVENT_ID_NULL
!= RamTst_ConfigParams.RAMTST_E_RAM_FAILURE) {
(void)Dem_ReportErrorStatus(
RamTst_ConfigParams.RAMTST_E_RAM_FAILURE,
DEM_EVENT_STATUS_FAILED);
}
#endif
RamTst_Internal.CurrentTestResult
= RAMTST_RESULT_NOT_OK;
}
RamTst_Internal.RamTst_BlockResultBuffer[
algParams->RamTstBlockParams[i].RamTstBlockIndex]
= res;
}
if( RAMTST_RESULT_UNDEFINED
== RamTst_Internal.CurrentTestResult ) {
RamTst_Internal.CurrentTestResult = RAMTST_RESULT_OK;
}
RamTst_Internal.ExecStatus = RAMTST_EXECUTION_STOPPED;
}
}
调度流程:
- 通过
ActiveAlgParamID查找算法配置 - 状态切换到 RUNNING
- 遍历所有 Block,按配置的算法执行测试
- 逐 Block 记录结果
- 任何 Block 失败→报告 DEM 事件并标记 NOT_OK
- 全部通过→OK
- 状态回到 STOPPED
注意 CurrentTestResult 初始化为 UNDEFINED,如果整个测试执行完后它仍然是 UNDEFINED,说明没有任何 block 出错,设为 OK。只要有一个 block 失败,就设为 NOT_OK。
四、ramTstMarchX:四步体检法
这是本节的技术核心,March X 算法的 C 语言实现:
static RamTst_TestResultType ramTstMarchX(uint32 *sa, uint32 *ea)
{
RamTst_TestResultType status;
boolean ret;
uint32 *ptr_start;
uint32 *ptr_end;
status = RAMTST_RESULT_OK;
ptr_end = ea;
ptr_start = sa;
ret = TRUE;
/* Step1: write 0 with up addressing order */
for( ;ptr_start < ptr_end; ptr_start++){
*ptr_start = 0;
}
/* Step2: read 0 and write 1 with up addressing order */
ptr_start = sa;
for( ;ptr_start < ptr_end; ptr_start++){
if(0 != *ptr_start){
status = RAMTST_RESULT_NOT_OK;
ret = FALSE;
break;
}
*ptr_start = 1;
}
if (ret == TRUE) {
/* Step3: read 1 and write 0 with down addressing order */
ptr_start = sa;
ptr_end--;
for( ; ptr_end >= ptr_start; ptr_end--){
if(1 != *ptr_end){
status = RAMTST_RESULT_NOT_OK;
ret = FALSE;
break;
}
*ptr_end = 0;
}
if (ret == TRUE) {
/* Step4: read 0 with down addressing order */
ptr_end = ea;
ptr_end--;
for( ; ptr_end >= ptr_start; ptr_end--){
if(0 != *ptr_end){
status = RAMTST_RESULT_NOT_OK;
break;
}
}
}
}
return status;
}
这四步每一步检测特定故障模型:
| 步骤 | 操作 | 寻址方向 | 检测的故障模型 |
|---|---|---|---|
| Step 1 | 全写0 | 升序 | 初始化 |
| Step 2 | 读0→写1 | 升序 | Stuck-at-0, 地址解码故障 |
| Step 3 | 读1→写0 | 降序 | Stuck-at-1, 耦合故障 |
| Step 4 | 读0 | 降序 | 写操作副作用检测 |
为什么 Step 3 和 Step 4 要用降序?因为地址线的切换方向不同。如果某根地址线有短路,升序和降序时故障表现不同。March X 的核心原理就是“升序写完再降序回读“,如果只做升序测试,A0 地址线的 stuck-at 故障可能被漏掉。
Step 3 的 ptr_end-- 细节:ea 指向的是“最后一个地址的下一个“(end-after),所以降序开始时要先 ptr_end-- 指向真正的最后一个位置。
五、SelectAlgParams:热切换测试策略
RamTst_SelectAlgParams 允许运行时切换算法但不能在 RUNNING 时切换:
void RamTst_SelectAlgParams( RamTst_AlgParamsIdType NewAlgParamsId )
{
VALIDATE( ( RamTst_Internal.InitStatus != RAMTST_UNINIT ),
RAMTST_SELECTALGPARAMS_SERVICE_ID, RAMTST_E_UNINIT);
VALIDATE( ( RamTst_Internal.ExecStatus
== RAMTST_EXECUTION_STOPPED ),
RAMTST_SELECTALGPARAMS_SERVICE_ID,
RAMTST_E_STATUS_FAILURE);
/* Check parameter NewAlgParamsId */
const RamTst_AlgParamsType *algParams = NULL;
for(RamTst_AlgParamsIdType i = 0;
(i < RamTst_ConfigParams.RamTstNumberOfAlgParamSets)
&& (NULL == algParams); i++) {
if( RamTst_ConfigParams.RamTstAlgParams[i].RamTstAlgParamsId
== NewAlgParamsId ) {
algParams = &RamTst_ConfigParams.RamTstAlgParams[i];
for(RamTst_NumberOfBlocksType block = 0;
block < algParams->RamTstNumberOfBlocks; block++) {
RamTst_Internal.RamTst_BlockResultBuffer[
algParams->RamTstBlockParams[block]
.RamTstBlockIndex]
= RAMTST_RESULT_NOT_TESTED;
}
RamTst_Internal.ActiveAlgParamID = NewAlgParamsId;
RamTst_Internal.ExecStatus
= RAMTST_EXECUTION_STOPPED;
RamTst_Internal.CurrentTestResult
= RAMTST_RESULT_NOT_TESTED;
}
}
VALIDATE( ( NULL != algParams ),
RAMTST_SELECTALGPARAMS_SERVICE_ID,
RAMTST_E_OUT_OF_RANGE);
}
这允许在不同阶段执行不同的测试策略,启动时快速检查(March X),维护时深度检查(Galpat/WalkPath)。切换算法时自动清除目标块的结果缓存,确保旧结果不会误导后续读操作。
六、其他算法接口:未实现的“预留位“
Arctic Core 定义了多种测试算法的框架但只实现了 March X:
#if (RAMTST_GALPAT_TEST_SELECTED == STD_ON)
RamTst_TestResultType RamTst_Galpat(...) { /* Not supported */ }
#endif
#if (RAMTST_ABRAHAM_TEST_SELECTED == STD_ON)
RamTst_TestResultType RamTst_Abraham(...) { /* Not supported */ }
#endif
#if (RAMTST_TRANSP_GALPAT_TEST_SELECTED == STD_ON)
RamTst_TestResultType RamTst_Transp_Galpat(...) { /* Not supported */ }
#endif
#if (RAMTST_WALK_PATH_TEST_SELECTED == STD_ON)
RamTst_TestResultType RamTst_Walk_Path(...) { /* Not supported */ }
#endif
#if (RAMTST_CHECKERBOARD_TEST_SELECTED == STD_ON)
RamTst_TestResultType RamTst_CheckerBoard(...) { /* Not supported */ }
#endif
这些是编译时的占位符,通过配置开关可以被客户替换为自定义硬件测试函数。March X 覆盖了最常见的故障模型(stuck-at, coupling, addressing),对于 ASIL B 等级已足够;Galpat 和 WalkPath 则是更高 ASIL 等级的补充。
七、GetTestResultPerBlock:精确到块的故障定位
当测试完成后,RamTst_GetTestResultPerBlock 允许查询特定内存块的测试结果:
RamTst_TestResultType RamTst_GetTestResultPerBlock(
RamTst_NumberOfBlocksType BlockID)
{
// ... init check ...
const RamTst_BlockParamsType *blockParamPtr = NULL;
const RamTst_AlgParamsType *algParams = NULL;
for(RamTst_AlgParamsIdType i = 0;
(i < RamTst_ConfigParams.RamTstNumberOfAlgParamSets)
&& (NULL == blockParamPtr); i++) {
algParams = &RamTst_ConfigParams.RamTstAlgParams[i];
for(RamTst_NumberOfBlocksType block = 0;
(block < algParams->RamTstNumberOfBlocks)
&& (NULL == blockParamPtr); block++) {
if(algParams->RamTstBlockParams[block].RamTstBlockId
== BlockID) {
blockParamPtr =
&algParams->RamTstBlockParams[block];
}
}
}
if(NULL != blockParamPtr) {
status = RamTst_Internal.RamTst_BlockResultBuffer[
blockParamPtr->RamTstBlockIndex];
} else {
status = RAMTST_RESULT_UNDEFINED; // Invalid block id
// DET report if enabled...
}
return status;
}
两层循环查找,先找算法参数组,再在组内找块。如果 BlockID 不存在,返回 UNDEFINED 并通过 DET 报告。这让诊断工具可以精确定位“哪个内存块坏了“,而不是笼统地说“RAM 有问题“。
RamTst 的设计哲学是“宁可误诊,不可漏诊“。March X 的四步序列覆盖了 stuck-at-0、stuck-at-1、耦合、地址线故障。虽然时间复杂度是 O(4n),对于启动时的一次性检查完全可接受。RunFullTest 的“UNDEFINED 初始值→OK/失败位设置“模式确保不会出现“忘了设置结果就认为 OK“的漏报,默认不安全,经过显式验证后才安全。
本篇小结
- RamTst 内部维护三层状态机:
ExecutionStatus(UNINIT→STOPPED→RUNNING)、InitStatus(UNINIT/INIT)、CurrentTestResult(NOT_TESTED/OK/NOT_OK/UNDEFINED),错误调用时序被 VALIDATE 宏拦截。 - March X 算法通过四遍遍历检测故障:Step1 全写 0(初始化)→ Step2 升序读 0 写 1(检测 stuck-at-0 和地址解码故障)→ Step3 降序读 1 写 0(检测 stuck-at-1 和耦合故障)→ Step4 降序读 0(检测写操作副作用),升序+降序双方向确保地址线故障不被遗漏。
RunFullTest采用“UNDEFINED 初始值→逐块检测→显式设 OK/NOT_OK“的默认不安全策略:只有所有 block 通过后才将 UNDEFINED 转为 OK,任一 block 失败即标记 NOT_OK 并报告 DEM 事件。RamTst_SelectAlgParams支持运行时热切换测试策略(如从 March X 切换到深度 Galpat),但禁止在 RUNNING 状态下切换,切换时自动清除目标块的结果缓存。- Arctic Core 预留了 Galpat、Abraham、Walk Path、Checkerboard 等算法的编译占位符,供客户自定义替换,March X 覆盖最常见的故障模型,适用于 ASIL B 等级。
【下集预告】: 硬件的硅片体检结束了,但整个安全系统的行为由什么决定?下一节回到 WdgM 的起点——
WdgM_ConfigTypes.h,从WdgM_Trigger到WdgM_ConfigType逐层解开 159 行配置的基因序列。每一个const关键字都不是语法糖:它意味着在 Flash 只读区域,系统行为在链接时就已经被固化。没有动态加载、没有运行时重配置——这就是功能安全最坚实的地基:让错误在编译阶段就暴露,而不是在行驶中。