Wednesday, January 11, 2012

Coding in 'C' with cc65 for custom 6502 platform.

Hello folks!

In this article just few remarks or should I say errata to previously published article about coding in 'C' and compiling/linking programs with cc65/cl65 to be loaded and ran from RAM on my MKHBC-8-R1 computer:

1) The memory configuration previously published in my blog has a flaw resulting in program not being properly linked (basically linker restarted ORG address several times to be at the beginning of my defined RAM memory). Some code configurations worked, however I noticed that with array (or any other variables) placed at the global section past constant data initialization, resulting code did not work. I went to debugging and realized that the linked code is all at wrong addresses.
Apart from ZP (zero page) section, separate non-overlapping RAM address ranges must be designated for DATA/BSS/HEAP, STARTUP/INIT/CODE and RODATA. Here is new config file mkhbcoslib.cfg:

MEMORY {
    ZP:     start    =   $20,     size =  $E0,    type   = rw, define = yes;
    RAMC:   start    =   $0400,   size =  $0400,  fill = yes;
    RAMD:   start    =   $0800,   size =  $0400,  fill = yes;
    RAM:    start    =   $0C00,   size =  $0400,  define = yes;
}

SEGMENTS {
    ZEROPAGE:  load = ZP,  type = zp,  define   = yes;
    DATA:      load = RAM, type = rw,  define   = yes;
    BSS:       load = RAM, type = bss, define   = yes;
    HEAP:      load = RAM, type = bss, optional = yes;
    STARTUP:   load = RAMC, type = rw;
    INIT:      load = RAMC, type = rw,  optional = yes;
    CODE:      load = RAMC, type = rw;
    RODATA:    load = RAMD, type = rw;
}

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;
}


With above configuration, mentioned sections will be consolidated with ORG addresses at adequate designated RAM sections starting addresses. Previously linker placed these segments several times into the same memory space, resulting in garbage instead of program.
Statement fill=yes is necessary for RAMC and RAMD memory sections in order for the resulting binary image to be continuous, so that my bin2hex conversion utility would produce nice loading script for hyper terminal/MKHBCOS user interface.

2) Due to above issues, the initialization routine in crt0.s - the start up code implementation for cc65 to properly run programs on custom platform, did not work. With the corrections above done, they could be un-commented and they work now. The resulting linked program starts with the STARTUP section so the load address is the execute address at the same time. Here is the corrected crt0.s code, which I had to reload to my custom cc65 library mkhbcos.lib with ar65 program (see cc65 documentation for details):

; ---------------------------------------------------------------------------
;
; File:        crt0.s
; Author:    Marek Karcz
;
; Original work: cc65 documentation/customization tutorial
;
; Purpose:
;
;     Startup code for cc65 (MKHBC-8-R1 version, to run under MKHBCOS,
;     M.O.S. derivative).
;
; Revision history:
;
; 2012-01-10:
;    Initial revision.
;
; 2012-01-11:
;   Initialization of memory storage enabled.
;
; ---------------------------------------------------------------------------

.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

; This is the entry point of compiled and linked program when ran under
; M.O.S. Look in the map file for beginning of STARTUP segment.
; Provide that address to 'x' command (remember to use lower letters).

_init:   
        ; NOTE: This part is already done by MKHBCOS
          ;LDX     #$FF                 ; Initialize stack pointer to $01FF
          ;TXS
          ;CLD                          ; Clear decimal mode

; ---------------------------------------------------------------------------
; Set cc65 argument stack pointer
; Must run, otherwise stack pointer will point to $ffff.

          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
          rts


The whole program can be built with this batch script under Windows:

cl65 -t none --cpu 6502 --config mkhbcoslib.cfg -l -m hello.map hello.c mkhbcos_serialio.s mkhbcos.lib
bin2hex -f hello -o hello_prg.txt -w 1024 -x 1024 -s
pause prompt



I also extended my library by several I/O functions, which are basically my own implementation of the stdio routines, that work with serial port on my platform, using MKHBCOS routines. I mostly had to just properly handle the parameters passing and return values and call MKHBCOS API functions to perform the actual job:

;------------------------------------------------------------------
;
; File:     mkhbcos_serialio.s
; Author:    Marek Karcz
; Purpose:    Implement's serial console functions for MKHBCOS to be
;            called from C programs (cc65).
;
; Revision history:
;    2012-01-10:
;        Initial revision.

;  
;   2012-01-11:
;       Added several I/O routines.

;
;------------------------------------------------------------------

; M.O.S. API defines (kernal)

.define        mos_PromptLine    $80
.define        mos_PromptLen    $D0
.define     mos_StrPtr        $E0
.define        tmp_zpgPt        $F6
.define        mos_CallGetCh    $FFED
.define        mos_CallGets    $FFF3
.define        mos_CallPutCh    $FFF0
.define        mos_CallPuts    $FFF6

.setcpu    "6502"
.import ldaxysp,pushax,popax,pusha,popa
.import incsp2

.define        sp                $20

; code

.export _mos_puts,_puts,_putchar,_gets,_getchar,_getc,_fgetc
;,_read

.segment "CODE"

.proc _mos_puts: near

.segment "CODE"

    ldy #$01
    jsr ldaxysp
    sta mos_StrPtr
    stx mos_StrPtr+1
    jsr mos_CallPuts
    jsr incsp2
    rts

.endproc
   
.proc _puts: near

.segment "CODE"

    sta mos_StrPtr
    stx mos_StrPtr+1
    jsr mos_CallPuts
    lda #$00
    tax
    rts

.endproc

.proc _putchar: near

.segment "CODE"

    jsr mos_CallPutCh
    lda #$00
    tax
    rts

.endproc

.proc _gets: near

.segment "CODE"

    sta tmp_zpgPt
    stx tmp_zpgPt+1
    jsr mos_CallGets    ; entered string stored in mos_PromptLine

; copy string to the return pointer location

    ldy #$00
   
gets_l001:

    lda mos_PromptLine,y
    sta (tmp_zpgPt),y
    beq gets_end
    iny
    bne gets_l001

gets_end:

    lda tmp_zpgPt
    ldx tmp_zpgPt+1
    rts

.endproc

.proc _getchar: near

.segment "CODE"

    jsr mos_CallGetCh
    ldx #$00
    rts

.endproc

.proc _getc: near

.segment "CODE"

    jsr mos_CallGetCh
    ldx #$00
    rts

.endproc

.proc _fgetc: near

.segment "CODE"

    jsr mos_CallGetCh
    ldx #$00
    rts

.endproc

Added some code to hello.c to test some of the new functions:

/*
 *
 * File: hello.c
 * Purpose: Hello World program to run under MKHBCOS
 *          (M.O.S. derivative).
 * Author: Marek Karcz
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern void mos_puts(char *s);

char hello[] = "Hello World!\n\r";
char buf[256];

void my_puts (char *s)
{
    mos_puts(s);
}

int main (void)
{
    my_puts(hello);
    mos_puts("It sure feels good to code in 'C' for g'old MOS 6502!\n\r");
    puts("Enter text:");
    gets(buf);
    putchar('\n');
    putchar('\r');
    puts("You entered:");
    puts(buf);
    putchar('\n');
    putchar('\r');

    return 0;
}


The results were satisfying:



That is it for today. Thank you for reading.

M.K.