4. MESI 写命中验证

验证目标

构造一个缓存行处于 M(Modified) 状态的场景,执行一次本地写命中,检查两件事:

  • 写操作只更新本地缓存数据,不产生任何总线事务(无 BusRd/BusRdX/BusUpgr/Flush 等)。
  • 写完成后该行状态保持 M,数据被更新为新值。

状态与事件关系

Cache Line state = M 本地写命中 write hit state = M data 已更新 总线事务:无

最小可复现实现

用一个简化的 MESI 缓存模型:单核、单行、带总线事务计数器。先通过一次写未命中把行带入 M 状态,再执行本地写命中并断言。

#include <stdio.h>
#include <assert.h>

typedef enum { I, S, E, M } mesi_t;

typedef struct {
    mesi_t state;
    int    data;
    int    bus_txn;   /* 总线事务计数 */
} cache_t;

/* 写未命中:分配行并进入 M(简化:直接置 M,计一次总线事务) */
void write_miss(cache_t *c, int val) {
    c->bus_txn++;          /* BusRdX */
    c->state = M;
    c->data  = val;
}

/* 本地写命中:M 状态下直接更新,不产生总线事务,状态保持 M */
void local_write_hit(cache_t *c, int val) {
    assert(c->state == M);  /* 前置条件:行处于 M */
    c->data = val;          /* 只改本地数据 */
    /* 状态保持 M,不触发任何总线事务 */
}

int main(void) {
    cache_t c = { .state = I, .data = 0, .bus_txn = 0 };

    /* 步骤1:写未命中,进入 M */
    write_miss(&c, 0x10);
    assert(c.state == M);
    int txn_after_miss = c.bus_txn;   /* 应为 1 */

    /* 步骤2:本地写命中 */
    local_write_hit(&c, 0x20);

    /* 验证1:状态保持 M */
    assert(c.state == M);
    /* 验证2:数据已更新 */
    assert(c.data == 0x20);
    /* 验证3:无新增总线事务 */
    assert(c.bus_txn == txn_after_miss);

    printf("PASS: state=M, data=0x%X, bus_txn=%d (no new txn)\n",
           c.data, c.bus_txn);
    return 0;
}

运行与判定

gcc -Wall -o mesi_m_hit mesi_m_hit.c && ./mesi_m_hit

通过条件:程序无断言失败,输出 PASS: state=M, data=0x20, bus_txn=1 (no new txn)。其中 bus_txn 在本地写命中前后保持不变,即证明 M 状态下的本地写不产生总线事务。

常见问题

  • 若把 local_write_hit 写成先判断状态再决定是否发总线请求,务必确认 M 分支不递增 bus_txn,否则断言会失败。
  • 真实 RTL 验证中,对应检查点是:写命中 M 时监视总线接口无新请求,且目录/一致性状态机不产生状态迁移。