6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue Nov 12, 2024 4:23 pm

All times are UTC




Post new topic Reply to topic  [ 20 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sun May 28, 2017 10:04 pm 
Offline

Joined: Mon May 04, 2015 3:14 am
Posts: 4
I'm getting back into 6502 assembly programming, and I have been wondering about a couple of things. I'm using Relaunch64 for my IDE, Kick Assembler, and VICE to emulate a C64 (I don't currently have any 6502 hardware).

1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this? In some cases, I have stored the registers in memory locations- other times, I have pushed them onto the stack and popped them off the stack after subroutine calls. A related question-- should it be the responsibility of the main program to save the registers, or should it be done by each subroutine? Seems to me that we should be able to jsr to any subroutine and assume that our registers will not be changed upon return from the subroutine.

2. How best to implement other variables? If, for example, I have a main program which uses a zero-page pointer "ScrPtr" at $fb, should the main program be the only code block which manipulates ScrPtr, or is it common practice for subroutines to work with this kind of variable (global)? In some cases, I have subroutines which manipulate pointers and other variables which the main program uses, but I'm not sure if this is a good idea. The thing is, zero-page memory locations are in short supply, so I'm tempted to just treat them as global variables.

I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2017 10:33 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
The way I see it, highly-modular coding techniques are an advantage for large systems intended to serve multiple programmers. If it's your own system, you are certainly able to choose the level of modularity that makes you the most comfortable and productive. If it's someone else's system, you are more constrained.

I am in favor of keeping machine code size to a minimum, even if it means sacrificing modular techniques to do so. This includes, but is not limited to, multiple entry and exit points in code blocks, register and flag side-effects, global variable side-effects and re-use for unrelated activities, return address tricks, and fall-through from one code block to the next. I got most of that from studying the code of the great Woz, and doing it that way gives me the most satisfaction. However, it can certainly be argued that it's not the best way to build a maintainable project. I comment my spaghetti code liberally, and that helps both me and others to understand what I'm trying to do, but it isn't necessarily for everyone.

No matter what techniques you choose, you will be making trade-offs. The way I see it, it's your code and your choice, as long as you don't let critics of working code get under your skin ... the 6502 certainly doesn't make any judgments about the code it's executing, and it's the most important participant in my opinion.

Mike B.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2017 10:46 pm 
Offline

Joined: Mon May 04, 2015 3:14 am
Posts: 4
Yes, I think the challenge is to find a balance between extreme modularity and extreme monolithic code. For my subroutines, I think what I'll do for now is to have them only save registers that they modify- if only .X is changed, then why save .Y and the Accumulator on the stack? That way, my code is still relatively efficient, and the calling subroutine or program doesn't have to worry about registers being changed by subroutine calls.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 28, 2017 11:16 pm 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1385
With subroutines, it sorta depends, as in some cases registers are used to either pass parameters to the routine or contain the result of the routine's function, i.e., get a character from a buffer. In any case, preserving working registers in routines is good practice, sans those that are required for results.

What is a basic requirement is that any interrupt service routine saves and restores all registers (including the processor status register) when returning from the ISR (interrupt service routine). This makes it transparent to any code that is executing when the interrupt is generated, unless of course the routine is timing critical.

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


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 12:12 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
DougBrunelle wrote:
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this?

There's often no need to do this, although the system you're working with may dictate that one or another may need to be kept intact. For example, X is used as the data stack pointer in a Forth system. In that case, the few primitives that use X for something else must restore it when they're done, or they may intentionally change it to reflect the resulting data stack depth when they're done. In other situations, A, X, and Y are commonly used to pass parameters, and there's no sense in passing the same thing right back.

Quote:
In some cases, I have stored the registers in memory locations

To do that, every subroutine would need its own memory locations. That could end up requiring a lot of memory. It also prevents re-entrant code, which you might need someday.

Quote:
other times, I have pushed them onto the stack and popped them off the stack after subroutine calls.

If something really needs to be saved, this is usually the preferred way.

Quote:
A related question-- should it be the responsibility of the main program to save the registers, or should it be done by each subroutine? Seems to me that we should be able to jsr to any subroutine and assume that our registers will not be changed upon return from the subroutine.

The calling routine presumably knows what the subroutine it's calling does and needs. In most cases, the calling program won't be using A, X, and Y to store data past the duration of the subroutine; but if it does, it (or rather, the person writing the source code) will know if the subroutine changes something, and if so, if it's a desired change or an undesired one, and if anything needs to be done, either in saving something on the stack, or in modifying the subroutine so you don't have to do the same save-and-restore steps in every part of the code that calls that subroutine.

Quote:
2. How best to implement other variables? If, for example, I have a main program which uses a zero-page pointer "ScrPtr" at $fb, should the main program be the only code block which manipulates ScrPtr, or is it common practice for subroutines to work with this kind of variable (global)? In some cases, I have subroutines which manipulate pointers and other variables which the main program uses, but I'm not sure if this is a good idea. The thing is, zero-page memory locations are in short supply, so I'm tempted to just treat them as global variables.

If a subroutine can't change A, X, or Y, and can't change variables either, its usefulness is quite limited. It can output something on the I/O, or do a delay, but otherwise cannot process information and give back results except on a stack (either hardware or virtual). In many cases a stack works well for this; but you will lose a lot of function if you require everything to go through a stack. I think you're going to have to loosen up and let subroutines do what they're there for.

Quote:
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.

In most (not all) cases, the subroutine is for code that gets executed in many places, not just one, and the reason to make the subroutine is so you don't have to repeat that code everywhere it's needed. Again, there are exceptions. As for maintaining the integrity of variables (which can also be pointers—forget about C where they're separate), you can do local variables on the page-1 stack or even a virtual stack after you get going a little more. The '02 doesn't have the needed stack-relative addressing modes like the 65816 does, but stack-relative addressing can be synthesized using an index register and a little overhead which the '816 is free of. My 6502 stacks treatise has a lot on passing parameters, inlining data, local variables and environments, recursion, and a lot more.

_________________
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: Mon May 29, 2017 4:40 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8482
Location: Midwestern USA
Others have answered, but as is my wont, I will throw in my two cents.

DougBrunelle wrote:
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this?

Generally speaking, pushing the registers to the stack is the best way to assure transparency. However, you should pick and choose which subroutines need to preserve the registers, as pushing and pulling registers takes time and of course, consumes stack space. You should survey your application to determine just how important register preservation before you go hog-wild and have every subroutine protect registers.

As Garth noted, subroutines are often of limited value if they can't pass results back through the registers. Also, don't waste time and code space pushing registers that won't be touched by the subroutine.

Quote:
2. How best to implement other variables?

The eight bit members of the 6502 family don't leave you a lot of options with this. You don't have the luxury of stack relative addressing, so you either have to do some potentially convoluted stack acrobatics or you have to allocate memory. My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.

The C-64 is a bit of a pain when it comes to zero page, in that the BASIC interpreter and the "kernal" consume almost all of it, leaving (if memory correctly serves me) only six uncommitted addresses.

Quote:
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.

My general approach has always been to in-line a routine if it will only used at one location in the program. After all, in eight bit 6502 assembly language there are no "local" variables, which means use of subroutines does not accomplish anything as far as data isolation goes. However, if the same routine is used more than two times and that routine is fairly involved, the code shrink and generality that a subroutine could offer becomes compelling.

Ultimately, as Mike noted, if it's your code for your use, do what's makes you happy. Modularity in assembly language usually produces the most readable and easy-to-maintain code but can become serious busy-work for the programmer if many of the functions can be linearly executed.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 6:06 am 
Offline

Joined: Thu Jan 21, 2016 7:33 pm
Posts: 279
Location: Placerville, CA
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call. The other approach is to have the subroutine save only the registers that it uses - the advantage is less code space taken up overall, but the subroutine has no way to know whether it's wasting time saving values the calling routine won't need. The main thing is to pick one and stick with it.


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 6:30 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
Quote:
My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.

Why ? What's the advantage of not using zero page ?


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 6:40 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
I took it to be because the C64 leaves you so little ZP space. I was surprised he said it was so little. I found a good home for all our C64 stuff a couple of years ago, someone who really valued it. One of our sons used the C64's a lot in his junior-high years (but not for games!), but I myself never looked into that detailed information which was in one of the books. I know Forth has been run on the C64 though, and I expect they still put the data stack in ZP, so I wonder if they found an area they could copy out into higher memory while Forth was running, like maybe a range of ZP memory that BASIC used.

_________________
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: Mon May 29, 2017 6:53 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
commodorejohn wrote:
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call.
I have a strong tendency to do it this way when I'm in 6502 land.

Quote:
The other approach is to have the subroutine save only the registers that it uses - the advantage is less code space taken up overall, but the subroutine has no way to know whether it's wasting time saving values the calling routine won't need. The main thing is to pick one and stick with it.
I have a strong tendency to do it this way when I'm in 65m32 land. That's because I have room in my opcode matrix for instructions that can push a register then load it from the operand, and instructions that can store a register to an operand then pull it. They are a memory cycle slower than their simpler load and store counterparts, but don't require additional code space in the source or the executable.

Mike B.


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 7:02 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
barrym95838 wrote:
commodorejohn wrote:
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call.
I have a strong tendency to do it this way when I'm in 6502 land.

Indeed. The 6502 has so few registers, and not much support for using the stack for storage. So the registers are best used as volatile locals in leaf routines. The key is planning and coordination: a non-leaf routine can use a register freely if it knows that all its leaf routines will not use (or will preserve) the register.

Therefore non-leaf routines, and also leaf routines, will use memory for their state, generally zero page where they can and always zero page when necessary for pointers, and so again planning and coordination is needed to decide which locations are to be used by which routines. It's not uncommon to have some shared locations, used by different routines at different times.

It's storage management from the world of the 1960s, which predates the storage management we're used to now.

Or, you can use more modern techniques, but the result will probably be larger, or slower, or not be so recognisably 6502 idiom.


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 7:20 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8482
Location: Midwestern USA
Arlet wrote:
Quote:
My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.

Why ? What's the advantage of not using zero page ?

It's not a matter of advantage as it is of conservation. Uncommitted zero page space in the C-64 (and C-128) is extremely limited, amounting to six bytes in the C-64 that are not touched by the BASIC interpreter and the "kernal."

GARTHWILSON wrote:
I know Forth has been run on the C64 though, and I expect they still put the data stack in ZP, so I wonder if they found an area they could copy out into higher memory while Forth was running, like maybe a range of ZP memory that BASIC used.

If the BASIC interpreter is not going to be used then zero page from $02 to $8F can be usurped for other purposes, which should be enough for a data stack, plus other zero page things the Forth kernel would need. The only thing is that upon exiting Forth the BASIC cold start function, called with JMP ($A000), would have to be run to re-establish BASIC's zero page setup, especially the all-important CHRGET code that reads BASIC text as a program runs.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 7:22 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
I didn't realize you were talking about the C64.


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 7:24 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10977
Location: England
Sounds like the C64, like Acorn's Beeb, allows you to use a healthy 128 bytes or so if your program only interacts with the OS and is not planning to return to Basic.

That's an example of planning and coordination - separation of the two major users of the 6502. (Acorn's OS has the concept of 'current language' which allows an alternate environment - application or programming language - to claim, use, and then ultimately free the resources it needs.)


Top
 Profile  
Reply with quote  
PostPosted: Mon May 29, 2017 7:26 am 
Offline
User avatar

Joined: Tue Nov 16, 2010 8:00 am
Posts: 2353
Location: Gouda, The Netherlands
Quote:
Sounds like the C64, like Acorn's Beeb, allows you to use a healthy 128 bytes or so if your program only interacts with the OS and is not planning to return to Basic.

And if you want to return to BASIC you can probably just restart the interpreter.


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

All times are UTC


Who is online

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