10

A Z80 on RESET, starts executing from 0x0000. This is why the ZX machines and so many others have ROM at the bottom of the address space. Presumably an 8080 is the same. But as I recall, a CP/M binary is loaded into memory from address 0x0100 and run from there. Why should these be so close together?

It means that someone who is just designing a new CP/M computer is forced to put some kind of bankswitching in place, just to be able to boot the thing. If I wanted to design an operating system, I wouldn't put the application's entry point in read-only address space.

Have I got this wrong? What is the rationale behind this decision?

10

The 8080 that CP/M was originally designed for has very limited possibilities to produce code that can run anywhere in memory - Everything has to have fixed addresses. This results in the need that CP/M systems need to be recompiled for every new memory size and address map - It is convenient to do that for the system only once and not for applications that you want to be portable between systems, after all, you wanted one WordStar binary running on any system - Thus, the applications need to go into low memory, starting from some fixed address, regardless of maker, memory size, and architecture of the system.

The first CP/M computers had RAM sizes starting from ~16kBytes (CP/M 2.2 needs about 20kB). So, CP/M, aiming to be a portable, "one-size-fits-all" operating system, had to assume some memory map. The main RAM area a bare-bone CP/M system needs for its own purposes is the sector buffer, sized 128 bytes, that has to end up somewhere. CP/M decided to put that at 080H by default. That left the first byte to be used by user code (thus, the TPA) at 0100H.

Below that (0-80H), CP/M puts the BDOS vector and a stub for the reset code (8080 starts execution at 0 after reset), so you had to have a limited amount of ROM that could overlay at least the first three bytes (JMP into the BDOS). At 5CH, you would normally see an FCB (the second main CP/M data structure), also all the other administrative data structures CP/M needs reside in the lower 80H bytes.

So, to sum it up, the space for applications had to start in low memory, at a fixed address that would be possible to implement in all architectures. They couldn't start at 0, because the OS had to be at 0 to be run after a reset, and they knew they needed at least the FCB, the vector area, and at least one sector buffer plus a minimum set of administrative variables - Thus 100H was simply the lowest possible address user code could start from.

In order to have the CPU see ROM (thus, "code") at address 0 at cold boot, you could use a number of tricks (well not tricks, but rather pretty common strategies). The luxury version was to have a paged-in cold-boot ROM (several kBytes) starting from 0 shadowed by writable RAM - This ROM would then load and set up CP/M from disk, and once done, be paged out, leaving an all-RAM system.

The "not-so-luxury" version would have been a permanently paged in ROM at higher addresses (say, 08000H, for simplicity), and a piece of circuitry that would fiddle with the address lines at cold boot to trick the CPU into running that ROM (In my example, that would be a simple flip-flop that switched address line A15 to high). The first instructions in that ROM would then need to be a JMP to 08003H where you would find code that switches that flip-flop. That way you would be able to have the ROM permanently paged in (but obviously this part of the address map is now occupied and limits the amount of available RAM for CP/M and applications)

  • That means they needed a 128 byte ROM to fit the reset code and BDOS vectors? Was such a part available/common? – Wilson May 11 '18 at 8:12
  • 2
    Most CP/M system didn't have a permanent memory map - They had an initial loader built into, say, some kBytes of ROM starting from 0, but shadowed with RAM. They would come up from ROM, load and set up CP/M from a disk boot in an "all-RAM" environment, then switch off that ROM and run completely in RAM. – tofro May 11 '18 at 8:17
  • You could also fiddle with the address lines at cold boot to force the CPU to run in high memory, tricking it to "think" it ran from 0. Some "simpler" machines not using the full 64kBytes for RAM did that and let the ROM permanently paged in. – tofro May 11 '18 at 8:32
  • I'm not 100% convinced that because memory tended to be small it also had to be at low addresses. If they had said, "Programs entry point will be at 0x8100", then the hardware designs could avoid this fiddly business, just needing to map RAM to higher addresses instead. – Wilson May 11 '18 at 9:13
  • In case you'd have put the program entry point to 8100H, you would have limited the design possibilities just as much - You're now assuming there is RAM there. Doesn't really change the picture, except you have now fragmented the address map for applications. Small memory systems would have ~32kBytes RAM starting from 08000H, expanded systems would have "some more" in the lower 32kBytes. Writing applications would be much more difficult. Note CP/M doesn't provide anything you could call "memory management" beyond that it tells you where the TPA ends. – tofro May 11 '18 at 9:35
