Skip to content

返回学习路线

从指令状态到缓存写回:同一程序的两份执行账本 ​

这份任务让一段短程序贯穿两个实验。先确认哪些指令真正完成、每次load得到什么;再把它产生的同一条数据访存轨迹送入两个缓存,核对命中、被逐出的块和脏数据。错误路径上取到的store不得混进轨迹。

主线有九站,默认你已经会读位串与简单汇编。如果数值表示还不稳,可先读定宽整数、扩展与截断和字节序;想检查机器码时读指令字段;想知道级间寄存器为什么这样更新,读时钟边界与单周期数据通路。这些是按需补课入口,不必在已经熟悉的内容上绕行。

题目与固定模型 ​

ISA、环境和程序 ​

使用RISC-V文档v20260120中RV32I 2.1的add/addi/lw/sw/beq正常执行子集。寄存器和PC为32位,算术和地址取低32位,x0恒为零。数据按字节寻址,小端,全部访问是有效且四字节对齐的普通内存。本例没有地址环绕、异常、设备或自修改代码。代码放在从0x1000开始的独立只读区域;表中PC列只写相对0x1000的偏移,实际架构PC等于基址加该偏移。数据地址均为绝对地址,初值约定只覆盖数据区。指令与数据使用独立访问端口,并不意味着ISA拥有两个同号地址空间。

初始架构PC=0x1000(相对偏移0)。数据区全部为零。初始x2=5、x4=1、x6=9、x7=11、x8=13,其他寄存器为零,包括作为基址的x1。

标签 PC偏移 指令
I1 0x00 lw x2,0(x1)
I2 0x04 add x3,x2,x6
I3 0x08 sw x3,4(x1)
I4 0x0c lw x4,32(x1)
I5 0x10 beq x4,x0,+12
J1 0x14 sw x7,0(x1)
J2 0x18 addi x6,x0,99
I6 0x1c lw x5,0(x1)
I7 0x20 sw x7,64(x1)
I8 0x24 lw x9,32(x1)
I9 0x28 lw x10,4(x1)
I10 0x2c sw x8,8(x1)
I11 0x30 lw x11,40(x1)
I12 0x34 lw x12,8(x1)
I13 0x38 lw x13,64(x1)

到PC偏移0x3c(实际0x103c)时停止取本片段,并让已有流水指令排空;这不是增加一条RV32I halt指令。

五级时序 ​

  • 单发顺序IF/ID/EX/MEM/WB;各级内容指本周期开始时的指令,级间结果在周期末保存
  • 指令存储、数据存储各有独立端口,固定一周期;不把下面缓存实验的miss代价算进这张流水图
  • WB前半写,ID后半读;EX/MEM非load结果和MEM/WB最终结果可送往EX,最近较老写者优先,x0不接受转发
  • load在MEM末生成数据,下一周期才可用于EX;store的base与data都在EX确定,data随EX/MEM携带,不设MEM阶段晚旁路
  • load-use时保持PC和IF/ID,下一ID/EX插气泡;旧EX/MEM和MEM/WB照常推进
  • beq在EX末用转发后的操作数决断,按未跳转取指;taken清除本周期ID与IF两条年轻指令,下一周期IF取目标
  • valid=false的气泡没有寄存器或内存副作用;store在MEM末写内存,store/beq的WB只作为空操作占位

两个缓存 ​

两个配置都用32位字节地址、32 B数据容量、8 B块;容量不计元数据。D为四组一路,A为两组两路。都冷启动、WB+WA、缺失先保存脏victim再填完整块、LRU命中与填入均更新。先用无效way,多个无效way选最低编号。每次访问为一个不跨块的四字节word,按程序数据trace串行完成。

本题刻意选择自然对齐的正常访存;想改变该条件,可接对齐与跨界访问。若要让某条load故障,则接精确异常,另核对异常前缀与错误路径故障取消,不能直接沿用本题无异常时序。

