HDL4SE:软件工程师学习Verilog语言

Posted 饶先宏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDL4SE:软件工程师学习Verilog语言相关的知识,希望对你有一定的参考价值。

11 流水线

前面一节介绍了状态机的概念。状态机用于描述事务处理的一个程序性流程,可以组成顺序,分支,循环的事务处理流程。这些概念本来在verilog中的行为级描述中是有的,但是由于不是RTL描述,因此无法直接编译成电路,状态机则提供了顺序,分支,循环等控制结构的RTL描述。
状态机的特点是,整个处理流程任何时候只会在一个状态中,只处理一个事务。比如描述一个软件工程师的工作,可能是需求分析,概要设计,详细设计,编码,调试,测试,并在这几个状态之间来回切换,用状态机是比较恰当的。但是如果对一个分工比较明确的团队,上述几种工作可能是固定由相应的小组完成的,整个团队可以同时开展几个项目,项目在小组之间流转,特定的小组只完成针对项目的固定事务,这就是工厂的生产流水线的概念。
流水线由处理不同事务的节点组成,节点之间必须协同工作,这种协同有两种方式,一种是同步的,这种流水线每个节点事务处理的时间是固定的,事务进入节点后,会在某个固定的时钟周期后完成。此时节点之间只要在时钟同步下进行协同就行了。典型的实例是4个时钟周期完成一个32位的浮点乘法运算,其实是将浮点乘法分成四步,每一步都在一个时钟周期内完成,从前一步接收输入数据,处理后送到下一步继续处理。这样,虽然每个浮点都需要四个时钟周期才能完成,但是浮点乘法器采用流水线结构后,每个时钟周期都能够接收一个运算请求,并且输出一个结果,也就是平均每个周期能够完成一个浮点乘法运算。而且这种流水线组织各个部件之间不需要连接控制信号,它们各自按照事先规定好的时钟周期处理,就能够协同完成事务,这种协同称为同步协同。还有一种就是异步的了,由于各个节点完成事务的时钟周期不确定,可能与事务内容相关,也有可能跟外部环境(比如多节点共享的DDR读写)相关。此时节点之间就需要进行通信,来实现节点之间的协同。一般的通信手段就是用一个通知信号传递信息,比如允许信号,完成信号什么的,高级一点的用个比如FIFO之类进行缓冲,让节点之间的协同相对柔性一些,不至于造成大量的相互等待,这样有助于提高整个流水线的效率。
我们按惯例还是举个例子来说明流水线的做法,本节先讨论异步流水线,关于同步流水线后面实现RISC-V CPU时会有相应例子。
本节以流行的深度卷积神经网络推理的实现作为例子。先介绍深度卷积神经网络的基本概念,然后描述具体实现步骤,也会分多次逐步修改实现,一开始是用c语言实现各个单元,后面逐步把每个c语言描述的模型改写成verilog的实现。目标是,将一个训练好的网络模型转换为c语言代码,然后在基本库的支持下运行推理过程,在运行过程中将网络结构和系数转换为verilog语言表示,然后将verilog代码描述的卷积神经网络编译为目标代码,用HDL4SE系统运行。

11.1 深度卷积神经网络简介

深度卷积神经网络是进行数据分类的比较有效的算法,本节中主要介绍GoogLeNet网络,它的输入是固定的224x224像素大小的彩色图像数据,每个像素有三个分量,对应RGB分量。GoogLeNet网络将图像分为1000类,每一幅图像经过GoogLeNet计算后,输出一个1000x1的向量,表示这幅图像属于对应类的概率。注意其中的字母L用大写,据说是为了纪念深度神经网络的先驱LeNet。
深度神经网络的基本算法是卷积。所谓卷积,可以理解为用一个模板,去与目标进行乘累加计算,得到的结果是目标与模板的相符程度。模板在宽度和高度上一般比较小,一般是1x1, 3x3, 5x5, 7x7 ,据说也有更大的。模板中每个位置对应图像每个分量也有一个值,这样对三个分量的原始图像, 一个7x7模板实际上由7x7x3个数字组成。一个1x1的模板,实际上是对分量之间的相关性进行计算,比如检验彩色图片中像素是否为红色,可能用个(1,-3,-3)的1x1x3模板去跟每个像素的RGB分量进行乘累加,后面抛掉小于零的结果,得到的数字基本上是像素偏红的程度。一般把模板称为卷积核,模板中的数字称为卷积核系数。用这个模板套在输入数据上,将7x7x3中的每个系数与对应的图像数据相乘后累加在一起,构成这次计算的一个输出值,可以理解为是图像这个位置与模板的相似程度。在水平方向和垂直方向上移动模板,每次移动的步长称为卷积的步长(Stride)。移动后再计算一次乘累加值,这样每移动到一个位置就得到一个的值,于是就构成了输出图像的一个分量。据说麻坛高手用拇指在牌面一划,就能通过卷积认出手上的牌是红中、发财、三万、九筒、七条等等中的哪一种,低手就只能区分白板和非白板,会不会跟他脑中的卷积核配置相关啊。用多个卷积核对图像进行计算,就可以得到一幅多分量图像,实际上已经不是通常意义下的图像了,姑且继续称之为图像吧。一般计算之前会在输入图像周边扩展几个像素行或列,保证图像边缘的数据信息不会丢失,这个称为边缘扩展(PAD)。
总结一下:对InWxInHxInC的输入图像,一个卷积操作就是用N个kWxkHxInC系数组成的卷积核,按照(sW,sH)的步长在图像上移动,移动到边缘时假定边上有pW, pH的扩展像素,扩展的图像像素分量全部假设为0。每个位置将kWxkHxInC个系数和图像数据对应相乘再累加在一起,生成一个输出图像的像素,这样输出的图像是OutWxOutHxN,其中
OutH = (InH + 2 * pH - kH) / sH + 1;
OutW = (InW + 2 * pW - kW) / sW + 1;
这个运算操作将一个InWxInHxInC的输入图像变换为一个OutWxOutHxOutC的图像。卷积核的参数包括卷积的大小kW, kH,卷积核的个数N也就是输出图像的分量数OutC,平移步长sW, sH,扩展宽度pW, pH,如下图所示。
在这里插入图片描述
卷积计算的结果中,每个结果像素的分量与原始图像中的一个卷积核大小范围内的所有像素的所有分量都相关,因此卷积核的大小,其实是结果分量中信息来源范围。从原理上讲,卷积核大小大的,可以从原图像中较大范围内得到相关的信息,卷积核小的,相关的范围就小一些,不同大小的卷积核构成了不同尺度的相关信息。按说卷积核大的能够收集到的信息要更加全面一些,大不了把有些系数设置为0,效果就跟小的核一样了,然而卷积核大的计算量要大,3x3是1x1的九倍,5x5是3x3的将近三倍,7x7是5x5的将近两倍。更重要的原因是,较大的卷积核由于范围输入更大,得到的特征值反而更容易受到污染,需要经过更多的样本计算才能得到合适的系数,我们看东西时实在眼花缭乱,可以眯着眼睛看,让进来的图像信息少一些,反而更容易识别是什么场景。因此设计算法时需要考虑计算量的考虑还需要考虑用合适大小的卷积核,当然分量数直接跟计算量线性相关,因此控制分量数也是算法设计中的一个考虑因素。
除了卷积之外,还有全连接层也是线性操作,就是输出图像中的像素分量跟输入图像中的每个像素及分量都相关,有对应的系数,不是类似卷积的局部相关,可以看成是一个大小跟输入图像一样大而且无法移动的卷积核,当然结果也就是一个数。对图像的操作还有所谓Pooling,有点象卷积,只不过它不是通过系数乘累加,而是计算出局部范围内的最大值或平均值, LRN则是在分量内或者跨分量进行能量归一化操作,这些都是非线性操作,还有更多类型的操作,每个操作称为一个算子。类型众多的算子给网络设计带来了更多的可能。
一般卷积或者Pooling之后对生成的每个像素分量会有一个所谓的激活函数的概念,大概的意思是模拟人类神经元细胞的输出必须有个最低阈值才能激活的意思。激活函数一般是非线性的,比如常用的ReLU激活,就是将小于0的分量全部变成0,只有大于0的分量才有机会携带信息参与后面的计算。(理论上有说道吗?我也不大清楚)
从理论上可以证明,用单层的卷积计算或者全连接计算是无法完成复杂分类的,因此卷积神经网络一般是多层连接在一起的,前面的输出图像作为后面的输入图像,构成所谓的深度卷积神经网络。后面还出现了一个输出图像送到多个不同的算子,或者多个计算节点的输出汇总成一个输出,前者试图对一个输出图像进行不同(尺度或方法)的特征提取,后者试图将不同的方法提取出来的特征连接在一起,通过一个卷积或其他办法得到这些特征的之间相关性,获得新的特征。这样一来,深度神经网络的计算就构成了一个计算网络。
在卷积神经网络的发展中,对于如何设计计算网络,没有明确的理论或者方法做指导,也无法对一个网络的效果进行理论分析,似乎大家都是拼结果,由结果好坏来证明网络的好坏(当然,也许是孤陋寡闻了…)。然而有一种所谓的inception结构,在网络中作为基本的构成部件,得到了很多网络模型的认同。inception结构的的基本思路是对同一个输入图像进行1x1, 3x3, 5x5三个卷积计算和一个Pooling计算,注意设置参数让每个计算输出的w,h一致,但是输出的分量数可以不一样,每个卷积后面都用ReLU进行激活,最后把四个计算结果的每个像素的各个分量合并在一起(Concat),形成一个新的图像,这样就能够将输入图像的不同尺度卷积和Pool的结果相关在一起,得到更为本质的图像特征(为什么?理论上能说道么?)。inception结构已经发展了四个版本,基本思路是一样的,只是在3x3, 5x5和Pooling的卷积的处理不同,大部分是为了减少计算量,比如在3x3之前先做个1x1, 5x5之前也先做个1x1,缩减输入分量数,达到减少计算量的目的,再比如用1x3和3x3替代5x5,减少计算量等等。
在GoogLeNet中,inception的结构如下:
在这里插入图片描述
整个GoogLeNet网络中有9个Inception结构,每个结构中只是输出的分量数不同。这些inception结构再加上一些其他节点构成整个GoogLeNet网络结构如下:
在这里插入图片描述
这里看着似乎就是一条流水线,然而如果把其中的inception展开,那么这个网络还是挺复杂的。注意其中inception_3a的结构稍有不同,没有3x3Pool之后的1x1卷积。
以下是各个节点的结构参数表,我们进行初步的统计,得到GoogLeNet对一幅图进行分类计算,需要的计算量是6390669848(乘累加),大概6.4G次左右,理论上1Tops的有效计算能力每秒能够分类156幅图像。

