verilog中的module

文章目录

  • 前言
  • 一、module的结构
    • 1.1 module的申明
    • 1.2 module的调用
  • 二、参数传递方式
    • 2.1 位置传参
    • 2.2 参数名传参
  • 三、嵌套多个模块
    • 3.1 简单模块嵌套
    • 3.2 复杂模块嵌套
  • 四、加法器
    • 4.1 Adder(1)
    • 4.2 Adder(2)
    • 4.3 Adder(3)
    • 4.4 Adder(4)
  • 总结


前言

  前面课程我们已经熟悉了模块,在模块中,我们实现了各种逻辑功能。本文我们将深入讨论和学习模块。


一、module的结构

  模块是一个电路,通过输入和输出端口与其外部交互。更大、更复杂的电路是通过将模块嵌套其他子模块,而子模块又是assign语句块和always语句块组成的。这就形成了一个层次结构,因为模块可以包含其他模块的实例。

verilog中的module_第1张图片

图1. 两输入一输出嵌套模块

1.1 module的申明

  模块申明格式:

  module 模块名(input 输入端口名,output 输出端口名);
  模块体
  endmodule

  如以下代码中mod_a是图1中的子模块,有三个端口,两个输入端口in1、in2,一个输出端口out。

module mod_a ( input in1, input in2, output out );
    // Module body
endmodule

1.2 module的调用

  要实现图1中顶层模块top_module调用实例化的子模块mod_a,需要编写以下代码:

module top_module ( input a, input b, output out );
mod_a instance1 (a, b, out);
endmodule

  工程师经验:

  1. 模块名称符合命名规则,在verilog语法基础中有讲解,特别注意命名要有意义,要见名知意,通过下划线连接;
  2. 实际的模块定义中,都包括输入输出,没有输入输出,就没有办法与其他模块交互;
  3. 模块定义时的端口列表中的端口,可以看做c语言函数定义中的形式参数,如mod_a中的in1、in2、out,以及top_module定义中的a、b、out。实例化模块的端口列表中的端口,可以看做c语言函数调用中的实际参数,如实例化mod_a中传入的a、b、out;
  4. 模块可以嵌套调用,也就是模块中嵌套的是实例化的子模块。不能嵌套定义,比如模块中定义模块;
  5. 模块实例化时,要另取一个名字,比如module的调用中,mod_a另取名字instance1。在后面的小节中将使用同一个模块,实例化多个模块,这些模块通过名字区分;
  6. 一般项目中,使用一个顶层模块调用其他多个子模块;

二、参数传递方式

2.1 位置传参

verilog中的module_第2张图片

图2. 四输入两输出嵌套模块

  模块mod_a的定义如下:

module mod_a ( output out1, output out2, input in1, input in2, input in3, input in4);
// Module body
endmodule

  顶层模块调用如下:

module top_module ( 
    input  a   , 
    input  b   , 
    input  c   ,
    input  d   ,
    output out1,
    output out2
);
mod_a instance1(out1, out2, a, b, c, d);
endmodule

  在参数传递过程中,参数的位置一定要匹对,否则会出现意想不到的错误。工程中,一般不建议使用位置传递参数的方式,进行值传递。

2.2 参数名传参

  顶层模块top_module调用的mod_a和2.1中的mod_a一致,但是通过参数名传递。
  注意:参数名前有点(.)号,参数名紧跟的是(实际参数)。

module top_module ( 
    input  a   , 
    input  b   , 
    input  c   ,
    input  d   ,
    output out1,
    output out2
);
mod_a instance1(.out1  (out1), 
                .out2  (out2), 
                .in1   (a)   , 
                .in2   (b)   , 
                .in3   (c)   , 
                .in4   (d));
endmodule

  实际工程中推荐使用的参数传递方式。


三、嵌套多个模块

3.1 简单模块嵌套

verilog中的module_第3张图片

图3. 嵌套多个模块

  模块my_dff是一个D触发器(D flip-flop),定义如下:

module my_dff(input clk, input d, output q);
//Module body
endmodule

  通过顶层模块top_module调用三个my_dff模块,代码如下:

module top_module ( input clk, input d, output q );
wire a;
wire b;
my_dff instance1(.clk  (clk),
                 .d    (d)  , 
                 .q    (a));//触发器instance1
my_dff instance2(.clk  (clk), 
                 .d    (a)  , 
                 .q    (b));//触发器instance2
my_dff instance3(.clk  (clk), 
                 .d    (b)  , 
                 .q    (q));//触发器instance3
endmodule

  三个触发器模块连接起来,需要两个wire,所以定义两个中间wire a和b,触发器将会在后期作品中讲解。

3.2 复杂模块嵌套

  在复杂的嵌套模块中,输入输出的情况不再是单个比特,同样是实例化三个模块my_dff8,中间wire a、b、c以及输入d的值都为8位。sel信号驱动4-1选择器,选择性将a、b、c、d赋值给输出q。原理图如下:
verilog中的module_第4张图片

图4. 复杂嵌套模块
  my_dff8 D触发器模块定义如下:
module my_dff8(input         clk, 
               input  [7:0]  d  ,
               output [7:0]  q );
//Module body
endmodule

  根据原理图编写的代码如下:

module top_module ( 
    input 		  clk, 
    input  [7:0]  d  , 
    input  [1:0]  sel, 
    output [7:0]  q 
);
wire [7:0] a;
wire [7:0] b;
wire [7:0] c;
my_dff8 instance1 (.clk	(clk),
                   .d   (d)  ,
                   .q   (a));
my_dff8 instance2 (.clk	(clk),
                   .d	(a)	 ,
                   .q	(b));
my_dff8 instance3 (.clk	(clk),
                   .d	(b)  ,
                   .q	(c));
always@(*)begin
    case(sel)
        2'd0:	q = d;
        2'd1:	q = a;
        2'd2:	q = b;
        2'd3:	q = c;
    default:;
    endcase
end 
endmodule

verilog中的module_第5张图片

图5. 复杂嵌套模块仿真

  因为是4-1选择器,有4种情况,所以sel位宽为4。sel从0到3改变,但是q并没有根据d马上改变,是因为my_dff8是D触发器,是在时钟上升沿时候触发,所以延迟了一个时钟周期。


四、加法器

4.1 Adder(1)

  在模块top_module中实例化两个add16,创建一个32位加法器。一个add16模块计算加法结果的低16位,第二个add16模块在收到第一个加法器的结果后,计算结果的高16位。cin默认传入0。
verilog中的module_第6张图片

图6. Adder (1)
module add16(input	[15:0]  a  , 
             input	[15:0]  b  , 
             input		    cin, 
             output	[15:0]  sum, 
             output         cout);
//Module body
endmodule
module top_module(
    input  [31:0] a,
    input  [31:0] b,
    output [31:0] sum
);
wire [15:0] sum_low;
wire [15:0] sum_up ;
wire 		cout1  ;
wire 		cout2  ;
add16 instance1(.a		(a[15:0]), 
                .b		(b[15:0]), 
                .cin	(1'b0)	 , 
                .sum	(sum_low), 
                .cout	(cout1));
add16 instance2(.a		(a[31:16]),
                .b		(b[31:16]), 
                .cin	(cout1)   , 
                .sum	(sum_up)  ,
                .cout	(cout2));
assign sum = {sum_up, sum_low};
endmodule

verilog中的module_第7张图片

图7. Adder (1)仿真

4.2 Adder(2)

  创建一个具有两个模块的电路。top_module实例化两个add16,每个将实例化addr16包括16个实例化的add1。一个add16模块计算加法结果的低16位,第二个add16模块在收到第一个加法器的结果后,计算结果的高16位。cin默认传入0。
verilog中的module_第8张图片

图8. Adder(2)
module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);

wire [15:0] sum_low;
wire [15:0] sum_up ;
wire 		cout1  ;
wire 		cout2  ;

add16 instance1(.a		(a[15:0]), 
                .b		(b[15:0]), 
                .cin	(1'b0)	 , 
                .sum	(sum_low), 
                .cout	(cout1));
add16 instance2(.a		(a[31:16]),
                .b		(b[31:16]), 
                .cin	(cout1)   , 
                .sum	(sum_up)  ,
                .cout	(cout2));
assign sum = {sum_up, sum_low};
endmodule
module add1 ( input a, input b, input cin,   output sum, output cout );

// Full adder module here
assign sum = a ^ b ^ cin;
assign cout = a&b | a&cin | b&cin;
endmodule

verilog中的module_第9张图片

图9. Adder(2)仿真

4.3 Adder(3)

  Adder(2)中的加法器计算进位的延迟相当慢,而且在第一级加法器完成运算之前,第二级加法器不能开始计算它的进位。这使得加法器变慢。一个改进是进位选择加法器,如下所示。第一级加法器与之前相同,只是复制了第二级加法器,一个假设carry-in为0,另一个假设carry-in为1,然后使用一个快速的2对1多路复用器来选择哪个结果恰好是正确的。
verilog中的module_第10张图片

图10. Adder (3)
module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);

wire cout1;
wire cout2;
wire cout3;
wire [15:0] sum_low  ;
wire [15:0] sum_up1  ;
wire [15:0] sum_up2  ;
wire [15:0] sum_final;

add16 instance1(.a		(a[15:0]),
                .b		(b[15:0]), 
                .cin	(1'b0)   , 
                .sum	(sum_low),
                .cout	(cout1));
add16 instance2(.a		(a[31:16]),
                .b		(b[31:16]), 
                .cin	(1'b0)    , 
                .sum	(sum_up1) ,
                .cout	(cout2));
add16 instance3(.a		(a[31:16]),
                .b		(b[31:16]), 
                .cin	(1'b1)    , 
                .sum	(sum_up2) ,
                .cout	(cout3));
always@(*)begin
    case(cout1)
        1'd0:	sum_final = sum_up1;
        1'd1:	sum_final = sum_up2;
        default:;
    endcase
end
assign sum = {sum_final, sum_low};
endmodule

verilog中的module_第11张图片

图11. Adder (3)仿真

4.4 Adder(4)

  一个加法器是可以处理正负数的,对负数进行操作,可以看成将输入求反,然后加1。最终的结果是一个电路可以做两种操作:(a + b + 0)和(a + ~b + 1)。如图12中,sub为1的时候表示b输入的是负数,执行的操作是(a + ~b + 1),sub为0的时候表示b输入的是负数,执行的操作是(a + b + 0)。因为b是32位,所以sub会进行32次位复制。

  1. 当sub为1的时候,{32{sub}}和b进行异或,相当于对b求反;
  2. 当sub为0的时候,{32{sub}}和b进行异或,相当于b不变;

verilog中的module_第12张图片

图12. Adder (4)
module top_module(
    input  [31:0] a  ,
    input  [31:0] b  ,
    input 		  sub,
    output [31:0] sum
);
wire [15:0] sum_low;
wire [15:0] sum_up ;
wire [31:0]	b_sub  ;
wire 		cout1  ;
wire 		cout2  ;

add16 instance1(.a		(a[15:0])    , 
                .b		(b_sub[15:0]), 
                .cin	(sub)	     , 
                .sum	(sum_low)    , 
                .cout	(cout1));
                
add16 instance2(.a		(a[31:16])    ,
                .b		(b_sub[31:16]), 
                .cin	(cout1)       , 
                .sum	(sum_up)      ,
                .cout	(cout2));
                
assign b_sub = b ^ {32{sub}};
assign sum = {sum_up, sum_low};
endmodule

verilog中的module_第13张图片

图13. Adder (4)仿真

总结

  我们从模块的结构出发,讲解了模块的实例化(调用),模块的值传递,到后来,我们从实例化一个简单的模块,到实例化一个复杂的模块,最后我们实现了加法器。加法器是中央处理器(Central Processing Unit,CPU)中的算术逻辑单元(Arithmetic logical Unit,ALU)的核心部分,我们也通过模块实现了。感谢你的观看!

你可能感兴趣的:(笔记,fpga开发,verilog,硬件工程,fpga,嵌入式硬件)