Jump to content

My ROM Works!


Scott Robison
 Share

Recommended Posts

Is it just me, or is finding good samples of ca65 syntax really difficult?

But I have, after a few hours, figured out how to write a 65c02 hello world application that uses JSRFAR to call the kernal, assembled it as a ROM image, appended it to the "stock roms", and poked a 7 byte stub at $0400, and used SYS 1024 to run my "ROM". Yay!

Now to do something "useful" with this...

  • Like 2
Link to comment
Share on other sites

I just yesterday created a new macro to simplify JSRFAR on ca65:

Quote

 

JSRFAR_kernal_addr = $FF6E

.macro JSRFAR far_sr_addr, bank
    jsr JSRFAR_kernal_addr
    .addr far_sr_addr
    .byte bank
.endmacro

 

Then you can just call JSRFAR like a function in your code, where $ABCD is the subroutine address and it sits in bank 42:

Quote

JSRFAR $ABCD, 42

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

31 minutes ago, SlithyMatt said:

JSRFAR_kernal_addr = $FF6E

.macro JSRFAR far_sr_addr, bank
    jsr JSRFAR_kernal_addr
    .addr far_sr_addr
    .byte bank
.endmacro

 

I've seen this kind of construct go by in several people's code lately. Just want to make sure my understanding of what's going on is correct:

Normally, jsr pushes the PC onto the stack - which would be PC-> .addr far_sr_addr  - but the code in JSRFAR (or any routine doing this trick) will at some point pull the PC off of the stack, use that as the address to read some arguments from, bump this value up by however many bytes of arguments were read (in this case, 3) and then push the new value back on the stack as if it were the original value pushed by JSR. This way, whenever the routine called by JSRFAR executes RTS, it doesn't come back and try to execute the .addr far_sr_addr as a command.

Essentially, this is a "pass by value" way to pass parameters to a function - it's "by value" because the values are copied to a new location and those copies are used. In this case, the new location happens to be right in the middle of the code, and the values are a pointer (to a function) and an unsigned char.


Is that about right?

Edited by ZeroByte
  • Like 1
Link to comment
Share on other sites

2 hours ago, SlithyMatt said:

I just yesterday created a new macro to simplify JSRFAR on ca65:

Then you can just call JSRFAR like a function in your code, where $ABCD is the subroutine address and it sits in bank 42:

 

Since my code is executing out of a ROM, I can't JSRFAR into the kernal (I don't think) directly, so I copied the bjsrfar stub from the BASIC ROM so that I had my own entry point in my own ROM space.

Really, most of my time was spent puzzling out how to get the right ca65 syntax for segments and configuration file. I have years of experience with assembly language on 6502 & x86, just not ca65.

I do look forward to utilizing the macro features Real Soon Now(TM).

Link to comment
Share on other sites

2 minutes ago, Scott Robison said:

I can't JSRFAR into the kernal (I don't think) directly

Correct, JSRFAR only works with the RAM banks. My repo here shows you how to do some other config magic, and it's going to be the subject of my next vlog coming out Thursday.

Edited by SlithyMatt
  • Thanks 1
Link to comment
Share on other sites

1 minute ago, SlithyMatt said:

Correct, JSRFAR only works with the RAM banks. My repo here shows you how to do some other config magic, and it's going to be the subject of my next vlog coming out Thursday.

JSRFAR seems like it works for ROM, there is just a chicken and egg problem. JSRFAR is a kernal routine at $FF6E (which I have memorized for the moment after typing it in multiple times over the last 12 hours). Kernal is in ROM bank 0. My Hello World ROM is in ROM bank 7. I can't change the ROM bank while my code is executing in ROM without bad things happening, so I need a little helper function in my own ROM to get to the RAM entry point. My (almost completely worthless) code is below:

.pc02

.code

        ldx #0
loop:   lda message,x
        bne print
        rts
print:  jsr xjsrfar
        .word $FFD2
        .byte 0
        inx
        bra loop

; -----------------------------------------------------------------------------
; code borrowed from BASIC ROM to support jsrfar to kernal
ram_bank = 0
rom_bank = 1
.include "c:/dev/x16/x16-rom/inc/banks.inc"
.export xjsrfar
xjsrfar:
.include "c:/dev/x16/x16-rom/inc/jsrfar.inc"
; -----------------------------------------------------------------------------

.rodata

message: .asciiz "HELLO WORLD"

Link to comment
Share on other sites

2 hours ago, evlthecat said:

Congrats on the success!

Thanks! I'm debating several project ideas.

1. An "advanced" (for some concept of advanced) BASIC-like interpreter that supports a more structured language than Commodore BASIC

2. An emulator of some other computer. I feel like basic emulation of some 40 year old computers should be possible with 2+ MB of RAM and 16 KB of ROM

  • Like 2
Link to comment
Share on other sites

What was going thru my head were earlier, simpler systems.   KIM-1?  Or something more exotic out of the 1970s. 

Would it be possible to emulate a PDP-8?  Completely alien architecture...  4096 "words" of memory, where a "word" is 12 bits... strange... only 8 opcodes... eeek no, wrong direction by FAR too painfully simple.  

I think I'll read up on the Wikipedia entry for the PDP-11.

 

 

Edited by rje
Link to comment
Share on other sites

1 hour ago, ZeroByte said:

You get the inception award if you can emulate a VIC-II + SID and C64 Kernal+BASIC on an X16, especially if it correctly runs any demoscene demos.

No way will I be trying for that level of emulation. My thought was to emulate an 8086 with simple INT interfaces that would allow a DOS COM file to be executed that didn't try for any fancy direct hardware access. Or a Timex Sinclair 1000. Strictly instruction level emulation of a CPU, no cycle accurate emulation.

  • Like 1
Link to comment
Share on other sites

Nice work!

The thing I struggled the most with was proper interrupt handling.

In a hello world that exits very quickly, chances are that you never have an interrupt during the ROM code execution. After all, there are 155,555 cycles per VBLANK at 8 Mhz.

If your program runs for a longer period of time, you would, however, not want the interrupt vector in $fffe-ffff point to outer space, but to some code that handles interrupts.

The solution is quite easy. Let $fffe-ffff in your ROM bank point to the KERNAL RAM code banked_irq. That is located at address $038b.

Link to comment
Share on other sites

12 minutes ago, Stefan said:

Nice work!

The thing I struggled the most with was proper interrupt handling.

In a hello world that exits very quickly, chances are that you never have an interrupt during the ROM code execution. After all, there are 155,555 cycles per VBLANK at 8 Mhz.

If your program runs for a longer period of time, you would, however, not want the interrupt vector in $fffe-ffff point to outer space, but to some code that handles interrupts.

The solution is quite easy. Let $fffe-ffff in your ROM bank point to the KERNAL RAM code banked_irq. That is located at address $038b.

Agreed. This was just to make sure I could write some "legitimate" asm, assemble it, link it, append it to the "blessed" roms, and do a JSRFAR into my bank. Now the real work begins. Like picking what project I want to work on. 🙂

Link to comment
Share on other sites

11 hours ago, rje said:

What was going thru my head were earlier, simpler systems.   KIM-1?  Or something more exotic out of the 1970s. 

Would it be possible to emulate a PDP-8?  Completely alien architecture...  4096 "words" of memory, where a "word" is 12 bits... strange... only 8 opcodes... eeek no, wrong direction by FAR too painfully simple.  

I think I'll read up on the Wikipedia entry for the PDP-11.

 

 

Perhaps a 4004 emulator. That should be "easy". 🙂

Link to comment
Share on other sites

1 hour ago, ZeroByte said:

Yaknow, Atari2600 might be doable because of how Stella worked. You’d have to be a mad genius of cycle exact coding though, because the 2600 was all about timing your code.

Not at full speed, but you could get some kind of emulation. Might work better to have the X16 transpile the 2600 ROM into a more native format -- convert all the TIA and RIOT load/stores to library subroutine calls.

  • Like 1
Link to comment
Share on other sites

1 hour ago, SlithyMatt said:

Might work better to have the X16 transpile the 2600 ROM into a more native format --

That's exactly what I was thinking. Make the loader go through and modify addresses as needed, etc. One thing I'd thought of would be to load the ROM to BANKRAM so that any carts which do bank swapping would actually behave the same way w/o any real work on the part of the X16. I hadn't thought of hooking TIA/RIOT with subroutine calls. That's a clever idea.

I was thinking that since there're only a few graphic primatives that pretty much only exist in X coordinate space in TIA, it might be doable by using layers and scroll registers to set the X location...

Link to comment
Share on other sites

12 minutes ago, ZeroByte said:

scroll registers to set the X location

scroll registers won't help with the X location, that just going to have to be sprite placement. You can't have multiple scroll X-coordinates on the same line. This will be the hardest part -- converting the delays for X positioning. You'd have to analyze the code to determine when those X-placement TIA writes relative to the last known line start, then convert that into sprite X coordinates. Same with the Y -- count the lines for TIA writes and convert that into Y positioning and row data. Doing all this conversion on the X16 would be intense, but doable if you're patient for the transpiler to spend some time doing its conversion before running the ROM. If you just did the conversion on a modern PC, it would be instantaneous and you would have an X16 program that you could just load and run.

Link to comment
Share on other sites

49 minutes ago, SlithyMatt said:

You can't have multiple scroll X-coordinates on the same line.

True, but I was thinking something along the lines of using both layers where L0 = sprite0 and L1 = sprite1, having only one tile in the top-left of each layer's tilemap, and writes to the sprite data register just re-write the first byte of the tile pixel map (which, conveniently, is just a single write because it's 1bpp) - not saying this is the best way at all - it's just what the idea was floating around in my head.

Doubling or tripling sprites would just amount to putting 1 into a couple more tile slots (unless the spacing is variable, which I think it isn't).

I'd use sprites for the ball and missiles - but thinking of it now, it seems using VERA sprites for the TIA sprites is the way to go anyway, because the tilemap is much more suited to emulating the playfield.

It's crazy to think about writing a general-purpose algorithm to analyze a VCS game's kernal(s) and generate code that "just shows it" on VERA, but that's probably the best way to go.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use