Jump to content
Sign in to follow this  
rawbits

Address table question. /urgent/

Recommended Posts

Hi!

 

I have written some cool things on the VCS in the past 3 months but now I have started a big - and quick - project. I want to do a demo and I tought I can made it load the different parts from a table of addresses. Basically I want to load the picture drawing code sequentially using an address table. But how can I know the addresses of the start of the parts? I know labels are for that, but can I store them somehow in a table? And how? And how can I read the addresses back from table to JSR them? Can I use bankswitching with this?

 

Please help me with this! The VBLANK and - mostly - the Overscan part will be the same - and used up pretty much all avaible cycles - so I don't want to waste space by copying them in all parts...

 

I have 1 month to finish the whole thing. Effects are more or less done. I have ordered the Harmony cart - but didn't get confirmation on the posting so I'm afraid it will not arrives in time or at all.

Edited by rawbits

Share this post


Link to post
Share on other sites

You can easily make a table of addresses like you can with anything else:

 

PointersToParts:
 .dw DemoPart0
 .dw DemoPart1
 .dw DemoPart3

 

And then you can make a subroutine that loads an address from the table and jumps to it, based on an index you give it:

 

CallPart:
 asl ;multiply the index by 2 because each address is 2 bytes
 tax ;put it into an index register
 lda PointersToParts+0, x ;copy the address to RAM
 sta Pointer+0
 lda PointersToParts+1, x
 sta Pointer+1
 jmp (Pointer) ;jump to the code

 

Then you can use the subroutine like this:

 

  lda #$01 ;load the index of the part
 jsr CallPart ;call it

 

Then the RTS at the end of each part will return to the instruction after this call.

 

Bankswitching makes things a bit more complicated. You'd have to somehow list the banks where each part resides, either in a second table or using the highest bits of the addresses in the existing table, and switch to the correct bank before jumping to the address. If the different parts can be called from different banks, you'll also have to remember the index of the bank that made the call so that you can return to it later. If the calls are made from a single bank, just switch back to that bank before returning.

  • Like 1

Share this post


Link to post
Share on other sites

Hi Rawbits,

 

 

There a couple of common used address modes for loading data that might be useful for you. I'm going to skip one of them "load Absolute,index" and give you an example of "(Indirect),Y" instead:

 

 

;usage example
 LDA	(addressPF1Gfx),Y
 STA	PF1

 

 

Okay, so you have lots of different graphics you want to use. Each has a label at the start of them like this:

 

PF1Graphics_A:
.byte $FF ; |XXXXXXXX|
.byte $FF ; |XXXXXXXX|
.byte $CF ; |XX  XXXX|
.byte $BF ; |X XXXXXX|
.byte $AF ; |X X XXXX|
.byte $AF ; |X X XXXX|
.byte $A8 ; |X X X   |
.byte $AF ; |X X XXXX|
.byte $AF ; |X X XXXX|

 

You need to simply build a table of address, like so:

 

MyPF1GfxTab:
.word PF1Graphics_A
.word PF1Graphics_B
.word PF1Graphics_C
.word PF1Graphics_D
.word PF1Graphics_E
 
;... and all the rest

 

More often you will see people using ".byte <PF1Graphics_A", that defines just the low part of the address, and likewise ".byte >PF1Graphics_A" could be used to define the high part.

 

But in this example we are using word, and loading the whole thing. So, here comes the loading part:

 

lda	graphicsIndex  ; used to index the address table...
asl  ; times by 2, because each address is 2 bytes long
tay  ; you can use either Y or X to index the address table
lda	PF1GfxTab,Y
sta	addressPF1Gfx	; low address
lda	MyPF1GfxTab+1,Y ; "+1" to get next byte
sta	addressPF1Gfx+1 ; high address

 

So that's it. You need to define some zero page ram, like in the example above "graphicsIndex" takes 1 byte or ram, and each indirect pointer requires 2. You said you've programmed before, so no need to explain this.

 

 

These routines are hardly anywhere near as efficient as they can be. You have to custom tailor your own code. For example If you had to chose between display two 48 displays you might do something like this for speed:

 

 

LOGO_HEIGHT = 8
  lda	#<TitleScreen
  bit	flags
  bvc	.loadGfxPtrs  ; using bit 6, if it's "1" do ending screen logo, otherwise do title screen logo
  lda	#<EndingScreen
.loadGfxPtrs:
  sta	gfxPtrs
  clc
  adc	#LOGO_HEIGHT
  sta	gfxPtrs+2
  adc	#LOGO_HEIGHT
  sta	gfxPtrs+4
  adc	#LOGO_HEIGHT
  sta	gfxPtrs+6
  adc	#LOGO_HEIGHT
  sta	gfxPtrs+8
  adc	#LOGO_HEIGHT
  sta	gfxPtrs+10

 

 

This assumes the data is on the same page, so you don't need to update the high pointers. For byte savings you would take a different approach and put it all in a loop.

Edited by Omegamatrix

Share this post


Link to post
Share on other sites

Wait, Omegamatrix's reply confused me a bit... :?

Rawbits, do you want to use tables for accessing code or data? My reply explains how you can call different parts of code dynamically (i.e. you decide what runs when at run time, as opposed to compile time), while Omegamatrix's deals with using the same code to manipulate different sets of data.

Share this post


Link to post
Share on other sites

I assumed here that he was drawing a picture using the PFx or GRPx registers, and updating the pointers mid-screen. That's how I read it anyhow.

Share this post


Link to post
Share on other sites

Omegamatrix: Thanks but I'm quite familiar with graphics on the VCS. I was interested in dinamically switch code segments to use for drawing.

 

Chopper Commander: Thank you! If you can store addresses using labels in a table like that then this is what I'm looking for. Bank switching is just an option for now because I don't know how big the effects are and I haven't done the gfx and music for it...

Share this post


Link to post
Share on other sites

Here's my humble suggestion using ca65 and make, hacked up in less than half an hour by copy'n'pasting other stuff floating around here. Trying to be simple, yet flexible. The code is for a PAL system, for NTSC, the timer values need to be changed.

 

Just 4K now, but I've got bankswitching templates for the ca65 as well.

 

Greetings,

SvOlli

demoscheduler.zip

Share this post


Link to post
Share on other sites

You can easily make a table of addresses like you can with anything else:

 

PointersToParts:
 .dw DemoPart0
 .dw DemoPart1
 .dw DemoPart3

 

And then you can make a subroutine that loads an address from the table and jumps to it, based on an index you give it:

 

CallPart:
 asl ;multiply the index by 2 because each address is 2 bytes
 tax ;put it into an index register
 lda PointersToParts+0, x ;copy the address to RAM
 sta Pointer+0
 lda PointersToParts+1, x
 sta Pointer+1
 jmp (Pointer) ;jump to the code

 

Then you can use the subroutine like this:

 

  lda #$01 ;load the index of the part
 jsr CallPart ;call it

 

Then the RTS at the end of each part will return to the instruction after this call.

 

Another "classic" method to implement a jump table is to push the target address onto the stack and use RTS instead of an indirect jump. This saves a couple of bytes, but you can only use it if you have room for two bytes on the stack. Note that RTS pulls an address from the stack and jumps to that address+1, so the table needs to be adjusted by subtracting 1 from the actual target.

 

; jump index in X

CallPart:
 lda PointersToPartsHi, x
 pha
 lda PointersToPartsLo, x
 pha
 rts ;jump to the code

 

PointersToPartsLo: ; address-1 lo-bytes
 .db <(DemoPart0-1)
 .db <(DemoPart1-1)
 .db <(DemoPart2-1)

PointersToPartsHi: ; address-1 hi-bytes
 .db >(DemoPart0-1)
 .db >(DemoPart1-1)
 .db >(DemoPart2-1)

Edited by Joe Musashi

Share this post


Link to post
Share on other sites
Chopper Commander: Thank you!

Heh, you're welcome, but "Chopper Commander" is my rank on the forums or something, not my username! =)

 

Another "classic" method to implement a jump table is to push the target address onto the stack and use RTS instead of an indirect jump.

Yeah, this is a very common trick on the 6502. I didn't want to confuse rawbits since he wasn't familiar with these things, but your method (address table split into low and high bytes and RTS instead of JMP) is indeed more optimized and what I'd use in an actual project.

Share this post


Link to post
Share on other sites

I prefer the method Omegamatrix mentioned where the lo bytes and hi bytes are stored in separate tables, because this gives you 256 possible entries. I don't know if that's the most common method, but I believe it's the one I've seen used the most.

 

And if you're using bankswitching, you can have a third table for the bank number. That's how I loaded the text data for all the pages in the infamous "E.T. Book Cart"-- one table for the lo byte, a second table for the hi byte, and a third table for the bank number.

Share this post


Link to post
Share on other sites

I seen both Joe's and tokumaru's methods used before, but Joe's is a little better because you can use stack ram that you normally use for JSR anyway. You just have to know where the stack pointer is.

 

 

Thinking about it now, if time was a factor you might be able to go a little quicker by jumping into a jump table. Takes more bytes, but saves some cycles. Might be economical for smaller tables.

 

 

lda	eventNumber   ;3  @3  can hold values 0 to 85
asl   ;2  @5  carry gets cleared also!
adc	eventNumber   ;3  @8  muliply by 3
sta	indirectAddr  ;3  @11
jmp.ind (indirectAddr);5  @16 + 3 = 19 cycles
 
 
JumpTable:
.byte $4C ; JMP absolute opcode
.word PartOne
.byte $4C
.word PartTwo
.byte $4C
.word PartThree
.byte $4C
.word PartFour
.byte $4C
.word PartFive
.byte $4C
.word PartSix

 

 

Of course you go alot faster still when your index is always in multiples of three.

 

lda	eventNumber   ;3  @3  always in multiples of 3
sta	indirectAddr  ;3  @6
jmp.ind (indirectAddr);5  @11 + 3 = 14 cycles

 

 

Edit: It is also implied here that the high address in "indirectAddr" has been set well before hand:

 

lda #>JumpTable

sta indirectAddr+1

 

You don't need to ever update it, so that is why it is left out of the routine.

Edited by Omegamatrix

Share this post


Link to post
Share on other sites

Omegamatrix, one question.

 

Why:

JumpTable:
.byte $4C ; JMP absolute opcode
.word PartOne
.byte $4C
.word PartTwo
.byte $4C
.word PartThree
.byte $4C
.word PartFour
.byte $4C
.word PartFive
.byte $4C
.word PartSix

 

And not:

JumpTable:
JMP PartOne
JMP PartTwo
JMP PartThree
JMP PartFour
JMP PartFive
JMP PartSix

 

Imo, it makes the code more readable.

Share this post


Link to post
Share on other sites

Thank you all for the help! Sorry of the missnaming I have played VCS games when I was a child but never could memorize the titles - sometimes I still can't do it... :) Also I couldn't find the Edit button on my profile and then I realized it's orange. XD Maybe I need more sleep...

Edited by rawbits

Share this post


Link to post
Share on other sites

You're right of course, SvOlli. I really had pointers stuck in my mind when I coded it last night. The train of thought blinded me, ha ha. :)

 

 

I should also mention that the jump table needs to start at a beginning of a page.

Share this post


Link to post
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.
Note: Your post will require moderator approval before it will be visible.

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.

Loading...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...