5

Along with the specific reasons why CP/M needed to have the memory map it used, in general, for an Intel 8080 or Z80 based computer to be truly flexible, you must have RAM in the first 128 bytes. This is because of (1) the RST instructions supported by these processors, and (2) the method for interrupt handling the 8080 used (and which the Z80 continued to use by default)

The RST instructions are extremely useful. There are 8 of them, RST 08h, RST 10h, and so on, all the way up to RST 38h (I beleive under the Intel opcode scheme they're RST 0 ... RST 7, but I learned with a Z80 so that's the way I remember them). When executed, they effectively produce a call to one of a static set of locations, distributed every 8 bytes in the first 64 bytes of memory. These instructions are very useful for writing code that stretches the limits of the system because (1) they save 2 bytes of code and (2) they save 6 clock cycles vs a standard CALL instructions. Being able to put frequently-used small functions into these locations can make a vast difference to performance of an application, so allowing for this is pretty important. If you have ROM at the start of memory, you need to make the selection of available functions in advance, which can be limiting.

Now, this is an optimisation, and one that wasn't strictly necessary, so CP/M could have gotten away without allowing users to take advantage of it, except ... the way the Intel 8080 interrupt scheme works is that when a device wants to cause an interrupt, it pulls the INT processor line down, waits for the interrupt to be acknowledged, then puts on the data bus the opcode of an instruction to execute. Realistically, there are only 8 reasonable choices of instruction a device could use for this purpose: one of the RST instructions. Alternatively, a device could use the non-maskable interrupt, and pull the NMI line down instead -- there there is no selection of action to take, the processor always calls an interrupt handler at 66h. Therefore, interrupt service routines must be installed within the first 128 bytes of memory, so for a machine that supports end-user installable peripheral devices and doesn't want to limit the performance of those devices by preventing them using a direct interrupt handler, that machine must provide RAM at the first 128 bytes.

Things are slightly better on the Z80, which provides configurable interrupt handling modes including IM 2 which uses a relocatable vector of interrupt handler routines, but remembering that CP/M was designed for the 8080 first, interrupt handling was almost certainly on the minds of the machine designers when they chose where to put RAM.

  • 1
    Hmm. CP/M doesn't really use any of the RST handlers. All of CP/M is being called by one single vector at 0005H. All of the memory between 0H and 100H (beginning of TPA) is reserved by CP/M anyhow - you would have some problems in placing your vectors there. Not sure the RTS vectors are a reason for the CP/M memory layout. – tofro May 11 '18 at 14:38
  • @tofro: The 8080 hardware is limited to invoking interrupts at multiples of 8 from 0x0000 to 0x0038. If CP/M were to use the first three bytes at any of those addresses for something else, it would limit the range of platforms upon which it could run. – supercat May 11 '18 at 14:54
  • Maybe I didn't make myself clear enough: If you want to use RST 8 to RST 38, you can - But the system doesn't use them. CP/M does, however, use bytes 3 to 7 inclusively (iobyte, default drive and BDOS vector). Many systems use the restart vectors for BIOS calls. But it's hard to imagine that the address map was chosen for that reason. – tofro May 11 '18 at 15:28
  • CALL 5 does imply some thought to the RST vectors: it's exactly a JMP xxxx's length before RST 8, so it's the earliest address that is as close as you can back up to an RST address while having space to redirect the call; with the IO byte and default drive in front of it there's also just enough room for JMP at the RST 0 point. That's working backwards from result to divine reasoning though, so take with a pinch of salt. – Tommy May 11 '18 at 15:28
  • @Tommy: I wonder if there would have been any disadvantages to having CP/M load programs at e.g. 0x1100 and have its "public" interface build down from there. On systems with less than 4K of ROM, CP/M could put as much of its own logic below 0x1100 as would fit (with the rest being placed at the top of RAM), but systems could have up to 4K of unalterable ROM at the bottom of memory without causing any difficulty. – supercat May 11 '18 at 17:10
