There have been CPUs with exposed branch delays, such as early MIPS: What was the first CPU with exposed pipeline?

(Later MIPS kept the delay slots from the early MIPS, though by that time, it wasn't about exposing hardware pipeline stages – which would have increased the number of delay slots – but keeping compatibility with the early ones.)

Did any CPU ever expose load delays? So that if you try to load a register from memory, then use the contents of that register, instead of pausing until the new value is loaded, for the next N clock cycles, you just get the previous value that was in the register?

  • 2
    @njuffa Not so much delay slots in the MIPS sense; rather the operations using the results of the loads had to be scheduled to account for load delays, otherwise the performance would suffer substantially. What rwallace is asking about is lack of interlock, and in presence of data cache having no load interlocks is nonsense, as the load delay could not be predicted. – Leo B. Jan 10 at 4:34
  • 4
    As a matter of fact, violating the load delay slot requirement did not guarantee the previous value in the register; it was undefined behavior: if there was an external interrupt just after the load instruction, it was going to finish, and the next instruction would see the new value. – Leo B. Jan 10 at 4:44
  • 2
    IIRC the Mill (which unfortunately doesn't seem to have made progress lately) exposes load delays in the sense that the final-stage compiler/loader is aware of them, and can move load instructions forward to spread the delay over other code, but it still will stall when accessing the loaded value early, and not read the previous value instead. – dirkt Jan 10 at 6:49
  • 1
    @Raffzahn Please watch this, it looks like you have completely misunderstood what I was talking about. – dirkt Jan 10 at 15:05
  • 1
    @dirkt: I thought of The Mill as well, while editing Raffzahn's answer to describe what I recall reading the R2000 did if you violate the load-delay. There's now a link in the answer to millcomputing.com/topic/… - the section on Loads describes it pretty well. – Peter Cordes Jan 10 at 17:50

Yes, it happened. MIPS I (R2000/R3000) did suffer from Load Delay Slots. In practice on R2000, on cache hit the next instruction would typically see the old value of the load-result register; on cache miss it would see the load result. So you couldn't usefully take advantage of it and there were no on-paper guarantees of anything, unlike on the Mill (*1).

Other than MIPS, the decision to use load delay slots was never an issue. Mostly because inserting a delay does not really save much hardware. Stalling the pipeline only needs one (or two) rather simple comparator checking if the previous target is used as source in the next instruction.

Detecting dependencies by adding a little logic can be used to kill two birds with one stone as any code, independent of ordering will run, while at the same time 'intelligent' instruction ordering can be used to take advantage of the load delay.

This method has already been used by (e.g) /360 in the 70s, and assembly programmers did take care thereof (or not, depending on their quality).

For RISC CPUs this logic can, for all practical purpose, be seen like an automatic 'insertion' of a NOP. Something otherwise the compiler had to do whenever it couldn't find an instruction to move into the load slot.

A simple addition like A+=B; would create at least two load slots (plus maybe a store slot) which more often than not can't be filled:

    L   R1,VAL1
    A   R1,VAL2
    ST  R1,VAL1

This is by no means a rare kind of code - programs are full of such lines.

Bottom line: Load slots bring almost no improvement but add considerable code bloat. (*2)

The whole situations of load delays differs a lot from branch delays, as the load delay happens to data access in linear code execution, not stalling code fetch. In contrast a (conditioned) jump instructions always needs at least one additional code fetch cycle that can't be avoided - or more exact discard the already fetched next instruction without using that cycle for operation. A branch delay slot means nothing else that this already fetched instruction gets executed. This situation does not exist in linear, non-branching code.

So one can just speculate why the R2000 had that load delay slot. IMHO they tried to make it more simple than it has to be. They even named the ISA after this fact: MIPS = Microprocessor without Interlocked Pipeline Stages means it doesn't stall for anything, except unpredictable long stalls (cache miss/RAM access).

Later, when the load delay slot was removed (MIPS II / R6000), existing R2000/R3000-compatible code was full of NOPs in cases where the compiler (or optimizing assembler) couldn't fill the load-delay slot with useful work. These cases were not as rare as MIPS architects may have hoped. It was not helping anything, only bloating the code.

*1 - Unlike the Mill architecture where a load instruction can indicate how many instructions later the result should be ready, allowing use of the old value for up to a few instructions, and reuse of the address register. The Mill is still a paper architecture, though, not a CPU that actually existed.

*2 - Same is as well true for branch slots, except they happen less often.

  • 2
    @rwallace Not really. Especially in embedded the cost of a load delay slot is even more, as there are many situations were the slot can't be filled with a useful instructions forcing the inclusion of an explicit NOP resulting in bloated code. Not really something embedded developers love to see. – Raffzahn Jan 9 at 23:33
  • 2
    I think I recall the MIPS 2000 assembler reordering instructions to populate the load-delay slot (it certainly did for branch-delay slots). – another-dave Jan 10 at 0:15
  • 1
    MIPS originally stood for Microprocessor without Interlocked Pipeline Stages (What is an "interlocked pipeline" as in the MIPS acronym?). It had to be able to stall for cache misses, and in some cases reading lo/hi mult results, but original MIPS I (R2000 / R3000) couldn't stall for "normal" stuff. This may have simplified more than just another comparator? That's partly why mult and div put their results in special registers with restrictive rules about reading them, although that might just have been to reduce comparators. (ping @rwallace) – Peter Cordes Jan 10 at 15:20
  • 1
    R3000 was also MIPS I. It was MIPS II that removed the load delay slot (apparently R6000 was the first CPU of that ISA revision: linux-mips.org/wiki/Instruction_Set_Architecture#MIPS_II. R4000 was MIPS III. Out-of-order naming of in-order pipelines... hmm.) – Peter Cordes Jan 10 at 15:26
  • 1
    I made a relatively large edit; you might want to take out some of it if you think it's too much of a tangent, or of course rearrange it. – Peter Cordes Jan 10 at 15:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.