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.
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.
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 HEX0–HEX5 (active-low seven-segment, segment lit when driven 0).
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.)
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 (0–9, then A–F for 10–15).
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);
endmoduleSuppose 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.
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.
Open the DE1-SoC Verilog lab. The editor shows a file
leds_mirror.v.Select all of the contents of
leds_mirror.vand replace them with the 4-bit counter module above. Keep the module namedleds_mirror. Note that it declaresinput CLOCK_50;— the template does not add the clock for you.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.
Click Upload to FPGA and wait for the live board session and camera.
Watch
LEDR[3:0]andHEX0. 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 onHEX0(for example, whenHEX0showsC,LEDR[3:0]should read1100).Press and hold
KEY[0]to reset the count to0, then release it and watch it climb again from0.Change
DIVto8,333,333(write27'd8_333_333), Synthesize, Upload, and notice the counter runs noticeably faster. Then changeDIVto100,000,000(write27'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.
Modify: a 0-9 BCD counter
7 min
A plain 4-bit counter runs 0–15 and shows A–F 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 0–9 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 A–F.
Edit your counter module so the counter
alwaysblock wraps to0after9, as shown above.Synthesize, fix any typo, and Upload to FPGA.
Watch
HEX0cycle0, 1, 2, … 9, 0, 1, …. Confirm it never reachesA. PressKEY[0]to reset to0if 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?
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;
endmoduleStarting 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.
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.
Replace the contents of
leds_mirror.vwith the 8-bit shift-register module above. Keep the module namedleds_mirror. It declaresinput CLOCK_50;.Synthesize, fix any typo, and Upload to FPGA.
Press and release
KEY[0]once to reset the register to all zeros (no LEDs lit amongLEDR[7:0]).Set
SW[0]up (serial in =1) and holdKEY[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).Now set
SW[0]down (serial in =0), keep holdingKEY[1], and let four more ticks pass. Zeros now follow the ones across until the register reads1111_0000.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.
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.
Change the single shift line in your module to the shift-right version above. Synthesize and Upload.
Reset, hold
KEY[1], setSW[0]=1for four ticks thenSW[0]=0for four ticks, and confirm the bits now move the opposite direction across the LEDs.(Optional extension) Build the ring counter: reset loads
8'h01, and the shift line feedsshreg[7]back into bit0. HoldKEY[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).