本文先总结不同AXI IP核
的实现的方法,性能的对比,性能差异的分析,可能改进的方面。使用的硬件平台是Zedboard
。
不同的AXI
总线卷积加速模块的概况
这次实现并逐渐优化了三个版本的卷积加速模块,先简要描述各个版本的主要内容。
版本一
版本一主要是用来测试AXI
总线IP
核的实现可能。
- 该模块拥有19个32位寄存器
- 其中前9个寄存器用来保存需要计算的值
- 后面9个寄存器用来保存卷积核
- 在读取第19个寄存器的地址的时候计算9个寄存器的卷积和(该计算可以在一个时钟周期内完成)
- 9个寄存器单独赋值,程序中分别向对应地址写入内容,通过总线进行传输。
- 故乐观的来算,需要10个总线周期可以获取一个输出
可以从驱动的书写简单理解一下:
void Conv_HW(int filter[3][3], int arr[100][100],
int filterW, int filterH, int arrW, int arrH) {
int i, j;
for (i = 2; i < filterH + arrH - 3; i++) {
for (j = 2; j < filterW + arrW - 3; j++) {
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR, arr[i][j]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+4, arr[i][j - 1]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+8, arr[i][j - 2]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+12, arr[i - 1][j]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+16, arr[i - 1][j - 1]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+20, arr[i - 1][j - 2]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+24, arr[i - 2][j]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+28, arr[i - 2][j - 1]);
Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR+32, arr[i - 2][j - 2]);
res[i][j] = Xil_In32(XPAR_CONV_0_S00_AXI_BASEADDR + 72);
}
if (i % 15 == 0)
printf("=");
}
}
版本一性能
- 版本一性能最惨,由于没有时间戳,目测软件计算速度远远快于FPGA核心运算速度。
- 版本一的改进速度就是引入滑动窗口,能够最大程度减少总线周期。
版本二
版本二引入滑动窗口,和初期设计的概念相同。
该模块拥有19个32位寄存器
其中前9个寄存器用来保存需要计算的值
后面9个寄存器用来保存卷积核
在读取第19个寄存器的地址的时候计算9个寄存器的卷积和(该计算可以在一个时钟周期内完成)
三个寄存器滑动赋值,该计算窗口在计算矩阵上滑动 除了冷启动多余两个周期用来预载寄存器,后面的每一个计算只需要四个总线周期
可以通过写的驱动简单理解一下:
void Conv_HW(int filter[3][3], int arr[100][100], int arrW, int arrH) { int i, j; i = 2; j = 2; for (i = 2; i < arrH; i++) { //pre load Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 8, arr[i - 1][j - 1]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 20, arr[i][j - 1]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 32, arr[i + 1][j - 1]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 8, arr[i - 1][j]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 20, arr[i][j]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 32, arr[i + 1][j]); for (j = 2; j < arrW; j++) { Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 8, arr[i - 1][j + 1]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 20, arr[i][j + 1]); Xil_Out32(XPAR_CONV_0_S00_AXI_BASEADDR + 32, arr[i + 1][j + 1]); res[i][j] = Xil_In32(XPAR_CONV_0_S00_AXI_BASEADDR + 72); } } }
版本二性能
测试样本 500*500
的32bit
单位的矩阵 计算200
次。
软件消耗33.78
秒,卷积IP
核心40.25
秒
这样的结果还是非常不乐观,分析可能有两种限制了IP核
的速度。
- 两个寄存器的乘法LUT太大,无法硬件优化
- 总线周期太慢太慢
版本三对于这两种可能进行探索。
版本二的FPGA部分核心代码
// Implement memory mapped register select and write logic generation
// The write data is accepted and written to memory mapped registers when
// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
// select byte enables of slave registers while writing.
// These registers are cleared when reset (active low) is applied.
// Slave register write enable is asserted when valid address and data are available
// and the slave is ready to accept the write address and write data.
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 0;
slv_reg1 <= 0;
slv_reg2 <= 0;
slv_reg3 <= 0;
slv_reg4 <= 0;
slv_reg5 <= 0;
slv_reg6 <= 0;
slv_reg7 <= 0;
slv_reg8 <= 0;
slv_reg9 <= 0;
slv_reg10 <= 0;
slv_reg11 <= 0;
slv_reg12 <= 0;
slv_reg13 <= 0;
slv_reg14 <= 0;
slv_reg15 <= 0;
slv_reg16 <= 0;
slv_reg17 <= 0;
// slv_reg18 <= 0;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
5'h00:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
5'h01:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 1
slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
5'h02:
begin
slv_reg0 <= slv_reg1;
slv_reg1 <= slv_reg2;
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 2
slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
end
5'h03:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 3
slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
5'h04:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 4
slv_reg4[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
5'h05:
begin
slv_reg3 <= slv_reg4;
slv_reg4 <= slv_reg5;
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 5
slv_reg5[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
end
5'h06:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )