48

The 80486 processor can execute many instructions in a single cycle, such as a register-to-register add instruction (ADD EAX, EBX, for example), which one would generally assume is fairly complex, requiring two registers to be read, a full 32 bit ALU operation, and both a register and flag writes.

However, other instructions that one would naturally assume to be relatively simple take multiple cycles, including the various flag manipulation instructions like STC and CLC, which are a strict subset of the operations required for an ADD instruction (only loading a precalculated value into the flags, rather than having to actually determine what the value to load should be based on inputs, and not needing a register update at all), yet take 2 cycles.

What is the reason for this apparent discrepancy?

1
  • 5
    Note that clc and stc leave the other flags in EFLAGS unmodified, while add overwrites all the condition flags. Modern OoO exec x86 CPUs typically rename CF separately from the SPAZO group of flags. May 23, 2020 at 22:15

2 Answers 2

21

First, it is not true that the 486 executes instructions in a single cycle. The 80486 is a pipelined architecture, so it's more accurate to say that most instructions can start one cycle after the preceding instruction has started. The pipeline length of an 80486 is 5 stages (IF → ID1 → ID2 → EX → WB). This means that an instruction entering this pipeline has a latency of 5 cycles.

These "1-cycle" instructions are the ones that belong to the so-called RISC subset: a subset of instructions that are commonly present in production software because compilers that produced that software use them the most.

Other instructions, despite being as simple as the first ones, don't belong to the RISC subset and must be executed by means of microcode, as how it were executed in previous architectures.

4
  • I see, so the processor has two entirely separate execution paths, and those more common instructions go via a more optimized path, but other instructions (even if they could easily have been implemented on that optimized path due to being simple instructions) use the less optimized mechanism and therefore take longer?
    – occipita
    May 21, 2020 at 23:23
  • 2
    Besides various optimisations around microcode and fast path instructions, flags on x86 are special as all instructions don't update the flags in the same way (INC/DEC don't update CF unlike ADD/SUB). Maybe Intel preferred to use a convoluted method to change only CF (such as reading the flag register to a temporary storage, apply a mask...) instead of adding signals to only update CF and track dependencies : if CF updates are particular, the CPU may need to stall the pipeline to avoid hazards.
    – TEMLIB
    May 21, 2020 at 23:49
  • @occipita: I wouldn't say "entirely separate". According to Raffzahn's answer, the only real difference is that it won't do bypass forwarding of stuff like partial flags modifications, so the instruction after CLC has to wait because of the potential hazard if it reads (or writes?) FLAGS itself. If you think of the pipeline like a water slide, with a lifeguard at the top letting the next person go, it's just a matter of whether the lifeguard has to be careful to wait for an instruction to come out the end, or if it knows the pipeline can handle the special case it lets it go back to back. May 23, 2020 at 22:55
  • The last sentence of this answer is potentially a bit misleading. Previous x86 architectures didn't use microcode for the RISC-subset instructions, either. The instructions that were microcoded on 486 (e.g., LOOP) were microcoded on previous generations of x86. May 24, 2020 at 23:44
76

TL;DR: It's the pipeline.

The 80486 contains parallel operating stages for decoding, operand fetch, execution and write back. So while an ADD reg,reg does take 3 clocks to perform, as it did in the original 8086, its execution overlaps with the previous/next operation, so the CPU can crank out one ADD reg,reg per clock.

The Long Read

