Hi all - this is my first post, sorry it's so long but there IS a question at the end…so please keep reading!!
I’m pretty much new to all this, so apologies if some of this seems to have obvious answers – they’re not obvious to me!
As a bit of context, over the last couple of months (having recently retired) I have built the Ben Eater breadboard 65C02 computer. To reinforce the learning, I took the breadboard 65C02 apart again and rebuilt it and added a 65C51, FTDI- USB connector and established a serial link.
I implemented Jan Roesner’s Sixty5-O-2 bootloader which jogged a few memories (I did some 6502 assembler in college – about 40 or so years ago!) and then hacked it around and got a 4x20 LCD working and added some extra menu stuff – great re-introduction to 6502 assembler.
Communicating via a 4-line LCD isn’t very sleek, so I then then began writing my own code which communicated via the serial link to implement a basic command line parser and memory dump etc. I also implemented Daryl Richter’s xmodem into my ROM code so I could upload binaries and execute them while retaining my basic ‘OS’ and not flashing to ROM for every little change, so I started to develop a library of routines.
My development environment is Retroassembler within VSCode, with my code divided up into modules (e.g. shell.asm, memdump.asm etc). I then have a ‘main.asm’ file which just has ‘include’ statements for all of the other modules, the first of which is my ‘memory.asm’ file where I define all constants (VIA & ACIA addresses etc.) and variables & buffers.
This is how I have developed and generated my ROM code, which give me a (very) basic system with some common routines, basic shell with command line parser etc.
When I assemble this ROM code, a list of symbols is generated along with their addresses, and I capture this into a file which I call my ‘symbols.asm’.
Then, when I want to write something to run from RAM – such as testing new library routines - I just include the ‘symbols.asm’ in that build, and thereby resolve references to all ROM routines which I can call by name in the routine being developed. This seems to work well for me.
I have now built my third breadboard machine, using Daryl Richter’s SBC2 decode logic (
https://sbc.rictor.org/sch2.html) so I have 16 I/O ‘segments’ to play with and lots of ROM & RAM. I’ve included two 65C22’s and a 65C51. I plan to put a 128x64 LCD onto this, but directly onto the data bus this time (I have read a few articles about this, and think I get it now!) and then look at SPI so I can get an SD card into the build (but that’s probably a way off)
So, getting to the whole point of this meandering missive, I have started to look at the code I wrote on my original BE6502 with a view improve & refine it as I move it to the new machine. IO addresses etc. are all constants defined in my ‘memory.asm’ file, so it’s trivial to move the ‘as-is’ to a new memory map, BUT it’s really got me thinking about the ‘philosophy’ or architecture around writing a basic 6502 OS.
There’s lots of 6502 material out there, with loads of useful routines to read, learn from & re-use, but I’ve not really come across much that talks about the high-level approach to pulling a large number of routines together into a coherent ‘OS’ and – the important bit – sharing variables, buffers etc. between these so they don’t trash each other’s memory.
Also, while we’re at it, what’s the ‘best’ way to pass parameters? Many routines use A,X & Y registers to pass in parameters, and then to pass them back out again, but this needs lot of pushes and pulls around each call – is that efficient? Is using zero page locations a better way to pass parameters back & forth? When you have more than three things to deal with, then the register approach isn’t sufficient anyway. I want to have a consistent approach as I develop and implement more routines.
My current memory allocation approach is to declare a selection of zero page labels (e.g. zpVar0 = $00, zpVar1 = $01, etc) in my ‘memory.asm’ which I then use in the various modules.
So now I’m combining more routines into my ROM code, it’s becoming difficult to track what variables (i.e. memory locations) are in use in any particular chain of nested subroutines. Consequently, I find I have been inadvertently trashing memory in use by the calling routine(s) in called routines.
For example, I have trashed ‘zpVar1’ in my ‘hex string to 16 bit int’ routine when ‘zpVar1’ is ALSO used by the routine which has called it, which leads to hangs & crashes and a lot (and lots) of debugging before I work out what I have done. In retrospect, there’s no surprises there, but it seemed a good idea at the time!
A ’brute force’ approach of allocating new memory locations for each routine would avoid such clashes, but would waste loads of ‘use once’ memory, which isn’t on at all – very crude.
I have (briefly) thought about writing a routine to allocate memory, call it on entry to a routine and then release that memory on exit of each routine, but that sounds quite complicated, would probably need some kind of garbage-collection routine as memory chunks wouldn’t be allocated sequentially, so then it sounds more like a linked list approach etc. etc…..so I’m not going there! Plus I have no idea how I would allocate readable labels to such a dynamic memory model, so code would become very hard to read.
So, (finally), my question is does anyone know of any material that covers this aspect of 6502 development, and/or does anyone have any suggestions/experience of how they went about managing this?
I have had some feedback on other forums that I should move to the cc65 toolset, and I realise this is a much more powerful tool, but I’m struggling to see how this addresses the architectural/design high level issue of how to safely share defined memory across a range of subroutines.
Am I over complicating things, or are these valid questions?
I would really appreciate any thoughts, suggestions and reflections - all responses very welcome!