Back to Blog

Building the Single-Cycle Datapath: ALU, Register File, Memory, and Buses

August 18, 202622 min read
Computer Architecture CPU Design Digital Logic Learning

Lesson 2, Part 1 nailed down the ISA we're implementing: a 32-bit MIPS-style machine with R0 hardwired to zero, R-type ADD/SUB/AND/OR, and I-type LW/SW/BEQ, decoded from opcode and funct fields. This part builds the hardware that actually executes those seven instructions — one wire, one mux, one memory port at a time — until we have a single labeled diagram that every later part of this lesson can point at.

1. The Rule We're Building Under

Every gate in this datapath has to do useful work for every instruction it's wired into, because in a single-cycle design there is exactly one piece of hardware per function and it's shared by whichever instructions pass through it. There's one ALU, not seven. One register file, not one per instruction type. The entire art of single-cycle datapath design is finding the union of what all seven instructions need, building shared hardware for that union, and then inserting a multiplexer wherever two instruction types disagree about what should flow down a shared wire.

That's the whole story, actually. Every mux in this post exists because two (or more) instructions want different data on the same physical wire in the same cycle. Keep that lens on as we go — it turns "why is there a mux here" from a memorization question into a derivation.

We'll build the datapath in the same order data actually flows through it: fetch, decode/read, execute, memory, writeback, and finally the PC-update logic that closes the loop back to fetch. Each section adds one piece to a running diagram, and Section 9 assembles the complete thing.

2. Fetch: PC and Instruction Memory

At the start of every cycle, the Program Counter holds a 32-bit address. That address goes straight into instruction memory, which is just a big combinational read-only lookup table for this design (no writes, no clock dependency on the read path):

PC (32) Instruction (32) PC Instruction Memory register (read-only, addr = PC, combinational)

Two things to notice already:

  • The bus carrying PC and the bus carrying Instruction are both 32 bits wide — full machine-word width, because both an address and an encoded instruction are 32-bit quantities in this ISA.
  • Nothing here depends on the opcode yet. Every one of the seven instructions is fetched by exactly this same hardware, with exactly this same 32-bit address-in, 32-bit instruction-out behavior. Fetch is the one stage with zero muxes, because there is nothing yet to choose between.

Recall from Part 1 that PC starts at 0x00400000. That value lives in the PC register and is loaded into instruction memory's address port every cycle, combinationally — the read happens continuously, not on a clock edge, exactly like the register-file reads from the earlier register-file post in this series.

3. Field Splitting: Slicing the Instruction

The 32-bit instruction word coming out of instruction memory isn't opaque — it's several fixed-position bit fields, and different instructions use different subsets of them. This "splitting" isn't a circuit at all; it's just labeled taps into the same 32 wires, wired to whatever needs each field:

R-type:  [ opcode 6 | rs 5 | rt 5 | rd 5 | shamt 5 | funct 6 ]
          31      26 25  21 20  16 15  11 10       6 5      0
 
I-type:  [ opcode 6 | rs 5 | rt 5 | immediate 16              ]
          31      26 25  21 20  16 15                          0

Concretely, off the 32-bit Instruction bus:

FieldBitsWidthFeeds
opcode[31:26]6Control unit (Part 3)
rs[25:21]5Register file ReadReg1
rt[20:16]5Register file ReadReg2, and one input of the RegDst mux
rd[15:11]5 (R-type only)Other input of the RegDst mux
funct[5:0]6 (R-type only)ALU-control decoder (Part 3)
immediate[15:0]16 (I-type only)Sign-extend unit

Every one of these taps is a plain wire bundle, not a piece of active logic — opcode, rs, rt, rd, funct, and immediate are just different-width slices of the same 32 bits, routed to wherever downstream hardware needs them. rs and rt are always valid regardless of instruction format (both formats put them in the same two 5-bit slots — this is a deliberate MIPS encoding regularity that avoids needing a mux just to find the source register fields). rd and funct are only meaningful for R-type; immediate is only meaningful for I-type. The datapath doesn't need to "know" which fields are meaningless for a given instruction — it just needs the right value to end up on each shared wire when it matters, which is exactly what the muxes in the next few sections handle.

4. Register Read: The Register File's Two Read Ports

rs and rt go directly into the register file's two read-address ports:

rs (5) ReadData1 (32) rt (5) Register File ReadData2 (32) 32 × 32-bit, R0=0 WriteReg (5) WriteData (32) RegWrite CLK

Both reads happen combinationally and simultaneously — no instruction in this ISA needs to read one register, wait, then read a second one. ADD/SUB/AND/OR read rs and rt as its two operands. LW reads rs as the base address (it doesn't use ReadData2 for computing the address, but it does read rt's register — wait, actually for LW, rt is the destination, not a source; more on that in Section 5). SW reads rs as the base address and rt as the value to store. BEQ reads rs and rt as the two operands to compare.

So ReadData1 is always fed by rs, for every one of the seven instructions — that wire never needs a mux in front of it. ReadData2 is always fed by rt — also no mux needed on the read side. The interesting mux shows up on the write side, because rt means something different depending on instruction type: for LW it's the destination register, but for R-type instructions the destination is rd, not rt.

R0 deserves a callout here: it's not a normal storage cell. Reading register address 00000 always returns the constant 0, and writes to address 00000 are architecturally discarded — the register file's write logic simply refuses to update storage when WriteReg == 0, regardless of RegWrite. Every ALU op, load, and address calculation in this ISA can therefore use R0 as a free source of the constant zero without a single dedicated "load immediate zero" instruction.

4.1 The RegDst Mux — Choosing the Write-Back Register Address

rt (5) MUX WriteReg (5) register file write-address port rd (5) RegDst (1)
  • What it chooses between: the rt field (bits [20:16]) vs. the rd field (bits [15:11]) as the 5-bit address of the register that gets written this cycle.
  • Who needs which input:
    • RegDst = 1 → select rd. Used by ADD, SUB, AND, OR — R-type instructions put their destination in the rd field by encoding convention (rd ← rs OP rt).
    • RegDst = 0 → select rt. Used by LW — I-type instructions have no rd field at all (those bits are the top of the 16-bit immediate instead), so the destination register for a load is encoded in rt (rt ← Mem[rs + signext(imm)]).
    • SW and BEQ don't write any register, so RegDst's value is irrelevant for them — RegWrite will be 0 and the mux output is simply ignored downstream.
  • Bus width: both inputs and the output are 5 bits — a register address, not a register value.

This is a textbook-style pattern that appears identically in Patterson and Hennessy's Computer Organization and Design, which is the canonical reference for this entire datapath shape (details in Further Reading).

5. The Immediate Path: Sign-Extend

I-type instructions (LW, SW, BEQ) carry a 16-bit immediate field. R-type instructions don't use it at all — but the hardware doesn't get to skip building the sign-extend unit for R-type instructions; it's simply unused (its output feeds a mux input that R-type ignores).

immediate [15:0] Sign-Extend SignExtImm [31:0] bit 15 replicated into bits 31:16

Sign extension replicates bit 15 (the sign bit of the 16-bit field) into the upper 16 bits of the 32-bit result:

