Rocket - core - ALU

https://mp.weixin.qq.com/s/C6Twva47PDyUev-BLhKLug

 

简单介绍ALU的实现。

 

 

1. object ALU

 

ALU对象中定义了ALU要使用的一些常量和辅助方法。

 

1) 常量

 

a. SIZE_ALU_FN: 表示ALU操作类型的占用的宽度为4位;

 

需要注意的是,这个位数只是用来表示操作类型,而不是指令中的宽度。

比如ADDI指令中的func3域只占用3位,并且与SLTI/SLTU的值相同:

而FN_ADD/FN_SLT/FN_SLTI占用4位,并且有不同的值:

 

b. FN_ADD: 相加;

c. FN_SL: 左移;

d. FN_SR: 右移;

e. FN_SEQ: 相等时置位;

f. FN_SNE: 不相等时置位;

g. FN_SLT: 小于时置位;

h. FN_SGE: 大于等于时置位;

i. FN_SLTU: 小于(作为无符号数进行比较)时置位;

j. FN_SGEU: 大于等于(作为无符号数进行比较)时置位;

k. FN_XOR: 异或;

l. FN_OR: 按位或;

m. FN_AND: 按位与;

n. FN_SUB: 相减;

o. FN_SRA: arithmetic right shift,带符号右移;

p. 乘除法相关的类型:

在规范中描述如下:

 

2) 辅助方法

 

定义对操作类型进行判断的辅助方法:

其中:

a. isMulFN:判断是否乘法操作;

b. isSub:判断是否减法;

c. isCmp:判断是否比较操作;

d. cmpUnsigned:判断是否无符号数相比较;

e. cmpInverted:判断是否基本判断的相反判断,比如相等是基本判断,不等是相反判断:

f. cmpEq:是否相等判断;

 

2. class ALU

 

用于实现ALU的逻辑:

 

1) io

 

定义ALU模块的IO接口:

其中:

a. dw:数据宽度;

b. fn:功能类型;

c. in2:第二个操作数;

d. in1:第一个操作数;

e. out:输出结果;

f. adder_out:加法器输出;

g. cmp_out:比较结果输出;

 

2) ADD/SUB

 

实现ADD/SUB指令:

其中:

a. 根据操作类型是否是减法,确定io.in2是否需要取反;

b. 如果不是减法,则in2_inv等于io.in2,而io.adder_out是相加的结果;

c. 如果是减法,则使用取反加一的方法进行计算;

 

3) SLT/SLTU

 

实现SLT/SLTU操作的逻辑:

A. 首先,判断符号位是否相同,如果相同,则取决于io.adder_out的符号位。

此时io.adder_out中是什么呢?

从SLT/SLTU的定义可以看出:

其第3位都是1,也就是isSub为真:

所以,io.adder_out中的值为io.in1-in.in2的差值;

进而:

a. 如果差值为负数,其符号位为1,表明io.in1 < io.in2;

b. 如果差值非负数,其符号位为0,表明io.in1 >= io.in2;

 

B. 其次,如果io.in1和io.in2的符号位不同,则需要区分是否要把二者看做无符号数进行比较。

a. 如果按照无符号数进行比较,在二者符号不同的前提下,如果io.in2的符号位为1,则io.in2较大,less than为真;

b. 如果按照有符号数进行比较,在二者符号不同的前提下,如果io.in1的符号位为1,则io.in2较大,less than为真;

 

C. 先不考虑cmpEq,io.cmp_out := cmpInverted ^ slt,表示是否要把比较结果取反。

如果cmpInverted为1,则取反;为0,则不取反。

 

D. cmpEq的定义为:

表示cmd小于8,即:

可以看到slt/sltu都是大于8,所以cmpEq对slt指令的比较结果不影响。

 

小于8的操作类型里面,意义为比较的操作类型为:SEQ/SNE。其实现为:

a. 当io.fn为SEQ/SNE时,in1_xor_in2 = in1 ^ in2;

b. in1_xor_in2为0,表示in1和in2中的各个比特都相同,也就是二者相等;

c. 根据cmpInverted的值,决定判断结果是否需要取反;

 

E. 这里把cmpEq和slt混合在一起的原因,是为了复用cmpInverted的判断逻辑。

 

4) SLL/SRL/SRA

 

实现SLL/SRL/SRA操作:

A. shamt是shift amount的缩写,意指移位的数量;

B. 当xLen == 32时,移动的位数shamt最多为32位,所以取io.in2的低5位;被移位的数据为io.in1;

C. 当xLen != 32时,要求xLen必须是64,否则不支持。然后对io.in1进行补位处理:

a. SLL/SRL/SRA中只有SRA的值大于8,其对应的isSub为真:

如果是SL/SR,则可能用于补位的数据为32个0;

如果是SRA,则可能用于补位的数据取决于io.in1的符号位,如果其符号位是1,则补位32个1,如果其符号位为0,则补位32个0:

b. 如果io.dw为64位,则说明io.in1是64位的,不需要补位;如果不是,则需要使用shin_hi_32补位:

c. 如果操作的数据宽度为32位,则移位的最大位数为32位,忽略io.in2的第5位;如果操作的数据宽度为64位,则需要使用io.in2的第5位来确定移位的位数:

d. 返回移位数量和补位后的待移位数:

 

D. 使用Reverse把左移操作转变为右移操作:

a. 如果是右移操作,则不需要改变shin_r;

b. 如果不是右移操作,即是SLL操作,把shin_r逆序;

 

E. 把shin作为有符号数,向右移位,而后取移位结果的低xLen位:

其中:

a. asSInt把待移位数转变为SInt类型,调用其右移方法;有符号数的右移会补符号位;

b. Cat(isSub(io.fn) & shin(xLen-1), shin)用于为shin添加一个正确的符号位:

a) 如果不是SRA,则isSub为0,添加符号位0,对应逻辑移位应该以0补位;

b) 如果是SRA,则取决于shin的符号位,再补一个符号位不改变其值;

可以看到引入这段逻辑,不是为了b),而是为了a)。

 

在a)中,isSub(io.fn) & shin(xLen-1)的结果是0,如果shin的符号位是0,则补位没有意义。可见这里补0是为了修正shin的符号位为1的情况。

在操作类型(io.fn)为SLL/SRL的情况下,这里有如下可能:

a.a. xLen == 32,io.fn == SRL,io.in1为负数;

a.b. xLen == 32,io.fn == SLL,io.in1最低位为1;

a.c. xLen == 64,io.dw == 64,io.fn == SRL,io.in1为负数;

a.d. xLen == 64,io.dw == 64,io.fn == SRL,io.in1最低位为1;

 

F. shout_r逆序之后为左移的结果:

 

G. 根据操作类型计算移位结果:

其中:

a. 如果操作类型为SR/SRA,则使用shout_r;

b. 如果操作类型为SL,则使用shout_l;

 

5) AND/OR/XOR

 

实现AND/OR/XOR操作:

其中:

a. 如果操作为XOR,则使用in1_xor_in2 = in1 ^ in2;

b. 如果操作为AND,则使用in1 & in2;

c. 如果操作为OR,则结果位(in1 ^ in2) | (in1 & in2);(只有一个1的位,加上有两个1的位);

 

6) 根据操作类型,决定shift_logic的取值:

因为操作类型同时只能去一个值,所以shift_logic的值,是从slt/logic/shout中选择一个。

 

7) 根据是否加减法操作,决定输出adder_out还是shift_logic:

 

8) 结果从io.out输出:

 

9) 根据xLen及操作的数据宽度,决定是否需要对结果进行补位:

 

posted @ 2022-03-22 20:03  wjcdx  阅读(393)  评论(0编辑  收藏  举报