Jump to content
IGNORED

Session 17: Asymmetrical Playfields - Part 1


Recommended Posts

Goodness, what a discussion for what is supposed to be a beginner tutorial.

 

That's to be expected... IMO, by the time you get to lesson 19, you're no longer a beginner... anybody that can get the 2600 to display anything is not a beginner anymore. They're not experts or anything remotely approaching the talent of people like yourself or Andrew, but definetly worthy of having discussions such as this :)

 

Hiding complexity doesn't do any good, especially in a subject such as this -- eventually, they have to learn it anyway.

Link to comment
Share on other sites

  • 3 weeks later...

I've been following along quite well and understand a hell of alot more then I did before these sessions. *Thx Andrew*. But I am having a problem at this point. I can't seem to get the timing down.. I understand all of it, when to start writing to the other playfields and what not. But I don't know exactly where (in my code) to write it.

 

Thx in advanced you can either post here or take it off line and email me at datafry@yahoo.com

 

--DF

 

 

Keep up the good work Andrew, I haven't found something as addicting and fun like this since Doom!

Link to comment
Share on other sites

I think I posted my last responce a little early.. This is drawn out a little with NOP's but it helped me just visualize this a tad better.. Am I counting this correctly?


;------------------------------------------------

      ; Do 192 scanlines of colour-changing (our picture)



MainScreen      ldx #0         ;+3

               lda #%1111     ;(3)+3

               sta PF0        ;(6)+3

               lda #%11111111 ;(9)+3

               sta PF1        ;(12)+3

               lda #%11111111 ;(15)+3

               sta PF2        ;(18)+3=21 cycles so far? or 63 TIA 

clocks



               NOP            ;(21)+2 = 23 I am hoping it should be 

safe

to redraw PF0 now at 69 tia clocks

               lda #%0000     ;(23)+3 ok at 26 have some buffer now 

at 78

tia clocks

               sta PF0        ;(26)+3  ok 29 (87 on TIA) need to burn

some time

               NOP            ;(29)+2

               NOP            ;(31)+2

               NOP            ;(33)+2

               NOP            ;(35)+2

               NOP            ;(37)+2 should be about 117 TIA CLOCK

               lda #%00000000 ;(39)+3

               sta PF1        ;(42)+3

               NOP            ;(45)+2

               NOP            ;(47)+2

               NOP            ;(49)+2 should be at 153 TIA CLOCK

               lda #%00000000

               sta PF2



               sta WSYNC



 

Thx

 

PS format broke alittle

Link to comment
Share on other sites


               lda #%1111     ;(3)+3  <<  'load immediate' is only 2 cycles

 

 

 

Your timing is mostly good, except for the error pointed out in the sample line above. Immediate loads to registers take only 2 cycles.

 

I think we should soon have a session about cycle counting and the pitfalls associated with this. Your comments said something like "should be about 117 cycles here" - we can with certainty count the exact cycle, of course.

 

I'm taking a short break before we delve into our next batch of sessions - which will probably be devoted to displaying sprites.

Link to comment
Share on other sites

  • 1 month later...

I'll say it again, I'm behind because I started only a week ago, but I'm catching up.

 

Here is my playfield. It draws GAME OVER. This was a great exercise, I feel like I'm getting somewhere.

 

Now I can read Session 19 :)

 



; 4 columns for letters in playfield, 40 pixels each
; (of course letters will have to span PF sections
; but doesn't matter since we're just reading from graphics bytes)
;
; Frame scanlines are laid out like this:
; 21 blank scanlines
; 64 GAME (each letter is 8 segments high, ea. segment 8 pixel high)
; 22 blank
; 64 OVER
; 21 blank



      ;------------------------------------------------ 

      ; Do 192 scanlines 



               lda #$4a

               sta COLUPF             ; reddish color for "GAME"



               ldx #0                 ; this counts our scanline number 



                                       

TopBlankArea    sta WSYNC 

               inx               ;(+2=2) ; my first attempt at cycle counting

               cpx #21           ;(+2=4)

               bne TopBlankArea  ;(+3=7)

               

               

               ldx #0            ;(+2=9) ; we've gone thru 21 scanlines so far...

               ldy #0            ;(+2=11); this counts our letter segment number

               

WordRow1

                                 ; load playfield for left half of screen

               lda P0_1_LEFT,Y   ;(+4=15)  or from branch below->;(+4=20)

               sta PF0           ;(+3=18)                        ;(+3=23)

               lda P1_1_LEFT,Y   ;(+4=22)                        ;(+4=27) * 3 = 81

               sta PF1           ;(+3=25)                        ;(+3=30) * 3 = 90

               lda P2_1_LEFT,Y   ;(+4=29)

               sta PF2           ;(+3=31) * 3 = 93

               

                                 ; load playfield for right half of screen

               lda P0_1_RIGHT,Y  ;(+4=35)

               sta PF0           ;(+3=38)

               

               lda P1_1_RIGHT,Y  ;(+4=42) * 3 = 126

               sta PF1

               

               nop               ;(+2=44)  ; wait until we can safely write PF2



   lda P2_1_RIGHT,Y  ;(+4=48)

   sta PF2           ;(+3=51)



   nop                          ; wait until we can safely

   nop                          ; change PF1

   nop

   

                                ; updating PF1 here in case this

                                ; is the end of a 8 pixel high segment and we

                                ; need to run the extra branch code

                                ; for y below.  if we do, then PF1 won't

                                ; be written in time unless we do it ahead

                                ; of time here

               lda P1_1_LEFT+1,Y

               sta PF1



               sta WSYNC 

               inx               ;(+2=2)

               cpx #8            ;(+2=4)

               bne WordRow1      ;(+3=7)   ; still in same segment?



                                            ; no.  so inc y for next segment

               

               ldx #0            ;(+2=9)



               iny               ;(+2=11)

               cpy #8            ;(+2=13)

               bne WordRow1      ;(+3=16)

               

               

               ldx #0                 ; we've done 21+64=85 scanlines so far

   lda #0

   sta PF0                ; blank out the playfield

   sta PF1

   sta PF2

   

   

      ;------------------------------------------------ 

       

MiddleBlankArea    sta WSYNC           ; 21+1=22 blank lines in middle of frame

               inx 

               cpx #21

               bne MiddleBlankArea        

               

                                      ; use one blank line for setting PF color

               lda #$68 

               sta COLUPF             ; bluish color for "OVER"

               sta WSYNC



      ;------------------------------------------------ 


; WordRow2 is just like WordRow1, but with different graphics
; if I knew more, I'd reuse the code for WordRow1 to display this
; section instead of doing a copy/paste of the code and changing a few things

               

               ldx #0            ;(+2=9) ; we've done 21+64+22=107 scanlines

               ldy #0            ;(+2=11); this counts our letter segment number

               

WordRow2

                                 ; load playfield for left half of screen

               lda P0_2_LEFT,Y   ;(+4=15)  or from branch below->;(+4=20)

               sta PF0           ;(+3=18)                        ;(+3=23)

               lda P1_2_LEFT,Y   ;(+4=22)                        ;(+4=27) * 3 = 81

               sta PF1           ;(+3=25)                        ;(+3=30) * 3 = 90

               lda P2_2_LEFT,Y   ;(+4=29)

               sta PF2           ;(+3=31) * 3 = 93

               

                                 ; load playfield for right half of screen

               lda P0_2_RIGHT,Y  ;(+4=35)

               sta PF0           ;(+3=38)

               

               lda P1_2_RIGHT,Y  ;(+4=42) * 3 = 126

               sta PF1

               

               nop               ;(+2=44)  ; wait until we can safely write PF2



   lda P2_2_RIGHT,Y  ;(+4=48)

   sta PF2           ;(+3=51)



   nop                          ; wait until we can safely

   nop                          ; change PF1

   nop

   

                                ; updating PF1 here in case this

                                ; is the end of a 8 pixel high segment and we

                                ; need to run the extra branch code

                                ; for y below.  if we do, then PF1 won't

                                ; be written in time unless we do it ahead

                                ; of time here

               lda P1_2_LEFT+1,Y

               sta PF1



               sta WSYNC 

               inx               ;(+2=2)

               cpx #8            ;(+2=4)

               bne WordRow2      ;(+3=7)   ; still in same segment?



                                            ; no.  so inc y for next segment

               

               ldx #0            ;(+2=9)



               iny               ;(+2=11)

               cpy #8            ;(+2=13)

               bne WordRow2      ;(+3=16)

               

               

               ldx #0                 ; we've done 21+64+22+64=171 scanlines so far

   lda #0

   sta PF0                ; blank out the playfield

   sta PF1

   sta PF2





      ;------------------------------------------------ 

       

BottomBlankArea    sta WSYNC 

               inx 

               cpx #21

               bne BottomBlankArea

       



 

s174.jpg

s174.zip

Link to comment
Share on other sites

  • 7 months later...

I've programmed an assymetrical playfield with a multi vertical

scrolling for the right side of the playfield.

 

Asymmetrical_Playfield.jpg

 


      ; Display 192 Scanlines of Asymetrical Playfield with

      ; Multi Scrolling effect of Playfield 2 (right side)

 

               LDY #48       ; 48 * 4 pixel high = 192 lines

               LDX #4        ; Change playfield pattern every

                             ; 4 lines

Picture                          

               LDA #%01010000

               STA PF0

               LDA #%00011000

               STA PF1

               LDA #%10111100

               STA PF2

 

               SLEEP 4

               LDA PF0ADR

               STA PF0

               SLEEP 4

               LDA PF1ADR

               STA PF1

               SLEEP 4

               LDA PF2ADR

               STA PF2

 

               STA WSYNC               

        DEX

               BNE Picture

               

               DEY

               BEQ Picture_End

               LDA #%01010000

               STA PF0

               LDA #%00011000

               STA PF1

               LDA #%10111100

               STA PF2

               INC PF0ADR      ; Change Pattern PF0

               LDA PF0ADR

               STA PF0

               INC PF1ADR      ; Change Pattern PF1

               LDA PF1ADR

               STA PF1

               INC PF2ADR      ; Change Pattern PF2

               LDA PF2ADR

               STA PF2

               LDX #3	; 3 Scanlines To Draw

               STA WSYNC;+1 (The Currenr One) = 4

               JMP Picture

               

Picture_End            

playfield_code.zip

Link to comment
Share on other sites

And since it doesn't contain a transistor level copy of the 6507, each instruction (documented or not) has to be "programmed" individually.

Are you sure?

 

IIRC the illegal opcodes are the result of optimizing the design of the 650x. And all opcodes follow a certain pattern which reduces the number of transistors. So I would assume that anybody who designs a new 650x-compatible chip should get very similar results when optimizing.

 

Maybe we should simply ask the guy before speculating too much here. ;)

 

Only if the same optimizations are made. There are 2 main parts to a simple processor: The ALU which contains the circuits to perform the various operations needed, and the Decode Logic which translates an instruction into a sequence of internal events.

 

There's not much you can do to reduce the size of the ALU and still keep the essential functions, but the 6502's Decode Logic was optimized by hand and one method that was used was what I'll call the "half-chain." The 2 bits at the end of an opcode were used to select what part of an internal sequence would be followed. The opcodes where both bits were set were left undocumented because they would cause the entire sequence to be executed, essentially running 2 instructions back to back. This is how we get instructions like ISC and RLA.

 

This intelligent form of optimization is not likely to be duplicated by someone using automated circuit tools. The designer would have to explicitly define the action for all opcodes they wanted to support.

 

Why didn't the designers of the 6502 document all working opcodes? I can think of 2 reasons:

 

1. No one cares what's inside an IC chip. What matters is how the features look on paper. The 6502 was designed to provide a complete set of easy to understand features. The instruction set becomes a CPU's fingerprint so including bizzare side effect opcodes in the list would have made the design look too complicated or unfinished.

 

2. If left unused, these extra opcodes provide space for future expansion of the chip. Extra logic could be added to provide new features, as was done with the 65C02.

 

The wisdom for a long time regarding undocumented opcodes was "don't go there." No one knew what they did, and no one knew if they'd do the same thing in the next shipment. Anyway, it took a while for people to become adventurous enough to not only figure them out, but verify that they were reliable enough to put into finished products.

 

-Bry

Link to comment
Share on other sites

Why didn't the designers of the 6502 document all working opcodes? I can think of 2 reasons:

 

1. No one cares what's inside an IC chip. What matters is how the features look on paper. The 6502 was designed to provide a complete set of easy to understand features. The instruction set becomes a CPU's fingerprint so including bizzare side effect opcodes in the list would have made the design look too complicated or unfinished.

 

2. If left unused, these extra opcodes provide space for future expansion of the chip. Extra logic could be added to provide new features, as was done with the 65C02.

I was digging trough the [stella] archives the other day and stumbled onto a discussion

about illegal opcodes.

Well the explanation Andrew gave for the opcodes being undocumented was that the

chip was already being marketed, A feature set was drawn up and the Manuals where

probably already printed before the hardware was finalized. So all it needed to do was meet

the specifications and any other "feature" was called illegal to discourage people from using it.

Link to comment
Share on other sites

Well the explanation Andrew gave for the opcodes being undocumented was that the

chip was already being marketed, A feature set was drawn up and the Manuals where

probably already printed before the hardware was finalized.

 

Nah... I doubt that. I'm sure that the way in which the processor would work was decided long before the silicon was ever finalized or the user documents were drawn up. I think it makes more sense to say that the spec was drawn up, and the design met the spec with some "side-effect" opcodes left over. Including them would have obfuscated the 6502's simple & easy to learn programming model.

 

I've used a table knife as a screwdriver, but I understand why the makers of the knife didn't put "can be used as a screwdriver" on the package. :)

 

-Bry

Link to comment
Share on other sites

 

Well the explanation Andrew gave for the opcodes being undocumented was that the

chip was already being marketed, A feature set was drawn up and the Manuals where

probably already printed before the hardware was finalized. So all it needed to do was meet

the specifications and any other "feature" was called illegal to discourage people from using it.

 

Well, I don't *think* that's what I said. I believe the illegal opcodes are a result of the operation of the circuitry itself, which decodes the opcode a certain way. Just because one implementation of the 6502 happens to behave in a known way doesn't mean that all will. They didn't design the chip and then look-see how all the opcodes behaved. They designed the chip so that predefined opcodes WOULD behave as expected. The side-effect operation of those opcodes we call 'illegal' are just that; a side-effect. They are not a part of the designed operation of the chip, and most certainly not guaranteed to work on all variants of the chip (say, if it were made by another manufacturer).

 

The manufacturer specified a set of opcodes and how they work. End of story. Those other opcodes we use, some of which are useful, were never intended to be used as opcodes at all. They operate in odd ways due to the way the opcode is decoded and causes certain functions in the chip to operate.

Link to comment
Share on other sites

