Rocket-chip-pipelinemul
由于电脑原因,旧电脑不能运行了,在新电脑中由于分辨率的问题,某些图的字体会特别小,大家认真点看,谢谢。
这次我们介绍的功能是pipeline-mul。
这部分没有协议的内容。
但我说明一下如何生成PipelineMultiplier模块的。
我用的是比较旧的scala代码,如果文件对不上了,那自行寻找关键字完成修改。
修改地方如下,将mulUnroll改为与总线位宽相同的值,则会自动生成PipelineMultiplier模块,至于更具体的类,大家可以以这几个关键字去搜索,我就不在这里展开详细的说明。(MulDivParams、pipelinedMul、PipelinedMultiplier())
接下来直接看测试代码。
#include <stdlib.h>
#include "L1Dcache.h"
#define U32 *(volatile unsigned int *)
#define DEBUG_SIG 0x70000000
#define DEBUG_VAL 0x70000004
//--------------------------------------------------------------------------
// handle_trap function
void handle_trap()
{
asm volatile ("nop");
while(1);
}
//--------------------------------------------------------------------------
// mul test function
void mul(signed int a,signed int b)
{
signed int sign_a,sign_b;
signed long long sign_r;
unsigned int unsign_a,unsign_b;
unsigned long long unsign_r;
sign_a = -a;
sign_b = b;
sign_r = sign_a * sign_b;
U32(0x60001000) = sign_r;
U32(0x60001004) = (sign_r>>32);
sign_a = -a;
sign_b = b;
sign_r = sign_a * (signed long long)sign_b;
U32(0x60001010) = sign_r;
U32(0x60001014) = (sign_r>>32);
sign_a = -a;
sign_b = b;
sign_r = (signed long long)sign_a * (signed long long)sign_b;
U32(0x60001020) = sign_r;
U32(0x60001024) = (sign_r>>32);
unsign_a = (unsigned int)a;
unsign_b = (unsigned int)b;
unsign_r = unsign_a * unsign_b;
U32(0x60001030) = unsign_r;
U32(0x60001034) = (unsign_r>>32);
unsign_a = (unsigned int)a;
unsign_b = (unsigned int)b;
unsign_r = unsign_a * (unsigned long long)unsign_b;
U32(0x60001040) = unsign_r;
U32(0x60001044) = (unsign_r>>32);
unsign_a = (unsigned int)a;
unsign_b = (unsigned int)b;
unsign_r = (unsigned long long)unsign_a * (unsigned long long)unsign_b;
U32(0x60001050) = unsign_r;
U32(0x60001054) = (unsign_r>>32);
}
//--------------------------------------------------------------------------
// Main
void main()
{
mul(0x20000001,0x12345678);
mul(0xFFFFFFF7,0x12345678);
mul(0xFFFFFFF7,0xFFFFFFFF);
U32(DEBUG_SIG) = 0xFF;
}
上面代码中我特意使用了int和long long的类型,这样做是为了实现32位CPU中运行32*32的乘法运算。
mul函数的作用是两个:
- 将32位有符号数a取负,然后和32位有符号数b相乘。
- 将32位有符号数a强制为无符号数,然后和32位强制为无符号数的b相乘。
每个功能运行3次,是为了体现rocket-chip对一个64位数据的高32位处理情况。
运算结果:
(+) (unsigned) 0x20000001 * (+) (unsigned) 0x12345678 = (unsigned) 0x02468ACF_12345678
(-) (signed) 0x20000001 * (+) (signed) 0x12345678 = (signed) 0xFDB97530_FEDCBA988
(+) (unsigned) 0xFFFFFFF7 * (+) (unsigned) 0x12345678 = (unsigned) 0x12345677_5C28F5C8
(-) (signed) 0xFFFFFFF7 * (+) (signed) 0x12345678 = (signed) 0x00000000_A3D70A38
(+) (unsigned) 0xFFFFFFF7 * (+) (unsigned) 0xFFFFFFFF = (unsigned) 0xFFFFFFF6_00000009
(-) (signed) 0xFFFFFFF7 * (+) (signed) 0xFFFFFFFF = (signed) 0xFFFFFFFF_FFFFFFF7
0x20000001和0x12345678的运算结果可以看下图。
图中PipelilineMultiplier模块是在core层中的,端口信息我也特意拉出来了。
模块中,只有io_req_valid为1时,才会进行乘法运算。黄色长箭头表示乘法运算时刻,黄色短箭头表示乘法运算结果输出时刻,可以看出输出结果是慢了一个周期,输出结果的时间是可以调节的,后面再详细说明。绿色箭头的信号是AXI4总线信号,分别输出64位计算结果的低32位和高32位,低32位先输出。蓝色箭头是指示io_req_bits_fn[3:0]的差别。第一个蓝色箭头的值是0,第二个是1,下面会对这个值进行说明。
需要注意的是,这张图中看到高32位的数据是0xFFFF_FFFF,而我们计算的结果是0xFDB97530_FEDCBA988,高32位应该是0xFDB_97530才对,那为什么会出现这种情况呢?是RTL代码错误了吗?接下来我们会对这种情况作详细的分析。
现在先对蓝色箭头的io_req_bits_fn[3:0]进行说明。
在对scala代码进行解释前,先看一下C语言编译出来的反汇编文件。
可以看到,(signed)32*(signed)32是由两条指令完成的,分别是mul处理低32位,mulh处理高32位,而(unsigned)32*(unsigned)32则有mul和mulhu这两条指令完成一次运算。移位处理是采用srai指令。
再看看scala代码对FN_MULX的定义,如下图。
可以看到FN_MUL对应的是0,FN_MULH对应的是1,FN_MULHSU对应的是2,FN_MULHU对应的是3,也就是上面蓝色箭头对应的操作分别是mul和mulh,下图是运行mulhu的波形,也就是io_req_bits_fn[3:0]==2’b3的时候。可以看到运算结果是延后一拍输出的,最右边的那个红色箭头应该是蓝色才对,对应mulhu的结果输出。
到这里蓝色箭头io_req_bits_fn[3:0]的含义就说明清楚了。
然后接下来看绿色箭头为什么高32位结果和计算结果不一样的原因。
在说明原因前,我们先看看C代码,如下图,这三次运算得到的结果应该是一样的,但到了CPU硬件层面后,好像出现了一定的差别。
差别看下图。
这个图中,黄色箭头表示输出低32位的结果,蓝色箭头表示输出高32位的结果,绿色箭头就是前面输出0xFFFF_FFFF的地方,特意用绿色箭头标出。这三组箭头和上面C代码的三次运算时一一对应的。也就是说,C代码中,变量即使声明了signed long long,在右边运算过程中,两个变量都是signed int的话,得到的结果只会是signed int,要左边变量也是signed long long,就必须将右边的一个或两个变量变为signed long long。
- (signed int) signed long long = signed int x signed int
- signed long long = signed int x signed long long
- signed long long = signed long long x signed long long
- signed long long = signed int x (signed long long) signed int
- signed long long = (signed long long) signed int x (signed long long) signed int
()为强制转化类型。
到这里,绿色箭头的异常原因也说明清楚了。
最后说明的是乘法运算结果输出周期可调的问题。
直接看scala 代码,在PipelinedMultiplier()调用的过程中,输入了两个参数,分别是xLen和2,在Multiplier.scala中知道,这个2是latency,是用延迟乘法运算结果的输出,具体延迟周期为latency-1。所以理论上可以调节这个参数来控制乘法运算的结果输出。这里只说明有这个功能,但具体的实现我没有操作过。
接下来看一下RTL的代码是如何的。
可以看到乘法运算的结果是在inPipe_valid为1的时候才能输出,而inPipe_valid则是io_req_valid打一拍的结果,如果修改了latency,变化的地方应该就是这个打一拍的地方。
关于PipelineMultiplier模块的说明,到这里为止。
此外需要说明的是,乘法实现pipeline需要的面积是可以接受的,16*16=32的情况,基本是单周期输出,所以乘法的运算速度是可以的,但除法实现单周期输出结果需要很大的面积,很不划算,所以除法没有类似pipeline的操作,除法只要多周期的操作。