Getting Started Guide

From C256 Foenix Wiki
Revision as of 13:18, 12 February 2020 by Bzuidgeest (talk | contribs)
Jump to navigation Jump to search

Getting Started Guide

Installing Drivers

Data and programs can be sent to the C256 Foenix over the USB debug port (the USB port on the right side of the board, next to the expansion connector). The debug interface is a USB serial port which requires a driver to be installed on the host system. The drivers for the serial USB device (XR21V1411) can be found at: https://www.maxlinear.com/support/design-tools/software-drivers.

Helpful Tools

Foenix IDE
The Foenix IDE is a full-featured emulator and board manager tool written in C#. The bulk of the Foenix IDE is an emulator of the C256 Foenix which emulates several key systems of the C256 and allows for breakpoints in the code, examination of memory, and so on. The IDE can also connect to the physical C256 through the USB debug interface. It can use the interface to upload data from a BIN file, an Intel HEX file, or the IDE emulator's memory into the C256, download memory from the C256 into the emulator's memory to be examined through the IDE, or to reprogram the C256's flash memory. Although a GUI application by default, the IDE can be used as a command line tool. The IDE also includes a character set / font editor tool, allowing you to design your own characters to use in your C256 programs. The source code for the IDE as well as the latest binary can be found at: https://github.com/Trinity-11/FoenixIDE.
C256Mgr
C256Mgr is a set of Python scripts allowing access to the C256's USB debug interface. The scripts allow you to use the command line to upload BIN and HEX files to the C256, reprogram the flash memory, or examine memory on the C256. If your code includes a 64Tass compatible LBL file, the C256Mgr scripts can use that LBL file to allow you to examine memory based on the labels in your code. C256Mgr was originally intended to provide a cross-platform option for those who cannot run .Net applications. The C256Mgr can be found at: https://github.com/pweingar/C256Mgr.
64Tass
Although any assembler that can produce 65816 code can be used to write code for the C256, the kernel, BASIC interpreter, and much of the sample code are written using 64Tass, which can be found at: https://sourceforge.net/projects/tass64/.
WDC816CC
Assembly seems to be the language of choice. But for those looking for something more portable, WDC provides a C-compiler. It's not necessarily more easy to use than assembly as Foenix programming is very close to the metal and it has some quirks. But if you have any skill using C on other platforms it will make you feel right at home and it does not require you to learn 65816 specific opcodes. Good assembly will always be the better performer however. You can even mix both if you want to.

Uploading with the Foenix IDE

To setup the Foenix IDE, not much is required beyond copying or cloning the repository from GitHub. There is no special installation required, and the EXE should just run.

To upload a BIN or HEX file with the Foenix IDE's user interface:

  1. Select the Tools > Uploader menu option. An "Uploader Window" will open.
  2. Select the COM port associated with the C256 debug interface in the drop down selector.
  3. Click the "Connect" button to establish a connection to the C256.
  4. Click the button labeled with ellipsis (...) to select the file to upload. By default, the uploader will show BIN files, but you can choose to search for HEX files instead.
  5. If you are uploading a BIN file, enter the starting destination address in the field "C256 Dest Address". This step is not needed with HEX files, since they have the destination addresses for their data included.
  6. Click the "Send Binary" button to start the upload process.

Foenix IDE Uploader.png

Uploading with the C256Mgr

To install the C256Mgr, you will need to make sure you have Python 3.7 installed as well as the PySerial library. You will need to edit the c256.ini file to set the default COM port and LBL file for your project, although both settings can be overridden by the command line options.

Once the manager is installed and configured correctly, there are two batch files that can be used to upload a file to the C256:

Binary
To upload a binary file, you use the UPLOAD.BAT file, which takes a BIN file and destination address (in hex) as arguments:

upload kernel.bin 380000

HEX File
To upload an Intel HEX file, you use the RUN256.BAT file, which takes just the HEX file as an argument:

run256 hello.hex

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