RV32新拓展指令集–Zilsd
最近RV32指令集将会迎来一位新成员,Zilsd拓展,截至目前已经定稿,相信不久将来将会发布。
Zilsd主要实现ld(load-pair)和sd(store-pair)功能:
- ld: 将64bit的数据从memory中load回两个连续寄存器中,例如,ld rd, off(rs1), 则reg(rd)=memory[rs1+off: rs1+off+3] ,reg(rd+1)=memory[rs1+off+4: rs1+off+7]。
- sd: 将两个连续32bit的寄存器的值写到memory中,例如,sd rs2, off(rs1), 则memory[rs1+off: rs1+off+3] =reg(rs2), memory[rs1+off+4: rs1+off+7] =reg(rs2+1)。
1. 目的
加这两种指令,作者提到了有如下好处:
- 可以将连续两条load或store合并成一条指令,从而减少code size
- 在有些实现中,如果总线位宽大于XLEN,可以有效利用起总线位宽。比如,在一个RV32的架构但是总线位宽是64的实现中,load-pair和store-pair可以一次访问64bit数据。
除此之外,我认为还有以下好处:
- 即使总线位宽等于XLEN,连续的两笔load或store也可以在总线上以burst的类型发出,提升总线效率。
2. 指令
本次新增指令,增加了基础的32位指令,以及对应的C拓展指令,并且为了方便在压栈弹栈的时候使用该指令,还增加了默认rs=sp(栈指针寄存器)的C拓展指令,并且由于rs1固定为sp,节省的位域可以拓宽rd为5bit。指令分别如下所示:
-
ld rd, offset(rs1): reg(rd) = memory[rs1+off: rs1+off+3]; reg(rd+1) = memory[rs1+off+4: rs1+off+7]
-
sd rs2, offset(rs1): memory[rs1+off: rs1+off+3] = reg(rs2); memory[rs1+off+4: rs1+off+7] = reg(rs2+1)
-
c.ld rd’, offset(rs1’) :reg(rd‘) = memory[rs1’+off: rs1‘+off+3]; reg(rd’+1) = memory[rs1‘+off+4: rs1’+off+7]
-
c.sd rs2’, offset(rs1’) :memory[rs1‘+off: rs1’+off+3] = reg(rs2‘); memory[rs1’+off+4: rs1‘+off+7] = reg(rs2’+1)
-
c.ldsp rd, offset(sp): reg(rd) = memory[sp+off: sp+off+3]; reg(rd+1) = memory[sp+off+4: sp+off+7]
-
c.sdsp rs2, offset(sp): memory[sp+off: sp+off+3] = reg(rs2); memory[sp+off+4: sp+off+7] = reg(rs2+1)
3. 硬件设计要点
3.1 大小端
无论大小端,对于load-pair指令,总是将64bit中的低32bit数据load回reg(rd),高32bit数据load回reg(rd+1);对于store-pair指令,总是将reg(rs2)的值store到memory中的低32bit,reg(rs2+1)的值store回memory的高32bit。
3.2 寄存器使用约束
- 对于load-pair指令,rd寄存器必须是偶数。
- 对于store-pair指令,rs2寄存器必须是偶数。
这样的约束,可以有效降低设计复杂度,比如在进行寄存器冲突判断时,rd/rs[0]=0和rd/rs[0]=1,即可指示两个访问寄存器。如果没有这个约束,恐怕就需要使用加法器来获取第二个访问寄存器了。
当使用x0作为操作数时:
- 对于load-pair指令,如果rd设置为x0,则load的结果会被彻底忽略,不会访问x1寄存器。
- 对于store-pair指令,如果rs2设置为x0,则要写出去的64bit数据均为0,不会读取x1寄存器。
3.3 地址访问约束
我们可以把load-pair和store-pair看成是两笔load word或两笔store word。因此:
- 如果访问的memory地址是8byte对齐的,则是天然对齐的,不会产生非对齐异常。这8byte的访问不一定是原子的,但单笔word访问必须是原子的。(和两笔load word一样)
- 如果memory地址是4byte对齐的,则允许产生非对齐异常。但要求单笔word访问必须是原子的。
- 如果memory地址不是4byte对齐的,则允许产生非对齐异常,且单笔word访问也不一定是原子的
总结下来,如下表所示:
3.4 可中断性
load-pair和store-pair允许在总线上被拆分成多笔完成,且是允许在中间被打断的(中断、同步异常、调试…)。
但这就涉及到一个问题:对于load-pair指令,如果软件把rd设置成了地址base寄存器,则有可能在中间被打断时,第一笔load已经将地址base寄存器改写,在返回现场时,地址base寄存器已经丢失原来的值,从而无法恢复。
为了解决这个问题,spec规定,响应打断事件时,地址base寄存器必须保持原先的值。
这句话给了硬件比较大的实现空间,spec提到了以下方式来实现以上规定:
- 对于具有寄存器堆奇偶写口的实现来说,可以实现load-pair的原子写回,即将两笔32bit的数据一次写回寄存器堆。
- 对于其他实现来说,可以在第一笔完成之后,delay写回,等两笔load都完成之后,再连续写回。
除了spec提到的这两种,其实也可以通过其他方式来实现,下面两种方式供大家参考:
- 硬件检查load-pair指令不能将rd/rd+1设置为地址base寄存器,否则报非法指令异常。
- 硬件检查地址base寄存器,如果地址base寄存器为奇数,则先执行更新偶数寄存器的load,如果地址base寄存器为偶数,则先执行更新奇数寄存器的load。例如,ld x14, 0(x14),则可以先执行lw x15, 4(x14), 再执行lw x14, 0(x14)。从而保证,第一笔load永远不会更新地址base寄存器,需要注意的是:spec并没有规定load-pair/store-pair在拆分成两笔请求后,两笔请求之间的执行顺序。
4. 软件视角
在软件使用时,也需要注意以下几点:
- 不应该认为load-pair/store-pair的memory只访问一次,因为上述提到的可中断性,可能会导致在一条指令中,相同地址memory被访问多次。比如store-pair在中间被打断,返回现场后,第一笔store又会被重复执行。
- 不应该设想load-pair/store-pair的拆分指令的执行顺序。spec并没有对拆分指令的执行顺序进行规定。例如在拆分成两笔时,可以先访问低32bit memory,也可以先访问高32bit memory。
- 软件应该把load-pair/store-pair当成两笔load word/store word(如上文所述),比如当load-pair访问地址为4byte对齐时,需要认为他是非对齐的,且单笔word是原子的。
最后,附上Zilsd指令拓展原文
https://github.com/riscv/riscv-zilsd/releases