Teach Remote lab lessons

Teach lesson

Digital Logic on the DE1-SoC (4/6): Counters and shift registers

Students implement counters and shift registers in Verilog, observe sequential LED patterns, and explain clocked updates on hardware.

  • Altera DE1-SoC
  • 65 min
  • Undergraduate, introductory
  • English
  • Digital systems & FPGA
Altera DE1-SoC
Altera DE1-SoC

Learning Outcomes

  • Explain what a clock divider does and use it to make 50 MHz behavior visible to the eye and camera.

  • Build a 4-bit counter and read its value on the LEDs and on a 7-segment display.

  • Build a shift register and reason about register bit order versus physical LED order on the camera.

  • Recognize register-width pitfalls, such as a constant that does not fit the declared register.

Student activity preview

Activity Content

Preview only. In a class session, students can fill in responses and submit their work to the teacher.

1

From storage to motion: why we need a clock divider

10 min

In Lesson 3 you built circuits that remember a bit or a byte: a flip-flop and a parallel-load register. Each one updates its stored value on a clock edge. In this lesson you reuse that idea to build circuits that move on their own every clock edge: a counter that climbs 0, 1, 2, 3, … and a shift register that slides its bits sideways.

The board's clock is CLOCK_50, a 50 MHz square wave: 50,000,000 rising edges every second. If a counter advanced once per CLOCK_50 edge, it would run through all sixteen values of a 4-bit counter about three million times per second. You would see nothing but a steady blur of LEDs, and the remote camera — which captures only a handful of frames per second — would have no chance.

So we slow things down with a clock divider. A clock divider is itself just a counter: it counts CLOCK_50 edges and, when it reaches a chosen terminal count DIV, it toggles a slower signal called slow_clk and starts counting again. The bigger DIV is, the slower slow_clk runs. We then clock the *real* circuit (the 4-bit counter, the shift register) from slow_clk instead of from CLOCK_50.

As in every lesson, sequential designs must declare the clock themselves. The default leds_mirror.v template lists only switches, buttons, and LEDs and has no clock, so each module in this lesson adds input CLOCK_50; to its port list. The board signals are the same as always: SW[9:0] (a switch up is 1), KEY[3:0] (active-low: a pressed button reads 0), LEDR[9:0] (active-high: lit when driven 1), and HEX0HEX5 (active-low seven-segment, segment lit when driven 0).

A clock divider taking the fast 50 MHz CLOCK_50 on the left into a counter that counts up to a terminal value DIV; each time the counter reaches DIV it toggles a much slower slow_clk output on the right, turning millions of fast edges per second into roughly one slow edge per second that the eye and camera can follow.

A clock divider counts fast CLOCK_50 edges and toggles a much slower slow_clk only when it reaches the terminal count DIV. Increasing DIV makes slow_clk slower. The real circuit is clocked from slow_clk, so its motion becomes visible.

A 4-bit counter clocked directly at CLOCK_50 steps about 3 million times per second, while the remote camera captures only a few frames per second. What would you actually see on the LEDs, and why?

If you doubled the terminal count DIV (say from 33,333,333 to 66,666,666), about how would the counter's step *rate* change, and why? (One sentence.)

2

Exercise 2-C: a 4-bit counter with a clock divider

12 min

A counter is a register whose next value is simply its current value plus one. A 4-bit counter holds the values 0 through 15; after 15 it wraps back to 0 automatically, because 15 + 1 = 16 = 10000 in binary and only the low four bits are kept (0000). You do not need special wrap-around code — the register width does it for you.

The design below has two parts. The divider counts CLOCK_50 edges up to DIV and toggles slow_clk. The counter advances by one on every rising edge of slow_clk, and KEY[0] resets it to 0 (active-low: held down means reset). The count drives LEDR[3:0] in binary and also HEX0 as a single hexadecimal digit (09, then AF for 1015).

The timing, stated carefully. With DIV = 33,333,333, the divider toggles slow_clk after about 0.67 seconds (33,333,333 edges out of 50,000,000 per second is about two-thirds of a second). A counter step happens only on a rising edge of slow_clk, and a square wave has one rising edge per *full* period — one low-to-high plus one high-to-low. So the counter advances about once every 1.33 seconds, roughly 0.75 Hz. Watch for a new value a little more than once per second.

A register-width pitfall to notice. The divider register here is 27 bits wide. That is deliberate. A 26-bit register can only hold values up to 2^26 - 1 = 67,108,863. A divider value such as DIV = 100,000,000 simply does not fit in 26 bits, so a design using that width would be broken before it ran. With 27 bits the register holds up to 2^27 - 1 = 134,217,727, so both DIV = 100,000,000 (slower) and DIV = 8,333,333 (faster) fit and work. Always check that a constant fits the register you declared for it.

One honest caveat. Clocking a register directly from a divided slow_clk, as we do here, is a common teaching shortcut because it makes the idea concrete. In production designs you normally keep everything on the single fast CLOCK_50 and instead use a clock-enable: a one-cycle pulse that tells the register "update on this edge." That keeps the whole design on one real clock, which the synthesis tools analyze far better. We use the divided clock here only because it is the clearest way to *see* the counter move.

// Lesson 4 - Exercise 2-C: 4-bit counter with clock divider
// KEY[0]=reset (active-low). LEDR[3:0]=count, HEX0=count as a hex digit.
// The divider DIV sets the speed. With DIV=33,333,333 a rising slow_clk edge
// (one count step) happens about every 1.33 s. The register is 27 bits wide, so
// you can experiment with DIV = 8,333,333 (faster) or 100,000,000 (slower).
module leds_mirror(CLOCK_50, KEY, LEDR, HEX0);
    input        CLOCK_50;
    input  [3:0] KEY;       // KEY[0] = reset_n
    output [9:0] LEDR;
    output [6:0] HEX0;

    localparam [26:0] DIV = 27'd33_333_333;   // 27 bits: fits up to 134,217,727
    reg [26:0] div_count;
    reg        slow_clk;
    always @(posedge CLOCK_50) begin
        if (!KEY[0]) begin div_count <= 0; slow_clk <= 0; end
        else if (div_count == DIV) begin div_count <= 0; slow_clk <= ~slow_clk; end
        else div_count <= div_count + 1'b1;
    end

    reg [3:0] count;
    always @(posedge slow_clk or negedge KEY[0]) begin
        if (!KEY[0]) count <= 4'd0;
        else         count <= count + 1'b1;   // wraps 15 -> 0 automatically
    end

    assign LEDR[3:0] = count;
    assign LEDR[9:4] = 6'b0;

    function [6:0] seg;        // active-low {g,f,e,d,c,b,a}
        input [3:0] d;
        case (d)
            4'h0: seg=7'b1000000; 4'h1: seg=7'b1111001; 4'h2: seg=7'b0100100; 4'h3: seg=7'b0110000;
            4'h4: seg=7'b0011001; 4'h5: seg=7'b0010010; 4'h6: seg=7'b0000010; 4'h7: seg=7'b1111000;
            4'h8: seg=7'b0000000; 4'h9: seg=7'b0010000; 4'hA: seg=7'b0001000; 4'hB: seg=7'b0000011;
            4'hC: seg=7'b1000110; 4'hD: seg=7'b0100001; 4'hE: seg=7'b0000110; 4'hF: seg=7'b0001110;
            default: seg=7'b1111111;
        endcase
    endfunction
    assign HEX0 = seg(count);
endmodule

Suppose you used a 24-bit divider register instead. What is the largest value it can hold (show 2^24 − 1), and would DIV = 33,333,333 still fit? Explain in one sentence.

3

Run the counter, reset it, and change its speed

12 min

Build the counter on the real board, watch it climb, reset it, and then try the two other divider values.

  1. Open the DE1-SoC Verilog lab. The editor shows a file leds_mirror.v.

  2. Select all of the contents of leds_mirror.v and replace them with the 4-bit counter module above. Keep the module named leds_mirror. Note that it declares input CLOCK_50; — the template does not add the clock for you.

  3. Click Synthesize. If a guided tour covers the button, close or skip the tour first. Wait for it to finish (about 1-3 minutes). Fix any reported typo and Synthesize again. Quartus may report many timing warnings for this teaching divider; continue only when the final status says the build succeeded with 0 errors.

  4. Click Upload to FPGA and wait for the live board session and camera.

  5. Watch LEDR[3:0] and HEX0. A new value should appear a little more than once per second (about every 1.33 s). Confirm the binary LEDs match the hex digit on HEX0 (for example, when HEX0 shows C, LEDR[3:0] should read 1100).

  6. Press and hold KEY[0] to reset the count to 0, then release it and watch it climb again from 0.

  7. Change DIV to 8,333,333 (write 27'd8_333_333), Synthesize, Upload, and notice the counter runs noticeably faster. Then change DIV to 100,000,000 (write 27'd100_000_000), Synthesize, Upload, and notice it runs slower. Both fit because the register is 27 bits wide.

Using the default DIV = 33,333,333, watch the counter and record a few values as they appear. For each, write the binary LEDR[3:0] pattern and the character you see on HEX0 (0-9 then A-F). Then reset with KEY[0] and confirm the count returns to 0.

