Sample Assembly Program: Hello, World!

From C256 Foenix Wiki
Revision as of 22:59, 14 February 2020 by Gadget (talk | contribs) (Created page with "== Sample Assembly Program: Hello, World! == The following code is a bare-bones example of C256 assembly code to print the usual "Hello, world!" message. The code may look a...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Sample Assembly Program: Hello, World!

The following code is a bare-bones example of C256 assembly code to print the usual "Hello, world!" message. The code may look a bit daunting for such a simple task, but that is partly because it is self-contained. Several things being done here would be provided by include files or be in macros. Also, this code is using a special trick to auto-run the program when it is uploaded to the C256: it over-writes the 65816's hardware RESET vector with a pointer to the start of the program. When the code is uploaded, the debug interface automatically resets the CPU, triggering the CPU to execute this code.

The complete example, along with a batch file to assemble it can be found at: https://github.com/pweingar/C256Samples

.cpu "65816"                        ; Tell 64TASS that we are using a 65816

; Kernel jump points... this would normally be in an include file

PUTS = $00101C                      ; Print a string to the currently selected channel

; Hardware RESET vector

* = $00FFFC

RESET   .word <>START               ; Over-ride the RESET vector with the start of our code

; Code

* = $002000                         ; Set the origin for the file

START   CLC                         ; Make sure we're native mode
        XCE

        ; This would normally be done with a macro "setas"
        SEP #$20                    ; Set M to 1 for 8-bit accumulator
        .as                         ; Tell 64TASS that the accumulator is 8-bit

        ; This would normally be done with a macro "setxl"
        REP #$10                    ; Set X to 0 for 16-bit index registers
        .xl                         ; Tell 64TASS that the index registers are 16-bit

        ; Set the data bank register to this bank. This might normally be done by a macro "setdbr"
        LDA #`GREET                 ; Set the data bank register to be the current bank of the program
        PHA
        PLB
        .databank `GREET            ; Tell 64TASS which data bank we're using

        LDX #<>GREET                ; Point to the message in an ASCIIZ string
        JSL PUTS                    ; And ask the kernel to print it

_done   NOP                         ; Infinite loop when we're finished
        BRA _done

GREET   .null "Hello, world!", 13   ; The text to display. Will include a terminal NUL

Step-by-step Explanation of the Program

The first thing our code has to do is tell 64TASS that the code is intended for a 65816 processor. It can do this using the .cpu assembler directive.

.cpu "65816" 

Next, the program will be printing a string using the kernel, so we need a reference to the kernel's routine to print a string. In this case, we'll be using the PUTS kernel function to print an ASCIIZ string. Normally, a program would use a kernel jump table include file, but here we'll just define a label. Note that on the FMX, the kernel jump table is located in bank 0, at $00:1000.

PUTS = $00101C

Since we're taking control after a CPU reset, we need to tell the 65816 to go to native mode. When the 65816 resets, it starts up in emulation mode, where it acts almost exactly like a 6502. We enter native mode by clearly the carry bit and exchanging the carry bit with the emulation flag:

START   CLC                         ; Make sure we're native mode
        XCE

The 65816 allows the accumulator and index registers to be different sizes. The registers can be either 8-bits wide, as in the standard 6502 processors, or they can be 16-bits wide. For this program, we want the accumulator to be 8-bits wide, and the index registers to be 16-bits wide (PUTS expects the 16-bit pointer to the string to print in X). This is done using a combination of the REP and SEP instructions, which clear or set bits in the processor status register, and assembler directives. The assembler directives are needed because the assembler needs to know how big the registers are for that section of code (some instructions are different lengths, depending on the register sizes):

        ; This would normally be done with a macro "setas"
        SEP #$20                    ; Set M to 1 for 8-bit accumulator
        .as                         ; Tell 64TASS that the accumulator is 8-bit

        ; This would normally be done with a macro "setxl"
        REP #$10                    ; Set X to 0 for 16-bit index registers
        .xl                         ; Tell 64TASS that the index registers are 16-bit

Next, we need to set the data bank register (B). This is a new register on the 65816, which specifies what address bits 23..16 should be for any addressing mode that only provides 16-bits. The PUTS function expects that the data bank register is set to the bank containing the string to print, so the full address of the string might be written as "B:X". As with the register sizes, the assembler needs to know what the data bank register setting is, and we can set that using the ".databank" assembler directive.

        ; Set the data bank register to this bank. This might normally be done by a macro "setdbr"
        LDA #`GREET                 ; Set the data bank register to be the current bank of the program
        PHA
        PLB
        .databank `GREET            ; Tell 64TASS which data bank we're using

Now, we can print our message. We do that by setting X to the address of the string and calling the PUTS function. Since we need X to be set to the lower 16-bits of the address of the string, we use the "<>" prefix, which is like the "<" and ">" prefixes used in 6502 assembly, but selects bits 15..0 of the address. Also, we use "JSL" instead of "JSR" to call the PUTS function, since all kernel calls are long subroutine calls.

        LDX #<>GREET                ; Point to the message in an ASCIIZ string
        JSL PUTS                    ; And ask the kernel to print it

Finally, we have the program enter an infinite loop at the end:

_done   NOP                         ; Infinite loop when we're finished
        BRA _done

The message to print is just provided using the ".null" directive, which stores the string as a null-terminated (ASCIIZ) string:

GREET   .null "Hello, world!", 13   ; The text to display. Will include a terminal NUL