Verilog 学习笔记(4)——Verilog 语言基础(2)
Contents
结构化描述
前文提到 Verilog 描述硬件的三种方法,本章介绍其中的结构化描述。对于复杂的电路,有必要采用模块的形式进行设计。对在设计中重复出现的功能块采用结构化描述可以提高效率。结构化描述就是说在设计中实例化已有的功能模块。前面我们已经对 module 有了一定的认识,一个 module 实际上描述的是输出与输入信号之间的关系,内部的结构在上层级并不重要。而复杂的电路模块是由一个个相对简单的模块相互连接而成的。在一个模块中我们可以调用以建成的下层级模块,将其建为实际模块(实例化),从而简化设计。
模块实例化
一般的,我们可以先搭建需要用到的简单模块(或者已经提供),然后在顶层模块例化底层的模块,达到简化设计的效果。而在顶层设计中,底层模块实例就相当于黑盒,盒内的内容并不重要,只需要关注盒子的输入输出端口。
举个例子:
如图所示模块,有一个顶层模块 top_module 和一个底层模块 mod_a,而我们并不关心底层模块 mod_a 的内部连接方式,只关注它的端口。其有两个输入端口 in1,in2 以及一个输出端口 out。这里给出这个底层模块的 Verilog 代码(不关注内容):
module mod_a ( input in1, input in2, output out );
// Module body
endmodule
实例化模块的基本格式为:模块名 实例名 (连接端口的信号);
比如该底层模块的实例化就可以写为:
mod_a mod_a1 (a,b,out);
那么我们就可以根据该底层模块的端口以及顶层模块的设计编写出相应的 top_module 的 Verilog 代码:
module top_module(
input a,b,
output out );
mod_a mod_a1 (a,b,out);
endmodule
前文提到过建议一个.v 文件里只放一个 module 定义,因此最好不要在顶层模块里定义底层模块。但是要把底层模块与顶层模块放在同一个工程里以调用。
实例化模块的连接方式
按端口顺序连接
在底层端口进行声明的时候,声明端口是按一定顺序的,如上面的 mod_a,就是以 in1,in2,out 的顺序进行的声明。那么在实例化的时候,可以根据端口声明的信号顺序进行连接。这种连接方式的实例化语句为 模块名 实例名 (外信号名,...);
但是外信号名一定要按顺序,如上面的例子,声明时的连接信号写为(a,b,out)
,即 a 与 in1 连接,b 与 in2 连接,out 与 out 连接。但是这种命名方式存在弊端,一旦端口列表发生改变,所有模块实例化中的端口连接都需要改变,所以一般不推荐这种模块实例化方式。
按端口名称连接
还可以根据端口名称将实例化的模块与外部信号相连接。这种连接方式与端口声明的顺序没有关系,即使底层模块出现改动,也只需要更改相应的模块。一般使用这种方式进行模块实例化。
这种连接方式的实例化语句为 模块名 实例名 (.端口名(外信号名),...);
以 mod_a为例:
mod_a mod_a1 (.in1(a),.in2(b),.out(out)); //这种命名方式括号里的信号顺序可调换
那么 top_module 的代码就可写为:
module top_module(
input a,b,
output out );
mod_a mod_a1 (
.in1(a),
.in2(b),
.out(out));
endmodule
实例化模块时,考虑到可读性,一般一个端口一行。
另外,端口和外信号也可以是 vector 形式,并且不需要位宽相同,但是会遵循多截少补的原则。
牛刀小试
下面来实践一下。
先搭建底层模块两位全加器,再利用四个两位全加器实例化搭建出八位行波进位加法器,八位加法器的端口声明为:
module adder_8(input [7:0] a,b, output [8:0] sum);
你可以使用我提供的 testbench 来进行功能验证,也可以自己仿照以前的例子进行编写。
Testbench(前提要求八位加法器的端口声明与我给出的一致):
`timescale 1ns/1ns
module adder_8_tb ();
reg [7:0] A;
reg [7:0] B;
wire [8:0] sum;
initial begin
A=0;
B=0;
#10;
A=8'b11000011;
B=8'b11110000;
#10;
A=200;
B=102;
#10;
A=198;
B=0;
#10;
A=0;
B=39;
#10
A=171;
B=249;
end
adder_8 adder0(
.a(A),
.b(B),
.sum(sum)
);
endmodule
理想的仿真结果:
参考解答:
二位全加器:
module adder_2(
input [1:0] a,
input [1:0] b,
input cin,
output [1:0] sum,
output co );
assign {co,sum}=a+b+cin;
endmodule
八位全加器:
module adder_8(
input [7:0] a,
input [7:0] b,
output [8:0] sum );
wire co1,co2,co3;
adder_2 adder1 (.a(a[1:0]),.b(b[1:0]),.cin(0),.sum(sum[1:0]),.co(co1));
adder_2 adder2 (.a(a[3:2]),.b(b[3:2]),.cin(co1),.sum(sum[3:2]),.co(co2));
adder_2 adder3 (.a(a[5:4]),.b(b[5:4]),.cin(co2),.sum(sum[5:4]),.co(co3));
adder_2 adder4 (.a(a[7:6]),.b(b[7:6]),.cin(co3),.sum(sum[7:6]),.co(sum[8]));
endmodule
这里我图方便实例化的时候把端口并到了一行,实际写的时候推荐一个端口一行。
思考题:
行波进位加法器由于进位是串行的,在进位运算时一级一级传递会造成较大延迟,并随着计算位数的增加而增加。为了改进,考虑设计一个进位并行计算的超前进位加法器(需要增加一个超前进位模块)。
提示:
第$i$级的进位$C_i$有以下表示方式
$$
C_i=A_i[1]\cdot B_i[1]+(A_i[1]+B_i[1])(A_i[0]\cdot B_i[0]+(A_i[0]+B_i[0])C_{i-1})
$$
(以上为逻辑运算而非数学运算)
No Comments