Teach Remote lab lessons

Teach lesson

Digital Logic on the DE1-SoC with VHDL (3/6): Memory -- flip-flops and registers

Students use VHDL clocked processes to build a D flip-flop and an 8-bit register, then verify memory behavior on real hardware.

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

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 VHDL signal assignment in clocked processes 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.

1

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 blink.vhd template lists only switches, buttons, and LEDs and has no clock, so you will add CLOCK_50 : in std_logic to the entity 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 CLOCK_50 : in std_logic. 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.)

2

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.

Timing diagram of a D flip-flop: a clock square wave, a data signal D that changes at arbitrary times, and the output Q which only changes to match D at each rising clock edge and holds that value flat until the next rising edge — illustrating sample-and-hold.

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 VHDL — two new ideas.

First, the clocked process. We describe edge-triggered behaviour with process(CLOCK_50) and if rising_edge(CLOCK_50) then: the signal assignments inside describe what each flip-flop captures on the rising edge, not instructions that run continuously.

Second, signal assignment <=. In VHDL, Q <= SW(0); is a signal assignment. Inside a clocked process it describes a flip-flop update at the clock edge. VHDL variables use := and update immediately inside a process; they can be useful, but they are not the beginner-safe way to describe several registers that should all sample together. Rule of thumb in this course: use signals and <= for stored values such as flip-flops, registers, counters, and FSM state.

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:

process(CLOCK_50)
begin
    if rising_edge(CLOCK_50) then
        if KEY(0) = '0' then Q <= TODO_RESET_VALUE;
        else                  Q <= TODO_D_INPUT;
        end if;
    end if;
end process;

Because KEY is active-low, the button reads 0 when pressed, so KEY(0) = '0' is true exactly while the reset button is held down.

Suppose you tried to update a stored signal using an immediate variable-style update instead of the signal assignment Q <= SW(0); inside a clocked process. Why is <= the right form for a flip-flop output in this lesson, and why do several registers need to sample together at the same clock edge?

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.

3

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) = not 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 not 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.

-- Student starter: fill the TODO lines before synthesizing.
-- Lesson 3 - Exercise 2-A: D flip-flop.
-- SW(0)=D, KEY(0)=reset (active-low reset), LEDR(0)=Q, LEDR(1)=not Q.
library ieee;
use ieee.std_logic_1164.all;

entity blink is
    port (
        CLOCK_50 : in  std_logic;
        KEY      : in  std_logic_vector(3 downto 0);
        SW       : in  std_logic_vector(9 downto 0);
        LEDR     : out std_logic_vector(9 downto 0)
    );
end entity blink;

architecture rtl of blink is
    signal Q : std_logic := '0';
begin
    process(CLOCK_50)
    begin
        if rising_edge(CLOCK_50) then
            if KEY(0) = '0' then Q <= TODO_RESET_VALUE;
            else                  Q <= TODO_D_INPUT;
            end if;
        end if;
    end process;

    LEDR(0) <= TODO_Q_OUTPUT;
    LEDR(1) <= TODO_NOT_Q_OUTPUT;
    LEDR(9 downto 2) <= (others => '0');
end architecture rtl;
  1. Open the DE1-SoC VHDL lab. The editor shows the file blink.vhd.

  2. Select all of the contents of blink.vhd and replace them with the D flip-flop starter above. Fill every TODO line, keep the entity named blink, and notice it declares CLOCK_50 : in std_logic — that line is required for any clocked design.

  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). If it reports an error, the most common cause is a missing CLOCK_50 in 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.

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

  5. Set SW(0) up (D=1). Confirm LEDR(0) (Q) is on and LEDR(1) (not Q) is off.

  6. Demonstrate reset: with SW(0) still up, press and hold KEY(0). Watch LEDR(0) turn off while the switch is unchanged — reset overrides the data. Release KEY(0) and confirm LEDR(0) comes back on.

Set SW(0) and KEY(0) as in each row and record what LEDR(0) (Q) and LEDR(1) (not 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) = not 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.

4

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 downto 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) = '0' 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.

-- Student starter: fill the TODO lines before synthesizing.
-- Lesson 3 - Exercise 2-B: 8-bit parallel-load register.
-- SW(7 downto 0)=data, KEY(0)=reset, KEY(1)=load. KEY inputs are active-low.
library ieee;
use ieee.std_logic_1164.all;

entity blink is
    port (
        CLOCK_50 : in  std_logic;
        KEY      : in  std_logic_vector(3 downto 0);
        SW       : in  std_logic_vector(9 downto 0);
        LEDR     : out std_logic_vector(9 downto 0)
    );
end entity blink;

architecture rtl of blink is
    signal Q : std_logic_vector(7 downto 0) := (others => '0');
begin
    process(CLOCK_50)
    begin
        if rising_edge(CLOCK_50) then
            if KEY(0) = '0' then
                Q <= TODO_RESET_VECTOR;
            elsif KEY(1) = '0' then
                Q <= TODO_LOAD_VALUE;
            end if; -- otherwise Q holds its previous value
        end if;
    end process;

    LEDR(7 downto 0) <= TODO_REGISTER_OUTPUT;
    LEDR(9 downto 8) <= (others => '0');
end architecture rtl;
  1. In blink.vhd, replace all contents with the 8-bit register starter above. Fill every TODO line, keep the entity named blink, and keep CLOCK_50 : in std_logic.

  2. Click Synthesize, wait for success, then click Upload to FPGA.

  3. Set the switches to SW(7 downto 0) = 10101010 (SW(7) is the most significant bit). The LEDs will not yet show this — nothing has been loaded.

  4. Load: press and release KEY(1). LEDR(7 downto 0) now shows 10101010. The register has captured the switches.

  5. See the memory: now change the switches to SW(7 downto 0) = 01010101 without touching KEY(1). Watch the LEDs — they do not change. The register is holding its stored value even though the inputs are different. This is memory.

  6. Update: press and release KEY(1) again. Now LEDR(7 downto 0) updates to 01010101.

  7. 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 downto 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 downto 0) set to Action on KEY LEDR(7 downto 0) observed

Upload your evidence. Capture a camera screenshot that proves the register is *holding*: LEDR(7 downto 0) reads 10101010 (the previously loaded value) while the on-screen switches SW(7 downto 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.

5

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 a VHDL clocked process with rising_edge(CLOCK_50) and signal assignments (<=), 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 process, so it only took effect on a rising clock edge. That is clean and predictable for these simple storage elements. Starting with the slow-clock designs in Lesson 4, and continuing in the finite-state-machine lessons (5 and 6), you will also see active-low asynchronous reset written as a process(..., KEY) with if KEY(0) = '0' then ... before the clock-edge branch; that reset forces the circuit to a safe state immediately, without waiting for the slow clock.

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.