Level 53: The Four-Byte Step

Open level →

Task

Goal: build the program counter path: every cycle pc = pc + 4, and when a beq is taken (branch & zero) — pc = pc + offset. The offset base is the address of the branch instruction itself, the offset is in bytes (not words!), and it is split across the instruction word in the B-format — BImm32 does the splitting. The add32.v adder is ready from level 50 — just instantiate it twice. The Assembler tab now has beq with labels.

Until now the PC simply stepped forward. Every instruction occupies 4 bytes, so the step is four. Now you will teach the processor to change its route: if the condition holds, jump; if not, take the ordinary four-byte step.

What is added:

  • PC+4 and PC+imm. The branch address adder: PC + imm, where imm is a signed offset in bytes. The PCSrc mux selects between them based on the branch signal.
  • B-format. The 13-bit offset (with an even step, the low bit is always 0) is split into two pieces in the instruction word — and not consecutively: imm[12|10:5] in the top bits and imm[4:1|11] below. Parsing this split is the main puzzle of the level.
  • beq. The condition comes from the ALU: subtract rs1 − rs2; if the result is zero, the registers are equal. The ALU from level 50 already outputs the zero signal — it finally gets a job.
  • Labels in the assembler. B-encoding and labels are added: beq x5, x6, loop — the assembler computes the byte offset to the label itself.

Check: an execution trace + a prediction of where beq will go — forward or backward, and to which address.

Solution for self-check

First trace the instruction sequence with a branch cycle by cycle. The solution below will help you check it.

What you should get: beq is correct in both directions: condition true — PC became PC + imm (the base is the branch address itself), false — PC + 4; the execution trace and the where-does-beq-jump prediction agree.

Step 1 — assemble the offset. The pieces form a 13-bit number: {imm[12], imm[10:5], imm[4:1], 1'b0}, then sign-extend to 32 bits. The low bit is always zero — all instructions are aligned to 4 bytes, so the offset is even. Swapped pieces send the jump "somewhere wrong": the level's negative test catches exactly that.

Step 2 — choose the base address. The offset is counted from the PC of the current branch instruction, not from the "next" one and not from zero. The second classic mistake is an offset in words instead of bytes: a label two instructions away is +8 bytes, not +2.

Step 3 — connect the condition. The ALU subtracts rs1 − rs2; the zero signal means equality. Branch = Branch & zero — it controls PCSrc. Note: the comparison subtraction does not corrupt the registers — the branch ALU result is simply not written to rd.

Step 4 — check both directions. A forward jump (label below in the code) and a backward one (label above — this is what loops will look like). For each case check: condition true — PC becomes PC + imm; condition false — PC becomes PC + 4.

Principle: a conditional branch does not change the order of instructions in memory. It changes the address from which the processor continues fetching instructions.

Check yourself

Why is the B-format offset split non-contiguously?

It is the price of a compact instruction: the offset bits are laid out into fixed fields (imm[12|10:5] in the top byte, imm[4:1|11] below) shared by all B instructions. The lowest bit is always 0 — instructions are 4-byte aligned, the offset is even.