38 comments

[ 2.7 ms ] story [ 96.1 ms ] thread
Author here if anyone wants to discuss the internals of the 8086.
Do you know if NOP is really executed as `XCHG AX, AX` (as would be implied from its position in the opcode map and its cycle count) or if it is special-cased in some way?

I've always been curious about the XLAT instruction. It performs an operation like `MOV AL, [BX + AL]` (if that were a valid instruction). I assume this instruction was designed to do EBCDIC <-> ASCII conversions using a simple loop consisting of `LODSB / XLAT / STOSB` which would explain why the input and output are both hardcoded to AL. But it seems like it would have been fairly easy to allow the programmer to specify different register(s) and the instruction probably would have been a lot more useful generally if that was allowed.

It is the only place in the ISA that I know of where an 8-bit value is zero-extended to 16 bits before being added to another value. Short jumps use a sign-extended 8-bit value (and are a special case anyway since they deal with the instruction pointer). Other additions are always 8+8 or 16+16. Is there some special case in the processor to allow just AL to be zero-extended in this way? If so, that would go some way toward explaining why this instruction isn't more flexible. I realize this may be very difficult to determine working from a die photo, at least without completely reverse engineering the entire microcode ROM.

Yes, NOP is just an XCHG AX, AX. For instructions such as XCHG, the microcode doesn't know anything about the specific registers; hardcoded circuitry substitutes the registers from the instruction. This saves on microcode size. Microcode also doesn't know about 8-bit vs 16-bit instructions; that is also done in circuitry.

As far as XLAT, I haven't seen anything yet to answer your question. I hope to figure out the microcode, which should provide more details.

One theory on XLAT is that it's there to make it easier to adapt code originally written for 6502, where the "indirect indexed" addressing mode was popular. The zero-extended 8 + 16 addition is the same, and this would otherwise require a sequence. For example, to do code page translation on 6502 you'd definitely use indirect indexed at the core. But yeah, this is an oddball instruction, one of the most non-RISC-like in the set.
The 8086/88 does in fact execute 90h (the NOP opcode) as XCHG AX, AX. It does not generate any change in the user-visible architectural state, however, and so it appears to be a NOP.

[Internally the CPU uses the "TMP" register as a temporary storage location during execution of the XCHG operation, and so the NOP will clobber whatever value was previously held there.]

One can observe two of the internal registers ("TMP" and "IND") using an invalid JMP FAR m32 opcode. Normally this is encoded as opcode FF/5 with a mod-rm byte that specifies a memory location (mod=00, mod=01, or mod=10) from which to fetch a doubleword CS:IP pointer. Specifying a mod-rm byte with mod=11 (register) - which is an undefined encoding - skips the "load doubleword from memory" microcode subroutine and causes the CPU to jump directly to the location IND:TMP instead, allowing one to observe the values held in these internal registers.

At some point I'll get around to writing this up in more detail, as I don't think anyone else has ever described the behaviour of this particular invalid 8086 opcode.

At some point I'll get around to writing this up in more detail, as I don't think anyone else has ever described the behaviour of this particular invalid 8086 opcode.

I'd love to see more about that --- there has been plenty of documentation on "first-byte" undocumented opcodes, but far less research and information on undocumented operand combinations/groups and such. In particular, the infamous FF FF (that gets disassembled by DEBUG and a few other assemblers as "??? DI" ) falls in that area, as does LEA with a register operand ("LEA AX, AX") and several others that don't immediately come to mind.

If I remember correctly, on the Z80 some of the hidden architectural registers' state was also visible as undefined flags. What you've described sounds very similar to that.

> LEA with a register operand ("LEA AX, AX")

Yep, reading about this (from a post that I can't find at the moment) was one of the things that inspired me to try illegal/undefined mod-rm byte encodings.

How did you figure this out? I'll keep my eyes open for this in the microcode.
I was inspired by a post from Raúl Gutiérrez Sanz [1] that described some of the undocumented 8086 opcodes, and was tantalizingly titled "Part I". Unfortunately it seems that Parts II and III were never posted.

I had also seen a comment (which I cannot find at the moment) describing the behaviour of LEA r16, m16 when it's given an r16 source operand (so the mod-rm byte has mod=11): the value loaded into the destination register is the value of the internal "IND" register, i.e. whatever memory address the CPU had most recently calculated.

So I tried all of the undefined mod-rm encodings (especially the instructions where the 3 'reg' bits encode a sub-opcode) and mostly just found a bunch of aliases for existing instructions. But the FE and FF opcodes -- which encode JMP and CALL -- would randomly crash my test code in strange and beautiful ways.

Single-stepping the execution of the opcode would crash the DOS 2.1 version of DEBUG.COM, so I ended up writing my own minimal INT1 handler to capture the value of CS:IP immediately after the malformed JMP or CALL, dump it to the screen, and then restore a sane CS:IP before exiting single-step mode.

My hypothesis (from a close reading of the 8086 patent) is that the group decode ROM marks which opcodes have a mod-rm byte and the microcode has a sort of subroutine call (triggered by mod != 11) that calculates the EA and leaves the result in the hidden temporary registers before falling through to the instruction-specific microcode. Skip that subroutine (using mod=11) and interesting things happen.

[1] http://www.os2museum.com/wp/undocumented-8086-opcodes-part-i...

Great work. I used to keep the MSDOS 2.11 version of debug.com too. All the debug.com goodness in a tiny .com file compared to bloated more recent debug.exe versions. Of course you had to patch it (with debug) to stop it exiting immediately due to the wrong OS version.
Have you also tried JMP SHORT +0 as an alternative NOP?
I don't really think there is a need for an "alternative" NOP, because the XCHG side effect (of clobbering the TMP register) is of little practical concern. In fact, as far as I have been able to determine, the malformed JMP/CALL FAR m32 operation -- which isn't even remotely useful! -- is the only way that code is even able to 'observe' the TMP register in the first place.

For all practical purposes 90h is indeed a NOP, despite the fact that the CPU actually executes the opcode.

I have written code for a cycle-exact 8088 emulator https://github.com/reenigne/reenigne/blob/master/8088/xtce/x... which handles all the invalid opcodes the same way that real hardware does. There is more potentially-useful way of observing TMP and IND that doesn't involve jumping to code that you might not control: use the LDS or LES opcodes with mod=11.
Hmm... I thought that would be the case, but when I tested it [on an AMD 80C88], LDS/LES with mod=11 just skipped the EA calculation subroutine and then proceeds as normal, loading a doubleword from whichever memory address happens to have been left in the IND register.

But I'll probably go back and re-test LDS/LES more thoroughly at some point just to make sure I haven't missed something [or more likely, that I'm not misinterpreting my scattered notes].

By the way, I really appreciated your detailed bus sniffer logs of the 8088 executing various instructions. It was enlightening to read through the traces and helped me understand what was going on "under the hood" of the CPU.

It might be different on an Intel 8088. What I saw was two bytes loaded from the bus (at the address that was left in the IND register) placed in ES or DS, and the offset part was loaded from a second hidden register that contained the previous word read from or written to the bus (excluding instruction fetches).

Glad the sniffer logs were useful! I have been using them pretty regularly for debugging and profiling things.

Thank you very much for this series of articles. Please continue with them!

One thing that I would love to see eventually is a series of comparisons with the 8088.

I've looked briefly at the 8088. As you know, the 8088 is a modification of the 8086 with an 8-bit bus instead of the 16-bit bus. Here's a quick summary of the differences:

Looking at the dies, the 8086 and 8088 are completely identical over most of the die. For the most part, I can look at the 8088 die if something is unclear in my 8086 photo.

The biggest difference is the bus control logic, which is entirely different (as you'd expect). This is in the upper-right corner.

The 8088 has a 4-byte instruction queue instead of a 6-byte instruction queue, so there's a bit of wasted space in the register file there. The queue registers and control circuitry are slightly different because the 8088 needs to access half of the word at a time.

I saw a couple of microcode differences, but I don't know if they are related to 8086 vs 8088 or are bug fixes.

Some of the pin driving circuitry is different, as you'd expect, since only 8 pins are used for data.

To summarize: is the 8088 a completely redone chip? No. Are they the same chip with a jumper? No. Most parts of the 8086 and the 8088 are the same, but some parts were completely redesigned.

Have you discovered any new opcodes?
No, but see kiwidrew's comments that discuss undocumented opcodes.
If the 8086 uses microcode to implement its ISA using simpler operations, could a CPU that does away with the translation layer theoretically be made & if so what would the simpler underlying language or microcode look like?

What portion of the chip handled the translation?

The 8086 uses "traditional" microcode, where microcode is executed to implement the instruction. Modern x86 processors are a bit different, translating an instruction into micro-ops which go into the instruction pipeline. So the 8086 may not be translating in the way you're thinking.

In any case, look elsewhere in these comments for a discussion of the NEC V33 processor, which replaced the 8086's microcode with hardwired control.

As for the 8086's microcode, each micro-instruction is 21 bits: 5 bits for a source register, 5 bits for a destination register, 3 bits for a "type" field, 7 bits for two more fields, and 1 bit to control the flags.

The microcode ROM is the big rectangle in the lower-right of the die photos.

I had a couple of questions. The first was about the blown up region on the right in this photo:

http://static.righto.com/images/8086-alu/die-labeled-alu.jpg

Those silicon shapes are pretty fascinating and each seem unique with respect to the other? How do they come up with these? Are these just the product of signal routing and minimal surface area needed for the circuit? Almost like a reverse puzzle?

My second question was about the right side of this photo:

http://static.righto.com/images/8086-alu/inverter-diagram.jp...

What are the purple lines and green regions here?

For your second question, I believe those lines are explained a bit further down the page: "The reddish horizontal lines are remnants of the metal layer, which was removed for this photo. These lines carried the control signals, power, and ground."
Thank you! I'm not sure how I missed this other than there's a lot of detail to absorb in this post. Cheers
That title looked familiar, so I checked, and it looks like we've had recent submissions from the same site for reverse engineering of different parts of the 8086:

https://news.ycombinator.com/item?id=24092605

https://news.ycombinator.com/item?id=24021415

Yes, I've been working through different parts of the 8086 lately. My goal is to understand the microcode. I hope people aren't getting tired of the 8086 :-)
Maybe the NEC V33 would be interesting to compare. 8086 compatible, but all done with regular logic, no microcode.
That's pretty interesting. How does the transistor count compare?
I'm surprised that NEC would do away with the microcode, since there is a lot of microcode in the 8086. It would be interesting to see how much circuitry it took to replace the microcode.
Supposedly that made it twice as fast at the same clock speed.
Someone (not me!) edited

https://en.wikipedia.org/wiki/NEC_V20

to say

"The NEC V33 is a super version of the V30 that separates address bus and data bus, and executes all instructions with wired logic instead of micro-codes, making it twice as fast as a V30 for the same clock frequency. V33 has the performance equivalent to Intel 80286. NEC V33 offers a method to expanding the memory address space to 16M bytes. It has two additional instructions BRKXA and RETXA to support extended addressing mode. The 8080 emulation mode was not supported."

> I hope people aren't getting tired of the 8086 :-)

On the contrary! These posts are fascinating and captivating!

No, quite the opposite. I look forward to reading your news posts. And this is generally how I find out about them. Please keep up the great work.
From note #13:

> The silicon implementation of the lower eight bits of the ALU / registers is flipped compared to the upper eight bits. The motivation is to put the ALU signals next to the flag circuitry that needs these signals.

This caught me out because I was expecting some more obvious rotation or discernible mirroring of the upper half and lower half. Is the layout of bits 15:8 entirely different yet logically equivalent? Looking closely though I do see some similarities in the outer circuits at the very top and very bottom, yet there is a large gap across the lower bank.

The top and bottom have almost identical circuitry, even though the layouts look entirely different. The layout of the ALU is highly optimized; since everything is repeated 16 times, squeezing out a bit of space makes a big difference.

I assume the layouts are different due to the surroundings: The top stages have two bus wires next to them, while the bottom stage just has one bus wire, so there is a bit more room. Some of the horizontal metal wiring is different (like the wiring in the gap you saw), and power and ground may be offset slightly. The top stage needs to fit with the register interface circuitry just above it. My suspicion is that these relatively small changes resulted in the layouts being visibly fairly different. Maybe I'll do a gate-by-gate comparison at some point to check.

This is fascinating. Really enjoyed reading this as a lay-person. The only bit I struggled to follow was matching up the diagrams with the actual photomicrographs of the circuits - wasn't quite sure what was meant by 'pullup' - is that just a straight connection? Have you any plans to do something similar for the 68000?
pullup resistors.

When the switch is closed, it creates a direct connection to ground or VCC, but when the switch is open, the rest of the circuit would be left floating (i.e., it would have an indeterminate voltage). For a switch that connects to ground, a pull-up resistor ensures a well-defined voltage (i.e. VCC, or logical high) across the remainder of the circuit when the switch is open. Conversely, for a switch that connects to VCC, a pull-down resistor ensures a well-defined ground voltage (i.e. logical low) when the switch is open.

https://en.m.wikipedia.org/wiki/Pull-up_resistor