EricBall Posted August 25, 2005 Share Posted August 25, 2005 (edited) 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 August 26, 2005 by EricBall 1 Quote Link to comment Share on other sites More sharing options...
supercat Posted August 26, 2005 Share Posted August 26, 2005 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. Quote Link to comment Share on other sites More sharing options...
cd-w Posted August 26, 2005 Share Posted August 26, 2005 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 Quote Link to comment Share on other sites More sharing options...
EricBall Posted August 26, 2005 Author Share Posted August 26, 2005 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). Quote Link to comment Share on other sites More sharing options...
supercat Posted August 27, 2005 Share Posted August 27, 2005 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. Quote Link to comment Share on other sites More sharing options...
mos6507 Posted August 27, 2005 Share Posted August 27, 2005 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 Quote Link to comment Share on other sites More sharing options...
EricBall Posted August 29, 2005 Author Share Posted August 29, 2005 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. Quote Link to comment Share on other sites More sharing options...
mos6507 Posted August 29, 2005 Share Posted August 29, 2005 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. Quote Link to comment Share on other sites More sharing options...
supercat Posted August 29, 2005 Share Posted August 29, 2005 ;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. Quote Link to comment Share on other sites More sharing options...
Eckhard Stolberg Posted August 29, 2005 Share Posted August 29, 2005 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 Quote Link to comment Share on other sites More sharing options...
Eckhard Stolberg Posted August 29, 2005 Share Posted August 29, 2005 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 Quote Link to comment Share on other sites More sharing options...
supercat Posted August 29, 2005 Share Posted August 29, 2005 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. Quote Link to comment Share on other sites More sharing options...
mos6507 Posted August 30, 2005 Share Posted August 30, 2005 (edited) 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 August 30, 2005 by mos6507 Quote Link to comment Share on other sites More sharing options...
Eckhard Stolberg Posted August 30, 2005 Share Posted August 30, 2005 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 Quote Link to comment Share on other sites More sharing options...
cd-w Posted September 5, 2005 Share Posted September 5, 2005 (edited) 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 September 5, 2005 by cd-w Quote Link to comment Share on other sites More sharing options...
vdub_bobby Posted March 7, 2006 Share Posted March 7, 2006 Rereading this, a few questions: 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? Quote Link to comment Share on other sites More sharing options...
supercat Posted March 7, 2006 Share Posted March 7, 2006 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. Quote Link to comment Share on other sites More sharing options...
vdub_bobby Posted March 7, 2006 Share Posted March 7, 2006 Thanks. And why do we want to bank in that ROM? Do we need to call something in that ROM for multiple-load games? Quote Link to comment Share on other sites More sharing options...
supercat Posted March 8, 2006 Share Posted March 8, 2006 Thanks. And why do we want to bank in that ROM? Do we need to call something in that ROM for multiple-load games? 1029738[/snapback] Precisely. If you're not using multi-load games, there's no need for the ROM. Quote Link to comment Share on other sites More sharing options...
vdub_bobby Posted March 8, 2006 Share Posted March 8, 2006 Ok; thanks. Next question: So, for a multiple-load game, what exactly needs to be done to load the next part? Quote Link to comment Share on other sites More sharing options...
mos6507 Posted March 12, 2006 Share Posted March 12, 2006 Ok; thanks. Next question: So, for a multiple-load game, what exactly needs to be done to load the next part? You may want to read the Cuttle Cart manual online here. It goes over most of this. Quote Link to comment Share on other sites More sharing options...
vdub_bobby Posted March 14, 2006 Share Posted March 14, 2006 Ok; thanks. I keep forgetting that the CC manual is so thorough. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.