实验对CPU的修改不多,如果完全按照RISC-V的要求设计CPU,将mul归类为R-Type的话,改动较多。但是我们可以单独设计一跟op_mul电线,然后单独设计乘法指令。
乘法指令的实现,直接使用其提供的乘法部件即可:
wire [31: 0] Result_MUL = RF_rdata1*RF_rdata2;
随后在选数里增加一项MUL的指令:
assign RF_wdata = {32{op_shift}}&shift_result|
{32{op_J_Type}}&PC_4|
{32{op_lui}}&extend|
{32{op_auipc}}&(PC_temp+extend)|
{32{(op_I_Type|op_R_Type)&~op_shift}}&ALU_result|
{32{op_lw}}&load_word|
{32{op_lb }}& {{24{load_byte[7]}},load_byte}|
{32{op_lbu}}& {{24{1'b0}},load_byte}|
{32{op_lh}}& {{16{load_half[15]}},load_half}|
{32{op_lhu}}& {{16{1'b0}},load_half}|
{32{op_mul}}& Result_MUL
;
并按照R-Type的指令设计,设计自动机的转移:
`state_EX : begin
if(op_B_Type)
next_state = `state_IF;
else
if(op_R_Type|op_I_Type|op_U_Type|op_J_Type|op_mul)
next_state = `state_WB;
else
if(op_S_Type)
next_state = `state_ST;
else
if(op_L_Type)
next_state = `state_LD;
else
next_state= `state_EX;
end
其次是DNN相关的代码:
按照定义,需要设计几种中间变量:
short bias;
short num_out;//output
short num_in;//input
short y,x,ky,kx;
short k_square=1+mul(weight_size.d2,weight_size.d3);
short in_square=mul(input_fm_h,input_fm_w);
bias指的是偏移量,存在
F
i
l
t
e
r
[
i
]
[
0
]
[
0
]
Filter\left[i\right]\left[0\right]\left[0\right]
Filter[i][0][0]位置。
其次num_in和num_out分别是吗枚举读入的图片通道数和输出的特征图片通道数。
x、y枚举的是当前图像处理的像素点位置,kx、ky指的是每一次卷积操作的时候卷积核的相对位置。
k_square和in_square是计算下标的中间变量。
在设计out的赋值时我发现,其赋值规律是顺序的,所以可以设计一个outoffset来计算位置。
int out_offset=0;
short bias_offset=mul(rd_size.d1,k_square);
在第一重循环,枚举的是当前输出的通道数,在这一层可以计算bias的值。在第二层,枚举输入的通道数,随后枚举当前通道这张图片的像素点位置。在这种过程中,更据定义,需要增加当前图片间的步长Stride_X、Stride_Y。对于每个卷积核,其每个方块的由kx、ky两个块进行枚举。
在这里由于存在边界填充,所以存在一个pad的偏移,于是在这里需要作差的得到iw和ih。随后求出当前的像素值和卷积的权重,随后便可以计算卷积后的结果,在这里中间变量需要用32位整数存储。
在这里,值得注意的是16位定点数的符号位需要由32位定点数的符号位求出。最后,由于发现输出特征图的像素顺序是良序的,所以可以直接不断加1得到结果。
另外一个比较重要的步骤是池化操作,池化操作本质是取最大值,所以实现起来比较简单。
池化操作的计算也就是找到对应像素点位置,然后比个大小就可以了。注意到这里最大值max的初始值是0x8000,这在16位定点数里表示最小值,因此一定会被更大的数所替换。
硬件加速器就很简单了:
#ifdef USE_HW_ACCEL
void launch_hw_accel()
{
volatile int* gpio_start = (void*)(GPIO_START_ADDR);
volatile int* gpio_done = (void*)(GPIO_DONE_ADDR);
* gpio_start =(* gpio_start )|0x01;
while(1){
if((*(volatile char*)gpio_done)&0x01)break;
}
return;
}
#endif
按照流程图,如果开始将对应位置低位赋值1,如果检测到结束位置低位赋值1就结束。