Teach lesson
Digital Logic on the DE1-SoC (3/6): Memory — flip-flops and registers
Students build D flip-flop and register circuits in Verilog, test clock and reset behavior, and distinguish combinational logic from stored state.
Learning Outcomes
Explain why sequential logic needs memory while combinational logic does not.
Describe a D flip-flop as sample-and-hold on the clock edge, with synchronous reset.
Build an 8-bit parallel-load register from D flip-flops sharing a clock and reset.
Use non-blocking assignment in clocked blocks and recognize active-low KEY inputs.
Student activity preview
Activity Content
Preview only. In a class session, students can fill in responses and submit their work to the teacher.
Why circuits need memory
10 min
Every design so far has been combinational: the LEDs were a pure function of the switches *right now*. Change a switch and the output changes the instant the signal propagates through the gates. A combinational circuit is stateless — it has no idea what the inputs were a moment ago, and it cannot remember anything.
But most useful systems need memory. A counter has to know its current value to produce the next one. A CPU has to hold operands and results between instructions. A traffic light has to know which colour is showing now to decide what comes next. None of that is possible with gates alone, because gates only ever reflect their present inputs.
Sequential logic is logic that remembers. Its output depends not only on the current inputs but also on stored state — what happened before. The building block that stores one bit of state is the flip-flop, and the timekeeper that tells every flip-flop *when* to update is the clock.
The DE1-SoC provides a free-running 50 MHz clock called CLOCK_50: a square wave that rises and falls 50,000,000 times per second. From this lesson on, your sequential designs must list it as an input. The default leds_mirror.v template lists only switches, buttons, and LEDs and has no clock, so you will add input CLOCK_50; to the module yourself — forgetting it is the most common error in sequential designs.
Which of these circuits is sequential (its output can depend on the past), rather than combinational?
The combinational designs in Lessons 1–2 listed only switches and LEDs, but a clocked design like a flip-flop must also list input CLOCK_50;. Why does a flip-flop or register need the clock as an input, and what tends to go wrong if you leave it out? (One or two sentences.)
The D flip-flop: sample-and-hold on the clock edge
11 min
A D flip-flop is the simplest memory element. It has a data input D, a clock input, and an output Q. Its entire rule is:
> On every rising edge of the clock, Q captures the value of D and then holds it until the next rising edge.
Between edges, nothing the input does matters — Q is frozen. That is sample-and-hold: the flip-flop samples D at the instant the clock goes from 0 to 1, then holds that sample steady. This is exactly how a circuit gains memory: Q can stay 1 long after D has gone back to 0, because it remembers the sample.
Sample-and-hold on the clock edge: Q updates only at each rising edge of CLOCK_50, copying whatever D is at that instant, then holds that value flat until the next rising edge. Changes to D between edges are ignored.
Writing this in Verilog — two new ideas.
First, the clocked block. We describe edge-triggered behaviour with always @(posedge CLOCK_50): the statements inside run once at each rising edge, not continuously. A signal assigned inside such a block must be declared reg (it holds a value between edges).
Second, non-blocking assignment <=. Inside a clocked block, always use <=, not =. The <= operator means "schedule all the right-hand sides to be sampled now, then update the left-hand sides together at the edge" — which is exactly how real flip-flops behave (they all sample at once). Using = (blocking) inside a clocked block is a classic Verilog bug: the assignments take effect in sequence within the edge, so one flip-flop can see another's *new* value instead of its old one, producing hardware that does not match your intent. Rule of thumb: <= in clocked blocks, = only in combinational assign/always @(*).
Synchronous reset. We also want a way to force Q back to a known value. A synchronous reset is checked *inside* the clocked block, so it only takes effect on a clock edge:
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= 1'b0; // synchronous reset, evaluated at the edge
else Q <= SW[0]; // otherwise capture D
end
Because KEY is active-low, the button reads 0 when pressed, so !KEY[0] is true exactly while the reset button is held down.
Suppose you wrote Q = SW[0]; with a blocking = (instead of <=) inside always @(posedge CLOCK_50). Which operator should you have used, and what can go wrong when several flip-flops in one clocked block update together with =?
Suppose D pulses to 1 only briefly *between* two rising clock edges and is back to 0 before the next edge. Does Q ever become 1? Explain your answer using the sample-and-hold rule.
Exercise 2-A — D flip-flop (reset in action)
12 min
Build a single D flip-flop on the real board. The wiring is SW[0] = D, KEY[0] = reset (active-low), LEDR[0] = Q, and LEDR[1] = ~Q.
Read this before you run it — important. At 50 MHz, Q follows D 50,000,000 times per second, far faster than your eye or the camera can ever see. So even though this circuit *does* store D, you cannot *watch* it store: flip SW[0] and LEDR[0] appears to follow instantly. That is why this exercise really demonstrates reset, not storage. The behaviour you will actually observe:
- With SW[0] up (D=1), LEDR[0] is on (and LEDR[1], showing ~Q, is off).
- Hold KEY[0] (pressed reads 0, so reset is active): LEDR[0] goes off even though SW[0] is still up. The reset wins over the data.
- Release KEY[0]: LEDR[0] follows SW[0] again.
You will *see* memory in the next exercise (2-B). For now, focus on the reset overriding the data input.
// Lesson 3 - Exercise 2-A: D flip-flop
// SW[0]=D, KEY[0]=reset (active-low), LEDR[0]=Q, LEDR[1]=~Q.
// Note: at 50 MHz, Q follows D faster than the eye/camera can see, so the
// clearly visible behavior here is RESET. Storage is demonstrated in 2-B.
module leds_mirror(CLOCK_50, KEY, SW, LEDR);
input CLOCK_50;
input [3:0] KEY; // KEY[0] = reset_n (active-low)
input [9:0] SW; // SW[0] = D
output [9:0] LEDR;
reg Q;
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= 1'b0; // synchronous reset: takes effect on the clock edge
else Q <= SW[0]; // capture D on every rising clock edge
end
assign LEDR[0] = Q;
assign LEDR[1] = ~Q;
assign LEDR[9:2] = 8'b0;
endmoduleOpen the DE1-SoC Verilog lab. The editor shows the file
leds_mirror.v.Select all of the contents of
leds_mirror.vand replace them with the D flip-flop module above. Keep the module namedleds_mirror. Notice it now declaresinput CLOCK_50;— that line is required for any clocked design.Click Synthesize. If a guided tour covers the button, close or skip the tour first. Wait for it to finish (about 1-3 minutes). If it reports an error, the most common cause is a missing
CLOCK_50in the port list or a missing semicolon; fix it and Synthesize again. 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.
Set
SW[0]up (D=1). ConfirmLEDR[0](Q) is on andLEDR[1](~Q) is off.Demonstrate reset: with
SW[0]still up, press and holdKEY[0]. WatchLEDR[0]turn off while the switch is unchanged — reset overrides the data. ReleaseKEY[0]and confirmLEDR[0]comes back on.
Set SW[0] and KEY[0] as in each row and record what LEDR[0] (Q) and LEDR[1] (~Q) show on the camera (1 = lit, 0 = off). Remember KEY is active-low: pressed = 0, released = 1.
| SW[0] (D) | KEY[0] (0=pressed, 1=released) | LEDR[0] = Q | LEDR[1] = ~Q |
|---|---|---|---|
What is the difference between this flip-flop and a plain wire from SW[0] to LEDR[0]? Answer in terms of memory.
This design uses a synchronous reset. Point to the line of code that performs the reset, and explain what "synchronous" means about *when* the reset takes effect. (You will meet asynchronous reset in Lesson 5.)
Why does LEDR[1] always show the opposite of LEDR[0]?
Exercise 2-A really does store D, yet on the camera you could only see the reset working, never the storing. Using the 50 MHz clock rate, explain why the storing is invisible while the reset is clearly visible.
Exercise 2-B — 8-bit parallel-load register (memory you can see)
12 min
A register is just N D flip-flops side by side, all sharing the same clock and the same reset. An 8-bit register stores 8 bits. A parallel load copies all 8 input bits into the register at once on a clock edge — every flip-flop captures its own data bit simultaneously.
The trick that makes memory visible here is the load control. Instead of capturing the data on *every* edge (as the bare flip-flop did), this register only captures when you tell it to. So you can change the switches all you like and the stored value stays put — until you load. The wiring is SW[7:0] = data, KEY[0] = reset (active-low), KEY[1] = load (active-low).
Because KEY is active-low, you press and release KEY[1] to load: pressing makes KEY[1] read 0, and the code tests !KEY[1] to detect that. Note KEY[1] is *sampled on the clock*, not a hardware-debounced clock signal — the design simply checks the button's level at each rising edge, which is fine for this exercise.
// Lesson 3 - Exercise 2-B: 8-bit parallel-load register
// SW[7:0]=data, KEY[0]=reset (active-low), KEY[1]=load (active-low). LEDR[7:0]=stored value.
// Because KEY is active-low, "press and release KEY[1]" loads the switches; the code tests !KEY[1].
module leds_mirror(CLOCK_50, KEY, SW, LEDR);
input CLOCK_50;
input [3:0] KEY; // KEY[0]=reset_n, KEY[1]=load_n
input [9:0] SW; // SW[7:0] = data to load
output [9:0] LEDR;
reg [7:0] Q;
always @(posedge CLOCK_50) begin
if (!KEY[0]) Q <= 8'h00; // reset overrides load
else if (!KEY[1]) Q <= SW[7:0]; // parallel load while KEY[1] is held
// else: hold (Q keeps its value even when the switches change)
end
assign LEDR[7:0] = Q;
assign LEDR[9:8] = 2'b0;
endmoduleIn
leds_mirror.v, replace all contents with the 8-bit register module above. Keep the module namedleds_mirrorand keepinput CLOCK_50;.Click Synthesize, wait for success, then click Upload to FPGA.
Set the switches to
SW[7:0] = 10101010(SW[7]is the most significant bit). The LEDs will not yet show this — nothing has been loaded.Load: press and release
KEY[1].LEDR[7:0]now shows10101010. The register has captured the switches.See the memory: now change the switches to
SW[7:0] = 01010101without touchingKEY[1]. Watch the LEDs — they do not change. The register is holding its stored value even though the inputs are different. This is memory.Update: press and release
KEY[1]again. NowLEDR[7:0]updates to01010101.Reset: press and release
KEY[0]. All eight LEDs go off (Q = 00000000), regardless of the switches.
Carry out the load sequence and record the 8 LEDs (LEDR[7:0]) at each stage. The key observation is the row 'switches changed, no load': the stored value must stay the same as the row before it.
| Stage | SW[7:0] set to | Action on KEY | LEDR[7:0] observed |
|---|---|---|---|
Upload your evidence. Capture a camera screenshot that proves the register is *holding*: LEDR[7:0] reads 10101010 (the previously loaded value) while the on-screen switches SW[7:0] are set to a different pattern such as 01010101 and you have not pressed KEY[1]. Show both the held LEDs and the different switch positions, and attach it below.
In step 5 you changed the switches but the LEDs did not move. Explain why, using the words "store" and "load".
If you held both reset (KEY[0]) and load (KEY[1]) at the same moment of a clock edge, what value would the register take, and why? Point to the line of code that decides this.
What is the practical purpose of a parallel-load register inside a CPU? Give one concrete example of what such a register might hold.
What you built, and a note on reset styles
8 min
You built the two foundations of all memory in digital systems: the D flip-flop (one bit, sample-and-hold on the clock edge) and the register (many flip-flops loaded in parallel, holding their value until told otherwise). You used always @(posedge CLOCK_50) with non-blocking <=, and you saw an active-low KEY act as a control input.
A note carried forward — two styles of reset. Both designs in this lesson used a synchronous reset: the reset was checked *inside* the clocked block, so it only took effect on a rising clock edge. That is clean and predictable, and it is what you will keep using for the simple storage elements in this lesson and the next. However, the finite-state-machine lessons (5 and 6) will switch to an active-low asynchronous reset (written as always @(posedge CLOCK_50 or negedge KEY[0])), which forces the circuit to a safe state immediately, without waiting for a clock edge — useful for getting a stuck state machine back under control on the board. The detailed difference, and why FSMs prefer the asynchronous style, will be explained there. For now, just know the two styles exist and that this lesson deliberately used the synchronous one.
Which sequence matches what you did for each exercise in this lesson?
In three or four sentences: explain how Exercise 2-B *proved* that the register has memory (cite your data), why Exercise 2-A could only demonstrate reset and not storage, and name one place where active-low KEY polarity mattered in your steps.