Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does MIPS I handle branching on the previous ALU instruction without stalling?

        addiu   $6,$6,5
        bltz    $6,$L5
        nop
        ...
$L5:

How is this safe without stalling, which classic MIPS couldn't even do, except on cache miss? (MIPS originally stood for Microprocessor Without Interlocked Pipeline Stages, and had a load delay slot instead of interlocking.)

Original MIPS I is a classic 5-stage RISC IF ID EX MEM WB design that hides all of its branch latency with a single branch-delay slot by checking branch conditions early, in the ID stage (correction: this was the mistake, go read this answer; don't be misled by the rest of the details in the question based on this false premise). Which is why it's limited to equal/not-equal, or sign-bit checks like lt or ge zero, not lt between two registers that would need carry-propagation through an adder.

Doesn't this mean that branches need their input ready a cycle earlier than ALU instructions? The bltz enters the ID stage in the same cycle that addiu enters EX.

MIPS I (aka R2000) uses bypass forwarding from EX-output to EX-input so normal integer ALU instructions (like a chain of addu/xor) have single-cycle latency and can run in consecutive cycles.


MIPS stands for "Microprocessor without Interlocked Pipeline Stages", so it doesn't detect RAW hazards; code has to avoid them. (Hence load-delay slots on first-gen MIPS, with MIPS II adding interlocks to stall in that case, invalidating the acronym :P).

But I never see any discussion of calculating the branch condition multiple instructions ahead to avoid a stall. (The addiu/bltz example was emitted by MIPS gcc5.4 -O3 -march=mips1 on Godbolt, which does respect load-delay slots, filling with nop if needed.)


Does it use some kind of trick like EX reading inputs on the falling edge of the clock, and ID not needing forwarded register values until the rising edge? (With EX producing its results early enough for that to work)

I guess that would make sense if the clock speed is capped low enough for cache access to be single-cycle.

Stalling or bubble in MIPS claims that lw + a beq on the load result needs 2 stall cycles because it can't forward. That's not accurate for actual MIPS I (unless gcc is buggy). It does mention half clock cycles, though, allowing a value to be written and then read from the register file in the same whole cycle.

like image 519
Peter Cordes Avatar asked Jun 13 '19 18:06

Peter Cordes


2 Answers

TL:DR: Classic MIPS I checks branch conditions in the first half cycle of EX, so forwarding to them is not special.

IF only needs the address in the 2nd half of a cycle so EX can forward to it.

These factors combine to give only 1 cycle of branch latency (hidden by 1 delay slot), with no problem for branches that depend the previous ALU instruction.


It was definitely safe to run sltu / beq on MIPS I (R2000). That's listed as the expansion for the bgeu pseudo-instruction, for example, in real MIPS manuals and books with no caveat about it being unsafe on MIPS R2000 or any other MIPS.

GCC uses sequences like that in practice even with march=mips1 which respects load-delay slots and other features of real MIPS R2000.


MIPS's IF doesn't need an address until the 2nd half of a clock cycle, allowing EX to produce it quickly enough.

From See MIPS Run by Dominic Sweetman, (covering MIPS I through MIPS IV), Chapter 1.5.1 Constraints on Instructions

We’ll see later that efficient conditional branching means that the decision about whether to branch or not has to be squeezed into only half a pipeline stage; the architecture helps out by keeping the branch decision tests very simple. So conditional branches (in MIPS) test a single register for sign/zero or a pair of registers for equality.

Their Figure 1.3: The pipeline and branch delays shows the branch condition being calculated in the first half of EX, and used in the 2nd half of IF, for a total branch latency of only 1 cycle / pipeline stage (ID) / instruction. IF doesn't actually start until the 2nd half of a clock cycle. (And continues into ID. The actual decode/register-fetch of ID only takes the last fraction of a clock cycle.)

That has the same end result as what I suggested in the question (check branch condition by the end of ID), except it only requires EX -> EX forwarding to branch on the result of the previous ALU instruction.

Perhaps I was misremembering or misinterpreting something I'd read previously about the half-cycle branch-decision. This half-cycle thing might well be exactly what I remembered seeing.

Further quoting See MIPS Run 1.5.5 Programmer-Visible Pipeline Effects

• Delayed branches: [first paragraph explains the branch-delay slot]

If nothing special was done by the hardware, the decision to branch or not, together with the branch target address, would emerge at the end of the ALU pipestage — in time to fetch the branch target instruction instead of the next instruction but two. But branches are important enough to justify special treatment, and you can see from Figure 1.3 [described above] that a special path is provided through the ALU to make the branch address available half a clock cycle early. Together with the odd half-clock-cycle shift of the instruction fetch stage, that means that the branch target can be fetched in time to become the next but one, so the hardware runs the branch instruction, then the branch delay slot instruction, and then the branch target — with no other delays.

... [don't waste your branch-delay slots]

... [many MIPS assemblers will reorder instructions for you if it's safe, to hide branch delay]

See MIPS Run has a foreword by John L. Hennessy, Founder of MIPS Technologies etc. etc.. That's not proof he signed off on everything in the book being accurate, but it's good evidence that the book's description of how MIPS managed this trick is accurate.

It's easily understandable and 100% plausible; we already know the data cache has single-cycle fetch latency (after address-generation in the EX stage).

like image 133
Peter Cordes Avatar answered Jan 01 '23 22:01

Peter Cordes


You are actually asking two questions:

  1. Is that safe on MIPS I?
  2. If so, how?

Is that safe on MIPS I?

I have seen different block diagrams of MIPS CPUs. Most of them perform the branch decision in the EX or even in the MEM stage instead of the ID stage.

Of course such designs will react differently when your example code is executed.

Without an official statement from the CPU manual of the CPU you are really using, your question cannot be answered with certainty.

(Paul Clayton's answer on Is that true if we can always fill the delay slot there is no need for branch prediction? agrees that one delay slot does fully hide branch latency on MIPS R2000, but not MIPS R4000. So that's good evidence that real commercial MIPS CPUs work the way the question assumes, despite the existence of various implementations that might not exactly follow the MIPS ISA.)

If so, how?

Doesn't this mean that branches need their input ready a cycle earlier than ALU instructions?

No.

The key is the bypass forwarding logic. Let's take a look at the following example:

add  $A, $B, $C      ; Currently in MEM stage
or   $D, $E, $F      ; Currently in EX stage
bltz $G, someLabel   ; Currently in ID stage

(While A, B, ... G are GPR numbers.)

The bypass forwarding logic for the EX phase (or instruction) contains a multiplexer that works the following way (pseudo code):

if E = A
    take ALU input from EX/MEM shift register output
else
    take ALU input from ID/EX shift register output
end-if

It is this multiplexer which allows you to use the result of some instruction (add) in the following one (or).

Of course the same can be done for the ID phase using a 3-way multiplexer:

if G = D
    take branch decision input from ALU output
else if G = A
    take branch decision input from EX/MEM shift register output
else
    take branch decision input from register bank output
end-if

Doing this, the signal propagation time will increase by the time needed in the EX phase. This means that this will limit the clock frequency of the processor.

However, the result of some instruction can already be used in the ID stage of the next instruction without needing an additional clock cycle.

like image 45
Martin Rosenau Avatar answered Jan 01 '23 21:01

Martin Rosenau