16

I'm poking at the Intel 8086/8088 (iAPX 86/88) User's Manual, which states on page 2-29 (PDF page 48), table 2-4, CPU State Following RESET, that the state of the CPU after the RESET pin rising followed by going low is guaranteed to be:

  • Flags = Clear
  • Instruction Pointer = 0000H
  • CS Register = FFFFH
  • DS Register = 0000H
  • SS Register = 0000H
  • ES Register = 0000H
  • Queue = Empty

Curiosly, this means that CS:IP points at physical address FFFF0H (1 MiB minus 16 bytes), and that the stack begins somewhere in the lowest 64 KiB of memory because SS (the stack segment register) is cleared, but SP (the stack pointer register) is not guaranteed to have any particular value. If SP happens to take on the value FFFFH on reset, then SS:SP becomes physical address 0FFFFH = 65,535, resulting in a possible range of physical address 00000H through 0FFFFH pointed to by SS:SP; thus 64 KiB.

Consequently, one of the first few things any CPU bootstrapping code would seem to have to do is to actually initialize SS:SP to some sensible value, probably immediately after the intersegment jump instruction recommended (page 2-29, PDF page 48) to be available at physical address FFFF0H. Only after SS:SP is explicitly initialized is it even remotely safe to perform any operations that use the stack.

This would be exacerbated by Intel specifically reserving the lower 128 bytes of the address space (0H through 7FH, page 2-16 figure 2-21, PDF page 35; also in text on page 2-15), so whatever code runs immediately on CPU reset must specifically make sure to not clobber that memory area.

Is my reading of the manual correct and Intel guarantees nothing about where SS:SP will point after RESET on the 8086 or 8088 other than that SS is 0000H, or am I missing something that is relevant here?

13
  • 4
    It's not unusual for a processor not to initialise its stack on power up. The processor can't know in advance there the ROM and RAM are going to be so why bother initialising whatever register is going to post to the stack?
    – JeremyP
    May 18, 2017 at 14:38
  • 1
    @JeremyP True, but then why guarantee a value for the SS register, which forms one half of the top of the stack pointer? (Or 20% of it, depending on how you look at things. The segment/offset to physical address conversion of the 8086/8088 was... funny?)
    – user
    May 18, 2017 at 14:40
  • Luck probably. |Interestingly I am writing a Z80 emulation at the moment. The documentation for that doesn't really guarantee the content of any of the registers except the instruction pointer, but real implementations tend to set them to predictable values.
    – JeremyP
    May 18, 2017 at 14:44
  • @JeremyP But the Z80 only had a 16-bit address bus, right? I see only a 16-bit stack pointer and program counter in Wikipedia's summary, and nothing similar to the 8086's segment registers, but will readily admit I haven't researched the topic in great detail. The 8086/8088 had a 20-bit address bus. And right; just because the initial value of a register isn't explicitly defined doesn't mean it wasn't predictable in practice, it just means that nobody went through the trouble of actually ensuring that it had some specific value after reset. Undefined NEQ random.
    – user
    May 18, 2017 at 19:12
  • 1
    I'm not actually sure what point you are trying to make. The 8086 is what it is. They don't initialise the SP because it's pointless. They do initialise the stack segment register for some reason - maybe because it uses the same electronics as the other segment registers and you obviously need a sane code segment. It doesn't have to be completely logical. My point was that not initialling the stack pointer at start up is a common thing to do.
    – JeremyP
    May 19, 2017 at 7:52

4 Answers 4

24

Your reading is correct, SP is undefined after a reset and the stack has to be set up appropriately by the initialisation code before it can be used. Interrupts are disabled after reset so there’s no risk of the stack being used by an interrupt (short of a NMI, but you’re in dire straits if that happens anyway at this stage).

The book I’m looking at just now (Robert L. Hummel’s The Processor and Coprocessor) gives the flags as F000 on the 8086/8088, not cleared (the top nibble, which isn’t used, is always set to F). On the 286 the flags are set to 0002 after reset, and likewise 00000002 on the 386 (bit 1 is always set to 1, all other reserved bits are set to 0, and all flags are cleared). Execution always starts 16 bytes from the top of the address space, FFFF0 on the 8086/8088, FFFFF0 on the 286, FFFFFFF0 on the 386.

The IBM PC BIOS is interesting to check in light of this question. The end of PCBIOS.ASM shows the code at the reset vector, which just does a far jump to the RESET label. The code there proceeds to trash all the registers: first the flags (START), with various tests to ensure the flags retain their values and, then the general purpose, segment, pointer and index registers (AX, DS, BX, ES, CX, SS, DX, SP, BP, SI, DI in that order, starting at C8). So the starting value of SP doesn’t matter.

A test further down helps understand why SP doesn’t matter in any case. Starting at C15, the DMA controller is set up to refresh memory — before that, RAM is effectively unusable... TEST.04 then tests the RAM in the system, C21 initialises the 8259 PIC and then sets up a temporary stack (to allow loading test programs from the keyboard — this is the first time a subroutine is used, although a RET using a fake stack in ROM happens earlier). At last at C25 the POST stack is set up, starting at 00030.

