MDS macro-assembler for PicoBlaze

MDS assembler for PicoBlaze is a two stage macro-assembler inspired by Intel assemblers and the C language. It supports wide range of output data formats, and when you master its advanced features like macro processing, conditions, loops, etc. it will give you means to write code with run-time efficiency typical for an assembly language while giving you some of the comfort of a more high-level language like C.

This page serves only as a brief introduction to capabilities and syntax of MDS assembler, for detailed reference please see the MDS User Manual. All examples shown bellow are tested and work in MDS as expected, these code examples are also part of the MDS Tutorial Project available in [Main Menu] -> [Help].

 


Main features

 

User defined macros

When you use the same portion of code repeatedly, you might consider defining it as a macro and use that macro instead. It will make your code shorter and thus easier to understand and maintain. Also it's generally a good practice to avoid "Copy & Paste" programming. In certain cases you might need to do some adjustments to the piece of code that you copy and paste over and over, in that case, using macros with parameters will delegate some of this work on the assembler and help you make your code less error prone. MDS macro-assembler is made to make usage of macros transparent and relatively easy to debug, macro expansions (including nested ones) are well visible in the code listing and are visible to the MDS processor simulator. So MDS assembler macros shouldn't be as "confusing and obscure" as macros in the C language for instance.

; Wait for the specified number of instruction cycles.
wait    macro    time
        nop
        load     S0, # ( time - 2 ) / 2
        djnz     S0, $ - 1
endm

wait     100    ; Wait 100 instruction cycles here.
; ...
wait     50     ; And 50 cycles here.

 

Case insensitiveness

In many programming languages it's considered a poor programming practice to use the same name which differs only by letter casing for variables, constants, functions, etc. Especially in assembly language which lacks high-level approach of C++, Java, and such languages, this may result in mysterious program behavior and bugs which are hard to find. For that reason, MDS assembler is case insensitive, this may need some getting used to but we believe it's worthed. Most assemblers are case insensitive so it shouldn't be too shocking... :-)

main:   load        s0, s1          ; Load S0 with content of S1.
        Load        s0, S1          ; The same as above.
        LOAD        S0, S1          ; Still the same thing.

Main:   ; <-- ERROR: label `main' has already been defined.

 

Numeral system radixes

Many times you might need to specify numeric constants in various numeral systems, for that reason we made MDS assembler to be able to handle the most common radixes: hexadecimal, decimal, octal, and binary; this assembler can also handle ASCII characters as numeric constants and supports C language escape sequences like '\n' or '\r'.

; Hexadecimal.
load        S0, #0xAB       ; prefix notation (`0x...')
load        S0, #0ABh       ; suffix notation (`...h')

; Decimal.
load        S0, #123        ; no prefix - default radix - decimal
load        S0, #123d       ; suffix notation (`d')

; Octal.
load        S0, #076        ; prefix notation (`0...')
load        S0, #76q        ; suffix notation (`...q')
load        S0, #76o        ; suffix notation (`...o')

; Binary.
load        S0, #0b1100     ; prefix notation (`0b...')
load        S0, #1100b      ; suffix notation (`...b')

; ASCII.
load        S0, #'A'        ; character capital A
load        S0, #'\t'       ; tab character

 

Explicit addressing mode specification

The `#' character instructs assembler to use immediate addressing instead of direct addressing, and the `@' character instructs assembler to use indirect addressing instead of direct. This enables for use of math expressions in place of instruction operands like show in the example below.

load        S0, S5          ; Copy contents of reg. S5 to reg. S0.
load        S0, #0x5        ; Copy immediate value 0x5 (hexadecimal number) to reg. S0
load        S0, 0x5         ; This is interesting: copy contents of reg. S5 to reg. S0, why? see next lines.

load        S0, # ( 1 + 2 ) ; S0 <- value 3
load        S0, ( 6 - 3 )   ; S0 <- register at address 3 (i.e. S3)

store       S0, @S1         ; Store value of register S0 in SPR at address pointed by register S1.
store       S0, @ ( 3 - 2)  ; The same as above.
store       S0, 0x01        ; Store value of register S0 in SPR at address 0x01.
store       S0, ( 3 - 2)    ; Store value of register S0 in SPR at address 0x01.

 

