12

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?

  • 2
    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 '17 at 14:38
  • @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?) – a CVn May 18 '17 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 '17 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. – a CVn May 18 '17 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 '17 at 7:52
12

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, memory 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.

  • 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). – a CVn May 17 '17 at 4:06
  • 1
    I’ve added more info based on reading the PC BIOS code. It turns out SP doesn’t matter at boot anyway, because the system’s RAM isn’t usable! – Stephen Kitt May 17 '17 at 15:01
  • "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 '18 at 22:34
  • @Jules: Full system initialization took over a minute on a 640K system, but the portion prior to that wouldn't. – supercat Aug 9 '18 at 18:33
  • "before that, memory is effectively unusable" may be better written to refer to RAM rather than memory (which includes ROM). If ROM wasn't usable, we'd have a hard time booting up :-) – user6464 Oct 8 '18 at 2:44
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.

  • 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. – Stephen Kitt May 18 '17 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. – Igor Skochinsky May 18 '17 at 17:00
2

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.

  • 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. – a CVn May 17 '17 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 '17 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. – a CVn May 17 '17 at 8:43
  • That's just a (popular) example that shows PC designers assumed RAM there. For this assumption it's really irrelevant where in the boot process it's taken. RAM won't magically appear there ;) at this boot stage. The PC's memory map is by far not the only one in the world, anyways. A lot of embedded x86 systems I came across actually had ROM in the lower 64k and couldn't place the stack there – tofro May 17 '17 at 8:47
  • 1
    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.) – Stephen Kitt May 17 '17 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.