Verilog HDLBits 第四期:2.3Modules:Hierarchy

目录

前言

2.3.1Modules(Module)

Connecting Signals to Module Ports

By position

By name

Solution:

2.3.2Connecting ports by position(Module pos)

Solution:

 2.3.3Connecting ports by name(Module name)

Solution:

 2.3.4Three modules(Module shift)

Solution:

2.3.5Modules and vectors(Module shift8)

Solution:

2.3.6Adder 1(Module add)

Solution:

 2.3.7Adder 2(Module fadd)

Solution:

2.3.8Carry-select adder(Module cseladd)

Solution:

2.3.9Adder-subtractor(Module addsub)

Solution:


前言

HDLbits网站如下

Problem sets - HDLBits (01xz.net)

从本期开始我们继续HDLbits第二章Verilog Language的学习,本期的内容是2.3Modules结构


2.3.1Modules(Module)

迄今为止,你已经熟悉了一个模块,它是一个通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是通过由小模块和连接在一起的其他部分(比如assign语句和always块)组成更大的模块来构建的。这形成了一种层次结构,即模块可以包含其他模块的实例。

下图展示了一个非常简单的带有子模块的电路。在这个练习中,构建模块mod_a的一个实例,并将底层模块的3个引脚(in1、in2、out)与你的顶层模块的三个端口(wire信号a、b、out)相连接。模块mod_a是为你提供的——你必须实例化它。

当进行模块连接时,只有模块的端口很重要。你不需要知道模块中的代码。模块mod_a的代码长这样:

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

模块的层次结构是通过在另一个模块中实例化一个模块来创建的,只要使用的所有模块都属于同一个项目(因此编译器知道在哪里可以找到该模块)。 一个模块的代码没有写在另一个模块的主体中​​(不同模块的代码没有嵌套)。

你可以通过端口名或者端口位置来连接模块的信号。为了更好的练习,请尝试两种方法。


Connecting Signals to Module Ports

以下两种常用的方法来将wire连接到端口:按位置或者按端口名

By position

你应该对按位置将wire信号连接到端口的语法应该很熟悉,因为它使用类似C语言的语法。当实例化一个模块时,端口通过模块声明从左往右进行连接。举个例子:

mod_a instance1 ( wa, wb, wc );

这将实例化一个mod_a类型的模块,并为其提供一个实例名称"instance1",并分别将信号wa(在新模块之外)与新模块的第一个端口(in1)、wb与in2、wc与out相连接。这种语法的一个缺点是:如果模块的端口列表发生更改,则还需要找到并且更改模块的所有实例以匹配新模块。

By name

按名称将信号连接到模块的端口,即使端口列表发生变化,wire也能保持正确连接。但是这种语法更加冗长。

mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );

上面一行实例了一个名为“instance2”的mod_a类型的模块,然后将信号wa与名为in1的端口连接,将信号wb与名为in2的端口连接,wc与名为out的端口连接。注意这里的端口顺序是如何无关紧要的,因为无论它在子模块的端口列表中的位置如何,都会连接到正确的名称。另请注意此语法中紧接在端口名称之前的句点

Solution:

module top_module (
	input a,
	input b,
	output out
);

	// 构建一个“mod_a”类型的名为“inst1”的实例,然后通过端口名进行连接
	mod_a inst1 ( 
		.in1(a), 	// 端口in1连接到信号wire a
		.in2(b),	// 端口in2连接到信号wire b
		.out(out)	// 端口out连接到信号wire out
				// (Note: mod_a的out与top_module的out无关,名字一样只是巧合
	);

/*
	// 构建一个“mod_a”类型的名为“inst2”的实例,然后通过端口位置进行连接
	mod_a inst2 ( a, b, out );	// 三个wire信号分别与端口in1、in2、out进行连接
*/
	
endmodule

其实模块实例与C语言中调用函数有类似的地方

模块实例还得多加练习才行!最好熟练掌握按端口名实例。两种方法在后面会有巩固练习。


2.3.2Connecting ports by position(Module pos)

这个问题与2.3.1的很类似。目前有一个名为 mod_a 的模块,该模块按顺序具有 2 个输出和 4 个输入。你必须将6个端口按位置与你的顶层模块端口 out1、out2、a、b、c、d按顺序依次连接。

module mod_a ( output, output, input, input, input, input );

