+mizapf Posted July 12, 2022 Share Posted July 12, 2022 The IDLE command is probably the only one that has a useful effect on the 99/4A, and side effects have not yet been reported. The problem (and the reason why TI stated that these commands "should not be used on the Home Computer") is that they all output a specific pattern on the address bus (A0-A2), and the CRUCLK is pulsed. This may look like a CRU output operation to attached devices, in particular the one at 0000. The difference to the CRU output operations is that those have A0-A2 set to (0,0,0), and the IDLE command puts a (0,1,0) on A0-A2. But we usually don't decode A0-A2. 1 1 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 12, 2022 Author Share Posted July 12, 2022 (edited) 3 hours ago, PeteE said: Lots. A search for "VSYNC" should get you what you're looking for. Here is what I've settled on: VSYNC CLR R12 ; CRU Address bit 0002 - VDP INT VWAIT TB 2 ; CRU bit 2 - VDP INT JEQ VWAIT ; Loop until set MOVB @VDPSTA,R12 ; Clear interrupt flag manually since we polled CRU The 9918A VDP sets an interrupt signal at the start of vertical blank (59.94 times per second) which is immediately after the 24th line of characters at the bottom of the visible portion of the screen, where it starts displaying solid border color. The VDP interrupt is connected 9900 CPU interrupt, and also to the 9901 chip which is connected to the CRU signals on the CPU. To read the interrupt from the 9901 we need to read CRU address 2, using the TB (test bit) instruction relative to the address in the R12 register. The JEQ spins in a loop until the interrupt occurs, but then interrupt must be cleared by doing a read of the VDP status register. Normally the ISR (interrupt service routine) would handle reading the status register after the interrupt, but I always run with the CPU interrupts off and the ISR disabled, by using "LIMI 0" at the start of my program. Thanks. It's night here and it's too late to code tomorrow I'm going to try this solution and I'll let you know. About interrupts, I do as suggested in Compute! Assembly manual: two consecutive instructions: LIMI 2 LIMI 0 I use them at the beginning of each cycle: Game loop: Set interrupts on/off Move pacman Move ghosts Check collision Update vdp Count to 1000 loop to slow down the game Repeat Should I move the instructions somewhere else? Edited July 12, 2022 by Sergioz82 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted July 12, 2022 Share Posted July 12, 2022 Your code looks OK. You don't usually get much screen tearing with sprites, so if you get a lot something else must be wrong. Could the problem be that either ATTLST or SPSHFT is an odd address? (I would drop the intermediate buffer and write directly to the VDP, but your code should be fine if speed is not very important.) Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 13, 2022 Author Share Posted July 13, 2022 (edited) @PeteE It works very well, thanks again. Please see below the difference with and without vsync @Asmusr With vsync things improved drastically. I also moved all the byte variants at the end of definition area so they don't mess with addressing in case they're odd number. I'm not very convinced of the intermediate buffer either. My alternative was to use SPSHFT and SWPOFF directly on VDP (SPSHFT pointing to >0300) but I had the doubt of doing multiple call to VMBW instead of a cumulative one. Would it be better? SpacmOk.mp4 SpacmGlitch.mp4 Edited July 13, 2022 by Sergioz82 1 1 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted July 13, 2022 Share Posted July 13, 2022 43 minutes ago, Sergioz82 said: I'm not very convinced of the intermediate buffer either. My alternative was to use SPSHFT and SWPOFF directly on VDP (SPSHFT pointing to >0300) but I had the doubt of doing multiple call to VMBW instead of a cumulative one. Would it be better? Once you have set up the VDP write address, you can use movb rx,@vdpwd to write the individual bytes, so there's no need for multiple VMBW calls. I use a function called VWAD that just sets up the VDP write address without writing anything. It's the same code as VSBW without the actual data write. 1 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 13, 2022 Share Posted July 13, 2022 4 minutes ago, Asmusr said: I use a function called VWAD that just sets up the VDP write address without writing anything. I do that too with two sequential entry points, one for write and one for read addresses. Do you call it as sub-routine or put it inline? Quote Link to comment Share on other sites More sharing options...
Tursi Posted July 14, 2022 Share Posted July 14, 2022 I quite like the look of that! 1 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 14, 2022 Author Share Posted July 14, 2022 23 hours ago, Asmusr said: Once you have set up the VDP write address, you can use movb rx,@vdpwd to write the individual bytes, so there's no need for multiple VMBW calls. I use a function called VWAD that just sets up the VDP write address without writing anything. It's the same code as VSBW without the actual data write. Thanks. I have the source code of Munch Man, in the last page there's a VDP write routine that I think it does what you say, if I understood it correctly: Quote Link to comment Share on other sites More sharing options...
Stuart Posted July 14, 2022 Share Posted July 14, 2022 (edited) On 7/12/2022 at 9:24 PM, HOME AUTOMATION said: But, doesn't the use of IDLE, violate the 4A's, hardwiring? Indeed, the console and PEB technical data manual warns against it. The instruction gets interpreted as a CRU operation ... (Ignore - just seen mizapf's earlier reply) Edited July 14, 2022 by Stuart Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 14, 2022 Share Posted July 14, 2022 53 minutes ago, Sergioz82 said: I have the source code of Munch Man, in the last page there's a VDP write routine that I think it does what you say, if I understood it correctly: No, that's the source code of VMBR and VMBW equivalents. All read/write operations are completed as a single block. What @Asmusrreferred to is when you set up the address on one occasion, then do occasional reads (or writes) from/to the data address now and then, without reloading the address. As long as nothing else changes the VDP address, it will just autoincrement as you read data. Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 14, 2022 Author Share Posted July 14, 2022 @apersson850 Oh, I see. I searched this forum for VWAD and found the explanation, I'll try to implement it in my game. Thanks Quote Link to comment Share on other sites More sharing options...
matthew180 Posted July 16, 2022 Share Posted July 16, 2022 On 7/12/2022 at 9:33 AM, Sergioz82 said: I'll try to figure out how to check and wait the vertical blank. Are there any topics on this forum? There is a long running assembly thread where just about everything has been discussed: game loops, VDP programming, vsync checking, stack and function calling, sound, random numbers, etc. https://atariage.com/forums/topic/162941-assembly-on-the-994a/ On 7/12/2022 at 1:39 PM, PeteE said: Here is what I've settled on: VSYNC CLR R12 ; CRU Address bit 0002 - VDP INT VWAIT TB 2 ; CRU bit 2 - VDP INT JEQ VWAIT ; Loop until set MOVB @VDPSTA,R12 ; Clear interrupt flag manually since we polled CRU Note that PeteE's routine suspends your program and polls for the VSYNC. If this is what you want, then it is a good solution, however there are other ways as well, which do not involve polling in a tight loop, that let your program do other work that it might need to do if it is not time to update the screen. On 7/12/2022 at 5:32 PM, Sergioz82 said: About interrupts, I do as suggested in Compute! Assembly manual: two consecutive instructions: LIMI 2 LIMI 0 I use them at the beginning of each cycle: You should know what the ISR on the 9900 does before allowing it to run. See the Assembly thread above, it is covered in detail around pages 2, 10, and 38 (IIRC). When writing something like a game in assembly, the ISR offers little benefit but takes a lot of cycles from your program. It can also interfere with your code if you don't know what areas of memory you have to avoid if you are allowing the ISR to run. In a game you are usually managing your own sprite movement, sound processing, and joystick / keyboard input, so you do not need the console ISR. Just set `LIMI 0` at the start of your program and carry on with your code. 3 1 Quote Link to comment Share on other sites More sharing options...
+FarmerPotato Posted July 16, 2022 Share Posted July 16, 2022 On 7/12/2022 at 7:46 AM, mizapf said: TMS9918 specs: 2.1.6 VDP Interrupt The VDP INT output pin is used to generate an interrupt at the end of each active-display scan, which is about every 1/60 second for the TMS9918A/9928A and 1/50 for the TMS9929A. ... I have always wondered--having no direct experience--how did this affect all the TI games? Besides running slower/faster? Software developed on 9929A then run on 9918A? Other way round? Did anyone see sprite auto-motion glitches? How about with Extended Basic games or anything with sprite automotion? Or music tempo or sound effects not syncing up? Is CALL COINC more precise with 50 Hz motion and not precise enough at 60 Hz? Supposing that programmers test at just one speed! At least musical frequencies are the same. Only interrupt-driven tempo different. [I live in a 9918 60-Hz world] 1 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted July 16, 2022 Share Posted July 16, 2022 1 hour ago, FarmerPotato said: I have always wondered--having no direct experience--how did this affect all the TI games? Besides running slower/faster? Lift 2 in Parsec never made sense to me. It was somewhat too fast in some situations and too slow in others, so I only used 1 and 3. When I tried Parsec with 60Hz, I noticed that I could very well make use of Lift 2. Also, I noticed that with 60Hz, the enemy vessels dive deeper into the screen, almost crashing with the ground. With 50 Hz, they stay higher. 2 Quote Link to comment Share on other sites More sharing options...
matthew180 Posted July 16, 2022 Share Posted July 16, 2022 2 hours ago, FarmerPotato said: I have always wondered--having no direct experience--how did this affect all the TI games? Besides running slower/faster? The answer is: it depends. It really comes down to the game loop and how the developers modeled position updates and such. Most devs BITD probably assumed 60Hz (or 50Hz), and also assumed their loops never ran longer than the time slice (VSYNC period). Those assumptions probably lead to unexpected behavior in some games, like what mizapf pointed out with the enemy going deeper on 60Hz vs 50Hz. With a little bit of effort it is possible to mitigate these affects. The assembly game loop on pg.2 of the assembly-thread posted above allows you to detect when you have over run your vsync. With a little initialization code it could be made to detect 60Hz vs 50Hz, which could then be factored into movement and other timing. 2 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 17, 2022 Share Posted July 17, 2022 It's when writing stuff like this I like my console modification a lot. Since I can overlay the ROM with RAM, I can change the interrupt service routines to be whatever I like. 3 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted August 30, 2022 Author Share Posted August 30, 2022 (edited) Hi everyone, I'm going on with the development, I improved the wall detection for 2X sprites and now pacman turns correctly. Ghosts also can move in the labyrinth in random mode and in chase mode (this one has still some work to do). Wait for V-Blank has been implemented. I have now a weird problem: I set up a routine to move timers (it will be used for ghosts/pacman speed, energizer duration and so on) but it only works when the address of the timer is numeric, if I use E/A labels it stops working.. That is, it doesn't work anymore if instead of >B400 and >B401 I define GHSPD DATA >0000 (or GHSPDH BYTE >00 and GHSPDL BYTE >00 for example) and I use the label in the same code. I also tried using registers instead of using other bytes in memory (example: LI R0,>0100 in place of @H01) but still nothing. I compile using R option in E/A and I'd prefer to be consistent and not use fixed addresses. What am I doing wrong? Here's the code: UPDTIM MOVB @H01,@>B400 *move number of waits in high byte (ghost slow down: number of loops to skip for ghosts movement routine) CB @>B401,@>B400 *check if low byte value is greater JGT SETZ *it is, set it back to zero AB @H01,@>B401 *it is not, increment low byte JMP NXTIM *done SETZ MOVB @H00,@>B401 *set low byte back to zero NXTIM B *R11 and this is how it is used in main loop LP LIMI 2 LIMI 0 BLWP @MOVPAC CB @>B400,@>B401 *is the counter in low byte equal to the number of cycles to wait in high byte? JNE FWAIT *no, don't move ghosts BLWP @MOVFAN FWAIT BL @CHKFCO BL @UPDTIM BLWP @UPDVDP JMP LP Edited August 30, 2022 by Sergioz82 2 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted August 30, 2022 Share Posted August 30, 2022 (edited) Quite complex code to just count to one. Why do you mess with byte variables? You're probably spending more memory bytes on handling them rather than using 16-bit values. I can't see any reason for why you shouldn't be able to use a named label instead of absolute addresses. Of course, if you use a DATA directive then you need to use the LABEL for the high byte and LABEL+1 for the low one. Edited August 30, 2022 by apersson850 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted August 30, 2022 Share Posted August 30, 2022 If you use a debugger and set a breakpoint in UPDTIM you should be able to see what the problem is. Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted August 30, 2022 Author Share Posted August 30, 2022 (edited) @apersson850 For now it's +1 increment for testing but the idea is to made it variable together with pacman's speed to add bonus/penalties according to level and the pick up of some items. However while testing I noticed +1 increment is already more than what I need, higher numbers make the ghosts too much slow. Probably I'll leave this part to later development. I'll try to simplify the code by handling words instead of bytes as you suggest. Didn't think on this very much, I just thought it would have been a neat organization to use high byte and low byte for a common purpose. In general I belie ve the code will have large margins of optimization once it's done as I'm learning ASM coding as I progress. @Asmusr Ok, I'll try. I asked here because I had the doubt that working on bytes in the same word might have some peculiarities (for example using registers for intermediate storage) as TI has many I don't know. But if for you guys labels shouldn't make any difference then surely I did something wrong. I'll try again. Thank you in the meanwhile Edited August 30, 2022 by Sergioz82 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted August 30, 2022 Share Posted August 30, 2022 6 hours ago, Sergioz82 said: I have now a weird problem: I set up a routine to move timers (it will be used for ghosts/pacman speed, energizer duration and so on) but it only works when the address of the timer is numeric, if I use E/A labels it stops working.. In reading your statement above but without seeing the code that "stops working" I might be able to offer some help. ? When I first looked into learning assembler after only using BASIC, it was not clear to me what the different language components actually do and that made it harder. My apology if you already know this but it was one my confusion points in the beginning. A label in Assembly language records the memory address of a line in your program where the label is placed. The label is held by the assembler program only. -BUT- If you wanted to increment/decrement the contents of an address named by a label, you must make that address free of anything else. (like instructions for example) The simplest way to do that is with the DATA statement for an integer or a BYTE statement for byte. LABEL1 DATA 0 The line above is equivalent to making a "variable" called LABEL1 because you did two things: You have allocated (reserved) a piece of memory with DATA and initialized it to zero You have given a name to that piece of memory with the label beside it. Now for example you can write a loop to decrement that "variable" until it hits zero again, like this: LOOP1 DEC @LABEL1 JNE @LOOP1 If I have this all wrong from your perspective my apology again. For me things only made sense after I understood what language parts do something during the Assembly process (DATA & labels etc.) and what parts do something later when my program is running. (instructions) 4 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted August 30, 2022 Share Posted August 30, 2022 That's a very important distinction. Items like DATA and BYTE are assembler directives, telling it what to do when assembling the program. BLWP, A and JEQ are instructions. They do something when the program is running. A label is a marker of something, some data or an instruction. When the program is assembled, it's made relative the program's first instruction (or data) location. When the program is loaded, it's tanslated to a real address, by adding the offset from the start to the loading address of the first instruction in memory. 3 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted August 30, 2022 Share Posted August 30, 2022 2 hours ago, Sergioz82 said: @apersson850 I'll try to simplify the code by handling words instead of bytes as you suggest. Didn't think on this very much, I just thought it would have been a neat organization to use high byte and low byte for a common purpose. Remember that some instructions can only work on words. CLR, SETO, INC and LI are typical examples.This means that clearing a variable is simpler if it's 16 bit, and so on. As a general thing, counting down to zero is more efficient, since the DEC instruction will automatically compare the result to zero. Thus you don't need to do an explicit compare to figure out if the loop is completed. Sometimes you can also use the fact that if you call a routine by BLWP, then when you do a RTWP the status register will be loaded from R15. Thus you can set the status bits to indicate something to the caller, where you can immediately test the outcome by following the BLWP with a JEQ or whatever is appropriate. The BLWP call by itself is more expensive, but if it saves you some other acrobatic stuff to control you program flow, it may be cheaper in the end. 4 Quote Link to comment Share on other sites More sharing options...
Tursi Posted August 31, 2022 Share Posted August 31, 2022 If you're worried about performance, the number one distinction on the 4A is number of instructions executed. The number of instructions trumps almost every other technique in almost every scenario... even shifts can lose to the very slow DIV if you need more than one of them. It doesn't sound like performance is your concern just yet though. Definitely learn to use a debugger. You can read your code over and over, and show it to other people, and walk through it in your head, but there's nothing so quick at shattering misconceptions that you didn't know you had like stepping through the code and checking the result of each instruction as it works on real data. 8 Quote Link to comment Share on other sites More sharing options...
Willsy Posted August 31, 2022 Share Posted August 31, 2022 12 hours ago, TheBF said: For me things only made sense after I understood what language parts do something during the Assembly process (DATA & labels etc.) and what parts do something later when my program is running. (instructions) Yes that's a very import distinction. In Assembler parlance, we call the instructions of our code 'instructions', and the instructions for the assembler are called 'directives'. An instruction is assembled into your program. That's it's job after all. Directives are there to tell the assembler to do something while it is assembling your program. LI, BLWP, INC, DEC etc. are instructions (they are TMS99xx processor instructions). DATA, BYTE, BSS, BES, AORG are directives - they tell the assembler to do something while it is assembling the program. 1 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.