HDLBits第三章练习及答案

HDLBits第三章

1、模块(Module)

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

下图显示了一个非常简单的带有子模块的电路。在这个练习中,创建一个实例模块mod_a,模块的三个引脚(连接in1,in2和out)到顶层模块的三个端口(电线a,b和out)。该模块mod_a是为您提供的——您必须实例化它。

连接模块时,只有模块上的端口很重要。您不需要知道模块内的代码。模块的代码mod_a如下所示:

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

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

您可以通过端口名称或端口位置将信号连接到模块。如需额外练习,请尝试两种方法。
在这里插入图片描述

将信号连接到模块端口

有两种常用的方法将电线连接到端口:按端口位置或按端口名称。

按端口位置

按位置将电线连接到端口的语法应该很熟悉,因为它使用类似 C 的语法。实例化模块时,端口根据模块的声明从左到右连接。例如:

mod_a instance1 ( wa, wb, wc );

此实例化类型的模块mod_a,并赋予它一个实例名“INSTANCE1”的,然后连接信号wa(新模块外部)的第一端口(in1新模块的),wb到第二端口(in2),以及wc所述第三端口(out)。这种语法的一个缺点是,如果模块的端口列表发生更改,则还需要找到并更改模块的所有实例以匹配新模块。

按端口名称

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

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

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

代码实现:

(1)按端口位置

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

(2)按端口名称

module top_module ( input a, input b, output out );
    mod_a instance2 ( .out(out), .in1(a), .in2(b) );
endmodule

验证结果:
在这里插入图片描述

2、模块位置(Module pos)

这个问题类似于上一个(模块)。您将获得一个名为的模块mod_a,该模块按该顺序具有 2 个输出和 4 个输入。您必须按位置将 6 个端口连接到顶级模块的端口out1, out2, a, b, c, 和d 。

您将获得以下模块:

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

在这里插入图片描述
代码实现:

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

验证结果:
在这里插入图片描述

3、模块名称(Module name)

这个问题类似于模块. 您将获得一个名为的模块mod_a,该模块按某种顺序具有 2 个输出和 4 个输入。您必须按名称将 6 个端口连接到顶级模块的端口:

PortPort in mod_aPort in top_module
outputout1out1
outputout2out2
inputin1a
inputin2b
inputin3c
inputin4d

您将获得以下模块:

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

在这里插入图片描述
代码实现:

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

验证结果:
在这里插入图片描述

4、模块转移(Module shift)

您将获得一个my_dff具有两个输入和一个输出的模块(实现 D 触发器)。实例化其中三个,然后将它们链接在一起以形成长度为 3 的移位寄存器。clk端口需要连接到所有实例。
提供给您的模块是:

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

请注意,要进行内部连接,您需要声明一些电线。命名电线和模块实例时要小心:名称必须是唯一的。
在这里插入图片描述
代码实现:

module top_module ( input clk, input d, output q );
    wire q1,q2;
    my_dff U1 ( clk, d, q1 );
    my_dff U2 ( clk, q1, q2 );
    my_dff U3 ( clk, q2, q );
endmodule

验证结果:
在这里插入图片描述

5、模块转移8(Module shift8)

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

您将获得一个my_dff8具有两个输入和一个输出的模块(实现一组 8 个 D 触发器)。实例化其中三个,然后将它们链接在一起以形成一个长度为 3 的 8 位宽移位寄存器。此外,创建一个 4 对 1 多路复用器(未提供),根据以下情况选择输出内容sel[1:0]: 输入值d,在第一个之后,在第二个之后,或者在第三个 D 触发器之后。(本质上,sel选择延迟输入的周期数,从零到三个时钟周期。)
提供给您的模块是:

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

不提供多路复用器,一种可能的写法是在always一个case语句块中。
在这里插入图片描述
拓展:

下面是一个2选1多路复用器。

always@(*)				// always语句块,在断电或中断前,会一直执行
	case(sel)			// case语句,根据不同的sel值选择不同的输出值
		1'b0: q = d;	// 输入sel=0时,输出q=d
		1'b1: q = q0;	// 输入sel=1时,输出q=q0
	endcase

其中,always@(*)里面的敏感变量为星号时,意思是敏感变量由综合器根据always里面的输入变量自动添加,不用自己考虑。

代码实现:

module top_module ( 
    input clk, 
    input [7:0] d, 
    input [1:0] sel, 
    output [7:0] q 
);
    wire [7:0]q1,q2,q3;
    my_dff8 ( .clk(clk), .d(d), .q(q1) );
    my_dff8 ( .clk(clk), .d(q1), .q(q2) );
    my_dff8 ( .clk(clk), .d(q2), .q(q3) );
    
    always@(*)
   	 case(sel)
        2'b00: q = d;
        2'b01: q = q1;
        2'b10: q = q2;
        2'b11: q = q3;
   	 endcase
endmodule

验证结果:
在这里插入图片描述

6、加法器1(Module add)

您将获得一个add16执行 16 位加法的模块。实例化其中两个以创建 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 );

在这里插入图片描述
代码实现:

