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!