Sample Assembly Program: Hello, World!
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