Math expressions

In this assembler you can use some basic math expressions in place of any numerical value; expression operators and their priorities are taken from the C language with some extensions.

MY_CONST    set         0x55                    ; Set a user defined constant named MY_CONST with value 0x55.
            load        S2, #MY_CONST           ; S2 <- value 0x55
            load        S2, # ( MY_CONST + 1 )  ; S2 <- value ( 0x55 + 1 )

MY_CONST2   set         MY_CONST + 1            ; Set a user defined constant named MY_CONST2 with value 0x55 + 1.
            load        S2, #MY_CONST           ; S2 <- value 0x56
            load        S2, # ( MY_CONST + 1 )  ; S2 <- value ( 0x56 + 1 )

MY_EXPR     define      ( {0} + {1} )
            load        S2, #MY_EXPR(MY_CONST, MY_CONST2)
            ; Explanation: with `define' we created an user defined expression with two parameters 0 and 1, any time
            ; later you can use this expression with any two arguments you want, and the assembler will compute the
            ; actual value of this particular instance of this expression and use it where you want, in this case as an
            ; operand.

 

User defined symbols

When you use the same constant multiple times in your code, like a port address for example, it might be beneficial for you to define such constant as symbol. Benefits from such approach include that when you change value of this symbol (on the line where it's defined) it gets automatically changed everywhere where you use it, also MDS assembler distinguishes several different types of symbols and can check whether the symbol is used properly. For instance symbol defined as port address cannot be used as address to program memory for jump or subroutine call because such operation wouldn't make sense. The aim of symbol type sensitiveness is to improve code quality.

my_number           EQU     0x55    ; Generic constant.
my_register         REG     0x2     ; Register address.
my_spr_addr         DATA    0x20    ; Scratch-pad RAM address.
my_prog_addr        CODE    0x3ff   ; Program memory address.
my_io_port          PORT    0x10    ; I/O port address.
my_input_port       PORTIN  0x10    ; Input port address.
my_output_port      PORTOUT 0x10    ; Output port address.

load        my_register, #my_number
load        my_register + 1, #my_number - 2         ; <-- Example of expression in operand.

store       my_register, my_spr_addr
store       my_register + 1, my_spr_addr + 1        ; <-- Another expression in operand.

output      my_register, my_output_port
output      my_register, my_output_port

#if my_number != 0
    jump    my_prog_addr
#endif

 

Conditional compilation

You can instruct the assembler to process some portion of your code only if certain criteria are met.

A           equ         4       ; User defined constant named `A' with value 4.
B           equ         A       ; User defined constant named `B' with value of constant `A' which is 4.

#if ( A > 5 )
        ; Assemble this block only if the user defined constant `A' is greater than 5.
#elseif ( A == B )
        ; Assemble this block only if the user defined constants `A' and `B' are equal.
#elseif ( A != B )
        ; Assemble only if `A' and `B' are different.
#else
        ; Assemble only if none of the preceding conditions was met.
#endif

 

Compile-time loops

Assembler can repeat specified portion of code for the given number of times (directive REPEAT) or while some condition is met (directive WHILE).

repeat  5       ; Repeat 5 times (copy) the code between `repeat' and `endr'.
    out         S0, @S1
    inc         S1
endr

 

Run-time loops

Instead of writing loops with loads, compares, and jumps, you might find it to be more straightforward to use MDS assembler to generate them for you. You can use three types of FOR loop and one type of WHILE loop.

for         S1, 0 .. 4      ; C: for ( S1 = 0; S1 <= 4; S1++ ) {
    out     S0, @S1         ; ...
endf                        ; C: }

 

Run-time conditions

Instead of writing conditional branching using compares and jumps, you can let the assembler do at least some this work for you with if, elseif, else, and endif directives. This feature resembles C language but don't forget that you are still working with assembler, these branching directives are merely a "syntax sugar", they are translated as compare and conditional jump, nothing more.

if S0 == S1
    ; Execute this block if the content of registers S0 and S1 are equal.
elseif S0 > #0x1b
    ; Execute this block if the content of register S0 is greater than 0x1B.
else
    ; Execute this block if none of the previous conditions was met.
endif

 

Shorthands & pseudo-instructions

In this assembler you may use the same instruction mnemonics as you are used to from the Xilinx assembler, or you may use their shortcuts. Shorthand is merely a way to save a few letters, some people prefer short instruction mnemonics over long ones, and some people don't. If you prefer the short ones, you can write for instance DIS instead of DISABLE INTERRUPT, or LDRET instead of LOAD & RETURN

Pseudo-instructions are way do some simple operation which can be easily accomplished by a clever use of for instance ADD instruction but you want it to be more readable for some people, like INC S0 for increment instead of ADD S0, #1.

            ld          S1, S0      ; S1 <- S0 (copy contents of S0 to S1)

            nop                     ; No operation, the simplest pseudo-instruction.
            cpl         S1          ; Complement (XOR with 0xFF).
            cpl2        S1          ; 2nd arithmetic complement (+1 and XOR 0xFF)
            inc         S1          ; Increment (add 1).
            dec         S1          ; Decrement (subtract 1).
            setr        S1          ; Set register (load with 0xFF).
            clrr        S1          ; Clear register (load with 0x00).
            setb        S1, 2       ; Set bit number 2 in register S1.
            clrb        S1, 2       ; Clear bit number 2 in register S1.
            notb        S1, 2       ; Invert bit number 2 in register S1.
label:      djnz        S1, label   ; Decrement and jump if not zero.
            ijnz        S1, label   ; Increment and jump if not zero.

 

Strings 

MDS assembler handles strings in similar fashion as the Xilinx assembler.

            device          kcpsm6

my_str      string          "Hello PicoBlaze!\r\n"      ; Define a string for later use.
my_port     portout         3                           ; Define a port address for later use.

            org             0                           ; Start from address 0x00.

            ; Send my_str string to my_port port.
            outputk         my_str, my_port             ; my_port <== "Hello PicoBlaze!\r\n"
            outputk         "\007\377\x26\t\n", my_port ; my_port <== "\007\377\x26\t\n"

abc:        load & return   S0, my_str
ghi:        load & return   S0, "Another string with binary values: \x87\x26\x00"

            ; Read from the string at abc: using indirect subroutine call.
            load            S1, #high(abc)      ; high(0x2211) == 0x22
            load            S0, #low(abc)       ;  low(0x2211) == 0x11
            call            @(S1, S0)

 

Code listing

MDS assembler generates code listing file (normally a file with .lst extension) in which you can see and inspect how exactly your code was compiled by the assembler and why. Code listing provides detailed insight into:

  • macro expansion (including nested macros),
  • file inclusion (directive include,
  • how exactly assembler evaluated your math expressions,
  • what are the addresses of labels,
  • how instructions were translated (OP Codes),
  • when you use conditional compilation, you see which portions of code were compiled and which were not,
  • what exactly did assembler do with directives like if, for, while, etc.,
  • ... and many other things.
                  26     ; Define macro named `backup' containing three loads: S10 <- S0, S11 <- S1, and S12 <- S2.
                  27     backup      macro
                  28                 load        S3, S0
                  29                 load        S4, S1
                  30                 load        S5, S2
                  31     endm
                  32
000 01001         33                 load        S0, #1         ; Load S0 with some value.
001 01102         34                 load        S1, #2         ; Load S1 with some value.
002 01203         35                 load        S2, #3         ; Load S2 with some value.
                  36
                  37                 backup                     ; Backup current content of S0..S2.
003 00300         38 +1              load        S3, S0
004 00410         39 +1              load        S4, S1
005 00520         40 +1              load        S5, S2

 


Examples

 

The first steps

            ; Start at address 0x000
            org         0x000
            jump        start

            ; Initialize some registers with some values.
start:      load        S0, #11         ; Load register S0 with value 11 decimal.
            load        S1, #0x0B       ; Load register S1 with value B hexadecimal (11 dec.).
            load        S2, #013        ; Load register S2 with value 13 octal (11 dec.).
            load        S3, #0b1011     ; Load register S3 with value 1001 binary (11 dec.).
            load        S4, #'a'        ; Load register S4 with ASCII value of character `a'.

            ; Something just to catch your eye... we will talk about this in next examples.
            for         S5, 0 .. 32, 2  ; C: for ( S5 = 0; S5 <= 32; S5 += 2 ) {
                output  S4, @S5
            endf                        ; C: }

            ; An infinite loop.
loop:       store       S0, @S1         ; Store value of register S0 in SPR at address pointed by register S1.
            output      S2, @S3         ; Set value of register S2 on output port at address pointed by register S3.
            xor         S0, #0xff       ; Complement register S0.
            load        S2, S0          ; Load register S2 with value of register S0.
            add         S1, #0x01       ; Increment register S1.
            sub         S3, #0x01       ; Decrement register S3.
            jump        loop            ; Jump to label `loop'.

            ; Assembler will stop here, everything after the `END' directive is ignored.
            end

 

Instructions, operands, and expressions

This example covers these topics:

  • What does `#' and `@' mean in instruction operands, and what is it good for.
  • How to define your own constants and expressions and use them.
  • How to use `$' symbol in the code.
            ; Start at address 0x000.
            org     0x000

; ======================================================================================================================
; Operands and types of addressing, what `#' and `@' means.
; ======================================================================================================================

            ; Lets' see some examples first:
            load        S0, S5          ; Copy contents of reg. S5 to reg. S0.
            load        S0, #0x5        ; Copy immediate value 0x5 (hexadecimal number) to reg. S0
            load        S0, 0x5         ; This is interesting: copy contents of reg. S5 to reg. S0, why? see next lines.

            load        S0, # ( 1 + 2 ) ; S0 <- value 3
            load        S0, ( 6 - 3 )   ; S0 <- register at address 3 (i.e. S3)
            ; Explanation: the `#' character instructs assembler to use immediate addressing instead of direct
            ; addressing, this is very important when you want to use expressions, and macros as instruction operand.
            ; Later in this example project you will see the potential of such capability.

            store       S0, @S1         ; Store value of register S0 in SPR at address pointed by register S1.
            store       S0, @ ( 3 - 2)  ; The same as above.
            store       S0, 0x01        ; Store value of register S0 in SPR at address 0x01.
            store       S0, ( 3 - 2)    ; Store value of register S0 in SPR at address 0x01.
            ; Explanation: the `@' character instructs assembler to use indirect addressing instead of direct.

; ======================================================================================================================


; ======================================================================================================================
; Lets take a look at constants and expressions.
; ======================================================================================================================
MY_CONST    set         0x55                    ; Set a user defined constant named MY_CONST with value 0x55.
            load        S2, #MY_CONST           ; S2 <- value 0x55
            load        S2, # ( MY_CONST + 1 )  ; S2 <- value ( 0x55 + 1 )

MY_CONST2   set         MY_CONST + 1            ; Set a user defined constant named MY_CONST2 with value 0x55 + 1.
            load        S2, #MY_CONST           ; S2 <- value 0x56
            load        S2, # ( MY_CONST + 1 )  ; S2 <- value ( 0x56 + 1 )

MY_EXPR     define      ( {0} + {1} )
            load        S2, #MY_EXPR(MY_CONST, MY_CONST2)
            ; Explanation: with `define' we created an user defined expression with two parameters 0 and 1, any time
            ; later you can use this expression with any two arguments you want, and the assembler will compute the
            ; actual value of this particular instance of this expression and use it where you want, in this case as an
            ; operand.
; ======================================================================================================================


; ======================================================================================================================
; More advanced usage of constants and expressions.
; ======================================================================================================================
; Lets make some definitions.
START       set         0x03                    ; START   := 0x03
MY_ORIG     define      START + {0}             ; MY_ORIG := ( START + <argument #0> )

; As `set' defines an user constant `reg' defines an user named register, they both operate the same way.
MY_REG      reg         MY_ORIG(S1)             ; MY_REG  := S1 + START  (i.e. 0x03 + S1 = 4)
MY_REG2     reg         MY_ORIG(MY_REG + 2)     ; MY_REG2 := MY_REG + 2  (i.e. 0x03 + S1 + 2 = 6)

; And lets use them:
            load        MY_REG, MY_REG2         ; If you understand what this does, you understand the concept of
                                                ; constants and expressions applied in the MDS built-in assembler.
                                                ; An if you don't, please study this example more deeply, or consult the
                                                ; user manual, and in the notes bellow is the answer.
; ======================================================================================================================


; That's all for now, and lets stuck in an infinite loop. :-) ... Please see the next example.
            jump        $

; Assembler will stop here, everything after the `END' directive is ignored.
            end

Notes:

  1. Directive `set' creates a redefinable user constant which you can use in place of any number in your code, the same operates also directive equ, these two directive can be used for most time interchangeably, the only difference it that constants defined with equ directive cannot be redefined later in the code.
  2. Directive `define' can actually use as many parameters as you want, not just two. Parameters are numbered 0, 1, 2, 3, ... and inside the expression definition they have to be enclosed with curly brackets like: {0}, {1}, {2}, {3}, ... You can even use expression within another expression, there is no limit in depth.
  3. load MY_REG, MY_REG2 does: S4 <- S6. It loads register at address MY_REG (0x4 i.e S4) with value of register at address MY_REG2 (0x6 i.e. S6).
  4. On any line in the code where an instruction is present, there is also automatically defined a special constant named $ (dollar), this constant hold the address of the instruction on that line. That's why you can make an infinite loop like this: jump $ or for instance jump over the next two instructions just by doing this: jump $+2.
  5. Mathematical expressions like 1 + 2 may be enclosed in parenthesis but it is not mandatory, in some cases it may improve readability of the code. Operand precedence is the same as in C language, please consult the user manual on details.

 

Shorthands and numeral system radix

            ; Start at address 0x000.
            org         0x000


; ======================================================================================================================
; Radix, and several ways how to write a number.
; ======================================================================================================================

            ; Hexadecimal.
            ld          S0, #0xAB       ; prefix notation (`0x...')
            ld          S0, #0ABh       ; suffix notation (`...h')
            ; Decimal.
            ld          S0, #123        ; no prefix - default radix - decimal
            ld          S0, #123d       ; suffix notation (`d')
            ; Octal.
            ld          S0, #076        ; prefix notation (`0...')
            ld          S0, #76q        ; suffix notation (`...q')
            ld          S0, #76o        ; suffix notation (`...o')
            ; Binary.
            ld          S0, #0b1100     ; prefix notation (`0b...')
            ld          S0, #1100b      ; suffix notation (`...b')
            ; ASCII.
            ld          S0, #'A'        ; character capital A
            ld          S0, #'\t'       ; tab character


; ======================================================================================================================
; Instruction shortcuts.
; When you don't want to write long instruction mnemonics like "enable interrupt" and use short ones instead like "ena".
; ======================================================================================================================

            load        S1, S0          ; S1 <- S0 (copy contents of S0 to S1)
            ld          S1, S0          ; Do the same as above, `ld' is shortcut for 'load'.

; The same way you can write `ld' instead of `load', you can also use:
;   * `st'      instead of `store'
;   * `ft'      instead of `fetch'
;   * `rb'      instead of `regbank'
;   * `in'      instead of `input'
;   * `out'     instead of `output'
;   * `outk'    instead of `outpuk'
;   * `cmp'     instead of `compare'
;   * `cmpcy'   instead of `comparecy'
;   * `ena'     instead of `enable interrupt'
;   * `dis'     instead of `disable interrupt'
;   * `ret'     instead of `return'
;   * `ldret'   instead of `load & return'
;   * `retie'   instead of `return enable'
;   * `retid'   instead of `return disable'


; ======================================================================================================================
; Pseudo-instructions.
; ======================================================================================================================

            nop                     ; No operation, the simplest pseudo-instruction.
            cpl         S1          ; Complement (XOR with 0xFF).
            cpl2        S1          ; 2nd arithmetic complement (+1 and XOR 0xFF)
            inc         S1          ; Increment (add 1).
            dec         S1          ; Decrement (subtract 1).
            setr        S1          ; Set register (load with 0xFF).
            clrr        S1          ; Clear register (load with 0x00).
            setb        S1, 2       ; Set bit number 2 in register S1.
            clrb        S1, 2       ; Clear bit number 2 in register S1.
            notb        S1, 2       ; Invert bit number 2 in register S1.
label:      djnz        S1, label   ; Decrement and jump if not zero.
            ijnz        S1, label   ; Increment and jump if not zero.

; ======================================================================================================================


            ; Infinite loop, and end of assembly.
            jump        $
            end

 

Conditions

            ; Start at address 0x000.
            org         0x000

; ======================================================================================================================
; Compilation time conditions.
; ======================================================================================================================

A           equ         4       ; User defined constant named `A' with value 4.
B           equ         A       ; User defined constant named `B' with value of constant `A' which is 4.

; Assemble the following block only if the user defined constant `A' is greater than 5.
#if ( A > 5 )
            ; Print a message to assembler output.
            message     "A is greater than 5"
            ld          S0, #A
; Assemble the block only if the user defined constants `A' and `B' are equal.
#elseif ( A == B )
            ; Print assembler warning.
            warning     "A is equal to B"
            ld          S0, #B
; Assemble only if `A' and `B' are different.
#elseif ( A != B )
            ; Print assembler warning.
            warning     "A is not equal to B"
            ld          S0, # A - B
; Assemble only if none of the preceding conditions was met, obviously this block of code will never get assembled.
#else
    ; Only if constant `C' is defined which isn't.
    #ifdef C
            ; End assembly with an error message.
            error       "C is defined, and that's not good."
    ; Else...
    #else
            message     "C is not defined so continue normally."
            ld          S0, #1
    #endif
#endif


; ======================================================================================================================
; Run time conditions.
; ======================================================================================================================

            ; Load some registers with some values.
            ld          S0, #0x1c
            ld          S1, #0x1b
            ld          S2, #0x10

    ; Execute the following block only if the current content of registers S0 and S1 are equal.
    if S0 == S1
            inc         S2      ; Increment S2.
    ; Execute the block only if the current content of register S0 is greater than 0x1b (note the immediate addressing).
    elseif S0 > #0x1b
        ; If S1 contains ASCII value of character small `a'.
        if S1 == #'a'
            ld          S2, #'A'; Load S2 with ASCII value of `A'.
        ; If S1 contains small `b'.
        elseif S1 == #'b'
            ld          S2, #'B'; Load S2 with ASCII value of `B'.
        ; And small `c'.
        elseif S1 == #'c'
            dec         S2      ; Decrement S2.
        ; And else...
        else
            clrr        S2      ; Clear S2 (put zero in it).
        ; `endif' is mandatory to close the tree of blocks.
        endif
    ; And else...
    else
            cpl         S2      ; Complement S2 (XOR it with 0xff).
    ; And `endif' is still mandatory.
    endif

; ======================================================================================================================


            ; Infinite loop, and end of assembly.
            jump        $
            end

Notes:

  1. This assembler is case insensitive (i.e. load s0, s1 is exactly the same as LOAD S0, S1 or Load s0, S1).
  2. In most cases the assembler doesn't care about white space (e.g. if(a==(b-4)) is the same as if ( a == ( b - 4 ) ).

 

Loops

            ; Start at address 0x000.
            org         0x000

; ======================================================================================================================
; A task.
; ======================================================================================================================

            ; Lets make an example, suppose we want for some reason to output the content of S0 register to ports in
            ; address range [0, 4]. Of course, there is a countless number of ways how to do it but lets see some the
            ; simple ones.

            ; 1) No loop at all.
            load        S0, #0x11       ; (value to output)
            load        S1, #0          ; (starting address)

            output      S0, @S1
            add         S1, #1
            output      S0, @S1
            add         S1, #1
            output      S0, @S1
            add         S1, #1
            output      S0, @S1
            add         S1, #1
            output      S0, @S1
            add         S1, #1

; ----------------------------------------------------------------------------------------------------------------------

            ; 2) Still the same approach but with a little less slavish writing.
            ld          S0, #0x22       ; (value to output)
            ld          S1, #0          ; (starting address)

            out         S0, @S1         ; (output)
            inc         S1              ; (increment)
            out         S0, @S1
            inc         S1
            out         S0, @S1
            inc         S1
            out         S0, @S1
            inc         S1
            out         S0, @S1
            inc         S1

; ======================================================================================================================
; Using a compilation time loop (i.e. repeat code automatically for the given fixed number of times).
; ======================================================================================================================

            ; 3) REPEAT & ENDR : This time let the assembler put the code down repeatedly for you.
            ld          S0, #0x33       ; (value to output)
            ld          S1, #0          ; (starting address)

        repeat  5                       ; 5 times repeat (copy) the code between `repeat' and `endr'.
            out         S0, @S1
            inc         S1
        endr

; ----------------------------------------------------------------------------------------------------------------------

            ; 4) #WHILE & #ENDW : Similar to point 3 but using a different kind of compilation time loop.
            ld          S0, #0x44       ; (value to output)
