Files
2026-02-07 18:21:37 +00:00

33 KiB

DSA Assembly Language Reference

Overview

This document is the comprehensive reference for writing DSA assembly programs. It covers assembly syntax, pseudo-instructions, directives, the module system, calling conventions, and provides complete examples.

Related Documents:

  • For hardware instruction details and encoding: See DSA ISA Specification
  • For build system and toolchain: See project documentation

Assembly Syntax

General Rules

  • Case Insensitive: Mnemonics can be uppercase or lowercase (mov = MOV)
  • Comments: Use // for line comments or /* */ for block comments
  • Labels: Identifier followed by colon (e.g., main:, loop:)
  • Whitespace: Flexible spacing between operands
  • Numbers:
    • Decimal: 100, 255
    • Hexadecimal: 0x10, 0xFFFF
    • Binary: 0b1010 (if supported by assembler)

Operand Order Convention

DSA assembly uses GAS-style syntax (source → destination):

mov rg0, rg1        ; Copy rg0 TO rg1 (destination is last)
add rg0, rg1, rg2   ; rg2 = rg0 + rg1 (destination is last)

For load/store with immediates:

lli 0x1234, rg0     ; Load immediate 0x1234 INTO rg0
ldw rg0, rg1, 8     ; Load from (rg0+8) INTO rg1
stw rg0, rg1, 8     ; Store rg0 TO address (rg1+8)

Registers

Register(s) Type Description Usage Notes
rg0-rgf General 16 general-purpose registers Use for variables, temporaries
acc Special Accumulator ⚠️ VOLATILE - Used as scratch by pseudo-instructions
spr Special Stack pointer Points to current top of stack
bpr Special Base pointer Used for stack frames
ret Special Return address Holds function return addresses
zero Read-only Always zero Reads return 0, writes discarded
pcx Read-only Program counter Cannot be written directly
idr Privileged Interrupt descriptor table Kernel mode only
mmr Privileged Memory map register Kernel mode only
noreg Placeholder No register Used in encoding, triggers fault if accessed

Alternative Names:

  • noreg can also be referenced as null in assembly

Register Conventions:

  • acc: Volatile scratch register used by pseudo-instructions - never preserved across pseudo-ops
  • rg0-rge: Available for general use; calling convention defines preservation rules
  • rgf: General purpose register, available for use

⚠️ CRITICAL: The acc register is used internally by label-based memory operations and other pseudo-instructions. Do not assume its value is preserved across any pseudo-instruction!

Hardware Instructions

This section shows assembly syntax. For encoding details, see the ISA Specification.

Data Movement

mov src_reg, dest_reg       ; Copy value from src_reg to dest_reg
movs src_reg, dest_reg      ; Copy with sign extension

Examples:

mov rg0, rg1                ; rg1 = rg0
movs acc, rg2               ; rg2 = sign_extend(acc)

Memory Load Instructions

ldb base_reg, dest_reg [, offset]      ; Load byte (zero-extend)
ldbs base_reg, dest_reg [, offset]     ; Load byte (sign-extend)
ldh base_reg, dest_reg [, offset]      ; Load halfword (zero-extend)
ldhs base_reg, dest_reg [, offset]     ; Load halfword (sign-extend)
ldw base_reg, dest_reg [, offset]      ; Load word

Offset: Optional signed 16-bit offset (defaults to 0)

Examples:

ldb rg0, rg1                ; Load byte from address in rg0
ldw rg0, rg1, 8             ; Load word from (rg0 + 8)
ldhs rg2, rg3, -4           ; Load signed halfword from (rg2 - 4)

Alignment Requirements:

  • ldb/ldbs: No alignment required
  • ldh/ldhs: Must be 2-byte aligned
  • ldw: Must be 4-byte aligned

Memory Store Instructions

stb src_reg, base_reg [, offset]       ; Store byte
sth src_reg, base_reg [, offset]       ; Store halfword
stw src_reg, base_reg [, offset]       ; Store word

Examples:

stb rg0, rg1                ; Store byte to address in rg1
stw rg0, rg1, 12            ; Store word to (rg1 + 12)
sth acc, spr, -2            ; Store halfword to (spr - 2)

Alignment Requirements: Same as loads

Immediate Load Instructions

lli immediate, dest_reg     ; Load lower 16 bits (CLEARS upper 16!)
lui immediate, dest_reg     ; Load upper 16 bits (preserves lower 16)

⚠️ CRITICAL: lli clears the upper 16 bits! Always use lli before lui.

Loading 32-bit Constants:

lli 0x1234, rg0             ; rg0 = 0x00001234
lui 0xABCD, rg0             ; rg0 = 0xABCD1234

Note: The assembler may process the immediate value for lui - specify the upper 16 bits directly (e.g., lui 0xABCD, rg0 to set upper bits to 0xABCD).

Loading Addresses: See lwi pseudo-instruction

Jump and Branch Instructions

jmp offset, base_reg        ; Unconditional jump to (base_reg + offset)
jeq offset, base_reg        ; Jump if equal
jne offset, base_reg        ; Jump if not equal
jgt offset, base_reg        ; Jump if greater than
jge offset, base_reg        ; Jump if greater or equal
jlt offset, base_reg        ; Jump if less than
jle offset, base_reg        ; Jump if less or equal

Target Address Calculation:

target = base_reg + sign_extend(offset)

Jump Modes:

; Absolute jump (using zero register)
jmp label, zero             ; Jump to label address

; Register-based jump  
jmp 0, ret                  ; Jump to address in ret register
jmp 4, ret                  ; Jump to (ret + 4)

; PC-relative (if assembler supports label resolution)
jeq loop_start, zero        ; Jump to loop_start if equal flag set

Conditional Jumps: Based on flags set by cmp instruction

Examples:

jmp start, zero             ; Absolute jump to 'start'
jmp 4, ret                  ; Jump to (ret + 4)
jeq end, zero               ; Jump to 'end' if equal flag set
jgt loop, zero              ; Jump to 'loop' if greater than flag set

Comparison

cmp reg1, reg2              ; Compare reg1 with reg2, set flags

Flags Set:

  • Equal: reg1 == reg2
  • GreaterThan: reg1 > reg2 (signed comparison)
  • LessThan: reg1 < reg2 (signed comparison)
  • GreaterThanOrEqual: reg1 >= reg2
  • LessThanOrEqual: reg1 <= reg2

Example:

cmp rg0, zero               ; Compare rg0 with 0
jeq is_zero, zero           ; Branch if rg0 == 0
jgt is_positive, zero       ; Branch if rg0 > 0
jlt is_negative, zero       ; Branch if rg0 < 0

Arithmetic Instructions

add src1, src2, dest        ; dest = src1 + src2
sub src1, src2, dest        ; dest = src1 - src2
iadd src, immediate, dest   ; dest = src + immediate (DEST REQUIRED!)
isub src, immediate, dest   ; dest = src - immediate (DEST REQUIRED!)
inc reg                     ; reg = reg + 1
dec reg                     ; reg = reg - 1

Examples:

add rg0, rg1, rg2           ; rg2 = rg0 + rg1
sub rg0, rg1, rg2           ; rg2 = rg0 - rg1
iadd rg0, 10, rg0           ; rg0 = rg0 + 10 (in-place)
iadd rg0, 10, rg1           ; rg1 = rg0 + 10 (separate dest)
isub rg1, 5, rg2            ; rg2 = rg1 - 5
inc spr                     ; spr = spr + 1
dec spr                     ; spr = spr - 1

⚠️ Note: For iadd/isub, the destination register is required (not optional). For in-place operations, specify the source register as both source and destination.

Bitwise Logical Operations

and src1, src2, dest        ; dest = src1 & src2
or src1, src2, dest         ; dest = src1 | src2
xor src1, src2, dest        ; dest = src1 ^ src2
not src, dest               ; dest = ~src
nand src1, src2, dest       ; dest = ~(src1 & src2)
nor src1, src2, dest        ; dest = ~(src1 | src2)
xnor src1, src2, dest       ; dest = ~(src1 ^ src2)

Examples:

and rg0, rg1, rg2           ; rg2 = rg0 & rg1
or rg0, rg1, rg2            ; rg2 = rg0 | rg1
not rg0, rg1                ; rg1 = ~rg0
xor rg0, rg0, rg0           ; rg0 = 0 (XOR register with itself)

Shift Operations

shl reg, shift_amount       ; Shift left by amount (0-31)
shr reg, shift_amount       ; Shift right by amount (0-31)

Shift Amount:

  • Literal: shl rg0, 2 - shift by constant 2
  • Register: shl rg0, rg1 - shift by value in rg1 (low 5 bits)

⚠️ Note: Current assembler may only support literal shift amounts. Check your assembler documentation for register shift support.

Examples:

shl rg0, 2                  ; rg0 = rg0 << 2
shr rg1, 3                  ; rg1 = rg1 >> 3
; shl rg0, rg1              ; May not be supported by assembler

Note: Shift right is logical (zero-fill), not arithmetic

System and Control Instructions

hlt                         ; Halt processor
nop                         ; No operation
int interrupt_code          ; Trigger interrupt (8-bit code)
irt                         ; Return from interrupt

Examples:

hlt                         ; Stop execution
nop                         ; Do nothing (timing/alignment)
int 0x21                    ; Trigger interrupt 0x21
irt                         ; Return from interrupt handler

Pseudo-Instructions

Pseudo-instructions are assembly-level constructs that expand into one or more hardware instructions.

⚠️ IMPORTANT: Many pseudo-instructions use the acc register as a scratch register. The value in acc is not preserved across these operations!

Data Definition Directives

db label: value1 [, value2, ...]    ; Define bytes
dh label: value1 [, value2, ...]    ; Define halfwords (16-bit)
dw label: value1 [, value2, ...]    ; Define words (32-bit)

Examples:

db message: "Hello, World!", 0       ; String with null terminator
db bytes: 0x01, 0x02, 0x03          ; Array of bytes
dh numbers: 1000, 2000, 3000        ; Array of halfwords
dw stack_base: 0x10000              ; Single word value
dw table: 0, 0, 0, 0                ; Array of 4 words

String Encoding: Strings are automatically null-terminated and encoded as byte sequences with escape sequences:

  • \n = newline (0x0A)
  • \t = tab (0x09)
  • \r = carriage return (0x0D)
  • \\ = backslash
  • \" = double quote
  • \0 = null (0x00)

⚠️ Endianness Note: Numeric values in data directives are stored in big-endian format by the assembler, unlike the little-endian used for instructions.

Memory Reservation Directives

resb label: size            ; Reserve 'size' bytes
resh label: size            ; Reserve 'size' halfwords
resw label: size            ; Reserve 'size' words

Examples:

resb buffer: 256            ; Reserve 256 bytes
resh array: 100             ; Reserve 100 halfwords (200 bytes)
resw heap: 1024             ; Reserve 1024 words (4096 bytes)

Note: Reserved memory is uninitialized (contents undefined).

Stack Operations

push reg                    ; Push register onto stack
pop reg                     ; Pop stack into register

⚠️ CRITICAL - Stack Direction: The DSA stack grows DOWNWARD (toward lower memory addresses).

Expansion:

; push rg0 expands to:
subi spr, 4, spr            ; spr = spr - 4 (allocate space, stack grows down)
stw rg0, spr, 0             ; Store rg0 to [spr]

; pop rg0 expands to:
ldw spr, rg0, 0             ; Load [spr] into rg0
addi spr, 4, spr            ; spr = spr + 4 (deallocate space)

Examples:

push rg0                    ; Save rg0 on stack
push rg1                    ; Save rg1 on stack
; ... do work ...
pop rg1                     ; Restore rg1
pop rg0                     ; Restore rg0

Stack Diagram:

Higher Memory Addresses
    ↑
    │  (old data)
    ├─────────────┤
    │   rg0 value │  ← After first push
    ├─────────────┤  ← SPR after first push
    │   rg1 value │  ← After second push
    ├─────────────┤  ← SPR after second push (stack grew down)
    ↓
Lower Memory Addresses

Push/Pop Multiple Registers

pusha count                 ; Push first 'count' general registers (rg0-rgN)
popa count                  ; Pop first 'count' general registers

Examples:

pusha 4                     ; Push rg0, rg1, rg2, rg3
; ... do work ...
popa 4                      ; Pop rg3, rg2, rg1, rg0

Expansion:

; pusha 3 expands to:
subi spr, 12, spr           ; Allocate space for 3 words
stw rg0, spr, 0             ; Store rg0
stw rg1, spr, 4             ; Store rg1  
stw rg2, spr, 8             ; Store rg2

; popa 3 expands to:
ldw spr, rg0, 0             ; Load rg0
ldw spr, rg1, 4             ; Load rg1
ldw spr, rg2, 8             ; Load rg2
addi spr, 12, spr           ; Deallocate space

Note: Registers are pushed/popped in order (rg0, rg1, rg2, ...).

Load Address Pseudo-Instruction

lwi label, dest_reg         ; Load address of label into register

Expansion:

; lwi message, rg0 expands to:
lli message, rg0            ; Load lower 16 bits of address
lui message, rg0            ; Load upper 16 bits of address

Example:

db message: "Hello!", 0
    
lwi message, rg0            ; rg0 = address of message
ldb rg0, rg1, 0             ; rg1 = first byte of message ('H')

Memory Access with Labels

Load and store instructions can use labels directly:

ldb label, dest_reg [, offset]
ldh label, dest_reg [, offset]
ldw label, dest_reg [, offset]
stb src_reg, label [, offset]
sth src_reg, label [, offset]
stw src_reg, label [, offset]

⚠️ CRITICAL: These pseudo-instructions use acc as a scratch register! The value in acc will be overwritten.

Expansion:

; ldw buffer, rg2 expands to:
lli buffer, acc             ; Load lower 16 bits of buffer address into acc
lui buffer, acc             ; Load upper 16 bits of buffer address into acc
ldw acc, rg2, 0             ; Load word from address in acc into rg2

; stw rg1, current expands to:
lli current, acc            ; Load lower 16 bits of current address into acc
lui current, acc            ; Load upper 16 bits of current address into acc
stw rg1, acc, 0             ; Store word from rg1 to address in acc

Examples:

dw counter: 0

ldw counter, rg0            ; Load value of counter (clobbers acc!)
addi rg0, 1, rg0            ; Increment
stw rg0, counter            ; Store back (clobbers acc!)

⚠️ Warning Example - ACC Clobbering:

lli 100, acc                ; acc = 100
ldw data, rg0               ; ACC IS NOW OVERWRITTEN!
; acc now contains garbage (address calculation temp)
; The value 100 is LOST!

DSA Assembly Language Reference - Part 2

Function Call Pseudo-Instructions

call namespace::function    ; Call function from included module
return                      ; Return from function

⚠️ CRITICAL: The calling mechanism uses the STACK to store return addresses, not just the ret register!

CALL Expansion:

; call print::print expands to:
subi spr, 4, spr            ; Decrement stack pointer (allocate space)
stw pcx, spr, 0             ; Store return address (PCX) on stack
jmp function_addr, zero     ; Jump to function address

RETURN Expansion:

; return expands to:
ldw spr, ret, 0             ; Load return address from stack into ret
addi spr, 4, spr            ; Increment stack pointer (deallocate)
jmp 4, ret                  ; Jump to (ret + 4)

Note on RETURN +4 Offset: The implementation adds 4 to the return address. This may be to account for instruction sizing or pipeline considerations. Consult CPU documentation if modifying.

Module System

include namespace "path/to/file.dsa"

Example:

include print "lib/print.dsa"
include math "lib/math.dsa"

; Can now call:
call print::print
call math::multiply

Namespace Resolution:

  • Functions in included modules are accessible via namespace::label
  • Namespace is the identifier before the filename
  • Labels in included files are prefixed with the namespace

Calling Convention

DSA uses a stack-based calling convention with downward-growing stack.

Stack Frame Layout

⚠️ CRITICAL: Stack grows DOWNWARD (toward lower addresses)!

Higher Memory Addresses ↑
                        │
    ┌─────────────┐
    │   Caller's  │
    │   Frame     │
    └─────────────┘
    │   Arg N     │  ← Caller pushed (highest address argument)
    │   ...       │
    │   Arg 2     │  
    │   Arg 1     │  
    │   Arg 0     │  ← First argument (lowest address argument)
    ├─────────────┤  
    │   Ret Addr  │  ← Return address (pushed by CALL)
    ├─────────────┤
    │   Old BPR   │  ← Saved base pointer (pushed by callee)
    ├─────────────┤  ← BPR (current base), SPR (current top)
    │   Locals    │  ← Local variables (if any)
    │     ...     │
    └─────────────┘  ← SPR grows downward
                        │
Lower Memory Addresses  ↓

Frame Pointer Offsets (from BPR):

bpr + 0   = Old BPR (saved by function prologue)
bpr + 4   = Return address (pushed by CALL)
bpr + 8   = Argument 0 (first argument)
bpr + 12  = Argument 1
bpr + 16  = Argument 2
bpr + 8 + 4*N = Argument N

Calling Sequence

Caller Responsibilities:

  1. Push arguments in reverse order (last argument first):
push arg2                   ; Push last argument first
push arg1
push arg0                   ; First argument pushed last
  1. Call the function:
call namespace::function    ; Pushes return address, jumps to function
  1. Clean up arguments after return:
pop zero                    ; Discard arg0 (or pop rg0 to get return value)
pop zero                    ; Discard arg1
pop zero                    ; Discard arg2

Callee Responsibilities:

  1. Set up stack frame:
function:
    push bpr                ; Save caller's base pointer
    mov spr, bpr            ; Establish new base pointer
  1. Access arguments:
    ldw bpr, rg0, 8         ; Load arg0 from bpr+8
    ldw bpr, rg1, 12        ; Load arg1 from bpr+12
    ldw bpr, rg2, 16        ; Load arg2 from bpr+16
  1. Execute function body:
    ; Function logic here
    add rg0, rg1, acc       ; Example: acc = arg0 + arg1
  1. Store return value (optional - overwrites arg0 location):
    stw acc, bpr, 8         ; Store result where arg0 was
  1. Restore stack frame:
    mov bpr, spr            ; Restore stack pointer to base
    pop bpr                 ; Restore caller's base pointer
  1. Return to caller:
    return                  ; Pop return address and jump

Stack Evolution During Call

Before CALL:

    ┌─────────────┐
    │   Arg 2     │
    │   Arg 1     │  
    │   Arg 0     │  ← SPR points here
    └─────────────┘

After CALL (before function prologue):

    ┌─────────────┐
    │   Arg 2     │
    │   Arg 1     │  
    │   Arg 0     │
    │   Ret Addr  │  ← SPR points here (CALL pushed this)
    └─────────────┘

After Function Prologue:

    ┌─────────────┐
    │   Arg 2     │
    │   Arg 1     │  
    │   Arg 0     │
    │   Ret Addr  │
    │   Old BPR   │  ← SPR and BPR point here
    └─────────────┘

Complete Calling Example

; Function: add two numbers
; Args: arg0, arg1
; Returns: sum (overwrites arg0 position)

add_function:
    ; Prologue
    push bpr                ; Save caller's base pointer
    mov spr, bpr            ; Set up our stack frame
    
    ; Load arguments
    ldw bpr, rg0, 8         ; rg0 = arg0
    ldw bpr, rg1, 12        ; rg1 = arg1
    
    ; Perform operation
    add rg0, rg1, acc       ; acc = arg0 + arg1
    
    ; Store return value (overwrites arg0 position)
    stw acc, bpr, 8         ; Store result at bpr+8
    
    ; Epilogue
    mov bpr, spr            ; Restore stack pointer
    pop bpr                 ; Restore caller's base pointer
    return                  ; Return to caller

; Caller example:
main:
    ; Set up stack
    lwi stack_base, bpr
    mov bpr, spr
    
    ; Prepare arguments
    lli 5, rg0              ; First argument = 5
    lli 7, rg1              ; Second argument = 7
    
    ; Push arguments (reverse order!)
    push rg1                ; Push arg1 (7) first
    push rg0                ; Push arg0 (5) second
    
    ; Call function
    call local::add_function
    
    ; Retrieve result and clean up
    pop rg2                 ; Get result (12) - was at arg0 position
    pop zero                ; Discard arg1 slot
    
    hlt
    
dw stack_base: 0x10000

Register Usage Conventions

Register(s) Usage Preserved Across Calls?
rg0-rg3 Function arguments, temporaries No (caller-saved)
rg4-rge Local variables Conditional (callee-saved if used)
rgf General purpose Conditional (callee-saved if used)
acc Temporary calculations, scratch Never (volatile)
spr Stack pointer Yes (must be restored)
bpr Base pointer Yes (must be restored)
ret Return address Managed by call/return

Preservation Rules:

  • Caller-saved (rg0-rg3, acc): Caller must save these before calling if needed after
  • Callee-saved (rg4-rgf): Callee must save/restore if it uses them
  • Always preserved (spr, bpr): Must be restored to original values
  • Never preserved (acc): Assume destroyed by any operation

Example - Callee-Saved Registers:

my_function:
    push bpr
    mov spr, bpr
    
    ; We want to use rg4 and rg5 - must save them!
    push rg4
    push rg5
    
    ; Use rg4 and rg5 for local work
    lli 100, rg4
    lli 200, rg5
    add rg4, rg5, acc
    
    ; Restore callee-saved registers before returning
    pop rg5
    pop rg4
    
    mov bpr, spr
    pop bpr
    return

Complete Examples

Example 1: Multiplication Library

// multiply.dsa
// Multiplies two numbers using repeated addition
//
// Usage:
//   include multiply "multiply.dsa"
//   push multiplicand
//   push multiplier
//   call multiply::multiply
//   pop result      ; Result is in arg0 position
//   pop zero        ; Clean up arg1

multiply:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8         ; Load multiplier (arg0)
    ldw bpr, rg1, 12        ; Load multiplicand (arg1)

    lli 0, acc              ; Initialize result to 0

loop_start:
    add acc, rg0, acc       ; acc += multiplier
    dec rg1                 ; multiplicand--

    cmp rg1, zero
    jgt loop_start, zero    ; Continue if multiplicand > 0

    stw acc, bpr, 8         ; Store result for caller (at arg0)
    
    mov bpr, spr
    pop bpr
    return

Example 2: Print Library

// print.dsa
// Prints null-terminated string to display memory
//
// Usage:
//   include print "print.dsa"
//   
//   push string_address
//   call print::print
//   pop zero
//
//   call print::reset     ; Reset cursor (no args)

dw display: 0x20000         ; Display memory base address
dw current: 0x20000         ; Current cursor position

// Print function
print:
    push bpr
    mov spr, bpr

    ldw bpr, rg0, 8         ; Get string address argument
    ldw current, rg1        ; Get current cursor position (clobbers acc!)

print_loop:
    ldb rg0, acc, 0         ; Load character
    stb acc, rg1, 0         ; Store to display

    addi rg0, 1, rg0        ; Advance string pointer
    addi rg1, 1, rg1        ; Advance cursor

    cmp acc, zero           ; Check for null terminator
    jne print_loop, zero    ; Continue if not null

    stw rg1, current        ; Save cursor position (clobbers acc!)

    mov bpr, spr
    pop bpr
    return

// Reset cursor function
reset:
    push bpr
    mov spr, bpr
    
    ldw display, rg1        ; Load display base (clobbers acc!)
    stw rg1, current        ; Reset cursor to start (clobbers acc!)
    
    mov bpr, spr
    pop bpr
    return

Example 3: Main Program

// main.dsa
// Demonstrates using included libraries

include print "./print.dsa"

dw stack: 0x10000
db string: "'To confuse your enemy, you must first confuse yourself' - Probably Sun Tzu.", 0

init:
    // Set up stack
    ldw stack, bpr          ; Load stack base (clobbers acc!)
    mov bpr, spr

start:
    // Load string address
    lwi string, rg1         ; Load address of string

    // Call print function
    push rg1
    call print::print
    pop rg1                 ; Clean up (or pop zero to discard)

    hlt

Example 4: Conditional Logic

// Demonstrates comparisons and branching

dw value: 42

main:
    ldw value, rg0          ; Load value (clobbers acc!)
    
    cmp rg0, zero
    jeq is_zero, zero
    jgt is_positive, zero
    jlt is_negative, zero

is_zero:
    lwi zero_msg, rg1
    jmp print_and_exit, zero

is_positive:
    lwi positive_msg, rg1
    jmp print_and_exit, zero

is_negative:
    lwi negative_msg, rg1
    jmp print_and_exit, zero

print_and_exit:
    push rg1
    call print::print
    pop zero
    hlt

db zero_msg: "Value is zero", 0
db positive_msg: "Value is positive", 0
db negative_msg: "Value is negative", 0

Example 5: Loop with Counter

// Count from 0 to 9

dw stack: 0x10000

main:
    ldw stack, bpr          ; Set up stack
    mov bpr, spr
    
    lli 0, rg0              ; Counter = 0
    lli 10, rg1             ; Limit = 10

loop:
    // Process counter value
    push rg0
    call process_value
    pop zero
    
    inc rg0                 ; Counter++
    cmp rg0, rg1            ; Compare with limit
    jlt loop, zero          ; Loop if counter < limit
    
    hlt

process_value:
    push bpr
    mov spr, bpr
    
    ldw bpr, rg0, 8         ; Get value
    ; Process value here...
    
    mov bpr, spr
    pop bpr
    return

Best Practices

1. Stack Management

  • Always balance push/pop operations in the same function
  • Set up stack frame (push bpr; mov spr, bpr) in every function
  • Clean up arguments after function calls (caller's responsibility)
  • Use pop zero to discard unwanted values
  • Remember: stack grows DOWNWARD

2. Register Usage

  • Never rely on acc being preserved across ANY operation
  • Treat acc as write-only temporary storage
  • Save caller-saved registers (rg0-rg3) before calls if needed
  • Restore callee-saved registers (rg4-rgf) if modified
  • Use zero register for zero constants

3. Memory Access

  • Ensure proper alignment for halfword/word access
  • Be aware that label-based loads/stores clobber acc
  • Prefer register-based addressing when acc value matters
  • Check that labels are defined before use

4. Function Design

  • Document calling convention in function comments
  • Always include prologue and epilogue
  • Validate input arguments when appropriate
  • Use consistent parameter order across related functions
  • Return values via stack (overwrite arg0) or designated register

5. Code Organization

  • Use meaningful label names (e.g., loop_start not l1)
  • Comment complex operations
  • Group related functions in modules
  • Use includes for code reuse
  • Keep functions focused and small

6. Performance Considerations

  • Minimize memory accesses (use registers when possible)
  • Avoid unnecessary comparisons
  • Use shifts for multiplication/division by powers of 2:
    shl rg0, 3              ; Multiply by 8 (2^3)
    shr rg0, 2              ; Divide by 4 (2^2)
    
  • Consider instruction pipelining if CPU supports it

7. ACC Register Awareness

⚠️ CRITICAL - Common Mistakes:

; WRONG - acc gets clobbered:
lli 100, acc
ldw data, rg0           ; acc is now GARBAGE!
add acc, rg0, rg1       ; Using garbage value!

; RIGHT - use a different register:
lli 100, rg2
ldw data, rg0           ; acc clobbered (don't care)
add rg2, rg0, rg1       ; rg2 still has 100

; WRONG - assuming acc preserved across call:
lli 42, acc
call some_function
add acc, rg0, rg1       ; acc probably destroyed!

; RIGHT - use caller-saved register:
lli 42, rg0
call some_function
add rg0, rg1, rg2       ; rg0 might be destroyed, so save it first!

Common Patterns

Loading 32-bit Constants

lli lower_16_bits, reg
lui upper_16_bits, reg

; Example:
lli 0x1234, rg0         ; rg0 = 0x00001234
lui 0xABCD, rg0         ; rg0 = 0xABCD1234

Zero a Register

mov zero, reg           ; Method 1 (preferred - clearest)
xor reg, reg, reg       ; Method 2 (clever but less clear)
lli 0, reg              ; Method 3 (works but wastes upper bits clear)

Copy Memory

ldw src_addr, rg0       ; Load from source (clobbers acc)
stw rg0, dest_addr, 0   ; Store to destination (clobbers acc)

; If you need acc preserved:
lli 0x1234, acc         ; Some value in acc
push acc                ; Save it
ldw src_addr, rg0       ; acc clobbered
stw rg0, dest_addr, 0   ; acc clobbered again
pop acc                 ; Restore acc

Multiply/Divide by Power of 2

shl reg, 3              ; Multiply by 8 (2^3)
shr reg, 2              ; Divide by 4 (2^2)

Boolean NOT

cmp reg, zero
jeq was_zero, zero      ; If reg == 0, result is 1
lli 0, reg
jmp done, zero
was_zero:
    lli 1, reg
done:

Min/Max

; max(rg0, rg1) -> rg2
mov rg0, rg2            ; Assume rg0 is max
cmp rg0, rg1
jge done, zero          ; If rg0 >= rg1, we're done
mov rg1, rg2            ; rg1 was larger
done:

Array Indexing

; Access array[i] where array is 32-bit words
; rg0 = array base address
; rg1 = index i
; Result in rg2

shl rg1, 2              ; Convert index to byte offset (i * 4)
add rg0, rg1, acc       ; acc = base + offset (use acc for temp)
ldw acc, rg2, 0         ; Load array[i]

Troubleshooting

Common Errors

Alignment Fault:

  • Symptom: Exception when loading/storing halfword or word
  • Cause: Address not properly aligned
  • Fix: Ensure halfword addresses are even, word addresses divisible by 4

Illegal Instruction:

  • Symptom: Unexpected halt or exception
  • Cause: Invalid opcode or malformed instruction
  • Fix: Check assembly syntax, verify instruction encoding

Stack Corruption:

  • Symptom: Function returns to wrong address, registers have wrong values
  • Cause: Unbalanced push/pop, incorrect stack frame management
  • Fix:
    • Verify push/pop balance
    • Check function epilogue restores bpr
    • Ensure caller cleans up arguments
    • Verify stack grows downward (push decrements)

Wrong Results / Unexpected Values:

  • Symptom: Calculations produce incorrect results
  • Cause: ACC clobbering, wrong register assumptions
  • Fix:
    • Check if acc was assumed preserved (it never is!)
    • Verify lli called before lui for 32-bit constants
    • Check signed vs unsigned loads (ldb vs ldbs)
    • Verify register preservation across calls

Label Not Found:

  • Symptom: Assembler error about undefined label
  • Cause: Label used before definition, typo in label name
  • Fix: Define labels before use, check spelling

Debugging Tips

  1. Add NOP markers at key points for breakpoints:

    nop                     ; Breakpoint here
    
  2. Print register values using display memory:

    stw rg0, debug_out      ; Store to known location
    
  3. Use single-step execution to trace program flow

  4. Verify stack pointer values at function boundaries:

    ; After prologue, check:
    ; spr == bpr (should point to same location)
    ; After epilogue, check:
    ; spr restored to entry value
    
  5. Check label addresses in disassembly output

  6. Trace ACC usage:

    lli 0xDEAD, acc         ; Marker value
    ; ... your code ...
    cmp acc, zero
    jeq acc_was_clobbered, zero  ; If acc==0, it was overwritten
    

Appendix: Instruction Quick Reference

Category Instructions
Data Movement mov, movs
Memory Load ldb, ldbs, ldh, ldhs, ldw
Memory Store stb, sth, stw
Immediate Load lli, lui
Jump/Branch jmp, jeq, jne, jgt, jge, jlt, jle
Comparison cmp
Arithmetic add, sub, iadd, isub, inc, dec
Logical and, or, xor, not, nand, nor, xnor
Shift shl, shr
System hlt, nop, int, irt
Pseudo - Data db, dh, dw, resb, resh, resw
Pseudo - Stack push, pop, pusha, popa
Pseudo - Memory lwi, ldb/ldh/ldw with labels, stb/sth/stw with labels
Pseudo - Control call, return
Pseudo - Module include

Version History

  • v2.0 - Corrected comprehensive reference based on implementation
    • CORRECTED: Stack grows downward (not upward)
    • CORRECTED: CALL/RETURN use stack for return addresses
    • CORRECTED: Pseudo-instructions use ACC as scratch (not rgf)
    • CORRECTED: Label loads use ACC for address calculation
    • CORRECTED: IADD/ISUB require destination (not optional)
    • ADDED: PUSHA/POPA documentation
    • ADDED: Detailed ACC volatility warnings throughout
    • ADDED: Stack evolution diagrams
    • CLARIFIED: Big-endian data directive encoding
    • CLARIFIED: Jump instruction semantics
    • All examples updated to reflect correct stack direction
    • Added extensive troubleshooting for ACC-related issues