Sequential circuits#

Learning goals#

  • Apply basic features of the language to describe sequential logic

Introductory problem#

Implement a circuit with the following requirements:

  1. Create a 4-bit counter that continuously counts from 0 to 15.

  2. The counter must work at 1 Hz

  3. The circuit must have an interface similar to:

module cntr #(
    int N = 4
) (
    input clk,
    output logic [N-1:0] o
);
endmodule

Implementation on the board#

Use the least-significant LEDs on the board as counter output.

Tasks#

Read section 5 of SV Guide.

Quiz#

TODO

Mini-lecture#

Processes#

Structured procedures#

Structured procedures
  • initial

  • always procedures

    • always

    • always_comb

    • always_latch

    • always_ff

  • final

  • task

  • function

  • initial procedure executed:

    • before always

    • only once

  • final executed after $finish

  • We should prefer the specific always_*

    • _ff stands for flip-flop. For describing sequential logic

    • _comb stands for combinational. For describing combinational logic

module tb;
  initial $display("initial");
  always #1 $display("always");
  final $display("final");

  initial begin
    #5 $finish;
  end
endmodule
initial
always
always
always
always
- tb.sv:7: Verilog $finish
always
final

Block statements#

Timing controls#

Procedural timing controls

We can advance the simulation time using the following methods:

  • delay control using #

  • event control using @

  • wait statement: similar to @ + while

Delay control#

a procedural statement following a delay control:

#delay_in_time_units statement;

For example a clock generator:

module tb;
  logic clk = 1;

  always #1 clk ^= 1;

  initial begin
    $dumpfile("signals.fst");
    $dumpvars();
    #10 $finish;  // Otherwise our simulation takes forever
  end
endmodule

Exercise 20

What is the frequency of clk in the previous module?

Event control#

module tb;
  logic clk = 1;
  integer edge_counter1 = 0, edge_counter2 = 0;

  assign #1 clk = !clk;

  initial forever @(edge clk)++edge_counter1;
  // Same:
  always_ff @(edge clk)++edge_counter2;


  initial begin
    $dumpfile("signals.fst");
    $dumpvars();
    #10 $finish;
  end
endmodule

Solution for the introductory problem#

Clock divider#

Listing 7 code/clkdiv/tb.sv#
module tb #(
    int unsigned DIVIDE_BY = 6
);
  logic clk = 1, o;
  always #1 clk = !clk;

  clkdiv #(DIVIDE_BY) clk_div_i (
      .i(clk),
      .o(o)
  );

  initial begin
    $dumpfile("signals.fst");
    $dumpvars();
    #(DIVIDE_BY * 10) $finish;
  end
endmodule
Listing 8 code/clkdiv/clkdiv.sv#
module clkdiv #(
    int unsigned DIVIDE_BY = 100e6
    // Must be divisible by two
) (
    input i,
    output logic o = 0
);
  // We toggle at DIVIDE_BY/2
  logic [$clog2(DIVIDE_BY/2)-1:0] cntr_for_toggling;

  always_ff @(posedge i) begin
    if (cntr_for_toggling != $bits(cntr_for_toggling)'(DIVIDE_BY / 2 - 1))
      cntr_for_toggling <= cntr_for_toggling + 1;
    else begin
      o ^= 1;
      cntr_for_toggling <= 0;
    end
  end
endmodule

Counter#

tb.sv
module tb #(
    int N = 4
);
  logic clk = 1;
  logic [N-1:0] o;

  assign #1 clk = !clk;

  cntr #(N) cntr_i (.*);

  initial begin
    $dumpfile("signals.fst");
    $dumpvars();
    #(2 ** (N + 1)) $finish;
  end
endmodule
cntr.sv
module cntr #(
    int N = 4
) (
    input clk,
    output logic [N-1:0] o
);
  always_ff @(posedge clk)++o;
endmodule

Putting clock divider and counter together#

Listing 9 code/counter-1hz/tb.sv#
module tb #(
    int CNTR_WIDTH = 4,
    int CLK_FREQ = 6,
    localparam int NLed = CNTR_WIDTH
);
  logic clk = 1;
  logic [NLed-1:0] led;

  assign #1 clk = !clk;

  top #(
      .CNTR_WIDTH(CNTR_WIDTH),
      .CLK_FREQ  (CLK_FREQ)
  ) cntr_i (
      .*
  );

  initial begin
    $dumpfile("signals.fst");
    $dumpvars();
    #(2 ** CNTR_WIDTH * CLK_FREQ + 1) $finish;
  end
endmodule
Listing 10 code/counter-1hz/top.sv#
module top #(
    int CNTR_WIDTH = 4,
    int CLK_FREQ = 100e6,
    localparam int NLed = CNTR_WIDTH
) (
    input clk,
    output [NLed-1:0] led
);
  logic clk_divided;
  clkdiv #(CLK_FREQ) clkdiv_i (
      .i(clk),
      .o(clk_divided)
  );
  cntr #(CNTR_WIDTH) cntr_i (
      .clk(clk_divided),
      .o  (led)
  );
endmodule

Homework#

Exercise 21

Implement a configurable clock divider with the following interface:

Listing 11 code/clkdiv_dyn/clkdiv.sv#
module clkdiv (
    input i,
    integer unsigned divide_by,
    // If 0 and 1, then output permanently low and high, respectively.
    // Must be at least 2 for creating a divided clock.

    output logic o
);

Exercise 22

Seven segment display is often used as a low-cost and simple alternative to LCD screens. It is useful to display numbers, e.g., the current temperature. Implement a seven segment display controller.

Read this page to get an idea how to drive a seven segment display.

Requirements:

  1. Use the following files and templates.

  2. The controller supports only unsigned integer input.

  3. Understand what the provided code does.

  4. Complete the array bcd2seg in seven_segment_controller_pkg.

  5. Complete the always_ff block in seven_segment_controller.

  6. Verify seven_segment_controller using seven-segment-controller/tb. Understand what the assert lines do.

  7. Implement a module called seven_segment_counter_test_boolean for the board using seven_segment_controller that counts up to 9999 ms on a 4 digit seven segment display. The second seven segment display displays the same values as the other one. Use the template seven_segment_controller_test_boolean.

  8. You can use the clock divider that we implemented before.

  9. Digit and segment signals must be active-high in seven_segment_controller. If the board where the circuit will be deployed requires active-low signals, the signals must be negated on the top layer.

  10. Verify your module for the board implementation using seven_segment_counter_test_boolean/tb. Understand what wait statement does.

Notes:

  1. The package seven_segment_controller_pkg contains types and constants which are useful for the implementation and the module interface, respectively. You don’t have to augment the package.

  2. The files below make use of many parameters. In the beginning you may also work with constant numbers by assuming a 4 digit and 7 segment display if you find it easier.

Listing 12 code/seven-segment-controller/seven_segment_controller_pkg.sv#
package seven_segment_controller_pkg;
  localparam int unsigned
    DigitCountLg2 = 2,
    DigitCount = 2 ** DigitCountLg2,
    SegmentCount = 7,
    MaxReprNumber = 10 ** DigitCount - 1,
    BcdCount = $clog2(
      10
  );

  typedef logic [BcdCount-1 : 0] bcd_t;
  // Single BCD digit

  typedef logic [$bits(bcd_t) * DigitCount - 1 : 0] bcds_t;
  // Many BCD digits

  typedef logic [$clog2(MaxReprNumber) - 1 : 0] number_t;
  typedef logic [SegmentCount-1 : 0] segment_t;

  segment_t bcd2seg[10] = '{
      0: 'b0111111,
  };  //   gfedcba
  // Converts a BCD to its seven segment representation.
  //  a
  // f b
  //  g
  // e c
  //  d

  function automatic bcds_t bin2bcd(number_t bin);
    // Converts a binary digit to its BCD representation.
    // Uses double-dabble algorithm (add 3 if greater than 4, then shift).
    // https://en.wikipedia.org/wiki/Double_dabble

    bcds_t bcds = '{default: 0};
    for (int i = 0; i < $bits(bin); ++i) begin
      // Check every BCD digit if > 4
      for (int j = 0; j < DigitCount; ++j) begin
        // $displayh(" i: ", i, " j: ", j,
        //     " bcds: ", bcds[BcdCount*(j+1)-1 -:BcdCount]);
        // For debugging
        if (bcds[BcdCount*(j+1)-1-:BcdCount] > 4) bcds[BcdCount*(j+1)-1-:BcdCount] += 3;
      end
      // Shift
      bcds = {bcds[$left(bcds)-1 : 0], bin[$left(bin)-i]};
    end
    return bcds;
  endfunction
endpackage

