nbasic Language Reference Manual

Updated March 15, 2004

Contents:

 

About nbasic

nbasic is a high-level programming language designed for the 6502 processor, the main CPU of the Nintendo Entertainment System. It has BASIC-like program flow, relying on goto, gosub, and return for most execution flow. It also has no dynamic memory allocation and no actual function parameter passing; rather, globally scoped variables and arrays take the place of these. This kind of language design allows for a very efficient implementation on a low-powered 8-bit CPU such as the NES's 6502.

The language was originally created by Bob Rost for development of the homebrew original NES game Sack of Flour, Heart of Gold, and its development continues as the language is used in Bob's class at Carnegie Mellon, Game Development for the 8-bit NES. While this document can be a useful guide when dealing with the language, the best way to learn is to play with some of the example nbasic programs on the course website.

 

Code Comments

Comments are created by using any of several common comment conventions. Comments begin with a double forward slash, a hash mark, or a semicolon. A comment may begin at any location in a line, and the rest of the line is ignored by the compiler.

   // this is a C++-style comment
   # this is a shell-style comment
   ; this is a nesasm-style comment (for inline assembly)
   ;// this nesasm-style comment shows up colored in my C++ editor

 

Inline Assembly

You may sometimes wish to use inline assembly, such as when a particular advanced feature is not provided natively by the nbasic compiler, or when you must feed directives to the assembler. Assembly blocks begin with the asm keyword, and they are ended by the endasm keyword. The lines of text in between are fed as-is to the assembler. Note that, if you are using nesasm as your assembler, you will be required to start most lines with a space or tab.

asm
  lda #0 ;nesasm comments start with a semicolon
  sta $2000
  sta $2000
endasm

In addition to a large block of assembly code, you can also create a single line of inline assembly. This is useful for using a single instruction without interrupting the flow of your code. The asmline keyword takes the remainder of its line and puts it into your file as assembly.

asmline lda [pointer],y

 

Numbers

For your convenience, nbasic supports binary (base 2), decimal (base 10), and hexadecimal (base 16) numbers. All numbers are treated as unsigned, usually in the range 0 to 255, though sometimes in the 16-bit range for memory locations. Binary numbers begin with a % character, decimal numbers are a string of digits, and hexadecimal numbers begin with a $ character. The hexadecimal letters may be either capital or lowercase. These are examples of the same number written in all three methods:

  %11111111
  255
  $ff

 

Variables

The first important rule of nbasic variables is that you should not use the names A, X, or Y as variable names. These are special registers of the 6502 processor. You are allowed to use them in many cases as you would a variable, but you should first be familiar with the 6502 and what you are doing in each particular use. As a general rule, array accesses will alter the X register, and any sort of arithmetic expression will alter the A register. Y is not touched in many cases.

General variables in nbasic are single unsigned bytes of memory. They have global scope throughout your program, and you have the option of initializing them manually or automatically. Automatic initialization is done the first time a variable's value is set, and a single byte of convenient memory is allocated. Manual memory allocation is described below in the Arrays section.

 

Setting Variables

The set statement is used to assign a value to a variable, array element, or a specific location in memory. The general syntax is set location value. The value is always an unsigned byte. These examples demonstrate setting a value to a variable, an array element, and a specific memory location.

  set my_var 5
  set [my_array 3] 1
  set $4016 0

It is important to note that the compiler automatically allocates 1 byte of memory for any variable which is assigned a value and not otherwise declared elsewhere in the program.

 

Push and Pop

There are times when you may wish to store data on the processor stack. For instance, saving and restoring the values of registers during an interrupt. For important cases like this, you can use the push and pop keywords to store or retrieve data on the stack. The nbasic implementation will only let you directly push or pop the values of registers. Note that, because gosub and return also manipulate the stack, you should always be certain to pop within the same function as a corresponding push. Due to the nature of stacks, if you push several registers, you should pop them in the reverse order. Here is an example of proper stack usage during an interrupt.

IRQ:
  push a
  push x
  push y
  gosub irq_handler
  pop y
  pop x
  pop a
  resume

 

Array Declaration

Arrays and variables are essentially the same in nbasic, and they differ primarily in their usage.

Arrays may be declared with several methods. Simple declaration assigns a specified number of bytes to a variable name, using the first conveniently available memory that the compiler finds. This example allocates 8 bytes for the variable my_array.

  array my_array 8

Arrays may also be declared in the zero page of memory, which guarantees that they are entirely within the first 256 bytes of system memory, which allows faster access time for frequently used data.

  array zeropage my_array 8

You may also wish for your array to exist at a particular location in memory. For this, you use an absolute array declaration, providing the location in memory, the variable name, and the array size. This is very helpful for allocating sprite memory to allow DMA.

  array absolute $c000 my_array 8
  array absolute $200 sprite_mem 256

If you wish to allocate a particular general variable in the zero page or at an absolute memory location, you may declare an array of size 1 using the above syntax. It is also sometimes helpful to declare an absolute array of size 0. This will allow you to reference a particular memory region by a simple variable name. This can be useful for NES system ports, or for quickly accessing certain parts of an array.

  array absolute $4016 joystick1port 0
  array absolute $c000 hero_data 8
  array absolute $c001 hero_lives 0

It is important to note that nbasic automatically allocates the memory region from $100 to $1FF for the variable nbasic_stack. This is the memory region of the 6502's call stack, which is very bad for you to accidentally overwrite. If you absolutely must manipulate this region manually, then you may use the nbasic_stack variable; otherwise, don't touch it.

 

Array Usage

As mentioned above, arrays and variables in nbasic are essentially the same, and they differ primarily in usage. An array is a contiguous region of memory assigned to a variable name. Elements in the array may be referenced by the general syntax [array_name index]. Additionally, the first element of the array may be referenced simply by its name. Array elements, like other variables, are unsigned bytes, and they may be used in arithmetic expressions or set like other variables. The index of an array element may be a constant number, a variable, or an arithmetic expression.

  [my_array 0] //the first element
  my_array //equivalent to the above line
  set [my_array my_var] 5
  [my_array [my_var 2]]
  [my_array x]

Notice in the last example that the index variable is the X register. In general, array accesses destroy the current value of the X register. However, you may manually set the value of either the X or Y registers and use one of them as your array index. This will preserve the value of the register, and the array access itself will be faster. This can be useful when using the same index multiple times, or in multiple arrays.
  set y 5
  set [my_array y] 3
  set [your_array y] 4
  set [his_array y] [my_array 2]

 

Static Data

The data keyword allows specifying a string of raw data to be included in the ROM. This is often used for text, palettes, and similar small and unchanging pieces of data. Data may consist of unsigned 8-bit numbers and ASCII strings, each separated by commas. ASCII strings must be enclosed in double quotes, and each character is individually converted to the corresponding ASCII value. Generally, a data statement will appear after a label, so that it may be referenced easily.

  my_data:
    data 1,3,"A string",0,$10,%10101

 

Labels

A label in nbasic is similar to a line number or label in many other programming languages. Your program may jump to a label to continue execution from that point on. In nbasic, labels may also be used as a variable, to allow you to reference static data at that point in the ROM. Labels are written as a name, followed by a colon. These examples demonstrate jumping to a label, and using a label as an array reference.

  my_label:
    set my_var [my_data 2]
    goto my_label
  my_data:
    data 0,1,2,3,4

 

Jumps

Program flow is controlled by jumps, which allow your program to move the execution point to a label. The two types of jumps are goto, and gosub. A goto will simply move the current execution point, as it does in BASIC. A gosub is also just like in BASIC; it moves the current execution point, but it also saves the previous execution point on the stack. The return statement will return execution to just after the point from which the jump was made. Note that if you call gosub rampantly and do not have a return for each one, your program will overflow the 256 byte stack, which will crash the game.

  my_label:
    gosub my_function
    goto my_label
  my_function:
    return

In BASIC, the goto statement is notorious for creating "spaghetti" code, and it is frowned upon in other languages. For this reason, I have two primary guidelines for proper usage of jumps, in order to help avoid some of the common problems.

Notice that I reference the concept of a function, even though nbasic does not actually support functions as in other languages. In general, a function is a block of code under a label, which is written to do one particular thing. With proper code design, the boundaries between one conceptual function and another will be obvious in almost all cases.

Another related keyword is resume. It is similar to return, but it is used to return execution after an interrupt is called, such as the NMI at the blanking interval.

Yet another related keyword is branchto. See the section on conditionals for information on that.

 

Arithmetic Expressions

Arithmetic expressions in nbasic are written in prefix notation. This creates non-ambiguous expressions that are easy for the compiler to parse. Valid operators are addition (+), subtraction (-), shift left (<<), shift right (>>), bitwise and (&), bitwise or (|), bitwise xor (^). Each of these operators should be followed by two numbers, variables, array lookups, or arithmetic expressions in order to create a valid expression. Note that bit shifting may only be done by a constant amount.

    set a + one two
    set four << one 1
    set value - & bitmask %10101011 + 3 two

 

Conditional Expressions

The nbasic language supports two types of conditional statements. The first is if condition result. This will execute a single nbasic statement if the provided condition is true. The second type is an if condition then...endif construct. In this type, when the provided condition is true, it will execute multiple nbasic statements listed between the then and endif keywords. Note that, due to limitations of the hardware, the assembler will fail if the statement block compiles to more than 127 bytes of code.


  if one <= 2 then
    set result1 one
    set result2 two
    gosub do_more_stuff
    return
    endif
  if x <> 0 branchto my_label

You may notice the use of the branchto keyword. This is a special case goto, designed for fast loop execution. You may use a branchto as the action of a simple comparison, and it will jump to the designated label. However, the jump may not cross more than 127 compiled bytes of code.

 

List of nbasic Keywords

  a
  absolute
  array
  asm
  asmline
  branchto
  data
  endasm
  endif
  gosub
  goto
  if
  pop
  push
  resume
  return
  set
  then
  x
  y
  zeropage