HDLBits 系列(2)——Verilog Language(Modules: Hierarchy、Procedures)

目录

2.Verilog Language

2.3 Modules: Hierarchy

1.Modules

2.Connecting ports by position

3.Connecting ports by name

4.Three modules

5.Modules and vectors

6.Adder 1

7.Adder 2

8.Carry-select adder

9.Adder-subtractor

2.4  Procedures

1.Always blocks (combinational)

2.Always blocks (clocked)

3.If statement

4.If statement latches

A common source of errors: How to avoid making latches

5.Case statement

A bit of practice

6.Priority encoder

7.Priority encoder with casez

8.Avoiding latches

参考网址:HDLBits


2.Verilog Language

2.3 Modules: Hierarchy

1.Modules

By now, you're familiar with a module, which is a circuit that interacts with its outside through input and output ports. Larger, more complex circuits are built by composing bigger modules out of smaller modules and other pieces (such as assign statements and always blocks) connected together. This forms a hierarchy, as modules can contain instances of other modules.

The figure below shows a very simple circuit with a sub-module. In this exercise, create one instance of module mod_a, then connect the module's three pins (in1in2, and out) to your top-level module's three ports (wires ab, and out). The module mod_a is provided for you — you must instantiate it.

The hierarchy of modules is created by instantiating one module inside another, as long as all of the modules used belong to the same project (so the compiler knows where to find the module). The code for one module is not written inside another module's body (Code for different modules are not nested).

You may connect signals to the module by port name or port position. For extra practice, try both methods.

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

    mod_a mod_a_inst(.in1(a),.in2(b),.out(out));
    
endmodule

放一张仿真图吧!

2.Connecting ports by position

This problem is similar to the previous one (module). You are given a module named mod_a that has 2 outputs and 4 inputs, in that order. You must connect the 6 ports by position to your top-level module's ports out1out2abc, and d, in that order.

