Question about smoothly handling multiple loops in asm.

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

lazerbeat
Posts: 64
Joined: Tue Jul 09, 2013 7:13 am

Question about smoothly handling multiple loops in asm.

Post by lazerbeat »

This might be a bit of a silly / obvious question and I am not sure what the correct terminology is so it is a bit hard to google. Using asm6 something like this

Code: Select all

function1:
	ldx #$00
loop
       --do something--
	inx
	bne loop

function2:
	ldx #$00
loop
       --do something--
	inx
	bne loop
Will obviously give me a "label already defined" error when compiling

Code: Select all

function1:
	ldx #$00
loop1
       --do something--
	inx
	bne loop1

function2:
	ldx #$00
loop2
       --do something--
	inx
	bne loop2
Works fine but with a long program that is a lot of branch locations to keep track of. Is there a way in ASM, specifically ASM6, to use the same branch location (sorry if that isn't the right terminology) if it is in a local loop?
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by rainwarrior »

What you're looking for is called a "local label".
README.TXT wrote:

Code: Select all

Labels are case sensitive.  The special '$' label holds the current program
address.  Labels beginning with '@' are local labels. They have limited scope,
visible only between non-local labels.  Names of local labels may be reused.

        label1:
          @tmp1:
          @tmp2:
        label2:
          @tmp1:
          @tmp2:

Labels beginning with one or more '+' or '-' characters are nameless labels,
especially useful for forward and reverse branches.

    example:

      --  ldx #0
       -  lda $2002 ;loop (wait for vblank)
          bne -
       -  lda $2002 ;nameless labels are easy to reuse..
          bne -

          cpx #69
          beq +     ;forward branch..
          cpx #96
          beq +here ;use more characters to make more unique

          jmp --    ;multiple --'s handy for nested loops
       +  ldx #0
   +here  nop
lazerbeat
Posts: 64
Joined: Tue Jul 09, 2013 7:13 am

Re: Question about smoothly handling multiple loops in asm.

Post by lazerbeat »

Thank you sir! That is exactly what I was looking for.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by Dwedit »

Note that any non-local label will act as bounds for local labels, even the nameless + and - labels.
So you can't do this:

@back:
-:
bne -
jmp @back
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by Garth »

This is my first post here. I'm the moderator on 6502.org.

In my article at http://wilsonminesco.com/StructureMacros/, I show how to do structure macros in 6502 assembly. The inner details of how your particular assembler does macros may vary slightly. You can however do something like the following, with no labels at all. The resulting assembled code will be exactly the same.

Code: Select all

function1:
        LDX  #0
        BEGIN
           <do_stuff>
           INX
        UNTIL_0


function2:
        LDX  #0
        BEGIN
           <do_stuff>
           INX
        UNTIL_0
or more to the point (and again, this will result in the same assembled code),

Code: Select all

function1:
        FOR_X  0, TO, 0
           <do_stuff>
        NEXT_X


function1:
        FOR_X  0,  UP_TO,  0
           <do_stuff>
        NEXT_X
0 to 0 might look funny, but that's what we're doing in the X register. The "to" part is slightly different from BASIC's "TO," because in assembly, we do the increment or decrement and then compare to the number specified to see if we have a match to drop through. IOW, the loop does not get run again with the final number like it would in BASIC.

These can be nested, too (although if you nest FOR_X loops, you'll want to save X before the nested loop and restore it after it, so the outer loop's NEXT_X will be looking at the right value). The macros will use the BEGIN or FOR to mark the address for the UNTIL or NEXT to branch up to, and put the addresses on a stack in the assembler itself (not in the target 6502 computer). The stack method is what allows them to be nested without fouling up each other's information as to what condition to branch on and where to branch to.

So you could have for example,

Code: Select all

        CMP  #14
        IF_EQ
            <actions>
            <actions>
            IF_NEG
                <actions>
                <actions>
                IF_EQ
                    <actions>
                    <actions>
                END_IF
            END_IF
            <actions>
            <actions>
        END_IF
with each "IF" line assembling a BEQ or BNE instruction, and marking the address where the operand will need to be filled in, and then the END_IF assembles nothing but is now able to go back and fill in the correct operand for the corresponding branch instruction. Again, because of synthesizing a stack in the assembler itself, they all go to the right places. No labels.

There's more about how the insides of the macros work in chapter 17 of my 6502 stacks treatise, at http://wilsonminesco.com/stacks/pgmstruc.html .

The last 30% of my 6502-oriented article on simple methods of multitasking without a multitasking OS has longer examples from my actual work that show extensive macro use in 6502 assembly, with program structures. See http://wilsonminesco.com/multitask/index.html .
Last edited by Garth on Sun May 30, 2021 6:40 pm, edited 1 time in total.
http://WilsonMinesCo.com/ lots of 6502 resources
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by FrankenGraphics »

Garth, welcome and thanks for moderating 6502.org, that side is a great learning resource. Using BASIC-like structure macros would help me a lot. Here's a newbie question: Is replacing BRA with a known condition in your macro files as "straightforward" as it would be when writing in plain assembly? I guess my question is; can the condition always be known in a general macro?
User avatar
dustmop
Posts: 136
Joined: Wed Oct 16, 2013 7:55 am

Re: Question about smoothly handling multiple loops in asm.

Post by dustmop »

Another option, that I personally find very useful, supported by ca65 (and possibly other assemblers), is to use scopes. Labels defined within one scope are only visible within that scope.

Code: Select all

.scope First
	ldx #$00
Loop:
	...
	inx
	bne Loop
.endscope

.scope Second
	ldx #$00
Loop:
	...
	inx
	bne Loop
.endscope
The .proc directive, meant to be used for procedures / functions, automatically creates a scope.
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by Garth »

WheelInventor wrote:Garth, welcome and thanks for moderating 6502.org, that side is a great learning resource. Using BASIC-like structure macros would help me a lot.
I patterned mine mostly off of standard Forth structures, with a notable exception being FOR...NEXT, because I didn't think the Forth way,
<to> <from> DO...LOOP
would be as easy to pick up for people who have not been using Forth. (It's like a gift with the tag on it, "To: <recipient> From: <giver>.)
Here's a newbie question: Is replacing BRA with a known condition in your macro files as "straightforward" as it would be when writing in plain assembly? I guess my question is; can the condition always be known in a general macro?
I'm not sure exactly what you're asking, so I'll try to answer a few different possibilities.

Yes, you can still use labels and normal branch instructions at any time, like if you don't have a structure macro yet to do exactly what you want. In most cases, it will be good discipline to try to stick with a good set of structures (which may mean you change the way you approach the problem); but there is no denying that once in a while you'll have a situation where you will get better efficiency if you can branch into the middle of one for example, using a label.

Another thing I can imagine you might mean is about BRA versus JMP, like if the structure is so long that B__ won't reach. I find that those situations are rare, so the macros I provide on that section of my website don't provide for them. (Again, you can use the old way at any time, if you need to.) I do use JMP for the END_OF's in a CASE structure where after executing the conditional code, you jump to the end of the structure which, with many cases involved may well be too far for a mere branch. (I also provide END_OF_ (with the trailing "_") to mean not to assemble anything right there for situations where you end the case with RTS for example, or follow it with END_CASE so there's no point in jumping to what comes next anyway.

I do use the CMOS 65c02 instructions, including BRA, in the macros I provide. You can easily replace BRA with JMP for your own use if you're using an NMOS 6502 that doesn't have those instructions. If you re-write them for the 65816, you can use BRL too, "Branch Relative Long," which uses a 16-bit offset suitable for relocatable code. The structure macro thing is not that difficult to understand, but it sure is hard to explain clearly. If the reader catches on, he will be able to make whatever new ones he wants. If you know what's in the macros, whether you use mine or write your own, you will know exactly what code gets laid down. The benefit of course is that you become more productive, get fewer bugs, better code maintainability, etc., and in most cases, there will be absolutely zero penalty in memory taken or in execution speed, because it's producing the same code you would do by hand anyway—it's just that you don't have to look at the ugly internal details every time anymore.

Andrew Jacobs' As65 assembler has the program-structure capability built in, and he has made it so it automatically uses JMP if a branch instruction won't reach. Again, the need is rare.
http://WilsonMinesCo.com/ lots of 6502 resources
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by Garth »

dustmop wrote:Another option, that I personally find very useful, supported by ca65 (and possibly other assemblers), is to use scopes. Labels defined within one scope are only visible within that scope.
The 2500AD assembler I used in the late 1980's used local variables to be ones followed by "$", and those were local to the space between global labels. That way you can re-use labels, even without the scope directive.
http://WilsonMinesCo.com/ lots of 6502 resources
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by FrankenGraphics »

You can easily replace BRA with JMP for your own use if you're using an NMOS 6502 that doesn't have those instructions.
Thanks - and apologies, i forgot to mention that using your structure macros with an assembly-time stack for the 6502 is the goal, and like the OP; using asm6 (which is one of the three most popular assemblers among nesdev board users; along with ca65/cc65, and nesasm, so there's a lot of resources, examples, and community knowledge).

So JMP can always replace BRA - However, it adds 1-3 cycles per use, depending on absolute/indirect mode, if i'm not mistaken. Would i gain from trying to do it like this (citing 6502.org opcode list)?:

Code: Select all

There is no BRA (BRanch Always) instruction but it can be easily emulated by branching on the basis of a known condition. One of the best flags to use for this purpose is the oVerflow which is unchanged by all but addition and subtraction operations
and:

Code: Select all

The overflow flag is not affected by increments, decrements, shifts and logical operations i.e. only ADC, BIT, CLV, PLP, RTI and SBC affect it. 
I think my confusion stems from me not knowing if there's a risk to using the overflow flag-based branch as a BRA surrogate in a structure macro.

Some other details:
-"SETL" seems to be written as "=" in asm6, from what i can tell, so an example would be "TO_PUSH_1 = $" if i'm not mistaken?
-In your files, what does list on and off do? I'm not sure what the corresponding directive would be.

Attaching the asm6 directives list as a .txt
Attachments
README.TXT
(8.79 KiB) Downloaded 204 times
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Question about smoothly handling multiple loops in asm.

Post by koitsu »

I would point out -- mainly for WheelInventor, as a reminder -- that if you're writing "general-purpose" macros where the amount of instructions is variable (say, based on what is passed into the macro (unrolled loop-generating macros would be an example)), using anything other than jmp can be asking for trouble. Imagine: "the macro works fine until I use a repeat value of 37, then the assembly fails with branch-out-of-range".

As for the cycle counts savings: branch instructions take 3 cycles if the branch is taken, otherwise 2. jmp (absolute) takes 3 cycles unconditionally. So whoever wrote that advice on 6502.org (re: since the NMOS 6502 lacks bra, you can accomplish the same using another branch opcode where you know the conditional will prove true) is advising so only for one purpose: to save 1 byte. Using said methodology provides no cycle savings. (For sake of argument: bra takes 3 cycles too, unless it crosses a page boundary, in which case it takes 4).

You'll need to decide for yourself if this is worth "obfuscating" code. (If so, I strongly suggest you put a comment next to the bvc/bvs instruction that acts unconditionally so that you don't ask yourself later "why am I checking overflow here?!?" -- I know it'd throw me off if I saw it anyway).
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about smoothly handling multiple loops in asm.

Post by tokumaru »

koitsu wrote:I strongly suggest you put a comment next to the bvc/bvs instruction that acts unconditionally so that you don't ask yourself later "why am I checking overflow here?!?"
I often use branch instructions for unconditional branching on the 6502, but I always write a comment next to the instruction indicating that it's supposed to act like bra. Speaking of known values for the status flags, I also often comment out clc and sec instructions before additions and subtractions if the state of the carry is known at that point, so the commented instructions just indicate what the value of the carry is supposed to be.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Question about smoothly handling multiple loops in asm.

Post by koitsu »

tokumaru wrote:I often use branch instructions for unconditional branching on the 6502, but I always write a comment next to the instruction indicating that it's supposed to act like bra.
Can you provide some insights into why you use this method in the first place, rather than jmp? I would love to know, as I'm sure readers would as well. I just can't see the justification other than size savings, but there may be cases I'm not thinking of thinking of; I'm not very good at thinking "outside the box".
tokumaru wrote:Speaking of known values for the status flags, I also often comment out clc and sec instructions before additions and subtractions if the state of the carry is known at that point, so the commented instructions just indicate what the value of the carry is supposed to be.
The removal of clc/sec where applicable is more commonplace and has additional benefits (specifically saving both bytes *and* cycles) compared to the above. I can think of advantages and disadvantages to doing it (both situationally and justifiably). I've always included them unconditionally in things I write for the following reasons: 1) it establishes a good base habit (better to be safe than sorry and end up with an off-by-one mistake I have to chase down, and nothing stops me from later on going back and removing them if unnecessary), 2) if someone else views the code I'm not having to explain why they're omitted (i.e. less experienced programmers won't be thrown off by their omission), and 3) in most things I've written I don't have to worry about saving 2 cycles + 1 byte (the few exceptions to this have been more about size constraints and not about cycles).
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by FrankenGraphics »

Thanks for clarifying that, koitsu.
koitsu wrote:You'll need to decide for yourself if this is worth "obfuscating" code.
In the light of what you just said: If the aim is not only to have a personal foundation for structured programming on the 6502, but also ask for permission to share the modified files, then the answer should be JMP. Even if not, with that little difference in efficiency, it may be better to just manually write it out in cases one'd think it may matter. (edit: i'm also interested in hearing what tokumaru has to say about that!)
(If so, I strongly suggest you put a comment next to the bvc/bvs instruction that acts unconditionally so that you don't ask yourself later "why am I checking overflow here?!?" -- I know it'd throw me off if I saw it anyway).
Maybe a good practice would be a simple consistent ";BRA surrogate" every time and where it is done to maintain Ctrl+f friendliness, in case bvc/bvs is used as such.
tokumaru wrote: I also often comment out clc and sec instructions before additions and subtractions if the state of the carry is known at that point, so the commented instructions just indicate what the value of the carry is supposed to be.
Good advice! :beer: I had written in two mini-macros for shortening the clc/adc sec/sbc combos to one line, and supposedly just write adc or sbc in other cases but i can see how that gets in the way of clarity, especially if the code is to be reviewed by someone else.


