Jump to content

Photo

Help with F8 processor programming


9 replies to this topic

#1 atari2600land OFFLINE  

atari2600land

    Channel F Viewer

  • 11,403 posts
  • Location:Salem, Oregon

Posted Fri Mar 9, 2018 12:11 AM

So I'm making a game for the Channel F and I'm having a bit of trouble. Surely there is a better way of doing what I want to do here. But darned if I can figure out what to do. Here is the code I want to shorten, but I can't figure out how:

    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    inc
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    inc
    inc
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives    

    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    inc
    inc
    inc
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives    
    
    
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    ai 0
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives        
    
 
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    ai 255
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives            
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    ai 254
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives            
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    ai 253
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives        
    
    LISU 3
    LISL 2

    lr A, S

    LISU 3
    LISL 6
    
    lr S, A
    ai 252
    lr S, A
    
    LISU 3
    LISL 4  
    
    XS S      ; You now get EXOR between A (from 'O'63) and 'O'62   
    bz subLives  

See what I mean? Any help is appreciated.



#2 carlsson OFFLINE  

carlsson

    Metagalactic Mule

  • 7,535 posts
  • Location:Västerås, Sweden

Posted Mon Mar 12, 2018 6:14 AM

Do LISU 3 and LISL 6 fill a purpose, or will LR S,A simply overwrite ISAR with the value of the accumulator anyway? Also will AI 0 make a difference? I understand that AI 255, AI 254, AI 253 are there due to lack of DEC operands.

 

Does it matter in which order you are testing? I mean now it is +1, +2, +3, 0, -1, -2, -3, -4. If it was possible to set up a loop that goes from -4 to +3 or perhaps 7 to 0 with some clever offsetting, you might be able to save a little code, but I don't understand enough to suggest how it is done.



#3 carlsson OFFLINE  

carlsson

    Metagalactic Mule

  • 7,535 posts
  • Location:Västerås, Sweden

Posted Mon Mar 12, 2018 6:48 AM

Entirely dry coded based on assumptions. I may have completely misunderstood your code. It might work just as expected, almost as expected or not at all.

 

    LIS 8
    LR r0,A ; load scratchpad register 0 with 8, which is used as a counter and offset
 
loop:
    LI (6*16)+2 ; equals LISU 3; LISL 2 if I understand correctly but goes directly to the accumulator
    AS r0 ; add register value
    AI 251 ; subtract to get the desired offset
    LR IS,A
 
    LI (6*16)+4
    XS S
    BZ subLives 
    
    DS r0 ; decrease the scratchpad register, should affect flags
    BNZ loop


#4 atari2600land OFFLINE  

atari2600land

    Channel F Viewer

  • Topic Starter
  • 11,403 posts
  • Location:Salem, Oregon

Posted Mon Mar 12, 2018 8:12 AM

LISU 3

LISL 6

is the place of the variable where I store the changed lettuce Y's direction for testing collisions so it doesn't screw up the actual Y position of the lettuce to appear on screen.



#5 carlsson OFFLINE  

carlsson

    Metagalactic Mule

  • 7,535 posts
  • Location:Västerås, Sweden

Posted Mon Mar 12, 2018 8:32 AM

Ok, so LR S,A doesn't load the value of the accumulator into ISAR as I imagined it would? I understand the regular registers go 0-11, 0-B or A-L depending on the assembler syntax, so I didn't know what S otherwise would be. It kind of seemed odd that you would be using immediate values all the time, but if those form addresses where values are read from, it makes more sense.

 

But even so, I would think my routine can be modified to work?

 

   LIS 8
    LR r0,A
 
loop:
    LISU 3
    LISL 2
 
    lr A, S
 
    LISU 3
    LISL 6
    
    lr S, A
    AS r0
    AI 251
    LR S,A
 
    LISU 3
    LISL 4  
 
    XS S
    BZ subLives 
    
    DS r0
    BNZ loop


#6 atari2600land OFFLINE  

atari2600land

    Channel F Viewer

  • Topic Starter
  • 11,403 posts
  • Location:Salem, Oregon

Posted Mon Mar 12, 2018 9:16 AM