2

In contrast to many other CPUs, a feature of the 8080 is the separate address space for I/O devices. Other CPUs (like the 6502) used memory mapped I/O.

As a result of this architecture, the entirety of the 64K address space is available to the CPU as normal memory.

Since CP/M is designed at the outset as a disk based system, there's no heritage of having any real requirement for anything in ROM. It could all be loaded from disk. If you wanted to, you could bootstrap a CP/M system on an IMSAI or Altair with nothing but RAM and the front panel. (Not suggesting anyone did, just that's it's possible.)

So, given now that you can have a system with access to the entire memory map, it behooves a designer to put the minimal amount of requirements on the underlying system up front, and then work within those minimal constraints.

Essentially, at a logical level, the system requirements for a CP/M system are a computer with RAM available from 0x0000-0x00FF, as this area is reserved by the OS, and then memory at 0x0100-0xXXXX.

The programs view of the OS is solely through the two entry points (JP 0000 and JP 0005), and the published memory map of the first page of RAM.

That's a pretty minimal surface requirement. The CP/M system "requires" very little. You could have a CP/M system with 2 pages of RAM (1 for CP/M, 1 for your application) and BIOS/BDOS, even the application, in ROM. You could have a CP/M system where the OS consumes 0 RAM (via crazy hardware architecture shenanigans banking in the BIOS whenever it sees "JP 5").

The point isn't whether these designs are practical, or that they were even considered, rather simply that they're possible.

In the end, CP/M is a system that requires 1 page of RAM, offers it's list of services (i.e. disk file access etc via JP 5), and mandates that your application starts at 0x0100. That's it. The sky is that-a-way -->.

In practice, this minimal baseline turned out to be quite flexible. Most systems are 64K RAM machines, banking out a ROM boot loader, with BIOS/BDOS at high memory. That's because of how it was implemented and shipped from DR. Simple, useable, and practical.

In the end, it enabled general purpose systems with different amounts of RAM (including large amounts), different architectures, memory bank schemes, even the rise of MP/M which could run off the shelf CP/M binaries. As long as it LOOKS like CP/M to the binary, it IS CP/M.

So, having programs load at 0x0100 simply gives them the biggest opportunity to have as much access to the system resources as possible.

  • Having implemented this stuff myself, an implementation that consumed no address space would create de facto compatibility issues. Some applications use the address at 0006 to determine where the BIOS is, then rely on the fact that the BIOS is defined to begin with JMP statements to each of the specific service routines to determine the addresses of those and branch to them directly. E.g. Microsoft BASIC takes the addresses from there and dynamically reprograms itself for access to CONIN, CONOUT, etc. – Tommy May 11 '18 at 20:06
  • 1
    @Tommy "...rely on the fact that the BIOS is defined to begin with JMP statements to each of the specific service routines to determine the addresses of those and branch to them directly. E.g. Microsoft BASIC" - disgusting, but I'm not surprised that Micro$oft would do that. – Bruce Abbott May 12 '18 at 16:22
  • 1
    @BruceAbbott I suspect back in those days, everyone took pretty much every shortcut they could and then some to squeeze a bit more performance out of the systems. Avoiding one layer of indirection seems a perfectly viable strategy in the light of the environment of the time, and hey, if it worked, then why not? 65,536 bytes of RAM isn't much. Microsoft apparently faced a lot of backwards compatibility issues for that kind of reasons when developing Windows 95 too, with computers far more powerful than those that typically ran CP/M, so they definitely have been on the receiving end as well. – a CVn May 15 '18 at 14: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.