---
In conclusion; stuff left to figure out:
-How to interpret List on/off in asm6*
-My assumption that "=" can be used to replace SETL w/o it doing anything else.

*It may be unnecessary?
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Question about smoothly handling multiple loops in asm.

Post by Garth »

I get up this morning and find a whole lot of new posts here. I'm glad this idea has quickly generated this level of interest here! It's refreshing, because what I've found in the past is that it's hard to get people past their preconceived notions and keep their attention long enough to clarify that macros offer absolutely zero penalty in most cases, only make the source code more readable and maintainable and make it easier to steer free of bugs and avoid losing control of a big project.

I am convinced that early 8-bit home computers would have been much more capable, even with the same hardware, if software methods had been more developed at he time, including with things like this extent of macro usage. However, at the time, everyone was green, and the entire field was shrouded in mystery and uncertainty. What directions would software development take? What companies and OSs would survive the shakedowns? What was possible to do in hardware, and in software, at the time or in the future? Etc.. It was all a mystery.
WheelInventor wrote:Some other details:
-"SETL" seems to be written as "=" in asm6, from what i can tell, so an example would be "TO_PUSH_1 = $" if i'm not mistaken?
-In your files, what does list on and off do? I'm not sure what the corresponding directive would be.

Attaching the asm6 directives list as a .txt
That looks correct. It may take some experimenting with your assembler to make sure you get the desired output. Actually, you'll want to experiment with the various macros before you depend on them, unless you use the same assembler I wrote mine for. I tested mine extensively, writing out the different structures, including with nesting, and examining the list file output for correct branching conditions and distances.

SETL in the C32 assembler I'm using (which runs on a PC) is like EQU but you can change it as many times as you want, because it's for assembler variables rather than constants. From your attached file, ORG and BASE appear to be the ones to change the assembly address, which the macros will do many, many times in the course of one assembly process.
---
In conclusion; stuff left to figure out:
-How to interpret List on/off in asm6
I'm not familiar with asm6; but LIST "ON" | LIST "OFF" in the C32 assembler turns the generation of list code on and off in the .lst file. Since this assembler didn't have any way to set up an array of assembler variables to use as a stack and be able to index into it, I had to do this mickey-mouse workaround to individually move a large set of variables up and down the list, and if you don't do LIST "OFF" for that portion in the macros, your .lst file would get ridiculously long with no benefits to justify it.
http://WilsonMinesCo.com/ lots of 6502 resources
Post Reply