6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Apr 27, 2024 11:32 am

All times are UTC




Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Sat Nov 27, 2021 4:26 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 745
Location: Germany
Hi everyone, i've been struggling with CC65, and i think it's finally time to just go online and ask for help.
I once got to the point where it was actually able to output a binary, but i had to start from scratch... so i don't even have that anymore.

I'm running on Windows so i'm using the the Snapshot ZIP that has everything pre-compiled.
I added "CC65\bin" to PATH and also added new variables for "CC65_HOME", "CC65_BIN", "CC65_INC", and "LD65_LIB" which are all pointing to the correct folders.
I somewhat loosely followed the tutorial on here: https://cc65.github.io/doc/customizing.html
"loosely" because obviously the Memory Map is different and because the Interrupt vectors cannot be changed in my SBC, so the "vectors.s" file and the "VECTORS" Segment were unnecessary.

So my setup looks like this right now:
"SBC.cfg" has this in it:
Code:
#
# Proxy's 65C02 SBC v2
#
# Memory Map:
# 0x0000 - 0xF6FF - RAM (61.75kB)
# 0xF700 - 0xF7FF - IO  ( 0.25kB)
# 0xF800 - 0xFFFF - ROM ( 2.00kB)
#
# some ZP is used for the ROM, so start 32B into ZP
MEMORY {
   ZP:      start = $0020, size = $00E0, type = rw, define = yes;
   RAM:   start = $0200, size = $F500, type = rw, define = yes;
   IO:      start = $F700, size = $0100, type = rw, define = yes;
   ROM:   start = $F800, size = $0800, type = ro, 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 = RAM,   type = ro;
   ONCE:      load = RAM,   type = ro,   optional = yes;
   CODE:      load = RAM,   type = ro;
   RODATA:      load = RAM,   type = ro;
}

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 (4kB) for the Program
    __STACKSIZE__:  value = $1000, type = weak;
}

my "crt0.s", which like the Tutorial i put into a copy of the Supervison's .lib file, looks like this:
Code:
; ---------------------------------------------------------------------------
; crt0.s
; ---------------------------------------------------------------------------
;
; Startup code for cc65 (Proxy SBC v2)

.export _init, _exit
.import _main, _brk_isr, _irq_isr, _nmi_isr

.export __STARTUP__ : absolute = 1      ; Mark as startup
.import __RAM_START__, __RAM_SIZE__      ; Linker generated

.import copydata, zerobss, initlib, donelib

.include "zeropage.inc"

.PC02      ; Force 65C02 assembly mode

; ---------------------------------------------------------------------------
; Place the startup code in a special segment

.segment "STARTUP"

; ---------------------------------------------------------------------------
; Set up the Stack and some Flags

_init:
   LDX #$FF
   TXS         ; Initialize stack pointer to 0xFF
   CLD         ; Disable BCD Mode
   SEI         ; Disable Interrupts

; ---------------------------------------------------------------------------
; Set CC65 argument stack pointer

   LDA #<(__RAM_START__ + __RAM_SIZE__)
   LDX #>(__RAM_START__ + __RAM_SIZE__)
   STA sp
   STX sp+1

; ---------------------------------------------------------------------------
; Initialize memory storage

   JSR zerobss      ; Clear BSS segment
   ;JSR copydata   ; Initialize DATA segment, any reason for this? since Data will be in RAM anyways...
   JSR initlib      ; Run constructors

; ---------------------------------------------------------------------------
; Set up the Interrupt Vectors in ZP, as the ROM redirects all Interrupts to these
   LDA #>_brk_isr
   LDX #<_brk_isr
   STA $00
   STX $01
   
   LDA #>_irq_isr
   LDX #<_irq_isr
   STA $02
   STX $03
   
   LDA #>_nmi_isr
   LDX #<_nmi_isr
   STA $04
   STX $05
   

; ---------------------------------------------------------------------------
; Call main()

   JSR _main

; ---------------------------------------------------------------------------
; Back from main (this is also the _exit entry)

