Jump to content
IGNORED

Can you help me with ASM?


Sergioz82

Recommended Posts

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.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

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 by Sergioz82
Link to comment
Share on other sites

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.)

Link to comment
Share on other sites

@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? 

 

 

 

 

 

 

Edited by Sergioz82
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

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.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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:

 

 

Immagine.jpg

Link to comment
Share on other sites

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 by Stuart
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

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]

 

 

 

 

 

  • Like 1
Link to comment
Share on other sites

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.

  • Like 2
Link to comment
Share on other sites

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.

 

  • Like 2
Link to comment
Share on other sites

  • 1 month later...

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 by Sergioz82
  • Like 2
Link to comment
Share on other sites

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 by apersson850
Link to comment
Share on other sites

@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 by Sergioz82
  • Like 3
Link to comment
Share on other sites

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:

  1. You have allocated (reserved) a piece of memory with DATA and initialized it to zero
  2. 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)

 

  • Like 4
Link to comment
Share on other sites

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.

  • Like 3
Link to comment
Share on other sites

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.

  • Like 4
Link to comment
Share on other sites

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. ;)

 

  • Like 8
Link to comment
Share on other sites

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. 

  • Like 1
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...