6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue Jun 25, 2024 11:36 pm

All times are UTC




Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Fri Jan 31, 2020 7:13 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
Say I want to write a fairly generic routine usable on multiple 6502 systems, such as Apple, Commodore, BBC, Atari, other popular commerical 8-bit systems, and homebrew systems using popular free monitors and other software. If it needs a few zero page locations, where's the safest place to which to default these? Obviously there's no location that will work for everything, but there are probably some that have fewer collisions than others.

It's probably also worth distinguishing locations safely usable only during a single call to a routine, but likely to be destroyed between calls, and locations that will be preserved across multiple calls to a routine, with other "normal" stuff (such as reading characters from/printing characters to the console) happening in between.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Fri Jan 31, 2020 7:37 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
On the BBC, locations $00-$8F are reserved for the running language. BASIC uses up to $6F itself, leaving $70-$8F for the user if he wants to merely return to BASIC without rebooting it - but it's permitted to clobber all of that if you're *not* returning to BASIC afterwards. On most BBCs, $90-$9F are also available as they are reserved for the Econet interface, which is comparatively rare in a home system but was fairly common in school machines.

The C64 uses a 6510, which treats locations $00-$01 specially (it's a built-in I/O port). I don't know any further details offhand, but it'd be wise to avoid those two addresses in generic code.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jan 31, 2020 7:58 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10834
Location: England
I've a feeling from previous excursions that the Beeb and the Apple II have chosen opposite ends of page zero to leave free, which then doesn't give any overlap.


Top
 Profile  
Reply with quote  
PostPosted: Fri Jan 31, 2020 9:16 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1435
Location: Scotland
BigEd wrote:
I've a feeling from previous excursions that the Beeb and the Apple II have chosen opposite ends of page zero to leave free, which then doesn't give any overlap.


^This )-:

However I can run Applesoft and BBC BASIC in my Ruby which has a more or less Acorn MOS compatible OS that uses a lot of $90->$FF, but Applesoft currently clobbers some of the zero page stuff I'm using for the filing system - it's on my "to do" list to re-assemble Applesoft to be more RubyOS compatible, but quite far-down as it's only for historical purposes.

EhBASIC runs fairly well though - I recall it only uses < $80.

I think the solution though is to just have a well defined block and make that block start (.ORG or whatever directive) easy to change - e.g. a little documentation and a set of named variables/constants at the top of the source file that lets you change the start of Zero page usage, the start of your data usage and the start of your code.

-Gordon

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


Top
 Profile  
Reply with quote  
PostPosted: Fri Jan 31, 2020 9:47 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1938
Location: Sacramento, CA, USA
drogon wrote:
I think the solution though is to just have a well defined block and make that block start (.ORG or whatever directive) easy to change - e.g. a little documentation and a set of named variables/constants at the top of the source file that lets you change the start of Zero page usage, the start of your data usage and the start of your code.

Nailed it. That is not the only way, but it is the most efficient IMO. The only general-purpose alternatives which come to mind are saving and restoring the contents of ZP locations you need to use via the stack or avoiding ZP usage altogether with self-modifying code, and I can't see either of them being able to reliably compete with drogon's suggestion ... creating a stack frame or a private DP might be feasible with an '802 or '816, but would be very cumbersome or impossible with an '02 or 'c02.

_________________
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: Fri Jan 31, 2020 1:41 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
Yes, I strictly avoid $00 and $01 due to the 6510 thing. For larger programs I've been reserving $00-$15 for "BIOS" stuff and starting my stuff at $16, but in this case I was thinking about small routines (such as an I/O driver) that you might want to share amongst multiple applications and might need 2-4 bytes of easy-to-access storage.

Sad to hear that BBC went the other direction from Apple. (Not that there are a lot of addresses free on an Apple II if you want to work with the monitor, Integer and Applesoft BASIC, DOS and ProDOS.) I guess in the end one needs to settle on specific addresses on a platform-by-platform basis, if one wants to fix them to run with multiple applications.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Fri Jan 31, 2020 2:49 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1435
Location: Scotland
cjs wrote:
Yes, I strictly avoid $00 and $01 due to the 6510 thing. For larger programs I've been reserving $00-$15 for "BIOS" stuff and starting my stuff at $16, but in this case I was thinking about small routines (such as an I/O driver) that you might want to share amongst multiple applications and might need 2-4 bytes of easy-to-access storage.

Sad to hear that BBC went the other direction from Apple. (Not that there are a lot of addresses free on an Apple II if you want to work with the monitor, Integer and Applesoft BASIC, DOS and ProDOS.) I guess in the end one needs to settle on specific addresses on a platform-by-platform basis, if one wants to fix them to run with multiple applications.