addr        set         0               ; (starting address)

        #while ( addr < 5 )             ; Repeat while `addr' is lower than 5.
            out         S0, addr
addr        set         addr + 1        ; Redefine user constant `addr' like this: "addr := addr + 1".
        #endw                           ; End the while loop.

; ======================================================================================================================
; Using loops, both hand written and assembler generated run time loops.
; ======================================================================================================================

            ; 5) A loop, hand written (one of possible ways to write it, not necessarily the best one).
            ld          S0, #0x55       ; (value to output)
            ld          S1, #0          ; (starting address)

loop_0:     out         S0, @S1
            inc         S1
            cmp         S1, #5
            jump        NZ, loop_0

; ----------------------------------------------------------------------------------------------------------------------

            ; 6) Another loop, also hand written.
            ld          S0, #0x66       ; (value to output)
            ld          S1, #0          ; (starting address)
            ld          S2, #5          ; (number of repeats)

loop_1:     out         S0, @S1
            inc         S1
            djnz        S2, loop_1      ; Decrement (S2) and jump (at loop_1) if not zero.

; ----------------------------------------------------------------------------------------------------------------------

            ; 7) The for loop - an assembler generated run time loop.
            ld          S0, #0x77       ; (value to output)

            ; S1 serves as the iterator, and 5 is the number of iterations.
            for         S1, 5           ; C: for ( S1 = 0; S1 < 5; S1++ ) {
                out     S0, @S1
            endf                        ; C: }

; ----------------------------------------------------------------------------------------------------------------------

            ; 8) The for loop again.
            ld          S0, #0x88       ; (value to output)

            ; S1 again as the iterator, 0 is the starting value, and 4 is the maximum value.
            for         S1, 0 .. 4      ; C: for ( S1 = 0; S1 <= 4; S1++ ) {
                out     S0, @S1
            endf                        ; C: }

; ----------------------------------------------------------------------------------------------------------------------

            ; 9) Yet another for loop.
            ld          S0, #0x99       ; (value to output)

            ; S1 again as the iterator, 0 is the starting value, 4 is the maximum value, and 1 is the increment.
            for         S1, 0..4, 1     ; C: for ( S1 = 0; S1 <= 4; S1 += 1 ) {
                out     S0, @S1
            endf                        ; C: }

; ----------------------------------------------------------------------------------------------------------------------

            ; 10) While loop.
            ld          S0, #0xAA       ; (value to output)
            ld          S1, #0          ; (starting address)

            while       S1 < #5         ; C: while ( S1 < 5 ) {
                out     S0, @S1         ; C:     S0 = *S1;
                inc     S1              ; C:     S1++;
            endw                        ; C: }

; ======================================================================================================================


            ; Infinite loop, and end of assembly.
            jump        $
            end

; ----------------------------------------------------------------------------------------------------------------------
; Notes:
;   1) you may also use nested loops, any loop may be present in any other loop with no limit in depth of nesting,
;      for instance you can write something like this:
;
            for         S0, 10..100, 5
                for     S1, 1..5
                    for S2, 10
                        nop
                    endf; S2, 10
                endf    ; S1, 1..5
            endf        ; S0, 10..100, 5

 

Macros

This example code demonstrates usage of user defined macros. Examples used here serve only as demonstration, they are not meant to have any usage in real life application but they certainly should give you some idea on how to use macros in this assembly language.

IMPORTANT NOTE: This is a pretty advanced topic, even entire books have been written about using macros and similar techniques applied in the assembly language; this example code only illustrates how that might look like, and demonstrates that macros really work here but if you are unsure how exactly that works, it might be advisable to avoid using it at all, at least before you get to know all the pitfalls and limitations of this. On the other hand when you master this technique, you will probably get much more efficient in writing assembly code, it's a powerful feature, one just needs to know how exactly to use it properly.

            ; Start at address 0x000.
            org         0x000

; ======================================================================================================================
; A simple example: macro without parameters.
; ======================================================================================================================

; Define macro named `backup' containing three loads: S10 <- S0, S11 <- S1, and S12 <- S2.
backup      macro
            ld          S3, S0
            ld          S4, S1
            ld          S5, S2
