Jump to content

Photo

Introduction: 2600 Programming for Newbies


171 replies to this topic

#126 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Sat Apr 9, 2016 7:38 AM

Did you change COLUPF to a non-zero value? CLEAN_START sets everything to 0, so all TIA objects will be black, and black on black doesn't show up very well.

#127 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sat Apr 9, 2016 8:31 AM

Cancel that..   Guess I'm a little rusty.  I was compiling it wrong:  forgot the o in the output file name. I'm surprised DASM didn't show me an error though.  Its displaying the stripes now.



#128 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sat Apr 9, 2016 8:41 AM

Thanks Darrell- I put that seed value right after clean start assuming it would be a good place to know that wouldn't happen.  I also didn't change any of your PF colors hoping they would already be correct.  Anyway,  its working now- just a dumb mistake.  On the bright side, it forced me to go through some of your old replies so I could try to remember how to use the debugger better.  I don't have any of the symbols(labels?) showing up and I know its some option I need to use.



#129 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Sun Apr 10, 2016 10:01 AM

Sounds good!

 

If you didn't get an error for excluding the o then the first letter of your filename was a valid option for dasm.

Symbol dump is specified with the -s option. There's a readme.txt file included with all version of collect that goes into detail on it.



#130 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Tue Apr 12, 2016 8:01 AM

Ah..  The first letter was a D..  OK I understand the options better now- I assumed you could only use one and I just used -o since its the one I learned first and it at least worked. I tried reading the dasm documentation at one point but it was way above my head at that point and I didn't understand it.  I've been meaning to ask you- after compiling, I see messages- bytes until the end of the cartridge and bytes until HumanGFX.  What's the relevance of bytes until the graphics?



#131 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Tue Apr 12, 2016 8:48 AM

I've been meaning to ask you- after compiling, I see messages- bytes until the end of the cartridge and bytes until HumanGFX.  What's the relevance of bytes until the graphics?

 

 

 

Would you ask this again in the blog?  I'm sure others will wonder about that at some point and would prefer the answer to be there.  



#132 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sat Apr 16, 2016 7:00 AM

Sure!



#133 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sun Jun 5, 2016 12:51 PM

Hi-  I'm still having trouble writing a two line kernal.  Does anone know of an easy to undertand code example and/or guide?



#134 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Sun Jun 5, 2016 1:44 PM

Hi-  I'm still having trouble writing a two line kernal.  Does anone know of an easy to undertand code example and/or guide?

 
 
Don't know if this will help, but I converted CollectMini to be a 2LK.  Basically line 1 updates TIA for player0, line 2 updates for player1.
 
ArenaLoop:                  ;   23 - worse case time to get here
        sta WSYNC           ; 3 26
;---------------------------- start of line 1 of the 2LK 
        lda #HUMAN_HEIGHT-1 ; 2  2 - height of the human graphics, 
        dcp Player0Draw     ; 5  7 - Decrement Player0Draw and compare with height
        bcs DoDrawGrp0      ; 2  9 - (3 10) if Carry is Set then player0 is on current scanline
        lda #0              ; 2 11 - otherwise use 0 to turn off player0
        .byte $2C           ; 4 15 - $2C = BIT with absolute addressing, trick that
                            ;        causes the lda (Player0Ptr),y to be skipped
DoDrawGrp0:                 ;   10 - from bcs DoDrawGRP0
        lda (Player0Ptr),y  ; 5 15 - load the shape for player0
        sta GRP0            ; 3 18
        sta WSYNC           ; 3 21
;---------------------------- start of line 2 of the 2LK 
        lda #BOX_HEIGHT-1   ; 2  2 - height of the box graphics, subtract 1 due to starting with 0
        dcp Player1Draw     ; 5  7 - Decrement Player1Draw and compare with height
        bcs DoDrawGrp1      ; 2  9 - (3 10) if Carry is Set, then player1 is on current scanline
        lda #0              ; 2 11 - otherwise use 0 to turn off player1
        .byte $2C           ; 4 15 - $2C = BIT with absolute addressing, trick that
                            ;        causes the lda (Player1Ptr),y to be skipped
DoDrawGrp1:                 ;   10 - from bcs DoDrawGrp1
        lda (Player1Ptr),y  ; 5 15 - load the shape for player1
        sta GRP1            ; 3 18       
        dey                 ; 2 20 - update loop counter
        bne ArenaLoop       ; 2 22 - 3 23 if taken 
Attached File  CollectMini_2LK.bin   2KB   67 downloads

Attached File  CollectMini_2LK.asm   20.65KB   61 downloads

#135 tschak909 OFFLINE  

tschak909

    River Patroller

  • 2,841 posts
  • Location:USA

Posted Mon Jun 6, 2016 12:19 AM

Hi-  I'm still having trouble writing a two line kernal.  Does anone know of an easy to undertand code example and/or guide?

 

The example that Darrell posts below should help.

 

My advice is to just keep trying, you will get it.

 

The biggest concept that you must understand, is that with the VCS, it is ALL ABOUT THE LINE. Your job in a kernel is to figure out where to find the time to write to the bits that you want the TIA to display on a line. This can be helped with some charts that show VCS timing as to when the latest you can enable the various bits, to put them in the right place...

 

..but really, there is enough known here that a utility could be made to parse little fragments of 6502 assembler, to count cycles, and to place a dot (for missile/ball enables), or a dash (for GRP0/1 writes or resets) on a chart to tell you where you are. This would help a TON. No HMOVE calculation would really be needed...but I digress. ;)

 

-Thom


Edited by tschak909, Mon Jun 6, 2016 12:20 AM.


#136 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Tue Jun 7, 2016 6:27 AM

I think I'm going to have to take the Collect Mini kernal code and embellish every comment so I can better keep track of what's going on at any one time.  The concept of 2LK makes perfect sense to me, but when I don't seem to be able to remember which variable? is doing what and why.



#137 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Thu Jun 9, 2016 10:46 AM

Hi! I was making my way fine through line 1 of the kernal (making lots of notes) until I got to where PlayerPtr is loaded.  When I looked into the routine that generated PlayerPtr, I found:

 

   ; Set Player0Ptr to proper value for drawing player0
        lda #<(HumanGfx + HUMAN_HEIGHT - 1)
        sec
        sbc ObjectY
        sta Player0Ptr
        lda #>(HumanGfx + HUMAN_HEIGHT - 1)
        sbc #0
        sta Player0Ptr+1

 

I must not understand the first lda because it looks like its taking graphics data and adding to it and subtracting from it.  I definitely don't understand what "<" does.  PlayerPtr is eventually stored in GRP0 so it must be the graphics data.  Definitely stumped on this one...  Also, what does Ptr stand for?  Pointer?  Painter? Peter?

 

Thanks for all the help!


Edited by BNE Jeff, Thu Jun 9, 2016 10:46 AM.


#138 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Thu Jun 9, 2016 1:12 PM

Ptr = pointer. They are used to point at data. From the source where the RAM locations are defined:

    ; DoDraw Graphic Pointers in $87-8a
Player0Ptr:         ds 2    ; used for drawing player0
Player1Ptr:         ds 2    ; used for drawing player1

 
From dasm.txt:

	20  <exp    take LSB byte of a 16 bit expression
	20  >exp    take MSB byte of an expression

 
LSB is the Least Significant Byte of a value. If the expression value is $1234 the LSB is $34
MSB is the Most Significant Byte of a value. If the expression value is $1234 the MSB is $12.
 
From CollectMini_2LK.sym (symbols):

HUMAN_HEIGHT             0014                  
HumanGfx                 fa00              (R )

 
HumanGfx + HUMAN_HEIGHT - 1 = $fa00 + $14 - 1 = $fa13

So the < value is $13 while the > value is $fa. You can see that in CollectMini_2LK.lst (the listing):

    385  f8db							; Set Player0Ptr to proper value for drawing player0
    386  f8db		       a9 13		      lda	#<(HumanGfx + HUMAN_HEIGHT - 1)
    387  f8dd		       38		      sec
    388  f8de		       e5 83		      sbc	ObjectY
    389  f8e0		       85 87		      sta	Player0Ptr
    390  f8e2		       a9 fa		      lda	#>(HumanGfx + HUMAN_HEIGHT - 1)
    391  f8e4		       e9 00		      sbc	#0
    392  f8e6		       85 88		      sta	Player0Ptr+1

 
The values going across the second line of that snippet are:

  • 386 - line number in source
  • f8db - location in ROM
  • a9 - machine code instruction
  • 13 - operand as required by the instruction (0, 1, or 2 bytes in length)
  • lda #<(HumanGfx + HUMAN_HEIGHT - 1) - original source

Look at the 6502 opcodes for LDA and you'll find:

LDA (LoaD Accumulator)

Affects Flags: S Z

MODE           SYNTAX       HEX LEN TIM
Immediate     LDA #$44      $A9  2   2
Zero Page     LDA $44       $A5  2   3
Zero Page,X   LDA $44,X     $B5  2   4
Absolute      LDA $4400     $AD  3   4
Absolute,X    LDA $4400,X   $BD  3   4+
Absolute,Y    LDA $4400,Y   $B9  3   4+
Indirect,X    LDA ($44,X)   $A1  2   6
Indirect,Y    LDA ($44),Y   $B1  2   5+

+ add 1 cycle if page boundary crossed

 
lda # is the mnemonic code for machine code instruction $A9.



#139 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sat Jun 11, 2016 5:37 AM

OK,  I think I might be following a little bit... 

 

So when you:

lda (Player0Ptr),y

 

is this pointing to random garbage data on every scan line except when we are on the lines where Player 0 is drawn, it runs through the graphics lines?


Edited by BNE Jeff, Sat Jun 11, 2016 6:01 AM.


#140 tschak909 OFFLINE  

tschak909

    River Patroller

  • 2,841 posts
  • Location:USA

Posted Sat Jun 11, 2016 8:32 AM

If the Y register is 0, and Player0Ptr has been set to an address where there is graphic data (e.g. somewhere off in $F000 in the ROM via a source code label), then the Y accumulator will now hold the first byte of that data.

 

If you change that Y register to 1, then Player0Ptr will advance to the next byte in that address.

 

Technically, this is called an (Indirect),Y addressing mode. The Indirect means, that you can change the address that you're indirecting into, to something else (e.g. another table with graphics for another player), and the code will still function as expected, rendering the new data, assuming it is in the format needed.

 

The extra layer of indirection gives you more flexibility, than if you were simply just using e.g. the Indexed,X or Indexed,Y modes. (There IS a very specific form of X that is called the Indirect X mode, but it has very limited use on the 2600 due to the limited amount of actual RAM)

 

Some good refs:

https://github.com/j...6502refcard.pdf



#141 alex_79 OFFLINE  

alex_79

    Stargunner

  • 1,180 posts
  • Location:Italy

Posted Sat Jun 11, 2016 8:50 AM

So when you:
lda (Player0Ptr),y
 
is this pointing to random garbage data on every scan line except when we are on the lines where Player 0 is drawn, it runs through the graphics lines?

The instruction "lda (Player0Ptr),y" is not executed at all if the player is not being drawn on the current scanline, because it's skipped using the ".byte $2C" trick.

it works because "lda (Player0Ptr),y" is a two bytes instruction: the first byte is the opcode for the "lda (),y or lda Indirect, Y" instruction ($B1, as per the table posted above by Spiceware), the second is the single byte indicating the zero-page address of the operand (Player0Ptr, which is $87 in this case).
So, when you assemble the source with dasm, the "lda (Player0Ptr),y" is translated into the byte sequence $B1 $87 in the binary file. (The actual values are not relevant in this case, just the fact that they are exactly 2 bytes).

If the player is not being drawn, the carry won't be set after "dcp Player0Draw", so the branch "bcs DoDrawGrp0" won't be taken. Therefore the following instruction will be executed (lda #0), then the 6502 cpu will read the next byte in the binary ($2C). This is the opcode for the instruction BIT Absolute, which is a 3 bytes instruction. (the opcode itself, plus 2 bytes indicating the absolute 16 bit address).
The next 2 bytes in the binary are those of the "lda (Player0Ptr),y" instruction, which in this case are interpreted as the operand (the address) of the BIT instruction.
So the three bytes $2C $B1 $87 are interpreted as BIT $87B1.
Since the BIT instruction affects some of the flags but not the Accumulator, the latter will still be set to 0 and the following "sta GRP0" instruction will therefore clear the player graphics.



#142 tschak909 OFFLINE  

tschak909

    River Patroller

  • 2,841 posts
  • Location:USA

Posted Sat Jun 11, 2016 8:57 AM

(for the record, I don't like using tricks, unless I am at the end of a project, and I need to squeeze things in...It's far too often that I write a piece of clever code, only to come back to it some time later, and try to remember WHY THE HELL I DID THAT?!) :P

 

-Thom



#143 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Sat Jun 11, 2016 9:44 AM

OK,  I think I might be following a little bit... 
 
So when you:
lda (Player0Ptr),y
 
is this pointing to random garbage data on every scan line except when we are on the lines where Player 0 is drawn, it runs through the graphics lines?

 
If that line was executed then yes, it would be pointing at "garbage" (the data in ROM before/after HumanGfx). You may like to review the comments of Step 4, I go over that routine in a bit more detail.
 
 

(for the record, I don't like using tricks, unless I am at the end of a project, and I need to squeeze things in...It's far too often that I write a piece of clever code, only to come back to it some time later, and try to remember WHY THE HELL I DID THAT?!) :P
 
-Thom

 
 
The byte $2c trick's been around a long time, I learned it in the early 80s on my Vic 20.   I didn't use the DCP trick until the 2600, the 3 cycle savings during the time critical kernel is too much to ignore.  Both tricks are covered in detail in the comments of Step 4.



#144 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • 1,990 posts

Posted Sat Jun 11, 2016 3:51 PM

Hi-  I'm still having trouble writing a two line kernal.  Does anone know of an easy to undertand code example and/or guide?

 

Jeff,

you might find the two line kernel in Virtual World BASIC a little simpler to follow, it's commented and I've recently revised it to remove an extra layer of indirection so it may be less confusing. 

 

The advanced Assembly programmers discussing nuances, tricks and esoteria may be difficult to understand on a beginner thread:

 

Programmer 1: You put the glop-de-glop in the gloop-gloop.

Programmer 2: And you adjust it 5 glicks.

Programmer 3: Yes, it's always 5 glicks! 

 

Johnny Mnemonic has a simple overview of addressing modes that is easy to understand.

 

If the Y register is 0, and Player0Ptr has been set to an address where there is graphic data (e.g. somewhere off in $F000 in the ROM via a source code label), then the Y accumulator will now hold the first byte of that data.

 

If you change that Y register to 1, then Player0Ptr will advance to the next byte in that address.

 

Technically, this is called an (Indirect),Y addressing mode. The Indirect means, that you can change the address that you're indirecting into, to something else (e.g. another table with graphics for another player), and the code will still function as expected, rendering the new data, assuming it is in the format needed.

 

The extra layer of indirection gives you more flexibility, than if you were simply just using e.g. the Indexed,X or Indexed,Y modes. (There IS a very specific form of X that is called the Indirect X mode, but it has very limited use on the 2600 due to the limited amount of actual RAM)

 

Some good refs:

https://github.com/j...6502refcard.pdf

 

That takes more cycles though tschak; it is possible to remove that extra layer or indirection by normalizing the data in the graphics table into multiple tables with a common index.

  

I recently did this transformation to the Virtual World BASIC kernel in order to free time for multicolor sprites and playfield attributes:

 

Programming notes.jpg  



#145 gauauu OFFLINE  

gauauu

    Moonsweeper

  • 395 posts
  • Location:Illinois

Posted Sat Jun 11, 2016 7:29 PM

(for the record, I don't like using tricks, unless I am at the end of a project, and I need to squeeze things in...It's far too often that I write a piece of clever code, only to come back to it some time later, and try to remember WHY THE HELL I DID THAT?!) :P

 

-Thom

 

I tend to try to keep my code clean and non-tricky as well, but it's easy enough to make a macro to make this one obvious and not feel hacky. I have a "Skip2Bytes" macro that I use, which just does the $byte 2c, but makes it obvious so my code still reads cleanly.



#146 Omegamatrix OFFLINE  

Omegamatrix

    Quadrunner

  • 6,215 posts
  • Location:Canada

Posted Sat Jun 11, 2016 8:18 PM

After spending a little time with assembly you'll find it's very easy to read "tricky" stuff like .byte $2C. You'll just recognize it.



#147 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Sat Jun 11, 2016 9:40 PM

Thanks Everybody..

 

I did have some understanding of the .byte trick.  I knew it worked, but as I've been trying to add comments to the CollectMini code, I finally got around to reading up on why..  I learned that it requires a two byte operand and so it swallows up the next line of code as its operand. I didn't know that the $2C had relevance till now though. 

 

By saying the lda points to random garbage, I was imagining the program counter going to it and passing over it, but I see, yes, it doesn't actually get there, it would, but it ends up as operand for the .byte.

 

Whew!  I'm at my limits of understanding..  I mean that in a good way.  Pretty exciting.

 

I did note that during this, we are using two bytes, while we only need one stored into the player register.(The LSB, I think)  Are we doing this only because $2C is looking for two bytes?

 

The MSB is treated strangely if so:

 

        lda #>(HumanGfx + HUMAN_HEIGHT - 1)
        sbc #0
        sta Player0Ptr+1



#148 alex_79 OFFLINE  

alex_79

    Stargunner

  • 1,180 posts
  • Location:Italy

Posted Sun Jun 12, 2016 8:36 AM

[...] it doesn't actually get there, it would, but it ends up as operand for the .byte.


.byte is not a 6502 instruction, but a dasm one, used to insert bytes values directly in the source. The .byte instruction itself doesn't translate into any value in the resulting binary. It is mostly used to insert data tables (like graphics, for example), but in this case is used because there's no way to tell dasm to just insert the BIT opcode without an operand (it would report an error).
So the two bytes corresponding to the "lda (),y" become the operand of the BIT instruction (that is, the $2C value), not of .byte.


 

I did note that during this, we are using two bytes, while we only need one stored into the player register.(The LSB, I think)  Are we doing this only because $2C is looking for two bytes?

All the data registers (A, X, Y) and every addressable memory location are 8 bit (1 byte) wide. So all load/store instructions read or write a single byte.
When you specify a two byte operand, that's the address where that single byte value is to be loaded from or stored to.

The lda (Player0Ptr),y has 1 byte as operand because it's a zero-page instruction and only the LSB of the address needs to be specified (the MSB is always $00 in page 0). Moreover is an indirect addressing instruction, so the operand is not the address of the value to be stored/loaded but the address of the pointer to the value (that is, the address where the address of the value is stored).
It means: take the address whose LSB is in memory location Player0Ptr and whose MSB is in memory location Player0Ptr+1, add the value of Y to it and then load the value stored in the resulting address into the Accumulator.

(and "memeory location Player0Ptr" means the address whose LSB is Player0Ptr and whose MSB is $00)

 


Edited by alex_79, Sun Jun 12, 2016 8:38 AM.


#149 SpiceWare ONLINE  

SpiceWare

    Draconian

  • 12,461 posts
  • Medieval Mayhem
  • Location:Planet Houston

Posted Sun Jun 12, 2016 10:01 AM

The instruction ldy (Player0Ptr),y uses a 16 bit value that's stored in zero_page memory locations Player0Ptr and Player0Ptr+1.  That's why the pointer is defined as 2 bytes, not 1:
    ; DoDraw Graphic Pointers in $87-8a
Player0Ptr:         ds 2    ; used for drawing player0
Player1Ptr:         ds 2    ; used for drawing player1
 

The MSB is treated strangely if so:
 
        lda #>(HumanGfx + HUMAN_HEIGHT - 1)
        sbc #0
        sta Player0Ptr+1

 
That's part of a routine which is doing 16 bit math. From before the (HumanGfx + HUMAN_HEIGHT - 1) value is $FA13, so this bit of code:
    ; Set Player0Ptr to proper value for drawing player0
        lda #<(HumanGfx + HUMAN_HEIGHT - 1)
        sec
        sbc ObjectY
        sta Player0Ptr
        lda #>(HumanGfx + HUMAN_HEIGHT - 1)
        sbc #0
        sta Player0Ptr+1
would be calculating $FA13 - ObjectY and storing the results in Player0Ptr. ObjectY is only an 8 bit value so we hard code 0 (the SBC #0) as the MSB for the value of ObjectY.

#150 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 344 posts
  • Location:$5787

Posted Thu Jun 16, 2016 6:44 AM

OK..  I see that .byte is not the command...  Now it seems like it is a way to trick DASM, not the 6502.  I'd think that DASM could just allow the command but I'm sure there is a reason it doesn't. 

 

So indirect addressing means the operand is the location where the location of the data is stored?  I hope that's right because I've been having a hard time with that stuff.  I have immediate addressing down pretty good though!

 

So if I understand correctly, we are using 16 bits, just because we are required to by a command that expects a 16 bit operand- One of the bytes does nothing in our case. 

 

I am still thrown off by the sbc #0.  This looks like "subtract zero" to me, and not "make it zero".  It  seems like deleting the command would have the same result stored into PlayerPtr+1, though the carry flag could potentially cause it to store 255? maybe?, in which case MSB is not "hard coded to zero" though?  Come to think of it, since we subtract zero, the carry flag would never matter, I think.  Hmm. 

 

Thanks for the 16 bit math link.  I think I grasped it.


Edited by BNE Jeff, Thu Jun 16, 2016 6:46 AM.





0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users