Acorn who made the BBC Micro already had "prior art" with the Acorn System 1 (1979) and Atom (1980) computers, so already had their own base to build on. The Apple II really was a bit all over the place... The monitor, sweet16 and Integer Basic went together fairly well, but then Applesoft - derived from MS Basic upset the cart somewhat (can't use sweet16 with Applesoft as it overwrites a lot of zero page and that's before you worry about switching the language card off.. (or on)

The Acorn approach is quite sensible, but it came after all the MS Basics (Apple, OSI, PET), so while they had a lot to look at and possibly base their own thing on, the also had the benefit of a fresh start and their setup worked over all BBC Micros - Beeb, Beeb64K, Master, Electron, Master compact (and whatever else)

Zero page $00 through $9F is for the current application (e.g. BASIC, or a word processor, spreadsheet, or other programming language, etc. which normally lives in $8000 through $BFFF) with $90 through $FF being reserved for the operating system. (BASIC itself reserves $80 through $8F for user programs which is handy because BASIC has a built in assembler)

The OS also claims RAM from $200 through $DFF , however $400 through $7FF are reserved for the current application (e.g. BASIC, wordprocessor, etc.). $800 through $DFF are reserved for buffers for various bits of the OS - sound and video processing, cassette tape buffers, keyboard, function key expansions, user-definable fonts and some other uses.

Utility ROMs can extend the value of PAGE (which starts at $E00) for private workspace - so e.g. a filing system ROM can reserve some private space for buffers and so on - typically the value of PAGE would be $1900 in a system using the Advanced Disk Filing System. So BASIC, on entry, would read the value of PAGE from the OS then use that as the start of program data. The upper value being the bottom of screen memory which started at $7FFF and grew downwards.

So I think that starting fresh put them in a good position to make the system expandable and usable by many programming languages, filing systems, and so on - as long as you stuck to the rules - stick a 2nd processor onto it and (if it's a 6502!) everything gets invisibly copied over and PAGE starts low ($200 IIRC) and the top of memory is way up there - $8000 or even higher if a "Hi BASIC" version is used.

These days - it's easy to start again, and we end up with XKCD 927 https://xkcd.com/927/ or just be prepared to be flexible....

Cheers,

-Gordon

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


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 01, 2020 3:47 am 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1378
Well, Page zero is always a challenge on the 65(C)02. Most of my early experience was with the Commodore machines... Vic-20 and C64. Both of those had very little page zero space free.

Fortunately, when building my own systems, I'm in control, so for better or worse, I tend to structure the memory map for what I can consider logical. For Page zero, my BIOS occupies the top 32 bytes, from $E0 - $FF. My Monitor uses 48 bytes just below the BIOS, from $B0 - $DF. As a result, Page zero from $B0 - $FF is already allocated. My CMOS version of Enhanced Basic uses Page zero from $00 - $85, which is less than the normal Enhanced Basic, as I keep the CHRGOT equivalent routine in ROM, not in Page zero. It's also contiguous space, where the original Enhanced Basic has usage spread throughout Page zero, making it difficult to get it working with any system. J.G. Hartson did an initial condensed page zero allocation for EhBasic (many thanks for this), which I used as a starting point and I made changes to that for my CMOS version.

I also find it useful to use some of the new instructions which Rockwell provided and were later added by WDC, namely, BBSx, BBRx, SMBx, RMBx. I use these quite a bit in my BIOS and Monitor. These allow me to use a single Page zero location for multiple flag bits easily for BIOS functions and Monitor routines, which can save multiple Page zero locations.

As for the rest of my preferred mapping, I use Page $FF for ROM and start with a JUMP table for BIOS functions. Page $FE is for I/O addressing. Based on this, one could actually have upwards for 63.5KB of RAM, provided you can bootstrap some loader in the top 250 bytes, or use some other method to load some functioning code.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 12:28 am 
Offline

Joined: Mon Sep 17, 2018 2:39 am
Posts: 133
Hi!

cjs wrote:
Say I want to write a fairly generic routine usable on multiple 6502 systems, such as Apple, Commodore, BBC, Atari, other popular commerical 8-bit systems, and homebrew systems using popular free monitors and other software. If it needs a few zero page locations, where's the safest place to which to default these? Obviously there's no location that will work for everything, but there are probably some that have fewer collisions than others.


On the Atari 8-bit computers the OS reserves half the zero page to itself, locations from $00 to $7F. Also, it you plan to call into the math-pack, it will use locations from $D4 up to $FF (but not all math-pack functions use all that).

If you want your assembly code to be callable from BASIC, you also need to preserve all the BASIC variables, so you only have from $CB to $D3 available - but you can use various ZP locations from the math-pack in that case, for example $D4 and $D5 holds the 16 bit result passed to BASIC from an ASM call.

I have to ask, why do you want fixed ZP locations? If you plan to distribute only binary code, you would also need a fixed absolute location for the code. If you have the source, just redefining the ZP locations addresses would work. And as any I/O will be very different from system to system, it is really hard to write portable binary code.

Have Fun!


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 3:51 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
dmsc wrote:
I have to ask, why do you want fixed ZP locations? If you plan to distribute only binary code, you would also need a fixed absolute location for the code. If you have the source, just redefining the ZP locations addresses would work. And as any I/O will be very different from system to system, it is really hard to write portable binary code.

It's actually about ABIs between bits of software. The particular idea I was kicking around came up in the OS or Filesystem for a 65C02 thread. First, having strictly separated the Block Device Interface from the higher levels (filesystem and DOS), one can the substitute different BDIs, even ones not available at the time the DOS was built, simply by changing a pointer. (Though I'd imagine it would be better for the DOS to handle having a list, modifiable at runtime, of pointers to different BDIs.) Then, as I worked on the details of a 6502 ABI for the BDI, I realized that there were enough parameters for a BDI call that a parameter block made sense, and passing this as a pointer in a zero page location is clearly more efficient than, e.g., passing the parameter block address in A:X or, slower yet, pushing parameters on the stack.

(As a side note, this illuminated for me one of the nice things about the 6800 design: in this style of ABI one can load the 16-bit X index register with the base address of the parameter block, use single-byte static offsets from X to set and read the various parameters, call the routine with that address still in X, and the subroutine would also use static single-byte offsets from X to access those parameters. I still doubt that makes up for having only one index register, though. :-) (The 6502 didn't exactly have multiple index registers usable for copying data, but the ZP,Y addressing mode basically gave you the same thing.))

This all actually makes even more sense for the upper layers: DOS calls also need a fair amount of information, particularly pointers to things, so using a parameter block there makes sense, too and, again, on the 6502 the zero page is the most efficient place to put such a pointer to pass between two routines. Here you really want to be able to have a common location if possible because it would be nice to be able to use the same DOS-using machine-language subroutines from different applications ("langauges," as the BBC OS refers to them) such as Integer BASIC, Applesoft BASIC, Pascal, KRUSADER, whatever.

Clearly this isn't going to happen across all machines, but perhaps one can come up with constant locations for such pointers for individual families: Apple, BBC, Commodore, etc.

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 9:10 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10834
Location: England
cjs wrote:
...passing this as a pointer in a zero page location is clearly more efficient than, e.g., passing the parameter block address in A:X or, slower yet, pushing parameters on the stack.

Although this is true, and accords with our instincts in coding for the 6502, I think it might not be worthwhile. It will save a few bytes of code and a few clock ticks, but as you've noted, it also comes with disadvantages, and the small saving is in the context of a costly function. So it might be a false economy. (It might be worth noting that Acorn's MOS OSWORD family of calls passes the parameter block address in YX and the call type in A. By this means a single call site gives access to a large number of facilities.)


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 10:14 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
BigEd wrote:
cjs wrote:
...passing this as a pointer in a zero page location is clearly more efficient than, e.g., passing the parameter block address in A:X or, slower yet, pushing parameters on the stack.

Although this is true, and accords with our instincts in coding for the 6502, I think it might not be worthwhile. It will save a few bytes of code and a few clock ticks, but as you've noted, it also comes with disadvantages, and the small saving is in the context of a costly function. So it might be a false economy.

Yes, now that I think about it, for most situations you're right.

That said, there are a few situations where, even for long-running functions, latency to the next call might be critical for performance. For example, in the Block Device Interface I mentioned above, if after reading a block from rotating media you find the next block you want is on the same track but you wait to long to call again to read it, you're going to have to wait until the appropriate sector(s) come around again. (This is a pretty classic disk performance problem, and the reason for interleaving sectors on disks.)

On a floppy disk with standard formatting or something similar, I calculate the gap between the CRC of a sector data field and the start of the sync just before the next sector's index address mark to be about 30 microseconds, or 30 clock cycles on a 1 MHz 6502. I'm toying with ideas for calling the block read routine again quickly enough that you could read the subsequent block/sector(s) without waiting for a full disk rotation, so when you have contiguous sectors allocated to a file you could read them in a single disk revolution, though that's probably not practical, but in such a case the calling convention might make all the difference in whether or not you could do that.

But if you interleave your sectors you have more time; well over 200 cycles for a 2:1 interleave and close to 500 for a 3:1 interleave. If you can use 2:1 instead of 3:1, you speed up loading of a track by about 33%.

Quote:
(It might be worth noting that Acorn's MOS OSWORD family of calls passes the parameter block address in YX and the call type in A. By this means a single call site gives access to a large number of facilities.)

Yeah, I totally stole that idea. :-)

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 3:55 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
The MOS approach is easy to implement. Your routine knows a zero-page location to use as a trampoline, and is passed the parameter block in Y and X. It takes only two instructions, four bytes, six cycles to move Y and X to zero-page. As a generic approach, this is hard to beat on the 6502.

The '816 has more addressing modes which make this considerably easier, even if you don't make use of the Direct Page register, because you can now sensibly pass parameters on the stack. Not only is the stack no longer limited to 256 bytes, but you can perform stack-relative addressing including indirects. Caveat: there is no stack-indirect-long addressing mode, so the DBR supplies the high byte of the indirect address implicitly.


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 4:30 pm 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
Chromatix wrote:
The MOS approach is easy to implement. Your routine knows a zero-page location to use as a trampoline, and is passed the parameter block in Y and X.

Is Y:X an arbitrary choice, or are there reasons to choose one pair of registers (and MSB/LSB choice within the pair) over another?

In my initial stab at an API I put the function code in X because then one could use a sequence of DEX, BEQ instructions to dispatch. (I was assuming just a few fixed functions, with the most-often used one being 1, the next being 2, etc.) If you have a large number of functions I suppose you'd probably want to put the code in A so you could shift it left, and use that as an index into a jump list in one of at least two ways.

But if you don't have considerations like that? (E.g., if the code, if any, is in the parameter block?)

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 02, 2020 5:00 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1435
Location: Scotland
cjs wrote:
Chromatix wrote:
The MOS approach is easy to implement. Your routine knows a zero-page location to use as a trampoline, and is passed the parameter block in Y and X.

Is Y:X an arbitrary choice, or are there reasons to choose one pair of registers (and MSB/LSB choice within the pair) over another?

In my initial stab at an API I put the function code in X because then one could use a sequence of DEX, BEQ instructions to dispatch. (I was assuming just a few fixed functions, with the most-often used one being 1, the next being 2, etc.) If you have a large number of functions I suppose you'd probably want to put the code in A so you could shift it left, and use that as an index into a jump list in one of at least two ways.

But if you don't have considerations like that? (E.g., if the code, if any, is in the parameter block?)


I'm not sure the reasons, but in the Acorn MOS there are 3 "levels" of API sort of - the simple ones e.g. osWrch and osGetch - write and get a character respectively - pass the character in A, get the result in A (there are also a few shortcuts too e.g. osNewl to take a new line) then there is osByte - which as it's name may suggest takes a single byte (ish) parameter - the code is in A with the data in X&Y - a lot of these are to do with setting internal variables in a controlled manner, so e.g. osByte 125 ($7D) sets an "Escape" condition (think Ctrl-C) and osByte 124 ($7C) clears it. There is also a range in there that works like:

Code:
; All "os variables" are accessed the same way:
;       new = (old AND Y) EOR X
; so set Y to zero to set the value, otherwise use it as a mask.


So your program doesn't need to know where in RAM the variables actually are, just that the calls will remain consistent over OS and even platform upgrades.

Then finally osWord which uses X (low byte) and Y (high byte) to point to a parameter block with A containing the code. e.g. osWord 0 is "get a line of text" and the parameter block containing the address, max length and character ranges. Your data block could be anywhere in RAM. Ruby OS which is mostly Acorn MOS compatible has this, but internally has a short-cut which does:

Code:
_getline:
        ldx     #<_getlineData
        ldy     #>_getlineData
        lda     #0
        jmp     osWord

_getlineData:
        .word   keyboardIn      ; Address of input buffer
        .byte   250             ; Max length
        .byte   32              ; Smallest value to accept
        .byte   126             ; largest...


which gives a good example of it's usage. (and the getlineData block would be in ROM if it were a ROM system)

The filing system "API" is more akin to osWord and again starts from the simple "whole file at a time" up to seek, get/put bytes and fiddle with file attributes and so on. osFile (while file at a time) has X (low) and Y (high) as the pointer to the data block with the command in A - that's the same as osWord in essence. The first word in the osFile parameter block is the address of the data to write from or read into.

Code:
; Offsets into the osFile parameter block

fName           =       $00
lAddress        =       $02             ; Load address
xAddress        =       $06             ; Exec address
sAddress        =       $0A             ; Start address for save, length for load
eAddress        =       $0E             ; End address for save or attributes


(Note the addresses are 32-bit as this can be called from a 16 or 32-bit 2nd processor to the BBC Micro host)

Hope that's of some use..

Cheers,

-Gordon

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


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

All times are UTC


Who is online

Users browsing this forum: No registered users and 31 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: