HDLbits day3

3、Module:Hierarchy

3.1、Modules

下图显示了一个带有子模块的非常简单的电路。在本练习中,创建一个module实例mod_a,然后将模块的三个引脚(in1in2out)连接到顶层模块的三个端口(电线about)。该模块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 个端口按顺序连接到顶层模块的端口out1out2abcd

您将获得以下模块: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_moduleadd1.

您将获得一个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
posted @ 2022-04-07 19:37  super_sweet  阅读(111)  评论(0编辑  收藏  举报