Jump to content
IGNORED

SuperCharger jumpstart 1/2


EricBall

Recommended Posts

Hopefully this will help those people interested in creating a SuperCharger homebrew for Glenn's contest. Part 2 will be about the SC header (but first I have to re-learn it myself.)

 

The SuperCharger contains 6K of RAM organized as three 2K banks. These three banks (and the 2K internal ROM) can be mapped in various combinations into the 4K cartridge address space ($1000-$1FF8) via the SuperCharger control register. (Note: Since the 6507 inside the 2600 only has 12 address lines, $1000 is equivalent to $F000.)

 

The SuperCharger control register also controls whether write mode is enabled. Once write mode is enabled, any RAM mapped to the the cartridge address space may be written to. Unfortunately, since the 2600 cartridge slot doesn't have the necessary control signals to allow direct writes (e.g. STA) to work, writes to the SuperCharger RAM require multiple reads to implement a write. First the program generates an access to address $10xx to select the value to write. This value is then written to the fifth address bus value after the $10xx access. For example

CMP	$1000
CMP	(PTR), Y

stores $00 in the address pointed to by (PTR),Y

CMP	$1000,Y
NOP
CMP	BASE,X

stores the value in Y into the address BASE+X (i.e. POKE BASE+X,Y), the NOP is to generate an additional address cycle. It's very important not to store code or data which will be accessed while write mode is enabled at $10xx, since it will generate spurrious writes.

 

The SuperCharger control register ($1FF8 aka $FFF8) is written to in the same way as SuperCharger RAM with some exceptions. First, write mode doesn't have to be enabled. Second, there is no restriction on the number of cycles between the $10xx access and the $1FF8 access; so any access to $1FF8 will trigger a control register update! (Hence why certain 2K/4K games are not compatible with an unmodified SuperCharger.) Third, the top three bits of the control register should not be changed, so a copy of the register is saved in normal RAM at $80.

 

Control register format: dddbbbwr where

ddd = write pulse delay (determined by internal ROM)

bbb = bank mode

w = 1 = write enable

r = 1 = ROM powerdown

bbb	$1000-$17FF	$1800-$1FFF
000	bank 3    ROM
001	bank 1    ROM
010	bank 3    bank 1
011	bank 1    bank 3
100	bank 3    ROM
101	bank 2  ROM
110	bank 3    bank 2
111	bank 2    bank 3

(Note: there are some combinations missing.  In particular, bank 1 and bank 2 never appear together.)

LDA	$80  ; load saved control register 
AND	#%11100000; save write pulse delay
OR	#%000bbbwr; set bank mode, write enable & ROM powerdown
STA	$80  ; save control register
TAX
LDA	$1000,X  ; set write register
LDA	$1FF8  ; set control register

Bankswitching occurs immediately, so unless you enjoy suffering, I'd recommend only changing one bank at a time. (And use the same location for each bank.)

 

Just like programming any other bankswitching game, you will need to use RORG to tell DASM what the actual memory location each bank is mapped to, and ORG to order the banks in the file. Note: the SC header allows for pages to be loaded out of order, so there is no requirement for the source to be in BANK 1,2,3 order.

Edited by EricBall
Link to comment
Share on other sites

	LDA	$1000,Y
NOP
STA	BASE,X

Shouldn't that be

    CMP $1000,Y
   NOP
   CMP BASE,X

to avoid bus contention on the SuperCharger write?

Third, the top three bits of the control register should not be changed, so a copy of the register is saved in normal RAM at $80.

Would it be useful to mask address $80 with #$E0 at program startup and leave a $10 stored at address $81? Then switch banks or modes with

    LDY #NEWMODE
   CMP ($80),Y

 

Also, how often is it necessary to enable/disable writes? Except when strapped for RAM, I'd think it simpler to simply avoid using $10xx.

Link to comment
Share on other sites

Hopefully this will help those people interested in creating a SuperCharger homebrew for Glenn's contest.  Part 2 will be about the SC header (but first I have to re-learn it myself.) ...

918465[/snapback]

 

Eric,

 

Thanks for writing this explanation - it cleared up a lot of questions for me on why the Supercharger does things in such a peculiar way!

 

Chris

Link to comment
Share on other sites

Shouldn't that be

    CMP $1000,Y
   NOP
   CMP BASE,X

to avoid bus contention on the SuperCharger write?

Doh! Yes, you are correct. (I've editted my post to reflect.) Both CMP & LDA will work. (Although CMP doesn't overwrite a register, it doesn't preserve the Carry bit.)

Would it be useful to mask address $80 with #$E0 at program startup and leave a $10 stored at address $81?  Then switch banks or modes with

    LDY #NEWMODE
   CMP ($80),Y

    CMP $FFF8

Hmm, interesting idea. I don't know if it would be compatible with the SC multi load subroutine, though.

Also, how often is it necessary to enable/disable writes?  Except when strapped for RAM, I'd think it simpler to simply avoid using $10xx.

918884[/snapback]

I think you mean strapped for ROM. You certainly could leave your program in write mode all the time as long as you leave $10xx empty, or use it as RAM (always writing back after each access to a dummy location).

Link to comment
Share on other sites

Hmm, interesting idea.  I don't know if it would be compatible with the SC multi load subroutine, though.

Remind me of the problem with that? What does $81 need to be?

I think you mean strapped for ROM.  You certainly could leave your program in write mode all the time as long as you leave $10xx empty, or use it as RAM (always writing back after each access to a dummy location).

919147[/snapback]

 

I meant 'SC-RAM'. You don't put anything in ROM on the SuperCharger after all. I agree it's important to distinguish SC-RAM from ZP-RAM, though I found when I did my 1k minigame for the Supercharger I had oodles of free ZP-RAM.

Link to comment
Share on other sites

Based on looking at the source to Commie Mutants, it looks like it's really only necessary to put a NOP between the two CMP instructions when you are hardcoding the value since it would reduce the cycle count to 3 (one less than the minimum required). So the common cases would be:

RAM = $F000
VALUE_TO_STORE = $80
DESTINATION_ADDRESS = $F100 (whatever)

;value from (hardcoded)
           CMP    RAM+VALUE_TO_STORE; preps the value for the write
           NOP
           CMP    DESTINATION_ADDRESS; does the write

;value comes from X
           LDX     #VALUE_TO_STORE
           CMP     RAM,X; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

;value comes from Y
           LDY     #VALUE_TO_STORE
           CMP     RAM,Y ; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

Link to comment
Share on other sites

Not to say your source is wrong, but it seems to contradict what the CC2 documentation states.

 

919585[/snapback]

;value from (hardcoded)
           CMP    RAM+VALUE_TO_STORE; preps the value for the write
           NOP
           CMP    DESTINATION_ADDRESS; does the write

Yes, this makes sense.

address 0 = read from $F080, load $80 into write register

address 1 = read NOP

address 2 = read CMP

address 3 = read $00 (DESTINATION_ADDRESS LSB)

address 4 = read $F1 (DESTINATION_ADDRESS MSB)

address 5 = read from $F100, write $80 to $F100

;value comes from X
           LDX     #VALUE_TO_STORE
           CMP     RAM,X; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

;value comes from Y
           LDY     #VALUE_TO_STORE
           CMP     RAM,Y; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

These don't make sense cause the DESTINATION_ADDRESS isn't accessed 5 address cycles after $F0xx. (Unless DESTINATION_ADDRESS is $1FF8 of course.) Remember it's not CPU cycles, but address cycles.

Link to comment
Share on other sites

These don't make sense cause the DESTINATION_ADDRESS isn't accessed 5 address cycles after $F0xx.  (Unless DESTINATION_ADDRESS is $1FF8 of course.)  Remember it's not CPU cycles, but address cycles.

920777[/snapback]

 

But it works in Commie Mutants, so I would say that the CC2 documentation is wrong or maybe we are just interpreting it wrong.

 

Try it for yourself in Leprechaun. Gotta go with what works. No need to waste space on NOPs if you don't need them.

Link to comment
Share on other sites

;value comes from X
           LDX     #VALUE_TO_STORE
           CMP     RAM,X; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

;value comes from Y
           LDY     #VALUE_TO_STORE
           CMP     RAM,Y; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

These don't make sense cause the DESTINATION_ADDRESS isn't accessed 5 address cycles after $F0xx. (Unless DESTINATION_ADDRESS is $1FF8 of course.) Remember it's not CPU cycles, but address cycles.

920777[/snapback]

 

There are times the above formulation will work. If it's desired to decrement a memory location if it's non-zero (and leave it alone if zero) one could use:

 ldy memoryloc
 cmp $0FFF,y
 cmp dest_address

If the memory location holds 10, the second instruction would generate reads to $0F09 and $1009. If it holds zero, the second instruction would only access $0FFF (not hitting the write trigger) and the third instruction would be harmless.

Link to comment
Share on other sites

RAM = $F000
VALUE_TO_STORE = $80
DESTINATION_ADDRESS = $F100 (whatever)

;value comes from X
           LDX     #VALUE_TO_STORE
           CMP     RAM,X; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

;value comes from Y
           LDY     #VALUE_TO_STORE
           CMP     RAM,Y; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

919585[/snapback]

No, Eric is right. These examples would only work if DESTINATION_ADDRESS was $1FF8, because writes to the SC config address will always happen immediately. And that's the only way writing is done this way in the Commie Mutants code too.

 

There are several cases of the following code though:

RAM = $F000
VALUE_TO_STORE = $80
DESTINATION_ADDRESS = $F100 (whatever)

;value comes from Y
           LDY     #VALUE_TO_STORE
           CMP     RAM+$FF,Y; preps the value for the write
           CMP    DESTINATION_ADDRESS; does the write

In this case you would write $7F to $F100. The reason for that is that the first CMP will need an extra cycle because it is crossing a page boundary for any Y-value other than $00. In this case the 6507 will first access $F07F (this will trigger the writing) and fix the high byte of the destination address. Then it will access $F17F. This is the extra address cycle that is needed for the access to DESTINATION_ADDRESS to be the 5th address cycle after the access to the writing trigger address.

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

There are times the above formulation will work.  If it's desired to decrement a memory location if it's non-zero (and leave it alone if zero) one could use:

 ldy memoryloc
 cmp $0FFF,y
 cmp dest_address

If the memory location holds 10, the second instruction would generate reads to $0F09 and $1009.  If it holds zero, the second instruction would only access $0FFF (not hitting the write trigger) and the third instruction would be harmless.

920934[/snapback]

But you still have the problem that there are not enough address cycles between the two CMPs because the access to the 'write trigger address' is still the last cycle in the first CMP. Therefore the write would go the address of the opcode byte of the instruction after the second CMP. You either have to put a NOP between the two CMPs, or you have to use $10FF,Y on the first CMP and make sure that Y is never zero.

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

But you still have the problem that there are not enough address cycles between the two CMPs because the access to the 'write trigger address' is still the last cycle in the  first CMP. Therefore the write would go the address of the opcode byte of the instruction after the second CMP. You either have to put a NOP between the two CMPs, or you have to use $10FF,Y on the first CMP and make sure that Y is never zero.

920966[/snapback]

 

Quite right. Though if the destination address is in the last page of address space, the code with your change would have the desired 'no effect' even if Y was zero. :)

 

BTW, I have used the code I posted except I guess I must have included a nop in it. Still slick not having to worry about the zero case.

Link to comment
Share on other sites

Here is the RAM equate from commit mutants:

RAM	equ $F000

Here is some actual sample code:

CMP RAM+$FF,Y
CMP DIG6+1  ; R

So you're saying that RAM+$FF,Y is equivalent to RAM,Y but with an extra address cycle added because of crossing a page boundary?

 

In that case wouldn't you say it's the preferred coding standard (as long as you don't have to store a zero) in order to save a byte by not having to put in a NOP?

 

 

This is some pretty esoteric stuff here...

Edited by mos6507
Link to comment
Share on other sites

So you're saying that RAM+$FF,Y is equivalent to RAM,Y but with an extra address cycle added because of crossing a page boundary?

RAM refers to $F000 and RAM+$FF refers to $F0FF. With the Y-register ranging from $00 to $FF you will never cross a page boundary in the first case, but you will always cross a page boundary in the second case except when Y=$00.

 

In the absolute-indexed addressing modes the 6507 will first add the contents of the index register to the low byte of the destination address and access this address. Only if the addition has overflown, it will increase the high byte of the destination address and access the corrected address too. This is were the extra cycle comes from.

 

So if Y = $80, then a LDA $F0FF,Y will first read from address $F07F (triggering the SC write) and then read from $F17F (the extra cycle after fixing the high byte).

 

In that case wouldn't you say it's the preferred coding standard (as long as you don't have to store a zero) in order to save a byte by not having to put in a NOP?

921640[/snapback]

You can save a zero, but you can't save a $FF. And you always have to add 1 to the value that you want to store. If you can live with these limitations, it's a clever way to save a byte for the NOP. But it probably is only really usefull for storing fixed values at fixed addressed. Or for easily decreasing values at fixed addresses as supercat pointed out.

 

 

Ciao, Eckhard Stolberg

Link to comment
Share on other sites

I'm getting a bit confused between cycle counts and address changes. Can someone confirm that the following sequence will work, and the cycle counts (in square brackets) are correct?

 

; Write the value Y into address BASE+1
  CMP $1000,Y    [4]
  NOP            [2]
  CMP BASE+1     [3]

 

Thanks,

Chris

 

EDIT: Ok, I think I found the problem - the second CMP actually takes 4 cycles ...

Edited by cd-w
Link to comment
Share on other sites

  • 6 months later...
First, what does "ROM powerdown" mean?

 

Second, the bank-switching options with "ROM" in a bank - what does that mean?  What "ROM" are we referring to?

1029649[/snapback]

 

A real SuperCharger contains 2K of ROM which, on startup, will show a starfield with a "REWIND TAPE / PRESS PLAY" message and, once audio is heard, load a program from tape into RAM. The Supercharger uses more power than a normal cart, so to avoid overheating the Atari's regulator it can power down the ROM to conserve some power.

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.
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...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...