大型Verilog代码快速连线
我们在编写一些比较复杂的Verilog代码时,通常需要进行大量的手动连线工作,这种工作十分容易出错,并且在代码模块的嵌套层级较多时,更改里层的一个代码,可能就需要更改其外部一系列模块的端口信息等,因此,使用emacs工具能快速实现大批量的、复杂的Verilog模块之间的连线操作。
emacs安装
在Ubuntu系统下,直接使用apt
安装即可:
sudo apt install emacs
大小大概有170MB左右,安装完成后不需要进行其他设置操作。
示例工程下载
为了方便演示emacs的Verilog自动连线的强大功能,这里我提供了一个国内的Git仓库,方便直观的进行理解和使用,首先下载该仓库:
git clone https://gitee.com/xlgforever/emacs_verilog.git
该仓库的README.md
文件提供了一个简单的操作介绍。下面将详细演示和解释emacs的连线功能。
该仓库下主要存在以下文件:
(base) xlg@xlg16p:~/wrk/emacs_verilog$ tree -L 2
.
├── Makefile
├── README.md
├── src
│ ├── model1.v
│ ├── top_ori2.v
│ ├── top_ori3.v
│ ├── top_ori.v
│ └── top.v
└── src2
└── model2.v
2 directories, 8 files
model1.v
和model2.v
是需要在顶层模块top.v
中实例化的两个模块,这两个模块位于不同的文件夹中,以模拟多处Verilog源码文件的分布;此外model2
还具有parameter
,这两个模块的内容如下(因为仅用来示范连线功能,因此功能为空):
module model1 (
input clk,
input rst,
input [7:0] in_data1,
output reg [7:0] out_data1,
output reg to_model2
);
endmodule //model1`
`module model2 #(
parameter LEN=8
)(
input clk,
input rst,
input [LEN-1:0] in_data2,
output reg [LEN-1:0] out_data2,
input from_model1
);
endmodule //model2
而我们的需求是,在顶层模块top.v
中实例化这两个模块
情况一:基础用法
直接通过代码讲解,top_ori.v
的代码如下:
module top(
/*autoarg*/
);
parameter LEN = 8;
/*AUTOINPUT*/
/*AUTOOUTPUT*/
/*AUTOWIRE*/
/*model1 AUTO_TEMPLATE(
.to_model2(from_model1_to_model2[]),
);
*/
model1 u_model1( /*autoinst*/
);
/*model2 AUTO_TEMPLATE(
.from_model1(from_model1_to_model2[]),
);
*/
model2 #(/*autoinstparam*/)u_model2( /*autoinst*/
);
endmodule
// Local Variables:
// verilog-auto-inst-param-value:t
// verilog-library-directories:("." "../src2/" )
// End:
其中:
/*autoarg*/
用于自动将子模块之间未使用的信号作为父模块的端口信号,例如如果子模块有个信号,名为unuse_signal1
,并且该信号没有连接到top模块的其他子模块,那么unuse_signal1
就会出现在该指令的下方;/*AUTOINPUT*/
和/*AUTOOUTPUT*/
配合/*autoarg*/
,自动为端口信号指定输入输出方向;-
/*AUTOWIRE*/
用于自动为子模块之间的互联信号生成wire
定义,并且只有子模块端口名称不相同的情况下才会生成;(后续会进行解释) AUTO_TEMPLATE
,为该子模块的自动连线指定模板,如果不指定,则会自动生成与子模块端口名相同的信号以在顶层模块中使用;- 上述代码中,
model1
子模块指定to_model2
所连接到信号名称为from_model1_to_model2
,model2
同理; - • 此外,由于
from_model1_to_model2
这个信号与其两端所连接的端口名不一致,因此在/*AUTOWIRE*/
下会自动生成该信号的wire
定义; []
方括号的意思是,如果该信号的位宽不固定为1,则会自动在wire
定义和端口引出处添加位宽;/*autoinst*/
,如果只是指定了模板,而不使用该指令,也不会自动实例化该子模块;使用该命令来自动按照所设置的TEMPLATE生成端口连接;/*autoinstparam*/
,自动将子模块内部定义为parameter
的变量添加在此处,**注意如果不想让该命令添加的参数,应该设置为localparam
**;endmodule
后面的四行代码用于指定所需要实例化的子模块应该在那些目录中查找其对应的源代码文件;parameter LEN = 8;
等和参数相关的代码最好放在靠前的位置,不然等下自动生成的、使用了该参数的代码将会报错,因为该参数需要事先声明;
在仓库根目录执行make emacs -s
,上述top.v
模块将自动实例化为以下代码:
module top(
/*autoarg*/
// Outputs
out_data2, out_data1,
// Inputs
rst, in_data2, in_data1, clk
);
parameter LEN = 8;
/*AUTOINPUT*/
// Beginning of automatic inputs (from unused autoinst inputs)
input clk; // To u_model1 of model1.v, ...
input [7:0] in_data1; // To u_model1 of model1.v
input [LEN-1:0] in_data2; // To u_model2 of model2.v
input rst; // To u_model1 of model1.v, ...
// End of automatics
/*AUTOOUTPUT*/
// Beginning of automatic outputs (from unused autoinst outputs)
output [7:0] out_data1; // From u_model1 of model1.v
output [LEN-1:0] out_data2; // From u_model2 of model2.v
// End of automatics
/*AUTOWIRE*/
// Beginning of automatic wires (for undeclared instantiated-module outputs)
wire from_model1_to_model2; // From u_model1 of model1.v
// End of automatics
/*model1 AUTO_TEMPLATE(
.to_model2(from_model1_to_model2[]),
);
*/
model1 u_model1( /*autoinst*/
// Outputs
.out_data1 (out_data1[7:0]),
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk),
.rst (rst),
.in_data1 (in_data1[7:0]));
/*model2 AUTO_TEMPLATE(
.from_model1(from_model1_to_model2[]),
);
*/
model2 #(/*autoinstparam*/
// Parameters
.LEN (LEN))u_model2( /*autoinst*/
// Outputs
.out_data2 (out_data2[LEN-1:0]),
// Inputs
.clk (clk),
.rst (rst),
.in_data2 (in_data2[LEN-1:0]),
.from_model1 (from_model1_to_model2)); // Templated
endmodule
// Local Variables:
// verilog-auto-inst-param-value:t
// verilog-library-directories:("." "../src2/" )
// End:
可以发现:
/*autoarg*/
将两个子模块之间没有互联的信号自动添加到了顶层端口的信号;/*AUTOINPUT*/
和/*AUTOOUTPUT*/
配合/*autoarg*/
,进一步为顶层端口的信号指明了输入输出方向,并且包含了信号的宽度信息;/*AUTOWIRE*/
仅自动生成了一个信号from_model1_to_model2
,因为该信号既不与model1
的to_model2
端口名相同,也不与model2
的from_model1
端口名相同;
情况二:匹配模板匹配
打开top_ori2.v
文件,其与top_ori.v
代码的区别主要如下:
/*model1 AUTO_TEMPLATE(
.to_model2(from_model1_to_model2),
.out_\(.*\)1 (in_\12),
.in_\(.*\)1 (out_\12),
);
*/
.out_(.*)1 (in_\12),
的意思是,将model1的以out_
开头、1
结尾的端口,连接到以in_
开头、2
结尾的信号;其效果就是,将model1的out_data1
端口,连接到in_data2
信号,而in_data2
正好是model2的同名端口,且model2的TEMPLATE中没有指定in_data2
的信号连接,即默认连接到与端口同名的信号;\(.*\)
这部分代码表示的是匹配任意长度的任意字符串;\1
表示是,与\(.*\)
成功匹配的字符串;- 在该例子中,
\(.*\)
与data
成功匹配,所以\1
代表的就是data
; .in_(.*)1 (out_\12),
同理;
在仓库根目录执行make emacs2 -s
,上述TEMPLATE生成的实例化代码如下:
model1 u_model1( /*autoinst*/
// Outputs
.out_data1 (in_data2[7:0]), // Templated
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk),
.rst (rst),
.in_data1 (out_data2[7:0])); // Templated
可以发现,model1的以out
或者in
开头信号军按照我们的规则进行了信号连接。
这种一般用于什么场景呢,想象一下有两个AXI接口的模块需要连接信号,Master的信号名可能是M00_AXI_xxxx
,而Slave的信号名可能是S00_AXI_xxxx
,那么我们在连接信号时,只需要在Master的TEMPLATE中使用.M00_AXI_(.*) (S00_AXI_\1),
一行命令,就能让emacs自动连接这两个模块。
情况三:使用TEMPLATE实例化多次不同信号连接的子模块
通常,我们可能需要将一个模块实例化多次,每个实例的端口所连接到信号会有所区别,这样,我们就需要在TEMPLATE中使用正则表达式。
module model1 (
input clk,
input rst,
input [7:0] in_data1,
output reg [7:0] out_data1,
output reg to_model2
);
endmodule //model1
通过在TEMPLATE中使用正则表达式,我们可以实现将每个实例的实例名的一部分作为改实例的端口所连接的信号名的一部分,以区分不同实例的信号;
以top_ori3.v
为例,我们将model1
的TEMPLATE改为:
/*model1 AUTO_TEMPLATE "_\(.*\)" (
.to_model2(from_model1_to_model2),
.out_\(.*\)1 (in_\1_@),
.in_\(.*\)1 (@_out_\1),
);
*/
model1 u_model1_inst1( /*autoinst*/
);
model1 u_model1_inst2( /*autoinst*/
);
此外,上述代码我们实例化了model1两次,每次使用了不同的实例名:
"_\(.*\)"
含义是,匹配实例名中第一个_
字符后的后续所有字符;- 对于实例名
u_model1_inst1
来说,匹配的就是model1_inst1
; .out_(.*)1 (in_\1_@),
中@
的含义是,使用匹配到的实例名的部分替换@
,组成最终的信号名;
执行make emacs2 -s
,最终生成的实例的代码如下:
.in_data1 (model1_inst2_out_data)); // Templated`
`model1 u_model1_inst1( /*autoinst*/
// Outputs
.out_data1 (in_data_model1_inst1), // Templated
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk),
.rst (rst),
.in_data1 (model1_inst1_out_data)); // Templated
model1 u_model1_inst2( /*autoinst*/
// Outputs
.out_data1 (in_data_model1_inst2), // Templated
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk),
.rst (rst),
我们这里举例的正则匹配,使用的都是\(.*\)
中的.*
规则,即万能匹配,可以匹配任意多的任意字符,通过修改该正则匹配的规则,可以实现自己想要的其他效果;
大型Verilog代码快速连线(2)
上面我们熟悉了三种情况下的emacs的连线用法,接下来进行一些进阶连线操作的举例。
正则表达式"\([0-9]+\)"
,即匹配至少一个数字,由于该匹配模式很常见,在使用时,可以直接使用@
符号来表示。
即
.pci_req\([0-9]+\)_1 (pci_req_jtag_[\1])
等效于:
.pci_req@_1 (pci_req_jtag_[\1])
。
使用AUTO_TEMPLATE匹配多个端口名/将输入端口赋值为相应宽度的0
在top_ori4.v
中,我们分别设置model1和model2的TEMPLATE,并执行make emacs4 -s
后,在top.v
中得到如下所示结果:
/*model1 AUTO_TEMPLATE (
.to_model2(from_model1_to_model2),
.out_\(.*\)@ (in_\1_\2),
.in_\(.*\)@ (out_\1_\2),
);
*/
// 执行 make emacs4 -s 之后得到:
model1 u_model1_inst1( /*autoinst*/
// Outputs
.out_data1 (in_data_1), // Templated
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk),
.rst (rst),
.in_data1 (out_data_1)); // Templated
/*model2 AUTO_TEMPLATE(
.from_model1(from_model1_to_model2),
.in_\(.*\) ({@"vl-width"{1'b0}}),
);
*/
model2 #(/*autoinstparam*/
// Parameters
.LEN (LEN)) u_model2( /*autoinst*/
// Outputs
.out_data2 (out_data2[LEN-1:0]),
// Inputs
.clk (clk),
.rst (rst),
.in_data2 ({LEN{1'b0}}), // Templated
.from_model1 (from_model1_to_model2)); // Templated
对于model1:
\(.*\)
匹配的是model1的端口名中的data
,而@
等同于([0-9]+)
,所以,匹配的是1
;- 所以根据该TEMPLATE,
out_data1
连接到了in_data_1
,in_data1
连接到了out_data_1
;
对于model2:
"vl-width"
表示是该端口的位宽;@“vl-width”
则表示获得该端口的位宽;- 所以根据该TEMPLATE,model2的
in_data2
端口被连接到了长度为LEN的0上;
除了
vl-width
,emacs的Verilog模式还有以下的内置变量:
vl-name
:端口的名字部分,例如端口如果是port1[7:0]
,则vl-name
代表的是port1
;vl-bits
:端口的宽度部分,例如端口如果是port1[7:0]
,则vl-bits
代表的是[7:0]
;vl-dir
:端口的方向,input/output/inout;vl-cell-type
:Verilog模块的名称;vl-cell-name
:Verilog模块的实例的名称;这些内置变量的使用方法感兴趣的可以自己研究一下。
如何信号名全小写/如何将实例名包含到信号名中
执行make emacs5 -s
,打开top.v
文件,可以看到模板和生成的实例如下:
/*model1 AUTO_TEMPLATE (
.to_model2(from_model1_to_model2),
.\(.*\) (@"(downcase vl-name)"[]),
);
*/
model1 u_model1_inst1( /*autoinst*/
// Outputs
.out_data1 (out_data1[7:0]), // Templated
.to_model2 (from_model1_to_model2), // Templated
// Inputs
.clk (clk), // Templated
.rst (rst), // Templated
.in_data1 (in_data1[7:0])); // Templated
/*model2 AUTO_TEMPLATE(
.from_model1(from_model1_to_model2),
.in_\(.*\) (@"vl-cell-name"_i),
.out_\(.*\) (@"vl-cell-name"_o),
);
*/
model2 #(/*autoinstparam*/
// Parameters
.LEN (LEN)) u_model2( /*autoinst*/
// Outputs
.out_data2 (u_model2_o), // Templated
// Inputs
.clk (clk),
.rst (rst),
.in_data2 (u_model2_i), // Templated
.from_model1 (from_model1_to_model2)); // Templated
对于model1:
.\(.*\) (@"(downcase vl-name)"[]),
中,首先通过万能匹配\(.*\)
,匹配了全部的端口,然后使用vl-name
内置变量将端口名全小写;- 由于本例中没有含有大写的端口名,所以看不出效果;
对于model2:
.in_\(.*\) (@"vl-cell-name"_i),
中,首先是针对所有以in_
开头的端口名,都连接到"实例名" + "_1"
的信号上(仅举例示范);.out_\(.*\) (@"vl-cell-name"_o),
同理;
其他
除了某些内置变量,还支持普通的算术运算。例如有一个信号in
,我们想要将该信号灯每8bit连接到同一个模块的不同实例的a
端口上,则可以如下:
/* InstModule AUTO TEMPLATE (
.a(@in[@"(+ (* 8 @) 7)":@"(* 8 @)"]),
);*/
InstModule u_a0 (/*AUTOINST*/
.a (in[7:0));// Templated
InstModule u_a1 (/*AUTOINST*/
.a (in[15:8));// Templated
从上述代码可以发现,运算符 操作数1 操作数2
配合@
可以实现很强大的功能:
* 8 @
,运算符为*
,第一个操作数为8,第二个操作数为@
,而@
匹配的是实例名u_a0
中的0
,所以对u_a0来说,@为0
,对u_a1来说,@为1
;+ (* 8 @) 7
,运算符为+
,将(* 8 @)
的结果作为第一个操作数,第二个操作数为7;@"(+ (* 8 @) 7)"
:将+
号产生的结果,通过""
号转换为字符;
详细资料见:https://www.veripool.org/verilog-mode/help
emacs在小型模块仿真中的使用技巧
相信很多人都有这种经历——想对某一个模块进行一些简单的仿真测试,但是这个模块端口很多,写一个tb文件的话工作量很大,这时候就可以使用emacs来快速生成仿真环境。
我们的需求如下:
- 为待测试的模块的所有INPUT生成reg类型的变量;
- 为待测试的模块的所有OUTPUT生成wire类型的变量;
DUT
我们依然使用model1
作为DUT,其端口如下:
module model1 (
input clk,
input rst,
input [7:0] in_data1,
output reg [7:0] out_data1,
output reg to_model2
);
endmodule //model1
新建一个tb.v
文件,代码如下:
module tb ();
/*AUTOREGINPUT*/
/*AUTOWIRE*/
/* model1 AUTO_TEMPLATE (
);
*/
model1 u_model1 (/*autoinst*/
);
endmodule //tb
// Local Variables:
// verilog-auto-inst-param-value:t
// verilog-library-directories:("." "../src2/" )
// End:
/*AUTOREGINPUT*/
为模块的所有INPUT生成reg类型的变量;/*AUTOWIRE*/
,同上一节,为模块的所有OUTPUT生成wire类型的变量;
执行:
emacs --batch ./src/tb.v -f verilog-auto -f save-buffer
生成的tb.v文件如下:
module tb ();
/*AUTOREGINPUT*/
// Beginning of automatic reg inputs (for undeclared instantiated-module inputs)
reg clk; // To u_model1 of model1.v
reg [7:0] in_data1; // To u_model1 of model1.v
reg rst; // To u_model1 of model1.v
// End of automatics
/*AUTOWIRE*/
// Beginning of automatic wires (for undeclared instantiated-module outputs)
wire [7:0] out_data1; // From u_model1 of model1.v
wire to_model2; // From u_model1 of model1.v
// End of automatics
/* model1 AUTO_TEMPLATE (
);
*/
model1 u_model1 (/*autoinst*/
// Outputs
.out_data1 (out_data1[7:0]),
.to_model2 (to_model2),
// Inputs
.clk (clk),
.rst (rst),
.in_data1 (in_data1[7:0]));
endmodule //tb
// Local Variables:
// verilog-auto-inst-param-value:t
// verilog-library-directories:("." "../src2/" )
// End:
可以看到,emac自动为我们生成了待测试模块的输入端口的reg变量和输出端口的wire变量,然后我们只需要操作这些变量即可,无需手动生成这些信号。
作者:SeeLemon
文章来源:江湖一叶漂
推荐阅读
- 一道有意思的设计&验证“麻雀”题,你会吗?
- IC设计:ram的应用-使用bitmap实现数据压缩
- Chisel相较于verilog有哪些优势?有哪些开源chisel项目?
- 标准延迟文件和时序报告中的delay value的一致性研究
- 老李带你看菜谱— stx_cookbook 之 加法器 (二)
更多IC设计干货请关注IC设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。