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.
Interesting Article. Hoping that you will continue posting an article having a useful information. Antigen Test Ket
ReplyDelete