verilog实现卷积运算

本文转自本人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])

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342