1.1 模块的基本概念

基于模块的系统设计

模块(Verilog Module)是Verilog HDL语言的基本单元,被用于描述某个设计的功能、结构及其与其他模块通信的外部接口

image1

基于模块的层次化系统设计思想:将复杂系统按照不同功能定义划分成多个小功能模块,对各模块进行独立设计开发后再组装到一起。系统设计一般按下面步骤进行:

  1. 把系统划分成模块(及其子模块);

  2. 规划各个模块的接口;

  3. 对模块编程并连接各模块,完成系统设计。

模块的基本结构

  1. 模块端口定义

    端口是模块和外界进行信息交互的接口。有些模块(特别是测试代码)与外界无信息交互,则不需要端口列表。

    // 格式
    module module_name(port1, port2, port3, ...);
    // 举例
    module add(a, b, q);
    
  2. 输入输出说明

    标识端口方向,关键字: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, ...);
    
  3. 参数及信号类型说明

    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;
    
  4. 逻辑功能说明

    assign(连续赋值),用于定义逻辑功能

    always(过程赋值),检测敏感信号并描述逻辑功能

    assign a = b & c;   // 该语句描述了一个二输入与门,a为输出端,b、c为输入端
    always @ (posedge clk)  // 检测到clk信号上升沿时,执行下面的语句
    begin
        <statements...>;
    end
    
  5. 模块结尾标识: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
  1. 例:三人表决问题

    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
    
  2. 例: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 通过“模块调用”(或称为“模块实例化”)实现子模块与高层模块的连接。

  1. 位置关联模式:在引用时,各连接端口严格按照模块定义时的端口顺序来依次连接,不用标明原模块定义时规定的端口名。

    // 格式
    module_name use_name(sig1_name, sig2_name,...);
    // 如果已经定义模块
    module MyDesign(sin, pout);
    // 调用该模块时链接外部信号SerialIn和ParallelOut
    MyDesign U1(SerialIn, ParallelOut);
    
  2. 名称关联模式:端口列表内指明与每个连接端口信号相连的模块端口名。

    // 格式
    module_name use_name(.port1_name(sig1_name),...);
    // 前述例子可以改写为
    MyDesign U1(.sin(SerialIn), .pout(ParallelOut));
    
  3. 调用内置模块:对于普通门,端口列表的顺序是:(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}}\)

  1. 与-或-非电路模块示例

    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
    

    模块结构要点:

    • 每个模块内容都嵌在 moduleendmodule 之间

    • 每个模块由两部分组成:

    • 描述接口:模块首先要进行端口定义,说明输入和输出(inputoutputinout

    • 描述逻辑功能

    • 除了 endmodule 等少数语句外,每个语句的最后必须有英文分号标记其结束

    • 一行可写多个语句,一个语句也可写成多行

    • 行注释 //,块注释 /*......*/

  2. 测试代码

    测试代码(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);

    • 测试代码调用被测试模块。