任务要求:

  1. 顺序执行程序,列出最终寄存器、内存和11个数据访问;解释J1/J2是否完成
  2. 从空流水开始列每周期状态、保持/气泡/清除及转发来源,核对最终状态与顺序执行相同
  3. 对同一trace计算两配置的tag/index/offset、命中、victim、dirty写回以及最后驻留内容
  4. 分开报告需求传输与终末flush,并给出取消互锁、取消flush或误用新tag回写的具体错误
  5. 在四块全相联LRU影子下分类两配置的实际缺失;只改对象布局构造一个冲突消失的例子

解答一:先确定架构结果 ​

I1把x2从旧5改为0,I2得到x3=0+9=9,I3把9写到0x04。I4把x4从旧1改为0,所以I5相等跳转。目标用分支自己的PC计算:0x1010+12=0x101c,即相对偏移0x10+12=0x1c,J1与J2都不应完成。

后续load应得到x5=0、x9=0、x10=9、x11=0、x12=13、x13=11。x6仍为9,x7仍为11,x8仍为13,x1仍为0。最后非零数据word只有0x04的9、0x08的13、0x40的11;例如地址0x04..0x07的字节为09 00 00 00。

I1至I13共13条有效指令,包括分支,不包括两条错误路径指令。按有效访存指令顺序,数据轨迹为:

次数 来源 操作 返回/写入的word
1 I1 R 0x00 0
2 I3 W 0x04 9
3 I4 R 0x20 0
4 I6 R 0x00 0
5 I7 W 0x40 11
6 I8 R 0x20 0
7 I9 R 0x04 9
8 I10 W 0x08 13
9 I11 R 0x28 0
10 I12 R 0x08 13
11 I13 R 0x40 11

解答二:完整逐周期账本 ​

IF栏表示本周期的取指尝试。发生前端保持时,该尝试不写入IF/ID,下一周期会重取同一PC;ID中的等待指令则确实保留。表中“—”为无效槽,未列出的寄存器和数据内存保持原值。所有有效MEM读写都列在最后一栏,所以可直接从表里提取上面的同一条trace。

周期 IF ID EX MEM WB 控制、转发与架构效果
1 I1 — — — — 正常推进
2 I2 I1 — — — 正常推进
3 I3 I2 I1 — — 保持PC/IF-ID;下一EX插气泡
4 I3 I2 — I1 — R 0x00 → 0
5 I4 I3 I2 — I1 MEM/WB→I2.rs1:x2=0;x2←0
6 I5 I4 I3 I2 — EX/MEM→I3.rs2:x3=9
7 J1 I5 I4 I3 I2 保持PC/IF-ID;下一EX插气泡;x3←9;W 0x04 ← 9
8 J1 I5 — I4 I3 R 0x20 → 0
9 J2 J1 I5 — I4 MEM/WB→I5.rs1:x4=0;分支成立;清J1/J2,下一IF=I6;x4←0
10 I6 — — I5 — 正常推进
11 I7 I6 — — I5 正常推进
12 I8 I7 I6 — — 正常推进
13 I9 I8 I7 I6 — R 0x00 → 0
14 I10 I9 I8 I7 I6 x5←0;W 0x40 ← 11
15 I11 I10 I9 I8 I7 R 0x20 → 0
16 I12 I11 I10 I9 I8 x9←0;R 0x04 → 9
17 I13 I12 I11 I10 I9 x10←9;W 0x08 ← 13
18 — I13 I12 I11 I10 R 0x28 → 0
19 — — I13 I12 I11 x11←0;R 0x08 → 13
20 — — — I13 I12 x12←13;R 0x40 → 11
21 — — — — I13 x13←11

两次load-use分别在周期3、7检出。周期4、8的EX为气泡,旧load继续MEM。周期5的I2从MEM/WB取I1的0;周期6的I3从EX/MEM取I2的9作为store数据;周期9的I5从MEM/WB取I4的0作分支比较。没有把load的地址误当数据,也没有把ID读到的旧x2/x4用于最终计算。

