具有手动内存管理的装配卷积

Assembly Convolution with manual memory management

我有 riscv 处理器和可编程的扩展处理器。换句话说,扩展程序有自己独特的 ISA。

我将向此扩展插入指令,以通过 运行 在 riscv 处理器上的程序执行卷积。 换句话说,如果我 运行 以下代码,则 riscv 处理器会在每次迭代时向此扩展插入 lw 指令。 k对应寄存器号,kernel_pointer是SRAM的地址。

for(int k = 0; k < input_channel_size; k++){lw_encode(k, kernel_pointer);kernel_pointer++;}

此扩展有自己的 DRAM 和 SRAM,必须手动管理。我很难处理内存管理。在我的情况下,DRAM 容量是无限的,SRAM 容量是 1024 字。我可以访问只有 32 字节对齐访问的 DRAM,并且它的访问必须传输 32 字节的倍数。

鉴于每个像素是 32 位,如您所知,由于在几乎所有情况下,整个图像和内核都无法提取到 SRAM 中,因此我只能提取少量图像并进行迭代计算。但是由于 DRAM 只允许访问 32 字节对齐的地址,而不是 32 位对齐的地址,并且由于它必须获取 32 字节而不是 32 位,所以在很多情况下我必须获取不必要的或下一个操作数像素。

对于SRAM的地址,我将0~255地址分配给输入图像,256~511分配给过滤器,512~755分配给目标地址,我想保留756~1023以供进一步扩展。当然这个寻址是任意的,可以临时更改。

在这种情况下,例如让image[32][32][6]和filter[3][3][6] 如果我根据 filter[0][0][0:6] 考虑乘法和加法,我必须获取图像 [0:30][0:30][0:6]。并且由于获取的图像像素的最小尺寸为 8,因此不可能成像 [0][0] 因为它只有 6 个像素,这意味着 2 个像素是不必要的。我试图通过引入循环队列概念来解决这个问题,但不确定如何处理这种情况。如果获取了超出范围的像素怎么办?例如,image[31][31][0:6] 可能由于 属性 的 DRAM 访问而被提取。

为了便于理解我的情况,这里是代码片段。我认为这段代码不起作用,因为它试图访问不是 32 字节对齐的 DRAM 地址。

        if(input_channel_size < 16){ // weight_stationary_case
        dram_read_wrapping(kernel_pointer, dram_kernel, 9 * input_channel_size, k_corner, false);
        for(int m = 0; m < 9; m++){
            for(int k = 0; k < input_channel_size; k++){
                lw_encode(k, kernel_pointer);
                kernel_pointer++;
            }
            for(int i = 0; i < size_of_output * size_of_output;){
                f_num = dram_read_wrapping(feature_pointer, dram_feature, input_channel_size, f_corner, true);
                for(int j = 0; j < f_num; j++){
                    if(((j + 1) % size_of_output) == 0){
                        feature_pointer += 2 * input_channel_size;
                        feature_pointer = feature_pointer % 0x100;
                        destination_end += 2;
                        if(destination_end >= 0x300) dram_write_wrapping(destination_start, dram_result, destination_end, d_corner);
                        j++;
                        continue;
                    }
                    for(int k = 0; k < input_channel_size; k++){
                        lw_encode(k, feature_pointer);
                        feature_pointer++;
                        feature_pointer = feature_pointer % 0x100 ;
                    }
                    // mac_reserve();
                    for(int k = 0; k < 3; k ++){
                        mac_encode(31, 1 + k, 4 + k);
                    }
                    // mac_reserve();
                    sw_encode(31, destination_end++);
                    if(destination_address == 0x300) dram_write_wrapping(destination_start, dram_result, destination_end, d_corner);
                }
                i += f_num;
            }
            if(m == 2 || m == 5) dram_feature += 2 * input_channel_size;
            else dram_feature++;
        }
    }

有什么想法对这种情况有帮助吗?谢谢。 另外,如果上下文不明确,请告诉我,以便我更新描述。

基本上我都是用上面介绍的方法来处理这个问题的

对于 DRAM 读取情况,如果存在未对齐访问,我会跨边界读取所有内容,这意味着我最多可以将三个块提取到 SRAM。我将其与切断指针一起使用。

对于DRAM写入情况,如果存在未对齐访问,我将相应的块读取到SRAM,然后连续写入我想要的东西到SRAM并写入DRAM。

这项技术解决了问题,'Cache Blocking' 和 'Loop Tiling' 使程序更快,@PeterCordes 提到了这一点。