Yes, there are those types of registers, but for values since I'm trying to learn this I put them into LISU/L positions. This is all still very new to me.



#7 Kurt_Woloch OFFLINE  

Kurt_Woloch

    Stargunner

  • 1,370 posts

Posted Mon Apr 23, 2018 1:42 PM

OK, now... seems like this thread got abandoned without finding a proper solution for the problem.

I'll try to find one here, but let's go step by step so that you can follow me.

First, let's see what this code actually does. I'll just pick the first sequence here.. but first, let's get some terminology straight...

 

The ISAR is actually a pointer to one of the 64 registers in the register file. Its upper half (3 bits) gets loaded with LISU, and its lower half (3 bits) with LISL. So LISU 3, LISL 2 selects register 2 in bank 3, if we say we have 8 banks with 8 registers each. The ISAR retains its value until it gets overwritten, so if you access multiple registers in the same bank, you only have to update the lower half of the register by doing LISL, but not LISU.

 

Then, LR S, A does not overwrite the ISAR (which would be the pointer), but the register which the ISAR currently points to, with the contents of A. Therefore, LI (6*16) + 2 does not set the ISAR, but only the A register. To write that value to the ISAR, you have to do LR IS, A. However, this takes more bytes than doing LISU and LISL. These two only take one byte each while you need 2 bytes for the LI (6*16) + 2 instruction and one more for LR IS; A. In Carlsson's first proposal, before this is done, the loop value is being added (minus a constant) in order to loop through the registers. But that's not what the original code does, instead it always works on the same three registers.

 

So, with that out of the way, let's see what the code actually does...

    LISU 3 ;point the ISAR at bank 3
    LISL 2 ;register 2 of bank 3

    lr A, S ;load the value from that register into A

    LISU 3  ;point the ISAR at bank 3 (this is useless, it already points at bank 3!)
    LISL 6  ;register 6 of bank 3
    
    lr S, A ;load the value from A into that register (which is useless because it's not accessed after that)
    inc     ;increment A
    lr S, A ;and load the value from A into the same register again (without the first value having been used)
    
    LISU 3  ;point the ISAR at bank 3 (this is useless, it still points at bank 3!)
    LISL 4  ;register 4 of bank 3
    
    XS S    ;EXOR the value of A (which is also in Register 6 of bank 3) with the value of register 4 bank 3
    bz subLives ;and branch to subLives if the value in A is now 0, that is, the EXORed values were the same

Now maybe there's one more useless line up there, but only if the value of register 6 of bank 3 isn't being used after branching to subLives. In that case we don't need to write to that register at all because the changed value is in A anyway, and we don't need it after the comparison.

 

Now, Carlsson's 2nd attempt has put this into a loop, but still retains the useless statements shown above. With those gone, Carlsson's code would look like this...

    LIS 8  ;start loop at 8
    LR r0,A ;into r0

    LISU 3 ;select bank 3 for whole loop
loop:
    LISL 2 ;read value from register 2 in bank 3
 
    lr A, S ;lr S, A obviously is useless in that case as well since the value is still there
    
    AS r0  ;add loop variable, then subtract 5 (?)
    AI 251
 
    LISL 4  ;point to register 4 to compare to
 
    XS S  ;eor
    BZ subLives  ;and branch if equal
    
    DS r0  ;otherwise decrease the loop value and loop back if > 0
    BNZ loop

Now this is still not the optimum solution because while the code is shorter, it's sill being executed in a loop, so it actually takes longer to execute than the unrolled code.

 

However, the actual purpose of the code seems to be to check if the contents of register 4 in bank 3 fall within a range of -4 to 3 plus the contents of register 2 of bank 3. This doesn't have to be compared in a loop, rather you only need two comparisons against an upper and a lower boundary, which again depends on one of the two registers

 

So, similar to the code posted above, I'd do this as follows:

    LISU 3 ;point the ISAR at bank 3
    LISL 2 ;register 2 of bank 3

    LR A, S ;load the value from that register into A
    COM ;complement A (this means A = 255 - A)

    LISL 4  ;point to register 4 of bank 3
    
    AS S    ;add that register to A, so now A contains the value of (Register 2 - Register 6 of bank 3 - 1)

    CI 2    ;Set carry flag if A > 2
    BNC subLives ;branch to subLives if carry was clear (Reg. 2 - Reg. 6 <= 3)

    CI 250  ;else compare against 250 (set carry flag if A > 250)
    BC subLives ;branch to subLives if carry was set (Reg. 2 - Reg. 6 > -5)

Let's see how this one works... we'll do some examples.

Example 1: Reg. 2 = 100, Reg. 4 = 96. In this case, the collision should just fire...

100 gets loaded from Reg. 2 and complemented to 155. We add 96 from Reg. 4 and get 155 + 96 = 251.

CI 2 here sets the carry flag because 251 is greater than 2, so the BNC command doesn't get executed.

CI 250 here sets the carry flag because 251 is greater than 250, so the BC command fires, and a collision is registered.

 

Example 2: Reg. 2 = 100, Reg. 4 = 95. In this case, the collision should not fire...

100 gets loaded from Reg. 2 and complemented to 155. We add 95 from Reg. 4 and get 155 + 95 = 250.

CI 2 here sets the carry flag because 250 is greater than 2, so the BNC command doesn't get executed.

CI 250 here clears the carry flag because 250 is not greater than 250, so the BC command doesn't fire as well, and no collision is registered.

 

Example 3: Reg. 2 = 100, Reg. 4 = 103. In this case, the collision should just fire...

100 gets loaded from Reg. 2 and complemented to 155. We add 103 from Reg. 4 and get 155 + 103 = 258 - 256 = 2.

CI 2 here clears the carry flag because 2 is not greater than 2, so the BNC command gets executed and registers the collision.

 

Example 4: Reg. 2 = 100, Reg. 4 = 104. In this case, the collision should not fire...

100 gets loaded from Reg. 2 and complemented to 155. We add 104 from Reg. 4 and get 155 + 104 = 259 - 256 = 3.

CI 2 here sets the carry flag because 3 is greater than 2, so the BNC command doesn't fire as well.

CI 250 here clears the carry flag because 3 is not greater than 250, so the BC command doesn't fire as well, and no collision is registered.

 

I think this is a pretty effective solution which doesn't need any additional registers or loop variables except for A.


Edited by Kurt_Woloch, Mon Apr 23, 2018 1:44 PM.


#8 carlsson OFFLINE  

carlsson

    Metagalactic Mule

  • 7,535 posts
  • Location:Västerås, Sweden

Posted Tue Apr 24, 2018 3:41 AM

Thanks for the explanations! I'm sure they're useful for those getting deep into the F8. I understood that atari2600land feared he was running out of ROM space, and thus wanted to optimize routines for size rather than speed. However as I found, several other Channel F programs are far bigger than his, so unless there is a limitation on the cartridge PCB to be used to make physical copies, I don't think restricting for size at the cost of content would be desired.

#9 Kurt_Woloch OFFLINE  

Kurt_Woloch

    Stargunner

  • 1,370 posts

Posted Tue Apr 24, 2018 11:54 AM

Well, in this case I think that my version was improved both speed- and size-wise since like Atari2600land's original version, it runs down in a straight line, only that it's much shorter and thus also should execute much faster. But I agree that there's often a trade-off between speed and size, for instance, you can probably draw objects the fastest if they're stored with a pixel per byte in the ROM, while compressing them saves space, but takes additional time for un-compressing. And sometimes you may also have to optimize for memory footprint, which is evident in the Channel F where there's effectively only 64 bytes of readable and writable RAM (in addition to the video RAM you can't read from). Thus you may be forced to compress the game data you store somewhat to get it all into memory, which slows down execution, or draw as much data as possible off pre-calculated tables in the ROM instead of generating it on the fly.



#10 atari2600land OFFLINE  

atari2600land

    Channel F Viewer

  • Topic Starter
  • 11,403 posts
  • Location:Salem, Oregon

Posted Wed Apr 25, 2018 2:10 PM

OK, I put Kurt's code in and it works great! I was able to put in some more stuff since the code was trimmed a lot.






0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users