• Please review our updated Terms and Rules here

Choosing MP/M and CP/M disk sizes, vs. remaining RAM

Sure, or use a better processor. Maybe an 8086 might work. Oh, wait...

edit:
To be a little less sarcastic, proper use of banked memory also requires bookkeeping, with the same consequences as I mentioned earlier. Also, most CP/M systems - certainly the ones DRI was targeting - did not have banked memory in the first place (CP/M 3 does not require banking). Making banked memory a requirement was a non-starter.
 
Last edited:
Large blocks are a problem, but did anyone ever create an algorythm that got put into actual file systems so that small files didn't occupy large blocks?
In the specific case of .COM files, there were ways to run them directly from a .LBR file, saving a bunch of disk blocks and directory entries.
 
I find smaller block sizes better on machines where I do a lot of 8080/Z80 assembly work in CP/M.
Just hate writing a thousand byte program and it eating a whopping 4K on the disk.
But then... maybe that's why we ended up with larger multi-function utilities instead of lots of little ones?

Well, today, physical media is just so large, "wasting" a 16K block on a 1K file isn't wasteful at all, it just feels funny. It also partly solves the dir size/extents problem; very few files are greater than 1 dir entry. The cheapest/bestest SD media I found is 16GB Sandisks, a couple of infinities on CP/M. The limitation is the allocation vector table in memory, one bit per block and that's that. So in 2025, the blocks ought to be huge. There's no downside. I'm just picking the allocation vector size I can stand, using the biggest blocks, and done.
 
Last edited:
Sure, or use a better processor. Maybe an 8086 might work. Oh, wait...

edit:
To be a little less sarcastic, proper use of banked memory also requires bookkeeping, with the same consequences as I mentioned earlier. Also, most CP/M systems - certainly the ones DRI was targeting - did not have banked memory in the first place (CP/M 3 does not require banking). Making banked memory a requirement was a non-starter.

Which is why I used the BDOS as the "bookkeeper" for my Banked Memory system, and turned all memory into Disk structure at the same time.

Then the BDOS keeps track of allocations, can find them anywhere in memory, keeps track of which programs have used specific banks to allow for TSR and Driver architectures, and allows hardware allocations to be mapped to memory as well - eg, Video Memory, Disk buffers etc.

A snapshot of the entire memory can be taken with PIP and if you want to see what memory is allocated at any one time, you can just do a DIR M: and up it comes.

It has the added benefit that any memory location can be accessed directly by mapping pages into the TPA, Can be accessed as I/O space in 128 byte blocks using the z80 16 bit I/O mode ( eg, can be copied into/out-of TPA from I/O via INDR opcode on z80 ) or can be accessed as a file via the BDOS through almost any language that can read/write disk files under CP/M.

Or you can simply access memory space byte by byte and know exactly who owns that space, or you can ignore the BDOS entirely and just use it however you want to use it - exactly how CP/M seems to have intended.

It means that my RAM can be used as memory or as a RAM disk, or as both simultaneously, without pre-allocating the size of the RAM disk since ALL memory is a part of the RAM disk.

And finally, it provides a basis for memory structures up to 256Mb and potentially 1Gb that the internal software can properly access with minimal upgrades to the OS.

The cost of this? The first block in RAM gets permanently reserved as directory space and my bootstrap needs to establish a directory there identifying the OS basics and formatting the directory at least - which it does within 256 bytes before handing off control to the CP/M BIOS.

And it even allows for option roms under CP/M, including installing hooks and shims, so that these roms can permanently allocate system RAM to their needs, or map system ram to hardware such as video memory or option ROMs, and bypasses all 64K memory limits of the z80 with minimal hardware.

It turns CP/M into a true unified multi-modal memory architecture and put the z80 in a roughly equal footing to a 12MHz 286 with 1Mb of memory running a real-mode OS, and likely exceeds it.

So yeah, playing with the BDOS as the memory management system in a well-expanded CP/M computer, even running stock CP/M 2.2 would smash the 8086 based systems of the era...

Once you can access a "data" segment as Disk, I/O or paged memory, you blur the lines and limitations of CP/M completely and it starts to act similarly to an 8086 based OS, except now memory is properly and reliably managed in 4K blocks, is already set up for conversion to support multitasking with future OS upgrades since the memory is already managed under a single-tasking OS.

I rediscovered a lot when I created LokiOS, but I don't think there are ANY examples of a truly unified memory multi-modal memory architecture like this anywhere else - not just during CP/M's era, but up to and including the current era...

I think @tomjennings playing around with the BDOS leads down the same path to some interesting discoveries. CP/M's file system still has some advantages over DOS's FAT system. FAT wasn't superior in ever way ! :)
 
In the specific case of .COM files, there were ways to run them directly from a .LBR file, saving a bunch of disk blocks and directory entries.

I was wondering about that - this would definitely count as a file system too.

Was there ever an extension to mapping LBR files into a drive so that files could be opened for read/write and to later clean up the LBR and remove de-allocated space to recover lost memory?
 
OK here's my solution, for CP/M 2.2 and MP/M II 2.1:

The host device supports six drives; four fixed, two removable. The fixed drives are all on one microSD card inside the machine, the two removables are full size SD cards sticking out the front like floppies. The drives are numbered 0..5, in a funny order.

0: fixed
1: fixed
2: removable
3: removable
4: fixed
5: fixed

Drive letter is mapped to drive physical number at boot time. Booting from the first drive, 0, maps the boot drive to A:. This is the normal and automatic boot case. But you can manually boot from other physical drives; the one you boot from becomes A:, and the other letters are assigned in sequence. This makes for confusing drive letters but it allows you to boot from a removable to repair a drive etc. All drives are 32 MB, with 2048 blocks (therefore the vector table is 256 bytes) of 16384 bytes each, and 512 dirs.

CP/M:

A: fixed (physical 0)
B: fixed
C: removable
D: removable
E: fixed
F: removable (physical 5)

TPA will be about 60K, I'll do the MOVCPM and all that next. Alloc vectors and dir bufs consume 1920 bytes, 6 * 256 + 3 * 128.


MP/M:

A: fixed (physical 0)
B: fixed
C: removable
D: removable

The banks are now Cf00, aka 51K banks/TPA. Now that's pretty good for MP/M.

My logic is, CP/M USER support sucks (though I'll write a program to better use it), and CP/M is smaller, so more drives is better. MP/M USER space support is tolerably OK; and 64K fixed is fine.

The removables are also 32 MB, same format -- and so they're fine as workspace. The built-in microSD however uses a 4-bit SDIO interface so it's much faster.

It's all working now, except the MOVCPM to push CCP/BDOS up higher.
 
This sounds wild! What's the hardware that supports all this?

At the moment the hardware is just emulated while I write the OS ( CP/M clone OS ) and finish converting the BASIC, but it's pretty simple in terms of hardware decoding. I'll make some physical hardware once I've finished all that - it's a long terms project for me, and without the software/OS/BASIC etc, there didn't seem a lot of point to build hardware that can't be tested - also, as I've written the OS and tested it in the emulated environment, I've learned where I need to change things to improve - and it's still not fully debugged, but works for the most part. Currently a functional version is on GIThub - so it's possible to try out, though the mechanisms for getting CP/M software into it are a bit clunky so once I've finished converting Sinclair ZX Spectrum BASIC to run under CP/M, I have to modify the environment so .COM files can simply be put into a directory under Windows 11 and when the emulator is run, it will convert them to/from CP/M format and then write files back to the directory when it's finished.

The emulator also covers the graphics hardware now - I wasn't fully confident in the graphics hardware so I did actually build that to the 90% level as a proof of concept - and it worked and produced video with very tight timing specs - It can do 512x192 graphics at 16 colours using just two GALs as raster-address and timing generators running from a 7 MHz clock. I wanted to see how far I could push 1985 technology based on designs that were touted and never built - The Loki was Sinclair's proposed "Super Spectrum" that was supposed to be impossible to build, so his investors pulled the plug, but if you follow the path left behind by the clues, it looks like there are certain specifications that require very low chip counts. For example, the proposed hardware vector graphics should be able to draw lines at 7 pixels per microsecond, yet works without any comparitors and requires just a single TTL adder and a counter - given they would have converted these to ULAs back in the day, it's an extremely low chip and gate count. I can't get ULAs so I just use PAL/GAL chips when I do test something.

The architecture itself follows this. It's from my earlier notes but mostly reflects the current state.

It uses two 22V10 PAL/GAL to drive the lower bus address lines, and a fast SRAM to drive the upper address lines MA12 to BA19. It swaps some Memory and I/O addresses to ensure that 128 bytes of RAM are mapped to the upper I/O address that z80 in/out commands use access individual RAM addresses, so the below assumes some additional decoding logic to tell it whether it's an I/O or a Memory access cycle, and of course, one decoded I/O address will appear to be a memory access cycle so that I/O to that port ( lower 8 address lines ) will swap the addressing via multiplexing in the PAL/GAL chips, and the latched MA7 to MA11 will be pushed out onto the bus rather than CPU addresses A7 to A11, and the latched "track" outputs show up from BA12 to BA19.... It's incredibly simple in design.

Also, the use of a SRAM for most memory decoding could be replaced by a single memory management chip in TTL ( they did exist in 1985 and worked with the same 4K blocks also ) but by using a RAM, I can create "processes" allowing me to switch which memory banks are paged in with a single out command - so drivers can have their own process, and when something like the video driver is accessed, so there's no need for the software to care about what is happening - it's automatic.

I could go further and hardware map the actual directory itself to the MMU, and slave them off of RSTs and Interrupts, which would hardware switch paging automatically to support multitasking environments ( ie, when an interrupt occurs, it automatically changes the paging model ) but I decided that's a "later" enhancement I can chase after I build the base hardware.


LOKI Architecture - Frame 1.jpg

If you follow some of the videos I post, you can see how this actually looks in practice.


The first few bits show the L: drive ( ROM Boot drive ) and the M: drive ( Memory Management show up when I do a "DIR" - Each file is a reserved memory map. )

Interestingly, because CP/M supports cross-linked allocations without any issues, I use that also - for example, the directory structure shares common allocations for Page0 and Page 14 and 15 between the RST allocations so that if a RST is called or activated by an interrupt, it can change the Process ID and page in it's own memory - The video driver does this, but I've left the RSTs alone so anyone can use them for their own purposes.

And of course, the Spectrum ROM BASIC you see running later simply redefines these anyway, as Spectrum ROM calls the RST for internal functions to save memory.

It's a fun architecture to play with. It links a file name to each process ID and allows for up to 256 process IDs ( though I'll only include about 32 in the first hardware version ) and you can use standard 3rd party CP/M DISK tools to see how memory is both allocated and being used, as well as telling you how much unused memory you have. Also also allow the creation of "unused" files so that if you don't have 1Mb of RAM, you simply allocate the unused memory to a file that tracks them and then the system won't try to reuse that memory. Even the TPA doesn't require contiguous RAM for this reason.

Finally, the upper 8 drives are permanently mapped to the MEMORY by the BIOS - But all in different locations. This means that all I need to do to support an option ROM is to look for a file that is supposed to exist on the drive letter assigned to that option ROM and if it's there, execute it. This performs the checking of the drive, makes sure the file is valid and in the right spot and then runs it -

The advantage of this? You can also store utilities in the ROM - for example, imagine if you install Floppy Drive Option ROM in Drive P: - Not only does it self-install a driver, but you can add critical routines such as "Format" - so you can add commands to CP/M that are hardware specific, and if you wanted to format the disk, you'd just type "P:FORMAT D:" or something like that. You can already reload the video driver which is on J: drive by typing "J:VIDEO" and it reboots the video driver and reloads itself into memory.

Why? Because if you're running video through a terminal, you can also do something like "ERA M:VIDEO.DRV" and "ERA M:VIDEOMEM.IMG" which would return 2 extra pages from the driver, and allow 64K (16 pages) of video memory to be used as system memory since the video output is no longer in use... Or if the NVM drive ( planned for 256K of static battery backed RAM) isn't wanted, you can "ERA NVMEMORY.DSK" and suddenly 256K of RAM becomes system memory and now the system has 917K of system memory available.

And, if you want to turn the NVM back on, a utility can determine which processes have to be killed or have their memory allocations copied and changed to release that memory! Which is pretty neat. It's a very flexible architecture.

I haven't written it as a multitasking OS, but given at least one RST (38) is going to be attached to an interrupt generator, and the same with the NMI, it wouldn't take much to write a preemptive kernal even in 1Mb. It's a shame I can't hardware switch all the z80 registers too!
 
...
TPA will be about 60K, I'll do the MOVCPM and all that next. Alloc vectors and dir bufs consume 1920 bytes, 6 * 256 + 3 * 128.
...
It's all working now, except the MOVCPM to push CCP/BDOS up higher.
Note, MOVCPM was traditionally only for systems with variable TOTAL memory size. There are other ways to allow for a variable sized BIOS, although MOVCPM can be made to work for that as well. I had worked on a system for CP/M 2.2 where the BIOS was modular and could be built out of whatever combination of drivers were needed. I forget how that adjusted things, but basically a base MOVCPM.COM was used to add driver modules on to, each one adjusting pointers in MOVCPM to account for the larger BIOS. Not really rocket science, but can be tricky. I can see if I can dig up that, if you want.
 
At the moment the hardware is just emulated while I write the OS ( CP/M clone OS ) and finish converting the BASIC, but it's pretty simple in terms of hardware decoding.

Lol, that's a lot! Nice memory scheme, from what I can puzzle out of it!

The bootstrap problem is a mild pain. I used @davidly's ntvcm emulator, for linux (and I think windows). It comes-with very many compilers etc. I first wrote a "ROM monitor" to exercise the devices (console, then disk, etc) and peek and poke memory, then wrote boot and BIOS code, and an Intel HEX loader in the Arduino IDE to put Z80 binary in RAM. Got CP/M to boot then moved everything inside. I figured the best way to do quality CP/M work is to use it as the dev system. The came-with files were very useful too.

 
Note, MOVCPM was traditionally only for systems with variable TOTAL memory size.
I did it the traditional way. My "ROM" monitor loads to f800 to top (2K) then BIOS below that, in the usual way. That was the first pass, and now it's time to revisit that and push everything upwards. I'm overwriting the monitor now, re-loaded when you hit front panel RESET. I'm trying to keep the work on the Z80 side as much as possible, the backside is just devices (including the MMU for MP/M).
 
I did it the traditional way. My "ROM" monitor loads to f800 to top (2K) then BIOS below that, in the usual way. ...
Sure, that was the way Intel did it on the MDS-800 (the first machine to run CP/M). But I'm not sure if a majority of systems that followed did the same. The ones I worked on did not. One of the problems with that approach is that the ROM can't easily be expanded plus you lose the top 2K (or more) to ROM that may not all be needed. It's just less flexible than having ROM a 0 and switching it out. But it sounds like you're already considering that change.
 
I figured the best way to do quality CP/M work is to use it as the dev system.
When I brought up CP/M, I closely followed the alteration guide at first. But the native workflows are unnecessarily complicated and the tools aren't great. Then I found a commented and disassembled version of CP/M [here] and fixed it to work with z80asm (from Debian/Ubuntu repositories).

Having a simple Makefile to generate a matching set of (CCP, BDOS, BIOS) and corresponding disk or rom images is just way more convenient. This become even clearer when I started targeting a weirder system and also needed a native loader, a secondary BIOS and a specific emulator configuration to even get started.
 
Sure, that was the way Intel did it on the MDS-800 (the first machine to run CP/M). But I'm not sure if a majority of systems that followed did the same.
Many of the ones I worked on did use ROMs, but on "our" machines we did what it sounds like you did -- a ROM in the address space at reset, copies itself to RAM, then hit a latch to disable it. ROM returns on reset. Gotta do something at power on... that's what I've done here in my new machine.

Early on I wanted the ROM monitor (sic) in place while I debugged "everything". Now that it's running I can recover that space, so the "ROM" no longer persists in the address space. But it sure makes life easier starting up virgin hardware.
 
I've used several methods of getting z80s running.

1 ROM at 0 RAM in higher memory. (won't run CP/M)
2 ROM at 0 copies itself to high memory and switches ROM to RAM.
3 ROM at 0 loads a secondary boot loader into RAM boot loader loads CP/M and switches ROM to RAM.
4 ROM in high memory Force a jmp $fXXX onto bus. Access to $FXXX disables jmp circuit.
5 ROM in high memory Force NOP onto bus until address = $fxxx.

I used #1 the most since I mostly did controllers and embedded systems.
For CP/M I prefered #3 It allowed me to use all the ram and load CBIOS to the top of memory.
 
I've used several methods of getting z80s running.

1 ROM at 0 RAM in higher memory. (won't run CP/M)
2 ROM at 0 copies itself to high memory and switches ROM to RAM.
3 ROM at 0 loads a secondary boot loader into RAM boot loader loads CP/M and switches ROM to RAM.
4 ROM in high memory Force a jmp $fXXX onto bus. Access to $FXXX disables jmp circuit.
5 ROM in high memory Force NOP onto bus until address = $fxxx.

I used #1 the most since I mostly did controllers and embedded systems.
For CP/M I prefered #3 It allowed me to use all the ram and load CBIOS to the top of memory.

I mostly used #1 also - it just fits well with z80 embedded systems, and if you don't need a lot of memory, then putting the RAM above $8000 really simplifies the decode glue logic.

There's also the option of hardware loading a bootstrap into RAM on reset ( Amstrad PCW's do this straight from the disk ! ) and there's an additional option to have a MMU remap things automatically on boot.

Although it's a little weird, you could also possibly have a pull-down on the data bus on reset have the CPU execute NOPs until it gets to a higher memory address where it finds a boot rom.

Lately, I'm looking at using the MMU method, but a latch that pages in the boot rom on reset seems to be the most popular that I can tell.
 
There's also the option of hardware loading a bootstrap into RAM on reset ( Amstrad PCW's do this straight from the disk ! ) and there's an additional option to have a MMU remap things automatically on boot.
Strictly speaking, the PCW's printer controller squirts bytes into the Z80 to copy the initial bootstrap into RAM at 0002h. That then loads the boot sector at F000h.
 
Strictly speaking, the PCW's printer controller squirts bytes into the Z80 to copy the initial bootstrap into RAM at 0002h. That then loads the boot sector at F000h.

I always thought it was the disk controller that loaded it into the ram from the boot disk - thank you for the extra information?

Though you have me curious - What executes at 0000h then on reset?

Is there a detailed description of the process on your site anywhere?
 
Not on my site - Jacob Nevins has the details. The printer controller sends a fixed stream of bytes to the Z80 which causes it to write the 256-byte boot program into RAM, and execute it.

The reason the bootstrap starts at 2 is because the last five bytes fetched from the printer controller are C3 00 00 D3 F8. The first three set PC to 0 (previously it was irrelevant) and the second two have to be the last two bytes in the instruction stream because they're what cause the switch to fetching from memory rather than the printer controller.
 
Not on my site - Jacob Nevins has the details. The printer controller sends a fixed stream of bytes to the Z80 which causes it to write the 256-byte boot program into RAM, and execute it.

The reason the bootstrap starts at 2 is because the last five bytes fetched from the printer controller are C3 00 00 D3 F8. The first three set PC to 0 (previously it was irrelevant) and the second two have to be the last two bytes in the instruction stream because they're what cause the switch to fetching from memory rather than the printer controller.

That is absolutely fascinating. Though I note then it still executes from 0000h with RST0 at the end of the load - which I assume means it executes two random "bytes" that just happen to be in memory at the time of reset?

That seems like a serious omission in the strategy, or am I missing something?

It's a truly fascinating boot method. I only knew it superficially before and always assumed they read the boot sector from disk and used something like BUSRQ to allow access to the ram to write the bytes from the FDD to the RAM directly.
 
Back
Top