module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cin1;
  
    add16 add_1( .a( a[15:0] ), .b( b[15:0] ), .cin(1'b0), .sum( sum [15:0] ), .cout(cin1) );
    add16 add_2( .a( a[31:16] ), .b( b[31:16] ), .cin(cin1), .sum( sum[31:16] ), .cout() );
    
endmodule

验证结果:
在这里插入图片描述

7、加法器2(Module fadd)

在本练习中,您将创建一个具有两个层次结构的电路。您top_module将实例化add16(提供)的两个副本,每个副本将实例化add1(您必须编写)的16 个副本。因此,您必须编写两个模块:top_module和add1.

例如module_add,您将获得一个add16执行 16 位加法的模块。您必须实例化其中的两个以创建 32 位加法器。一个add16模块计算加法结果的低 16 位,而第二个add16模块计算结果的高 16 位。您的 32 位加法器不需要处理进位(假设为 0)或进位(忽略)。

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

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

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

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

回想一下,全加器计算 a+b+cin 的总和和进位。

综上所述,本设计共有三个模块:
(1)top_module — 您的顶级模块包含两个…
(2)add16, 提供 — 1个 16 位加法器模块,由 16 个…
(3)add1 — 1 位全加器模块。

如果您提交的文件缺少module add1,您将收到一条错误消息,内容为Error (12006): Node instance “user_fadd[0].a1” instantiates undefined entity “add1”。
在这里插入图片描述
拓展:

全加器逻辑表达式:
sum = a ^ b ^ cin;
cout = a&b | a&cin | b&cin;

代码实现:

module top_module (
   input [31:0] a,
   input [31:0] b,
   output [31:0] sum
);
  	wire cin1;
 
   add16 add_1( .a( a[15:0] ), .b( b[15:0] ), .cin(1'b0), .sum( sum [15:0] ), .cout(cin1) );
   add16 add_2( .a( a[31:16] ), .b( b[31:16] ), .cin(cin1), .sum( 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;
   
endmodule

验证结果:
在这里插入图片描述

8、进位选择加法器(Module cseladd)

行波进位加法器的一个缺点(参见前面的练习) 是加法器计算进位的延迟(在最坏的情况下来自进位)相当慢,并且第二级加法器在第一级加法器完成之前无法开始计算其进位. 这会使加法器变慢。一项改进是进位选择加法器,如下所示。第一级加法器和之前一样,但我们复制第二级加法器,一个假设进位=0,一个假设进位=1,然后使用一个快速的2对1多路复用器来选择哪个结果碰巧是正确的。

在本练习中,为您提供了与add16上一练习相同的模块,它将两个 16 位数字与进位相加,并产生一个进位和 16 位和。您必须使用自己的 16 位 2 对 1 多路复用器实例化其中三个以构建进位选择加法器。

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

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

在这里插入图片描述
拓展:
(1)条件表达式:a ? b : c;
表示:如果a为真,则表达式值为b; 如果a为假,则表达式值为c 。
(2)case语句(条件分支语句):
case(控制表达式)
值1:语句块1;
值2:语句块2;
……
值n:语句块n;
default:语句块n+1;
endcase

代码实现:
(1)条件表达式

module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire sel;
    wire [31:16]sum0,sum1;
    
    add16 add_1( .a(a[15:0]), .b(b[15:0]), .cin(1'b0), .sum(sum[15:0]), .cout(sel) );
    add16 add_2( .a(a[31:16]), .b(b[31:16]), .cin(1'b0), .sum(sum0), .cout() );
    add16 add_3( .a(a[31:16]), .b(b[31:16]), .cin(1'b1), .sum(sum1), .cout() );
    
    assign sum[31:16] = sel ? sum1 : sum0;
endmodule

(2)case选择语句

module top_module(
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);
    wire cout1;
    wire [31:16]sum0,sum1;
    add16 add_1( .a(a[15:0]), .b(b[15:0]), .cin(1'b0), .sum(sum[15:0]), .cout(cout1) );
    add16 add_2( .a(a[31:16]), .b(b[31:16]), .cin(1'b0), .sum(sum0), .cout() );
    add16 add_3( .a(a[31:16]), .b(b[31:16]), .cin(1'b1), .sum(sum1), .cout() );
    
    always@(*)
    case(cout1)
        1'b0:sum[31:16]=sum0;
        1'b1:sum[31:16]=sum1;
    endcase
endmodule

验证结果:
在这里插入图片描述

9、加减法(Module addsub)

加法器-减法器可以通过可选地否定其中一个输入来构建加法器-减法器,这等效于将输入取反然后加 1。最终结果是一个可以执行两种运算的电路:(a + b + 0) 和 ( a + ~b + 1)。如果您想更详细地说明该电路的工作原理,请参阅维基百科。

构建下面的加法器-减法器。

为您提供了一个 16 位加法器模块,您需要将它实例化两次:

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

每当sub为 1时,使用 32 位宽的 XOR 门来反转b输入。(这也可以被视为b[31:0]与 sub 复制 32 次的异或。参见复制运算符.) 还将子输入连接到加法器的进位。

代码实现:
在这里插入图片描述

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 = b ^ {32{sub}};
    
    add16 add_1( .a(a[15:0]), .b(b_sub[15:0]), .cin(sub), .sum(sum[15:0]), .cout(cout1) );
    add16 add_2( .a(a[31:16]), .b(b_sub[31:16]), .cin(cout1), .sum(sum[31:16]), .cout() );
    
endmodule

验证结果:
在这里插入图片描述

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值