Using sim65 To Test ASM

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
segaloco
Posts: 286
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Using sim65 To Test ASM

Post by segaloco »

Figured I'd make an informational post since I had to piece this together from documentation and just some plain old experimentation. Sim65 is the 6502 simulator packaged with the cc65 development suite, and at least in my experience, a tad bit more tinkering was required than just what is described at https://cc65.github.io/doc/sim65.html to get a working test built using just asm.

So to start, the linker script is found at /usr/share/cc65/cfg/sim6502.cfg on my install, local install paths may vary. This configuration is requested by passing "-t sim6502" on the linker command line. This is documented above, but what I found I needed to also do is include the sim6502.lib file for, among other things, the "exit" routine described therein.

Additionally, the documentation indicates that a 12 byte header is needed to properly assemble a test case, but doesn't indicate directly that this must be in a segment "EXEHDR" and labeled "__EXEHDR__". Attempting to link without this will spit out the necessary warning and error about the missing segment and missing export respectively. In addition, suggested, but not required, is that the main entry point should be in a "STARTUP" segment.

Pulling it all together, a minimal test that actually assembles, links, and runs correctly looks something like:

Code: Select all

	.segment "EXEHDR"
        .export __EXEHDR__
__EXEHDR__:
        .byte   "sim65"	; magic number
        .byte   2	; simulator version: 2 = current
        .byte   0	; CPU version: 0 = 6502, 1 = 65c02
        .byte   $FF	; initial SP
        .addr   _main   ; load address
        .addr   _main   ; start address (these are the same if _main is first in STARTUP)

        .segment "STARTUP"
        .export _main
_main:
        lda     status
        jmp     exit

        .segment "RODATA"
        .export status
status:
        .byte   0
Presuming this is named test.s, the command lines

Code: Select all

ca65 -U -o test.o test.s
ld65 -t sim6502 -o test.bin test.o sim6502.lib
sim65 test.bin
echo $?
Should return whatever value is set at "status".

This was a little tricky for a few reasons. The documentation describes the header format, but does not note that you have to create this header yourself nor what segment and label it must be created under. Perhaps this is to be gleaned from documentation of other parts of the cc65 toolchain, but in any case, isn't included right there in the sim65 documentation. Additionally the target does require inclusion of the library explicitly. I'm not sure if it's common practice to include a <foo>.lib any time you request "-t <foo>" with ld65, but in any case, this step is crucial to getting the exit routine in place. The documentation implies you can also just return from _main and the byte in the accumulator will be your return value, but if I don't use exit, I get an illegal opcode error instead. Not sure if this implies the linking above also expects you to manually go pull a crt0 in, but in any case, it isn't clear from the sim65 documentation alone.

Just to review, to use this in the narrowest sense:
  • Add an EXEHDR segment with an __EXEHDR__ label followed by the data described in Section 5
  • Place _main first in the STARTUP segment (or ensure the load address is the first symbol loaded into the MAIN memory segment)
  • Ensure that main exits with a "jmp exit" with desired return value in a
  • Include sim6502.lib in the linker call
Note too, _main can be named something else, presumably this also derives from some expectation that a crt0 be present, but if attempting to work with pure assembly, this is just extraneous stuff, and can be avoided with these steps. In any case, this is what worked for me.
User avatar
rainwarrior
Posts: 8736
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Using sim65 To Test ASM

Post by rainwarrior »

Here's a simpler, more minimal example:

Code: Select all

.export _main

_main:
	; do your test
	; return from main with error code in A
	lda #0
	rts

.import exit
	; instead of returning from main, we can use exit to return the error code.
	lda #0
	jmp exit

;build and run test:
; ca65 -o example.o example.s
; ld65 -t sim6502 -o example.prg example.o sim6502.lib
; sim65 example.prg
The EXEHDR exists already in sim6502.lib, so you shouldn't need to recreate it yourself. It is not directly documented because it's considered an internal part of sim65, and may be subject to change.

Just link with the sim6502 target (-t) and the library sim6502.lib, define and export _main, and return from main with your error code in A.

