1.1 模块的基本概念
基于模块的系统设计
模块(Verilog Module)是Verilog HDL语言的基本单元,被用于描述某个设计的功能、结构及其与其他模块通信的外部接口。
基于模块的层次化系统设计思想:将复杂系统按照不同功能定义划分成多个小功能模块,对各模块进行独立设计开发后再组装到一起。系统设计一般按下面步骤进行:
把系统划分成模块(及其子模块);
规划各个模块的接口;
对模块编程并连接各模块,完成系统设计。
模块的基本结构
模块端口定义
端口是模块和外界进行信息交互的接口。有些模块(特别是测试代码)与外界无信息交互,则不需要端口列表。
// 格式 module module_name(port1, port2, port3, ...); // 举例 module add(a, b, q);
输入输出说明
标识端口方向,关键字:
input
(输入),output
(输出),inout
(双向)input port1_name, port2_name, ...; //位宽为1的输入端 output [w-1:0] port_name; //位宽为w的输出端 inout [t:1] port_name; //位宽为t的双向总线端
也可以在端口定义语句中直接声明输入输出:
module module_name(input port1, output port2, inout port3, ...);
参数及信号类型说明
wire
(线网型信号)描述的硬件是连线,实时变化,没有存储记忆能力reg
(寄存器型信号)描述的硬件可能是寄存器,寄存器中的值在两次赋值之间保持不变parameter
(参数)声明一个可变常量,常用于定义延时及宽度变量wire variable1_name, variable2_name; //表示位宽为1的两个线网型信号 reg [w-1:0] variable_name; //表示位宽为w的寄存器型信号 wire [t:1] var1_name, var2_name; //表示位宽为t的两个线网型信号 parameter p1_name = exp1, p2_name = exp2; //表示两个参数常量 // 举例 parameter size = 8; input [size-1] a, b;
逻辑功能说明
assign
(连续赋值),用于定义逻辑功能always
(过程赋值),检测敏感信号并描述逻辑功能assign a = b & c; // 该语句描述了一个二输入与门,a为输出端,b、c为输入端 always @ (posedge clk) // 检测到clk信号上升沿时,执行下面的语句 begin <statements...>; end
模块结尾标识:
endmodule
模块模板
// 定义模块
module <module_name>(<port_list>);
output <output_port_list>; // 输出端口声明
input <input_port_list>; // 输入端口声明
// 定义数据,信号的类型,函数声明
wire <signal_name>;
reg <variable_name>;
//逻辑功能定义
assign <result_signal>=<expression>; // 定义逻辑功能
always @ (<sensitive_signal_expression>) // 块描述逻辑功能
begin
// 过程赋值
// if-else,case 语句
// while,repeat,for 循环语句
// task,function 调用
end
// 调用其他模块
<module_name> <use_name>(<port_list>);
// 门元件例化
<gate_keyword> <use_name>(<port_list>);
endmodule
例:三人表决问题
module vote(a, b, c, f); // 模块名与端口列表 input a,b,c; // 模块的输入端口 output f; // 模块的输出端口 wire a,b,c,f; // 定义信号的数据类型 assign f=(a&b)|(a&c)|(b&c); // 逻辑功能描述 endmodule
例:4位BCD码加法计算
module add4_bcd(cout, sum, ina, inb, cin); input cin; input [3:0] ina, inb; output [3:0] sum; reg [3:0] sum; output cout; reg cout; reg [4:0] temp; always @ (ina, inb, cin) begin temp<=ina+inb+cin; if(temp>9) {cout, sum}<=temp+6; else {cout, sum}<=temp; end endmodule
模块的调用
Verilog HDL 通过“模块调用”(或称为“模块实例化”)实现子模块与高层模块的连接。
位置关联模式:在引用时,各连接端口严格按照模块定义时的端口顺序来依次连接,不用标明原模块定义时规定的端口名。
// 格式 module_name use_name(sig1_name, sig2_name,...); // 如果已经定义模块 module MyDesign(sin, pout); // 调用该模块时链接外部信号SerialIn和ParallelOut MyDesign U1(SerialIn, ParallelOut);
名称关联模式:端口列表内指明与每个连接端口信号相连的模块端口名。
// 格式 module_name use_name(.port1_name(sig1_name),...); // 前述例子可以改写为 MyDesign U1(.sin(SerialIn), .pout(ParallelOut));
调用内置模块:对于普通门,端口列表的顺序是:(output, input1, input2,...)
// 格式 module_name use_name(sig1_name, sig2_name,...) // 调用三输入与门 and U1(out, in1, in2, in3);
测试代码
例:“与-或-非”电路模块 \( f= \overline{a \cdot b + \overline{c \cdot d}}\)
与-或-非电路模块示例
module aoi(a,b,c,d,f); // 模块名为aoi,端口列表a,b,c,d,f input a,b,c,d; // 模块的输入端口为 a b c d output f; // 模块的输出端口为 f wire a,b,c,d,f; //定义信号的数据类型 assign f=~((a&b)|(~(c&d))); //逻辑功能描述 endmodule
模块结构要点:
每个模块内容都嵌在
module
和endmodule
之间每个模块由两部分组成:
描述接口:模块首先要进行端口定义,说明输入和输出(
input
,output
,inout
)描述逻辑功能
除了
endmodule
等少数语句外,每个语句的最后必须有英文分号标记其结束一行可写多个语句,一个语句也可写成多行
行注释
//
,块注释/*......*/
测试代码
测试代码(testbench 或 testfixture):通过测试代码调用需要验证的模块,用一段程序描述信号的变化,产生输入信号波形(激励信号)输入被测试电路的 HDL 模型,记录模型输出,实现仿真分析。
例:针对上述"与-或-非"电路模块的测试代码:
`timescale 1ns/1ps module aoi_tb; // 定义测试模块 reg a,b,c,d; // 变量 a,b,c,d 用于产生变化输入 wire f; // 变量 f 用于记录输出 initial // Verilog关键字用于初始化 begin a=0; b=0; c=0; d=0; // 仿真 0 时刻输入信号的值 #100; a=1; b=0; c=0; d=1; // 仿真 100ns 时输入信号值 #100; a=1; b=1; c=1; d=0; // 仿真 200ns 时输入信号值 #100; a=0; b=0; c=1; d=1; // 仿真 300ns 时输入信号值 #100; a=0; b=1; c=1; d=1; // 仿真 400ns 时输入信号值 #100; a=1; b=1; c=1; d=1; // 仿真 500ns 时输入信号值 end aoi u1(.a(a),.b(b),.c(c),.d(d),.f(f)); // 调用被仿真电路的模型 endmodule
测试代码特点:
测试代码也使用
module
定义,但没有输入输出;测试代码带有时间控制功能(
#100
);测试代码调用被测试模块。