Fairly complete multiprocess computer design
Posted: Mon Dec 04, 2023 2:13 am
After posting that simple glue logic for a two-mode system last week, my mind keeps being drawn back to more fully-fledged multitasking, and as I couldn't do anything practical this weekend, I fleshed out my ideas a bit more on paper. This is a bit like the one from last week, but with a process ID register, a page table, and thus a completely virtualised memory map for user processes. It is quite a bit more complex than the last one, but still I think fairly simple given what it (hopefully) achieves.
With appropriate kernel code, and a bit of missing glue logic, this design should support:
The process can request more memory using system calls, as well mapping/unmapping pages if it needs to page through more than 64K, and of course any other process management facilities and I/O would be accessed through system calls as well. Inter-process communication could be done through shared memory.
As the pages are 4K in size, they're identified by the first hex digit of the address - the process's first logical page is $0000-$0FFF, its second one is $1000-$1FFF, etc up to $F000-$FFFF. I initially used a 2K page size but changed it to 4K to simplify some of the circuit a bit.
The supervisor mode has a different memory map which is of course forced by the hardware. It's optimised for simple decoding rather than best use of address space. The memory map is as follows:The lower half is mapped in the same way as in user mode, i.e. split into 4K pages which are mapped through the pagetable. There's not much ROM, just for address decoding simplicity, but plenty of RAM to load kernel code into from SD or serial. The kernel has its own process ID, probably either 0 or 255, and it will use some of its logical pages to access user memory when that's required by system calls.
The upper half is mostly fairly standard I/O decoding. The unusual entries there are the process ID interface and the pagetable interface. The process ID is just an 8-bit register which the kernel can write to. It is write-only - reading wouldn't be hard to add but doesn't seem worthwhile - and its main function is to influence the virtual memory paging.
The pagetable is 4K in size, stored in an extra static RAM module. It is accessible (read and write) by the kernel at $A000-$AFFF. This allows the kernel to configure which pages of physical memory are visible to individual processes (including the kernel itself, in its lower 32K of address space). Within the pagetable, the lower 8 bits of address contain the process ID, and the upper 4 bits contain the logical page number. In user mode, these are plumbed through all the time; in kernel mode, they are also plumbed through when the lower 32K is accessed, but during pagetable read/write operations, the address and data bus of the pagetable (PTA and PTD in the schematic below) are connected one-to-one to the CPU's address and data buses.
Each location in the pagetable stores 8 bits of data, and these are prefixed to the low 12 address lines from the CPU to form a 20-bit address (hence 1MB) which is used to drive the RAM.
One last subtlety is the transition between modes. Supervisor mode can only be entered by the processor reading the vectors, in response to a BRK system call, IRQ from hardware, or an NMI (used for preemptive multitasking). It is exited just after the opcode fetch of the instruction following any write to the PID register (thanks Proxy for the suggestion of using SYNC for this). This means the kernel can write a new PID then immediately RTI to resume a user process. (Though now that I think, this isn't going to work, as it would need an extra PLA or something in the middle, in order to restore the register that was used to hold the process ID being written to the PID register. I might make it wait for two instructions, or use another operation (e.g. read with BIT) to trigger the mode transition.)
Finally, here's the schematic I have so far. I'm not sure I have the patience to build a breadboard prototype this time as there are just so many buses to wire up, that gets quite tedious - so after giving it some time to settle, I might go straight to PCB layout this time. In the schematic we have:
As always, any questions or corrections are much appreciated, as well as other pointers to similar systems!
With appropriate kernel code, and a bit of missing glue logic, this design should support:
- Up to 255 user processes
- Clean 64K address space for each process
- 1MB total physical memory shared between processes, mapped in pages of 4K at a time
- NMI timer-based pre-emptive multitasking
- Interrupts and I/O handled by the kernel
- BRK-based system call interface
- Flexible I/O decoding - the schematic shows an ACIA, a VIA, and an SD card interface, but there's plenty of address space to decode others
The process can request more memory using system calls, as well mapping/unmapping pages if it needs to page through more than 64K, and of course any other process management facilities and I/O would be accessed through system calls as well. Inter-process communication could be done through shared memory.
As the pages are 4K in size, they're identified by the first hex digit of the address - the process's first logical page is $0000-$0FFF, its second one is $1000-$1FFF, etc up to $F000-$FFFF. I initially used a 2K page size but changed it to 4K to simplify some of the circuit a bit.
The supervisor mode has a different memory map which is of course forced by the hardware. It's optimised for simple decoding rather than best use of address space. The memory map is as follows:
Code: Select all
F000-FFFF ROM
E000-EFFF ACIA
D000-DFFF VIA
C000-CFFF SD card interface
B000-BFFF PID (process ID) assignment interface
A000-AFFF PT (pagetable) read/write
0000-7FFF RAM (paged)
The upper half is mostly fairly standard I/O decoding. The unusual entries there are the process ID interface and the pagetable interface. The process ID is just an 8-bit register which the kernel can write to. It is write-only - reading wouldn't be hard to add but doesn't seem worthwhile - and its main function is to influence the virtual memory paging.
The pagetable is 4K in size, stored in an extra static RAM module. It is accessible (read and write) by the kernel at $A000-$AFFF. This allows the kernel to configure which pages of physical memory are visible to individual processes (including the kernel itself, in its lower 32K of address space). Within the pagetable, the lower 8 bits of address contain the process ID, and the upper 4 bits contain the logical page number. In user mode, these are plumbed through all the time; in kernel mode, they are also plumbed through when the lower 32K is accessed, but during pagetable read/write operations, the address and data bus of the pagetable (PTA and PTD in the schematic below) are connected one-to-one to the CPU's address and data buses.
Each location in the pagetable stores 8 bits of data, and these are prefixed to the low 12 address lines from the CPU to form a 20-bit address (hence 1MB) which is used to drive the RAM.
One last subtlety is the transition between modes. Supervisor mode can only be entered by the processor reading the vectors, in response to a BRK system call, IRQ from hardware, or an NMI (used for preemptive multitasking). It is exited just after the opcode fetch of the instruction following any write to the PID register (thanks Proxy for the suggestion of using SYNC for this). This means the kernel can write a new PID then immediately RTI to resume a user process. (Though now that I think, this isn't going to work, as it would need an extra PLA or something in the middle, in order to restore the register that was used to hold the process ID being written to the PID register. I might make it wait for two instructions, or use another operation (e.g. read with BIT) to trigger the mode transition.)
Finally, here's the schematic I have so far. I'm not sure I have the patience to build a breadboard prototype this time as there are just so many buses to wire up, that gets quite tedious - so after giving it some time to settle, I might go straight to PCB layout this time. In the schematic we have:
- Left side - 6502 (U1), ROM (U16)
- Central columns - pagetable management - pagetable (U11), PID register (U8), pagetable address multiplexing (U9, U10), pagetable data interface (U13)
- Top right - main RAM (U12, U15) and RAM selection logic (U5B, U4B)
- Bottom centre - supervisor flag (U2A, U2B)
- Bottom right - ROM and I/O address decoding (U3, U4A, U5A, U6A)
- D[0..7], A[0..15] - CPU buses
- PTD[0..7], PTA[0..11] - Pagetable buses
- PTCS, PTOE, PTWE - read/write the pagetable
- PIDCS, PIDWE - write to the process ID register
- SUPER - supervisor mode flag
- ROMCS, ACIACS, VIACS, SDCS - ROM and I/O selection signals
- The address mapping going into the PTA bus - there are two mappings, if we're reading/writing the pagetable then we pass A[0..11], otherwise we pass PID[0..7] and A[12..15]. U10 selects between the top four bits, while U8 or U9 provides the bottom 8 bits.
- The pagetable's OE signal needs to be asserted at all times other than pagetable write operations (U6A)
- The RAM is selected when not in SUPER mode, and also when accessing the bottom half of the address space (U5B)
- U2A and U2B (super mode flags) are both set on VPB. U2A is cleared when the PID register is written. U2B copies this state at the end of the next instruction fetch cycle (using SYNC).
- The inverters can probably be implemented using spare gates from elsewhere.
- The mechanism to drive NMI isn't shown, it will just be a countdown timer, probably reset while in supervisor mode to prevent NMIs in that mode.
- RESET, IRQ, and PHI2 need to be driven in the usual ways - I'm not anticipating any clock stretching here as the large RAM modules are only 55ns, so can't go above about 10MHz anyway.
- U13 is the wrong way around, it need its A and B pins swapped - oops!
As always, any questions or corrections are much appreciated, as well as other pointers to similar systems!