Skip to content

Latest commit

 

History

History
282 lines (228 loc) · 6.35 KB

File metadata and controls

282 lines (228 loc) · 6.35 KB

Control Assembly

Conditional Assembly

NOTE: As of Version 5, all code blocks are wrapped in { and } braces. To revert to previous behavior, you must pass the --legacy-blocks option in the command line.

Certain directives permit the programmer to assemble code conditionally. The most basic conditional assembly statement, where an expression is tested to be true or false, is:

        .if * % 256 != 0    // if program counter not page aligned
        {
            nop             // output 2 nops
            nop
        }

Alternate conditions are possible where previous conditions are not met.

        .if * % 256 == 0
        {
            jmp tightloop
        }
       .else
        {
            nop
            nop
        }
        .if BACKGROUND == 1
        {
            lda #1
        }
        .elseif BACKGROUND == 2
        {
            lda #2
        }
        .else
        {
            lda #0
        }

Other conditions can check if a symbol is defined.

        .ifdef DEBUGMODE
        {
            brk
        }

        .ifndef RELEASE // RELEASE not defined
        {
            rts
        }

.else versions exist for each of these conditional directives.

        .ifdef CBM
        {
chrout      = $ffd2
        }
        .elseifdef APPLEII
        {
chrout      = $fded
        }

Jump Assembly

Goto

The programmer can direct the assembler to jump to other parts of source. The .goto directive will commence assembly at the specified label.

        .ifdef CBM
        {
            .goto commodore
        }
commodore
        jsr $ffd2

Switch

Switch statements allow more compact forms of conditional assembly. For each .switch directive one more more .case labels follows where the case condition is tested for equality with the .switch argument.

    .switch CPU_NAME
    {
        .case "65816"
            jsr long_address
            .break
        .case "45GS02"
        .case "65CE02"
        .case "m65"
            jmp long_address
            .break
        .default
            jmp long_address
    }
afterswitch nop

In the above example the .break directives cause assembly to resume to the afterswitch label. If ommitted assembly would fall through to the next case.

Loop Assembly

Repeat

Assembly repetitions are possible using the .repeat directive, where a block of code will be assembled a specified number of times.

ldx #$00
    .repeat 3
    {
        inx
    }
    rts
    /* will assemble as:
    ldx #$00
    inx
    inx
    inx
    rts
    */

Do and While

The .do and .while directives allow repeat assembly conditionally.

        num := 0
        .while num < 256
       {
            ld b,num
            ld (hl),b
            inc hl
            num += 1
       }

.do and .while are nearly identical, except that for .do the condition occurs after the first iteration of the block assembly, so the code block process at least once.

        * = 0
        .do
        {
            nop
        } .while * < 0 // emits a nop

        .while * < 1
        {
            nop
        }       // no code generated

If using --legacy-blocks option, the .do expects a .whiletrue condition expression.

For and Foreach

For loops are a common feature in higher level languages. 6502.Net provides two variants. The .for directive is C-like, where a variable is initialized or defined, a condition is tested, and then one or more one or more iterations are evaluated.

        .for i = 0, i < 5, i += 1
        {
            nop
        } // five nops

The initial variable assignment and condition are optional. An alternative way to perform the above is:

        .let i = 0
        .for ,,i += 1
        {
            .if i == 5
            {
                .break
            }
            nop
        }

The .foreach takes a more modern approach to the .for loop, where loop assembly occurs during the iteration of a string or collection.

high_scores = [10000,5000,3000,2000,1000]
    .foreach score, high_scores
    {
        .long score
    }

// The iteration in the dictionary is a key/value pair
prizes = {.cherry: 100, .strawberry: 200, .peach: 300}
    .foreach prize, prizes
    {
        .string prize.key
        .long prize.value
    }

Any variables declared in the .for and .foreach directives are local in scope to the directive block unless previously declared.

Break And Continue

The .break and .continue directive can appear within the code block of any loop directive. As expected, .continue will return processing to the beginning of the block while .break takes the assembler out of the loop altogether.

        .for addr=0x100,,addr += 1
        {
            .if addr >= 0x200
            {
                .break // no more output at page change
            }
            .byte $11
        }
        .for i = 0, i < 5, i += 1
        {
            .if i % 2 == 0
            {
                .continue   // do not process on even counts
            }
            .byte i
        }

Loop Assembly and Labels

Because labels might change address values between passes, and 6502.Net is a multi-pass assembler, this can cause an issue if a label is declared inside a loop assembly block.

        .while * < 5
{
start   nop
}

The above code is technically "legal", but what happens is start defined at each loop, which is not valid.

The solution is to declare all labels before the block.

start
        .while * < 5
{
        nop
}

Alternatively, you can use an anonymous label if there is a a need to branch to a specific instruction inside the loop.

        .repeat 5
        {
            ldx #0
-           stx *+$100
            bne -
        }

Other Topics