Observation # LEDR[3:0] (binary) HEX0 character Value in decimal (0-15)

Upload your evidence. Watch for a moment when HEX0 shows the digit C (decimal 12), capture a camera screenshot (LEDR[3:0] should read 1100 at the same time), and attach it below.

You ran the lab with DIV = 8,333,333 and with DIV = 100,000,000. Which value made the counter step faster, and using the two numbers, about how many times faster was it than the other?

Suppose you wanted to use a counter like this as a clock that stays accurate to within plus or minus one second over a full 24-hour day. Besides the counter itself, what would you need to get right? Name the key ingredient and say why it matters.

4

Modify: a 0-9 BCD counter

7 min

A plain 4-bit counter runs 015 and shows AF for the top six values. Many real displays want a single decimal digit instead: count 0, 1, 2, …, 9, then wrap straight back to 0. A counter that counts only 09 is called a BCD (binary-coded decimal) counter.

The change is small: instead of letting the 4-bit register wrap on its own at 15, you detect when the count reaches 9 and force the next value to 0. Replace the counter's always block with logic of this shape:

    reg [3:0] count;
    always @(posedge slow_clk or negedge KEY[0]) begin
        if (!KEY[0])           count <= 4'd0;
        else if (count == 4'd9) count <= 4'd0;   // wrap to 0 after 9
        else                    count <= count + 1'b1;
    end

Keep everything else (the divider, the LEDR/HEX0 assignments, the seg function) the same. Now HEX0 should cycle 0 through 9 and never show AF.

  1. Edit your counter module so the counter always block wraps to 0 after 9, as shown above.

  2. Synthesize, fix any typo, and Upload to FPGA.

  3. Watch HEX0 cycle 0, 1, 2, … 9, 0, 1, …. Confirm it never reaches A. Press KEY[0] to reset to 0 if you want to re-check from the start.

In the BCD counter, what is the value of count on the clock edge immediately after HEX0 shows 9, and which line of code forces that?

5

Exercise 2-D: an 8-bit shift register

9 min

A shift register is a row of flip-flops where, on every clock edge, each bit moves into its neighbor and a brand-new bit enters at one end. It is the heart of how serial links work: protocols like UART, SPI, and I2C send data one bit at a time, and a shift register is what assembles those bits into a byte (or takes a byte apart bit by bit).

The design below is an 8-bit register shreg[7:0]. On each rising slow_clk edge, while shift is enabled, it does:

        shreg <= {shreg[6:0], SW[0]};   // shift LEFT, bring SW[0] into bit 0

Read that right-hand side carefully: it takes the low seven bits shreg[6:0], moves them up one position, and drops SW[0] into bit 0. Every bit moves toward the high end, so this is a left shift. The controls are SW[0] = the serial bit coming in, KEY[0] = reset (active-low), and KEY[1] = shift-enable (active-low — hold KEY[1] down to make it shift on each tick).

The expected pattern. Reason it through instead of trusting the camera orientation. Start from 0000_0000. Set SW[0] = 1 and let four ticks pass: the register fills from bit 0 upward, giving 0000_0001, then 0000_0011, 0000_0111, 0000_1111. Now set SW[0] = 0 and let four more ticks pass: zeros now enter at bit 0 and push the ones up and out, giving 0001_1110, 0011_1100, 0111_1000, and finally 1111_0000. So after four 1s followed by four 0s, shreg[7:0] is 1111_0000.

Bit value versus what you see. Record both the binary register value *and* what the camera shows. The physical order of the LEDs on the board may look mirrored relative to how you write the bits (shreg[7] on one side, shreg[0] on the other), so the lit LEDs can appear flipped left-to-right compared to the number 1111_0000. That is a labeling/orientation effect, not a logic error — which is exactly why you write down the value you computed alongside the photo.

// Lesson 4 - Exercise 2-D: 8-bit shift register (shifts left)
// SW[0]=serial data in, KEY[0]=reset (active-low), KEY[1]=shift-enable (active-low, hold to shift).
// LEDR[7:0]=register contents. shreg <= {shreg[6:0], SW[0]} shifts LEFT and brings SW[0] into bit 0.
// After four ticks with SW0=1 then four ticks with SW0=0, the value shreg[7:0] is 1111_0000.
// On the board the lit LEDs may look mirrored, so always record BOTH the bit value and what you see.
module leds_mirror(CLOCK_50, KEY, SW, LEDR);
    input        CLOCK_50;
    input  [3:0] KEY;       // KEY[0]=reset_n, KEY[1]=shift_enable_n
    input  [9:0] SW;        // SW[0] = serial data in
    output [9:0] LEDR;

    localparam [26:0] DIV = 27'd33_333_333;
    reg [26:0] div;
    reg        slow_clk;
    always @(posedge CLOCK_50) begin
        if (!KEY[0]) begin div <= 0; slow_clk <= 0; end
        else if (div == DIV) begin div <= 0; slow_clk <= ~slow_clk; end
        else div <= div + 1'b1;
    end

    reg [7:0] shreg;
    always @(posedge slow_clk or negedge KEY[0]) begin
        if (!KEY[0])      shreg <= 8'h00;
        else if (!KEY[1]) shreg <= {shreg[6:0], SW[0]};   // shift left, bring in SW[0]
    end

    assign LEDR[7:0] = shreg;
    assign LEDR[9:8] = 2'b0;
endmodule

Starting from 0000_0000, with KEY[1] held, apply the input sequence SW[0] = 1, 1, 0, 1 over four ticks (oldest first). Work it out tick by tick and write shreg[7:0] after the four ticks.

6

March the bit across the LEDs

9 min

Build the shift register and watch a single lit LED march across the row, then watch zeros follow it.

  1. Replace the contents of leds_mirror.v with the 8-bit shift-register module above. Keep the module named leds_mirror. It declares input CLOCK_50;.

  2. Synthesize, fix any typo, and Upload to FPGA.

  3. Press and release KEY[0] once to reset the register to all zeros (no LEDs lit among LEDR[7:0]).

  4. Set SW[0] up (serial in = 1) and hold KEY[1] down. On each tick (about every 1.33 s) a new lit LED enters at one end and the lit ones march along. Let four ticks pass — you should have four LEDs lit (0000_1111).

  5. Now set SW[0] down (serial in = 0), keep holding KEY[1], and let four more ticks pass. Zeros now follow the ones across until the register reads 1111_0000.

  6. Release KEY[1] at any time to freeze the current pattern so it is easy to read and photograph.

Reset, then hold KEY[1]. Record the register value tick by tick: four ticks with SW0=1, then four ticks with SW0=0. Write shreg as eight bits (shreg[7] on the left, shreg[0] on the right). In the last column, note whether the lit LEDs on the camera look mirrored relative to the bits you wrote.

Tick # SW[0] in shreg[7:0] (8 bits) Camera: which LEDs lit / mirrored?

Upload your evidence. After four ticks of SW0=1 then four ticks of SW0=0, release KEY[1] to freeze the pattern (shreg = 1111_0000), capture a camera screenshot, note in your records whether the lit LEDs look mirrored, and attach it below.

Compare the binary value you computed (1111_0000) with the lit LEDs in your screenshot. If they appear mirrored, explain why that is a labeling/orientation effect and not a bug in the shift logic.

7

Modify: shift right, and a ring counter

6 min

Shift the other way. To make the register shift right instead of left — each bit moving toward the low end, with the new bit entering at the top (shreg[7]) — you change a single line. Replace the shift assignment with:

        else if (!KEY[1]) shreg <= {SW[0], shreg[7:1]};   // shift right, bring in SW[0] at the top

Only that one line changes. Now the incoming SW[0] lands in shreg[7] and the high seven bits shreg[7:1] move down into shreg[6:0].

Extension — a ring counter. A ring counter feeds the register's own output back into its input instead of taking a new external bit, so a fixed pattern circulates forever. Make a left-shifting ring that feeds the top bit shreg[7] back into bit 0, and initialize the register to 0000_0001 so exactly one lit LED chases around the row. Use a reset that loads 8'h01 (not 8'h00) and a shift line of the form shreg <= {shreg[6:0], shreg[7]};. Hold KEY[1] and watch one LED loop around endlessly.

  1. Change the single shift line in your module to the shift-right version above. Synthesize and Upload.

  2. Reset, hold KEY[1], set SW[0]=1 for four ticks then SW[0]=0 for four ticks, and confirm the bits now move the opposite direction across the LEDs.

  3. (Optional extension) Build the ring counter: reset loads 8'h01, and the shift line feeds shreg[7] back into bit 0. Hold KEY[1] and watch one lit LED circulate.

In the shift-right version shreg <= {SW[0], shreg[7:1]};, start from 0000_0000 and feed SW[0] = 1 for four ticks. Which end of the register fills, and what is shreg[7:0] after the four ticks?

In three sentences, explain: (1) what the clock divider did so you could see the counter and shift register move, (2) one concrete piece of evidence you captured (the counter value on HEX0, or the 1111_0000 shift pattern), and (3) one pitfall you watched out for (a constant not fitting the 26-bit register, or bit value versus mirrored LED order).