I have a theory about that. It seems that early programs were usually written with more structure in mind (like early Basic programmers using the LET instruction...it's totally unneeded execpt to indicate to a human reading it that something like A=A+1 is not an algebraic function). Programs often use wasteful JMP commands to skip ahead even just a few bytes...i.e. if the program is ALWAYS to jump it's used instead of a more compact branch. If a program is to test a bit that is part of a multiple-purpose ram location, AND is often used when just a simple BIT instruction would have sufficed. And I've run across zero comparisons many times, when those are totally unneeded when they are preceded by loading or updating the value.

Link to comment
Share on other sites

I have a theory about that.  It seems that early programs were usually written with more structure in mind (like early Basic programmers using the LET instruction...it's totally unneeded execpt to indicate to a human reading it that something like A=A+1 is not an algebraic function).  Programs often use wasteful JMP commands to skip ahead even just a few bytes...i.e. if the program is ALWAYS to jump it's used instead of a more compact branch.  If a program is to test a bit that is part of a multiple-purpose ram location, AND is often used when just a simple BIT instruction would have sufficed.  And I've run across zero comparisons many times, when those are totally unneeded when they are preceded by loading or updating the value.

 

I've disassembled ROMs to find that the programmer must have made their own "branch always" macro to avoid using JMP. Throughout the code was CLV,BVC XX. Great! A 3-byte 5-cycle JMP!

 

-Bry

Link to comment
Share on other sites

I'm a little late to the races, but if you skip over my ramblings, there's a pretty nice screenshot at the end.

 

I've disassembled ROMs to find that the programmer must have made their own "branch always" macro to avoid using JMP. Throughout the code was CLV,BVC XX. Great! A 3-byte 5-cycle JMP!

 

Actually, I have a list of 6502 opcodes that I've been working off of, and it actually recommends using BVC/BVS as "branch always" instructions. I think the author intended you to use it in a location where you'd already know the value of the overflow bit without having to explicitly set it.

 

As for illegal opcodes, I think Eric had it right in the first place. Any 8-bit processor by definition has room for 256 opcodes, but you really don't need that many, so the designers concentrate on making the 151 opcodes that it has work as well as possible (faster and cheaper) while the rest of the combinations were left as "don't care" combinations. For the most part, the illegal opcodes end up acting as chaotic combinations of other operations, that end up writing to multiple registers at once, or performing different addressing methods where they aren't applicable (like our friend, 'nop 0').

 

I think the main reason that you should probably avoid them is that you might not be able to rely on some of the more goofy ones. I have a list of undocumented 6502 opcodes that specifically discusses inconsistencies with opcode $9C (which it calls SAY). SAY essentially ANDs Y with a memory location, and then stores Y in a memory location (Yes, it's very goofy, and not very useful)... however, in seems that every once in a while (very rarely, but not never (yes, that makes sense)), it ends up operating as STY, and just stores Y in memory without any ANDing. My guess here is that the act of storing Y in memory, and performing the AND are done almost simultaneously by the CPU, so closely timed that some random event may cause one to be done before the other (that is, the AND actually IS done, but Y was already stored, so the result of the AND is just lost).

 

Now, this article I'm referring to is geared towards the C-64, of which there was a lot more variety... I'm sure pretty much all the 2600's out there use such identical chips that there probably isn't any worry about illegal opcodes doing something odd... but what about some of the goofy devices out there like the 2600 adaptors for the INTV and ColecoVision? They're probably safe as well... probably...

 

Anyways.... Getting a little back on topic of asymmetrical playfields, I did one of my own. It displays a diamond and a spade on opposite sides of the screen, reading all the data out of memory (left out, since it's a little long to post in here). Here's the meat of it:

        ; 192 scanlines of picture...

       

       ldx #0

       ldy #0



DrawPF                 ; Draw left side of PF

       lda PF0Left,y  ; [4] = 9

       sta PF0        ; [3] = 12

       lda PF1Left,y  ; [4] = 16

       sta PF1        ; [3] = 19

       lda PF2Left,y  ; [4] = 23

       sta PF2        ; [3] = 26



                      ; Draw right side of PF

       lda PF0Right,y ; [4] = 30

       sta PF0        ; [3] = 33

       lda PF1Right,y ; [4] = 37

       sta PF1        ; [3] = 40

       lda PF2Right,y ; [4] = 44

       sta PF2        ; [3] = 47



       inx            ; [2] = 49

       txa            ; [2] = 51

       lsr            ; [2] = 53

       lsr            ; [2] = 55

       lsr            ; [2] = 57  (Divide by 8)

       tay            ; [2] = 59

           

       sta WSYNC      ; [3] = 62 total cycles

       cpx #192       ; [2] ----- Start of scanline!

       bne DrawPF     ; [3] = 5

 

62 cycles seems a little high to me, and the way I'm calculating Y looks awfully messy... but it gets the job done.

 

--Zero

post-134-1082614654_thumb.jpg

Link to comment
Share on other sites

Now, this article I'm referring to is geared towards the C-64, of which there was a lot more variety... I'm sure pretty much all the 2600's out there use such identical chips that there probably isn't any worry about illegal opcodes doing something odd... but what about some of the goofy devices out there like the 2600 adaptors for the INTV and ColecoVision? They're probably safe as well... probably...

:idea: Most illegal opcodes behave completely stable on all platforms, especially the more usefull ones like LAX (except for LAX #imm), NOP, DCP, ISB...

There are only a very few (<10) which don't behave stable. Usually the documentation warn you to use them.

 

62 cycles seems a little high to me, and the way I'm calculating Y looks awfully messy... but it gets the job done.

:idea: Usually you would do two nested loops here. The outer loop counts the number of blocks and the inner loop the (completely variable then) height of each block.

Link to comment
Share on other sites

:idea: Usually you would do two nested loops here. The outer loop counts the number of blocks and the inner loop the (completely variable then) height of each block.

 

In fact, I did just that... I woke up and had an epiphany of sorts and I just dived in:

       ; 192 scanlines of picture...



       ldy #23

NextSet

       ldx #7

OneLine

       lda PF0Left,y  ; [4] = 9

       sta PF0        ; [3] = 12   

       lda PF1Left,y  ; [4] = 16

       sta PF1        ; [3] = 19

       lda PF2Left,y  ; [4] = 23

       sta PF2        ; [3] = 26



       nop            ; [2] = 28

       nop            ; [2] = 30



       lda PF0Right,y ; [4] = 34

       sta PF0        ; [3] = 37

       lda PF1Right,y ; [4] = 42

       sta PF1        ; [3] = 45

       lda PF2Right,y ; [4] = 50

       sta PF2        ; [3] = 53 total cycles



       sta WSYNC

       dex            ; [2] --- Start of scanline

       bpl OneLine    ; [3] = 5



       dey

       bpl NextSet

 

Much better. Plus, it saves 9 cycles + 4 for the two nops, although I had to flip my data upside down to skip the compares.

 

I was trying to work out a way to use indirect addressing to hold a pointer to the PF data so I could actually cause each side to scroll (vertically) at a different speed... but I've yet to wrap my head around indirect addressing completely, and I'm a little unsure of how to handle the bounds checking (That is, if my "start of image" pointer is pointing to the middle of the image, I have to make sure I wrap around to the start of the data properly so I don't display garbage)

 

--Zero

Link to comment
Share on other sites

  • 8 months later...
At the beginning of any scanline, then, we know that we have exactly 68 colour clocks (=68/3 = 22.667 cycles) before the TIA starts 'drawing' the line itself.

 

Hi, I think I'm confused about what exactly WSYNC does. I thought I only had 160 color clocks to work with and that the other 68 color clocks were used up by WSYNC.

From Stella:

When the electron beam reaches the end of a scan line, it returns to the left side of the screen, waits for the 68 horizontal blank clock counts, and proceeds to draw the next line below.

I took this to mean that the cpu is halted during the 68 color clocks of horizontal blank But if the cpu is not halted, then.... no wonder my timing is always off.

:ponder:

 

Jim

Link to comment
Share on other sites

WSYNC halts the CPU until the end of a scanline, so it syncs your code with the TV beam.

 

Each scanline has CPU 76 cycles. If you don't use WSYNC you can use all those cycles, if you use it, you can use up to 73 cycles, since sta WSYNC uses 3 cycles.

Link to comment
Share on other sites

At the beginning of any scanline, then, we know that we have exactly 68 colour clocks (=68/3 = 22.667 cycles) before the TIA starts 'drawing' the line itself.

 

Hi, I think I'm confused about what exactly WSYNC does. I thought I only had 160 color clocks to work with and that the other 68 color clocks were used up by WSYNC.

 

Hi Jim

 

There are 228 ( = 160 visible + 68 not visible ) colour clocks on each scanline. The CPU is active ALL the time, *unless* you write to WSYNC at which point the CPU is *immediately* halted and doesn't become active again until the start of the next scanline. Since it takes 3 cycles to actually write WSYNC, a kernel which is using this to time scanlines only has 73 CPU cycles per line. Why 73? Because if we look at the colour clocks per line, we see 228; but if we look at 3 colour clocks for every CPU cycle, we actually have 228/3 = 76 CPU cycles per line. And if we use 3 of those to do a WSYNC, then we only have 73 available for other stuff. Voila!

 

And note, these 76 cycles for the whole line actually encompass the WHOLE line... 228 colour clocks worth. Some of those will be during the 160 visible onscreen pixels (colour clocks). Some will be during the 68 "horizontal blank" period -- the invisible colour clocks. And the CPU can be halted by a WSYNC at *any* time during the line -- and it will be turned on at the start of the next line -- no matter how long away that is.

 

Hope this clears things up a bit.

 

Cheers

A

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