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)
- Decimal:
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 requiredldh/ldhs: Must be 2-byte alignedldw: 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:
- Push arguments in reverse order (last argument first):
push arg2 ; Push last argument first
push arg1
push arg0 ; First argument pushed last
- Call the function:
call namespace::function ; Pushes return address, jumps to function
- 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:
- Set up stack frame:
function:
push bpr ; Save caller's base pointer
mov spr, bpr ; Establish new base pointer
- 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
- Execute function body:
; Function logic here
add rg0, rg1, acc ; Example: acc = arg0 + arg1
- Store return value (optional - overwrites arg0 location):
stw acc, bpr, 8 ; Store result where arg0 was
- Restore stack frame:
mov bpr, spr ; Restore stack pointer to base
pop bpr ; Restore caller's base pointer
- 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 zeroto discard unwanted values - Remember: stack grows DOWNWARD
2. Register Usage
- Never rely on
accbeing preserved across ANY operation - Treat
accas write-only temporary storage - Save caller-saved registers (rg0-rg3) before calls if needed
- Restore callee-saved registers (rg4-rgf) if modified
- Use
zeroregister 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
accvalue 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_startnotl1) - 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
-
Add NOP markers at key points for breakpoints:
nop ; Breakpoint here -
Print register values using display memory:
stw rg0, debug_out ; Store to known location -
Use single-step execution to trace program flow
-
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 -
Check label addresses in disassembly output
-
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