写在前面
在自己准备写一些简单的verilog教程之前,参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好,奈何没有中文,在下只好斗胆翻译过来(加了自己的理解)分享给大家。
这是网站原文:http://asic-world.com/verilog/veritut.html
这是系列导航:Verilog教程系列文章导航
模块(Modules)
- 模块是verilog设计的基本组成形式
- 你可以在模块中调用别的模块来实现层次化设计
在下面的图片中可以看到:
- 顶层模块分别由左、右上、右下三个子模块构成
- 左边的模块由4个子模块组成;而右边的两个模块又分别由两个更小子模块组成
端口(Ports)
- 端口是不同模块间实现通讯的渠道
- 除了顶层模块外的所有层级的模块都有端口(其实最顶层的模块也有端口,不过由于其一般连接到FPGA的管脚,所以一般叫Pins)
- 端口间的例化有两种方式:顺序端口例化、命名端口例化
像下面示例这种一行只声明一个端口的方式是个不错的编码习惯。
input clk ; // 时钟输入
input [15:0] data_in ; // 16位数据输入总线
output [7:0] count ; // 8位计数器输出
inout data_bi ; // 双向数据总线
下面请看一个全加器的示例代码:
//这是一个加法器示例
module addbit (
a , // 输入a
b , // 输入b
ci , // 进位输入
sum , // 加法结果输出
co // 进位输出
);
//输入声明
input a;
input b;
input ci;
//输出声明
output sum;
output co;
//端口的数据类型
wire a;
wire b;
wire ci;
wire sum;
wire co;
//主代码部分
assign {co,sum} = a + b + ci;
endmodule // End of Module addbit
端口例化方法
前面说了模块的例化有两种方法:顺序端口例化与命名端口例化。接下来分别看下这两种方法是如何使用的:
(1)顺序端口例化
这种方法将需要例化的模块端口按照模块声明时端口的顺序与外部信号进行匹配连接,位置要严格保持一致。这种方法在增加或减少端口时会特别容易导致BUG,所以我个人不建议使用该方法。
这是对上面提到的全加器模块的例化:
module adder_implicit (
result , // Output of the adder
carry , // Carry output of adder
r1 , // first input
r2 , // second input
ci // carry input
);
// 输入端口声明
input [3:0] r1 ;
input [3:0] r2 ;
input ci ;
// 输出端口声明
output [3:0] result ;
output carry ;
// Wires端口
wire [3:0] r1 ;
wire [3:0] r2 ;
wire ci ;
wire [3:0] result ;
wire carry ;
// 内部变量
wire c1 ;
wire c2 ;
wire c3 ;
// 多次例化,按顺序
addbit u0 (
r1[0] ,
r2[0] ,
ci ,
result[0] ,
c1
);
//按顺序例化
addbit u1 (
r1[1] ,
r2[1] ,
c1 ,
result[1] ,
c2
);
//按顺序例化
addbit u2 (
r1[2] ,
r2[2] ,
c2 ,
result[2] ,
c3
);
//按顺序例化
addbit u3 (
r1[3] ,
r2[3] ,
c3 ,
result[3] ,
carry
);
endmodule // End Of Module adder
(2)命名端口例化
这种方法将需要例化的模块端口与外部信号按照其名字进行连接,端口顺序随意,可以与引用 module 的声明端口顺序不一致,只要保证端口名字与外部信号匹配即可。这种方法非常灵活,我个人强烈建议使用该方法。
这是对上面提到的全加器模块的例化:
module adder_explicit (
result , // Output of the adder
carry , // Carry output of adder
r1 , // first input
r2 , // second input
ci // carry input
);
// 输入端口声明
input [3:0] r1 ;
input [3:0] r2 ;
input ci ;
// 输出端口声明
output [3:0] result ;
output carry ;
// Wires端口
wire [3:0] r1 ;
wire [3:0] r2 ;
wire ci ;
wire [3:0] result ;
wire carry ;
// 内部变量
wire c1 ;
wire c2 ;
wire c3 ;
// 多次例化,按对应名称
addbit u0 (
.a (r1[0]) ,
.b (r2[0]) ,
.ci (ci) ,
.sum (result[0]) ,
.co (c1)
);
addbit u1 (
.a (r1[1]) ,
.b (r2[1]) ,
.ci (c1) ,
.sum (result[1]) ,
.co (c2)
);
addbit u2 (
.a (r1[2]) ,
.b (r2[2]) ,
.ci (c2) ,
.sum (result[2]) ,
.co (c3)
);
addbit u3 (
.a (r1[3]) ,
.b (r2[3]) ,
.ci (c3) ,
.sum (result[3]) ,
.co (carry)
);
endmodule // End Of Module adder
端口连接规则
Verilog中的端口连接规则是这样的:
- 输入端口:内部必须是net,外部可以是net也可以是reg
- 输出端口:内部可以是net也可以是reg,外部必须是net
- 双向端口:内部和外部都只能是net
-
位宽匹配:将位宽不同的内部和外部信号连接到一起是合法的。但是要注意,综合工具可能会报告警告或错误
-
悬空端口:悬空端口可以使用","(顺序端口例化); 悬空端口也可以直接空着(命名端口例化)
下面的例子展示了两种例化方法是如何处理悬空端口的:
module exam();
reg clk,d1,d2,rst,pre1,pre2;
wire q1,q2;
//顺序端口例化时,悬空的信号用","
dff u1 ( q1,,clk,d1,rst,pre1);
//命名端口例化时,悬空的信号直接控制
dff u2 (
.q (q2),
.d (d2),
.clk (clk),
.q_bar (),
.rst (rst),
.pre (pre2)
);
endmodule
// D fli-flop
module dff (q, q_bar, clk, d, rst, pre);
input clk, d, rst, pre;
output q, q_bar;
reg q;
assign q_bar = ~q;
always @ (posedge clk)
if (rst == 1'b1) begin
q <= 0;
end else if (pre == 1'b1) begin
q <= 1;
end else begin
q <= d;
end
endmodule
模块的层次结构
由于模块之间具备的层次结构,所以可以使用名称索引的方式来找到不同模块的信号。
示例:下面的adder_hier模块内4次调用了子模块addbit,将其分别例化为u0、u1、u2和u3。
module adder_hier (
result , // Output of the adder
carry , // Carry output of adder
r1 , // first input
r2 , // second input
ci // carry input
);
// Input Port Declarations
input [3:0] r1 ;
input [3:0] r2 ;
input ci ;
// Output Port Declarations
output [3:0] result ;
output carry ;
// Port Wires
wire [3:0] r1 ;
wire [3:0] r2 ;
wire ci ;
wire [3:0] result ;
wire carry ;
// Internal variables
wire c1 ;
wire c2 ;
wire c3 ;
// 调用addbit子模块
addbit u0 (r1[0],r2[0],ci,result[0],c1);
addbit u1 (r1[1],r2[1],c1,result[1],c2);
addbit u2 (r1[2],r2[2],c2,result[2],c3);
addbit u3 (r1[3],r2[3],c3,result[3],carry);
endmodule // End Of Module adder
下面的代码展示了如何使用子模块内的信号:
module tb();
reg [3:0] r1,r2;
reg ci;
wire [3:0] result;
wire carry;
// Drive the inputs
initial begin
r1 = 0;
r2 = 0;
ci = 0;
#10 r1 = 10;
#10 r2 = 2;
#10 ci = 1;
#10 $display("+--------------------------------------------------------+");
$finish;
end
// Connect the lower module
adder_hier U (result,carry,r1,r2,ci);
// Hier demo here
initial begin
$display("+--------------------------------------------------------+");
$display("| r1 | r2 | ci | u0.sum | u1.sum | u2.sum | u3.sum |");
$display("+--------------------------------------------------------+");
$monitor("| %h | %h | %h | %h | %h | %h | %h |",
r1,r2,ci, tb.U.u0.sum, tb.U.u1.sum, tb.U.u2.sum, tb.U.u3.sum); //这里分别展示了4个子模块的sum信号
end
endmodule
代码中的 tb.U.u0.sum, tb.U.u1.sum, tb.U.u2.sum, tb.U.u3.sum 分别是4个子模块的内部信号sum,它们是不同的信号,只是名称相同。这种方法在调试时很有用。
- 📣您有任何问题,都可以在评论区和我交流📃!
- 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵,博客主页:wuzhikai.blog.csdn.net
- 📣您的支持是我持续创作的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!