+
+The cc65 toolset provides a set of pre-defined libraries that allow the
+user to target the executable image to a variety of hardware platforms.
+In addition, the user can create a customized environment so that the
+executable can be targeted to a custom platform. The following
+instructions provide step-by-step instructions on how to customize the
+toolset for a target that is not supported by the standard cc65
+installation.
+
+The platform used in this example is a Xilinx Field Programmable Gate
+Array (FPGA) with an embedded 65C02 core. The processor core supports
+the additional opcodes/addressing modes of the 65SC02, along with the
+STP and WAI instructions. These instructions will create a set of files
+to create a custom target, named SBC, for
+
+The targeted system uses block RAM contained on the XILINX FPGA for the
+system memory (both RAM and ROM). The block RAMs are available in
+various aspect ratios, and they will be used in this system as 2K*8
+devices. There will be two RAMs used for data storage, starting at
+location $0000 and growing upwards. There will be one ROM (realized as
+initialized RAM) used code storage, starting at location $FFFF and
+growing downwards.
+
+The cc65 toolset requires a memory configuration file to define the
+memory that is available to the cc65 run-time environment, which is
+defined as follows:
+
+ ZEROPAGE: Data in page 0, defined by ZP as starting at $0 with length $100
+ DATA: Initialized data that can be modified by the program, stored in RAM
+ BSS: Uninitialized data stored in RAM (used for variable storage)
+ HEAP: Uninitialized C-level heap storage in RAM, optional
+ STARTUP: The program initialization code, stored in ROM
+ INIT: The code needed to initialize the system, stored in ROM
+ CODE: The program code, stored in ROM
+ RODATA: Initialized data that cannot be modified by the program, stored in ROM
+ VECTORS: The interrupt vector table, stored in ROM at location $FFFA
+
+A note about initialized data: any variables that require an initial
+value, such as external (global) variables, require that the initial
+values be stored in the ROM code image. However, variables stored in
+ROM cannot change; therefore the data must be moved into variables that
+are located in RAM. Specifying run = RAM as part of
+the DATA segment definition will indicate that those variables will
+require their initialization value to be copied via a call to the
+copydata routine in the startup assembly code. In addition,
+there are system level variables that will need to be initialized as
+well, especially if the heap segment is used via a C-level call to
+malloc ().
+
+The final section of the definition file contains the data constructors
+and destructors used for system startup. In addition, if the heap is
+used, the maximum C-level stack size needs to be defined in order for
+the system to be able to reliably allocate blocks of memory. The stack
+size selection must be greater than the maximum amount of storage
+required to run the program, keeping in mind that the C-level subroutine
+call stack and all local variables are stored in this stack. The
+FEATURES section defines the required constructor/destructor
+attributes and the SYMBOLS section defines the stack size. The
+constructors will be run via a call to initlib in the startup
+assembly code and the destructors will be run via an assembly language
+call to donelib during program termination.
+
+
+
+In the cc65 toolset, a startup routine must be defined that is executed
+when the CPU is reset. This startup code is marked with the STARTUP
+segment name, which was defined in the system configuration file as
+being in read only memory. The standard convention used in the
+predefined libraries is that this code is resident in the crt0 module.
+For this custom system, all that needs to be done is to perform a little
+bit of 6502 housekeeping, set up the C-level stack pointer, initialize
+the memory storage, and call the C-level routine main ().
+The following code was used for the crt0 module, defined in the file
+"crt0.s":
+
+
+
+The next step in customizing the cc65 toolset is creating a run-time
+library for the targeted hardware. The easiest way to do this is to
+modify a standard library from the cc65 distribution. In this example,
+there is no console I/O, mouse, joystick, etc. in the system, so it is
+most appropriate to use the simplest library as the base, which is for
+the Watara Supervision and is named "supervision.lib" in the
+lib directory of the distribution.
+
+The only modification required is to replace the crt0 module in
+the supervision.lib library with custom startup code. This is simply
+done by first copying the library and giving it a new name, compiling
+the startup code with ca65, and finally using the ar65 archiver to
+replace the module in the new library. The steps are shown below:
+
+
+
+For this system, the CPU is put into a wait condition prior to allowing
+interrupt processing. Therefore, the interrupt service routine is very
+simple: return from all valid interrupts. However, as mentioned
+before, the BRK instruction is used to indicate a software fault, which
+will call the same interrupt service routine as the maskable interrupt
+signal IRQ. The interrupt service routine must be able to tell the
+difference between the two, and act appropriately.
+
+The interrupt service routine shown below includes code to detect when a
+BRK instruction has occurred and stops the CPU from further processing.
+The interrupt service routine is in a file named
+"interrupt.s".
+
+ $FFFA - $FFFB: NMI interrupt vector (low byte, high byte)
+ $FFFC - $FFFD: Reset vector (low byte, high byte)
+ $FFFE - $FFFF: IRQ/BRK interrupt vector (low byte, high byte)
+
+using the .addr assembler directive. The contents of the file are:
+
+
+
+The cc65 instruction set only supports the WAI (Wait for Interrupt) and
+STP (Stop) instructions when used with the 65816 CPU (accessed via the
+--cpu command line option of the ca65 macro assembler). The 65C02 core
+used in this example supports these two instructions, and in fact the
+system benefits from the use of both the WAI and STP instructions.
+
+In order to use the WAI instruction in this case, a C routine named
+"wait" was created that consists of the WAI opcode followed by
+a subroutine return. It was convenient in this example to put the IRQ
+interrupt enable in this subroutine as well, since interrupts should
+only be enabled when the code is in this wait condition.
+
+For both the WAI and STP instructions, the assembler is
+"fooled" into placing those opcodes into memory by inserting a
+single byte of data that just happens to be the opcode for those
+instructions. The assembly code routines are placed in a file, named
+"wait.s", which is shown below:
+
+
+
+Oftentimes, it can be advantageous to create small application helpers
+in assembly language to decrease codespace and increase execution speed
+of the overall program. An example of this would be the transfer of
+characters to a FIFO (
+
+The following short example demonstrates programming in C using the cc65
+toolset with a custom run-time environment. In this example, a Xilinx
+FPGA contains a UART which is connected to a 65c02 processor with FIFO
+(
+
+The following commands will create a ROM image named "a.out"
+that can be used as the initialization data for the Xilinx Block RAM
+used for code storage:
+
+
+MEMORY {
+ ZP: start = $0, size = $100, type = rw, define = yes;
+ RAM: start = $200, size = $0E00, define = yes;
+ ROM: start = $F800, size = $0800, file = %O;
+}
+
+SEGMENTS {
+ ZEROPAGE: load = ZP, type = zp, define = yes;
+ DATA: load = ROM, type = rw, define = yes, run = RAM;
+ BSS: load = RAM, type = bss, define = yes;
+ HEAP: load = RAM, type = bss, optional = yes;
+ STARTUP: load = ROM, type = ro;
+ INIT: load = ROM, type = ro, optional = yes;
+ CODE: load = ROM, type = ro;
+ RODATA: load = ROM, type = ro;
+ VECTORS: load = ROM, type = ro, start = $FFFA;
+}
+
+FEATURES {
+ CONDES: segment = STARTUP,
+ type = constructor,
+ label = __CONSTRUCTOR_TABLE__,
+ count = __CONSTRUCTOR_COUNT__;
+ CONDES: segment = STARTUP,
+ type = destructor,
+ label = __DESTRUCTOR_TABLE__,
+ count = __DESTRUCTOR_COUNT__;
+}
+
+SYMBOLS {
+ # Define the stack size for the application
+ __STACKSIZE__: value = $0200, weak = yes;
+}
+
+; ---------------------------------------------------------------------------
+; crt0.s
+; ---------------------------------------------------------------------------
+;
+; Startup code for cc65 (Single Board Computer version)
+
+.export _init, _exit
+.import _main
+
+.export __STARTUP__ : absolute = 1 ; Mark as startup
+.import __RAM_START__, __RAM_SIZE__ ; Linker generated
+
+.import copydata, zerobss, initlib, donelib
+
+.include "zeropage.inc"
+
+; ---------------------------------------------------------------------------
+; Place the startup code in a special segment
+
+.segment "STARTUP"
+
+; ---------------------------------------------------------------------------
+; A little light 6502 housekeeping
+
+_init: LDX #$FF ; Initialize stack pointer to $01FF
+ TXS
+ CLD ; Clear decimal mode
+
+; ---------------------------------------------------------------------------
+; Set cc65 argument stack pointer
+
+ LDA #<(__RAM_START__ + __RAM_SIZE__)
+ STA sp
+ LDA #>(__RAM_START__ + __RAM_SIZE__)
+ STA sp+1
+
+; ---------------------------------------------------------------------------
+; Initialize memory storage
+
+ JSR zerobss ; Clear BSS segment
+ JSR copydata ; Initialize DATA segment
+ JSR initlib ; Run constructors
+
+; ---------------------------------------------------------------------------
+; Call main()
+
+ JSR _main
+
+; ---------------------------------------------------------------------------
+; Back from main (this is also the _exit entry): force a software break
+
+_exit: JSR donelib ; Run destructors
+ BRK
+
+; ---------------------------------------------------------------------------
+; interrupt.s
+; ---------------------------------------------------------------------------
+;
+; Interrupt handler.
+;
+; Checks for a BRK instruction and returns from all valid interrupts.
+
+.import _stop
+.export _irq_int, _nmi_int
+
+.segment "CODE"
+
+.PC02 ; Force 65C02 assembly mode
+
+; ---------------------------------------------------------------------------
+; Non-maskable interrupt (NMI) service routine
+
+_nmi_int: RTI ; Return from all NMI interrupts
+
+; ---------------------------------------------------------------------------
+; Maskable interrupt (IRQ) service routine
+
+_irq_int: PHX ; Save X register contents to stack
+ TSX ; Transfer stack pointer to X
+ PHA ; Save accumulator contents to stack
+ INX ; Increment X so it points to the status
+ INX ; register value saved on the stack
+ LDA $100,X ; Load status register contents
+ AND #$10 ; Isolate B status bit
+ BNE break ; If B = 1, BRK detected
+
+; ---------------------------------------------------------------------------
+; IRQ detected, return
+
+irq: PLA ; Restore accumulator contents
+ PLX ; Restore X register contents
+ RTI ; Return from all IRQ interrupts
+
+; ---------------------------------------------------------------------------
+; BRK detected, stop
+
+break: JMP _stop ; If BRK is detected, something very bad
+ ; has happened, so stop running
+
+; ---------------------------------------------------------------------------
+; vectors.s
+; ---------------------------------------------------------------------------
+;
+; Defines the interrupt vector table.
+
+.import _init
+.import _nmi_int, _irq_int
+
+.segment "VECTORS"
+
+.addr _nmi_int ; NMI vector
+.addr _init ; Reset vector
+.addr _irq_int ; IRQ/BRK vector
+
+; ---------------------------------------------------------------------------
+; wait.s
+; ---------------------------------------------------------------------------
+;
+; Wait for interrupt and return
+
+.export _wait, _stop
+
+; ---------------------------------------------------------------------------
+; Wait for interrupt: Forces the assembler to emit a WAI opcode ($CB)
+; ---------------------------------------------------------------------------
+
+.segment "CODE"
+
+.proc _wait: near
+
+ CLI ; Enable interrupts
+.byte $CB ; Inserts a WAI opcode
+ RTS ; Return to caller
+
+.endproc
+
+; ---------------------------------------------------------------------------
+; Stop: Forces the assembler to emit a STP opcode ($DB)
+; ---------------------------------------------------------------------------
+
+.proc _stop: near
+
+.byte $DB ; Inserts a STP opcode
+
+.endproc
+
+; ---------------------------------------------------------------------------
+; rs232_tx.s
+; ---------------------------------------------------------------------------
+;
+; Write a string to the transmit UART FIFO
+
+.export _rs232_tx
+.exportzp _rs232_data: near
+
+.define TX_FIFO $1000 ; Transmit FIFO memory location
+
+.zeropage
+
+_rs232_data: .res 2, $00 ; Reserve a local zero page pointer
+
+.segment "CODE"
+
+.proc _rs232_tx: near
+
+; ---------------------------------------------------------------------------
+; Store pointer to zero page memory and load first character
+
+ sta _rs232_data ; Set zero page pointer to string address
+ stx _rs232_data+1 ; (pointer passed in via the A/X registers)
+ ldy #00 ; Initialize Y to 0
+ lda (_rs232_data) ; Load first character
+
+; ---------------------------------------------------------------------------
+; Main loop: read data and store to FIFO until \0 is encountered
+
+loop: sta TX_FIFO ; Loop: Store character in FIFO
+ iny ; Increment Y index
+ lda (_rs232_data),y ; Get next character
+ bne loop ; If character == 0, exit loop
+
+; ---------------------------------------------------------------------------
+; Append CR/LF to output stream and return
+
+ lda #$0D ; Store CR
+ sta TX_FIFO
+ lda #$0A ; Store LF
+ sta TX_FIFO
+ rts ; Return
+
+.endproc
+
+#define FIFO_DATA (*(unsigned char *) 0x1000)
+#define FIFO_STATUS (*(unsigned char *) 0x1001)
+
+#define TX_FIFO_FULL (FIFO_STATUS & 0x01)
+#define RX_FIFO_EMPTY (FIFO_STATUS & 0x02)
+
+extern void wait ();
+extern void __fastcall__ rs232_tx (char *str);
+
+int main () {
+ while (1) { // Run forever
+ wait (); // Wait for an RX FIFO interrupt
+
+ while (RX_FIFO_EMPTY == 0) { // While the RX FIFO is not empty
+ if (FIFO_DATA == '?') { // Does the RX character = '?'
+ rs232_tx ("Hello World!"); // Transmit "Hello World!"
+ } // Discard any other RX characters
+ }
+ }
+
+ return (0); // We should never get here!
+}
+