I am building a laptop using some MCUs to emulate a 6502, 256kb memory, SID-sound and filesystem.
I've been coding 6502-assembler for a while now, working on the system code. It's interesting to plan all the bits, and see how it works out in reality. So far so good.
Latest functionality I made was an S19-loader so it is easier to start new threads without juggling with the SD-card. The S19 format is surprisingly powerful, but can also be implemented VERY compactly if corners are cut. I'll have to revisit this in the future, to add the corners.
Focus now is on making os-code to be able to load/start/stop/manipulate threads. I've decided the commands for this will be available from Basic, bringing a truly 80's retro vibe. It's also quite usable. Beginnings of re-entrant os routines have been coded, but are not fully tested.
Basic will run as thread 0 and it will be possible to run more threads. Only zero page memory and cpu-cycles are limiting the number of practically usable threads.
In a weak moment I was thinking about trying out cc65 to write code in C. That idea is postponed since I started this project to code 6502 assembler.
I've finalised the latest version of the HW now. The new 40x4 text display instead of the old 40x3. I soldered everything properly, also melt-glued and screwed the stuff onto a board so I can bring it with me when needed (soon).
The graphical display is not connected right now, it will be an add-on accessory later. I am fully occupied coding the multitasking system which does not need any graphics.
The I2S sound was scrapped. It worked VERY well, but is not needed on this lo-fi sound adventure :) Mono sound for SID and 8-bit samples works well enough.
It took me longer to implement than expected, but was very rewarding. The debugger is not done in 6502, but in the Teensy-level. Much easier and quicker to get at all the stuff, and also much easier to format responses. Now I can set a break point, single step, look at memory, set any memory/registers, do NMI/IRQ and run again.
My first session dealt with single-stepping startup code from reset, and how IRQ and NMI was bouncing around in my scheduler. Turned out I had counted stack positions wrong in my threads. It was indeed a quick session to realise what was wrong :D
One of the things a 6502 is bad at is multitasking. I've tried to understand how to mitigate the shortcomings by designing the (virtual) HW around it so that it becomes as useful as possible. Always with a mind that a real implementation in HW would be possible and not too complex.
I've implemented a simple bank-switched system with 8KiB banks. The top page $FFxx is always locked to the same memory block, so vectors and its code can be the same for all tasks. The I/O-page at $FExx is also always locked.
A task gets 1-8 blocks of 8KiB memory, and the first always reside in bank 0. A task will thus always get a FULL zero page and stack of its own, which is very useful on a 6502. A task will get minimum 7.5KiB for code and data, maximum 63KiB without bank switching. And don't forget: a full zero page for each task.
The timer is designed so that it was as easy as possible to implement an efficient pre-emptive multitasking scheduler.
I got a new display yesterday, a Newhaven NHD-0440AZ-FL-YBW 40x4 text display with backlight. Not a giant upgrade from the 40x3 display I've used, but this one can be bought new from Mouser, has 33% more lines, has backlight and also a HD44780-compatible controller. Soldering and changing the code to run took less than 4h, so not too bad.
During this I also remember why I have a secondary processor dealing with the keyboard and text-display: it takes A LOT of time to wait for each byte sent to the display. Running this on the Teensy would quickly eat up precious CPU-cycles better spent on heavy lifting (emulation).
I had missed the fact that the backlight draws 2W. It always pays to read the documentation thoroughly... Might have to fix some PWM control for the backlight level later.
Almost forgot: I could ditch the DC-DC converter used on the old display :D
I just added the possibility to change irq-rate by writing to a 16-bit register in the PZ1. The irq used to be hard-coded @50Hz, coinciding with the frame counter, but those are now separated. This also leads to the possibility to change play-back rate of SID-songs with a simple DOKE in basic, very handy :)
I've added commands in ehBasic now so I can play music in the background.
SIDPLAY :REM PLAY DEFAULT SUBSONG
SIDPLAY2 :REM PLAY SUBSONG 2
SIDSTOP :STOP PLAYING
The text display I use is 40x3 characters, not a lot. I implemented a local 40x30 copy that can be scrolled around in with Alt-arrowkeys, a bit like looking at the print-out on an old paper terminal. The primary MCU does not have to deal with any of the scrolling, and it is possible to actually get some overview on printed stuff.
I'd really like more than 30 lines back-buffer, but the Mini Pro MCU is very limited on RAM, so this is what's possible with the HW at hand.
I've hinted that I use basic as an easy way to test the system functions as I code them. Here is an example:
10 PRINT DEEK($FE42), DEEK($FE44) : GOTO 10
DEEK is the equivalent of PEEK, but on a 16-bit word. So the line of code prints two 16-bit values, from I/O-ports $FE42 and $FE44, which are the touch-screen X- and Y-coordinate. It also loops forever. A very simple and informative way to test the implementation.
The 6502 in the PZ1 has 256kb RAM, no ROM. Any ROM contents are copied at startup to the correct place in memory by the emulator.
The memory is divided into 8kb memory banks, with each bank having an 8-bit register to choose where in the 256kb-space it points. It is possible to change memory bank 0 to swap between several Zero pages. Multi-tasking possibilities abound :D
I/O-area is always overlaid at $FE00-$FEFF.