周期9只清年轻的J1/J2,I5继续流过MEM/WB;它不写寄存器,但仍是有效的已执行分支。最后I13在周期21写回11。从第一条IF到最后一条WB,共21个模型周期,可分账为13条有效指令+4个填充/排空周期+2次load-use空槽+2个taken分支空槽。这个等式依赖本例没有相互重叠的其他停顿,不能作为任意程序的罚时相加公式,更不是实际CPU预测。

解答三:同一trace的地址拆分 ​

D:offset为地址[2:0],index为[4:3],tag为[31:5]。A:offset仍为[2:0],index改为[3],tag为[31:4]。下面tag、index、offset用十进制,地址用十六进制。way不是地址位,由命中或替换结果决定。

次数 操作地址 D:tag/index/offset A:tag/index/offset
1 R 0x00 0/0/0 0/0/0
2 W 0x04 0/0/4 0/0/4
3 R 0x20 1/0/0 2/0/0
4 R 0x00 0/0/0 0/0/0
5 W 0x40 2/0/0 4/0/0
6 R 0x20 1/0/0 2/0/0
7 R 0x04 0/0/4 0/0/4
8 W 0x08 0/1/0 0/1/0
9 R 0x28 1/1/0 2/1/0
10 R 0x08 0/1/0 0/1/0
11 R 0x40 2/0/0 4/0/0

命中、受害者和写回 ​

victim一律写块基址;“脏”表示本次替换前必须回写。空行没有victim。即使是只读请求,也可以触发旧脏块回写。

次数 D结果 D victim A结果及way A victim
1 缺失 — 缺失,way 0 —
2 命中 — 命中,way 0 —
3 缺失 0x00(脏,回写) 缺失,way 1 —
4 缺失 0x20(干净) 命中,way 0 —
5 缺失 0x00(干净) 缺失,way 1 0x20(干净)
6 缺失 0x40(脏,回写) 缺失,way 0 0x00(脏,回写)
7 缺失 0x20(干净) 缺失,way 1 0x40(脏,回写)
8 缺失 — 缺失,way 0 —
9 缺失 0x08(脏,回写) 缺失,way 1 —
10 缺失 0x28(干净) 命中,way 0 —
11 缺失 0x00(干净) 缺失,way 0 0x20(干净)

D的第3、6、9次访问分别回写块0x00、0x40、0x08。A的第6、7次访问分别回写块0x00、0x40;第6次是读0x20却先保存旧块0x00中的9,正好检验旧tag是否保留。

LRU和数据状态也要留下 ​

以下是每次访问后A的组内块次序,均按最近→最久排列,星号表示dirty。这比仅列最终tag多保留了“下次该逐出谁”的依据。

次数 A组0 A组1
1 0x00 空
2 0x00* 空
3 0x20,0x00* 空
4 0x00*,0x20 空
5 0x40,0x00 空
6 0x20,0x40* 空
7 0x00,0x20 空
8 0x00,0x20 0x08*
9 0x00,0x20 0x28,0x08*
10 0x00,0x20 0x08*,0x28
11 0x40,0x00 0x08*,0x28

最终有效缓存行如下,每个八字节块用两个小端word表示;未列的行无效。

配置 组/way 块基址 两个word dirty
D 0/0 0x40 (11, 0) 0
D 1/0 0x08 (13, 0) 0
A 0/0 0x40 (11, 0) 0
A 0/1 0x00 (0, 9) 0
A 1/0 0x08 (13, 0) 1
A 1/1 0x28 (0, 0) 0

解答四:结束条件决定传输账单 ​

口径 D A
需求命中/缺失 1 / 10 3 / 8
整块填入 10 8
需求脏回写 3 2
需求结束后dirty块数 0 1
此时下层word 0x04 / 0x08 / 0x40 9 / 13 / 11 9 / 0 / 11
若要求终末flush,追加整块回写 0 1
含终末flush的总填入+回写块数 13 11

