6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Nov 22, 2024 10:36 pm

All times are UTC




Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sun Dec 05, 2021 11:55 am 
Offline
User avatar

Joined: Fri Dec 03, 2021 2:54 pm
Posts: 11
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!


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 5:10 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
Welcome. It seems like you're well on your way to figuring things out, and just might benefit from a little reassurance.

I'm a fan of stacks for parameter passing, but the '02 and 'c02 can't use the stack pointer directly as an index register, so if you decide to go that way you're going to need X or Y for a stack pointer. I'm partial to the historic 65xx FORTH way, which is to have a page 0 "data" stack indexed with X and a page 1 "return" stack "indexed in air-quotes" with S, but every decision is a balance between theory and application.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 5:51 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Welcome! Some fairly advanced questions, there, I think. It feels to me like it comes down to some engineering choices - how organised do you want to be, how much effort do you want to put in upfront, and so on. But you mention Architecture, and that feels like signalling an inclination to be quite formal.

So, if at present you need to rebuild your applications and utilities when you rebuild your OS, you can perhaps be said to have an API but not an ABI. To nail down an ABI so it doesn't change - or perhaps, so it's extensible but backward compatible - you need some thought about what should be in it and how it's to be called. I think, for example, Acorn's ABI is quite small but each call site allows many possible functions, whereas Commodore's C64 has lots of call sites.

I suspect study of one or more ABIs might be fruitful. I'm most familiar with Acorn's MOS for the BBC Micro - and it is a good case, because every call can be a remote call, and it's quite well done. No doubt Atari and perhaps Apple had a good story too, as well as the aforementioned Commodore. There are also several more recent homebrew OS and library implementations.

Acorn doesn't have, I think, an allocator for zero page, and I'm not sure if anyone has. Static allocations between the OS and the application might be more common. Acorn does have a system of allocation for low memory though. And some of the zero page is (I think) claimed and released, for example by a filing system.

Managing zero page is, I think, a matter of discipline and documentation. Your various leaf routines can share some scratch space, but as you've noted, a non-leaf routine can only use the scratch space between calls. As noted, the stack is rarely used in an '02 system.

Overall, I think study of existing approaches would be the way to go - you're unlikely to find a treatise on general principles, although I could be wrong.

Good luck and please keep us informed of your journey, and don't hesitate to ask questions. You'll get a variety of answers, that's for sure.


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 9:23 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8544
Location: Southern California
Welcome.

Quote:
Also, while we’re at it, what’s the ‘best’ way to pass parameters?

I have a whole treatise on 6502 stacks (plural, not just the page-1 hardware stack) at http://wilsonminesco.com/stacks/ . Chapter 6, on parameter-passing methods, will be of particular interest to you; also chapter 14, on local variables and environments, and the links at the bottom of that page. Do go through the whole thing though. Stacks go far beyond what most 6502 programmers realize or have ever imagined, and can solve a lot of problems and keep you out of trouble in situations that may otherwise have no solution.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 9:46 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
Also, there are lots of previous discussions here which might help - for example, perhaps:
Passing a word from the call-site
Safest Zero Page Locations across Multiple Systems


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 10:07 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
8bitPogle wrote:
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!
Hard to believe the answer is actually simple. But a stack can very easily allocate memory upon entry and release it upon exit. And in case you think I'm talking about an abstract, theoretical possibility, let me assure you I have "been there, done that" and I know it works.

Among the various advantages is relief from crowding in zero page. In my own case I don't require backward compatibility with an existing BIOS, and there's actually quite a lot of unused zero page. :shock:

-- Jeff

ps- welcome!

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Sun Dec 05, 2021 10:41 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
8bitPogle wrote:

I would really appreciate any thoughts, suggestions and reflections - all responses very welcome!


Couple of good replies already, but one thing of interest, or at least noteworthy to me at least, is that unlike the 8080/Z80 there was never really a common software work environment for the 6502. In 8080/Z80 land there was CP/M. So many hardware vendors and just one CP/M to rule them all...

OK, not quite as there were many Z80 systems but outside the typical home use it was CP/M.

