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及操作的数据宽度,决定是否需要对结果进行补位: