← Back to Course
# Lab: Sequential Logic ## CS 315 Computer Architecture --- ## Learning Objectives - Distinguish **combinational** (no memory) from **sequential** logic (state) - Read a **clock waveform**: period, frequency, rising/falling edges - Build an **SR latch** from cross-coupled NOR gates - Construct a **D latch** and a **D flip-flop** - Add **CLR** and **EN** controls to make a **1-bit register** - Compose 1-bit registers into an **N-bit register** - Wire a register + adder into a **counter** --- ## The Storage Hierarchy
Goal for today:
climb this hierarchy step by step
```text SR latch → D latch → D flip-flop → 1-bit register (+ CLR, EN) → N-bit register (splitter + merger) → counter (register + adder + constant 1) ``` The **counter** is the concrete target: holds a number and adds 1 every clock cycle. --- ## Combinational vs. Sequential Logic | Property | Combinational | Sequential | |----------|--------------|------------| | Feedback | No (DAG) | Yes | | Memory | None | State | | Output depends on | Current inputs only | Inputs + stored state | | Examples | Adder, MUX, decoder | Counter, register, CPU |
A sequential circuit:
state → combinational logic → state
--- ## The Sequential Circuit Loop
flowchart LR CLK([CLK]) --> STATE STATE["State\n(registers / latches)"] --> COMB["Combinational\nLogic"] COMB -- "next state" --> STATE
- **State** = registers, program counter - **Combinational logic** = ALU, adders, MUXes, decoders - **Clock** paces the whole machine forward one step at a time --- ## The Clock ```text cycle period |<----------->| 1 ___ ____ ____ | | | | | 0 |____| |____| |____ → time ^ ^ rising falling edge edge ``` | Term | Meaning | |------|---------| | **Period** | Time for one full cycle | | **Frequency** | Cycles/second (Hz). 1 GHz = 10⁹ Hz | | **Rising edge** | Transition 0 → 1 | | **Falling edge** | Transition 1 → 0 | --- ## Why Edges Matter
Storage elements update at a single, well-defined
instant
(the rising edge), not over the entire high phase. This keeps all elements in the chip synchronized.
- **Level-sensitive** (latch): transparent the whole time CLK is high - **Edge-triggered** (flip-flop): captures data at exactly one moment
Design rule: use
one
top-level clock source. Never route the clock through extra logic.
--- ## SR Latch: Storing One Bit Two cross-coupled NOR gates create a **feedback loop** that sustains a value: ```text R ---+----[ NOR ]----+--- Q | | +---[ NOR ]----+--- Q-bar S ---+ ``` | R | S | Q | Q-bar | Meaning | |---|---|---|-------|---------| | 0 | 0 | hold | hold | **Hold** current value | | 0 | 1 | 1 | 0 | **Set** Q = 1 | | 1 | 0 | 0 | 1 | **Reset** Q = 0 | | 1 | 1 | X | X | **Forbidden** | --- ## SR Latch: The Memory Property Trace: start Q = 0, then apply S = 1, then S = 0: ```text R S | Q Q-bar | Action 0 0 | 0 1 | hold (initial 0) 0 1 | 1 0 | SET → Q becomes 1 0 0 | 1 0 | HOLD → stays 1 ← memory! 1 0 | 0 1 | RESET → Q = 0 0 0 | 0 1 | HOLD → stays 0 ```
The
R=0, S=0
row after a SET keeps Q = 1 indefinitely. This is the stored bit.
--- ## SR Latch: Key Points - Behaves like one cell of **static RAM (SRAM)** - Holds value continuously through self-sustaining signal flow — no refresh needed (unlike DRAM) - **Power-on state is undefined** — always provide a CLR signal for initialization
Never assert R = 1 and S = 1 simultaneously. Both outputs are forced to 0; if both then drop to 0, the latch can oscillate unpredictably.
--- ## D Latch: Clocked Storage Problem with SR latch: two inputs, a forbidden state, and no notion of "update now". **D latch** solution: derive S and R from a single data input D and CLK: - `S = D AND CLK` - `R = (NOT D) AND CLK` Because S and R come from D and NOT D, they can **never both be 1**. | CLK | D | Effect | |-----|---|--------| | 0 | x | **Hold** — latch ignores D | | 1 | 0 | Reset → Q = 0 | | 1 | 1 | Set → Q = 1 | --- ## D Latch: Transparency When CLK = 1, the latch is **transparent**: Q follows D instantly. When CLK = 0, Q holds its last value. ```text D ___________ ____________ | | | ____| |___| CLK __ __ __ __ | | | | | | | | ____| |__| |__| |__| |____ Q follows D during high pulses only ```
Problem: if D wiggles while CLK is high, Q wiggles too. Dangerous in feedback loops!
--- ## D Flip-Flop: Edge-Triggered Storage **Two D latches in series** (master-slave) with **opposite** clock phases:
flowchart LR D --> M["Master Latch\n(CLK = NOT CLK)"] M --> S["Slave Latch\n(CLK = CLK)"] S --> Q CLK([CLK]) -- "direct" --> S CLK -- "inverted" --> M
- **CLK low**: master is open (tracks D), slave is closed (holds output) - **Rising edge**: master closes (freezes D), slave opens (copies to Q) - **CLK high**: master closed, slave passes frozen value --- ## D Flip-Flop: Capture at the Rising Edge ```text D _______ ___________ _______ | | | | | ____| |___| |___| CLK /| /| /| /| / | / | / | / | ____/ |__/ |__/ |__/ |__ ^ ^ ^ ^ sample D at each rising edge only Q updates only at the rising edge ```
The triangle (▷) on a clock input in a schematic means
edge-triggered
.
--- ## Why Edge-Triggering Saves the Counter With a flip-flop: 1. Rising edge: register samples D, Q becomes count+1 2. Q is now **stable** for the entire cycle 3. Adder sees stable input, computes count+2 for next cycle 4. Next rising edge: captures count+2 No races. One clean increment per cycle.
A D latch in a feedback loop would let the value race around multiple times per cycle. A flip-flop prevents this.
--- ## 1-Bit Register: Adding CLR A bare flip-flop captures D on **every** rising edge. A register needs control. **CLR (clear)**: force Q to 0, regardless of D, for known initialization. OR the CLR signal into the reset path: ```text R path = ((NOT D) AND CLK) OR CLR ``` When CLR = 1, R is forced high → latch resets → Q = 0. --- ## 1-Bit Register: Adding Enable **EN (enable)**: only update when enabled; otherwise hold current value. Use a **2-input MUX** on the data path: ```text +-------+ D ---->|0 | | MUX |----> [D flip-flop w/ CLR] ---+---> Q Q ---->|1 | | +-------+ | ^ | EN Q feeds back -+ ``` - EN = 1: MUX selects D → load new value - EN = 0: MUX selects Q → register reloads itself (hold) --- ## 1-Bit Register: Truth Table Inputs: D, CLK, CLR, EN. Assume current Q = 1, D = 0. | EN | CLR | Q after edge | Reason | |----|-----|-------------|--------| | 0 | 0 | 1 | HOLD: EN off, Q feeds back | | 1 | 0 | 0 | LOAD: capture D | | x | 1 | 0 | CLEAR wins — always |
CLR has highest priority. When CLR = 0, EN selects between load and hold.
--- ## N-Bit Register Eight 1-bit registers in parallel, sharing CLK, CLR, EN:
flowchart LR D["D (8-bit)"] --> SP["Splitter\n8→1×8"] SP --> R0["1-bit Reg"] SP --> R1["1-bit Reg"] SP --> Rdots["..."] SP --> R7["1-bit Reg"] R0 --> MG["Merger\n1×8→8"] R1 --> MG Rdots --> MG R7 --> MG MG --> Q["Q (8-bit)"] CLK([CLK]) --> R0 & R1 & Rdots & R7 CLR([CLR]) --> R0 & R1 & Rdots & R7 EN([EN]) --> R0 & R1 & Rdots & R7
All bits update **synchronously** — no skew between bits. --- ## Splitters and Mergers in Digital - **Splitter**: breaks a multi-bit bus into individual 1-bit wires - **Merger**: combines individual 1-bit wires back into a bus ```text 8-bit D bus | [splitter] ||||||||| bit0..bit7 → [eight 1-bit registers] ||||||||| [merger] | 8-bit Q bus ```
Digital ships with a built-in
Register
component. For this lab, build it by hand to understand the internals.
--- ## The Counter Circuit Register + adder + constant 1, output fed back to input:
flowchart LR CLK([CLK]) --> REG CLR([CLR]) --> REG EN([EN]) --> REG REG["8-bit Register"] -->|"Q = count"| OUT([Count out]) REG -->|Q| ADD["8-bit Adder"] ONE["const 1"] --> ADD ADD -->|"Q + 1"| REG
1. Register output Q = current count 2. Adder computes Q + 1 3. Result feeds back to register D input 4. Rising edge captures Q + 1 --- ## Counter: Cycle Trace Starting from CLR pulse (Q = 0), EN = 1: ```text Cycle Q (before) Adder = Q+1 Q (after) ----- ---------- ----------- --------- 0 00000000 00000001 00000001 1 00000001 00000010 00000010 2 00000010 00000011 00000011 ... ... ... ... 255 11111111 00000000 00000000 ← wraps! ``` 8-bit counter counts 0 → 255 → 0 → ... **(modulo 256)** Carry out of the 8-bit adder is discarded. --- ## Counter Control Signals | Signal | Role | |--------|------| | **CLR** | Pulse once at startup → reset count to 0 | | **EN** | 1 = count each cycle, 0 = freeze the count | | **CLK** | Each rising edge advances count by 1 |
This same circuit becomes the
Program Counter (PC)
in your processor project — holds the current instruction address and increments each cycle.
--- ## Build Order in Digital ```text 1. SR Latch two NOR gates, cross-coupled 2. D Latch AND gates + NOT: S=D&CLK, R=(~D)&CLK 3. D Latch+CLR OR CLR into the R path 4. D Flip-Flop two D latches, master on ~CLK, slave on CLK 5. 1-bit Register D flip-flop (CLR) + MUX(EN) + Q feedback 6. N-bit Register splitter + N×(1-bit reg) + merger, shared CLK/CLR/EN 7. Counter N-bit reg + 8-bit adder + constant 1, Q fed back ``` Save and encapsulate each sub-circuit (`sr-latch.dig`, `d-flip-flop.dig`, etc.) before building the next level. --- ## Simulator Tips - **One clock**: place a single Clock input and fan it out — never multiple clock sources - **Manual clock** while debugging: step one edge at a time, watch values change - **Add probes** on Q, adder output, and control signals - **Clear first**: pulse CLR once before counting to start from known 0 - **Name signals exactly**: `CLK`, `CLR`, `EN`, `D`, `Q` — the autograder matches on these names
The Digital labs run on your
host OS
, not the RISC-V VM. Use WSL Ubuntu on Windows.
--- ## Common Pitfalls | Pitfall | Consequence | Fix | |---------|-------------|-----| | Multiple clock sources | Skew, missed edges | One clock, fan out | | SR forbidden state (R=S=1) | Undefined / oscillation | Use D latch instead | | Uninitialized power-on | Garbage starting value | Always pulse CLR | | Latch in feedback loop | Mid-cycle races | Use edge-triggered flip-flop | | Wrong signal names | Autograder fails | Match names exactly | --- ## Key Concepts Reference | Concept | One-Line Definition | |---------|---------------------| | **SR latch** | Cross-coupled NOR gates, stores 1 bit | | **D latch** | Level-sensitive; transparent while CLK high | | **D flip-flop** | Edge-triggered; captures D only at rising edge | | **CLR** | Forces Q = 0 for known initialization | | **EN** | Enables update; off = hold via self-feedback | | **N-bit register** | N parallel 1-bit cells, shared CLK/CLR/EN | | **Counter** | Register + adder + constant 1, wraps at 2^N | --- ## Summary 1. **Sequential logic adds memory** via feedback and a clock: `state → comb logic → state` 2. **The clock's rising edge** is the single shared instant when all edge-triggered elements update 3. **SR latch** stores 1 bit with cross-coupled NOR gates; avoid the R=S=1 forbidden state 4. **D latch** is level-sensitive (transparent while CLK high); eliminates forbidden state 5. **D flip-flop** (master-slave) is edge-triggered; essential for safe feedback loops 6. **1-bit register** = flip-flop + CLR (force 0) + EN (MUX feedback for hold) 7. **N-bit register** = N parallel 1-bit cells with splitter/merger 8. **Counter** = register + adder; this pattern becomes the processor's Program Counter