Advanced Syntax

File imports

Files can be imported using a Python-like syntax:

import as graphics
import "libs/menu.cor"

  jsr graphics.begin
  jsr menu.main
  jsr graphics.end
  jmp main

As in python, imported file labels and variables are not implicit. Unlike Python, however, Corvassembly does not support star imports (i.e. from graphics import *). ram and rom addresses are statically allocated in the order of importing. Import paths are relative to the file doing the importing, not where the assembler is executed (as it should be).

Assemble-time Math

Arbitrary math can be executed with static elements of a program at assemble time in any place where static elements are valid. The math is executed by Python, so any valid Python statement can be used:

pre MAX = 2**16 - 1
pre SIZE = 128*64
rom sizes[] = {SIZE/8, SIZE/16}

cmp a, SIZE - 1


Corvassembly permits the declaration of arrays in ram and rom. ram arrays cannot be initialized, while rom arrays require initialization. rom arrays should not generally be given a size, as it is automatically determined by the contents of the initializer. Strings are interpreted as arrays, and are initialized with the same left-side syntax. String arrays are converted to their ascii representation and null- terminated:

ram arr[20]
rom arrInit[] = {1, 1, 2, 3, 5, 8, 13}
rom string[] = "Hello, world!"

Character Constants

Character constants, much like in other languages, are surrounded by single ticks and are converted to their ASCII representation. You can operate on them like any other number, and even manually declare your own strings (if you like pain):

rom char = 'a'
rom escaped = '\''
rom string[] = {'H', 'e', 'l', 'l', 'o', '\0'}

cmp a, 'A' + 24

Variable Addressing

Variable addresses can be accessed with C-like syntax, using &. In addition, array names are automatically understood as static addresses rather than variables, and can also be treated in a C-like manner:

ram variable
ldr f, &variable

rom array[] = {1, 2, 3, 'a', 'b', 'c'}
ldr g, array + 3

Pointer Registers

In Corvassembly, there are three registers that double as pointers to specific types of memory for indirect addressing.


This register can be used to access ram.


This register can be used to access rom.


This register can be used to access the gpu, should it be implemented.

The address space of any memory does not exceed 16 bits, so each pointer can be fully loaded in a single instruction. These pointers are invoked with a lpt or spt instruction and the corresponding memory type, e.g.:

ram array[20]
ldr f, array
lpt a, ram


The CorvusPrudensUnit provides two interrupt sources: FRAME and TIMER.

The frame interrupt fires after the gpu finishes sending out the current frame. If you have written instructions to the gpu, these will be executed just before the interrupt. See The GPU for details.

The TIMER interrupt fires when the memory-mapped timer module reaches the given compare value. See Flash for details.

The syntax for attaching interrupts is covered here.

If statements

if, elif, and else statements are fully supported by Corvassembly. They are structured as you would expect, where an if can stand alone, it can be followed by an elif or else, or any number of elif statements and a closing else. Conditions are indicated with an is or isnt and the desired condition. By the nature of Corvassembly, isnt statements require one less instruction, but may be more difficult to read:

  if (cmp a, 1 is equal) {
    jmp wait

  // assuming a will only be 1 or 0, this is
  // equivalent to the above routine
  if (cmp a, 0 isnt equal) {
    jmp waitShort

  if (cmp a, 1 is equal) {
    jsr condition1
  } elif (cmp a, 2 is equal) {
    jsr condition2
  } elif (cmp a, 3 is equal) {
    jsr condition3
  } else {

The first two examples assemble to:

cmp a, 1
joc equal, __if0_branch0_t
jmp __if0_end
jmp wait

cmp a, 0
joc equal, __if1_end
jmp waitShort

if statements can, of course, be nested:

if (cmp a, 1 is equal) {
  if (cmp b, 1 is equal) {
    jsr somewhereElse
jmp waitNested

if statements also permit an arbitrary number of instructions to precede the actual evaluation, separated by semi-colons. This facilitates more complicated behavior within if elif else blocks:


  ram state1
  ram state2

  ldr a, state1
  if (cmp a, 1 is equal) {
    jsr state1True
  } elif (ldr a, state2; cmp a, 1 is equal) {
    jsr state2True



Logical and (&&) and or (||) operations within an if statement, e.g. if (cmp a, 1 is equal && cmp b, 1 is equal) is not currently supported, but is slated for inclusion.

For loops

Corvassembly features a somewhat limited form of the for loop, with syntax similar to C’s:

for (ldr a, 0; cmp a, 256; add a, 1) {
  str a, UART

This assembles to:

ldr a, 0
cmp a, 256
joc equal, __loop0_end
str a, UART
add a, 1
jmp __loop0_begin

The feature is limited because it can only accept register incrementors, meaning the chosen register cannot be altered during the loop. It’s often useful to jump to subroutines during a loop, so this limitation is somewhat crippling.

There are a number of keywords available for use inside a loop, including continue, break, and breakall. continue simply jumps to the end of the loop, skipping to the next iteration. break will break out of the loop, jumping to the end. breakall will break out of nested loops (I’m not sure why more languages don’t have this feature):

ram interruptBreak
ram interruptCont

for (ldr a, 0; cmp a, 256; add a, 1){
  for (ldr b, 0; cmp b, 256; add b, 1){
    if (cmp a, interruptStop is less) {
    } else if (cmp b, interruptCont is less) {
    str a, UART