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:
- 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
A lot of the specifics of operation would depend on the choices in the kernel code, but my plan is for new processes to be spawned with enough memory mapped to hold their binary code, loaded from SD card or serial connection. They'd perhaps load at $0000 and execute at $0100, allowing initialization of page zero and placing a JMP instruction or other init code at the bottom of the stack. Possibly a page of standard helper routines would also be mapped at $F000, but that probably only makes sense if I add support for it to be read-only.
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:
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 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.
Attachment:
File comment: Multitasking 6502 computer schematic
6502multitaskingcomputer-schematic.png [ 146.71 KiB | Viewed 7165 times ]
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)
Interesting signals in the schematic:
- 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
Some points of note:
- 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!
I think compared to similar systems, my understanding is that Andre has also implemented a system like this using normal RAM as the pagetable, but I think I've only seen schematics for his older system using a more specialized IC for the pagetable (I think it was dual port?). I also read BDD's thoughts on this sort of thing in his POC thread, but I think I need a different approach here as for the 6502 it doesn't make as much sense to try to mesh with features of the CPU itself, as it doesn't internally have things like banking at all.
As always, any questions or corrections are much appreciated, as well as other pointers to similar systems!