(Caveat, there's a whole lot more going on in these stages, so I might not remember all details)

Two clocks was the minimum execution time for any instruction since the 8088. ADD (or similar) as register/register did take 3 cycles. With the 286 they were sped up to two cycle operation due to optimized microcode and routing (*1). While the 386 did speed up execution even more due to better address generation, the basic execution scheme stayed the same with a 3 stage instruction prefetch.

The 486 did straighten address generation even further, but more importantly, it redid the instruction fetch architecture, introducing a 5-stage pipeline.

  • Fetch - Get 16 bytes from Cache (or memory)
  • Decode 1 - Primary instruction decode - here only the first 1..3 bytes were examined
  • Decode 2 - Address decode/Operand prefetch
  • Execution - Do the Operation
  • Write back - Store data in register file (or memory buffer)

Operation of these stages is conditional, but they will always be taken (passed thru) to keep a constant clock rate.

  • Fetch always gets a cache (memory) line of 16 byte at a time into one of its two buffers. Thus on average it only needs to do so every 4-5 instructions. This time is available to prefill the second buffer. Thus the 486 can keep up fast execution even without cache as long as the code is linear.
  • Decode 1 looks at opcode and addressing field to determine the action to be taken, feed back the real instruction length to the fetch stage (so it can advance), and set up the operation that D2 performs. Of note here, each prefix byte is handled as an instruction of its own, taking another clock cycle (*2,3). Similarly for the first byte (e.g. 0Fh) of a two byte opcode.
  • Decode 2 decodes operand access, fetches offsets or immediate values from the fetch stage's buffer. It also performs address calculations. Simple ones within a single cycle, complex ones need two.
  • Execution. If all data is present as either immediate, or in cache, or from the register file, the execution performs in a single cycle.
  • Write Back stores either the read value (memory, cache or Fetch buffer) or the latched results from Execution into the registers and or memory write buffers.

So a single ADD reg,reg still needs 3 cycles but due to pipelining the CPU can finish one every clock.

But wait, what happens if there are two ADD reg,reg, the second needing the result of the previous? Like ADD AX,BX; ADD CX,AX. Wouldn't that mean the second has to wait in execution until write back of the first is finished?

Well, yes, except the engineers added a shortcut/feedback between Write Back and Execution. The WB input, the result of the previous Execute, is not only written to the register set, but as well offered back to Execution as alternative input (tagged with what the target was). So if one instruction needs to read a register that was the result of the prior instruction it's available even before it gets written into the 'real' register.

Cool, isn't it? But it gets even more tricky. The flags of any operation done in Execute are also provided back from Write Back to be used in the next instruction. This is done to enable a one cycle not-taken jump after flag modifying operations like compare or subtract.

Despite that optimization, status register manipulating instructions do not benefit from this shortcut to avoid implications. Direct status register manipulations don't go through WB but are two step microinstructions handling the Status Register directly. This is important as status register settings not only change over all operation behaviour but also can generate various exceptions.


It's beneficial to remember that the 80486 is not a RISC CPU and does not translate internally to RISC(-like) instructions, but continues to operate like their predecessors. Just with a very cleverly designed pipeline with feedback between various stages (*4) and parallel operation of address calculation units. While The Pentium (P5) introduced superscalar operation (through parallel pipelines), it wasn't until the P6 (Pentium Pro) that a code translation to a (somewhat) RISC like core happened - but even then lots of un-RISCy side channels were used.

Following designs really expanded on that, with the pipeline growing to 10 stages in Pentium III, 20 for the first Pentium 4 and IIRC 40 (!) stages for the last Netburst CPU, before going back to a simpler but massively parallel design like the one P6 already had... But that's another story altogether.


*1 - The main gain of the 80286 in speed was a dedicated address calculation unit, but that's a different story.

*2 - That's why a LOCK takes zero cycles on a 286/386 but one cycle on a 486.

*3 - Somewhere I read a nice study made by Intel (IIRC) about prefixes and the pipeline. One really remarkable part was that Unix programs on average use only half as many prefixes as DOS programs. Proves my point how stupid it is to do address calculation with segment values.

*4 - Which substantially violates the simplicity criterion of RISC designs.

8
  • 2
    If the 80386 and successors had included a mode where segment values are 32 bits, with the top portion selecting a descriptor and the bottom being an offset with a scale factor controlled by the descriptor, then object-oriented frameworks like Java or .NET could have accessed many gigs of storage using 32-bit object references (that could e.g. select among a billion 16-byte-aligned objects within a 16GiB area, a billion 256-byte-aligned objects in a 256GiB area, and a billion 4096-byte-aligned objects in a 4TiB area). Using 32-bit references would then benefit cache efficiency.
    – supercat
    May 22, 2020 at 17:21
  • 2
    For future readers, forwarding the exec result back to the exec stage is called "bypass forwarding". Wikipedia's classic RISC article explains the basics of pipelining fairly well, including this. (As you say, it wasn't until P6 that the CPU could break a complex instruction like memory-destination add into separate parts. So tuning for 486 and P5 Pentium sometimes involved using the RISCy subset of x86, e.g. using more simpler instructions and avoiding memory dest or even memory src to avoid stalling the pipeline) May 23, 2020 at 22:27
  • @PeterCordes All true. Except it's noting related to RISC, but any kind of pipelining (as its occurrence with the 486 shows). In fact, most (early) RISC voted for dropping the complexity of backfeeding (I learned that term ca 1980 - way before RISC 'inventors' renamed it) and went for having the pipeline stall and/or put all blame toward compilers to make sure all dependencies have time to settle.
    – Raffzahn
    May 23, 2020 at 22:39
  • Right, I never claimed otherwise. Pipelining is easier for a RISC ISA, or for the limited subset of x86 that separates ALU from load or store (i.e. the RISCy subset of x86). That's why we happen to find a good write-up of forwarding in a Wiki article about classic RISC pipelines; IDK if there is an article about pipelining in general with that much detail. Anyway, classic MIPS R2000 did do bypass forwarding for ALU operations; I think it was widespread in that generation of classic RISC CPUs (MIPS / SPARC / etc.) Are you talking about earlier pipelined CPUs? IDK much about them. May 23, 2020 at 22:49
  • @PeterCordes I did learn about that stuff with mainframes around 1980. Classic CISC (and I mean real CISC, not x86) did use pipelined architecture already way before and did of course run into the same issues. With so many things in IT, everyone knows when something got reinvented at some ivory tower, but noone remembers that 99% has already been used way before in real world applications. Anyway, doing Assembly back then also included to think about register use in sequences, giving the pipeline time to 'settle' and so on.
    – Raffzahn
    May 23, 2020 at 23:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .