| ⭅ Previous (The 8088 CPU) |
Unique features of the 8088 CPU
We continue on our effort to build an 8088 CPU emulator, as well as emulating the rest of the original IBM PC. Last time we looked at some high level features of the 8088 CPU, and compared it with the (somewhat contemporary) MOS 6502 CPU.
One notable difference was the much more flexible instruction format of the 8088, which allowed it to support far more instructions. In this article we will look at a few ways this chip made use of its expanded instruction set, providing features that were not common in other CPUs.
MOV used in examples
For the next few features, we’ll use the move instruction aka ‘mov’ to explain a few ideas. This instruction moves a value from somewhere into somewhere else. MOV is very flexible, and can take from registers, memory, or immediates(constants encoded into the instruction).
I’ll be using “Intel syntax”, which puts the destination first. So:
mov ax, 0xCAFEMeans moving the immediate value 0xDA into the register AX.
Segment registers: 20 bit addresses from 16 bits.
In the last article we saw that the 8088 has 16 bit registers. With 16 bits, one is able to represent 65K different values. Given that the memory is byte-addressable, each position in memory only holds a byte. This would suggest only 65K bytes of usable memory. Yet we saw the 8088 actually has a 20 bit address bus, allowing access to about 1M bytes of memory.
The 8088 makes this possible via segment registers. The 8088 has a few special purpose registers, which are used for pointing to “segments” of memory.
To get a physical address, a 16 bit segment register is merged with a 16 bit value(from another register or an immediate, for example) according to:
PHYS = (SEGMENT << 4) + BASEThe notation SEGMENT:BASE is often used for referring to 20 bit values. So if we wanted to write a byte 0xFF to the address 0xD ECAF, we could set a segment register to 0xD, and use an offset of 0xECAF.
In 8088 assembly this might look like:
; immediate values cant be moved directly to segment
; registers, so we go -> bx -> cs
mov bx, 0xD
mov cs, bx
mov bx, 0xECAF
mov cs:bx, 0xFFSegment overrides
In practice, the 4 segment registers have typical usages, though some can be used arbitrarily.
| Segment | Name / Typical Purpose | Automatic usage |
|---|---|---|
| ES | “extra” | used by string ops |
| CS | “code” | instruction fetches come from CS:IP |
| DS | “data” | The default segment for most memory reads |
| SS | “stack” | push/pop point to memory at SS:SP |
For operations which include a register as part of an address, the relevant segment will be selected automatically. Most registers implicitly use the data segment. The exception is that addresses built using the stack pointer are likely referring to the stack, and thus default to the stack segment. An example might make this more clear:
mov bx, [ax]
; same as:
; mov bx, [ds:ax]
; and some assemblers will require this notation
; where as:
mov bx, [sp]
; is the same as mov bx, [ss:sp]By having a default segment, typical usage can be encoded efficiently into the instruction bytes. But for increase flexibility, the 8088 allows a segment override. This is an extra byte that comes before an instruction, and overrides the segment used for the next instruction. This allows us to write code like
; Reads using the code segment, not the default data segment typically used
; for ax.
mov bx, [cs:ax]This flexibility comes at a cost, as the “segment override prefix” takes another byte to represent these 4 possible segments.
IO Ports vs MMIO
Segment registers show the extent that the designers went through to enable access to 1 meg of memory with the 8086. You might not be surprised then, that the designers added a new way to talk to hardware peripherals without losing memory space.
On systems with a MOS 6502, hardware was typically accessed through Memory-Mapped IO, or MMIO. In this design, certain addresses refer not to memory, but hardware devices. Special hardware on the system board is responsible for activating the device when its address(es) are mentioned. The device can then read or write to the bus, as though it were a memory device itself.
The downside of MMIO, however, is that it takes away addresses available for memory. Hypothetically, MMIO address 0xCAFE no longer refers to a byte in memory but an interaction with a sound card.
To eliminate this, the 8088 has the concept of IO ports. It provides two instructions, IN [port] and OUT [port], specifically for interacting with an IO device.
This comes at some reduced flexibility for talking to IO devices, since no longer can one use any other instruction that writes to memory. But the upside is that the full 1M of memory can be used for memory.
Loops in hardware: String ops and REP
Perhaps one of the most powerful features of the 8088 is the support for hardware loops. For any non-programmers reading, a loop is a way to repeat a sequence of steps multiple times. This could be used in the “Find” dialog, for example, which checks over and over if the string you are looking for exists at a certain position in your document.
The 8088 has two features that help these repeated operations run quickly. First, is a collection of “string operations”, which operate on arrays or strings of values. We’ll look at just one to get a feel for these.
This instruction is CMPS, or compare string. As with many operations on the 8088, it supports both byte and word(16 bit) variants.
We’ll look at the 8 bit version, cmpsb, for simplicity. Its purpose is to compare two bytes, then advance both pointers to the next
byte in their array. It sets some status bits that can be used to check if the two bytes were equal.
cmpsb allows comparison logic to be implemented very concisely. Instead of loading two bytes, comparing, and incrementing a pointer, we can do this with one instruction.
; Without cmpsb
mov al, [si]
mov bl, es:[di]
cmp al, bl
inc si
inc di
; With cmpsb
; ds:si and es:di are implicit arguments.
; pointers are updated automatically
cmpsbSince string operations are frequent in many programs, this makes it both easier to write and faster. cmpsb is a single byte instruction, vs the roughly 5 for the above, which reduces the amount of instruction bytes fetched and read.
And the 8088 goes even further, allowing a single isntruction to say ‘repeat cmpsb until no more bytes or mismatch’. Two variants are supported, repne for repeat while not equal, or repz for repeat while equal (zero flag=1).
This would be:
rep cmpsbString instructions allow one to represent a common instruction sequence in a single instruction. And the rep allows one to eliminate
the jumping and fetching of code bytes, which can further improve performance.
For further reading, Abrash’s Zen of Assembly Language has a great chapter on the string operations.
Next up: BIOS
While its hard to cover all the subtleties of the 8088 in an article series, hopefully these last few articles gave a good taste for what made the 8088 and interesting CPU.
Next up we’ll be looking at BIOS, a sort of proto-OS that you may have encountered even today.
If you enjoyed this article and want to be notified of updates, consider joining our newsletter below.
| ⭅ Previous (The 8088 CPU) |