HDLbits day3
3、Module:Hierarchy
3.1、Modules
下图显示了一个带有子模块的非常简单的电路。在本练习中,创建一个module实例mod_a
,然后将模块的三个引脚(in1
、in2
和out
)连接到顶层模块的三个端口(电线a
、b
和out
)。该模块mod_a
是为您提供的——您必须实例化它。
模块调用语法:
模块名 <参数值列表> 实例名(端口名列表);
module top_module ( input a, input b, output out ); mod_a U1(.in1(a),.in2(b),.out(out)); endmodule
3.2、Connecting ports by position
您将获得一个名为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 U1(out1,out2,a,b,c,d); endmodule
3.3、Connecting ports by name
您将获得一个名为mod_a的模块,该模块按某种顺序具有 2 个输出和 4 个输入。您必须按名称将 6 个端口连接到顶级模块的端口.
您将获得以下模块: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(.in1(a),.in2(b),.in3(c),.in4(d),.out1(out1),.out2(out2)); endmodule
3.4、Three modules
您将获得一个my_dff
具有两个输入和一个输出的模块(实现 D 触发器)。实例化其中的三个,然后将它们链接在一起以制作长度为 3 的移位寄存器。clk
端口需要连接到所有实例。
提供给您的模块是: module my_dff ( input clk, input d, output q );
请注意,要建立内部连接,您需要声明一些连线。命名你的连线和模块实例时要小心:名称必须是唯一的。
module top_module ( input clk, input d, output q ); wire w1,w2; my_dff U1(.clk(clk),.d(d),.q(w1)), U2(.clk(clk),.d(w1),.q(w2)), U3(.clk(clk),.d(w2),.q(q)); endmodule
3.5、Modules and vectors
模块端口不再只是单个引脚,我们现在有带有向量作为端口的模块,您将连接线向量而不是普通线。与 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
语句
module top_module ( input clk, input [7:0] d, input [1:0] sel, output [7:0] q ); wire [7:0] w[2:0]; my_dff8 U1(.clk(clk),.d(d),.q(w[0])); my_dff8 U2(.clk(clk),.d(w[0]),.q(w[1])); my_dff8 U3(.clk(clk),.d(w[1]),.q(w[2])); always@(clk or sel or d) case(sel) 2'b00:q=d; 2'b01:q=w[0]; 2'b10:q=w[1]; 2'b11:q=w[2]; endcase endmodule
3.6、Adder 1
您将获得一个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 w1; add16 U1(.a(a[15:0]),.b(b[15:0]),.cin(0),.sum(sum[15:0]),.cout(w1)); add16 U2(.a(a[31:16]),.b(b[31:16]),.cin(w1),.sum(sum[31:16]),.cout()); endmodule
3.7、Adder 2
在本练习中,您将创建具有两个层次结构的电路。您top_module
将实例化add16
(提供)的两个副本,每个副本将实例化add1
(您必须编写)的 16 个副本。因此,您必须编写两个模块:top_module
和add1
.
您将获得一个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
中,16 个全加器(模块add1
,未提供)被实例化以实际执行加法。您必须编写具有以下声明的完整加法器模块:
module add1 ( input a, input b, input cin, output sum, output cout );
回想一下,全加器计算 a+b+cin 的和和进位。
综上所述,本设计共有三个模块:
top_module
— 您的顶级模块包含两个...add16
, 提供 — 一个 16 位加法器模块,由 16 个...add1
— 1 位全加器模块。
module top_module ( input [31:0] a, input [31:0] b, output [31:0] sum );// wire w1; add16 U1(.a(a[15:0]),.b(b[15:0]),.cin(0),.sum(sum[15:0]),.cout(w1)); add16 U2(.a(a[31:16]),.b(b[31:16]),.cin(w1),.sum(sum[31:16]),.cout()); endmodule module add1 ( input a, input b, input cin, output sum, output cout ); // Full adder module here assign {cout,sum}=a+b+cin; endmodule
3.8、Carry-select adder
改进是进位选择加法器,如下所示。第一级加法器与之前相同,但我们复制第二级加法器,一个假设进位=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 );
module top_module( input [31:0] a, input [31:0] b, output [31:0] sum ); wire [15:0] sel,w1,w2; add16 U1(.a(a[15:0]),.b(b[15:0]),.cin(0),.sum(sum[15:0]),.cout(sel)); add16 U2(.a(a[31:16]),.b(b[31:16]),.cin(0),.sum(w1),.cout()); add16 U3(.a(a[31:16]),.b(b[31:16]),.cin(1),.sum(w2),.cout()); always@(*) begin if(sel) //条件语句只能在initial和always语句引导的语句块(begin-end)中使用,模块的其他部分都不能使用 sum[31:16]=w1; else sum[31:16]=w2; end endmodule
3.9、Adder-subtractor
构建下面的加减法器。
为您提供了一个 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 [31:0] w1; wire w2; assign w1=b^{32{sub}}; add16 U1(.a(a[15:0]),.b(w1[15:0]),.cin(sub),.sum(sum[15:0]),.cout(w2)); add16 U2(.a(a[31:16]),.b(w1[31:16]),.cin(w2),.sum(sum[31:16]),.cout()); endmodule
4、Procedures
4.1、Always blocks(combination)
使用 assign 语句和组合 always 块构建 AND 门。
1)组合always块等同于赋值语句。程序块内部代码的语法与外部代码不同。程序块具有更丰富的语句集(例如,if-then、case),但不能包含连续赋值。
2)例如,assign 和combinationalways 块描述了相同的电路。两者都创建了相同的组合逻辑块。每当任何输入(右侧)更改值时,两者都会重新计算输出。
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
3)对于组合的 always 块,始终使用(*)的敏感度列表。明确列出信号很容易出错(如果你错过了一个),并且在硬件综合时会被忽略。如果您明确指定灵敏度列表并错过了一个信号,则合成硬件的行为仍将与指定(*)一样,但模拟不会也不匹配硬件的行为。
4)关于 wire 与 reg 的注意事项:assign 语句的左侧必须是net类型(例如,wire),而过程赋值(在 always 块中)的左侧必须是变量类型(例如,reg)。这些类型(wire vs. reg)与合成的硬件无关,只是 Verilog 用作硬件模拟语言时留下的语法。
// synthesis verilog_input_version verilog_2001 module top_module( input a, input b, output wire out_assign, output reg out_alwaysblock ); assign out_assign=a&b; always@(*) out_alwaysblock=a&b; endmodule
4.2、Always blocks(clocked)
以三种方式构建异或门,使用分配语句、组合always块和时钟always块。请注意,时钟always块产生的电路与其他两个不同:有一个触发器,因此输出被延迟。
1)对于硬件综合,有两种相关的always块:
- Combinational: always @(*)
- Clocked: always @(posedge clk)
时钟always块与组合always块一样,创建一个组合逻辑电路,不同的是,时钟always块在组合逻辑电路的输出处创建了一组触发器(或“寄存器”),不是立即可见逻辑块的输出,而是仅在下一个(posedge clk)之后立即可见输出。
2)Verilog 中有三种类型的赋值:
- 连续赋值(assign x = y;)。Can only be used when not inside a procedure ("always block").。
- 程序块分配:( x = y; )。只能在程序(procedure)内部使用。
- 程序非阻塞赋值:( x <= y; )。只能在程序内部使用。
在组合的always 块中,使用块分配。在时钟控制的always 块中,使用非阻塞分配。
// synthesis verilog_input_version verilog_2001 module top_module( input clk, input a, input b, output wire out_assign, output reg out_always_comb, output reg out_always_ff ); assign out_assign=a^b; always@(*) out_always_comb=a^b; always@(posedge clk) out_always_ff=a^b; endmodule
4.3、If statement
Build a 2-to-1 mux that chooses between a and b. Choose b if both sel_b1 and sel_b2 are true. Otherwise, choose a. Do the same twice, once using assign statements and once using a procedural if statement.
if语句通常创建一个2 对 1 多路复用器,如果条件为真则选择一个输入,如果条件为假则选择另一个输入。
always @(*) begin if (condition) begin out = x; end else begin out = y; end end
这等效于使用带有条件运算符 的连续赋值:
assign out = (condition) ? x : y;
// synthesis verilog_input_version verilog_2001 module top_module( input a, input b, input sel_b1, input sel_b2, output wire out_assign, output reg out_always ); assign out_assign=(sel_b1&sel_b2)?b:a; always@(*) if(sel_b1&sel_b2) out_always=b; else out_always=a; endmodule
4.4、If statement latches
以下代码包含创建闩锁的不正确行为。修复错误,以便您仅在计算机确实过热时关闭计算机,并在您到达目的地或需要加油时停止驾驶。
1)设计电路时,首先要从电路方面考虑:
- 我想要这个逻辑门
- 我想要一个具有这些输入并产生这些输出的组合逻辑块
- 我想要一个组合的逻辑块,然后是一组触发器
你不能做的是先写代码,然后希望它生成一个合适的电路。
2)保持输出不变”的行为意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如逻辑门)不能记住任何状态。
3)在组合逻辑中,if分支一定要写全,否则输出只在指定的条件变化,在未指定的条件保持不变。但是组合逻辑无法记忆状态,这就会导致综合时输出端连接在输入端作为输入产生锁存器。
1 // synthesis verilog_input_version verilog_2001 2 module top_module ( 3 input cpu_overheated, 4 output reg shut_off_computer, 5 input arrived, 6 input gas_tank_empty, 7 output reg keep_driving ); // 8 //模拟计算机 9 always @(*) begin 10 if (cpu_overheated) 11 shut_off_computer = 1; 12 else 13 shut_off_computer = 0; 14 end 15 //模拟开车 16 always @(*) begin 17 if (~arrived) 18 keep_driving = ~gas_tank_empty; 19 else 20 keep_driving = ~arrived; 21 end 22 23 endmodule
4.5、Case statement
创建一个 6 对 1 多路复用器。当sel在 0 到 5 之间时,选择对应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。
1 // synthesis verilog_input_version verilog_2001 2 module top_module ( 3 input [2:0] sel, 4 input [3:0] data0, 5 input [3:0] data1, 6 input [3:0] data2, 7 input [3:0] data3, 8 input [3:0] data4, 9 input [3:0] data5, 10 output reg [3:0] out );// 11 12 always@(*) begin // This is a combinational circuit 13 case(sel) 14 3'b000:out=data0; 15 3'b001:out=data1; 16 3'b010:out=data2; 17 3'b011:out=data3; 18 3'b100:out=data4; 19 3'b101:out=data5; 20 default:out=4'b0000; 21 endcase 22 end 23 24 endmodule
4.6、Priority encoder
构建一个 4 位优先级编码器。对于这个问题,如果没有一个输入位为高(即输入为零),则输出零。请注意,一个 4 位数字有 16 种可能的组合。
优先级编码器是一种组合电路,当给定输入位向量时,输出向量中第一个1位的位置。例如,给定输入8'b100 1 0000的 8 位优先级编码器将输出3'd4,因为 bit[4] 是第一个高位。
// synthesis verilog_input_version verilog_2001 module top_module ( input [3:0] in, output reg [1:0] pos ); always@(*) begin casez(in) //4'b???1:pos=2'd0; //?只能存在casez或者case1中, //4'b??10:pos=2'd1; //4'b?100:pos=2'd2; //4'b1000:pos=2'd3; //4'bzzz1:pos=2'd0; //4'bzz10:pos=2'd1; //4'bz100:pos=2'd2; //4'b1000:pos=2'd3; default:pos=2'd0; endcase end endmodule
4.7、Priority encoder with casez
为 8 位输入构建优先级编码器。
// synthesis verilog_input_version verilog_2001 module top_module ( input [7:0] in, output reg [2:0] pos ); always @(*) casez (in) 8'bzzzz_zzz1: pos = 0; 8'bzzzz_zz1z: pos = 1; //在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就会忽略,不予考虑,而只关注其他位的比较结果。?同理 8'bzzzz_z1zz: pos = 2; 8'bzzzz_1zzz: pos = 3; 8'bzzz1_zzzz: pos = 4; 8'bzz1z_zzzz: pos = 5; 8'bz1zz_zzzz: pos = 6; 8'b1zzz_zzzz: pos = 7; default: pos = 0; endcase endmodule
4.8、Avioding latches
假设您正在构建一个电路来处理来自游戏的 PS/2 键盘的扫描码。
为避免创建锁存器,必须在所有可能的条件下为所有输出分配一个值。
仅仅有一个默认情况是不够的。您必须为所有四种情况和默认情况下的所有四个输出分配一个值。这可能涉及大量不必要的输入。
解决此问题的一种简单方法是在 case 语句 之前为输出分配一个“默认值” :
// synthesis verilog_input_version verilog_2001 module top_module ( input [15:0] scancode, output reg left, output reg down, output reg right, output reg up ); //assign always@(*) begin up=1'b0; //reg型变量必须在always语句块或者initial语句块中赋值,不能使用assign赋值 right=1'b0; down=1'b0; left=1'b0; case(scancode) 16'he06b:left=1'b1; 16'he072:down=1'b1; 16'he074:right=1'b1; 16'he075:up=1'b1; endcase end endmodule
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」