RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0

  

1. 整数乘除法的标准扩展“M”

  整数乘法和除法的指令扩展“M”包含对两个整数寄存器中的值做乘、除法的指令。而之所以把乘除法单独列出来,是为了简化低端硬件的实现,毕竟某些场景极少用到整数乘除法,不用单独实现。

(1) 乘法操作

   MUL/MULH[S][H]/MULW
RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第1张图片
  MUL执行一次两个XLEN位的带符号乘法rs1×rs2,并将结果的低XLEN存到rd中,忽略溢出。
MULH、MULHU、MULHSU执行相同的乘法,分别针对的对象为有符号×有符号、无符号×无符号、 有符号×无符号乘法,并将运算结果2×XLEN位中的高XLEN位返回。
  如果同时需要结果的高、低XLEN位,一般代码顺序为:
    MULH[[S]U] rdh,rs1,rs2 ;
    MUL rdl,rs1,rs2;
  (源寄存器区分符必须按照同样的顺序,并且 rdh 不能是 rs1 或者 rs2 )。允许硬件实现时将其融合为单一操作,而不是分两次操作。MULW属于RV64I暂不表(>_< 暂时用不到,就不写了)。

(2) 除法操作

   DIV/DIVU/REM/REMU
RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第2张图片
  DIV/DIVU分别做带符号和无符号的整数除法,REM/REMU分别做带符号和无符号的取余。
  如果同时要商和余数,建议代码为
    DIV[U] rdq,rs1,rs2;
    REM[U] rdq,rs1,rs2;
  同样建议硬件实现能够把这两个操作融合为单一操作。
另外除法溢出语义如下:
溢出
  
  

2. 标注扩展"A" 原子操作

  原子性包含了对储存器执行原子性的读、写、修改等操作,以支持不同线程之间的同步。有两种原子性的操作包括load-reserved/store-conditional和fetch-and-op存储器指令。两种指令都支持各种存储器的一致性排序,包括乱序、获取、释放和顺序一致性语义。

(1)原子操作的指定顺序

  基本的RISC-V ISA 有一个宽泛约束的储存模型,使用FENCE来加强顺序性约束。地址空间被执行环境分为两类:存储器域和IO域,FENCE指令提供其中一个或者两个地址域实现按序访问。
每一条原子性指令有两位(aq、 rl位)用于指定其他RISC-V线程看到的额外存储器操作的顺序性。
  如果两个均为0,则没有任何其他顺序性约束强加于原子操作。
  只有aq为1, 获取访问(acquire access)—— 在储存器操作被硬件执行之前,任务序列的所有后续访问指令对于硬件来讲都是看不到的(不会被执行)。
  只有rl为1, 释放访问(release access) ——在储存器操作完成前所有释放操作无法被硬件看到。
  aq和rl均为1,顺序一致性(sequentially consistent) ——即储存器操作完成前,其后的操作不可见;存储操作完成后,其前面的操作不可见。也就是操作对于其他任何线程来讲其操作都是顺序一致的,即严格按照代码逻辑来走,乱序无效。
  这两个bit使得两个地址域(存储器域和I/O域)中一个的访问具有顺序,这依赖于原子性指令正在访问哪个域。对另外一个域的访问并没有隐式的顺序约束,需要使用一条FENCE指令来在两个域实现按序访问(应该是FENCE指令只作用在其中的一个域)。

(2) load-reserved/store-conditional 指令

RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第3张图片
  单字长储存器的复杂原子操作是通过LR/SC指令来完成的。
  LR将rs1所指的地址处的一个字长的数据读出经符号扩展后放入rd,同时在储存器地址上注册一个监视预约。SC将rs2所指地址中的一个字长的数据保存到rs1所指的存储器地址中,并提供一个此地址的监视预约。如果执行成功将0写入rd,否则写非零值。总的来说,就是LR从储存器读取一个值,并且将让此地址处于被监控状态,检查其他线程是访问过此地址。SC指令如果发现此期间此地址没有被修改则将新值填入此地址。因此,一个原子的LR/SC指令对就是LR读取该值做一些计算后并试图保存新值,如果失败则重新开始这个操作。
  虽然比较-交换指令(CAS)和LR/SC指令也可以用来构建lock-free的数据结构,但是RISCV偏向选择LR/SC来实现。因为,
  <1>. LR/SC监视此地址的所有访问,而不仅仅是检查数值是否发生变化。
  <2>. CAS指令需要一种新的整数指令格式,以支持3个源操作数,还有一个不同存储器消息格式,导致为硬件实现结构复杂化。
  <3>. LR/SC提供了比CAS更加高效的实现。
  为了满足代码的可移植性,LR/SC错误代码在目前版本中仅仅要求错误代码为“非零”,具体错误码没有指定,硬件实现中可自定义。在标准扩展“A”中,为确保指令序列能够成功执行,需要对LR/SC指令序列做出必要的限定。LR/SC序列的静态代码加上失败重试这个序列的代码不能超过16条。而且LR/SC指令之间的代码只能由子集 “I” 中的指令构成(不会生成异常),并需要排除load、store、向后跳转或者分支指令、FENCE、FENCE.I和SYSTEM指令。失败后重试代码可以包含向后的跳转和分支,其他要求不变。SC指令必须和最近执行的LR地址相同。
  真正的硬件实现中可以用任意储存器空间子集,对于单硬件线程而言可以在这个储存器空间上拥有多个LR指令的预约。如果在此SC指令和前面最近的LR指令之间没有其他线程访问这个地址,则预约成功,如果前面的LR指令用了其他别名已经预约了这个地址,则会预约失败。如果有来自其他硬件线程对此地址所监控的储存器进行访问、或硬件线程上有一个上下文切换,或者此线程上有一条特权异常指令返回,那么SC就必须失败。
  LR/SC指令可以用来构建lock-free数据结构。一个用LR/SC实现一个比较交换的函数
RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第4张图片
  在处理的LR指令之前其他RISC-V线程看不到SC指令,而在LR指令和一个成功的SC指令之间任何线程的都看不到这个存储器的操作。这个 LR/SC序列可以通过设置SC指令的aq位而被赋予获取(acquire)语义。这个LR/SC序列可以通过设置LR指令的rl位而被赋予释放(release)语义。设置LR指令的aq位和rl位都为1,设置SC指令的aq位为1,使得LR/SC指令序列对于其他操作来说都是顺序一致的。 如果LR和SC的所有aq、rl位都被清零,那么在来自同一个RISC-V线程的附近存储器操作之前或之后,可以看到LR/SC指令序列。并行归约操作就很适合用LR/SC 序列来实现。

(3)原子性储存器操作

RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第5张图片
  原子性储存器操作(AMO)为多处理器同步执行读、写、修改操作,并编码为R类指令格式。这些AMO指令原子性地从rs1地址读取数据值,将这个值写入rd,在这个值和rs2的原始值上做二进制操作、然后把结果保存到rs 所指的储存器中。AMO指令可以对储存器中的64位或者32位字进行操作。
  保存在rs1中的地址必须与操作数的大小对齐,否则会产生一个非对齐地址异常。支持的储存器操作包括SWAP、ADD、AND、OR、XOR、MAX[U]/MIN[U]。没有额外顺序性约束,这些指令可以用于实现并行规约操作,返回值通过写x0而丢掉。
AMO指令也可用于提供顺序化一致性load和store。
  一个顺序化的load可以实现为一条AMOADD x0指令,其aq位和rl位都设置为1。
  一个顺序化一致性store可以实现为一条 AMOSWP指令,它将旧值写入x0,其aq位和rl位都设置为1。
  一个使用test-and-set spinlock实现关键段保护的示例代码序列如图 6.2所示。注意到在 关键段前面,第一个AMO指令设置了aq位,以便按序获取锁,而在释放锁之前,第二个AMO 指令设置了rl位,以便按序进入关键段。 RISC-V ISA 学习笔记(2) 乘除法标准扩展“M”和原子扩展“A” v2.0_第6张图片
待续。。。。

你可能感兴趣的:(RISC-V指令集)