Listing 13 code/seven-segment-controller/seven_segment_controller.sv#
// Seven segment controller for unsigned integers
// - Uses only seven segments without dot or colon.
// - Seven segment signals are shared among digits.
module seven_segment_controller
  import seven_segment_controller_pkg::*;
(
    input clk,
    rst,
    input number_t number,
    output logic [DigitCount - 1 : 0] digit,  // Selects currently active digit
    output segment_t segment  // Currently active segments
);

logic[DigitCountLg2 - 1 : 0] digit_addr;
// Digit address stores the current digit id which is being illuminated. For
// example 1 would be the second least significant BCD digit. In every clock
// cycle the digit address is incremented which in turn determines which
// segments to illuminate dependent on the input `number`.

always_ff @(posedge clk, posedge rst) begin
    digit_addr <= rst ? 0 : digit_addr + 1;
    digit <=
                   };
    // `digit` is initialized with 1 and continuously shifted to the left.
    // If `digit` would be created combinationally using `digit_addr`, then
    // the more significant digits (e.g., 3 and 4) will be faded out, because
    // the more significant digit signals will be less active compared to the
    // less significant signals. The address logic requires more time to
    // propagate to the more significant bits.
end

assign segment = bcd2seg[
    { bin2bcd(number) }
        [BcdCount * digit_addr +: BcdCount]
                        ];
endmodule

Listing 14 code/seven-segment-controller/tb.sv#
module tb;
  import seven_segment_controller_pkg::*;
  logic clk = 0, rst;
  logic [$clog2(MaxReprNumber) -1 : 0] number;
  logic [DigitCount -1 : 0] digit;
  logic [SegmentCount -1 : 0] segment;
  seven_segment_controller dut (.*);

  /// Test `bin2bcd`
  bcds_t number_to_bcd_map[type(dut.number)] = '{
      4: {4'd0, 4'd0, 4'd0, 4'd4},
      1234: {4'd1, 4'd2, 4'd3, 4'd4},
      9999: {4'd9, 4'd9, 4'd9, 4'd9}
  };
  initial begin
    foreach (number_to_bcd_map[number]) begin
      $display("Testing %d", number);
      assert (dut.bin2bcd(number) == number_to_bcd_map[number]);
    end
  end
  ///

  /// Test digit and segment signals
  always #1 clk ^= 1;

  initial begin
    rst = 1;
    #2 rst = 0;

    // Check the number 4's seven segment representation
    #10 number = 4;
    wait (digit == 'b1000);
    assert (segment == bcd2seg[0]);
    wait (digit == 'b0100);
    assert (segment == bcd2seg[0]);
    wait (digit == 'b0010);
    assert (segment == bcd2seg[0]);
    wait (digit == 'b0001);
    assert (segment == bcd2seg[4]);

    #10 $finish;
  end
  ///

  initial begin
    $dumpfile("signals.fst");
    $dumpvars;
    #1000 $display("Timeout");
    $finish;
  end
endmodule

Listing 15 code/seven-segment-counter-test-boolean/seven_segment_counter_test_boolean.sv#
import seven_segment_controller_pkg::*;
module seven_segment_counter_test_boolean #(
    int unsigned CLKDIV_FOR_1MS = 100e6 / 1000,  // 1 ms
    int unsigned MAX_NUMBER = 9999
) (
    input clk,
    input [3:0] btn,
    output [3:0] D0_AN,
    D1_AN,
    output [7:0] D0_SEG,
    D1_SEG
);

Listing 16 code/seven-segment-counter-test-boolean/tb.sv#
module tb;

  logic clk;
  logic [3:0] btn;
  logic [3:0] D0_AN, D1_AN;
  logic [7:0] D0_SEG, D1_SEG;

  seven_segment_counter_test_boolean #(.CLKDIV_FOR_1MS(4)) dut (.*);

  always #1 clk ^= 1;
  initial begin
    btn = 1;  // Reset
    #2 btn = 0;

    // Check the least significant digit of 8, where every segment must be
    // illuminated. Note that both D*_AN and D*_SEG are active-low.
    wait (dut.cntr == 8 && D0_AN == 4'b1110);
    // Wait for counter to reach 8 and the D0_AN to select the
    // least-significant digit.
    assert (D0_SEG[6:0] == ~7'b1111111);
    #100 $finish;
  end

  initial begin
    $dumpfile("signals.fst");
    $dumpvars;

    #200 $display("Timeout");
    $finish;
  end
endmodule