NO名称类型INWINHINCOUTWOUTHOUTCW/C0H/C1SW/C2SH/C3PWPHNACT计算量系数个数
1dataData22422432242243
2conv1/7x7_s2Convolution22422431121126477223364ReLU1188167689472
4pool1/3x3_s2Pooling11211264565664332200115806208
5pool1/norm1LRN565664565664802816
6conv2/3x3_reduceConvolution56566456566411110064ReLU130457604160
8conv2/3x3Convolution5656645656192331111192ReLU347418624110784
10conv2/norm2LRN565619256561922408448
11pool2/3x3_s2Pooling56561922828192332200260262912
13inception_3a/1x1Convolution282819228286411110064ReLU968396812352
15inception_3a/3x3_reduceConvolution282819228289611110096ReLU1452595218528
17inception_3a/3x3Convolution2828962828128331111128ReLU86804480110720
19inception_3a/5x5_reduceConvolution282819228281611110016ReLU24209923088
21inception_3a/5x5Convolution28281628283255112232ReLU1006028812832
23inception_3a/poolPooling28281922828192331111260262912
26inception_3a/outputConcat282828284166412832192
28inception_3b/1x1Convolution28284162828128111100128ReLU4184678453376
30inception_3b/3x3_reduceConvolution28284162828128111100128ReLU4184678453376
32inception_3b/3x3Convolution28281282828192331111192ReLU173558784221376
34inception_3b/5x5_reduceConvolution282841628283211110032ReLU1046169613344
36inception_3b/5x5Convolution28283228289655112296ReLU6028646476896
38inception_3b/poolPooling282841628284163311111221409280
39inception_3b/pool_projConvolution282841628286411110064ReLU2092339226688
41inception_3b/outputConcat282828284801281929664
42pool3/3x3_s2Pooling28284801414480332200406519680
44inception_4a/1x1Convolution14144801414192111100192ReLU1810099292352
46inception_4a/3x3_reduceConvolution141448014149611110096ReLU905049646176
48inception_4a/3x3Convolution1414961414208331111208ReLU35264320179920
50inception_4a/5x5_reduceConvolution141448014141611110016ReLU15084167696
52inception_4a/5x5Convolution14141614144855112248ReLU377260819248
54inception_4a/poolPooling14144801414480331111406519680
55inception_4a/pool_projConvolution141448014146411110064ReLU603366430784
57inception_4a/outputConcat141414145121922084864
59loss1/ave_poolPooling141451244512553300104865792
67inception_4b/1x1Convolution14145121414160111100160ReLU1608768082080
69inception_4b/3x3_reduceConvolution14145121414112111100112ReLU1126137657456
71inception_4b/3x3Convolution14141121414224331111224ReLU44299136226016
73inception_4b/5x5_reduceConvolution141451214142411110024ReLU241315212312
75inception_4b/5x5Convolution14142414146455112264ReLU753894438464
77inception_4b/poolPooling14145121414512331111462522368
78inception_4b/pool_projConvolution141451214146411110064ReLU643507232832
80inception_4b/outputConcat141414145121602246464
82inception_4c/1x1Convolution1414641414128111100128ReLU16307208320
84inception_4c/3x3_reduceConvolution1414641414128111100128ReLU16307208320
86inception_4c/3x3Convolution14141281414256331111256ReLU57852928295168
88inception_4c/5x5_reduceConvolution14146414142411110024ReLU3057601560
90inception_4c/5x5Convolution14142414146455112264ReLU753894438464
92inception_4c/poolPooling1414641414643311117237888
93inception_4c/pool_projConvolution14146414146411110064ReLU8153604160
95inception_4c/outputConcat141414145121282566464
97inception_4d/1x1Convolution1414641414112111100112ReLU14268807280
99inception_4d/3x3_reduceConvolution1414641414144111100144ReLU18345609360
101inception_4d/3x3Convolution14141441414288331111288ReLU73213056373536
103inception_4d/5x5_reduceConvolution14146414143211110032ReLU4076802080
105inception_4d/5x5Convolution14143214146455112264ReLU1004774451264
107inception_4d/poolPooling1414641414643311117237888
108inception_4d/pool_projConvolution14146414146411110064ReLU8153604160
110inception_4d/outputConcat141414145281122886464
112loss2/ave_poolPooling141452844528553300111522048
120inception_4e/1x1Convolution14145281414256111100256ReLU26543104135424
122inception_4e/3x3_reduceConvolution14145281414160111100160ReLU1658944084640
124inception_4e/3x3Convolution14141601414320331111320ReLU90379520461120
126inception_4e/5x5_reduceConvolution141452814143211110032ReLU331788816928
128inception_4e/5x5Convolution1414321414128551122128ReLU20095488102528
130inception_4e/poolPooling14145281414528331111491878464
131inception_4e/pool_projConvolution14145281414128111100128ReLU1327155267712
133inception_4e/outputConcat14141414832256320128128
134pool4/3x3_s2Pooling141483277832332200305311552
136inception_5a/1x1Convolution7783277256111100256ReLU10449152213248
138inception_5a/3x3_reduceConvolution7783277160111100160ReLU6530720133280
140inception_5a/3x3Convolution7716077320331111320ReLU22594880461120
142inception_5a/5x5_reduceConvolution77832773211110032ReLU130614426656
144inception_5a/5x5Convolution773277128551122128ReLU5023872102528
146inception_5a/poolPooling7783277832331111305311552
147inception_5a/pool_projConvolution7783277128111100128ReLU5224576106624
149inception_5a/outputConcat7777832256320128128
151inception_5b/1x1Convolution7783277384111100384ReLU15673728319872
153inception_5b/3x3_reduceConvolution7783277192111100192ReLU7836864159936
155inception_5b/3x3Convolution7719277384331111384ReLU32532864663936
157inception_5b/5x5_reduceConvolution77832774811110048ReLU195921639984
159inception_5b/5x5Convolution774877128551122128ReLU7532672153728
161inception_5b/poolPooling7783277832331111305311552
162inception_5b/pool_projConvolution7783277128111100128ReLU5224576106624
164inception_5b/outputConcat77771024384384128128
165pool5/7x7_s1Pooling77102411102477110051381248
167loss3/classifierInnerProduct111024111000100010240001024000
168loss3/loss3SoftmaxWithLoss1110001110001000
合计63906698486735888

总共有85个不同类型的节点,注意中间有inception结构,ReLU已经跟卷积层节点合在一起。
每个节点我们统一看成是一个数据处理单元,输入来自于上一节点的输出,每个节点包括一个处理单元,数据缓冲区,如果是卷积节点和全连接节点还包括系数缓冲区。我们用数据缓冲区来协同上下游节点的处理过程。具体过程是,数据缓冲区先允许上游节点写,上游节点写完成后,通知数据缓冲区,数据缓冲区于是禁止上游节点写入,并允许下游节点读,下游节点读完后通知数据缓冲区,数据缓冲区又开放上游节点的写允许,这样就可以形成一个前后协同的流水线结构。这个结构可以让多幅图在流水线中同时进行分类。下面是计算节点的示意图:
在这里插入图片描述

具体配置时,可以根据每个节点生成的数据大小来配置存储器,每个缓冲区用内部的RAM实现,系数缓冲区也如此处理。这样处理对内部存储器的大小要求比较高,很多FPGA都无法满足要求,此时可以用一个cache结构来实现缓冲区,数据统一存放在DDR等外存中。另外,对计算量比较大的节点,比如卷积或者池化节点,还可以分成若干个并发的子节点,每个负责计算其中的一部分。
我们目前的实现方案,采用最简单的方案,每个缓冲区用RAM实现,参数也放在RAM中,每个节点不进行分割,作为一个单一节点处理。

