The Project and Modules

The project

NGA is designed for scalability, so there are no loose source files here: every file belongs to a project, and the project is what you hand to the tool. The only thing the minimal project must say is which files the program is made of:

modules { "sum.asm" }

the whole of sum

modules lists them, one module per file, named by the file's stem.

The module defined by a source file is the smallest unit of translation. There is no concept of file inclusion and each module is assembled/compiled independently in parallel.

The program starts at a label called entry.

The module

.section
total
        .res 1
.ends

.proc entry
        lda #0
        ldx #4
@add    clc
        adc numbers,x
        dex
        bpl @add
        sta total
        rts
.endp

.section
numbers
        .byte 3, 1, 4, 1, 5
.ends

the whole of sum

A section is a run of bytes that stays together, and it is a unit of allocation handled by the tool which is given an address. Everything that emits or reserves bytes stands inside one and a statement outside a section is an error. .res 1 reserves a byte and emits nothing, so total occupies an address and costs the output file nothing.

A label is an identifier in column one with no colon after it. @add is a local label, which belongs to the proc it stands in and may be spelled the same in another.

A proc is a section of code with one entry. Nothing outside it may name a position inside it, which is what makes it the unit that is dropped when nothing calls it.

Sections can stand in a module any order.

Building it

nga main.ngp -o sum.bin --map sum.map

sum.bin is a raw memory image. The map is where the result of that decision is read:

NGA memory map

phase phase0 (0)
  zero page: 0 of 16 bytes
  memory:    21 of 32768 bytes
  $2000-$2000  sum.total   section            phase0
  $2001-$200F  sum.entry   proc               phase0
  $2010-$2014  sum.numbers section            phase0

the whole of sum

Three rows, for the three sections of sum.asm, at $2000 onwards because that is where the stand-in leaves memory free. Nothing in the source asked for that address, or for any address. Change the program and they change with it, and this is where you look to see what they became.

The same program in C

static u8 total;

static void entry()
{
  u8 sum = 0;
  for ( u8 i = 0; i < 5; ++i )
    sum += numbers[i];
  total = sum;
}

static const u8 numbers[] = { 3, 1, 4, 1, 5 };

the whole of sum-in-c

A .ngc file is a module like any other:

modules { "sum.ngc" }

the whole of sum-in-c

The types are the machine's — u8, i8, u16, i16 — and there is no int.

There is no preprocessor so no #include and no #define. The rest of this program is C with some C++ flavour.

What the C became

A .ngc module is compiled to the text of an assembler module, and that text is assembled exactly as the one you wrote by hand. --emit-asm DIR writes it out:

.section
total
        .res 1
.ends

.proc entry
        lda #0
        tax
        clc
@l2
        adc numbers,x
        inx
        cpx #5
        jcc @l2
@l4
        sta total
        rts
.endp

.section readonly
numbers
        .byte 3, 1, 4, 1, 5
.ends

the whole of sum-in-c

The compiler has no runtime of its own to hide in and no stack frames to build: it writes the assembler you would have written, against the same model, and everything the rest of this tutorial teaches about memory applies to both languages in the same words. The one word it wrote that the C did not is readonly, which is what a const becomes where it is bytes rather than a number the assembler can fold — it lets the solver put the table in ROM on a machine that has some, and chapter fourteen is where that matters.