verilog實現矩陣卷積運算

氫立方發表於2019-05-24

verilog實現卷積運算

卷積的運算原理

卷積是一種線性運算,是很多普通影像處理操作的基本演算法之一。它提供了兩個陣列相乘的方式,兩個陣列擁有不同的大小,但是具有相同的維數,生成了一個用於相同維數的新陣列。可以用來影像的執行操作,輸入一組特定的畫素值線性組合成為另一組畫素值。在影像處理中常見的msk運算都是卷積,廣泛應用於影像濾波。
1.1卷積出現的背景
卷積是在訊號與線性系統的基礎或背景中出現的,脫離這個背景單獨談卷積是沒有任何意義的,除了那個所謂褶反公式上的數學意義和積分(或求和,離散情況下)。
訊號與線性系統,討論的就是一個訊號經過一個線性系統以後發生的變化(就是輸入、輸出和所經過的所謂系統,這三者間的運算關係)。
因此,實際都是要根據我們需要處理的訊號形式,來設計所謂的系統傳遞函式,那麼這個系統的傳遞函式和輸入訊號,在數學上的形式就是所謂的卷積關係。
卷積關係最重要的一種情況,就是在訊號與線性系統或數字訊號處理中的卷積定理。利用該定理,可以將時間域或空間域中的卷積運算等價為頻率域的相乘運算,從而利用FFT等快速演算法,實現有效的計算,節省運算代價。卷積的本質是滑動平均思想,它就是一種微元相乘累加的極限形式。卷積本身不過是一種數學運算而已,就跟“蝶形運算”一樣,在訊號與系統中,y(t)=f(t)*h(t)。時域的卷積等於頻域的乘積,即有Y(s)=F(s)×H(s),拉氏變換後得到的函式其實就是訊號的頻域表示式)。
然而,在通訊系統中,我們關心的以及要研究的是訊號的頻域,不是時域,原因是因為訊號的頻率是攜帶有資訊的量。所以,我們需要的是這個表示式,但是實際上,我們往往不能很容易得到。
卷積其實就是通過兩個函式 fg生成第三個函式的一種數學演算法,表徵函式與經過翻轉和平移的的重疊面積。如果將參加卷積的一個函式看作區間的指示函式,卷積也可以被看做是“移動平均”的推廣。
圖1.1中兩個方形脈衝波做卷積。其中函式首先對反射,接著平移“t”,成為。那麼重疊部分的面積就相當於“”處的卷積,其中橫座標代表待積變數以及新函式的自變數。

圖1.1
構造一個3 * 3的卷積核,並利用該卷積核完成與66矩陣的卷積運算,資料位寬8bit補碼數, 結果位寬20bit補碼數。
卷積的基本過程如下:
對卷積核進行180度翻轉(資料讀寫順序的排程)將33卷積核的中心對準66矩陣的每個數進行對應資料乘累加得出結果,如此往復作業。輸入資料補碼8bit,實際有效7bit,輸出資料補碼20bit,實際有效19bit,卷積增加位數3
3=9,所以單個乘法最多增加19-7-9=3bit。所以卷積核採用3bit數,即4bit補碼數。
內建電路圖如圖二所示:
晶片內建圖
正如第二部分對本次設計的介紹,我們要做到的是對模擬訊號的取樣由A/D轉換器來完成,而卷積過程由訊號的移位來實現。為了設計卷積運算器,首先要設計RAM 和A/D轉換器的VerilogHDL 模型。在電子工業發達的國家,可以通過商業渠道得到非常準確的外圍器件的虛擬模型。如果沒有外圍器件的虛擬模型。因為RAM和A/D轉換器不是我們設計的硬體物件,所以需要的只是他們的行為模型,精確的行為模型需要認真細緻的編寫,並不比綜合模組容易編寫。
運算過程簡介
系統內建33的4bit補碼數的卷積核
3*3卷積核
外部輸入6
6的8位元補碼數:
6*6的補碼數
工作過程:系統將反轉後的卷積核與上圖某一紫色圈圈資料和其周圍的紅色框框中的資料進行相乘並相加。直到所有紅色矩形框中的部分均進行過卷積操作。
舉例如下,對應第一個要進行卷積操作的數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結果驗證
模擬說明
Modelsim可以支援命令列的方式,通過建立do檔案,可以整合多個可執行的命令。那麼對於前期一邊編寫程式碼,一邊進行功能模擬,使用do檔案是可以明顯提高工作的效率。
編寫wave檔案對其進行波形模擬:在模擬前Transcript中命令:do wave.do載入預設波形;輸出結果在Transcript中檢視,輸出結果如圖:
模擬結果
模擬結果
可以看到上圖的紅色框框中的輸出結果與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])

小編還在成長,請大家多多指教!

相關文章