endm

; Define macro named `restore' to do the exact opposite of what the `backup' does.
restore     macro
            ld          S0, S3
            ld          S1, S4
            ld          S2, S5
endm

            ; Now lets load S0..S2 with some values.
            ld          S0, #1
            ld          S1, #2
            ld          S2, #3
            ; Backup current content of S0..S2.
            backup

            ; And load S0..S2 with another values.
            ld          S0, #10
            ld          S1, #20
            ld          S2, #30
            ; Restore the previous content of S0..S2.
            restore

; ----------------------------------------------------------------------------------------------------------------------

; Wait 100 instruction cycles where you want.
w100        macro
            nop
            ld          S0, #49
            djnz        S0, $-1
endm

            ; Wait 100 instruction cycles here.
            w100

; ======================================================================================================================
; A little more advanced example: macro with parameters.
; ======================================================================================================================

; Double load, loads two different registers with two different values at once.
dload       macro       reg0, reg1, val0, val1
            ld          reg0, #val0
            ld          reg1, #val1
endm

            ; Lets give it a try...
            dload       S0, S1, 0x11, 0x22

; ----------------------------------------------------------------------------------------------------------------------

; Copy content of registers at addresses [source, source+4] to registers at addresses [target, target+4].
copy5       macro       target, source
            ld          target + 0, source + 0
            ld          target + 1, source + 1
            ld          target + 2, source + 2
            ld          target + 3, source + 3
            ld          target + 4, source + 4