In 6502 land... Well, every vendor, system, etc. had it's own eco-system. You wanted to program for the Apple II? Sure - there's the big red book. The Commodore PET? The BBC Micro? Ohio Scientific? Aim-65? Oric? Tangerine? and who knows how many more... They all have their own reference manual(s) and make code run seamlessly over more than one system was never a trivial task. The only thing in common was the 6502...

A memory allocator? Sure - I wrote one in Sweet16 for a project a few years back. It worked well, but it was for one specific application rather than generic for the whole (operating) system... The next time I wrote one for a 65xx system it was in BCPL. First get BCPL going.. See "bootstrap paradox" ...

And so today... What do we do? We roll our own monitor or operating system or make it look like (and in some few cases identical to) systems of old - like the ones we grew up on... My own Ruby project is typical there - I started with some thing that resembled WozMon but when I wanted something more usable for my own projects I wrote an Acorn MOS compatible OS for it which I use today on both the 65C02 and 65C816 boards.

The Acorn MOS has a very coherent operating system call system with methods to do simple tasks like print a character or get a character to slightly more complex functions that mostly deal with the hardware but only require 1 or 2 bytes of parameters (OSBYTE) to very complex functions where you pass a pointer (in XY) to a parameter block (OSWORD). Then over that is the filing system - the OS ROM defines the filing system interfaces but (other than the tape and ROM filing systems) passes those calls off to other ROMs to do your bidding. Memory usage is very well defined - e.g. for applications (Which live at $8000 through $BFFF) you have Zero page from $00 through $8F and a private area from $0400 through $07FF. Your application data might typically start at $0E00 although this can be raised to leave private space for filing systems - Your application is expected to use the operating system to tell it the value of PAGE which is the lowest location you can use and HIMEM which is the highest (Screen RAM ends at $7FFF and comes down from there, so in a typical system with disks you might have from $1900 through $7BFF with the 1KB from $7C00 through $7FFF being the 40x25 text screen.

From my Ruby system running BBC BASIC:

Code:
* /basic4
>
>
>P.~PAGE,~HIMEM
       E00      8000



However it doesn't have a specific memory allocator for applications - applications just get all of RAM minus the reserved bits...

So it looks like you adapt something you like and want to stick to or roll your own...

It's never easy!

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
 Post subject: Thanks!
PostPosted: Mon Dec 06, 2021 11:29 am 
Offline
User avatar

Joined: Fri Dec 03, 2021 2:54 pm
Posts: 11
Some stuff to get my head around here! Many thanks everyone


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 06, 2021 11:58 am 
Offline
User avatar

Joined: Wed Aug 05, 2020 8:41 pm
Posts: 47
Location: Montreal, QC, Canada
Dr Jefyll wrote:
Hard to believe the answer is actually simple. But a stack can very easily allocate memory upon entry and release it upon exit. And in case you think I'm talking about an abstract, theoretical possibility, let me assure you I have "been there, done that" and I know it works.
I presume that you can't do this with ISRs though?

_________________
Fred Segard
A.K.A. The Micro Hobbyist
https://6502sbc.blogspot.com/


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 06, 2021 1:45 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3367
Location: Ontario, Canada
fredericsegard wrote:
I presume that you can't do this with ISRs though?
Yes you can if the ISR is sensibly written. By the time the ISR is complete and the RTI is about to occur, the number of items pushed must equal the number of items pulled. In other words, the interrupt must leave things as it found them. SOP. :)

Edited to clarify: If you've pushed something then pulled it off again then the stack pointer will end up exactly as it was. However, there's been a write to memory, and this memory location is not put back as it was. We can tolerate that because the usual rule is to always treat memory below the stack pointer as undefined. Ie; if you index into the stack the index must be positive.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 20, 2021 4:55 am 
Offline

Joined: Wed Jan 09, 2019 1:15 pm
Posts: 8
I've been writing an application framework and encountered similar issues during development. Many of the possible approaches have already been covered in this thread, but one I've found useful is a simple dynamic allocation of memory.

For example, a subroutine that instantiated a number of "objects" with a section of memory (array element) for temporary storage of data linked to common subroutines, such as for a game character (X, Y, colour, image) or display port (Origin, width, height, cursor position)) accessed by a unique ID that served as a handle to the object instance and also as an index to its memory space within an array (usually sized in a power of 2 for easy expansion of the handle to a valid CPU index value). The Allocator pulled or pushed a handle from pool populated from 0-n. A pointer marked the next unallocated ID, like a stack, but the extra layer of indirection allowed dynamic allocation, which was useful when persistence was variable, (eg display ports). This proved quite useful for iterating over objects that were the same, or featured similar subsets of variables such as coordinates and colours.


Top
 Profile  
Reply with quote  
PostPosted: Mon Dec 20, 2021 6:09 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
CronicBadger wrote:
... The Allocator pulled or pushed a handle from pool populated from 0-n. A pointer marked the next unallocated ID, like a stack, but the extra layer of indirection allowed dynamic allocation, which was useful when persistence was variable, (eg display ports). This proved quite useful for iterating over objects that were the same, or featured similar subsets of variables such as coordinates and colours.

It sounds like you're describing a heap. If implemented and utilized carefully, it's a valid and possibly even an efficient technique.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 31, 2022 10:49 pm 
Offline

Joined: Tue Jan 11, 2022 5:56 pm
Posts: 17
Good stuff, I came to ask a very similar question so I'll just add to this thread.

Does anybody have links to any good reading material for OS design on 8 bit machines?

I'm hoping to build something along the lines of CP/M or DOS. I'm picturing a BIOS ROM with hardware specific drivers for basic stuff, a boot routine to load the full OS from storage of some kind, the ability to load device drivers, and of course load and run programs from storage. IMO using a 65816 would make everything incredibly easier as far as memory management and passing parameters, but it's of course doable on a 6502 (I'd consider the 65C02 bare minimum).

It would be really cool if we could create an open source 65xx OS that we all contribute to. Then anybody building a CPU board could write a BIOS for their hardware and boot the OS on their own board.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 31, 2022 11:16 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8544
Location: Southern California
Greg816v2 wrote:
Does anybody have links to any good reading material for OS design on 8 bit machines?

Go to my links page and do a <Ctrl>F ("find") search for the line:

    André Fachat's context Switching and thread synchronization on a 6502

which is the first line of about a dozen and a half on OSs. I won't take the time to convert all the links to phpBB code here. 11 of the links are to topics on this forum, including DOS/65 and CP/M-65.

This does not seem very closely related to the OP though, so do start another topic if you want to carry it further, or revive one of the ones linked there if you have comments or questions on their same subject.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 01, 2022 9:24 pm 
Offline

Joined: Tue Jan 11, 2022 5:56 pm
Posts: 17
GARTHWILSON wrote:
Greg816v2 wrote:
Does anybody have links to any good reading material for OS design on 8 bit machines?

Go to my links page and do a <Ctrl>F ("find") search for the line:

    André Fachat's context Switching and thread synchronization on a 6502

which is the first line of about a dozen and a half on OSs. I won't take the time to convert all the links to phpBB code here. 11 of the links are to topics on this forum, including DOS/65 and CP/M-65.

This does not seem very closely related to the OP though, so do start another topic if you want to carry it further, or revive one of the ones linked there if you have comments or questions on their same subject.


Perhaps I was too brief. The concerns in the OP, API calling conventions, parameter passing, etc, plus the next step that is somewhat more architectural in regard to how the OS/programs coexist, is what I'm after - with a focus on what 8bitPogle mentioned:

Quote:
my question is does anyone know of any material that covers this aspect of 6502 development


I know quite a bit about it on modern computers but nothing is relevant to 8 bits. Surely someone has written something about OS design on 6502 that covers different design options and pros/cons? I could look at how Commodore or Atari or whoever organized their systems, but, did they do a good job? In many cases I don't think so, rush to market and other factors lead to sub-optimal design choices in many cases, IMO. Is there a shining example of the best way to do it? Apple?

As for the rest, drogon touched on the fact that there was no common 6502 environment. Would be nice if there was now - I don't see anybody putting running DOS/65 as a goal when designing a board, whereas people putting Z80 boards together will frequently try to run CP/M. That is certainly a topic for another thread, sorry :D


Last edited by Greg816v2 on Tue Feb 01, 2022 10:19 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 17 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: