Jump to content

SeaGtGruff

Members
  • Content Count

    5,587
  • Joined

  • Last visited

  • Days Won

    2

Everything posted by SeaGtGruff

  1. Was it you had mentioned SAX in the thread about mid-line color changes? Yes, after I took a closer look-- and constructed a spreadsheet that showed what the ANDed color would be for any two "base" colors-- I decided this could be very useful indeed, and I started tinkering with ideas for a tile-based kernel (as mentioned in my previous post). As for "illegal" opcodes, I do generally avoid them if I can get by without them, but I don't avoid them if they would solve a problem I'm having with timing or space. The reason I usually avoid them on general principle is so I won't run into a situation where I've used an "illegal" opcode and it ends up causing a problem on some machine with a CPU in which the "illegal" opcode doesn't perform as expected. You're correct that as long as they work on all versions of the VCS and its clones, then there's no reason to avoid them-- but I think there *have* been some cases involving batari Basic where the compiler uses some "illegal" opcodes for certain things and it has caused problems on certain versions or clones of the VCS.
  2. Well, I try to avoid them on general principle, but there are definitely times when certain "illegal" opcodes can be very useful-- and LAX is one of them. Another interesting one that someone mentioned in another thread a year or two ago is SAX, which could potentially be useful for getting three different colors if A and X have two colors in them-- STA COLUPF for one color, STX COLUPF for the second color, and SAX COLUPF for a third color which is the A color ANDed with the X color. My initial reaction to that suggestion was that ANDing colors generally isn't as useful as just using specific colors. But I started to experiment with the idea, and am tinkering with an idea for a tile-like kernel that uses this trick to select between three different playfield colors for each tile. To work, the code needs to be copied into RAM so the store instructions can be modified according to which color is to be used for a given tile. I haven't done much in the way of a working demo yet-- too many irons in the fire and too many kettles on too many back burners-- but I hope to post a working demo one of these days!
  3. Yes, the assembly should fail if the lines aren't indented. I don't know why the forum software has such an appetite for leading spaces-- it's rather frustrating. It also seems to like to convert multiple spaces into tabs.
  4. That reminds me of when my first nephew loved to play Adventure with me way back when, and he always called it "Re-Venture."
  5. By the way, it's hard to tell what's indented and what's not, because the forum software likes to eat leading spaces-- but it looks like the "asm" keyword is definitely indented but I can't see that the actual assembly code is. You need to indent assembly code the same way you do batari Basic code: asm this_is_a_label ; labels aren't indented lda #1 ; this is assembly code, it needs to be indented at least 1 space end ; the end keyword is typed as though it were a label-- do not indent it That goes for things like BYTE, WORD, and HEX, too: My_Data ; do not indent the label HEX DE AD BE EF DE AD BE EF DE AD BE EF ; but do indent HEX, BYTE, WORD, etc.
  6. I'll have to go back and look at the code in the example I posted, because now that you said that, I thought LDX ,Y was what I *had* used. If I didn't, I assume I must have had a reason (although it might have been as simple as "It's 3 o'clock in the morning and I'm sleep-typing! Dum dee dum dee dum!"). EDIT: I'm pretty sure I used LDA and TAX because the alternative was to use LDX and NOP, which didn't seem any better.
  7. The code in bogax's second reply looks like the way to go-- although I haven't tried it. I don't think anyone really explained why your code didn't work right. You can include math in an assembly instruction like you did-- LDA #<(playerpointers+temp2)-- but that doesn't mean the math will be done at runtime. What happens is the math is done by the assembler while it's assembling the code, so what you actually end up with is something quite different than what you were expecting. The assembler can't treat temp2 like a variable, because it has no way to know ahead of time what value will be stored in temp2 when the instruction is being executed. So what is has to do is treat temp2 like a label and use the value of the label-- i.e., its address-- while boiling the math down into a single value that can be written to the ROM file in the second half of that instruction (i.e., the byte that follows the opcode).
  8. Good suggestion, and I was tempted, but I wanted to stick to the "legal" opcodes as much as possible. Since it would have saved only 2 cycles, and I was able to include the color changing that he'd wanted, and the whole thing (including looping) took exactly 76 cycles, there was no point in using LAX in this particular case. After all, what would that get-- the need to use a NOP to make the loop 76 cycles? But in other cases-- LAX would definitely be the way to go.
  9. I think it's a good idea to delay any implementation of a disassembly output until (1) any other issues are dealt with and (2) you've worked out a good plan for how you want to implement it. In order of relative priorities, I think getting the TIA emulation fixed and (if possible) adding options to emulate the differences between the different versions of the TIA and its clones should be the first priority. This also includes any possibility of adding options to compute the color palettes internally and let the user tweak them by adjusting brightness, contrast, and tint. The debugger has second priority. And any plans to implement a disassembly output should have the lowest priority. Stella is an emulator, first and foremost, so emulation should always be its top priority. The debugging features are extremely useful for developers, but they make up only a fraction of Stella's users. And disassembly would be really nice, but I don't see how it would be of any use except to someone who wants to hack a game, or a developer who wants to study someone else's game to see how some nifty trick was done. And since there are already existing programs and tools for hacking and disassembling games, there's no urgent need for adding this functionality to Stella.
  10. I'd love to see Stella be able to output a disassembly of any cartridge type it can run. HOWEVER, there's no reason you'd need to implement something like that all at once. It would make good sense to start with just the non-bankswitched cartridge types first-- 2K and 4K. Once you've got the disassembly for those working to your satisfaction, the next logical step would be adding the bankswitched cartridge types that swap out the entire 4K cartridge space. And after that's working well, you could begin thinking about slowly adding the more exotic cartridge types, with the more common ones taking precedence over the less common ones. Besides, the ones that swap out the entire 4K cartridge space are probably far and away the more commonly-used ones. Why fret now about how to handle a seldom-used exotic bankswitching scheme if there's only one or two ROMs that use it? And once you've got a better idea of how to handle the common 4K-bank schemes, the more exotic schemes might start to look less intimidating.
  11. Yes, TIA registers can be called using an index. However, that doesn't necessarily mean you can use this to update REFP0 through REFP9 using an index, since only REFP0 and REFP1 are actual TIA registers. Looking at the header file for the multisprite kernel, I don't even see _REFP1 at all, just _NUSIZ1 through NUSIZ5. If I remember correctly (it's been a while since I've used the multisprite kernel), batari combined REFP with NUSIZ for sprites 1 through 5 in the multisprite kernel, since REFP uses bit 3 but NUSIZ doesn't. Thus, _NUSIZ1 through NUSIZ5 can combine the functions of NUSIZ with the functions of REFP. For sprite 0 you have to use the two separate registers, NUSIZ0 and REFP0. For the other sprites you use _NUSIZ1 through NUSIZ5, which are RAM variables or "virtual registers" rather than actual TIA registers. Whatever you write to _NUSIZ1 will be copied into NUSIZ1 and REFP1 for drawing sprite 1, and likewise the contents of NUSIZ2 through NUSIZ5 will be copied into NUSIZ1 and REFP1 for drawing sprites 2 through 5. The reason you need the underline at the beginning of _NUSIZ1 is to specify that you want to write to the RAM variable rather than to the TIA register named NUSIZ1. Likewise, I don't see any _REFP or REFP variables in the DPC+ header, just _NUSIZ1 through NUSIZ9. I assume they work the same way as in the multisprite kernel-- i.e., that they combine the functionality of the NUSIZ and REFP registers. _NUSIZ1 has an underline at the beginning but NUSIZ2 through NUSIZ9 do not. Since _NUSIZ1 through NUSIZ5 (in the multisprite kernel) and _NUSIZ1 through NUSIZ9 (in the DPC+ kernel) are defined one after the other in RAM, you should be able to set them using _NUSIZ1 with an index-- e.g., _NUSIZ1[index]=whatever. The value you set them to should combine the missile size, reflect flag, and number of sprite copies into a single value: bits 4 and 5 = missile size bit 3 = reflect flag bits 0 through 2 = sprite size or number of sprite copies
  12. That should make the disassembly process easier, I think. I'll try to work up a partial example using an actual ROM to illustrate how I'd probably go about it if I were doing it with a disassembly program of my own (as opposed to using distella or DIS6502).
  13. There should be a set of examples for the different bankswitching methods here in the 2600 programming forum-- I think they're zipped up into attached files with names like bankswitching.zip, bankswitching_2.zip, bankswitching_3.zip, etc.
  14. Regarding banks that have "mixed" addresses, yeah, I've seen that. As long as you're building a set of pseudo-labels that combine the bank number with the "referenced" address, I don't think it would be a problem. Use the "majority vote" idea to get the RORG address, but you can still use the referenced address in the label. For example, bank 1 might end up with a RORG address of $1000 based on the addresses used by the majority of code that refers to bank 1, but an individual label in bank 1 might still be B1LF123 even though bank 1 doesn't have $F000 as its RORG address. As far as ending up with a disassembly that will re-assemble into a byte-exact copy of the original ROM, this situation could be handled by two different labels-- one in the code itself, and the other defined at the top of the code, such as B1LF123 = $F123 ORG $0000 RORG $1000 ; bank 1 ; ... JMP B1LF123 ; <-- reflecting that it's jumping address $F123 within bank 1 ; ... B1L1123 ; <-- label inserted at address $1123 within the code, synonymous with B1LF123 but not actually referenced as B1L1123 [...]
  15. I think I've probably done only one multi-bank disassembly with distella, but I've done several with DIS6502, so I'll jump in. As you know, the first task is to determine the type of bankswitching or ROM format used, so you'll know how to break up the ROM if necessary-- into multiple 4K banks, multiple 2K banks, multiple 1K banks, etc. Both distella and DIS6502 require that the ROM file be split up into its appropriate parts first. In distella you can work with only one bank at a time, but DIS6502 lets you load all of the banks simultaneously as long as they'll all fit into the 64K framework (or really 32K, I suppose, since the code would go at $1000, $3000, $5000, etc.). I don't know if Stella loads the entire ROM at once, or just the bank it's currently running in, but the automatic detection of ROM types is a great boon. I always look at the 6502 vectors to see which address to load a given bank into (DIS6502 displays the contents of the file in a scroll box before you tell it where to load the file, so you can scroll down and see what the vectors are set to). Of course, this helps only if the ROM type uses banks that have their own vectors-- and some ROM types have banks smaller than 4K which go to areas of memory that don't have the vectors in them. But we'll focus on 4K-bank ROMs first. If each bank uses a different page number in its vector addresses, you can just load each bank in the appropriate memory space. But if the banks use the same page number (whether it's $F0, $10, or anything else), the solution is to use different ORG addresses for each bank but followed by the same RORG address: ORG $1000 RORG $F000 ; bank 1 ;etc. Of course, this also works with ROMs that have unique addresses for each bank anyway, so you could use it as the defacto method. And you can actually pick any ORG addresses you want, such as ORG $0000 RORG $1000 ; bank 1 ORG $1000 RORG $3000 ; bank 2 ; etc. In fact, the ORG addresses should define a contiguous block of memory anyway, so the ROM assembles into a contiguous block of code. In other words, you wouldn't want to use ORG $1000 for bank 1 and then ORG $3000 for bank 2, because then the re-assembled ROM would have a 4K bank ($1000-$1FFF), followed by a 4K *gap* ($2000-$2FFF), followed by another 4K bank ($3000-$3FFF), etc. It's really the RORG addresses that determine what the addresses inside each bank will be, and the ORG addresses can just start at $0000 for the first bank and go up from there depending on the size of each bank-- for example, if the ROM uses multiple 2K banks then the ORG addresses could be $0000, $0F00, $1000, $1F00, etc., but for multiple 4K banks the ORG addresses could be $0000, $1000, $2000, etc. So it would probably be simplest to always use both ORG and RORG addresses, with ORG always starting at $0000 and incrementing as necessary to reflect each bank's physical position within the ROM file. In fact, you could just read the entire ROM file into memory with a single ORG of $0000 to start with, even before determining what type of ROM it is. Then you don't even need to keep track of the ORG addresses as such, since the ORG address for any given byte of code or data within the ROM would be directly indicated by its position in memory, if you follow me. Next, after determining the cartridge format or bankswitching scheme, you can start defining a set of RORG addresses for each bank, based on its vector addresses if it has any. If it's a bank that doesn't have vector addresses, you'll need to look at the code within the bank (assuming it isn't just a bank filled with nothing but data) to see what addresses it uses internally, if any-- or, in some cases, you'll need to look at the code in *other* banks to see what addresses they use when referring to the given bank. I'm thinking you'll end up with three sets of numbers-- the ORG addresses, the RORG addresses, and the bank numbers. For any given bit of code or data, its ORG address would simply be its offset into the chunk of memory where you've loaded the entire ROM, and its RORG address would be dependent on how it's referred to by the entire program, but you'd actually need to combine the bank number with the RORG address to distinguish which bit of code or data you're dealing with, since it's possible that multiple banks may use the same RORG address as each other. Of course, in an assembly program that wouldn't be a problem, since the programmer would be using labels rather than addresses per se. But when you're disassembling a program you have no idea what the labels were (other than the labels for the TIA and RIOT addresses), so you end up with non-meaningful labels like LF123 for the label that corresponds to address $F123. All you need to do is add the bank number to that-- e.g., B1LF123 would be $F123 in bank 1, and B2LF123 would be $F123 in bank 2. Now, I'm not familiar with how Stella handles ROMs-- whether it loads the entire ROM at once into a single chunk of memory, or whether it loads the banks into separate chunks of memory, etc. So the idea I've described above might not be suitable.
  16. That's a good question! SLEEP is a macro, which is a special kind of thingy you're going to run into from time to time with assembly programs for the 2600. Perhaps the easiest way to understand what a macro is would be to think of it as a kind of abbreviation or shorthand for saying something else. But that's only partly right, because a macro can use arguments-- SLEEP being an example of such a macro. When you put a command like "SLEEP 13" in your assembly program (and assuming the SLEEP macro is defined within your program-- which it is if you're including the MACRO.H file), the assembler will substitute the "SLEEP 13" command for the code that's defined within the SLEEP macro. And since the SLEEP macro takes an argument, the code that gets inserted in place of the SLEEP command will vary based on the argument. For example, "SLEEP 2" will be replaced by a NOP command, whereas "SLEEP 4" will be replaced by two NOP commands in a row. Any even-numbered argument-- 2, 4, 6, 8, etc.-- will be replaced by half as many NOPs, since one NOP wastes 2 cycles. Any odd-numbered argument will be replaced by a certain number of NOPs plus either a "NOP 0" or a "BIT 0" command, which wastes 3 cycles. So using three NOPs in a row is exactly the same as saying "SLEEP 6," and using a NOP followed by "BIT 0" is the same as saying "SLEEP 5." I used "BIT 0" instead of "NOP 0" because "NOP 0" is an "illegal" opcode. #37*76/64 is doing high-end math, but it isn't done in the assembly program-- it gets done by the assembler during the assembly process. The actual code will end up being "LDX #43" because 37*76/64=43.9375 and the assembler will drop the decimal places. What that code is doing is setting the timer for about 37 scan lines' worth of time. On the 2600 a scan line lasts 76 cycles, so 37 scan lines last 37*76=2812 cycles. But we can't set the timer to 2812 because the highest we can set it to is 255. On the other hand, we can set the timer to count down in intervals of 1 cycle, 8 cycles, 64 cycles, or 1024 cycles. We can't use 1-cycle intervals because 2812/1=2812, which is greater than 255. Likewise, we can't use 8-cycle intervals because 2812/8=351.5, which is still greater than 255. But 2812/64=43.9375, which is less than 255, so we can set the timer to 43 and tell it to count in 64-cycle intervals. That's what TIM64T is-- it's the location we write to when we want to set the timer and have it count down 64 cycles at a time. Since 43*64=2752, setting TIM64T to 43 will make the timer count down from 43 to 0 over a period of about 2752 cycles-- really 2754 cycles, because during the first cycle it will be 43, then in the second cycle it will be 42, after which it will decrement every 64 cycles, and then we need one more cycle before it rolls over from 0 to 255 and sets the timer interrupt flag. So if we set TIM64T to 43, the timer interrupt flag will get set (indicating that the timer has finished counting down) 2754 cycles later. Since there are 76 cycles in a scan line, that's 2754/76=36.2368 scan lines. I suspected you were trying to draw a circle when I saw the program run. There's three ways to fix it: (1) Turn off the mirroring (as you say you've done). That means in addition to rearranging the second set of PF writes you'll also need to change the data around to compensate. EDIT: Actually, in this case, all you need to do is change the second PF2 to PF0, and change the second PF0 to PF2. Leave the LDA commands alone-- just change the STA commands. EDIT #2: Sorry, flip the LDA statements, too. I should have done it myself before trying to answer. I'm attaching the updated code. (2) Leave the mirroring on and leave the second set of PF writes as they were, but change the data around. Either way you need to redo the data for the last three strips. (EDIT: Sorry, not for the first solution; just swap the STA commands as indicated above.) However, drawing an aymmetric playfield without mirroring is actually easier as far as timing constraints, because you have more leeway as to when you need to update the PF registers for the right half. (3) Leave the mirroring on but take out the second set of PF writes entirely. Then the right half of the playfield will be a mirror image of the left half. But since the exercise was to do an asymmetrical playfield, I guess you don't want to use the third option. But the playfield *is* an asymmetric playfield as it is, since the right half is different than the left half. basekernel.asm basekernel.asm.bin
  17. Here is a modified version of basekernel.asm that works. I moved some of the code around, renamed one of the line labels, changed the branching logic at the end of the playfield loop, changed the y index to decrement instead of increment, changed the Overscan and VerticalBlank to use the timer, and inserted some NOPs and BIT 0 instructions to get the timing right. I would have changed the x index to decrement as well, but at that time I wasn't sure what the playfield was going to look like and I didn't want it to be upside down! basekernel.asm basekernel.asm.bin
  18. D'oh! I must be having a bad night-- I didn't notice that the playfield color IS being set in basekernel.asm! So the black screen is because the initialization is falling through into the playfield data and crashing the Atari. Anyway, I'm modifying basekernel.asm to reflect my suggestions, and will post it shortly when I'm done.
  19. I wouldn't have done it that way, either-- but I think basekernel.asm and mockup.asm were taken from Andrew's tutorials (which I've never worked through), and I think his intent was to give an example where the reader could just change the data in mockup.asm to draw a different playfield. But there are some issues with basekernel.asm, the biggest being that it's incomplete-- e.g., "; delay as appropriate" was obviously intended to be replaced by some code, presumably NOPs, although the exact amount of delay needed would depend on how many other instructions (for drawing the sprites, missiles, and/or ball) were also being performed at that point in the code. As such, it isn't a complete example, it's really more of a "partial program" to illustrate how to draw an asymmetrical playfield, with the understanding and expectation that each reader will fill in the rest of the code with whatever other instructions they'd like to include-- not the least of which should be setting the color registers to something besides black!
  20. As far as what I said about the loop where the playfield is being drawn, some delay timing (NOPs or whatever) should be inserted between the left side of the playfield and the right side, so the second STA PF2 occurs at cycle 48, but you also need to be sure the overall timing is kept stable-- i.e., that the loop always starts at a particular cycle so the writes will always occur at the expected times.
  21. I'm disagreeing with where you said "What you are doing is best described as a macro." It isn't a macro, it's just screen data. Putting it at the beginning of the program (ahead of Reset) would move it out of the way and also ensure that the data lines up with a page boundary, so that's fine-- but if you do that, it should go between "ORG $F000" and Reset. I generally stick data at the end of the code, rather than at the beginning or in the middle, which is why I suggested putting it just after the overscan and before "ORG $FFFA." EDIT: I answered too quickly to your second post and didn't see where you mentioned putting it after "ORG $F000."
  22. I disagree. It looks to me like mockup.asm is nothing more than screen data for the playfield, the intent being to create a base program with easily-changed screen data-- just change the data in mockup.asm and the entire playfield changes when the program is reassembled. As such, the include for mockup.asm could be moved to the end of basekernel.asm, just before ORG $FFFA. In fact, I think it *should* be moved there, since there's no code to jump over the data, hence the initialization code will fall through into the screen data instead of falling through into StartOfFrame as it should.
  23. I haven't tried assembling and testing the program, nor have I studied it in depth, but I see three issues in basekernel.asm: (1) You aren't setting any colors, therefore everything on the screen will be black, since the Clear loop initializes the four color registers to 0 (black). (2) The first instruction in StartOfFrame needs to be changed from LDA #0 to LDA #2 so the VBLANK will be turned on, not off. Having VBLANK turned on during vertical sync can interfere with the vertical sync signal. Personally, I like to start my kernels with the so-called EDIT: (Duh, I forgot to finish that sentence!) Personally, I like to start my kernels with the so-called "overscan" instead of the vertical sync, although this is a matter of personal preference and most programmers seem to prefer starting with the vertical sync. (3) In ALine, the comment that says "; delay as appropriate" needs to be replaced with code to delay the three LDA and STA instructions for the right half of the screen so they will occur at the appropriate times. You should probably also rearrange their order-- i.e., for the left side set them in the order PF0, PF1, and PF2; but for the right side set them in the order PF2, PF1, and PF0-- since you're drawing a reflected playfield. For a reflected playfield the second write to PF2 should occur at exactly cycle 48, then you can write to PF1 and PF0 after that. EDIT: (4) Either move the include for mockup.asm to the end of basekernel.asm, just before the "ORG $FFFA" line, or put a "JMP StartOfFrame" command just before the include for mockup.asm.
  24. My favorite for nostalgic reasons is FireWorld. But my favorite game to play is Millipede-- although the game I actually play the most is Adventure.
×
×
  • Create New...