immediate = 1111 1111 1111 0100          (-12 as a 16-bit two's-complement value)
                ↓ sign-extend (bit 15 = 1 replicated upward)
SignExtImm = 1111 1111 1111 1111 1111 1111 1111 0100     (-12 as 32-bit two's-complement)

If bit 15 had been 0 (a positive or zero immediate), the upper 16 bits would all become 0 instead — the unit is a pure combinational replication circuit, one wire fanning out to sixteen positions, not an adder or anything stateful. Note this is genuinely a 16-bit-in, 32-bit-out circuit — the bus width changes here, which is worth flagging explicitly since most of this datapath keeps buses at a constant width end to end.

Why sign-extend rather than zero-extend? Because LW/SW use the immediate as a signed byte offset from a base register (Mem[rs + signext(imm)]) — negative offsets need to work exactly the same as positive ones for address arithmetic to be correct, and BEQ's branch offset (Section 8) is signed for the same reason: branching backward (a loop) is exactly as common as branching forward.

6. Execute: The ALU and the ALUSrc Mux

The ALU always takes its first operand from ReadData1 (i.e., from register rs) — no instruction in this ISA needs anything else on that side. The second operand is where instructions disagree, and that disagreement is exactly what ALUSrc resolves.

6.1 The ALUSrc Mux — Choosing the ALU's Second Operand

ReadData2 (32) MUX ALU input B (32) SignExtImm (32) ALUSrc (1)
  • What it chooses between: the raw value read from register rt (ReadData2) vs. the sign-extended 16-bit immediate (SignExtImm).
  • Who needs which input:
    • ALUSrc = 0 → select ReadData2. Used by ADD, SUB, AND, OR (both operands are registers) and by BEQ (comparing two registers means both ALU inputs are register values — see Section 8).
    • ALUSrc = 1 → select SignExtImm. Used by LW and SW — both compute an effective address as rs + signext(imm), so the immediate has to reach the ALU's second input.
  • Bus width: 32 bits on every wire here — ReadData2, SignExtImm, and the mux output are all full register width, even though the immediate started life as only 16 bits (that's exactly why Section 5's sign-extend unit exists: to widen it to 32 bits before it reaches this mux, so the mux itself never has to deal with mismatched widths).

6.2 The ALU Itself

ALU input A (32) ALU Result (32) ALU input B (32) ALU Zero (1) ALUCtrl (from ALU-control decoder, Part 3)

The ALU is a single shared piece of combinational hardware performing whichever operation ALUCtrl selects (ADD, SUB, AND, OR — the same four functional units from the ALU built in an earlier post in this series, muxed together internally by ALUCtrl). Two of its outputs matter to the rest of the datapath:

  • Result (32 bits) — the arithmetic/logic answer for R-type instructions, or the computed memory address for LW/SW, or the "are these equal" difference for BEQ (computed as rs − rt; equality shows up as the Zero flag, not as Result itself).
  • Zero (1 bit) — asserted when Result == 0. For ADD/SUB/AND/OR this flag is simply unused downstream. For BEQ, this is the entire point of running the ALU at all: BEQ uses the ALU as a comparator by computing rs − rt and checking whether the answer is zero, rather than needing separate comparator hardware.

This reuse is worth sitting with, because it's the same design principle as the whole datapath: rather than build a dedicated equality comparator for BEQ, the design reuses the subtractor that already exists inside the ALU and taps its zero-detect output. One piece of hardware, three uses (add/subtract results, address arithmetic, and now equality testing).

7. Memory: Data Memory and the MemToReg Mux

The ALU's Result becomes the address into data memory for LW and SW — the same Result bus, no mux needed, because in this ISA the ALU is the only source of memory addresses (there's no separate address-generation unit).

Address (32) ALU Result ReadData2 (32) Data Memory (write-data, from rt) (byte-addressable) ReadData (32) MemRead MemWrite
  • Address comes straight from the ALU Result — for LW/SW this is rs + signext(imm); for ADD/SUB/AND/OR/BEQ this input is simply unused because MemRead and MemWrite are both 0 for them.
  • Write-data into memory is ReadData2 — the value read from register rt — used only by SW (Mem[rs + signext(imm)] ← rt).
  • MemRead is asserted only for LW.
  • MemWrite is asserted only for SW.
  • ReadData (32 bits) is the value memory returns on a read — meaningful only when MemRead = 1, i.e., only for LW.

Every instruction's Address, write-data, and ReadData wires are physically connected to this same memory every cycle — data memory doesn't get to "know" it's being talked to by an ADD instruction and switch itself off. It's MemRead/MemWrite that determine whether anything observable actually happens.

7.1 The MemToReg Mux — Choosing the Write-Back Value

ALU Result (32) MUX WriteData (32) register file write-data port Mem ReadData (32) MemToReg (1)
  • What it chooses between: the ALU's Result vs. the value just read from data memory.
  • Who needs which input:
    • MemToReg = 0 → select ALU Result. Used by ADD, SUB, AND, OR (the ALU result is the answer to write back).
    • MemToReg = 1 → select Mem ReadData. Used by LW (the loaded value from memory is what belongs in the destination register, not the address that was used to fetch it).
    • SW and BEQ write no register at all (RegWrite = 0), so MemToReg's value is irrelevant for them, exactly like RegDst was for them in Section 4.1.
  • Bus width: 32 bits on all three wires — this mux picks between two full register-width values, unlike RegDst, which picked between two 5-bit addresses. Don't confuse the two — they're both "the write-back mux family" conceptually, but one selects an address and the other selects data.

The MemToReg mux's output feeds straight back into the register file's WriteData port from Section 4 — closing the loop from ALU/memory back into storage.

8. PC Update: Sequential Flow, Branch Target, and PCSrc

Every cycle, the CPU needs a next-PC value, and — mirroring every other decision point in this datapath — there are exactly two candidates and a signal that picks between them.

8.1 The Sequential Adder: PC + 4

PC (32) Adder PC + 4 (32) constant 4 (32)

Every instruction is 4 bytes wide in this fixed-width ISA, so "the next instruction, assuming no branch" is always PC + 4. This adder runs every single cycle, for every instruction, unconditionally — it's the default path.

8.2 The Branch-Target Adder

SignExtImm (32) << 2 SignExtImm × 4 (32) PC + 4 (32) Adder Branch Target (32)

BEQ's target isn't an absolute address — it's an offset relative to PC + 4, scaled by 4 because branch offsets in this ISA count instructions, not bytes, and every instruction is 4 bytes. SignExtImm << 2 is a pure wiring shift (append two zero bits at the low end, or equivalently, shift the 32-bit bus left by two positions with zeros shifted in) — it costs no gates, just relabeled wires, since a left-shift-by-2 is nothing more than reading the same bits at different bit positions.

Concretely, from Part 1's running example: BEQ R6, R2, 1 at instruction index 4 has immediate = 1 (signext(1) = 0x00000001). Shifted left by 2, that's 0x00000004. If PC at that instruction is 0x00400010 (instruction 4, four words past the program start at 0x00400000), then PC + 4 = 0x00400014, and Branch Target = 0x00400014 + 0x00000004 = 0x00400018 — which is exactly instruction index 6, skipping over instruction 5 as the ISA semantics specify (PC ← PC + 4 + (signext(imm) << 2)).

This adder, like the sign-extend unit and the ALU, runs unconditionally every cycle regardless of whether the current instruction is actually a branch — the datapath computes a branch target for ADD instructions too, it just never gets used. That's the price of a single-cycle design: every functional unit that any instruction might need is present and active on every cycle; only the muxes decide what actually matters.

8.3 The Branch Decision: Branch AND Zero, and the PCSrc Mux

Branch (1, from control unit) AND PCSrc (1) Zero (1, from ALU) PC + 4 (32) MUX Next PC (32) loaded into PC register on next clock edge Branch Target (32) PCSrc (1)

PCSrc is not a direct control-unit output — it's a derived signal, computed by ANDing two things together:

  • Branch — asserted by the control unit if and only if the current instruction is BEQ. This alone doesn't mean the branch is taken; it just means "this instruction is even capable of branching."
  • Zero — asserted by the ALU (Section 6.2) if and only if rs − rt == 0, i.e., rs == rt.

Only when both are true — the instruction is BEQ and the two registers are equal — does PCSrc = 1, selecting Branch Target as the next PC. For every non-branch instruction, Branch = 0 unconditionally forces PCSrc = 0 regardless of what Zero happens to be (and Zero genuinely can be 1 for a non-branch instruction — e.g., SUB R6, R1, R1 produces Result = 0 and Zero = 1, but since Branch = 0 for SUB, the AND gate output stays 0 and the sequential PC + 4 path is selected anyway). For BEQ with unequal registers, Branch = 1 but Zero = 0, so the AND still outputs 0, and PC + 4 — sequential fall-through — is correctly selected.

This is the same technique from Section 6.2 taken one level further: rather than build dedicated "is this a taken branch" hardware, the design reuses the ALU's existing zero-detect output and combines it with one control bit via a single 2-input AND gate. MIT's 6.004 course materials and Patterson and Hennessy's textbook both describe this exact Branch AND Zero → PCSrc structure as the standard mechanism for deriving the branch-taken signal in a single-cycle MIPS datapath (see Further Reading).

  • Bus width note: Branch, Zero, and PCSrc are all single bits — this is the one place in the whole datapath where two 1-bit control-ish signals combine through a logic gate (not a mux) to produce a third 1-bit signal that then drives a mux select line. Every other mux in this design is selected directly by a control-unit output; PCSrc is the sole exception, because it's the one decision that depends on data (are the two registers actually equal) rather than purely on instruction type.

9. The Complete Single-Cycle Datapath

Everything above assembles into one picture. This is the centerpiece diagram of this lesson — every instruction this CPU executes flows through exactly this hardware, differing only in which muxes route which values and which of RegWrite/MemRead/MemWrite/Branch are asserted (that decoding is Part 3's job; this part only builds the pipes the water flows through).

PC-UPDATE LOGIC (Section 8) PC(32) Adder +4 PC+4(32) Branch-Target Branch Adder Target MUX SignExtImm<<2 (PC+4 + imm) (32) 0:PC+4 (32, from 1:BT Sign-Extend block below) PCSrc AND(Branch,Zero) Branch Zero (ctrl unit) (from ALU) Next PC(32) (MUX output, loaded on next clock edge) PC(32) Instruction Memory (read-only) Instruction(32) FIELD SPLIT (Section 3) opcode[31:26](6) rs[25:21](5) rt[20:16](5) rd[15:11](5, R-type) funct[5:0](6, R-type) immediate[15:0](16, I-type) opcode rs(5) rt(5) rd(5) imm(16) to Control Unit (Part 3) Sign-Extend bit15 replicated SignExtImm(32) (feeds ALUSrc mux (feeds <<2, input 1, below) Section 8.2 above) ReadReg1 (=rs) REGISTER FILE ReadReg2 32 × 32-bit, R0=0 (=rt) WriteReg(5) WriteEnable=RegWrite ReadData1 ReadData2 (32) (32) MUX rt(5) 0:rt rd(5) 1:rd (write-data to Data Memory, RegDst Section 7) WriteReg(5) feeds Register File above MUX SignExtImm(32) [input 1] 0:ReadData2 1:SignExt ALUSrc ALU input B(32) ALU input A(32) ALUCtrl ALU Zero(1) to AND gate (Part 3) ADD / SUB / AND / OR (PC-update logic, top) Result(32) Address(32) Data Memory write-data(32,ReadData2) (byte-addr., MemRead(ctrl) MemRead/ MemWrite(ctrl) MemWrite) ReadData(32) MUX 0: ALU Result 1: Mem ReadData MemToReg WriteData(32) back into Register File's WriteData port (write commits on next clock edge if RegWrite=1)

This ASCII rendering is drawn in my own layout to convey the same well-known design taught in Patterson and Hennessy's Computer Organization and Design — it is not a reproduction of their textbook figure, just a from-scratch depiction of the identical, decades-standard set of connections (PC → instruction memory → register file/sign-extend → ALU → data memory → writeback, plus the branch-adder/AND-gate/PCSrc loop back into the PC). If the linearized diagram above is hard to trace visually, walk it stage by stage using Sections 2 through 8 — every wire in the picture is named and explained there.

10. Mux Summary Table

Five multiplexers appear in this datapath. Here's the complete reference:

MuxInput 0Input 1Selected byWidthInstructions using Input 0Instructions using Input 1
RegDst muxrt fieldrd fieldRegDst5 bitsLWADD, SUB, AND, OR
ALUSrc muxReadData2 (register rt)SignExtImmALUSrc32 bitsADD, SUB, AND, OR, BEQLW, SW
MemToReg muxALU ResultMem ReadDataMemToReg32 bitsADD, SUB, AND, ORLW
PCSrc muxPC + 4Branch TargetPCSrc (= Branch AND Zero)32 bitsADD, SUB, AND, OR, LW, SW, and BEQ not-takenBEQ taken
PC-input mux (implicit — same mux as PCSrc row above)

(The last row is intentionally the same mux as the PCSrc row — it's listed as "PC-input mux" in some textbook treatments and as "PCSrc mux" in others; they name the identical piece of hardware.)

Note what's not in this table: there's no mux selecting the ALU's first input (ReadData1/rs always feeds it), no mux on the register file's read-address ports (rs/rt always feed ReadReg1/ReadReg2 directly), and no mux on the memory address port (the ALU Result always feeds it). Single-cycle datapath design is exactly this exercise — cataloging every wire, and inserting a mux only where the catalog reveals genuine disagreement between instructions.

11. Wire-Width Cheat Sheet

Pulling every bus width mentioned above into one place, since getting these wrong is the single most common bug when actually building this circuit (e.g., in Verilog for a class lab):

SignalWidthWhy
PC, PC + 4, Branch Target, Next PC32Full address width
Instruction32Fixed instruction width
opcode6Encoding field
rs, rt, rd, RegDst/ALUSrc mux address-type outputs52^5 = 32 addressable registers
funct6Encoding field
shamt5Encoding field (unused by this 7-instruction subset)
immediate (raw, from instruction)16Encoding field
SignExtImm32Widened from 16 bits for ALU/address use
SignExtImm << 232Shift doesn't change width, only value
ReadData1, ReadData2, WriteData (register file)32Register width
ALU input A, ALU input B, Result32Register/address width
Zero1Single-bit flag
Address, memory write-data, ReadData (data memory)32Byte-addressed, word-sized accesses in this ISA
RegDst, ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, PCSrc1Each is a single control bit
ALUOp2Feeds the secondary ALU-control decoder (Part 3) — 2 bits is enough to distinguish "R-type, look at funct" from "always add" (LW/SW) from "always subtract" (BEQ)

The pattern to internalize: data buses are 32 bits, register addresses are 5 bits, control signals are 1 bit (except ALUOp at 2 bits), and the immediate field is the only place a 16-to-32-bit widening happens. Every wire in Section 9's diagram falls into exactly one of these categories.

Takeaway: The single-cycle datapath is not a pile of special-purpose circuits — it's a small, fixed set of shared hardware (one register file, one ALU, one data memory, two adders) wired together with exactly five multiplexers, each mux existing precisely because two or more of the seven instructions disagree about what value belongs on some shared wire. RegDst and MemToReg resolve register-file disagreements (which address to write, which value to write). ALUSrc resolves what the ALU's second operand should be. PCSrc — the only mux driven by data rather than pure instruction type — resolves whether execution continues sequentially or jumps to a branch target, by ANDing the control unit's Branch bit with the ALU's Zero flag.

12. Per-Instruction Walkthrough: What Every Mux Selects

Part 3 will derive where each control bit's value comes from (the opcode-to-control-signal decoder). Here, treating the control values as given, is the complete map of what every one of the five muxes actually selects for each of the seven instructions — useful as a reference while reading Section 9's diagram, and worth checking your own understanding against before moving on.

ADD, SUB, AND, OR (R-type)

  • RegDst = 1 → write address comes from rd.
  • ALUSrc = 0 → ALU's second input is ReadData2 (register rt); both operands are registers.
  • MemToReg = 0 → the value written back is the ALU Result directly.
  • PCSrc = 0 always, because Branch = 0 regardless of what Zero happens to be — so Next PC = PC + 4 unconditionally, even for SUB instructions whose result happens to be zero.
  • Data memory is untouched: MemRead = MemWrite = 0. The Address, write-data, and ReadData wires all carry values, but nothing observable happens because the enables are off.

LW

  • RegDst = 0 → write address comes from rt (I-type has no rd field).
  • ALUSrc = 1 → ALU's second input is SignExtImm; the ALU computes rs + signext(imm) as an address, not a "real" arithmetic result.
  • MemRead = 1 → data memory actually performs a read at Address = ALU Result.
  • MemToReg = 1 → the value written back is Mem ReadData, not the ALU result — this is the one instruction where the address computed by the ALU is not what ends up in the destination register.
  • PCSrc = 0, same reasoning as R-type.

SW

  • ALUSrc = 1 → same address computation as LW: rs + signext(imm).
  • MemWrite = 1 → data memory performs a write at Address = ALU Result, using ReadData2 (register rt) as the write-data.
  • RegWrite = 0 → no register is written this cycle, so RegDst and MemToReg are both don't-cares; their mux outputs exist but feed nothing that matters.
  • PCSrc = 0, same reasoning as R-type.

BEQ

  • ALUSrc = 0 → both ALU inputs are registers (rs and rt), exactly like R-type, because BEQ is comparing two register values, not computing an address.
  • The ALU performs a subtraction internally regardless of the surface-level "compare" semantics; only Zero is consulted, Result itself is discarded.
  • RegWrite = 0 → same as SW, no register write, so RegDst/MemToReg are don't-cares.
  • MemRead = MemWrite = 0 → data memory is untouched.
  • Branch = 1 → this is the one instruction where the AND gate's first input is asserted, making PCSrc actually depend on Zero: PCSrc = 1 (branch target taken) if rs == rt, else PCSrc = 0 (fall through to PC + 4).

Notice the pattern across all seven: only BEQ ever sets Branch = 1, only LW ever sets MemRead = 1, only SW ever sets MemWrite = 1, and RegWrite = 0 for exactly the two instructions that write no register (SW, BEQ). Every other control combination reduces to a choice between "R-type shape" (RegDst=1, ALUSrc=0, MemToReg=0) and "I-type memory shape" (RegDst=0, ALUSrc=1, MemToReg=1 for LW; ALUSrc=1 alone for SW). That regularity — few distinct control patterns covering seven instructions — is exactly what makes the opcode-driven control unit in Part 3 tractable as a small truth table rather than seven hand-written special cases.

Further Reading

With the pipes built, the next question is where the actual 0s and 1s driving RegDst, ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, and ALUOp come from. That's Lesson 2, Part 3: The Control Unit — Generating Every Control Signal from the Opcode.