Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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;
    }
}

调度流程:

  1. 通过 ActiveAlgParamID 查找算法配置
  2. 状态切换到 RUNNING
  3. 遍历所有 Block,按配置的算法执行测试
  4. 逐 Block 记录结果
  5. 任何 Block 失败→报告 DEM 事件并标记 NOT_OK
  6. 全部通过→OK
  7. 状态回到 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“的漏报,默认不安全,经过显式验证后才安全。

本篇小结

  1. RamTst 内部维护三层状态机:ExecutionStatus(UNINIT→STOPPED→RUNNING)、InitStatus(UNINIT/INIT)、CurrentTestResult(NOT_TESTED/OK/NOT_OK/UNDEFINED),错误调用时序被 VALIDATE 宏拦截。
  2. March X 算法通过四遍遍历检测故障:Step1 全写 0(初始化)→ Step2 升序读 0 写 1(检测 stuck-at-0 和地址解码故障)→ Step3 降序读 1 写 0(检测 stuck-at-1 和耦合故障)→ Step4 降序读 0(检测写操作副作用),升序+降序双方向确保地址线故障不被遗漏。
  3. RunFullTest 采用“UNDEFINED 初始值→逐块检测→显式设 OK/NOT_OK“的默认不安全策略:只有所有 block 通过后才将 UNDEFINED 转为 OK,任一 block 失败即标记 NOT_OK 并报告 DEM 事件。
  4. RamTst_SelectAlgParams 支持运行时热切换测试策略(如从 March X 切换到深度 Galpat),但禁止在 RUNNING 状态下切换,切换时自动清除目标块的结果缓存。
  5. Arctic Core 预留了 Galpat、Abraham、Walk Path、Checkerboard 等算法的编译占位符,供客户自定义替换,March X 覆盖最常见的故障模型,适用于 ASIL B 等级。

【下集预告】: 硬件的硅片体检结束了,但整个安全系统的行为由什么决定?下一节回到 WdgM 的起点——WdgM_ConfigTypes.h,从 WdgM_TriggerWdgM_ConfigType 逐层解开 159 行配置的基因序列。每一个 const 关键字都不是语法糖:它意味着在 Flash 只读区域,系统行为在链接时就已经被固化。没有动态加载、没有运行时重配置——这就是功能安全最坚实的地基:让错误在编译阶段就暴露,而不是在行驶中。