If you need to customize things, you might override the linker CFG provided by -t sim6502, and you can use cfg/sim6502.cfg as a template, but for most purposes this shouldn't be necessary. The sim6502 config does have a ZEROPAGE segment you can use if you need to test ZP behaviour.

We can also replace "6502" with "65c02" if we want to test that CPU variation.
User avatar
rainwarrior
Posts: 8736
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Using sim65 To Test ASM

Post by rainwarrior »

Here's a similar minimal C example.

Code: Select all

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

int main()
{
	printf("Hello world!\n");
	// sim65 C applications can use stdio.h standard I/O functions,
	// take keyboard input, read/write files, etc.
	// return the desired error code from main, but note that only the low byte is used.
	return 0;
	// stdlib.h exit may also be used to return immediately with error code
	// exit(0);
}

//build and test:
// cc65 -o example.c.s example.c
// ca65 -o example.c.o example.c.s
// ld65 -t sim6502 -o example.prg example.c.o sim6502.lib
// sim65 example.prg
You can also combine C and assembly if needed, in the usual way. This can be helpful if you want to mix C I/O (e.g. diagnostic printf) with assembly tests.

Your main can also take command line arguments if you need that. I used this for the 6502vdelay project combined with the -c cycle counting argument to verify the correct timing for the delay routines.
User avatar
segaloco
Posts: 286
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Re: Using sim65 To Test ASM

Post by segaloco »

Huh...well I guess I was today years old when I learned overriding a symbol in a library doesn't cause a multiple definitions error on linking, it just takes the locally defined version of the symbol. That would explain why linking with sim6502.lib didn't bark at me for redefining something it already contained. You learn something new every day...thanks for the corrections rainwarrior, I defined the symbols before I figured out you had to explicitly go link the library so it just silently took my definitions making me think there were none in sim6502.lib due to lack of collision.
User avatar
rainwarrior
Posts: 8736
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Using sim65 To Test ASM

Post by rainwarrior »

Object modules within libraries are "lazy" linked, so the only time anything gets included from a lib is when an earlier object or the CFG has an .import that isn't yet resolved.

It's pretty convenient for the platform libraries, because as a result it only includes things you actually use.

It's maybe a bit inconvenient when you have an actual duplicate symbol though... it can be difficult to know where the error is coming from.

Anyway, I decided to submit minimal examples like above to the official documentation: Github PR
User avatar
segaloco
Posts: 286
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Re: Using sim65 To Test ASM

Post by segaloco »

Wow yeah checked all the way back to Sixth Edition UNIX, indeed even if the symbol is defined in a separate compilation unit, you get no linker collision if the name is defined in any locally linked object at all.

Code: Select all

% cat putchar.c
putchar(a)
{
        return a;
}
% cat main.c
main()
{
        putchar('a');
        return 0;
}
% cc main.c putchar.c
main.c:
putchar.c:
% ./a.out
% cc main.c
% ./a.out
a%
With putchar.c linked, the libc version is overridden and no character print occurs. With main.c linked in isolation, it grabs the library version and uses that. I'm glad this has never come up in my asm/C experience in the past honestly, quite frustrating that you can override a symbol a library is expected to provide and get no notice. I see where it'd be helpful but yeah, it can also very easily mask an unfortunate naming collision with little to no notice. Well glad I know that now, never override an expected library function name, it will mask it without warning (aside from header includes barking about redefined prototypes, which doesn't come into play with asm and older C environments)
Oziphantom
Posts: 1570
Joined: Tue Feb 07, 2017 2:03 am

Re: Using sim65 To Test ASM

Post by Oziphantom »

if you are looking for a more complete testing solution you might enjoy https://github.com/martinpiper/BDD6502 it lets you load files and then you make tests external to it using Gerkin/Cucumber, this allows you to fully make use of memory and not have to support the CRT memory map. It also means your test are not assembled into something that also hosts the code and it can load a direct final binary without any modifications to the state of the registers or machine. Tracing similar to VICE 'chis' can be enabled to help debug failing tests with ease and speed.
Post Reply