美文网首页
verilog实现卷积运算

verilog实现卷积运算

作者: 氢立方 | 来源:发表于2019-05-24 09:35 被阅读0次

    本文转自本人csdn
    本文地址
    卷积是一种线性运算,是很多普通图像处理操作的基本算法之一。它提供了两个数组相乘的方式,两个数组拥有不同的大小,但是具有相同的维数,生成了一个用于相同维数的新数组。可以用来图像的执行操作,输入一组特定的像素值线性组合成为另一组像素值。在图像处理中常见的msk运算都是卷积,广泛应用于图像滤波。
    1.1卷积出现的背景
    卷积是在信号与线性系统的基础或背景中出现的,脱离这个背景单独谈卷积是没有任何意义的,除了那个所谓褶反公式上的数学意义和积分(或求和,离散情况下)。
    信号与线性系统,讨论的就是一个信号经过一个线性系统以后发生的变化(就是输入、输出和所经过的所谓系统,这三者间的运算关系)。
    因此,实际都是要根据我们需要处理的信号形式,来设计所谓的系统传递函数,那么这个系统的传递函数和输入信号,在数学上的形式就是所谓的卷积关系。
    卷积关系最重要的一种情况,就是在信号与线性系统或数字信号处理中的卷积定理。利用该定理,可以将时间域或空间域中的卷积运算等价为频率域的相乘运算,从而利用FFT等快速算法,实现有效的计算,节省运算代价。卷积的本质是滑动平均思想,它就是一种微元相乘累加的极限形式。卷积本身不过是一种数学运算而已,就跟“蝶形运算”一样,在信号与系统中,y(t)=f(t)h(t)。时域的卷积等于频域的乘积,即有Y(s)=F(s)×H(s),拉氏变换后得到的函数其实就是信号的频域表达式)。
    然而,在通信系统中,我们关心的以及要研究的是信号的频域,不是时域,原因是因为信号的频率是携带有信息的量。所以,我们需要的是这个表达式,但是实际上,我们往往不能很容易得到。
    卷积其实就是通过两个函数 f
    g生成第三个函数的一种数学算法,表征函数与经过翻转和平移的的重叠面积。如果将参加卷积的一个函数看作区间的指示函数,卷积也可以被看做是“移动平均”的推广。
    图1.1中两个方形脉冲波做卷积。其中函数首先对反射,接着平移“t”,成为。那么重叠部分的面积就相当于“”处的卷积,其中横坐标代表待积变量以及新函数的自变量。

    卷积示意图
    构造一个3 * 3的卷积核,并利用该卷积核完成与6
    6矩阵的卷积运算,数据位宽8bit补码数, 结果位宽20bit补码数。
    卷积的基本过程如下:
    对卷积核进行180度翻转(数据读写顺序的调度)将33卷积核的中心对准66矩阵的每个数进行对应数据乘累加得出结果,如此往复作业。输入数据补码8bit,实际有效7bit,输出数据补码20bit,实际有效19bit,卷积增加位数3*3=9,所以单个乘法最多增加19-7-9=3bit。所以卷积核采用3bit数,即4bit补码数。
    内置电路图如图二所示:
    芯片内置图.PNG

    正如第二部分对本次设计的介绍,我们要做到的是对模拟信号的采样由A/D转换器来完成,而卷积过程由信号的移位来实现。为了设计卷积运算器,首先要设计RAM 和A/D转换器的VerilogHDL 模型。在电子工业发达的国家,可以通过商业渠道得到非常准确的外围器件的虚拟模型。如果没有外围器件的虚拟模型。因为RAM和A/D转换器不是我们设计的硬件对象,所以需要的只是他们的行为模型,精确的行为模型需要认真细致的编写,并不比综合模块容易编写。
    运算过程简介
    系统内置33的4bit补码数的卷积核

    33卷积核.PNG
    外部输入6
    6的8比特补码数
    66矩阵..PNG
    工作过程:系统将反转后的卷积核与上图某一紫色圈圈数据和其周围的红色框框中的数据进行相乘并相加。直到所有红色矩形框中的部分均进行过卷积操作。
    举例如下,对应第一个要进行卷积操作的数D32
    输出数据为:O32= D21C33 + D22C32 + D23C31+ D31C23 + D32C22 + D33C21+ D11C13 + D42C12 + D43C11 ;
    工作说明
    每次启动后TB读取要卷积的数据,并将此数据传输给CONV,每次传输一个数据即8bit。
    CONV接收完数据后开始卷积。卷积结束后把数据传输给TB,每次传输一个数据即20bit。
    结果验证
    Python中的Scipy包致力于科学计算中常见问题的各个工具箱。它的不同子模块相应于不同的应用。例如:插值、积分、优化、图像处理、统计、特殊函数等等。通常用于计算numpy矩阵,有效便捷。
    1)Python中卷积实现的原理:对于in_c个通道的输入图,如果需要经过卷积后输出out_c个通道图,那么总共需要in_c
    out_c个卷积核参与运算。例如,输入为[h:5,w:5,c:4],那么对应输出的每个通道,需要4个卷积核。输出为3个通道,所以总共需要3*4=12个卷积核。对于单个输出通道中的每个点,取值为对应的一组4个不同的卷积核经过卷积计算后的和。
    python卷积结果.PNG
    仿真说明
    Modelsim可以支持命令行的方式,通过创建do文件,可以集成多个可执行的命令。那么对于前期一边编写代码,一边进行功能仿真,使用do文件是可以明显提高工作的效率。
    编写wave文件对其进行波形仿真:在仿真前Transcript中命令:do wave.do加载预设波形;输出结果在Transcript中查看,输出结果如图:
    波形仿真结果图.PNG
    图片1.png
    可以看到上图的红色框框中的输出结果与python的验证结果相同,仿真结果图中的输入数据与input_data中的数据一致。

    我们以输入为6个通道的矩阵作为输入、3*3的卷积核、1个通道宽高分别为4的输出,作为结果验证。
    在modelsim,我们主要对testbench进行仿真。testbench代码如下:

    //TESTBENCH 
    `timescale 1us/1us
    module TESTBENCH();
    reg  signed  [7:0] TiData[1:6][1:6];  // Test input  Data
    reg  signed [19:0] ToData[1:4][1:4];  // Test output Data
    reg  signed  [7:0] TiDataSingle;  // for transmission
    wire signed [19:0] ToDataSingle;  // for transmission
    reg clk;
    reg reset;
    reg CONV_start;
    wire CONV_finish;
    reg [7:0] i;
    reg [7:0] j;
    parameter period = 10;
    parameter hperiod = 5;
    CONV CONV_T(
        .reset(reset),
        .clk(clk),
        .CONV_start(CONV_start),
        .CONV_finish(CONV_finish),
        .CONV_iData(TiDataSingle),
        .CONV_oData(ToDataSingle));            
    initial
    begin 
    $display("0.Load  Data");
      $readmemh("Data_input.txt", TiData);
      for(i = 1; i < 7; i = i + 1)
        $display("%d %d %d %d %d %d", TiData[i][1], TiData[i][2], TiData[i][3],
                                      TiData[i][4], TiData[i][5], TiData[i][6]);
      
      clk = 0;
      CONV_start = 0;  
      reset = 1;      // Reset Chip
      #period  
      reset = 0;      // Chip Working
      #period 
      CONV_start = 1; // CONV start and writing data
      // align test data to the negedge of clk  
    $display("1.Write Data");
      for(i = 1; i < 7; i = i + 1)
      for(j = 1; j < 7; j = j + 1)
      begin
          TiDataSingle = TiData[i][j];
          #period;
      end
      CONV_start = 0; // finish writing data  
    $display("2.Convolution");
      while(!CONV_finish) #period;
      #period;
    $display("3.Read  Data");
      for(i = 1; i < 5; i = i + 1)
      for(j = 1; j < 5; j = j + 1)  
      begin
          ToData[i][j] = ToDataSingle;
      end  
      for(i = 1; i < 5; i = i + 1)
        $display("%d %d %d %d", ToData[i][1], ToData[i][2], ToData[i][3], ToData[i][4]);  
    $display("End"):
    end
    always #hperiod clk = !clk;
    endmodule
    
    

    verlog源码

    module CONV(
    input wire reset,
    input wire clk,
    input wire CONV_start,
    output reg CONV_finish,
    input wire signed  [7:0] CONV_iData,
    output reg signed [19:0] CONV_oData
    );
      
    reg signed [3:0]CONV_core[1:9];
      
    reg  [3:0] ii_count;
    reg  [3:0] ij_count;
    reg  [3:0] ci_count;
    reg  [3:0] cj_count;
    reg  [3:0] oi_count;
    reg  [3:0] oj_count;
    
    reg  signed  [7:0] CONV_iArrayData[1:6][1:6];  // input  Data
    reg  signed [19:0] CONV_oArrayData[1:4][1:4];  // output Data
    reg  CONV_StartCal;  // Start convolution
    
    // For ReConstruct
    wire signed  [7:0] CONV_iReCon[1:9];  // input ReConstruct Temp
    wire signed [19:0] CONV_mul[1:9];
    wire signed [19:0] CONV_result;
    
    // Calculating Convolution
    assign CONV_iReCon[1] = CONV_iArrayData[ci_count+0][cj_count+0];
    assign CONV_iReCon[2] = CONV_iArrayData[ci_count+0][cj_count+1];
    assign CONV_iReCon[3] = CONV_iArrayData[ci_count+0][cj_count+2];
    assign CONV_iReCon[4] = CONV_iArrayData[ci_count+1][cj_count+0];
    assign CONV_iReCon[5] = CONV_iArrayData[ci_count+1][cj_count+1];
    assign CONV_iReCon[6] = CONV_iArrayData[ci_count+1][cj_count+2];
    assign CONV_iReCon[7] = CONV_iArrayData[ci_count+2][cj_count+0];
    assign CONV_iReCon[8] = CONV_iArrayData[ci_count+2][cj_count+1];
    assign CONV_iReCon[9] = CONV_iArrayData[ci_count+2][cj_count+2];
    
    assign CONV_mul[1] = CONV_core[9]*CONV_iReCon[1];
    assign CONV_mul[2] = CONV_core[8]*CONV_iReCon[2];
    assign CONV_mul[3] = CONV_core[7]*CONV_iReCon[3];
    assign CONV_mul[4] = CONV_core[6]*CONV_iReCon[4];
    assign CONV_mul[5] = CONV_core[5]*CONV_iReCon[5];
    assign CONV_mul[6] = CONV_core[4]*CONV_iReCon[6];
    assign CONV_mul[7] = CONV_core[3]*CONV_iReCon[7];
    assign CONV_mul[8] = CONV_core[2]*CONV_iReCon[8];
    assign CONV_mul[9] = CONV_core[1]*CONV_iReCon[9];
    
    assign CONV_result = CONV_mul[1] + CONV_mul[2] + CONV_mul[3] + 
                         CONV_mul[4] + CONV_mul[5] + CONV_mul[6] + 
                         CONV_mul[7] + CONV_mul[8] + CONV_mul[9];
        
                    
    // Init Core
    always @(posedge reset)
    begin
      CONV_core[1] <= 4'h1;
      CONV_core[2] <= 4'h2;
      CONV_core[3] <= 4'hf;
      CONV_core[4] <= 4'hd;
      CONV_core[5] <= 4'h5;
      CONV_core[6] <= 4'h3;
      CONV_core[7] <= 4'he;
      CONV_core[8] <= 4'h1;
      CONV_core[9] <= 4'h2;
    end
    
    
    // Load input Data
    always @(posedge clk or posedge reset or posedge CONV_finish)
    begin
      if(reset || CONV_finish)
      begin
        ii_count <= 1;
        ij_count <= 1;  
        CONV_StartCal <= 0;
      end
      else if(CONV_start && (ii_count < 7))
      begin
        if(ij_count < 6)  ij_count <= ij_count + 1;
        else  
        begin
          if(ii_count < 6)begin ii_count <= ii_count + 1; ij_count <= 1;  end
          else            begin CONV_StartCal <= 1; end
        end
        CONV_iArrayData[ii_count][ij_count] <= CONV_iData;  // Load Data
      end
    end
    
    
    // Convolution
    always @(posedge clk or posedge reset)
    begin
      if(reset)
      begin
        ci_count <= 1;
        cj_count <= 1;  
        CONV_finish <= 0;
    
      end
      else if(CONV_StartCal && (ci_count < 5))
      begin
        if(cj_count < 4)            cj_count <= cj_count + 1;
        else 
        begin
          if(ci_count < 4)  begin ci_count <= ci_count + 1; cj_count <= 1;  end
          else              begin CONV_finish <= 1; end
        end
          
        CONV_oArrayData[ci_count][cj_count] <= CONV_result; // Record the Result
      end
    end
      
    // Output Data
    always @(posedge clk or posedge reset or posedge CONV_start)
    begin
      if(reset || CONV_start)
      begin
        oi_count <= 1;
        oj_count <= 1;
      end
      else if(CONV_finish && (oi_count < 5))
      begin  
        if(oj_count < 4)  oj_count <= oj_count + 1;
        else  
        begin
          if(oi_count < 4)begin oi_count <= oi_count + 1; oj_count <= 1;  end
    
        end
        CONV_oData <= CONV_oArrayData[oi_count][oj_count];  // Output Data
      end
      
    end
      
      
    endmodule
    

    python验证

    import numpy as np
    from scipy import signal
    from scipy import misc
    input_data=[
                 [1,    2,  3,  4,  5,  6],
                 [17,   18,19,20,21,22],
                 [33,   34,35,36,37,38],
                 [65,   66,67,68,69,70],
                 [-127,-126,-125,   -124,   -123,   -122],
                 [-95,-94,-93,  -92,-91,    -90]
                ]
    heigh,wid=input_data[:2]
    weights_data=[
                  [1    ,2,-1],
                  [-3,5,3],
                  [-2   ,1,2]
    
               ]
    heigh1,wid1 = weights_data[:2]
    con_result = signal.convolve(input_data,weights_data,mode=
                                 'full')
    grad=signal.convolve2d(weights_data,input_data)
    print(grad[2:6,2:6])
    

    小编还在成长,请大家多多指教!

    相关文章

      网友评论

          本文标题:verilog实现卷积运算

          本文链接:https://www.haomeiwen.com/subject/vipwzqtx.html