endm

; Put the given number of NOPs (No Operation instruction) at the place where this macro is expanded.
nops        macro       number
            repeat      number
                nop
            endr
endm

            ; Put 10 NOPs here.
            nops        10

; Wait for the given number number of instruction cycles, and use the given register as iterator for the delay loop.
wait        macro       register, cycles
            for         register, ( cycles - 1 ) / 4
                nop
            endf
endm

; ======================================================================================================================
; A pretty complex example: macro with local symbol(s), and forced exit combined with conditions and loops.
; ======================================================================================================================

; Fill registers at addresses in range [target, target+length] with the given value.
fill        macro       target, value, length
            local       n
    n       set         0

        #if ( length == 0 )
            warning     "Zero length doesn't make sense here."
        #elseif ( target == S1 )
            warning     "I won't accept S1 as target ... for some reason, I guess."
            exitm
        #endif

        #while n < length
            ld          target + n, #value
    n       set         n + 1
        #endw
endm

; Global label named simply `label'.
label:      jump        $

; Copy contents of registers addresses in range [source, source+number] to registers at [target, target+number].
copy        macro       target, source, number

            local       n
    n       set         0

    ; Local label with same name a global label declared before.
            local       label
            jump        label
    label:

        #while n < number
            ld          target + n, source + n
    n       set         n + 1
        #endw
endm

; Lets have some fun here...
loop:       fill        S0, 0x55, 8    ; S0..S7 <- #0x55
            copy        S8, S0, 8      ; S8..SA <- S0..S7

            fill        S0, 0xAA, 8    ; S0..S7 <- #0xAA
            copy        S8, S0, 8      ; S8..SA <- S0..S7

            jump        loop            ; And again...


; ======================================================================================================================

            ; End of assembly.
            end