11.2 深度卷积神经网络基本单元

我们来定义每个节点可能的类型,也就是深度卷积神经网络的基本单元。从11.1中的分析可以看出,我们需要如下的基本单元:

单元名称模块名功能
卷积Convolution读入上游的图像数据和卷积系数数据,进行卷积计算和激活函数计算
池化Pooling读入上游的图像数据,完成池化计算
汇合Concat将多个上游输出汇合成一个
局部归一化LRN对输入图像进行局部响应归一化处理
全连接点积InnerProduct对输入数据进行全连接点积计算
归一化Softmax对输入数据进行归一化,输出分类结果概率值
数据缓冲区buffer保存节点生成的数据,供下游节点读,输出读写允许信号,
协同上下游的工作流程数据缓冲区根据读写节点数量不同分别有一读一写,
一读四写,四读一写,四读四写等类型,缓冲区在处理多个读写节点的信号时,
可以考虑用分时复用的方式提供服务,也可以设计一个命令FIFO,放松节点和
缓冲区之间的耦合,提高处理效率
数据源datasource提供输入数据,先可以提供一个简单的固定输入数据,将来可以考虑从目录中读
一系列图片进行分类,也可以直接从摄像头获得数据。
结果输出dataoutput读出分类结果,输出Top 5的类型名称和对应的概率值

事实上,caffe支持的算子类型很多,这里只给出了我们关心的几个,能够支持GoogLeNet的运行。如果运行其他网络,可能需要新的算子支持,需要重新定义和实现新的基本单元。
我们给出基本单元的verilog描述如下,目前基本单元正在用c语言实现,后面先验证c语言实现的版本,然后在逐个改写成verilog语言实现,这样我们就有了一个全部由verilog代码实现的深度卷积神经网络的推理系统了。

/*
* cnncell.v
    202107030835: rxh, initial version
*/
(* 
  HDL4SE="LCOM", 
  CLSID="C72D0D42-2D4D-4DC3-9DDA-4F58CE7569BC", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_coeffbuf
  #(parameter filename="test.coeff")
  (
    input   wClk,
    input   nwReset,    
    input   wCoeffRead,    
    output  wCoeffReadValid,
    input [31:0] bCoeffReadAddr,
    output[31:0] bCoeffReadData
  );
endmodule

(* 
  HDL4SE="LCOM", 
  CLSID="66106096-D61B-4B27-B278-8BED3A27B563", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_buf
  #(parameter wordsize=32, wordcount=1024)
  (
    input   wClk,
    input   nwReset,

    output       wDataReadEnable, 
    input        wDataRead,
    output       wDataReadValid,
    input [31:0] bDataReadAddr,
    output[31:0] bDataReadData,
    input        wDataReadComplete,

    output       wDataWriteEnable, 
    input        wDataWrite,
    input [31:0] bDataWriteAddr,
    input [31:0] bDataWriteData,
    input        wDataWriteComplete
  );
endmodule

(* 
  HDL4SE="LCOM", 
  CLSID="9AA0D743-5FFB-4649-ACEC-4B4675AE2A57", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_buf_r4
  #(parameter wordsize=32, wordcount=1024)
  (
    input   wClk,
    input   nwReset,

    output       wDataReadEnable_0, 
    input        wDataRead_0,
    output       wDataReadValid_0,
    input [31:0] bDataReadAddr_0,
    output [31:0]bDataReadData_0,
    input        wDataReadComplete_0,

    output       wDataReadEnable_1, 
    input        wDataRead_1,
    output       wDataReadValid_1,
    input [31:0] bDataReadAddr_1,
    output [31:0]bDataReadData_1,
    input        wDataReadComplete_1,

    output       wDataReadEnable_2, 
    input        wDataRead_2,
    output       wDataReadValid_2,
    input [31:0] bDataReadAddr_2,
    output [31:0]bDataReadData_2,
    input        wDataReadComplete_2,

    output       wDataReadEnable_3, 
    input        wDataRead_3,
    output       wDataReadValid_3,
    input [31:0] bDataReadAddr_3,
    output [31:0]bDataReadData_3,
    input        wDataReadComplete_3,

    output       wDataWriteEnable, 
    input        wDataWrite,
    input [31:0] bDataWriteAddr,
    input [31:0] bDataWriteData,
    input        wDataWriteComplete
  );
endmodule

`define ACTIVATION_NONE     0
`define ACTIVATION_RELU     1
`define ACTIVATION_TANH     2
`define ACTIVATION_SIGMOID  3
`define ACTIVATION_RELUNEG  4
(* 
  HDL4SE="LCOM", 
  CLSID="FB3D226E-D508-476B-81E1-F3B22CCF8F11", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_convolution
  #(parameter 
    INW=224, INH=224, INC=3, 
    OUTW=112, OUTH=112, OUTC=64, 
    W=7, H=7, 
    SW=2, SH=2, 
    PW=3, PH=3, 
    ACT_FUNC=`ACTIVATION_NONE)
  (
    input wClk,
    input nwReset,
    
    output        wCoeffRead,    /*系数读接口*/
    input         wCoeffReadValid,
    output [31:0] bCoeffReadAddr,
    input [31:0]  bCoeffReadData,
    
    input         wDataReadEnable, /*上游数据读接口*/
    output        wDataRead,
    input         wDataReadValid,
    output [31:0] bDataReadAddr,
    input [31:0]  bDataReadData,
    output        wDataReadComplete,

    input         wDataWriteEnable, /*写下游数据接口*/
    output        wDataWrite,
    output [31:0] bDataWriteAddr,
    output [31:0] bDataWriteData,
    output        wDataWriteComplete
  );
endmodule

(* 
  HDL4SE="LCOM", 
  CLSID="78B9BEC2-3DF7-4421-9274-645BFB60320B", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_innerproduct
  #(parameter 
    INW=1, INH=1, INC=1024,
    OUTW=1, OUTH=1, OUTC=1000
  )
  (
    input wClk,
    input nwReset,
    
    output        wCoeffRead,    /* 系数读接口 */
    input         wCoeffReadValid,
    output [31:0] bCoeffReadAddr,
    input [31:0]  bCoeffReadData,
    
    input         wDataReadEnable, /* 上游数据读接口 */
    output        wDataRead,
    input         wDataReadValid,
    output [31:0] bDataReadAddr,
    input [31:0]  bDataReadData,
    output        wDataReadComplete,

    input         wDataWriteEnable, /* 写下游数据接口 */
    output        wDataWrite,
    output [31:0] bDataWriteAddr,
    output [31:0] bDataWriteData,
    output        wDataWriteComplete
  );
endmodule

(* 
  HDL4SE="LCOM", 
  CLSID="8EC66CDF-C750-46A1-AA1F-C44308E1F001", 
  softmodule="hdl4se" 
*) 
module hdl4se_cnn_concat4
  #(parameter 
  INW0=128, INH0=64, INC0=1, 
  INW1=128, INH1=64, INC1=1, 
  INW2=128, INH2=64, INC2=1, 
  INW3=128, INH3=64, INC3=1, 
  OUTW3=128, OUTH3=64, OUTC3=4 
  )
  (
    input wClk,
    input nwReset,
    
    input         wDataReadEnable_0, /*上游数据读接口 0*/
    output        wDataRead_0,
    input         wDataReadValid_0,
    output [31:0] bDataReadAddr_0,
    input [31:0]  bDataReadData_0,
    output        wDataReadComplete_0,

    input         wDataReadEnable_1, /*上游数据读接口 1*/
    output        wDataRead_1,
    input         wDataReadValid_1,
    output [31:0] bDataReadAddr_1,
    input [31:0]  bDataReadData_1,
    output        wDataReadComplete_1,

    input         wDataReadEnable_2, /*上游数据读接口 2*/
    output        wDataRead_2,
    input         wDataReadValid_2,
    output [31:0] bDataReadAddr_2,
    input [31:0]  bDataReadData_2,
    output        wDataReadComplete_2,

    input         wDataReadEnable_3, /*上游数据读接口 3*/
    output        wDataRead_3,
    input         wDataReadValid_3,
    output [31:0] bDataReadAddr_3,
    input [31:0]  bDataReadData_3,
    output        wDataReadComplete_3,

    input         wDataWriteEnable, /*写下游数据接口*/
    output        wDataWrite,
    output [31:0] bDataWriteAddr,
    output [31:0] bDataWriteData,
    output        wDataWriteComplete
  );

endmodule

`define LRN_NormRegion_ACROSS_CHANNELS  0
`define LRN_NormRegion_WITHIN_CH

以上是关于HDL4SE:软件工程师学习Verilog语言的主要内容,如果未能解决你的问题,请参考以下文章

HDL4SE:软件工程师学习Verilog语言(十四)

HDL4SE:软件工程师学习Verilog语言(十四)

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言