A在需求结束时仍能从本地脏块读到13,所以它和顺序执行结果一致;只是下层内存还没收到这次修改。终末flush后两者的下层都包含9、13、11。这个flush只传播到本模型下层,未给出掉电持久化承诺。

若每次查询费用H、填入费用F、脏回写费用W均串行且不可重叠,则需求账单为D:11H+10F+3W,A:11H+8F+2W;要求终末flush时A再加W。没有H/F/W或允许重叠的接口,就不应把块计数改写成纳秒或真实CPU耗时。平均访存时间把局部缺失率、脏回写条件概率和串行罚时逐项展开。

解答五:反例与可迁移的检查 ​

漏互锁,错误不仅出现在一条add ​

若允许消费者在load的MEM周期使用旧寄存器值,I2会用旧x2=5得到14,I3把14写到0x04。I5又可能用旧x4=1误判不跳转,于是J1把0x00写成11,J2把x6改为99,后续x5读到11。一个“取消停顿”的改动同时破坏数据与控制语义。

这条变异特意选用旧值,不把尚未就绪的地址当数据。后者是另一种错误,也必须由转发选择的load屏蔽排除。

漏清除,正确目标仍不能挽回副作用 ​

如果周期9重定向PC却保留已在途的J1/J2,它们仍会写0x00=11和x6=99。目标I6确实被取到了,最后结果仍然错误。检查“PC到达目标”不足以证明分支恢复正确。

用新tag回写会污染新请求 ​

A第6次读0x20时,victim是旧块0x00,里面的word0x04为9。正确回写地址来自旧tag和组号。若先覆盖tag,再把旧八字节写到新块0x20,就会把9错误放入0x24并丢失0x04的修改;以后读0x04会得到旧0。不能只检查本次R0x20仍返回0就判定回写正确。

替换和3C分类 ​

用同容量、四块全相联LRU影子处理每一次访问。D的10个实际缺失是5首次、4冲突、1容量;A的8个实际缺失是5首次、2冲突、1容量。最后一次重读0x40时影子也已逐出该块,所以归为容量;它不是一个只靠增加路数就必然消失的缺失。

另取只交替读取两个对象的逻辑程序。在D上,布局a=0x00、b=0x20产生 00,20,00,20,四次全缺失;把b移到0x28产生 00,28,00,28,前两次缺失、后两次命中。算法与对象访问次序不变,修复的是索引冲突。局部性与3C分类给出影子参考的完整口径,以及空间局部性的另一组对照。

正确性证据与适用范围 ​

顺序解释器与逐级流水模拟分别计算寄存器和字节内存,逐条提取的11次数据操作完全相同。缓存模拟在每次R时核对该word值,并保存tag、index、offset、way、valid/dirty、LRU次序、victim和下层字节;终末flush单独计费。自动检查也覆盖负分支编码、补码扩展与溢出判据。

这些有限实验为上面的具体答案提供可复算证据,不证明所有RV32I实现,也不证明任意长度程序上的流水正确性。流水数据正确性依赖“最近较老写者已就绪或互锁等待”,控制正确性依赖“错误路径在副作用前失效”,缓存正确性依赖“每次丢弃唯一新副本前保存脏数据”。

多核进阶可沿从缓存写回到多核写权限继续。MSI协议提供三个核心的完整交接表与归纳不变量;伪共享终点再对同一六次写复算共行/隔行的6对2独占请求。协议另固定原子总线,不会从本页单核trace直接推出跨地址顺序或持久性。

参考资料 ​

  • RISC-V International,RV32I 2.1,固定文档版v20260120;五条指令的真实语义来源
  • UC Berkeley CS61C,Data Hazards 与 Set-Associative Cache,访问于2026-10-08;流水与缓存背景
  • 本文EX末分支、时序、存储端口、LRU并列规则、完整程序和全部数值均为显式教学模型,不是规范规定的微架构或硬件测量