_exit:
   JSR donelib      ; Run destructors
   JMP ($FFF8)      ; Indirectly Jump to the Termination Function in ROM

the "Interrupt.s" file is pretty barren right now, for "i first want to get this running" reasons:
Code:
; ---------------------------------------------------------------------------
; Interrupt.s
; ---------------------------------------------------------------------------
;
; Interrupt handler
;

.export _brk_isr, _irq_isr, _nmi_isr

.segment "CODE"

.PC02      ; Force 65C02 assembly mode

; ---------------------------------------------------------------------------
; Break (BRK) Service Routine

_brk_isr:
   NOP
RTI

; ---------------------------------------------------------------------------
; Interrupt (IRQ) Service Routine

_irq_isr:
   NOP
RTI

; ---------------------------------------------------------------------------
; Non-Maskable Interrupt (NMI) Service Routine

_nmi_isr:
   NOP
RTI

and finally the C source file called "test.c" has this:
Code:
#include <stdint.h>
int main(){
   
   uint8_t T = 0;
   uint8_t i;
   
   for ( i = 0; i < 70; i++) {
      T = (T << 1) ^ (i + T); // Just something to get the Compiler working :p
   }
   
   return 0;
}

on a side note it's kinda annoying how this compiles fine:
Code:
uint8_t i;
for (i = 0; i < 70; i++) {}

But this doesn't:
Code:
for (uint8_t i = 0; i < 70; i++) {}

like why?

anyways, I threw the "crt0.s" into the renamed "SBC.lib" file with these commands:
Code:
CA65 --cpu 65C02 -i crt0.s
AR65 r SBC.lib crt0.o

and then i try to Compile, Assemble, and Link everything else.
I split everything into seperate folders so i can order it better:
"Temp" is used to contain temporary files that are supposed to get deleted after successfully linking everything
"Config" holds the .cfg and .lib files, and "crt0.s"
"Include" has the "Interrupt.s" file and in the future various hardware drivers.
Code:
CC65 -t none -O --cpu 65C02 -o Temp\test.s test.c
CA65 --cpu 65C02 -o Temp\test.o Temp\test.s
LD65 -C Config\SBC.cfg -m main.map -v Include\Interrupt.o Temp\test.o Config\SBC.lib


but at this point the Linker is always just throwing "Unresolved External" Errors, with my old setup it would always throw those errors when i include some hardware drivers, but now it's also throwing them from things that should be working on their own:
Code:
crt0.s:75: Error: Unresolved external 'DONELIB'
crt0.s:46: Error: Unresolved external 'INITLIB'
crt0.s:38: Error: Unresolved external 'SP'
crt0.s:39: Error: Unresolved external 'SP'
crt0.s:44: Error: Unresolved external 'ZEROBSS'
crt0.s:69: Error: Unresolved external '_MAIN'
LD65: Error: 5 unresolved external(s) found - cannot create output file

to me this sounds like it cannot link crt0.o to test.o, but why? the PATHs are correct, and i gave it all the files it should (hopefully) need in the same order as the tutorial.
So what am i missing? :?
I added a zip file containing everything i made so far.


Attachments:
CC65_Setup.zip [225.25 KiB]
Downloaded 42 times
Top
 Profile  
Reply with quote  
PostPosted: Sat Nov 27, 2021 9:04 pm 
Offline

Joined: Sat Aug 14, 2021 6:04 pm
Posts: 10
For some reason the crt0.o you generated and included in your package is looking for these names in ALL CAPS, the way you see them in the error message. If you do "od65 --dump-all crt0.o" you can see them. But the symbols in the library are lowercase. If you do "ar65 x SBC.lib zerobss.o" and then "od65 --dump-all zerobss.o" you'll see that "zerobss" is exported as lowercase. I'm not sure how you got here. Maybe you assembled crt0.s with case sensitivity disabled?


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 28, 2021 1:20 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 745
Location: Germany
I did assemble it without case-sensitivity, that's what the "-i" argument does in the "CA65 --cpu 65C02 -i crt0.s" command

after a bit of experimenting it turns out that assembling everything without case-sensitivity is what's breaking the linker for some reason.
when i re-assemble everything without the "-i" argument it works... so it seems that disabling case-sensitivity makes it MORE case-sensitive than without, to the point where it cannot even link externals with the same case like with "zerobss" (which is lowercase in crt0.s but the linker says it's uppercase for some reason).
Same with my custom Serial functions, when i disabled case-sensitivity the Linker was unable to link "_ftprintc" in in the C file (or rather the .s file generated from the C file) with "_ftprintc" in the "serial.s" file, despite both being completely identical down to the case.

so that makes me think it's a bug in CC65... but if that were the case how did noone else stumble upon that before?


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 28, 2021 4:56 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 745
Location: Germany
either way i'm glad i got it working now, I was even able to add my SBC's Custom header.

My SBC loads programs via Serial, so I designed an 8 byte long Header that is expected to be at the start of the file it's loading.
the first 2 Bytes are a hardwired Magic Number ($65 followed by $02),
the next 2 Bytes (Little Endian) specify where the program should be loaded into Memory,
the 2 Bytes (Little Endian) after that say how many Bytes should be loaded from Serial (basically: "file size" - "header" - 1)
and the final 2 Bytes tell the ROM where to jump to after the Loading has finished

I used sim6502's CC65 as an example on how to do this. getting the total program size was the hardest part but at the same time it was pretty straight forward:
Get the address where the last segment ends, and subtract the starting address of the first segment from that.

here's what the "exehdr.s" file looks like:
Code:
; ---------------------------------------------------------------------------
; exehdr.s
; ---------------------------------------------------------------------------
;
; Header Data for the output file

.export __EXEHDR__ : absolute = 1       ; Linker referenced
.import _init, __RAM_START__, __CODE_LOAD__, __CODE_SIZE__

.PC02      ; Force 65C02 assembly mode

.segment "EXEHDR"

.byte $65, $02                                       ; "6502" Magic Number
.addr __RAM_START__                                    ; Address to Load Program into
.word ((__CODE_LOAD__ + __CODE_SIZE__) - 1) - __RAM_START__      ; Program Size - 1
.addr _init                                          ; Address to Jump to after Loading


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 28, 2021 5:59 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1926
Location: Sacramento, CA, USA
Proxy wrote:
... the first 2 Bytes are a hardwired Magic Number ($65 followed by $02)

Big-endian? For shame ... :wink:
[Well, since it's BCD, I guess you deserve a pass ...]

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 28, 2021 8:40 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 745
Location: Germany
barrym95838 wrote:
Proxy wrote:
... the first 2 Bytes are a hardwired Magic Number ($65 followed by $02)

Big-endian? For shame ... :wink:
[Well, since it's BCD, I guess you deserve a pass ...]

it's only in that order so you can see it in the binary as "6502" instead of "0265" :p
i also just noticed that you can represent some 65xx variants in hex as well.
3 bytes for 06-5C-02 and 65-C8-16, and even the 65-CE-02

maybe i'll use that in the future to detect if the program can run on the system (like trying to load a 65C816 program on a 65C02 SBC)


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 28, 2021 5:01 pm 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
Glad you got it running!
Quote:
on a side note it's kinda annoying how this compiles fine:
Code:
uint8_t i;
for (i = 0; i < 70; i++) {}

But this doesn't:
Code:
for (uint8_t i = 0; i < 70; i++) {}

like why?
If you look at the CC65 documentation under 2.2 for the description of --standard, you'll see that CC65 doesn't support all of C89 or C99. Declaring a variable inside a for loop wasn't introduced until C99 I think. You can try compiling with --standard c99 but even then it may not be implemented. Another one that got me with CC65 was declaring a function without arguments such as "int foo()" when the compiler was only accepting "int foo(void)" as in C89.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 21 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: