Computing History · Microprocessor Revolution
Segment Arithmetic and the One-Megabyte Wall
How sixteen-bit segment registers produce a twenty-bit address, why the resulting space wraps at the top, and what the A20 gate and the high memory area cost.
The 8086 has sixteen-bit registers and a twenty-bit address bus. The gap is bridged by computing an address from two registers rather than holding it in one, and the arithmetic that does this is simple enough to perform in a few gates. Every consequence of real-mode addressing follows from that arithmetic, including the fact that the address space is not a clean 1,048,576 bytes but a set of overlapping windows onto it that wraps at the top.
Two registers, one adder, twenty bits
A segment register holds a sixteen-bit value that the processor treats as the high part of an address. The offset is a second sixteen-bit value. The physical address is the segment value shifted left by four bits, plus the offset, truncated to twenty bits. Shifting by four multiplies by sixteen, so a segment value of 0x1000 places its base at physical 0x10000, and an offset of 0x0020 within it addresses physical 0x10020.
The shift is not a design flourish. Multiplying the segment by sixteen gives a reach of 65,536 bytes from each of 65,536 possible segment bases, and the overlap between adjacent segments is fifteen sixteenths. Segment 0x1000 and segment 0x1001 both cover physical 0x10010, the first at offset 0x0010 and the second at offset 0x0000. The overlap is what lets a program address any byte in the space without ever loading a new segment: from a base, 64 KB is reachable, and stepping the base by one paragraph moves the window sixteen bytes.
The alternative designs were considered and rejected. A flat twenty-bit address in a pair of registers would have required the processor to treat the pair as a single value, which doubles the width of every address computation and every pointer. Using a page table would have required memory for the table and a translation step per access. The segment-plus-offset scheme computes the address with one shift and one add, and the shift by a constant is free in silicon because it is a wire renaming.
The cost is that a pointer is not a number. Two segment-offset pairs can name the same byte, so comparing two pointers for equality requires normalising them, and a structure that contains pointers cannot be relocated by adding a constant to its address, because the segment part and the offset part scale differently. A 64 KB object cannot cross a segment boundary without the program managing the split, and a single array larger than 64 KB cannot be addressed with one offset at all.
The wrap at the top of the space
Twenty bits of address can name 1,048,576 bytes, and the arithmetic that produces them is truncated to twenty bits, so an addition that carries past the top wraps to zero. The pair segment 0xFFFF, offset 0x0010 computes to 0x100000, which the truncation turns into 0x000000. A program that places a segment base at the top of memory and indexes past the end reads the interrupt vector table instead of the byte it wanted.
The wrap is a documented property of the processor, and it is not an error condition. Code that relies on it, or that relies on it not happening, is relying on the address arithmetic rather than on memory contents. The 80286 has a twenty-four-bit address bus and can address sixteen megabytes in protected mode, but in real mode it emulates the 8086, which means it must also emulate the wrap. The emulation is done by a gate on the twentieth address line, and that gate is what the term A20 refers to.
Holding A20 low forces the twentieth address bit to zero, so any access whose computed address exceeds 0xFFFFF is redirected into the first megabyte, reproducing the 8086’s truncation exactly. Releasing A20 lets the bit pass and the processor can reach the region from 0x100000 upward. The gate exists so that a program written for the 8086, which might have a segment base near the top of memory and an offset that overflows, continues to behave as it did.
The high memory area as an accident of the gate
With A20 released, the address pair segment 0xFFFF, offset 0x0010 computes to 0x100000 rather than wrapping to zero, and the next 65,520 bytes above the megabyte are reachable from that single segment value without changing it. That region is a little under 64 KB and it exists only because the segment base 0xFFFF is the last one, and the offset can extend past the base’s 64 KB window when the wrap is disabled.
Nothing in the architecture was designed to provide it. It is the residue of the 8086’s truncation being removed while the segment arithmetic stayed the same. Software used it anyway, because it was the only way to reach memory above 1 MB from real mode without switching the processor into protected mode. A program that wanted a large buffer could place it there and address it with an unchanged segment register. Combined with the first megabyte, some memory is addressable from real mode over the range from zero to 0x10FFEF, although the top region is usable only when no device occupies the addresses just below it.
The region’s usefulness depends on the gate being open, which makes the gate a resource that programs must negotiate. A program that opens A20 and leaves it open breaks any 8086-era code that relies on the wrap. A program that closes it breaks any program using the high region. The convention that emerged was that software opening the gate should restore its previous state when done, and that a program needing the gate for its whole lifetime should leave it open and accept that the machine is no longer 8086-compatible.
; Enable A20 through the keyboard controller's output port.
; The sequence reads the current output register, sets bit 1, and writes it
; back, so the other bits of the register are preserved.
a20_enable:
call wait_input ; controller input buffer must be empty
mov al, 0xD0 ; command: read output port
out 0x64, al
call wait_output ; wait for the controller to answer
in al, 0x60 ; current output register value
mov bl, al ; keep it for the write-back
or bl, 0x02 ; bit 1 controls the A20 gate
call wait_input
mov al, 0xD1 ; command: write output port
out 0x64, al
call wait_input
mov al, bl ; write the modified value
out 0x60, al
call wait_input
ret
The controller route is one of several. Later chipsets provided a fast gate at an I/O port, and firmware provided a service that hid the mechanism. Each route has different timing and different behaviour when the machine has no keyboard controller, which is why code that needed the gate tried the fast method first and fell back.
Segmentation as a protection mechanism it never was
Real mode has no privilege levels and no segment limits. A segment register holds a base, and the offset is added without a bounds check beyond the sixteen-bit wrap. Every address in the space is writable by any code, including the interrupt vectors at zero and the firmware’s data areas. The segment registers therefore provide addressing convenience only; they do not isolate anything.
The 80286’s protected mode reuses the same registers for a different purpose. A segment register holds a selector, which is an index into a descriptor table, and the descriptor holds a base, a limit, and access rights. The address computation is the same shift-and-add, but the base comes from the table and the offset is checked against the limit. A program that exceeds its segment’s limit faults rather than reaching memory it should not have, and a program that loads a selector naming a segment it lacks the privilege for faults at the load.
That reuse is why protected mode was adopted gradually. The instruction encodings stayed the same, so a program could be recompiled with a different memory model rather than rewritten, and the transition from real mode to protected mode could be made by loading a table and setting a control bit. The difficulty was that everything the program depended on, from the interrupt vectors to the firmware services, was built around real-mode assumptions. The interrupt table is a table of four-byte entries at zero; in protected mode it is a table of eight-byte gates whose location is held in a register. A service that switches modes must rebuild both.
The practical consequence is that the one-megabyte wall was not a hardware limit that software could ignore. It was the boundary of the mode the software ran in, and moving past it meant changing the mode, which meant rebuilding the environment the program ran in. The next layer of software had to decide whether to do that, and how much of the real-mode world to keep available afterwards, which is the design problem that DOS extenders exist to solve.
The wall also constrained ordinary programs in ways that are easy to overlook. A program that allocates buffers has to fit them below the megabyte alongside the operating system, the display memory, and the firmware’s own data areas, and the amount actually free is typically a few hundred kilobytes rather than the full megabyte. That scarcity is why the interrupt vector table’s fixed position at the bottom of memory was a constant in every memory map: nothing could be placed there, and nothing could be moved.
References
- Intel 80286 Programmer's Reference Manual — Intel Corporation, 1987
- IA-32 Intel Architecture Software Developer's Manual, Volume 3 — Intel Corporation
- IBM Personal Computer Technical Reference — IBM Corporation, 1981