期望的答案:1行 

Solution:

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

endmodule

 2.3.3Connecting ports by name(Module name)

这个问题与2.3.1的很类似。目前有一个名为 mod_a 的模块,该模块按顺序具有 2 个输出和 4 个输入。你必须将6个端口按端口名与你的顶层模块端口 out1、out2、a、b、c、d按顺序依次连接。

module mod_a ( output out1, output out2, input in1, input in2, input in3, input in4);

期望的答案:1行 

Solution:

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

endmodule

在使用端口名进行实例化时,一行实例化一个端口可以使得代码更加简洁一点。

 2.3.4Three modules(Module shift)

你有一个my_dff模块,其中它有两个输入、一个输出,实现D触发器的功能。实例化3个模块,然后将它们连接起来实现一个3位移位寄存器。clk端口需要连接到所有实例。

module my_dff ( input clk, input d, output q );

主要它们的内部联系,你需要声明一些wire信号,并且谨慎为wire信号和模块实例进行命名,保证它们是独一无二的。

Solution:

module top_module (
	input clk,
	input d,
	output q
);

	wire a, b;	// 声明两个wire变量,命名为a, b

	// 对my_dff进行了三次实例化,用了三个不用的名字 d1, d2, d3
	// 端口使用了按位置连接的方式( input clk, input d, output q)
	my_dff d1 ( clk, d, a );
	my_dff d2 ( clk, a, b );
	my_dff d3 ( clk, b, q );

endmodule

2.3.5Modules and vectors(Module shift8)

本练习是2.3.4的扩展。此时模块端口不再只是单个引脚,我们现在有带有向量作为端口的模块,您将在其上连接线矢量而不是普通wire。与 Verilog 中的其他任何地方一样,端口的向量长度不必与连接到它的wire匹配,但这会导致向量的零填充或截断。本练习不使用向量长度不匹配的连接。

您将获得一个具有两个输入和一个输出的模块 my_dff8(实现8 位 D 触发器)。实例化其中三个,然后将它们连接在一起以形成长度为 3 的 8 位向量移位寄存器。另外,构建一个4选1数据选择器(没有提供),根据sel[1:0]选择输出内容:分别输出d、第一个D触发器的输出、第二个D触发器的输出、第三个D触发器的输出。(本质上,sel选择延迟输入的周期数,从零到三个时钟周期。)

module my_dff8 ( input clk, input [7:0] d, output [7:0] q );

数据选择器没有提供。一种可能的方法是在一个带有case语句的always块中。

Solution:

module top_module (
	input clk,
	input d,
	output q
);

	wire a, b;	// 声明两个wire变量,命名为a, b

	// 对my_dff进行了三次实例化,用了三个不用的名字 d1, d2, d3
	// 端口使用了按位置连接的方式( input clk, input d, output q)
	my_dff d1 ( clk, d, a );
	my_dff d2 ( clk, a, b );
	my_dff d3 ( clk, b, q );

endmodule

2.3.6Adder 1(Module add)

你已经有一个add16模块可以实现16位加法器。实例化2个add16模块来创建32位加法器。一个add16计算低16位的加法结果,在从第一个加法器接收进位以后计算,另一个add16计算结果的高16位。你的32位加法器不需要考虑低位的进位(假设是0)和当前位的进位(忽略),但是内部模块需要考虑才能正常运行。(换句话说,add16模块计算16位的 a+b+cin,而你的模块只计算32位的a+b)。

如下图所示将模块连接起来。提供的add16模块的声明如下:

module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

Solution:

module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cout1;
    add16 add_low(a[15:0],b[15:0],1'b0,sum[15:0],cout1);
    add16 add_high(a[31:16],b[31:16],cout1,sum[31:16],);  //cout信号缺省了
endmodule

 2.3.7Adder 2(Module fadd)

在本练习中,你将创建一个具有两个层次结构的电路。你的顶层模块将要实例化两个add16(已经提供),其中每一个将实例化16次add1(需要你来编写)。因此,你需要写两个模块:top_module和add1。

像2.3.6一样,add16是16位加法器。你必须实例化2个add16来创建一个32位加法器。1个add16计算低16位结果,其中另一个计算高16位结果。你的32位加法器不需要考虑carry-in(假设为0)和carry-out(忽略)。

如下图所示将 add16 模块连接在一起。提供的模块 add16 具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

在每个 add16 中,实例化了 16 个全加器(模块 add1,未提供)以实际执行加法。您必须编写具有以下声明的加法器模块:

module add1 ( input a, input b, input cin, output sum, output cout );

全加器计算a+b+cin的总和 和 进位。

总的来说,本设计中有3个模块

  • top_module——你的顶层模块,包括两个add16
  • add16(提供了)——一个16位加法器模块,由16个add1组成
  • add1——1位全加器

*****注意:如果你的提交缺少add1模块,你会收到一条错误消息,

Error (12006): Node instance "user_fadd[0].a1" instantiates undefined entity "add1". 

Solution:

module top_module (
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);//
    wire cout1;
    add16 add_low(a[15:0],b[15:0],1'b0,sum[15:0],cout1);
    add16 add_high(a[31:16],b[31:16],cout1,sum[31:16],);  //cout信号缺省了

endmodule

module add1 ( input a, input b, input cin,   output sum, output cout );

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

endmodule

相比较2.3.6,本题要自己写add1模块,其实全加器有更简单的描述,当然全加器的原理仍然需要掌握!!

module add1 ( input a, input b, input cin,   output sum, output cout );
    assign {cout,sum}=a+b+cin;

endmodule

2.3.8Carry-select adder(Module cseladd)

2.3.7中纹波进位加法器的一个缺点是加法器计算进位t的延迟相当慢(在最坏情况下来自cin),这造成在第一级加法器完成之前,第二级加法器无法开始计算其进位。进而使得加法器变得很慢!为了改进延迟出现了进位选择加法器,所下图所示。第一级加法器与之前的相同,但是我们将第二级加法器复制一遍,一个假设cin=0、一个假设cin=1,然后使用一个快速2选1数据复用器来选择正确的加法器。

在本练习中,你有add16模块。你必须使用你自己的16位2选1数据复用器,实例化3个add16来创建进位选择加法器。

如下图所示将模块连接在一起。提供的模块 add16 具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

Solution:

module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cout;
    wire [15:0] sum2,sum3;
    
    add16 adder1(a[15:0],b[15:0],1'b0,sum[15:0],cout);
    add16 adder2(a[31:16],b[31:16],1'b0,sum2[15:0],);
    add16 adder3(a[31:16],b[31:16],1'b1,sum3[15:0],);
    
    assign sum[31:16]=cout?sum3[15:0]:sum2[15:0];
endmodule

使用条件运算符是代码更具可读性。

2.3.9Adder-subtractor(Module addsub)

加法器-减法器 可以通过可选地将一个输入取它相反数来从加法器创建,这等效于将输入取反再加1。最终结果是一个可以执行两种操作的电路:(a+b+0)和(a+~b+1)。

创建下面的加减器。

你有一个需要实例化2次的16位加法器模块:

module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

每当sub=1时,使用一个32位的异或门来反转输入(这也可以看做b[31:0]与sub复制32次后相异或)。同时sub信号连接到加法器的进位。

 

3.7中纹波进位加法器的一个缺点是加法器计算进位的延迟相当慢(在最坏情况下来自cin),这造成在第一级加法器完成之前,第二级加法器无法开始计算其进位。进而使得加法器变得很慢!为了改进延迟出现了进位选择加法器,所下图所示。第一级加法器与之前的相同,但是我们将第二级加法器复制一遍,一个假设cin=0、一个假设cin=1,然后使用一个快速2选1数据复用器来选择正确的加法器。

在本练习中,你有add16模块。你必须使用你自己的16位2选1数据复用器,实例化3个add16来创建进位选择加法器。

如下图所示将模块连接在一起。提供的模块 add16 具有以下声明:

module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );

Hint:一个异或门可以看成是一个可编程的反相器,其中一个输入控制另一个输入是否反相。下面两个电路都是异或电路。

 

Solution:

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,
    output [31:0] sum
);
    wire cout1;
    wire [31:0] b_sub;
    
    assign b_sub[31:0] = b[31:0]^{32{sub}} ;
    
    add16 adder1(a[15:0],b_sub[15:0],sub,sum[15:0],cout1);
    add16 adder2(a[31:16],b_sub[31:16],cout1,sum[31:16],);

endmodule

这一期磕磕绊绊的花了很久时间,翻译得也不是很好

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值