You are given the following module:

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 mod_a_inst(
        .out1(out1),
        .out2(out2),
        .in(a),
        .in(b),
        .in(c),
        .in(d)
 */
    mod_a mod_a_inst(out1,out2,a,b,c,d);
    
    
  
endmodule

3.Connecting ports by name

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

4.Three modules

You are given a module my_dff with two inputs and one output (that implements a D flip-flop). Instantiate three of them, then chain them together to make a shift register of length 3. The clk port needs to be connected to all instances.

The module provided to you is: module my_dff ( input clk, input d, output q );

Note that to make the internal connections, you will need to declare some wires. Be careful about naming your wires and module instances: the names must be unique.

module top_module ( input clk, input d, output q );
    
    wire q1,q2;
    my_dff my_dff1(
        .clk(clk),
        .d(d),
        .q(q1)
    );
   my_dff my_dff2(
        .clk(clk),
        .d(q1),
        .q(q2)
    );
    my_dff my_dff3(
        .clk(clk),
        .d(q2),
        .q(q)
    );
endmodule

5.Modules and vectors

This exercise is an extension of module_shift. Instead of module ports being only single pins, we now have modules with vectors as ports, to which you will attach wire vectors instead of plain wires. Like everywhere else in Verilog, the vector length of the port does not have to match the wire connecting to it, but this will cause zero-padding or trucation of the vector. This exercise does not use connections with mismatched vector lengths.

You are given a module my_dff8 with two inputs and one output (that implements a set of 8 D flip-flops). Instantiate three of them, then chain them together to make a 8-bit wide shift register of length 3. In addition, create a 4-to-1 multiplexer (not provided) that chooses what to output depending on sel[1:0]: The value at the input d, after the first, after the second, or after the third D flip-flop. (Essentially, selselects how many cycles to delay the input, from zero to three clock cycles.)

The module provided to you is: module my_dff8 ( input clk, input [7:0] d, output [7:0] q );

The multiplexer is not provided. One possible way to write one is inside an always block with a case statement inside. (See also: mux9to1v)

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 my_dff1(
        .clk(clk),
        .d(d),
        .q(q1)
    );
    my_dff8 my_dff2(
        .clk(clk),
        .d(q1),
        .q(q2)
    );
    my_dff8 my_dff3(
        .clk(clk),
        .d(q2),
        .q(q3)
    );
    always @(*) begin
        case(sel)
            2'd0:q=d;
            2'd1:q=q1;
            2'd2:q=q2;
            2'd3:q=q3;
        endcase
    end
    
    
    
endmodule

6.Adder 1

You are given a module add16 that performs a 16-bit addition. Instantiate two of them to create a 32-bit adder. One add16 module computes the lower 16 bits of the addition result, while the second add16 module computes the upper 16 bits of the result, after receiving the carry-out from the first adder. Your 32-bit adder does not need to handle carry-in (assume 0) or carry-out (ignored), but the internal modules need to in order to function correctly. (In other words, the add16 module performs 16-bit a + b + cin, while your module performs 32-bit a + b).

Connect the modules together as shown in the diagram below. The provided module add16 has the following declaration:

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 cout1,cout2;
add16 add_low16( 
    .a(a[15:0]), 
    .b(b[15:0]), 
    .cin(1'b0), 
    .sum(sum[15:0]), 
    .cout(cout1) 
);
add16 add_up16( 
    .a(a[31:16]), 
    .b(b[31:16]), 
    .cin(cout1), 
    .sum(sum[31:16]), 
    .cout(cout2) 
);

endmodule

7.Adder 2

Nextmodule_cseladd

In this exercise, you will create a circuit with two levels of hierarchy. Your top_module will instantiate two copies of add16(provided), each of which will instantiate 16 copies of add1 (which you must write). Thus, you must write two modules: top_module and add1.

Like module_add, you are given a module add16 that performs a 16-bit addition. You must instantiate two of them to create a 32-bit adder. One add16 module computes the lower 16 bits of the addition result, while the second add16 module computes the upper 16 bits of the result. Your 32-bit adder does not need to handle carry-in (assume 0) or carry-out (ignored).

Connect the add16 modules together as shown in the diagram below. The provided module add16 has the following declaration:

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

Within each add16, 16 full adders (module add1, not provided) are instantiated to actually perform the addition. You must write the full adder module that has the following declaration:

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

Recall that a full adder computes the sum and carry-out of a+b+cin.

In summary, there are three modules in this design:

  • top_module — Your top-level module that contains two of...
  • add16, provided — A 16-bit adder module that is composed of 16 of...
  • add1 — A 1-bit full adder module.

If your submission is missing a module add1, you will get an error message that says Error (12006): Node instance "user_fadd[0].a1" instantiates undefined entity "add1".

module top_module (
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);//
    wire cout1,cout2;
    add16 add16_lower_inst(
        .a(a[15:0]),
        .b(b[15:0]),
        .cin(1'b0),
        .sum(sum[15:0]),
        .cout(cout1)
    );
    add16 add16_up_inst(
        .a(a[31:16]),
        .b(b[31:16]),
        .cin(cout1),
        .sum(sum[31:16]),
        .cout(cout2)
    );
    
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

8.Carry-select adder

One drawback of the ripple carry adder (See previous exercise) is that the delay for an adder to compute the carry out (from the carry-in, in the worst case) is fairly slow, and the second-stage adder cannot begin computing its carry-out until the first-stage adder has finished. This makes the adder slow. One improvement is a carry-select adder, shown below. The first-stage adder is the same as before, but we duplicate the second-stage adder, one assuming carry-in=0 and one assuming carry-in=1, then using a fast 2-to-1 multiplexer to select which result happened to be correct.

In this exercise, you are provided with the same module add16 as the previous exercise, which adds two 16-bit numbers with carry-in and produces a carry-out and 16-bit sum. You must instantiate three of these to build the carry-select adder, using your own 16-bit 2-to-1 multiplexer.

Connect the modules together as shown in the diagram below. The provided module add16 has the following declaration:

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 cout_sel,cout1,cout2;
    wire [31:0]sum1,sum2;
    add16 add16_lower_inst(a[15:0],b[15:0] ,1'b0,sum[15:0],cout_sel);
    add16 add16_up1_inst(a[31:16],b[31:16] ,1'b0,sum1[31:16],cout1);
    add16 add16_up2_inst(a[31:16],b[31:16] ,1'b1,sum2[31:16],cout2);
    
    assign sum[31:16]=cout_sel?sum2[31:16]:sum1[31:16];
    
    
    
endmodule

9.Adder-subtractor

An adder-subtractor can be built from an adder by optionally negating one of the inputs, which is equivalent to inverting the input then adding 1. The net result is a circuit that can do two operations: (a + b + 0) and (a + ~b + 1). See Wikipedia if you want a more detailed explanation of how this circuit works.

Build the adder-subtractor below.

You are provided with a 16-bit adder module, which you need to instantiate twice:

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

Use a 32-bit wide XOR gate to invert the b input whenever sub is 1. (This can also be viewed as b[31:0] XORed with sub replicated 32 times. See replication operator.). Also connect the sub input to the carry-in of the adder.

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,
    output [31:0] sum
);
    wire cout1,cout2,cout3,cout4;
    wire [31:0]sum1,sum2;

    add16 add16_inst1(a[15:0],b[15:0],1'b0,sum1[15:0],cout1);
    add16 add16_inst2(a[31:16],b[31:16],cout1,sum1[31:16],cout2);
    add16 add16_inst3(a[15:0],~b[15:0],1'b1,sum2[15:0],cout3);
    add16 add16_inst4(a[31:16],~b[31:16],cout3,sum2[31:16],cout4);
    assign sum=sub?sum2:sum1;
    
endmodule

2.4  Procedures

Procedures include always, initial, task, and function blocks. Procedures allow sequential statements (which cannot be used outside of a procedure) to be used to describe the behaviour of a circuit.

1.Always blocks (combinational)

Build an AND gate using both an assign statement and a combinational always block. (Since assign statements and combinational always blocks function identically, there is no way to enforce that you're using both methods. But you're here for practice, right?...)

// 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

2.Always blocks (clocked)

For hardware synthesis, there are two types of always blocks that are relevant:

  • Combinational: always @(*)
  • Clocked: always @(posedge clk)

Clocked always blocks create a blob of combinational logic just like combinational always blocks, but also creates a set of flip-flops (or "registers") at the output of the blob of combinational logic. Instead of the outputs of the blob of logic being visible immediately, the outputs are visible only immediately after the next (posedge clk).

Blocking vs. Non-Blocking Assignment

There are three types of assignments in Verilog:

  • Continuous assignments (assign x = y;). Can only be used when not inside a procedure ("always block").
  • Procedural blocking assignment: (x = y;). Can only be used inside a procedure.
  • Procedural non-blocking assignment: (x <= y;). Can only be used inside a procedure.

In a combinational always block, use blocking assignments. In a clocked always block, use non-blocking assignments. A full understanding of why is not particularly useful for hardware design and requires a good understanding of how Verilog simulators keep track of events. Not following this rule results in extremely hard to find errors that are both non-deterministic and differ between simulation and synthesized hardware.

// 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 @(*)begin
        out_always_comb=a^b;
    end
    
    always @(posedge clk)begin
        out_always_ff<=a^b;
    end
    
    
   
    
endmodule

3.If statement

An if statement usually creates a 2-to-1 multiplexer, selecting one input if the condition is true, and the other input if the condition is false.

Always if mux.png

always @(*) begin
    if (condition) begin
        out = x;
    end
    else begin
        out = y;
    end
end

This is equivalent to using a continuous assignment with a conditional operator:

assign out = (condition) ? x : y;

However, the procedural if statement provides a new way to make mistakes. The circuit is combinational only if out is always assigned a value.

// 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   ); 
    
    always @(*)begin
        if(sel_b1&&sel_b2) begin
            out_always<=b;
        end
        else begin
            out_always<=a;
        end
    end
    
    assign out_assign=(sel_b1&&sel_b2)?b:a;
    
endmodule

4.If statement latches

缺少else 导致锁存问题。

A common source of errors: How to avoid making latches

When designing circuits, you must think first in terms of circuits:

  • I want this logic gate
  • I want a combinational blob of logic that has these inputs and produces these outputs
  • I want a combinational blob of logic followed by a set of flip-flops

What you must not do is write the code first, then hope it generates a proper circuit.

  • If (cpu_overheated) then shut_off_computer = 1;
  • If (~arrived) then keep_driving = ~gas_tank_empty;

Syntactically-correct code does not necessarily result in a reasonable circuit (combinational logic + flip-flops). The usual reason is: "What happens in the cases other than those you specified?". Verilog's answer is: Keep the outputs unchanged.

This behaviour of "keep outputs unchanged" means the current state needs to be remembered, and thus produces a latch. Combinational logic (e.g., logic gates) cannot remember any state. Watch out for Warning (10240): ... inferring latch(es)" messages. Unless the latch was intentional, it almost always indicates a bug. Combinational circuits must have a value assigned to all outputs under all conditions. This usually means you always need else clauses or a default value assigned to the outputs.

// synthesis verilog_input_version verilog_2001
module top_module (
    input      cpu_overheated,
    output reg shut_off_computer,
    input      arrived,
    input      gas_tank_empty,
    output reg keep_driving  ); //

    always @(*) begin
        if (cpu_overheated)
           shut_off_computer = 1;
        else 
            shut_off_computer=1'b0;
    end

    always @(*) begin
        if (~arrived)
           keep_driving = ~gas_tank_empty;
        else 
           keep_driving=1'b0;
    end

endmodule

5.Case statement

Case statements in Verilog are nearly equivalent to a sequence of if-elseif-else that compares one expression to a list of others. Its syntax and functionality differs from the switch statement in C.

always @(*) begin     // This is a combinational circuit
    case (in)
      1'b1: begin 
               out = 1'b1;  // begin-end if >1 statement
            end
      1'b0: out = 1'b0;
      default: out = 1'bx;
    endcase
end
  • The case statement begins with case and each "case item" ends with a colon. There is no "switch".
  • Each case item can execute exactly one statement. This makes the "break" used in C unnecessary. But this means that if you need more than one statement, you must use begin ... end.
  • Duplicate (and partially overlapping) case items are permitted. The first one that matches is used. C does not allow duplicate case items.

A bit of practice

Case statements are more convenient than if statements if there are a large number of cases. So, in this exercise, create a 6-to-1 multiplexer. When sel is between 0 and 5, choose the corresponding data input. Otherwise, output 0. The data inputs and outputs are all 4 bits wide.

Be careful of inferring latches (See.always_if2)

// synthesis verilog_input_version verilog_2001
module top_module ( 
    input [2:0] sel, 
    input [3:0] data0,
    input [3:0] data1,
    input [3:0] data2,
    input [3:0] data3,
    input [3:0] data4,
    input [3:0] data5,
    output reg [3:0] out   );//

    always@(*) begin  // This is a combinational circuit
        case(sel)
             3'd0:out<=data0;
             3'd1:out<=data1;
             3'd2:out<=data2;
             3'd3:out<=data3;
             3'd4:out<=data4;
             3'd5:out<=data5;
            default:out<=4'd0;
        endcase
            
    end

endmodule

6.Priority encoder

priority encoder is a combinational circuit that, when given an input bit vector, outputs the position of the first 1 bit in the vector. For example, a 8-bit priority encoder given the input 8'b10010000 would output 3'd4, because bit[4] is first bit that is high.

Build a 4-bit priority encoder. For this problem, if none of the input bits are high (i.e., input is zero), output zero. Note that a 4-bit number has 16 possible combinations.

// synthesis verilog_input_version verilog_2001
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
    
    always @(*)begin 
        if(in[0])begin
            pos<=2'd0;
        end
        else if(in[1])begin
            pos<=2'd1;
        end
        else if(in[2])begin
            pos<=2'd2;
        end
        else if(in[3])begin
            pos<=2'd3;
        end
        else begin
            pos<=2'd0;
        end
    end

endmodule

7.Priority encoder with casez

Build a priority encoder for 8-bit inputs. Given an 8-bit vector, the output should report the first bit in the vector that is 1. Report zero if the input vector has no bits that are high. For example, the input 8'b10010000 should output 3'd4, because bit[4] is first bit that is high.

From the previous exercise (always_case2), there would be 256 cases in the case statement. We can reduce this (down to 9 cases) if the case items in the case statement supported don't-care bits. This is what casez is for: It treats bits that have the value z as don't-care in the comparison.

For example, this would implement the 4-input priority encoder from the previous exercise:

always @(*) begin
    casez (in[3:0])
        4'bzzz1: out = 0;   // in[3:1] can be anything
        4'bzz1z: out = 1;
        4'bz1zz: out = 2;
        4'b1zzz: out = 3;
        default: out = 0;
    endcase
end

A case statement behaves as though each item is checked sequentially (in reality, it does something more like generating a giant truth table then making gates). Notice how there are certain inputs (e.g., 4'b1111) that will match more than one case item. The first match is chosen (so 4'b1111 matches the first item, out = 0, but not any of the later ones).

  • There is also a similar casex that treats both x and z as don't-care. I don't see much purpose to using it over casez.
  • The digit ? is a synonym for z. so 2'bz0 is the same as 2'b?0
  • // synthesis verilog_input_version verilog_2001
    module top_module (
        input [7:0] in,
        output reg [2:0] pos  );
        always @(*)begin
            casez(in)
                8'bzzzzzzz1:pos<=3'd0;
                8'bzzzzzz10:pos<=3'd1;
                8'bzzzzz100:pos<=3'd2;
                8'bzzzz1000:pos<=3'd3;
                8'bzzz10000:pos<=3'd4;
                8'bzz100000:pos<=3'd5;
                8'bz1000000:pos<=3'd6;
                8'b10000000:pos<=3'd7;
                default:pos<=3'd0;
            endcase
        end
        
    
    endmodule
    

8.Avoiding latches

Suppose you're building a circuit to process scancodes from a PS/2 keyboard for a game. Given the last two bytes of scancodes received, you need to indicate whether one of the arrow keys on the keyboard have been pressed. This involves a fairly simple mapping, which can be implemented as a case statement (or if-elseif) with four cases.

Your circuit has one 16-bit input, and four outputs. Build this circuit that recognizes these four scancodes and asserts the correct output.

To avoid creating latches, all outputs must be assigned a value in all possible conditions (See also always_if2). Simply having a defaultcase is not enough. You must assign a value to all four outputs in all four cases and the default case. This can involve a lot of unnecessary typing. One easy way around this is to assign a "default value" to the outputs before the case statement:

always @(*) begin
    up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
    case (scancode)
        ... // Set to 1 as necessary.
    endcase
end

This style of code ensures the outputs are assigned a value (of 0) in all possible cases unless the case statement overrides the assignment. This also means that a default: case item becomes unnecessary.

Reminder: The logic synthesizer generates a combinational circuit that behaves equivalently to what the code describes. Hardware does not "execute" the lines of code in sequence.

// 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  ); 
    
    always @(*)begin
        left<=1'b0;down<=1'b0;right<=1'b0;up<=1'b0;
        case(scancode)
            16'he06b:left<=1'b1;
            16'he072:down<=1'b1;
            16'he074:right<=1'b1;
            16'he075:up<=1'b1;
            default:begin
                left<=1'b0;down<=1'b0;right<=1'b0;up<=1'b0;
            end
        endcase   
    end
    
endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FPGA&SDR探索者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值