背景
RISCV采用了弱内存模型–RVWMO,在RVWMO模型中,对于WAR,WAW,RAW的约束是相当直观的,非常符合我们的通用思维,:
- WAR: store(new) + load(old),则这笔新的store不能先于老的load发出去。(否则load数据错误)
- WAW: store(new) + store(old),则这笔新的store不能先于老的store发出去。(否则新值被老值覆盖)
- RAW: load(new) + store(old),则这笔新的load需要取回老的store写出去的数据
但还有一条RAR的约束,理解起来有点隐晦:
a and b are loads, x is a byte read by both a and b, there is no store to x between a and b in program order, and a and b return values for x written by different memory operations
翻译下来即是:如果A和B(假设A老于B)为两条访问相同地址的load,则A返回的是老值,B返回的是新值。
这就要求处理器在硬件实现时,特别是乱序处理器,如果有两条地址交叠的load,则新load不能先于老load执行,即RAR约束。
原因
为什么要有这条约束呢?
先说结论:如果软件期望两笔访问相同地址的load能返回不一样的值,允许这两笔load的乱序执行,可能会导致违反软件的期望。因此,RVWMO为了便于软件编程和减少corner场景,便增加了RAR约束。
举个例子:软件人员期望,核0在等到核1对一个地址写新值后,将这个新值读回来,如以下程序。
如果允许hart0的( c )load先于(a)load执行,在以下场景中,( c )load还是可能回读到老值。
1.(a) load由于某种原因被阻塞住,比如和前序指令有数据依赖还没解除
2.(b) 由于(a)没有完成也被堵住不能执行
3.( c) load 将老值load回t1
4.(d) 将新值写到地址X
5.(a) load阻塞解除,将新值load回来,指令提交
6.(b) 预测不跳转,预测成功,指令提交
7.( c) 指令提交,写回老值
因此,如果没有RAR的约束的话,软件就需要在(b)和( c )之间加fence来实现想要的效果了。
引申
如果你仔细观察协议的措辞,你就会发现他非常绕口,为什么不直接写:如果两个load地址交叠,则他们需要保序执行呢?事实上,spec这样写,是为了允许一些特殊情况下,RAR也能乱序执行,但他并不会违反软件人员的期望。其中最典型的场景是fri-rfi场景。
(d) stalls (for whatever reason; perhaps it’s stalled waiting for some other preceding instruction)
(e) executes and enters the store buffer (but does not yet drain to memory) • (f) executes and forwards from
(e) in the store buffer
(g), (h), and (i) execute
(a) executes and drains to memory, (b) executes, and © executes and drains to memory
(d) unstalls and executes
(e) drains from the store buffer to memory
在这个案例中,后面的(f)load虽然先于(d)load执行了,但是(f)load回的是更新的值:(e)store写出的值。这种程序是经常常见的,因此为了不限制这种场景下的性能,RVWMO允许(f)load先执行。
同样的,如果能保证两笔背靠背的load取回的值一定是被同一笔store写的,这两笔load也可以乱序执行,具体案例可参考risc-v手册。
可关注我的个人微信公众号:John的打铁站
解析更多协议易错点