Memory usability at boot-up is even more problematic nowadays; synchronising the timing on all the memory in a modern system takes a little while. The early bootup in a modern CPU actually uses the CPU’s cache as memory to be able to store the data it needs to set the real memory up.

4
  • Thank you. And the flags being F000H is interesting, but inconsequential, since the top nibble of the flags register is unused on the 8086/8088 (user's manual page 2-7 (26) figure 2-9 and page 2-9 (28) figure 2-10).
    – user
    May 17, 2017 at 4:06
  • "Starting at C15, the DMA controller is set up to refresh memory — before that, memory is effectively unusable" ... well, it's usable as long as you don't want to be able to leave it around for longer than 4 milliseconds at any rate. And if system initialisation takes longer than that, I'd be seriously worried about what it was doing...
    – Jules
    Aug 8, 2018 at 22:34
  • 2
    @Jules: Full system initialization took over a minute on a 640K system, but the portion prior to that wouldn't.
    – supercat
    Aug 9, 2018 at 18:33
  • Even without the DMA control set up for DRAM refresh, I would expect anything in RAM that was accessed would remain valid for at least the next few milliseconds. Indeed, I would think a good RAM test should, with the DRAM controller disabled, program a pattern into RAM, execute a time-wasting loop at some address, confirm that everything reads back okay, then use another pattern and a time-wasting loop at a different address, and confirm the read-back, to ensure reliability even at a refresh interval longer than what would be used during operation.
    – supercat
    Feb 16 at 18:02
9

Yes, it might seem somewhat odd that Intel added extra logic to set the segment registers but not not the stack pointer; this means that even though you don't need to set up both the stack segmeent register SS and the stack pointer register SP to have a working stack, you still need to do some setup.

The decision to set the segment registers at start had a cost, too: extra gates are needed in the reset logic for each register that you want to have a predetermined value after reset rather than a random one. That leads to either removing other features you'd like to have or a bigger die and its consequent lower yield.

But it starts to make sense when you remember that:

Marketed as source compatible, the 8086 was designed to allow assembly language for the 8008, 8080, or 8085 to be automatically converted into equivalent (suboptimal) 8086 source code, with little or no hand-editing.[wp]

The 8080 didn't define the SP's value because there is no sensible default for it: where the stack can be depends entirely on the system address map design. This means that any 8080 code you "auto-port" to the 8086 will not be using the stack unless it has set up the SP first. But such code certainly won't be setting up the SS, because that didn't exist on the 8080. Thus, to make such auto-porting easier, and offer an alternative to changing the software, the system designer updating hardware to use an 8086 instead of an 8080 can simply design it to have a similar enough memory map to the 8080 system that the code that sets up the stack can be re-assembled for the 8086 without changes.

So why is 0000h a reasonable default for the SS? The default is only necessary for systems designed for auto-ported code (systems with new code would set SS at the same time as they set SP). On such systems it does force the designer to put RAM at the bottom of the memory map, but this is a reasonable enough place to put it anyway, since it doesn't actually matter what segement the RAM or ROM is in: the ported code assumed it was running in a single 64K address space anyway. This also allows you to re-use almost exactly the 8080 board's existing memory decoding logic on an 8088 system as well; you simply ignore or force to zero the top four bits of the 20-bit external addresses.

But why make the CS different, FFFFh instead of 0000h? Doing this gives a little more flexability with a new 8088 design intended to upgrade an existing 8080 design. The new design you must always have a little bit of new startup code at FFFF0 since the 8080 did not run code there at startup (it loaded the program counter with 0000h at reset), but there are two simple options for this depending on what you're trying to achieve with the upgrade.

If your new 8088 system still can live within a 64 KB memory space for RAM and ROM, you can re-use almost exactly the existing address deocding logic and minimize the amount of new logic you need to add for the 8088 by ignoring the top four bits of the 20-bit address bus, causing the second and subsequent 64K chunks of the address space to be mirrors of the first 64K chunk. The only thing you need to add is a bit of ROM at 0FFF0 with a new startup routine that loads CS with 0000 and does a JMP #0000, which would call the original (auto-ported) startup code.

If you need more than 64K address space in the new system (perhaps because that was the whole point of switching from an 8080 to an 8088, or you can't conveniently put any ROM at 0FFF0), you can put the new ROM outside that 64K address space or also map the original ROM to a new address at 10000 or above, giving you 64K of address space for RAM at 00000 and another 64K of address space for ROM above that. (This would be a fairly easy way of solving the problem of having an 8080 system that had run out of address space for RAM and ROM but was otherwise satisfactory.) In either case, your new startup code is as described above: load the CS with an appropriate value for the ROM location and jump to your original (auto-ported) startup code.

In the case of moving the ROM outside the lowest 64K of address space, if your code never ran data from the "data area," this should be a pretty simple conversion with almost all of the machine code re-assembled automatically from the original 8080 source code. (If not, you'll still probably have an easier time than doing a full rewrite.)

There are actually some further subtle issues with the above that I am ignoring here in the interests of saving space, but you (or anyone else) could ask another question specifically about this kind of conversion if you're interested.

3

This definitely assumes the upper 16 bytes of the address space is ROM. SS:SP must point to RAM, though. So what should Intel engineers assume for a valid RAM address? They didn't know, so assumed nothing and left it to the firmware writers - who had to initialize SP (and, probably, SS as well, even if that had a defined initial value) to some area actually containing RAM anyways. When the 8086 was designed, RAM was so valuable you couldn't simply assume some arbitrary value. On the other hand, you could most probably assume valid RAM in the vector area, thus lower memory area - because this definitely needs to be populated for interrupt vectors - Maybe this was the reason for SS=0.

Also, you can pretty easily write firmware that doesn't really need a valid stack (or even RAM, for that matter) and only uses registers.

When booting, the PC traditionally loads the MBR to 0000:7c00 and places the stack 512 bytes above that. So at least for that first boot stage, SS=0 is just fine.

The 6502, for example, assumed a fixed-size stack in the lower 1k of memory which makes the CPU a bit inflexible. I think "leave it to the designer" is much better and makes for a much more flexible memory map.

The Motorola 68k does a similar thing with vectors in the lower 2 longwords at address 0 and 4: These give the initial longword values for the program counter and stack pointer that are picked up by the CPU in the RESET cycle. So, the 68k CPUs didn't strictly guarantee initial values for PC and SP as well - Because of the differing maximum address ranges for various 68k CPUs (68008: 1MByte, 68000: 16MBytes) they couldn't simply put these values to the end of the address space which would have been more flexible.

Interesting side note unrelated to 8086/8088 CPUs:

With later CPUs, when Intel extended the segment registers to 32 bits and thus extended the CPUs address range, there's an interesting hack involved that made sure the BIOS could still live at the very end of the addressable memory (which was now much further up):

Even if the CPU starts in real mode, which actually has a limited address range of 1MByte, the CPU temporarily sets the upper (inaccessible in real mode) bits of ECS to 1 until the first long jump - This makes the CPU run code from a normally inaccessible memory area during the startup process.

8
  • I agree with the general gist of this answer, that you couldn't really assume (any significant amount of) RAM at any particular location, but I find it a little curious that Intel would guarantee one half of the pair to have a particular value. It's not like SS = 0000H is more likely than any other value to be all that useful for the programmer writing the CPU initialization code anyway, so the initialization code needs to set both SS and SP to some specific values anyway.
    – user
    May 17, 2017 at 8:14
  • My assumption would be that SS=0 was an initialization that somehow came for free during the CPU design with no other design effort than to put it in the manual. Agree that most firmware writers would have had to initialize both segment and offset registers anyways.
    – tofro
    May 17, 2017 at 8:19
  • 1
    "When booting, the PC traditionally loads the MBR to 0000:7c00 and places the stack 512 bytes above that." Yes, but that's long after CPU initialization, and well after completion of the POST. At that point, you are in the realm of the guarantees the BIOS makes to the first-stage OS bootloader, not the guarantees the CPU makes to the CPU initialization code.
    – user
    May 17, 2017 at 8:43
  • 2
    The CS extension hack dates to the 286, and relies on the shadow descriptors, not on “ECS”. The whole idea of having an initial address at the top of memory is so that ROM doesn’t have to sit in the middle of the address space, potentially making RAM management more complicated than necessary (as explained by Stephen Morse); of course the Intel designers thought of that, but then the PC memory map (and the requirement for backwards compatibility) made a mess of things... (LOADALL allows the shadow descriptors to be set too.) May 17, 2017 at 18:55
  • 2
    I strongly suspect that initialising the segment registers to zero was a conscious design choice: for a system with <64K of RAM (which would have been expected as a common configuration) it would allow the designers to effectively forget about the existence of them entirely, and just leave them completely untouched, thus simplifying porting existing 8 bit code. The 8086 design was all about simplifying movement from 8 bit systems, i.e. stuff that was using the 8080 or 8085.
    – Jules
    Aug 8, 2018 at 22:38
3

I think the SS was set to 0 because usually there was RAM at 0 (al least for the interrupt table) but SP had to be set to the end of the available RAM (since stack grows downwards) and this could only be done once its size was detected by the BIOS. Of course, if there was more than 64K of RAM, one could use a non-zero SS but I guess it was not a very common situation back then, and not having to change SS from the default probably saved a couple bytes.

2
  • 1
    The fact that the stack grows downwards doesn’t imply that it has to start at the top of memory; you can place the stack anywhere (that’s not used for something else), you just point SP at its top. May 18, 2017 at 6:07
  • 1
    yes, maybe "at the top of the stack region" would be a better way to say it but it often was at the top of RAM. May 18, 2017 at 17:00

You must log in to answer this question.

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