home *** CD-ROM | disk | FTP | other *** search
Text File | 2019-04-13 | 175.4 KB | 4,352 lines |
-
- ########
- ##################
- ###### ######
- #####
- ##### #### #### ## ##### #### #### #### #### #### #####
- ##### ## ## #### ## ## ## ### ## #### ## ## ##
- ##### ######## ## ## ## ##### ## ## ## ## ##
- ##### ## ## ######## ## ## ## ### ## ## #### ## ##
- ##### #### #### #### #### ##### #### #### #### #### #### ######
- ##### ##
- ###### ###### Issue #6
- ################## Date ##, 1993
- ########
-
- -------------------------------------------------------------------------------
- Editor's Notes:
- by Craig Taylor
-
- School, 2 jobs, a play, and other work has seriously restricted the amount
- of time that I am able to devote to Commodore Hacking. What I am looking at
- doing now is to stop writing articles for Commodore Hacking and hoping that
- I'll still have enough time to write these notes, and organize and pull all
- the other articles by other contributors together. The two articles I
- had hoped to include in this issue : the one about multi-tasking and
- about the 1351 have again been delayed. I've decided to go ahead and
- release issue 6 and hopefully will have them in the next issue.
- (Remember: You get what you pay for.. *smile*)
-
- As always, Commodore Hacking is constantly looking for articles, notes,
- short little programs, what-not on any side of the Commodore 64 and 128 -
- primarily on the technical or hardware side. If you think you have something
- in mind, or already written then feel free to drop me a line letting me
- know.
-
- In regards to several queries recently about reprinting individual articles
- that have appeared in Commodore Hacking. You may reprint Commodore Hacking
- and redistribute in whole, freely. For use of individual articles you _must_
- contanct the individual author as they still retain rights to it. Please see
- the legal notice / mumbo below.
-
- I recently recieved some mail from wolfgang@halcyon regarding a disk
- magazine that he was in the process of starting and he has written the
- following preview of a disk magazine that looks to be exciting:
-
- "_Scenery_, a new disk-magazine focusing on the american and
- european demo scenes, will soon be available for download at a
- site/BBS near you! With articles on everything from coding to
- instrument synthesis to art, _Scenery_ will be the definitive word
- when it comes to creating a demo, or simply coding in general.
- Articles are being written by some of the top names in the scene,
- and promise to help everybody from the Basic Baby to the ML Mogul!
- Set to be released mid-August, _Scenery_ will hopefully be a worthy
- edition to the likes of Coder's World and C=Hacking. I am making
- the magazine available on various Internet sites, so look for it. We
- are always on the lookout for art, music, and coding talent, and if
- you'd be interested in submitting an article for publication, or
- simply have a question or comment, please mail me at
- 'wolfgang@halcyon.com'. Thanks.. and see you on the Net!"
-
- ================================================================================
-
- Please note that this issue and prior ones are available via anonymous FTP
- from ccosun.caltech.edu under pub/rknop/hacking.mag and via a mailserver
- which documentation can be obtained by sending mail to
- "duck@pembvax1.pembroke.edu" with a subject line of "mailserver" and the
- line "help" in the body of the message.
-
- ================================================================================
-
- NOTICE: Permission is granted to re-distrubte this "net-magazine", in
- whole, freely for non-profit use. However, please contact individual
- authors for permission to publish or re-distribute articles seperately.
- A charge of no greather than 5 US dollars or equivlent may be charged for
- library service / diskette costs for this "net-magazine".
-
- ================================================================================
- In This Issue:
-
- DYCP - Horizontal Scrolling
-
- DYCP - is a name for a horizontal scroller, where characters go smoothly
- up and down during their voyage from right to left. One possibility is a
- scroll with 8 characters - one character per sprite, but a real demo coder
- won't be satisfied with that.
-
- Opening the borders
-
- VIC has many features and transparent borders are one of them. You can not
- make characters appear in the border, but sprites are displayed in the
- border too.
-
- 64 Documentation
-
- This file, taken directly from a Commodore 64 emulator and development
- system being developed is full of information about undocumented
- opcodes, processor timings, memory configurations and the such. A
- must-have reference article.
-
- A Heavy Duty Power supply for the C-64
-
- This article describes how to build a heavier duty power supply for your
- Commodore 64 computer and includes a full schematic in GeoPaint format.
-
- LZW Compression
-
- LZW is perhaps the most widely used form of data compression today. It
- is simple to implement and achieves very decent compression at a fairly
- quick pace. LZW is used in PKZIP (shrink),PKARC (crunch), gifs,V.42bis
- and unix's compress. This article will attempt to explain how the
- compression works with a short example and 6502 source code in Buddy
- format.
-
- THREE-KEY ROLLOVER for the C-128 and C-64.
-
- This article examines how a three-key rollover mechanism works for the
- keyboards of the C=128 and C=64 and will present Kernal-wedge
- implementations for both machines. Webster's doesn't seem to know, so I'll
- tell you that this means that the machine will act sensibly if you are
- holding down one key and then press another without releasing the first.
- This will be useful to fast touch typers.
-
- ================================================================================
- The Demo Corner: DYCP - Horizontal Scrolling
- by Pasi 'Albert' Ojala (po87553@cs.tut.fi or albert@cc.tut.fi))
- Written: 16-May-91 Translation 02-Jun-92
-
- DYCP - too many sprites !?
- --------------------------
-
- DYCP - Different Y Character Position - is a name for a horizontal scroller,
- where characters go smoothly up and down during their voyage from right to
- left. One possibility is a scroll with 8 characters - one character in each
- sprite, but a real demo coder won't be satisfied with that.
-
- Demo coders thought that it looks good to make the scrolling text change its
- vertical position in the same time it proceeded from the right side of the
- screen to the left. The only problem is that there is only eight sprites
- and that is not even nearly enough to satisfy the requirements needed for
- great look. So the only way is to use screen and somehow plot the text in
- graphics, because character columns can not be scrolled individually.
- Plotting the characters take absolutely too much time, because you have to
- handle each byte seperately and the graphics bitmap must be cleared too.
-
-
- _Character hack_
-
- The whole DYCP started using character graphics. You plot six character
- rows where the character (screen) codes increase to the right and down.
- This area is then used like a small bitmap screen. Each of the text chars
- are displayed one byte at a time on each six rows high character columns.
- This 240 character positions big piece of screen can be moved horizontally
- using the x-scroll register (three lowest bits in $D016) and after eight
- pixels you move the text itself, like in any scroll. The screen is of course
- reduced to 38 columns wide to hide the jittering on the sides.
-
- A good coder may also change the character sets during the display and
- even double the size of the scroll, but because the raster time happens
- to go to waste using this technique anyway, that is not very feasible. There
- are also other difficulties in this approach, the biggest is the time needed
- to clear the display.
-
-
- _Save characters - and time_
-
- But why should we move an eight-byte-high character image in a 48-line-high
- area, when 16 is really enough ? We can use two characters for the graphics
- bitmap and then move this in eight pixel steps up and down. The lowest
- three bits of the y-position then gives us the offset where the data must
- be plotted inside this graphical region. The two character codes are usually
- selected to be consecutive ones so that the image data has also 16
- consecutive bytes. [See picture 1.]
-
-
- _Demo program might clear things up_
-
- The demo program is coded using the latter algorithm. The program first
- copies the Character ROM to ram, because it is faster to use it from there.
- You can easily change the program to use your own character set instead,
- if you like. The sinus data for the vertical movement is created of a 1/4
- of a cycle by mirroring it both horizontally and vertically.
-
- Two most time critical parts are clearing the character set and plotting the
- new one. Neither of these may happen when VIC is drawing the area where the
- scroll is, so there is a slight hurry. Using double buffering technique we
- could overcome this limitation, but this is just an example program. For
- speed there is CLC only when it is absolutely needed.
-
- The NTSC version is a bit crippled, it only covers 32 columns and thus the
- characters seem to appear from thin air. Anyway, the idea should become
- clear.
-
-
- _Want to go to the border ?_
-
- Some coders are always trying to get all effects ever done using the C64 go
- to the border, and even successfully. The easiest way is to use only a region
- of 21 pixels high - sprites - and move the text exactly like in characters.
- In fact only the different addressing causes the differences in the code.
-
- Eight horizontally expanded sprites will be just enough to fill the side
- borders. You can also mix these techiques, but then you have the usual
- "chars-in-the-screen-while-border-opened"-problems (however, they are
- solvable). Unfortunately sprite-dycp is even more slower than char-dycp.
-
-
- _More movement vertically_
-
- You might think that using the sprites will restrict the sinus to only
- 14 pixels. Not really, the only restriction is that the vertical position
- difference between three consequent text character must be less than 14
- pixel lines. Each sprites' Y-coordinate will be the minimum of the three
- characters residing in that sprite. Line offsets inside the sprites
- are then obtained by subtracting the sprite y-coordinate from the character
- y-coordinate. Maybe a little hard to follow, but maybe a picture will
- clear the situation. [See picture 2.]
-
- Scrolling horizontally is easy. You just have to move sprites like you would
- use the character horizontal scroll register and after eight pixels you
- reset the sprite positions and scroll the text one position in memory.
- And of course, you fetch a new character for the scroll. When we have
- different and changing sprite y-coordinates, opening the side borders become
- a great deal more difficult. However, in this case there is at least two
- different ways to do it.
-
-
- _Stretch the sprites_
-
- The easiest way is to position all of the sprites where the scroll will
- be when it is in its highest position. Then stretch the first and last line
- of each sprite so that the 19 sprite lines in the middle will be on the
- desired place. Opening the borders now is trivial, because all of the sprites
- are present on all of the scan lines and they steal a constant amount of
- time. However, we lose two sprite lines. We might not want to use the first
- and the last line for graphics, because they are stretched.
- [See previous C=Hacking Issues for more information about stretching and
- stolen cycles.]
-
- A more difficult approach is to unroll the routine and let another routine
- count the sprites present in each line and then change the time the routine
- uses accordingly. In this way you save time during the display for other
- effects, like color bars, because stretching will take at least 12 cycles
- on each raster line. On the other hand, if the sinus is constant (user is
- not allowed to change it), it is usually possible to embedd the count
- routine directly to the border opening part of the routine.
-
-
- _More sprites_
-
- You don't necassarily need to plot the characters in sprites to have more
- than eight characters. Using a sprite multiplexing techiques you can double
- or triple the number of sprites available. You can divide the scroll
- vertically into several areas and because the y-coordinate of the scroll
- is a sinus, there always is a fixed maximum number of sprites in each area.
- This number is always smaller than the total number of sprites in the
- whole scroll. I won't go into detail, but didn't want to leave this out
- completely. [See picture 3.]
-
-
- _Smoother and smoother_
-
- Why be satisfied with a scroll with only 40 different slices horizontally ?
- It should be possible to count own coordinates for each pixel column on
- the scroll. In fact the program won't be much different, but the routine
- must also mask the unwanted bits and write the byte to memory with ORA+STA.
- When you think about it more, it is obvious that this takes a generous amount
- of time, handling every bit seperately will take much more than eight times
- the time a simple LDA+STA takes. Some coders have avoided this by plotting
- the same character to different character sets simultaneously and then
- changing the charsets appropriately, but the resulting scroll won't be much
- larger than 96x32 pixels.
-
- --------------------------------------------------------------------------
- Picture 1 - Two character codes will make a graphical bitmap
-
- Screen memory:
- ____________________________________
- |Char |Char |Char |Char | ...
- |Code |Code |Code |Code |
- |0 |2 |80 |80 | .
- | |** ** | | | .
- | |** ** | | | .
- |***** |****** | | |
- |****** | **** | | |
- |**__**_|__**___|_______|_______|
- |** ** | ** | **** |Char |
- |** ** | ** |****** |Code |
- |****** | |** ** |6 |
- |***** | |** | |
- |Char |Char |** ** | |
- |Code |Code |****** | |
- |1 |3 |4**** |***** |
- |_______|_______|_______|******_|
- |Char |Char | |** ** |
- |Code |Code | |****** |
- |80 |80 | |***** |
- | | | |** |
- | | |Char |**ar |
- | | |Code |Code |
- | | |5 |7 |
- |_______|_______|_______|_______|
-
- Character set memory:
-
- _________________________________________________________________
- |Char 0 |Char 1 |Char 2 |Char 3 |Char 4 |Char 5 |Char 6 |Char 7 | ...
- |_______|_______|_______|_______|_______|_______|_______|_______|__
- DDDDDDDD YYYYYYYY CCCCCCCC PPPPPPPP
- First column Second column Third column Fourth column
-
- --------------------------------------------------------------------------
- Picture 2 - DYCP with sprites
-
- Sprite 0
- _______________________
- | |** ** | |
- | |** ** | |
- | |****** | |
- |***** | **** | |
- |****** | ** | |
- |** ** | ** | |
- |** ** | ** | |
- |**__**_|_______|_______|
- |****** | | **** |
- |***** | |****** |
- | | |** ** |
- | | |** |
- | | |** ** |
- | | |****** |
- | | | **** |
- |_______|_______|_______| Sprite 1
- | | | | _______________________
- | | | ||***** | | |
- | | | ||****** | | |
- | | | ||** ** | | |
- |_______|_______|_______||****** | | |
- |***** | | |
- |** | | |
- |** | | |
- |_______|_______|_______|
- | | | |
- | | | |
- | | | |
- | | **** | |
- | | **** | |
- | | |****** |
- | | |****** |
- |_______|_______|__**___|
- | | | ** |
- | | | ** |
- | | | ** |
- | | | ** |
- |_______|_______|_______|
-
- --------------------------------------------------------------------------
- Picture 3 - Sprite multiplexing
-
- __ Set coordinates for eight sprites
- __|3 | that start from the top half.
- |4 | |__
- __| `--|2 |
- |5 `--' | |
- | | `--'__
- `--' |1 |
- | |
- __ `--'
- |6 |
- | | __
- `--' |0 |
- | |
- -__------------------------------------`--'When VIC has displayed the last
- |0 | __ sprite, set coordinates for the
- | | |6 | sprites in the lower half of the
- `--' | | area.
- `--'
- __
- |1 | __
- | | |5 |
- `-- __ | |
- |2 | __`--'
- | |__|4 | You usually have two sprites that
- `--|3 | | are only 'used' once so that you
- | `--' can change other sprites when VIC
- `__' is displaying them.
- --------------------------------------------------------------------------
-
- DYCP demo program (PAL)
-
-
- SINUS= $CF00 ; Place for the sinus table
- CHRSET= $3800 ; Here begins the character set memory
- GFX= $3C00 ; Here we plot the dycp data
- X16= $CE00 ; values multiplicated by 16 (0,16,32..)
- D16= $CE30 ; divided by 16 (16 x 0,16 x 1 ...)
- START= $033C ; Pointer to the start of the sinus
- COUNTER= $033D ; Scroll counter (x-scroll register)
- POINTER= $033E ; Pointer to the text char
- YPOS= $0340 ; Lower 4 bits of the character y positions
- YPOSH= $0368 ; y positions divided by 16
- CHAR= $0390 ; Scroll text characters, multiplicated by eight
- ZP= $FB ; Zeropage area for indirect addressing
- ZP2= $FD
- AMOUNT= 38 ; Amount of chars to plot-1
- PADCHAR= 32 ; Code used for clearing the screen
-
- *= $C000
-
- SEI ; Disable interrupts
- LDA #$32 ; Character generator ROM to address space
- STA $01
- LDX #0
- LOOP0 LDA $D000,X ; Copy the character set
- STA CHRSET,X
- LDA $D100,X
- STA CHRSET+256,X
- DEX
- BNE LOOP0
- LDA #$37 ; Normal memory configuration
- STA $01
- LDY #31
- LOOP1 LDA #66 ; Compose a full sinus from a 1/4th of a
- CLC ; cycle
- ADC SIN,X
- STA SINUS,X
- STA SINUS+32,Y
- LDA #64
- SEC
- SBC SIN,X
- STA SINUS+64,X
- STA SINUS+96,Y
- INX
- DEY
- BPL LOOP1
- LDX #$7F
- LOOP2 LDA SINUS,X
- LSR
- CLC
- ADC #32
- STA SINUS+128,X
- DEX
- BPL LOOP2
-
- LDX #39
- LOOP3 TXA
- ASL
- ASL
- ASL
- ASL
- STA X16,X ; Multiplication table (for speed)
- TXA
- LSR
- LSR
- LSR
- LSR
- CLC
- ADC #>GFX
- STA D16,X ; Dividing table
- LDA #0
- STA CHAR,X ; Clear the scroll
- DEX
- BPL LOOP3
- STA POINTER ; Initialize the scroll pointer
- LDX #7
- STX COUNTER
- LOOP10 STA CHRSET,X ; Clear the @-sign..
- DEX
- BPL LOOP10
-
- LDA #>CHRSET ; The right page for addressing
- STA ZP2+1
- LDA #<IRQ ; Our interrupt handler address
- STA $0314
- LDA #>IRQ
- STA $0315
- LDA #$7F ; Disable timer interrupts
- STA $DC0D
- LDA #$81 ; Enable raster interrupts
- STA $D01A
- LDA #$A8 ; Raster compare to scan line $A8
- STA $D012
- LDA #$1B ; 9th bit
- STA $D011
- LDA #30
- STA $D018 ; Use the new charset
- CLI ; Enable interrupts and return
- RTS
-
- IRQ INC START ; Increase counter
- LDY #AMOUNT
- LDX START
- LOOP4 LDA SINUS,X ; Count a pointer for each text char and according
- AND #7 ; to it fetch a y-position from the sinus table
- STA YPOS,Y ; Then divide it to two bytes
- LDA SINUS,X
- LSR
- LSR
- LSR
- STA YPOSH,Y
- INX ; Chars are two positions apart
- INX
- DEY
- BPL LOOP4
-
- LDA #0
- LDX #79
- LOOP11 STA GFX,X ; Clear the dycp data
- STA GFX+80,X
- STA GFX+160,X
- STA GFX+240,X
- STA GFX+320,X
- STA GFX+400,X
- STA GFX+480,X
- STA GFX+560,X
- DEX
- BPL LOOP11
-
- MAKE LDA COUNTER ; Set x-scroll register
- STA $D016
- LDX #AMOUNT
- CLC ; Clear carry
- LOOP5 LDY YPOSH,X ; Determine the position in video matrix
- TXA
- ADC LINESL,Y ; Carry won't be set here
- STA ZP ; low byte
- LDA #4
- ADC LINESH,Y
- STA ZP+1 ; high byte
- LDA #PADCHAR ; First clear above and below the char
- LDY #0 ; 0. row
- STA (ZP),Y
- LDY #120 ; 3. row
- STA (ZP),Y
- TXA ; Then put consecuent character codes to the places
- ASL ; Carry will be cleared
- ORA #$80 ; Inverted chars
- LDY #40 ; 1. row
- STA (ZP),Y
- ADC #1 ; Increase the character code, Carry won't be set
- LDY #80 ; 2. row
- STA (ZP),Y
-
- LDA CHAR,X ; What character to plot ? (source)
- STA ZP2 ; (char is already multiplicated by eight)
- LDA X16,X ; Destination low byte
- ADC YPOS,X ; (16*char code + y-position's 3 lowest bits)
- STA ZP
- LDA D16,X ; Destination high byte
- STA ZP+1
-
- LDY #6 ; Transfer 7 bytes from source to destination
- LDA (ZP2),Y : STA (ZP),Y
- DEY ; This is the fastest way I could think of.
- LDA (ZP2),Y : STA (ZP),Y
- DEY
- LDA (ZP2),Y : STA (ZP),Y
- DEY
- LDA (ZP2),Y : STA (ZP),Y
- DEY
- LDA (ZP2),Y : STA (ZP),Y
- DEY
- LDA (ZP2),Y : STA (ZP),Y
- DEY
- LDA (ZP2),Y : STA (ZP),Y
- DEX
- BPL LOOP5 ; Get next char in scroll
-
- LDA #1
- STA $D019 ; Acknowledge raster interrupt
-
- DEC COUNTER ; Decrease the counter = move the scroll by 1 pixel
- BPL OUT
- LOOP12 LDA CHAR+1,Y ; Move the text one position to the left
- STA CHAR,Y ; (Y-register is initially zero)
- INY
- CPY #AMOUNT
- BNE LOOP12
- LDA POINTER
- AND #63 ; Text is 64 bytes long
- TAX
- LDA SCROLL,X ; Load a new char and multiply it by eight
- ASL
- ASL
- ASL
- STA CHAR+AMOUNT ; Save it to the right side
- DEC START ; Compensation for the text scrolling
- DEC START
- INC POINTER ; Increase the text pointer
- LDA #7
- STA COUNTER ; Initialize X-scroll
-
- OUT JMP $EA7E ; Return from interrupt
-
- SIN BYT 0,3,6,9,12,15,18,21,24,27,30,32,35,38,40,42,45
- BYT 47,49,51,53,54,56,57,59,60,61,62,62,63,63,63
- ; 1/4 of the sinus
-
- LINESL BYT 0,40,80,120,160,200,240,24,64,104,144,184,224
- BYT 8,48,88,128,168,208,248,32
-
- LINESH BYT 0,0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,3
-
- SCROLL SCR "THIS@IS@AN@EXAMPLE@SCROLL@FOR@"
- SCR "COMMODORE@MAGAZINE@BY@PASI@OJALA@@"
- ; SCR will convert text to screen codes
-
- --------------------------------------------------------------------------
- Basic loader for the Dycp demo program (PAL)
-
- 1 S=49152
- 2 DEFFNH(C)=C-48+7*(C>64)
- 3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<white><clr>":SYS49152:END
- 4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
- 5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
- 6 PRINT"CHECKSUM ERROR":END
- 100 DATA 78A9328501A200BD00D09D0038BD00D19D0039CAD0F1A9378501A01FA942187D, 3441
- 101 DATA 75C19D00CF9920CFA94038FD75C19D40CF9960CFE88810E4A27FBD00CF4A1869, 4302
- 102 DATA 209D80CFCA10F3A2278A0A0A0A0A9D00CE8A4A4A4A4A18693C9D30CEA9009D90, 3231
- 103 DATA 03CA10E58D3E03A2078E3D039D0038CA10FAA93885FEA99B8D1403A9C08D1503, 3338
- 104 DATA A97F8D0DDCA9818D1AD0A9A88D12D0A91B8D11D0A91E8D18D05860EE3C03A026, 3864
- 105 DATA AE3C03BD00CF2907994003BD00CF4A4A4A996803E8E88810EAA900A24F9D003C, 3256
- 106 DATA 9D503C9DA03C9DF03C9D403D9D903D9DE03D9D303ECA10E5AD3D038D16D0A226, 3739
- 107 DATA 18BC68038A7995C185FBA90479AAC185FCA920A00091FBA07891FB8A0A0980A0, 4224
- 108 DATA 2891FB6901A05091FBBD900385FDBD00CE7D400385FBBD30CE85FCA006B1FD91, 4440
- 109 DATA FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FB88B1FD91FBCA, 6225
- 110 DATA 109FEE19D0CE3D031028B99103999003C8C026D0F5AD3E03293FAABDBFC10A0A, 3593
- 111 DATA 0A8DB603CE3C03CE3C03EE3E03A9078D3D034C7EEA000306090C0F1215181B1E, 2159
- 112 DATA 202326282A2D2F3133353638393B3C3D3E3E3F3F3F00285078A0C8F018406890, 2268
- 113 DATA B8E008305880A8D0F82000000000000000010101010101020202020202020314, 1379
- 114 DATA 08091300091300010E000518010D100C05001303120F0C0C00060F1200030F0D, 304
- 115 DATA 0D0F040F1205000D0107011A090E050002190010011309000F0A010C01000000, 257
- 200 DATA END,0
-
- --------------------------------------------------------------------------
- Uuencoded C64 executable of the basic loader (PAL)
-
- begin 644 dycp.64
- M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`4@@#`$-(@
- MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(@63(CJ>-#DQ-3(Z@`")"`0`@4:RE
- M,*0S,3I1LJ5(*,8HRBA!)"Q&K#*J,2DI*:PQ-JJE2"C&*,HH020L1JPRJC(IA
- M*2D`J@@%`$-(LD-(JE$ZEU,L43I3LE.J,3J".HM#2+)!IS,`P@@&`)DB0TA%.
- M0TM354T@15)23U(B.H``#PED`(,@-SA!.3,R.#4P,4$R,#!"1#`P1#`Y1#`P.
- M,SA"1#`P1#$Y1#`P,SE#040P1C%!.3,W.#4P,4$P,49!.30R,3@W1"P@,S0T#
- M,0!<"64`@R`W-4,Q.40P,$-&.3DR,$-&03DT,#,X1D0W-4,Q.40T,$-&.3DV&
- M,$-&13@X.#$P131!,C=&0D0P,$-&-$$Q.#8Y+"`T,S`R`*D)9@"#(#(P.40X3
- M,$-&0T$Q,$8S03(R-SA!,$$P03!!,$$Y1#`P0T4X031!-$$T031!,3@V.3-#P
- M.40S,$-%03DP,#E$.3`L(#,R,S$`]@EG`(,@,#-#03$P134X1#-%,#-!,C`WY
- M.$4S1#`S.40P,#,X0T$Q,$9!03DS.#@U1D5!.3E".$0Q-#`S03E#,#A$,34P@
- M,RP@,S,S.`!#"F@`@R!!.3=&.$0P1$1#03DX,3A$,4%$,$$Y03@X1#$R1#!!B
- M.3%".$0Q,40P03DQ13A$,3A$,#4X-C!%13-#,#-!,#(V+"`S.#8T`)`*:0"#]
- M($%%,T,P,T)$,#!#1C(Y,#<Y.30P,#-"1#`P0T8T031!-$$Y.38X,#-%.$4X$
- M.#@Q,$5!03DP,$$R-$8Y1#`P,T,L(#,R-38`W0IJ`(,@.40U,#-#.41!,#-#]
- M.41&,#-#.40T,#-$.40Y,#-$.41%,#-$.40S,#-%0T$Q,$4U040S1#`S.$0Q*
- M-D0P03(R-BP@,S<S.0`J"VL`@R`Q.$)#-C@P,SA!-SDY-4,Q.#5&0D$Y,#0W^
- M.4%!0S$X-49#03DR,$$P,#`Y,49"03`W.#DQ1D(X03!!,#DX,$$P+"`T,C(T:
- M`'<+;`"#(#(X.3%&0C8Y,#%!,#4P.3%&0D)$.3`P,S@U1D1"1#`P0T4W1#0P;
- M,#,X-49"0D0S,$-%.#5&0T$P,#9",49$.3$L(#0T-#``Q`MM`(,@1D(X.$(Q?
- M1D0Y,49".#A",49$.3%&0C@X0C%&1#DQ1D(X.$(Q1D0Y,49".#A",49$.3%&V
- M0C@X0C%&1#DQ1D)#02P@-C(R-0`1#&X`@R`Q,#E&144Q.40P0T4S1#`S,3`RK
- M.$(Y.3$P,SDY.3`P,T,X0S`R-D0P1C5!1#-%,#,R.3-&04%"1$)&0S$P03!!M
- M+"`S-3DS`%X,;P"#(#!!.$1"-C`S0T4S0S`S0T4S0S`S144S13`S03DP-SA$H
- M,T0P,S1#-T5%03`P,#,P-C`Y,$,P1C$R,34Q.#%",44L(#(Q-3D`JPQP`(,@0
- M,C`R,S(V,C@R03)$,D8S,3,S,S4S-C,X,SDS0C-#,T0S13-%,T8S1C-&,#`R[
- M.#4P-SA!,$,X1C`Q.#0P-C@Y,"P@,C(V.`#X#'$`@R!".$4P,#@S,#4X.#!!8
- M.$0P1C@R,#`P,#`P,#`P,#`P,#`P,#$P,3`Q,#$P,3`Q,#(P,C`R,#(P,C`R^
- M,#(P,S$T+"`Q,S<Y`$0-<@"#(#`X,#DQ,S`P,#DQ,S`P,#$P13`P,#4Q.#`Q7
- M,$0Q,#!#,#4P,#$S,#,Q,C!&,$,P0S`P,#8P1C$R,#`P,S!&,$0L(#,P-`"02
- M#7,`@R`P1#!&,#0P1C$R,#4P,#!$,#$P-S`Q,4$P.3!%,#4P,#`R,3DP,#$P.
- L,#$Q,S`Y,#`P1C!!,#$P0S`Q,#`P,#`P+"`R-3<`G`W(`(,@14Y$+#`````PK
- ``
- end
- size 1439
- --------------------------------------------------------------------------
- Uuencoded C64 executable of the basic loader (NTSC)
-
- begin 644 dycp-ntsc.bas
- M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
- MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
- MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
- M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
- M2U-532!%4E)/4B(Z@`#'"%H`@``4"60`@R`W.$$Y,S(X-3`Q03(P,$)$,#!$L
- M,#E$,#`S.$)$,#!$,3E$,#`S.4-!1#!&,4$Y,S<X-3`Q03`Q1D$Y-#(Q.#=$I
- M+"`S-#0Q`&$)90"#(#<U0S$Y1#`P0T8Y.3(P0T9!.30P,SA&1#<U0S$Y1#0P!
- M0T8Y.38P0T9%.#@X,3!%-$$R-T9"1#`P0T8T03$X-CDL(#0S,#(`K@EF`(,@R
- M,C`Y1#@P0T9#03$P1C-!,C(W.$$P03!!,$$P03E$,#!#13A!-$$T031!-$$QJ
- M.#8Y,T,Y1#,P0T5!.3`P.40Y,"P@,S(S,0#["6<`@R`P,T-!,3!%-3A$,T4P.
- M,T$R,#<X13-$,#,Y1#`P,SA#03$P1D%!.3,X.#5&14$Y.4(X1#$T,#-!.4,P;
- M.$0Q-3`S+"`S,S,X`$@*:`"#($$Y-T8X1#!$1$-!.3@Q.$0Q040P03E",#A$:
- M,3)$,$$Y,4(X1#$Q1#!!.3%%.$0Q.$0P-3@V,$5%,T,P,T$P,4,L(#,X-C(`9
- ME0II`(,@044S0S`S0D0P,$-&,CDP-SDY-#`P,T)$,#!#1C1!-$$T03DY-C@PB
- M,T4X13@X.#$P14%!.3`P03(T1CE$,#`S0RP@,S(U-@#B"FH`@R`Y1#4P,T,Y$
- M1$$P,T,Y1$8P,T,Y1#0P,T0Y1#DP,T0Y1$4P,T0Y1#,P,T5#03$P135!1#-$E
- M,#,X1#$V1#!!,C%#+"`S-S(Y`"\+:P"#(#$X0D,V.#`S.$$W.3DU0S$X-49")
- M03DP-#<Y04%#,3@U1D-!.3(P03`P,#DQ1D)!,#<X.3%&0CA!,$$P.3@P03`L#
- M(#0R,C0`?`ML`(,@,C@Y,49"-CDP,4$P-3`Y,49"0D0Y,#`S.#5&1$)$,#!#H
- M13=$-#`P,S@U1D)"1#,P0T4X-49#03`P-D(Q1D0Y,2P@-#0T,`#)"VT`@R!&C
- M0C@X0C%&1#DQ1D(X.$(Q1D0Y,49".#A",49$.3%&0C@X0C%&1#DQ1D(X.$(QA
- M1D0Y,49".#A",49$.3%&0D-!+"`V,C(U`!8,;@"#(#$P.49%13$Y1#!#13-$T
- M,#,Q,#(X0CDY,3`S.3DY,#`S0SA#,#(V1#!&-4%$,T4P,S(Y,T9!04)$0D9#0
- M,3!!,$$L(#,U.3,`8PQO`(,@,$$X1$(V,#-#13-#,#-#13-#,#-%13-%,#-!D
- M.3`W.$0S1#`S-$,W145!,#`P,S`V,#DP0S!&,3(Q-3$X,4(Q12P@,C$U.0"P2
- M#'``@R`R,#(S,C8R.#)!,D0R1C,Q,S,S-3,V,S@S.3-",T,S1#-%,T4S1C-&/
- M,T8P,#(X-3`W.$$P0SA&,#$X-#`V.#DP+"`R,C8X`/T,<0"#($(X13`P.#,P2
- M-3@X,$$X1#!&.#(P,#`P,#`P,#`P,#`P,#`P,3`Q,#$P,3`Q,#$P,C`R,#(P>
- M,C`R,#(P,C`S,30L(#$S-SD`20UR`(,@,#@P.3$S,#`P.3$S,#`P,3!%,#`P3
- M-3$X,#$P1#$P,$,P-3`P,3,P,S$R,$8P0S!#,#`P-C!&,3(P,#`S,$8P1"P@J
- M,S`T`)4-<P"#(#!$,$8P-#!&,3(P-3`P,$0P,3`W,#$Q03`Y,$4P-3`P,#(Q`
- M.3`P,3`P,3$S,#DP,#!&,$$P,3!#,#$P,#`P,#`L(#(U-P"A#7@`@R!%3D0LZ
- $,````#`P0
- ``
- end
- size 1444
-
- ================================================================================
- Opening the borders
- by Pasi 'Albert' Ojala (po87553@cs.tut.fi or albert@cc.tut.fi)
- Written: 20-Jul-92
-
- All timings are in PAL, principles will apply to NTSC too.
- Refer to VIC memory map in Hacking Issue 4.
-
- VIC has many features and transparent borders are one of them. You can not
- make characters appear in the border, but sprites are displayed in the
- border too. "How to do this then?" is the big question.
-
- The screen resolution in C64 has been and will be 320 x 200 pixels. Most
- games need to use the whole screen to be efficient or just plain playable.
- But there still is that useless border area, and you can put score and
- other status information there instead of having them interfere with the
- full-screen smooth-scrolling playing area.
-
-
- _How to disable the vertical borders_
-
- When VIC (Video Interface Controller) has displayed all character rows,
- it will start displaying the vertical border area. It will start displaying
- the characters again in top of the screen. The row select register sets the
- number of character lines on the screen. If we select the 24-row display
- when VIC is drawing the last (25th) row, it does not start to draw the
- border at all ! VIC will think that it already started to draw the border.
-
- The 25-row display must be selected again in the top of the screen, so that
- the border may be opened in the next frame too. The number of displayed rows
- can be selected with the bit 3 in $d011. If the bit is set, VIC will display
- 25 rows and 24 rows otherwise. We have to clear the bit somewhere during the
- last row (raster lines $f2-$fa) and set it again in top of the screen or at
- least somewhere before the last row (line $f2). This has to be done in every
- frame (50 times per second in PAL).
-
-
- _How to open the sideborders_
-
- The same trick can be applied to sideborders. When VIC is about to start
- displaying the sideborder, just select 38-column mode and restore 40-column
- mode so that you can do the trick again in the next scan line. If you need to
- open the sideborders in the bottom or top border area, you have to open the
- vertical borders also, but there shouldn't be any difficulty in doing that.
-
- There is two drawbacks in this. The timing must be precise, one clock cycle
- off and the sideborder will not open (the sprites will generally take care of
- the timing) and you have to do the opening on each and every line. With
- top/bottom borders once in a frame was enough.
-
- Another problem is bad-lines. There is not enough time to open the borders
- during a bad line and still have all of the sprites enabled. One solution
- is to open the borders only on seven lines and leave the bad lines unopened.
- Another way is to use less than eight sprites. You can have six of them
- on a bad line and still be able to open the sideborders (PAL). The old and
- still good solution is to scroll the bad lines, so that VIC will not start
- to draw the screen at all until it is allowed to do so.
- [Read more about bad lines from previous C=Hacking Issues]
-
-
- _Scrolling the screen_
-
- VIC begins to draw the screen from the first bad line. VIC will know what
- line is a bad line by comparing its scan line counter to the vertical
- scroll register : when they match, the next line is a bad line. If we change
- the vertical scroll register ($d011), the first bad line will move also.
- If we do this on every line, the line counter in VIC will never match with
- it and the drawing never starts (until it is allowed to do so).
-
- When we don't have to worry about bad lines, we have enough time to open the
- borders and do some other effects too. It is not necassary to change the
- vertical scroll on every line to get rid of the bad lines, just make sure
- that it never matches the line counter (or actually the least significant
- 8 bits).
-
- You can even scroll the bad lines independently and you have FLD - Flexible
- Line Distance. You just allow a bad line when it is time to display the next
- character row. With this you can bounce the lines or scroll a hires picture
- very fast down the screen. But this has not so much to do with borders, so
- I will leave it to another article. (Just send requests and I might start
- writing about FLD ..)
-
-
- _Garbage appearing_
-
- When we open the top and bottom borders, some graphics may appear. Even
- though VIC has already completed the graphics data fetches for the screen
- area, it will still fetch data for every character position in top and bottom
- borders. This will not do any harm though, because it does not generate any
- bad lines and happens during video fetch cycles [see Missing Cycles article].
- VIC reads the data from the last address in the current video bank, which is
- normally $3fff and displays this over and over again.
-
- If we change the data in this address in the border area, the change will be
- visible right away. And if you synchronize the routine to the beam position,
- you can have a different value on each line. If there is nothing else to do
- in the border, you can get seven different values on each scan line.
-
- The bad thing about this graphics is that it is impossible to change its
- color - it is always black. It is of course possible to use inverted graphics
- and change the background color. And if you have different data on each line,
- you can as easily have different color(s) on each line too.
-
- If you don't use $3fff for any effects, it is a good idea to set it to zero,
- but remember to check that you do not store anything important in that
- address. In one demo I just cleared $3fff and it was right in the middle of
- another packed demopart. It took some time to find out what was wrong with
- the other part.
-
-
- _Horizontal scrolling_
-
- This new graphics data also obeys the horizontal scroll register ($D016), so
- you can do limited tech-tech effects in the border too. You can also use
- sprites and open the sideborders. You can see an example of the tech-tech
- effect in the first example program. Multicolor mode select has no effect
- on this data. You can read more about tech-tech effects in a future article.
-
-
- _Example routine_
-
- The example program will show how to open the top and bottom borders and how
- to use the $3fff-graphics. It is fairly well commented, so just check it for
- details. The program uses a sprite to do the synchronization [see Missing
- Cycles article] and reads a part of the character ROM to the display data
- buffer. To be honest, I might add that this is almost the same routine than
- the one in the Missing Cycles article. I have included both PAL and NTSC
- versions of the executables.
-
- --------------------------------------------------------------------------
- The example program - $3fff-graphics
-
- IMAGE0= $CE00 ; First graphics piece to show
- IMAGE1= $CF00 ; Second piece
- TECH= $CD00 ; x-shift
- RASTER= $FA ; Rasterline for the interrupt
- DUMMY= $CFFF ; Dummy-address for timing (refer to missing_cycles-article)
-
- *= $C000
- SEI ; Disable interrupts
- LDA #$7F ; Disable timer interrupts (CIA)
- STA $DC0D
- LDA #$01 ; Enable raster interrupts (VIC)
- STA $D01A
- STA $D015 ; Enable the timing sprite
- LDA #<IRQ
- STA $0314 ; Interrupt vector to our routine
- LDA #>IRQ
- STA $0315
- LDA #RASTER ; Set the raster compare (9th bit will be set
- STA $D012 ; inside the raster routine)
- LDA #RASTER-20 ; Sprite is situated 20 lines before the interrupt
- STA $D001
-
- LDX #111
- LDY #0
- STY $D017 ; Disable y-expand
- LDA #$32
- STA $01 ; Select Character ROM
- LOOP0 LDA $D000,X
- STA IMAGE0,Y ; Copy a part of the charset to be the graphics
- STA IMAGE0+112,Y
- LDA $D800,X
- STA IMAGE1,Y
- STA IMAGE1+112,Y
- INY ; Until we copied enough
- DEX
- BPL LOOP0
- LDA #$37 ; Char ROM out of the address space
- STA $01
-
- LDY #15
- LOOP1 LDA XPOS,Y ; Take a half of a sinus and mirror it to make
- STA TECH,Y ; a whole cycle and then copy it as many times
- STA TECH+32,Y ; as necassary
- LDA #24
- SEC
- SBC XPOS,Y
- STA TECH+16,Y
- STA TECH+48,Y
- DEY
- BPL LOOP1
- LDY #64
- LOOP2 LDA TECH,Y
- STA TECH+64,Y
- STA TECH+128,Y
- DEY
- BPL LOOP2
- CLI ; Enable interrupts
- RTS ; Return to basic (?)
-
-
- IRQ LDA #$13 ; Open the bottom border (top border will open too)
- STA $D011
- NOP
- LDY #111 ; Reduce for NTSC ?
- INC DUMMY ; Do the timing with a sprite
- BIT $EA ; Wait a bit (add a NOP for NTSC)
-
- LOOP3 LDA TECH,Y ; Do the x-shift
- STA $D016
- FIRST LDX IMAGE0,Y ; Load the graphics to registers
- SECOND LDA IMAGE1,Y
- STA $3FFF ; Alternate the graphics
- STX $3FFF
- STA $3FFF
- STX $3FFF
- STA $3FFF
- STX $3FFF
- STA $3FFF
- STX $3FFF
- STA $3FFF
- STX $3FFF
- LDA #0 ; Throw away 2 cycles (add a NOP for NTSC)
- DEY
- BPL LOOP3
-
- STA $3FFF ; Clear the graphics
- LDA #8
- STA $D016 ; x-scroll to normal
- LDA #$1B
- STA $D011 ; Normal screen (be ready to open the border again)
- LDA #111
- DEC FIRST+1 ; Move the graphics by changing the low byte of the
- BPL OVER ; load instruction
- STA FIRST+1
- OVER SEC
- SBC FIRST+1
- STA SECOND+1 ; Another graphics goes to opposite direction
- LDA LOOP3+1 ; Move the x-shift also
- SEC
- SBC #2
- AND #31 ; Sinus cycle is 32 bytes
- STA LOOP3+1
-
- LDA #1
- STA $D019 ; Acknowledge the raster interrupt
- JMP $EA31 ; jump to the normal irq-handler
-
- XPOS BYT $C,$C,$D,$E,$E,$F,$F,$F,$F,$F,$F,$F,$E,$E,$D,$C
- BYT $C,$B,$A,$9,$9,$8,$8,$8,$8,$8,$8,$8,$9,$9,$A,$B
- ; half of the sinus
-
- --------------------------------------------------------------------------
- Basic loader for the $3fff-program (PAL)
-
- 1 S=49152
- 2 DEFFNH(C)=C-48+7*(C>64)
- 3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<clear>":SYS49152:END
- 4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
- 5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
- 6 PRINT"CHECKSUM ERROR":END
- 100 DATA 78A97F8D0DDCA9018D1AD08D15D0A9718D1403A9C08D1503A9FA8D12D0A9E68D,4003
- 101 DATA 01D0A26FA0008C17D0A9328501BD00D09900CE9970CEBD00D89900CF9970CFC8,4030
- 102 DATA CA10EAA9378501A00FB9DCC09900CD9920CDA91838F9DCC09910CD9930CD8810,4172
- 103 DATA E8A040B900CD9940CD9980CD8810F45860A9138D11D0EAA06FEEFFCF24EAB906,4554
- 104 DATA CD8D16D0BE53CEB91CCF8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8E,4833
- 105 DATA FF3F8DFF3F8EFF3FA9008810D18DFF3FA9088D16D0A91B8D11D0A96FCE85C010,4163
- 106 DATA 038D85C038ED85C08D88C0AD7FC018E901291F8D7FC0EE19D04C31EA0C0C0D0E,3719
- 107 DATA 0E0F0F0F0F0F0F0F0E0E0D0C0C0B0A09090808080808080809090A0B00000000,318
- 200 DATA END,0
-
- --------------------------------------------------------------------------
- An uuencoded C64 executable $3fff-program (PAL)
-
- begin 644 xFFF.64
- M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
- MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
- MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
- M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
- M2U-532!%4E)/4B(Z@``."60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0Q040P.$0QK
- M-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$+"`T,#`S9
- M`%L)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0P.3DP,$-%Y
- M.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``J`EF`(,@0T$Q,$5!S
- M03DS-S@U,#%!,#!&0CE$0T,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$0T,P.3DQL
- M,$-$.3DS,$-$.#@Q,"P@-#$W,@#U"6<`@R!%.$$P-#!".3`P0T0Y.30P0T0Y0
- M.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T14%".3`V5
- M+"`T-34T`$(*:`"#($-$.$0Q-D0P0D4U,T-%0CDQ0T-&.$1&1C-&.$5&1C-&%
- M.$1&1C-&.$5&1C-&.$1&1C-&.$5&1C-&.$1&1C-&.$4L(#0X,S,`CPII`(,@'
- M1D8S1CA$1D8S1CA%1D8S1D$Y,#`X.#$P1#$X1$9&,T9!.3`X.$0Q-D0P03DQ-
- M0CA$,3%$,$$Y-D9#13@U0S`Q,"P@-#$V,P#<"FH`@R`P,SA$.#5#,#,X140X+
- M-4,P.$0X.$,P040W1D,P,3A%.3`Q,CDQ1CA$-T9#,$5%,3E$,#1#,S%%03!#.
- M,$,P1#!%+"`S-S$Y`"@+:P"#(#!%,$8P1C!&,$8P1C!&,$8P13!%,$0P0S!#P
- M,$(P03`Y,#DP.#`X,#@P.#`X,#@P.#`Y,#DP03!",#`P,#`P,#`L(#,Q.``T>
- -"\@`@R!%3D0L,````#@P1
- ``
- end
- size 823
- --------------------------------------------------------------------------
- An uuencoded C64 executable $3fff-program (NTSC)
-
- begin 644 xfff-ntsc.64
- M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
- MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
- MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
- M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
- M2U-532!%4E)/4B(Z@`#'"%H`@``4"60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0QX
- M040P.$0Q-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$H
- M+"`T,#`S`&$)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0PX
- M.3DP,$-%.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``K@EF`(,@H
- M0T$Q,$5!03DS-S@U,#%!,#!&0CE$14,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$`
- M14,P.3DQ,$-$.3DS,$-$.#@Q,"P@-#$W-@#["6<`@R!%.$$P-#!".3`P0T0Y8
- M.30P0T0Y.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T+
- M14%%04(Y+"`T-S@R`$@*:`"#(#`P0T0X1#$V1#!"13`P0T5".3`P0T8X1$9&>
- M,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8L(#0U.#``?
- ME0II`(,@.$5&1C-&.$1&1C-&.$5&1C-&14%!.3`P.#@Q,$0P.$1&1C-&03DP`
- M.#A$,39$,$$Y,4(X1#$Q1#!!.39&0T4X-BP@-#,S,0#B"FH`@R!#,#$P,#,XY
- M1#@V0S`S.$5$.#9#,#A$.#E#,$%$.#!#,#$X13DP,3(Y,48X1#@P0S!%13$YO
- M1#`T0S,Q14$P0S!#+"`S.3`U`"X+:P"#(#!$,$4P13!&,$8P1C!&,$8P1C!&W
- M,$4P13!$,$,P0S!",$$P.3`Y,#@P.#`X,#@P.#`X,#@P.3`Y,$$P0D$R,#`L%
- 4(#4P-P`["VP`@R!%3D0L+3$````PB
- ``
- end
- size 830
-
- ==============================================================================
- 64 Documentation
- by Jarkko Sonninen, Jouko Valta, John West, and Marko M"akel"a
- (sonninen@lut.fi, jopi@stekt.oulu.fi, john@ucc.gu.uwa.edu.au,
- msmakela@hylk.helsinki.fi)
-
- [Ed's Note: I'm leaving this file as is because of its intention to
- serve as a reference guide, and not necessarily to be presented in
- article format. The detail and clarity with which the authors have
- presented the material is wonderful!!]
-
- #
- # $Id: 64doc,v 1.3 93/06/21 13:37:18 jopi Exp $
- #
- # This file is part of Commodore 64 emulator
- # and Program Development System.
- #
- # See README for copyright notice
- #
- # This file contains documentation for 6502/6510/8502 instruction set.
- #
- # Written by
- # Jarkko Sonninen (sonninen@lut.fi)
- # Jouko Valta (jopi@stekt.oulu.fi)
- # John West (john@ucc.gu.uwa.edu.au)
- # Marko M"akel"a (msmakela@hylk.helsinki.fi)
- #
- # $Log: 64doc,v $
- # Revision 1.3 93/06/21 13:37:18 jopi
- # X64 version 0.2 PL 0
- #
- # Revision 1.2 93/06/21 13:07:15 jopi
- # *** empty log message ***
- #
- #
- #
-
- 6510 Instructions by Addressing Modes
-
- ++++++++ Positive ++++++++++ -------- Negative ----------
- 00 20 40 60 80 a0 c0 e0 mode
-
- +00 BRK JSR RTI RTS NOP* LDY CPY CPX Impl/immed
- +01 ORA AND EOR ADC STA LDA CMP SBC (indir,x)
- +02 t t t t NOP*t LDX NOP*t NOP*t ? /immed
- +03 SLO* RLA* SRE* RRA* SAX* LAX* DCP* ISB* (indir,x)
- +04 NOP* BIT NOP* NOP* STY LDY CPY CPX Zeropage
- +05 ORA AND EOR ADC STA LDA CMP SBC -"-
- +06 ASL ROL LSR ROR STX LDX DEC INC -"-
- +07 SLO* RLA* SRE* RRA* SAX* LAX* DCP* ISB* -"-
-
- +08 PHP PLP PHA PLA DEY TAY INY INX Implied
- +09 ORA AND EOR ADC NOP* LDA CMP SBC Immediate
- +0a ASL ROL LSR ROR TXA TAX DEX NOP Accu/impl
- +0b ANC** ANC** ASR** AR
- Cancel
-
- /duck/mailserv/hacking>
- Interrupt
-
- /duck/mailserv/hacking>
-
- 6510 Instructions by Addressing Modes
-
- ++++++++ Positive ++++++++++ -------- Negative ----------
- 00 20 40 60 80 a0 c0 e0 mode
-
- +00 BRK JSR RTI RTS NOP* LDY CPY CPX Impl/immed
- +01 ORA AND EOR ADC STA LDA CMP SBC (indir,x)
- +02 t t t t NOP*t LDX NOP*t NOP*t ? /immed
- +03 SLO* RLA* SRE* RRA* SAX* LAX* DCP* ISB* (indir,x)
- +04 NOP* BIT NOP* NOP* STY LDY CPY CPX Zeropage
- +05 ORA AND EOR ADC STA LDA CMP SBC -"-
- +06 ASL ROL LSR ROR STX LDX DEC INC -"-
- +07 SLO* RLA* SRE* RRA* SAX* LAX* DCP* ISB* -"-
-
- +08 PHP PLP PHA PLA DEY TAY INY INX Implied
- +09 ORA AND EOR ADC NOP* LDA CMP SBC Immediate
- +0a ASL ROL LSR ROR TXA TAX DEX NOP Accu/impl
- +0b ANC** ANC** ASR** ARR** ANE** LXA** SBX** SBC* Immediate
- +0c NOP* BIT JMP JMP STY LDY CPY CPX Absolute
- +0d ORA AND EOR ADC STA LDA CMP SBC -"-
- +0e ASL ROL LSR ROR STX LDX DEC INC -"-
- +0f SLO* RLA* SRE* RRA* SAX* LAX* DCP* ISB* -"-
-
- +10 BPL BMI BVC BVS BCC BCS BNE BEQ Relative
- +11 ORA AND EOR ADC STA LDA CMP SBC (indir),y
- +12 t t t t t t t t ?
- +13 SLO* RLA* SRE* RRA* SHA** LAX* DCP* ISB* (indir),y
- +14 NOP* NOP* NOP* NOP* STY LDY NOP* NOP* Zeropage,x
- +15 ORA AND EOR ADC STA LDA CMP SBC -"-
- +16 ASL ROL LSR ROR STX y) LDX y) DEC INC -"-
- +17 SLO* RLA* SRE* RRA* SAX* y) LAX* y) DCP ISB -"-
-
- +18 CLC SEC CLI SEI TYA CLV CLD SED Implied
- +19 ORA AND EOR ADC STA LDA CMP SBC Absolute,y
- +1a NOP* NOP* NOP* NOP* TXS TSX NOP* NOP* Implied
- +1b SLO* RLA* SRE* RRA* SHS** LAS** DCP* ISB* Absolute,y
- +1c NOP* NOP* NOP* NOP* SHY** LDY NOP* NOP* Absolute,x
- +1d ORA AND EOR ADC STA LDA CMP SBC -"-
- +1e ASL ROL LSR ROR SHX**y) LDX y) DEC INC -"-
- +1f SLO* RLA* SRE* RRA* SHA**y) LAX* y) DCP ISB -"-
-
- Legend:
-
- t Jams the machine
- *t Jams very rarely
- * Undocumented command
- ** Unusual operation
- y) indexed using IY instead of IX
-
-
-
- 6510/8502 Undocumented Commands
-
- -- A brief explanation about what may happen while
- using don't care states.
-
-
- ANE $8B AC = (AC | #$EE) & IX & #byte
- same as
- AC = ((AC & #$11 & IX) | ( #$EE & IX)) & #byte
-
- In real 6510/8502 the internal parameter #$11 may
- occasionally be #$10, #$01 or even #$00. This occurs
- probably when the VIC halts the processor right between
- the two clock cycles of this instruction.
-
- LXA $AB C=Lehti: AC = IX = ANE
- Alternate: AC = IX = (AC & #byte)
-
- TXA and TAX have to be responsible for these.
-
-
- SHA $93,$9F Store (AC & IX & (ADDR_HI + 1))
- SHX $9E Store (IX & (ADDR_HI + 1))
- SHY $9C Store (IY & (ADDR_HI + 1))
- SHS $9B SHA and TXS, where X is replaced by (AC & IX).
-
- Note: The value to be stored is copied also
- to ADDR_HI if page boundary is crossed.
-
-
- SBX $CB Carry and Decimal flags are ignored but set in
- substraction. This is due to the CMP command,
- which is executed instead of the real SBC.
-
-
- Many undocumented commands do not use AND between registers, the CPU just
- throws the bytes to a bus simultaneously and lets the open-collector drivers
- perform the AND. I.e. the command called 'SAX', which is in the STORE section
- (opcodes $A0...$BF), stores the result of (AC & IX) by this way.
-
- More fortunate is its opposite, 'LAX' which just loads a byte simultaeously
- into both AC and IX.
-
-
- $CB SBX IX <- (AC & IX) - Immediate
-
- The 'SBX' ($CB) may seem to be very complex operation, even though it is
- combination of subtraction of accumulator and parameter, as in the 'CMP'
- instruction, and the command 'DEX'. As a result, both AC and IX are connected
- to ALU but only the subtraction takes place. Since the comparison logic was
- used, the result of subtraction should be normally ignored, but the 'DEX' now
- happily stores to IX the value of (AC & IX) - Immediate.
- That is why this instruction does not have any decimal mode, and it does not
- affect the V flag. Also Carry flag is ignored in the subtraction but set
- according to the result.
-
- Proof:
-
- begin 644 vsbx
- M`0@9$,D'GL(H-#,IJC(U-JS"*#0T*:HR-@```*D`H#V1*Z`_D2N@09$KJ0>%
- M^QBE^VEZJ+$KH#F1*ZD`2"BI`*(`RP`(:-B@.5$K*4#P`E@`H#VQ*SAI`)$K
- JD-Z@/[$K:0"1*Y#4J2X@TO\XH$&Q*VD`D2N0Q,;[$+188/_^]_:_OK>V
- `
- end
-
- and
-
- begin 644 sbx
- M`0@9$,D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'BI`*!-D2N@3Y$KH%&1*ZD#
- MA?L8I?M*2)`#J1@LJ3B@29$K:$J0`ZGX+*G8R)$K&/BXJ?2B8\L)AOP(:(7]
- MV#B@3;$KH$\Q*Z!1\2L(1?SP`0!H1?TIM]#XH$VQ*SAI`)$KD,N@3[$K:0"1
- 9*Y#!J2X@TO\XH%&Q*VD`D2N0L<;[$))88-#X
- `
- end
-
- These test programs show if your machine is compatible with ours
- regarding the opcode $CB. The first test, vsbx, shows that SBX does
- not affect the V flag. The latter one, sbx, shows the rest of our
- theory. The vsbx test tests 33554432 SBX combinations (16777216
- different AC, IX and Immediate combinations, and two different V flag
- states), and the sbx test doubles that amount (16777216*4 D and C flag
- combinations). Both tests have run successfully on a C64 and a Vic20.
- They ought to run on C16, +4 and the PET series as well. The tests
- stop with BRK, if the opcode $CB does not work expectedly. Successful
- operation ends in RTS. As the tests are very slow, they print dots on
- the screen while running so that you know that the machine has
- not jammed. On computers running at 1 MHz, the first test prints
- approximately one dot every four seconds and a total of 2048 dots,
- whereas the second one prints half that amount, one dot every seven seconds.
-
- If the tests fail on your machine, please let us know your processor's part
- number and revision. If possible, save the executable (after it has stopped
- with BRK) under another name and send it to us so that we know at which stage
- the program stopped.
-
- The following program is a Commodore 64 executable that Marko M"akela
- developed when trying to find out how the V flag is affected by SBX.
- (It was believed that the SBX affects the flag in a weird way, and this
- program shows how SBX sets the flag differently from SBC.)
- You may find the subroutine at $C150 useful when researching other
- undocumented instructions' flags. Run the program in a machine language
- monitor, as it makes use of the BRK instruction. The result tables will be
- written on pages $C2 and $C3.
-
- begin 644 sbx-c100
- M`,%XH`",#L&,$,&,$L&XJ8*B@LL7AOL(:(7\N#BM#L$M$,'M$L$(Q?OP`B@`
- M:$7\\`,@4,'N#L'0U.X0P=#/SB#0[A+!T,<``````````````)BJ\!>M#L$M
- L$,'=_\'0":T2P=W_PM`!8,K0Z:T.P2T0P9D`PID`!*T2P9D`PYD`!<C0Y``M
- `
- end
-
-
- Other undocumented instructions usually cause two preceding opcodes being
- executed. However 'NOP' seems to completely disappear from 'SBC' code $EB.
-
- The most difficult to comprehend are the rest of the instructions located on
- the '$0B' line.
-
- All the instructions located at the positive (left) side of this line should
- rotate either memory or the accumulator, but the addressing mode turns out
- to be immediate!
- No problem. Just read the operand, let it be ANDed with the accumulator
- and finally use accumulator addressing mode for the instructions above them.
-
- The rest two instructions on the same line, called 'ANE' and 'LXA' ($8B and
- $AB respectively) often give quite unpredictable results.
- However, the most usual operation is to store ((A | #$ee) & X & #$nn) to
- accumulator. Note that this does not work reliably in a real 64!
- On 8502 opcode $8B uses values 8C,CC, EE, and occasionally 0C and 8E for the
- OR instead of EE,EF,FE and FF used by 6510. With 8502 running at 2 MHz #$EE is
- always used.
- Opcode $AB does not cause this OR taking place on 8502 while 6510 always
- performs it. Note that this behaviour depends on chip revision.
-
- Let's take a closer look at $8B (6510).
-
- AC <- IX & D & (AC | VAL)
-
- where VAL comes from this table:
-
- IX high D high D low VAL
- even even --- $EE (1)
- even odd --- $EE
- odd even --- $EE
- odd odd 0 $EE
- odd odd not 0 $FE (2)
-
- (1) If the bottom 2 bits of AC are both 1, then the LSB of the result may
- be 0. The values of IX and D are different every time I run the test.
- This appears to be very rare.
- (2) VAL is $FE most of the time. Sometimes it is $EE - it seems to be random,
- not related to any of the data. This is much more common than (1).
-
- In decimal mode, VAL is usually $FE.
-
-
- Two different functions has been discovered for LAX, opcode $AB. One is
- AC = IX = ANE (see above) and the other, encountered with 6510 and 8502,
- is less complicated AC = IX = (AC & #byte). However, according to what is
- reported, the version altering only the lowest bits of each nybble seems to
- be more common.
-
- What happens, is that $AB loads a value into both AC and IX, ANDing
- the low bit of each nybble with the corresponding bit of the old AC. However,
- there are exceptions. Sometimes the low bit is cleared even when AC contains
- a '1', and sometimes other bits are cleared. The exceptions seem random (they
- change every time I run the test). Oops - that was in decimal mode. Much
- the same with D=0.
-
- What causes the randomness? Probably it is that it is marginal logic levels -
- when too much wired-anding goes on, some of the signals get very close to
- the threshold. Perhaps we're seeing some of them step over it. The low bit
- of each nybble is special, since it has to cope with carry differently
- (remember decimal mode). We never see a '0' turn into a '1'.
-
- Since these instructions are unpredictable, they should not be used.
-
-
- There is still very strange instruction left, the one named SHA/X/Y, which is
- the only one with only indexed addressing modes. Actually, the commands 'SHA',
- 'SHX' and 'SHY' are generated by the indexing algorithm.
-
- While using indexed addressing, effective address for page boundary crossing
- is calculated as soon as possible so it does not slow down operation.
- As a result, in the case of SHA/X/Y, the address and data are prosessed at the
- same time making AND between the to take place. Thus, the value to be stored
- by SAX, for example, is in fact (AC & IX & (ADDR_HI + 1)).
- On page boundary crossing the same value is copied also to high byte of the
- effective address.
-
-
-
- Register selection for load and store
-
- bit1 bit0 AC IX IY
- 0 0 x
- 0 1 x
- 1 0 x
- 1 1 x x
-
- So, AC and IX are selected by bits 1 and 0 respectively, while ~(bit1 | bit0)
- enables IY.
-
- Indexing is determined by bit4, even in relative addressing mode, which
- is one kind of indexing.
-
- Lines containing opcodes xxx000x1 (01 and 03) are treated as absolute after
- the effective address has been loaded into CPU.
-
- Zeropage,y and Absolute,y (codes 10x1 x11x) are distinquished by bit5.
-
-
- Decimal mode in NMOS 6500 series
-
- Most sources claim that the NMOS 6500 series sets the N, V and Z flags
- unpredictably. Of course, this is not true. While testing how the flags are
- set, I also wanted to see what happens if you use illegal BCD values.
-
- ADC works in Decimal mode in a quite complicated way. It is amazing how it
- can do that all in a single cycle. Here's a pseudo code version of the
- instruction:
-
- AC accumulator
- AL low nybble of accumulator
- AH high nybble of accumulator
-
- C Carry flag
- Z Zero flag
- V oVerflow flag
- N Negative flag
-
- s value to be added to accumulator
-
- AL = (AC & 15) + (s & 15) + C; ! Calculate the lower nybble.
-
- if (AL > 9) ! BCD fixup
- AL += 6; ! for lower nybble
-
- AH = (A >> 4) + (s >> 4) + (AL > 15); ! Calculate the upper nybble.
-
- Z = (AC + s + C != 0); ! Zero flag is set just
- ! like in Binary mode.
-
- ! Negative and Overflow flags are set with the same logic than in
- ! Binary mode, but after fixing the lower nybble.
-
- N = (AH & 8 != 0);
- V = ((AH & 8) ^ (A >> 4)) && (!(A ^ s) & 128);
-
- if (AH > 9) ! BCD fixup
- AH += 6; ! for upper nybble
-
-
-
- ! Carry is the only flag set after fixing the result.
-
- C = (AH > 15);
- AC = ((AH << 4) | (AL & 15)) & 255;
-
-
- The C flag is set as the quiche eaters expect, but the N and V flags
- are set after fixing the lower nybble but before fixing the upper one.
- They use the same logic than binary mode ADC. The Z flag is set before
- any BCD fixup, so the D flag does not have any influence on it.
-
- Proof: The following test program tests all 131072 ADC combinations in
- Decimal mode, and aborts with BRK if anything breaks this theory.
- If everything goes well, it ends in RTS.
-
- begin 600 dadc
- M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@ 'BI&* A/N$_$B@+)$KH(V1
- M*Q@(I?PI#X7]I?LI#V7]R0J0 FD%J"D/A?VE^RGP9?PI\ C $) ":0^JL @H
- ML ?)H) &""@X:5\X!?V%_0AH*3W@ ! ""8"HBD7[$ JE^T7\, 28"4"H**7[
- M9?S0!)@) J@8N/BE^V7\V A%_= G:(3]1?W0(.;[T(?F_-"#:$D8\ )88*D=
- 0&&4KA?NI &4LA?RI.&S[ A%
-
- end
-
- All programs in this chapter have been successfully tested on a Vic20
- and a Commodore 64. They should run on C16, +4 and on the PET series as
- well. If not, please report the problem to Marko M"akel"a. Each test in
- this chapter should run in less than a minute at 1 MHz.
-
- SBC is much easier. Just like CMP, its flags are not affected by
- the D flag.
-
- Proof:
-
- begin 600 dsbc-cmp-flags
- M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@ 'B@ (3[A/RB XH8:66HL2N@
- M09$KH$R1*XII::BQ*Z!%D2N@4)$K^#BXI?OE_-@(:(7].+BE^^7\"&A%_? !
- 5 .;[T./F_-#?RA"_8!@XCEY<7%
-
- end
-
-
- The only difference in SBC's operation in decimal mode from binary mode
- is the result-fixup:
-
- AC accumulator
- AL low nybble of accumulator
- AH high nybble of accumulator
-
- C Carry flag
- Z Zero flag
- V oVerflow flag
- N Negative flag
-
- s value to be added to accumulator
-
- AL = (AC & 15) - (s & 15) - !C; ! Calculate the lower nybble.
-
- if (AL & 16) ! BCD fixup
- AL -= 6; ! for lower nybble
-
- AH = (AC >> 4) - (s >> 4) - (AL > 15); ! Calculate the upper nybble.
-
- if (AH & 16) ! BCD fixup
- AH -= 6; ! for upper nybble
-
- ! Flags are set just like in Binary mode.
-
- C = (AC - s - !C > 255);
- Z = (AC - s - !C != 0);
- V = ((AC - s - !C) ^ s) && ((AC ^ s) & 128);
- N = ((AC - s - !C) & 128);
-
- AC = ((AH << 4) | (AL & 15)) & 255;
-
-
- Again Z flag is set before any BCD fixup. The N and V flags are set
- at any time before fixing the high nybble. The C flag may be set in any
- phase.
-
- Decimal subtraction is easier than decimal addition, as you have to
- make the BCD fixup only when a nybble flows over. In decimal addition,
- you had to verify if the nybble was greater than 9. The processor has
- an internal "half carry" flag for the lower nybble, and it uses it to
- trigger the BCD fixup. When calculating with legal BCD values, the
- lower nybble cannot flow over again when fixing it. So the processor
- does not handle overflows while performing the fixup. Similarly, the
- BCD fixup occurs in the high nybble only if the value flows over,
- i.e. when the C flag will be cleared.
-
- Because SBC's flags are not affected by the Decimal mode flag, you
- could guess that CMP uses the SBC logic, only setting the C flag
- first. But the SBX instruction shows that CMP also temporarily clears
- the D flag, although it is totally unnecessary.
-
- The following program, which tests SBC's result and flags,
- contains the 6502 version of the pseudo code example above.
-
- begin 600 dsbc
- M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@ 'BI&* A/N$_$B@+)$KH':1
- M*S@(I?PI#X7]I?LI#^7]L /I!1@I#ZBE_"GPA?VE^RGP"#CE_2GPL KI7RBP
- M#ND/.+ )*+ &Z0^P NE?A/T%_87]*+BE^^7\"&BH.+CXI?OE_-@(1?W0FVB$
- 8_47]T)3F^]">YOS0FFA)&- $J3C0B%A@
-
- end
-
- Obviously the undocumented instructions RRA (ROR+ADC) and ISB
- (INC+SBC) have inherited also the decimal operation from the official
- instructions ADC and SBC. The program droradc shows this statement
- for ROR, and the dincsbc test shows this for ISB. Finally,
- dincsbc-deccmp shows that ISB's and DCP's (DEC+CMP) flags are not
- affected by the D flag.
-
- begin 644 droradc
- M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'BI&*``A/N$_$B@+)$KH(V1
- M*S@(I?PI#X7]I?LI#V7]R0J0`FD%J"D/A?VE^RGP9?PI\`C`$)`":0^JL`@H
- ML`?)H)`&""@X:5\X!?V%_0AH*3W@`!`""8"HBD7[$`JE^T7\,`28"4"H**7[
- M9?S0!)@)`J@XN/BE^R;\9_S8"$7]T"=HA/U%_=`@YOO0A>;\T(%H21CP`EA@
- 2J1T892N%^ZD`92R%_*DX;/L`
- `
- end
-
- begin 644 dincsbc
- M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'BI&*``A/N$_$B@+)$KH':1
- M*S@(I?PI#X7]I?LI#^7]L`/I!1@I#ZBE_"GPA?VE^RGP"#CE_2GPL`KI7RBP
- M#ND/.+`)*+`&Z0^P`NE?A/T%_87]*+BE^^7\"&BH.+CXI?O&_.?\V`A%_="9
- ::(3]1?W0DN;[T)SF_-"8:$D8T`2I.-"&6\
- `
- end
-
- begin 644 dincsbc-deccmp
- M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'B@`(3[A/RB`XH8:7>HL2N@
- M3Y$KH%R1*XII>ZBQ*Z!3D2N@8)$KBFE_J+$KH%61*Z!BD2OX.+BE^^;\Q_S8
- L"&B%_3BXI?OF_,?\"&A%_?`!`.;[T-_F_-#;RA"M8!@XCFYL;&Q\?GYP#8
- `
- end
-
-
-
- 6510 features
-
- o PHP always pushes the Break (B) flag as a `1' to the stack.
- Jukka Tapanim"aki claimed in C=lehti issue 3/89, on page 27 that the
- processor makes a logical OR between the status register's bit 4
- and the bit 8 of the stack register (which is always 1).
-
- o Indirect addressing modes do not handle page boundary crossing at all.
- When the parameter's low byte is $FF, the effective address wraps
- around and the CPU fetches high byte from $xx00 instead of $xx00+$0100.
- E.g. JMP ($01FF) fetches PCL from $01FF and PCH from $0100,
- and LDA ($FF),Y fetches the base address from $FF and $00.
-
- o Indexed zero page addressing modes never fix the page address on
- crossing the zero page boundary.
- E.g. LDX #$01 : LDA ($FF,X) loads the effective address from $00 and $01.
-
- o The processor always fetches the byte following a relative branch
- instruction. If the branch is taken, the processor reads then the
- opcode from the destination address. If page boundary is crossed, it
- first reads a byte from the old page from a location that is bigger
- or smaller than the correct address by one page.
-
- o If you cross a page boundary in any other indexed mode,
- the processor reads an incorrect location first, a location that is
- smaller by one page.
-
- o Read-Modify-Write instructions write unmodified data, then modified
- (so INC effectively does LDX loc;STX loc;INX;STX loc)
-
- o -RDY is ignored during writes
- (This is why you must wait 3 cycles before doing any DMA -
- the maximum number of consecutive writes is 3, which occurs
- during interrupts except -RESET.)
-
- o Some undefined opcodes may give really unpredictable results.
-
- o All registers except the Program Counter remain the same after -RESET.
- (This is why you must preset D and I flags in the RESET handler.)
-
-
- Different CPU types
-
- The Rockwell data booklet 29651N52 (technical information about R65C00
- microprocessors, dated October 1984), lists the following differences between
- NMOS R6502 microprocessor and CMOS R65C00 family:
-
- 1. Indexed addressing across page boundary.
- NMOS: Extra read of invalid address.
- CMOS: Extra read of last instruction byte.
-
- 2. Execution of invalid op codes.
- NMOS: Some terminate only by reset. Results are undefined.
- CMOS: All are NOPs (reserved for future use).
-
- 3. Jump indirect, operand = XXFF.
- NMOS: Page address does not increment.
- CMOS: Page address increments and adds one additional cycle.
-
- 4. Read/modify/write instructions at effective address.
- NMOS: One read and two write cycles.
- CMOS: Two read and one write cycle.
-
- 5. Decimal flag.
- NMOS: Indeterminate after reset.
- CMOS: Initialized to binary mode (D=0) after reset and interrupts.
-
- 6. Flags after decimal operation.
- NMOS: Invalid N, V and Z flags.
- CMOS: Valid flag adds one additional cycle.
-
- 7. Interrupt after fetch of BRK instruction.
- NMOS: Interrupt vector is loaded, BRK vector is ignored.
- CMOS: BRK is executed, then interrupt is executed.
-
-
-
- 6510 Instruction Timing
-
- The NMOS 6500 series uses a sort of pipelining. It always reads two
- bytes for each instruction. If the instruction was only two cycles long,
- the opcode for the next instruction can be fetched during the third cycle.
- As most instructions are two or three bytes long, this is quite efficient.
- But one-byte instructions take two cycles, even though they could be
- performed in one.
-
- The following tables show what happens on the bus while executing different
- kinds of instructions. The tables having "???" marks at any cycle may be
- totally wrong, but the rest should be absolutely accurate.
-
-
- Interrupts
-
- NMI and IRQ both take 7 cycles. Their timing diagram is much like
- BRK's. IRQ will be executed only when the I flag is clear.
- The processor will usually wait for the current instruction to
- complete before executing the interrupt sequence.
-
- There is one exception to this rule: If a NMI occurs while the
- processor is executing a BRK, the two interrupts may take 7 to 14
- cycles to execute, and the processor may totally lose the BRK
- instruction. Probably the results are similar also with IRQ.
- Marko M"akel"a experimented with BRK/NMI, but he still hasn't
- analyzed the results.
-
- RESET does not push program counter on stack, and we don't know how
- long it lasts. But we know that RESET preserves all registers
- (except PC).
-
-
- Accumulator or implied addressing
-
- BRK
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PC R fetch opcode, increment PC
- 2 PC R read next instruction byte (and throw it away),
- increment PCR
- 3 $0100,S W push PCH on stack (with B flag set), decrement S
- 4 $0100,S W push PCL on stack, decrement S
- 5 $0100,S W push P on stack, decrement S
- 6 $FFFE R fetch PCL
- 7 $FFFF R fetch PCH
-
-
- RTI
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R read next instruction byte (and throw it away),
- increment PCR
- 3 $0100,S R increment S
- 4 $0100,S R pull P from stack, increment S
- 5 $0100,S R pull PCL from stack, increment S
- 6 $0100,S R pull PCH from stack
-
-
- RTS
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R read next instruction byte (and throw it away),
- increment PCR
- 3 $0100,S R increment S
- 4 $0100,S R pull PCL from stack, increment S
- 5 $0100,S R pull PCH from stack
- 6 PCR R increment PCR
-
-
- PHA, PHP
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R read next instruction byte (and throw it away),
- increment PCR
- 3 $0100,S W push register on stack, decrement S
-
-
- PLA, PLP
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R read next instruction byte (and throw it away),
- increment PCR
- 3 $0100,S R increment S
- 4 $0100,S R pull register from stack
-
- Note: The 3rd cycle does NOT read from PCR.
- Maybe it reads from $0100,S.
-
-
- Other instructions
-
- # address R/W description
- --- ------- --- -----------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R read next instruction byte (and throw it away),
- increment PCR
-
-
- Immediate addressing
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch value, increment PCR
-
-
- Absolute addressing
-
- JMP
-
- # address R/W description
- --- ------- --- -------------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address's low byte to latch, increment PCR
- 3 PCR R copy latch to PCL, fetch address's high byte to
- latch, increment PCR, copy latch to PCH
-
-
- JSR
-
- # address R/W description
- --- ------- --- -------------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address's low byte to latch, increment PCR
- 3 $0100,S R store latch
- 4 $0100,S W push PCH on stack, decrement S
- 5 $0100,S W push PCL on stack, decrement S
- 6 PCR R copy latch to PCL, fetch address's high byte to
- latch, increment PCR, copy latch to PCH
-
-
- Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
- LAX, NOP)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address, increment PCR
- 4 address R read from effective address
-
-
- Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
- SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address, increment PCR
- 4 address R read from effective address
- 5 address W write the value back to effective address,
- and do the operation on it
- 6 address W write the new value to effective address
-
-
- Write instructions (STA, STX, STY, SAX)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address, increment PCR
- 4 address W write register to effective address
-
-
- Zero page addressing
-
- Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
- LAX, NOP)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address R read from effective address
-
-
- Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
- SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address R read from effective address
- 4 address W write the value back to effective address,
- and do the operation on it
- 5 address W write the new value to effective address
-
-
- Write instructions (STA, STX, STY, SAX)
-
- # address R/W description
- --- ------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address W write register to effective address
-
- Zero page indexed addressing
-
- Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
- LAX, NOP)
-
- # address R/W description
- --- --------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address R read from address, add index register to it
- 4 address+I* R read from effective address
-
- Notes: I denotes either index register (X or Y).
-
- * The high byte of the effective address is always zero,
- i.e. page boundary crossings are not handled.
-
-
- Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
- SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- --------- --- ---------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address R read from address, add index register X to it
- 4 address+X* R read from effective address
- 5 address+X* W write the value back to effective address,
- and do the operation on it
- 6 address+X* W write the new value to effective address
-
- Note: * The high byte of the effective address is always zero,
- i.e. page boundary crossings are not handled.
-
-
- Write instructions (STA, STX, STY, SAX)
-
- # address R/W description
- --- --------- --- -------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch address, increment PCR
- 3 address R read from address, add index register to it
- 4 address+I* W write to effective address
-
- Notes: I denotes either index register (X or Y).
-
- * The high byte of the effective address is always zero,
- i.e. page boundary crossings are not handled.
-
-
- Absolute indexed addressing
-
- Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
- LAX, LAE, SHS, NOP)
-
- # address R/W description
- --- --------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address,
- add index register to low address byte,
- increment PCR
- 4 address+I* R read from effective address,
- fix the high byte of effective address
- 4+ address+I R re-read from effective address
-
- Notes: I denotes either index register (X or Y).
-
- * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100.
-
- + This cycle will be executed only if the effective address
- was invalid during cycle #4, i.e. page boundary was crossed.
-
-
- Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
- SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- --------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address,
- add index register X to low address byte,
- increment PCR
- 4 address+X* R read from effective address,
- fix the high byte of effective address
- 5 address+X R re-read from effective address
- 6 address+X W write the value back to effective address,
- and do the operation on it
- 7 address+X W write the new value to effective address
-
- Notes: * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100.
-
-
- Write instructions (STA, STX, STY, SHA, SHX, SHY)
-
- # address R/W description
- --- --------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch low byte of address, increment PCR
- 3 PCR R fetch high byte of address,
- add index register to low address byte,
- increment PCR
- 4 address+I* R read from effective address,
- fix the high byte of effective address
- 5 address+I W write to effective address
-
- Notes: I denotes either index register (X or Y).
-
- * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100. Because
- the processor cannot undo a write to an invalid address,
- it always reads from the address first.
-
-
- Relative addressing (BCC, BCS, BNE, BEQ, BPL, BMI, BVC, BVS)
-
- # address R/W description
- --- --------- --- ---------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch operand, increment PCR
- 3 PCR R Fetch opcode of next instruction,
- If branch is taken, add operand to PCL.
- Otherwise increment PCR.
- 3+ PCR* R Fetch opcode of next instruction.
- Fix PCH. If it did not change, increment PCR.
- 3! PCR R Fetch opcode of next instruction,
- increment PCR.
-
- Notes: * The high byte of Program Counter (PCH) may be invalid
- at this time, i.e. it may be smaller or bigger by $100.
-
- + If branch is taken, this cycle will be executed.
-
- ! If branch occurs to different page, this cycle will be
- executed.
-
-
- Indexed indirect addressing
-
- Read instructions (LDA, ORA, EOR, AND, ADC, CMP, SBC, LAX)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, add X to it,
- increment PCR
- 3 ??? R internal operation
- 4 pointer+X R fetch effective address low
- 5 pointer+X+1 R fetch effective address high
- 6 address R read from effective address
-
- Note: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, add X to it,
- increment PCR
- 3 ??? R internal operation
- 4 pointer+X R fetch effective address low
- 5 pointer+X+1 R fetch effective address high
- 6 address R read from effective address
- 7 address W write the value back to effective address,
- and do the operation on it
- 8 address W write the new value to effective address
-
- Note: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- Write instructions (STA, SAX)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, add X to it,
- increment PCR
- 3 ??? R internal operation
- 4 pointer+X R fetch effective address low
- 5 pointer+X+1 R fetch effective address high
- 6 address W write to effective address
-
- Note: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- Indirect indexed addressing
-
- Read instructions (LDA, EOR, AND, ORA, ADC, SBC, CMP)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, increment PCR
- 3 pointer R fetch effective address low
- 4 pointer+1 R fetch effective address high,
- add Y to low byte of effective address
- 5 address+Y* R read from effective address,
- fix high byte of effective address
- 5+ address+Y R read from effective address
-
- Notes: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100.
-
- + This cycle will be executed only if the effective address
- was invalid during cycle #5, i.e. page boundary was crossed.
-
-
- Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISB, DCP)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, increment PCR
- 3 pointer R fetch effective address low
- 4 pointer+1 R fetch effective address high,
- add Y to low byte of effective address
- 5 address+Y* R read from effective address,
- fix high byte of effective address
- 6 address+Y W write to effective address
- 7 address+Y W write the value back to effective address,
- and do the operation on it
- 8 address+Y W write the new value to effective address
-
- Notes: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100.
-
-
- Write instructions (STA, SHA)
-
- # address R/W description
- --- ----------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address, increment PCR
- 3 pointer R fetch effective address low
- 4 pointer+1 R fetch effective address high,
- add Y to low byte of effective address
- 5 address+Y* R read from effective address,
- fix high byte of effective address
- 6 address+Y W write to effective address
-
- Notes: The effective address is always fetched from zero page,
- i.e. the zero page boundary crossing is not handled.
-
- * The high byte of the effective address may be invalid
- at this time, i.e. it may be smaller by $100.
-
-
- Absolute indirect addressing (JMP)
-
- # address R/W description
- --- --------- --- ------------------------------------------
- 1 PCR R fetch opcode, increment PCR
- 2 PCR R fetch pointer address low, increment PCR
- 3 PCR R fetch pointer address high, increment PCR
- 4 pointer R fetch low address to latch
- 5 pointer+1* R fetch PCH, copy latch to PCL
-
- Note: * The PCH will always be fetched from the same page
- than PCL, i.e. page boundary crossing is not handled.
-
-
-
- MEMORY MANAGEMENT
-
- normal ultimax
- 1111 101x 011x 001x 1110 0100 1100 xx01
- 1000 00x0
- 10000
- ------------------------------------------------------------------------
- F000
- Kernal RAM Kernal RAM Kernal Kernal Kernal module
- E000
- ------------------------------------------------------------------------
- D000 I/O I/O** I/O RAM I/O I/O I/O I/O
- ------------------------------------------------------------------------
- C000 RAM RAM RAM RAM RAM RAM RAM -
- ------------------------------------------------------------------------
- B000
- BASIC RAM RAM RAM BASIC module module -
- A000
- ------------------------------------------------------------------------
- 9000
- RAM RAM RAM RAM module RAM module module
- 8000
- ------------------------------------------------------------------------
- 7000
-
- 6000
- RAM RAM RAM RAM RAM RAM RAM -
- 5000
-
- 4000
- ------------------------------------------------------------------------
- 3000
-
- 2000
- RAM RAM RAM RAM RAM RAM RAM RAM
- 1000
-
- 0000
- ------------------------------------------------------------------------
-
- **) Chargen not accessible by the CPU
-
-
- AUTOSTART CODE
-
- If memory places $8004 to $8008 contain 'CBM80' (C3 C2 CD 38 30),
- the RESET routine jumps to ($8000) and the default NMI handler jumps to
- ($8002).
-
-
- HOW REAL PROGRAMMERS ACKNOWLEDGE INTERRUPTS
-
- With RMW instructions:
-
- ; beginning of combined raster/timer interrupt routine
- LSR $D019 ; clear VIC interrupts, read raster interrupt flag to C
- BCS raster ; jump if VIC caused an interrupt
- ... ; timer interrupt routine
-
- Operational diagram of LSR $D019:
-
- # data address R/W
- --- ---- ------- --- ---------------------------------
- 1 4E PCR R fetch opcode
- 2 19 PCR+1 R fetch address low
- 3 D0 PCR+2 R fetch address high
- 4 xx $D019 R read memory
- 5 xx $D019 W write the value back, rotate right
- 6 xx/2 $D019 W write the new value back
-
- The 5th cycle acknowledges the interrupt by writing the same
- value back. If only raster interrupts are used, the 6th cycle
- has no effect on the VIC.
-
-
- With indexed addressing:
-
- ; acknowledge interrupts to both CIAs
- LDX #$10
- LDA $DCFD,X
-
- Operational diagram of LDA $DCFD,X:
-
- # data address R/W description
- --- ---- ------- --- ---------------------------------
- 1 BD PCR R fetch opcode
- 2 FD PCR+1 R fetch address low
- 3 DC PCR+2 R fetch address high, add X to address low
- 4 xx $DC0D R read from address, fix high byte of address
- 5 yy $DD0D R read from right address
-
-
- ; acknowledge interrupts to CIA 2
- LDX #$10
- STA $DDFD,X
-
- Operational diagram of STA $DDFD,X:
-
- # data address R/W description
- --- ---- ------- --- ---------------------------------
- 1 9D PCR R fetch opcode
- 2 FD PCR+1 R fetch address low
- 3 DC PCR+2 R fetch address high, add X to address low
- 4 xx $DD0D R read from address, fix high byte of address
- 5 ac $DE0D W write to right address
-
-
- With branch instructions:
-
- ; acknowledge interrupts to CIA 2
- LDA #$00 ; clear N flag
- JMP $DD0A
- DD0A BPL $DC9D ; branch
- DC9D BRK ; return
-
- You need the following preparations to initialize the CIA registers:
-
- LDA #$91 ; argument of BPL
- STA $DD0B
- LDA #$10 ; BPL
- STA $DD0A
- STA $DD08 ; load the ToD values from the latches
- LDA $DD0B ; jam the ToD display
- LDA #$7F
- STA $DC0D ; assure that $DC0D is $00
-
- Operational diagram of BPL $DC9D:
-
- # data address R/W description
- --- ---- ------- --- ---------------------------------
- 1 10 $DD0A R fetch opcode
- 2 91 $DD0B R fetch argument
- 3 xx $DD0C R fetch opcode, add argument to PCL
- 4 yy $DD9D R fetch opcode, fix PCH
- ( 5 00 $DC9D R fetch opcode )
-
-
- ; acknowledge interrupts to CIA 1
- LDA #$00 ; clear N flag
- JMP $DCFA
- DCFA BPL $DD0D
- DD0D BRK
-
- ; Again you need to set the ToD registers of CIA 1 and the
- ; Interrupt Control Register of CIA 2 first.
-
- Operational diagram of BPL $DD0D:
-
- # data address R/W description
- --- ---- ------- --- ---------------------------------
- 1 10 $DCFA R fetch opcode
- 2 11 $DCFB R fetch argument
- 3 xx $DCFC R fetch opcode, add argument to PCL
- 4 yy $DC0D R fetch opcode, fix PCH
- ( 5 00 $DD0D R fetch opcode )
-
-
- ; acknowledge interrupts to CIA 2 automagically
- ; preparations
- LDA #$7F
- STA $DD0D ; disable CIA 2's all interrupt sources
- LDA $DD0E
- AND #$BE ; ensure that $DD0C remains constant
- STA $DD0E ; and stop the timer
- LDA #$FD
- STA $DD0C ; parameter of BPL
- LDA #$10
- STA $DD0B ; BPL
- LDA #$40
- STA $DD0A ; RTI/parameter of LSR
- LDA #$46
- STA $DD09 ; LSR
- STA $DD08 ; load the ToD values from the latches
- LDA $DD0B ; jam the ToD display
- LDA #$09
- STA $0318
- LDA #$DD
- STA $0319 ; change NMI vector to $DD09
- LDA #$FF ; Try changing this instruction's operand
- STA $DD05 ; (see comment below).
- LDA #$FF
- STA $DD04 ; set interrupt frequency to 1/65536 cycles
- LDA $DD0E
- AND #$80
- ORA #$11
- LDX #$81
- STX $DD0D ; enable timer interrupt
- STA $DD0E ; start timer
-
- LDA #$00 ; To see that the interrupts really occur,
- STA $D011 ; use something like this and see how
- LOOP DEC $D020 ; changing the byte loaded to $DD05 from
- BNE LOOP ; #$FF to #$0F changes the image.
-
- When an NMI occurs, the processor jumps to Kernal code, which jumps to
- ($0318), which points to the following routine:
-
- DD09 LSR $40 ; clear N flag
- BPL $DD0A ; Note: $DD0A contains RTI.
-
- Operational diagram of BPL $DD0A:
-
- # data address R/W description
- --- ---- ------- --- ---------------------------------
- 1 10 $DD0B R fetch opcode
- 2 11 $DD0C R fetch argument
- 3 xx $DD0D R fetch opcode, add argument to PCL
- 4 40 $DD0A R fetch opcode, (fix PCH)
-
-
- With RTI:
-
- ; the fastest possible interrupt handler in the 6500 family
- ; preparations
- SEI
- LDA $01 ; disable ROM and enable I/O
- AND #$FD
- ORA #$05
- STA $01
- LDA #$7F
- STA $DD0D ; disable CIA 2's all interrupt sources
- LDA $DD0E
- AND #$BE ; ensure that $DD0C remains constant
- STA $DD0E ; and stop the timer
- LDA #$40
- STA $DD0C ; store RTI to $DD0C
- LDA #$0C
- STA $FFFA
- LDA #$DD
- STA $FFFB ; change NMI vector to $DD0C
- LDA #$FF ; Try changing this instruction's operand
- STA $DD05 ; (see comment below).
- LDA #$FF
- STA $DD04 ; set interrupt frequency to 1/65536 cycles
- LDA $DD0E
- AND #$80
- ORA #$11
- LDX #$81
- STX $DD0D ; enable timer interrupt
- STA $DD0E ; start timer
-
- LDA #$00 ; To see that the interrupts really occur,
- STA $D011 ; use something like this and see how
- LOOP DEC $D020 ; changing the byte loaded to $DD05 from
- BNE LOOP ; #$FF to #$0F changes the image.
-
- When an NMI occurs, the processor jumps to Kernal code, which jumps to
- ($0318), which points to the following routine:
-
- DD0C RTI
-
- How on earth can this clear the interrupts? Remember, the processor
- always fetches two successive bytes for each instruction.
-
- A little more practical version of this is redirecting the NMI (or IRQ)
- to your own routine, whose last instruction is JMP $DD0C or JMP $DC0C.
- If you want to confuse more, change the 0 in the address to a
- hexadecimal digit different from the one you used when writing the RTI.
-
- Or you can combine the latter two methods:
-
- DD09 LSR $xx ; xx is any appropriate BCD value 00-59.
- BPL $DCFC
- DCFC RTI
-
- This example acknowledges interrupts to both CIAs.
-
-
- If you want to confuse the examiners of your code, you can use any of
- these techniques. Although these examples use no undefined opcodes, they do
- not run correctly on CMOS processors. However, the RTI example should run on
- 65C02 and 65C816, and the latter branch instruction example might work as
- well.
-
- The RMW instruction method has been used in some demos, others were
- developed by Marko M"akel"a. His favourite is the automagical RTI method,
- although it does not have any practical applications, except for some
- time dependent data decryption routines for very complicated copy protections.
-
-
-
- MAKING USE OF THE I/O REGISTERS
-
- If you are making a resident program and want to make as invisible to the
- system as possible, probably the best method is keeping most of your code
- under the I/O area (in the RAM at $D000-$DFFF). You need only a short routine
- in the normally visible RAM that pushes the current value of the processor's
- I/O register $01 on stack, switches I/O and ROMs out and jumps to this area.
- Returning from the $D000-$DFFF area is easy even without any routine in the
- normally visible RAM area. Just write a RTS to an I/O register and return
- through it.
-
- But what if your program needs to use I/O? And how can you write the RTS
- to an I/O register while the I/O area is switched off? You need a swap area
- for your program in normally visible memory. The first thing your routine at
- $D000-$DFFF does is copying the I/O routines (or the whole program) to
- normally visible memory, swapping the bytes. For instance, if your I/O
- routines are initially at $D200-$D3FF, exchange the bytes at $D200-$D3FF
- with the contents of $C000-$C1FF. Now you can call the I/O routines from
- your routine at $D000-$DFFF, and the I/O routines can switch the I/O area
- temporarily on to access the I/O circuitry. And right before exiting your
- program at $D000-$DFFF swaps the old contents of that I/O routine area in,
- e.g. exchanges the memory areas $D200-$D3FF and $C000-$C1FF again.
-
- What I/O registers can you use for the RTS? There are two alternatives:
- 8-bit VIC sprite registers or CIA serial port register. The CIA register is
- usually better, as changing the VIC registers might change the screen layout.
- However, also the SP register has some drawbacks: If the machine's CNT1 and
- CNT2 lines are connected to a frequency source, you must stop either CIA's
- Timer A to use the SP register method. Normally the 1st CIA's Timer A is the
- main hardware interrupt source. And if you use the Kernal's RS232, you cannot
- stop the 2nd CIA's Timer A either. Also, if you don't want to lose any CIA
- interrupts, remember that the RTS at SP register causes also the Interrupt
- Control Register to be read.
-
- Also keep in mind that the user could press RESTORE while the Kernal ROM
- and I/O areas are disabled. You could write your own NMI handler (using
- the NMI vector at $FFFA), but a fast loader that uses very tight timing
- would still stop working if the user pressed RESTORE in wrong time. So, to
- make a robust program, you have to disable NMI interrupts. But how is this
- possible? They are Non-Maskable after all. The NMI interrupt is
- edge-sensitive, the processor jumps to NMI handler only when the -NMI line
- drops from +5V to ground. Just cause a NMI with CIA2's timer, but don't
- read the Interrupt Control register. If you need to read $DD0D in your
- program, you must add a NMI handler just in case the user presses RESTORE.
- And don't forget to raise the -NMI line upon exiting the program. This can
- be done automatically by the latter two of the three following examples.
-
-
- ; Returning via VIC sprite 7 X coordinate register
-
- Initialization: ; This is executed when I/O is switched on
- LDA #$60
- STA $D015 ; Write RTS to VIC register $15.
-
- Exiting: ; NOTE: This procedure must start at VIC register
- ; $12. You have multiple alternatives, as the VIC
- ; appears in memory at $D000+$40*n, where $0<=n<=$F.
-
- PLA ; Pull the saved 6510 I/O register state from stack
- STA $01 ; Restore original memory bank configuration
- ; Now the processor fetches the RTS command from the
- ; VIC register $15.
-
-
- ; Returning via CIA 2's SP register (assuming that CNT2 is stable)
-
- Initialization: ; This is executed when I/O is switched on
- LDA $DD0E ; CIA 2's Control Register A
- AND #$BF ; Set Serial Port to input
- STA $DD0E ; (make the SP register to act as a memory place)
- LDA #$60
- STA $DD0C ; Write RTS to CIA 2 register $C.
-
- Exiting: ; NOTE: This procedure must start at CIA 2 register
- ; $9. As the CIA 2 appears in memory at $DD00+$10*n,
- ; where 0<=n<=$F, you have sixteen alternatives.
- PLA
- STA $01 ; Restore original memory bank configuration
- ; Now the processor fetches the RTS command from
- ; the CIA 2 register $C.
-
-
- ; Returning via CIA 2's SP register, stopping the Timer A
- ; and forcing SP2 and CNT2 to output
-
- Initialization: ; This is executed when I/O is switched on
- LDA $DD0E ; CIA 2's Control Register A
- AND #$FE ; Stop Timer A
- ORA #$40 ; Set Serial Port to output
- STA $DD0E ; (make the SP register to act as a memory place)
- LDA #$60
- STA $DD0C ; Write RTS to CIA register $C.
-
- Exiting: ; NOTE: This procedure must start at CIA 2 register
- ; $9. As the CIA 2 appears in memory at $DD00+$10*n,
- ; where, 0<=n<=$F, you have sixteen alternatives.
- PLA
- STA $01 ; Restore original memory bank configuration
- ; Now the processor fetches the RTS command from
- ; the CIA 2 register $C.
-
-
- For instance, if you want to make a highly compatible fast loader, make
- the ILOAD vector ($0330) point to the beginning of the stack area. Remember
- that the BASIC interpreter uses the first bytes of stack while converting
- numbers to text. A good address is $0120. Robust programs practically never
- use so much stack that it could corrupt this routine. Usually only crunched
- programs (demos and alike) use all stack in the decompression phase. They
- also make use of the $D000-$DFFF area.
-
- This stack routine will jump to your routine at $D000-$DFFF, as described
- above. For performance's sake, copy the whole byte transfer loop to the swap
- area, e.g. $C000-$C1FF, and call that subroutine after doing the preliminary
- work. But what about files that load over $C000-$C1FF? Wouldn't that destroy
- the transfer loop and jam the machine? Not necessarily. If you copy those
- bytes to your swap area at $D000-$DFFF, they will be loaded properly, as
- your program restores the original $C000-$C1FF area.
-
- If you want to make your program user-friendly, put a vector initialization
- routine to the stack area as well, so that the user can restore the fast
- loader by issuing a SYS command, rather than loading it each time he has
- pressed STOP & RESTORE or RESET.
-
-
-
- NOTES
-
- See MCS 6500 Microcomputer Family Programming Manual for more information.
- There is also a table showing functional description and timing for complete
- 6510 instruction set on C=Hacking magazine issue 1/92 (available via FTP at
- ccosun.caltech.edu:/pub/rknop/hacking.mag/ and
- nic.funet.fi:/pub/cbm/c=hacking/).
-
-
- References:
- C64 Memory Maps C64 Programmer's Reference Guide pp. 262-267
- 6510 Block Diagram C64 Programmer's Reference Guide p. 404
- Instruction Set C64 Programmer's Reference Guide pp. 416-417
- C=Hacking Volume 1, issue #1, 1/92
- C=Lehti magazine 4/87
- =============================================================================
- A Heavy-Duty Power Supply for the C-64
- by John C. Andrews (no email address)
-
- As a Commodore User for the last 4 plus years, I am aware of the many
- articles and letters in the press that have bemoaned the burn-out
- problem of the C-64 power supply. When our Club BBS added a one meg
- drive and stayed on around the clock, the need for heavy-duty power
- supply became very apparent.... Three power supplies went in 3
- successive days!
-
- Part of the problem was my ignoring the seasons. You see during the
- winter I had set the power supply between the window and the screen,
- Yes, outside! With the advent of Spring... well, you get the picture.
-
- The turn-around time forgetting a new commerical supply was not in the
- best interest of the BBS and its members. Therefore, taking power
- supply inhand, I proceeded to cut one open on my shop bandsaw. I do
- not suggest that you do this. The parts are FIRMLY and COMPLETELY
- encased in a hard plastic potting compound. The purpose of this is not
- to make the item difficult to repair, but to make the entire unit
- conductive to the heat generated inside. I doubt the wisedom of
- potting the fuse as well. However, CBM was probably thinking of the
- number of little fingers that could fit into an accessable fuse
- holder. if you want the punch line it is: the final circuit board and
- its componets are about the size of a box of matches. This includes
- the built-in metal heat sink.
-
- From these minscule innards I traced out the circuit and increased the
- size of ALL components.
-
- The handiest source of electronic parts is, of course, Radio Shack.
- All but one part can be purchased there.
-
- 212-1013 Capacitor, 35V, 4700 mF
- 212-1022 Capacitor, 35V, 10 uF
- 273-1515 Transformer, 2 Amp, 9-0-9 VAC
- 276-1184 Rectifier
-
- 270- 742 Fuse Block
- 270-1275 Fuses
-
- Note that there are only five parts. The rest are fuses, fuse blocks,
- heat sinks, wire and misc. hardware. Note also that I have not listed
- any plugs and cords. This because you can clip the cords off of both
- sides of your defunct power supply. This will save you the hassle of
- wriing the DIN power plug correctly:
-
- DIN PIN OUT COLOR
- pin 6 9VAC black
- pin 7 9VAC black
- pin 5 +5 Volts blue
- pin 1,2,3 shield, gnd orange
-
- The part that you can NOT get at Radio Shack is the power regulator.
- This part will have to be scrounged up from some local, big
- electronics supply house:
-
- SK 9067 5 volt voltage regulator, 3+ amps. (I prefer the 5 amp.)
-
- Radio Shack does carry regulators, but their capacity is no larger
- than that with which you started.
-
- The Heat sinks, (yes, more than one!) are the key to the success of
- this project. The ones I used came from my Model Railroading days.
- Sorry to say, I did just ahve them 'lying about'. The heat sinks that
- I priced at the local electronics supply were more costly than the
- other parts. The worst case situation is that you may need to drill
- out a couple pieces of aluminum sheet. Try for 12 x 12, and bend them
- into square bottomed U-shapes to save room. heat sinks should not
- touch, or be electronically grounded to each other. You can also mount
- them on stand-offs from your chassis for total air circulation.
-
- The Radio Shack transformer is rated at only 2 amps. If you can not
- find one with a higher rating elsewhere, it is possible to hook two in
- parallel to get a 4 ampere output. This si tricky, as it can be done
- either right or wrong!
-
- Here is how to do it the right way:
- Tape off one yellow secondary lead on each transformer. With tape
- mark the four remaining secondary leads and letter them A and B on
- one transformer, C and D onthe other. Hook up the black primary
- leads to a plug to your 120 wall outlet:
-
- |-------------
- Note: *'s - indicate connections | 3 ||
- +'s - indicate skip overs | 3 || (Transformer)
- | 3 ||
- | 3 ||
- | ----------
- | |
- +--\ /-------------------*---+---------
- --|120|/ | 3 ||
- --|Vlt| ____ | 3 ||
- -|Plg|------------|FUSE|-------* 3 ||
- +--/ ---- | 3 || (Transformer)
- |---------
-
- This would now be a good time to install a fuse in your 120 VAC
- line. Now before plugging this into the wall, tie two of the
- scondary leads (one from EACH transformer) together.
-
- Something like this: A--Xfmr--B+C--Xfmr--D
-
- Plug in your 120V side. Now using a VOM meter, measure the voltage
- between A and D.
- If the meter reads 18 volts, then:
- 1. unplug from the 120.
- 2. tie A and C together. tie B and D together.
- 3. your 2 transformers will now give you 9 volts at 4 amps.
- If the meter reads 0 volts, then:
- 1. unplug from the 120.
- 2. tie A and D together. Tie B and C together.
- 3. your 2 transformers will now give you 9 volts at 4 amps.
-
- Below is the file corresponding to the full schematic of the power
- supply. [Ed's note: in GeoPaint format, converted, then uuencoded]. As
- you can see in the picture, I used only one transformer. Because it
- got hot, I epoxied a small heat sink to it. While this solved the heat
- problem, it did not increase the capacity of the total power supply.
-
- Note that I used fuses on all lines.
-
- -----------------------------------------------------------------------------
- begin 700 schematic.
- M@PD,<V-H96UA=&ECH*"@H*"@H`D&`0=8!P8-,Q(`4%)'(&9O<FUA='1E9"!'
- M14]3(&9I;&4@5C$N,``CF````"```.``0X(``$5P<V]N($U8+3@P`*"@H*``
- MUH>-UH?(F!AE`H4"D`+F`V"@H*"@H*"@H`@%`0A6!`<,`"````"""@A30TA%
- M34%424.@H*"@H*"@````````````$P!"3$%35$52)U,@0T].5D525$52(%8R
- M+C4$6`<X"````@RP#0T].5D525*"@H*"@H*"@H"P2``99`Q$&`10`````
- M```````````````````````````````````````#%;_____```.@``6?__F5
- M55F:JJF555F:JJF555F:JJF555F:JJF?__F@``7```/___\```````-__[:`
- M`/Y__[R#!P$``/__``!086EN="!);6%G92!6,2XQ````````````````````
- M````````````9V5O4&%I;G0@("`@5C(N,``````@*$')!M`"J1*-(D"I`(TG
- M0"#]/Y`%J0"-)T`@#!\@2$&I`(VL7ZD`A1&I!X40J3^%%ZGQA1:I7X4-J:R%
- M#*!`J0X@3S&B_Z4"R0+P)LG_T`8@-$&X4*G)!M`-H$.I)B!/,2"APKA0F*VL
- M7_`&(+T\(($\8%!A:0#_"@#_`3P!>`)+`EL"/P&R`38!B@%Z`38!C@(G`/\`
- M_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_`/\`_P#_
- M`/\`_P#_`/\`_P#_`/\`````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M`````````````````````````````````````````````````````````/\`
- M_P#_`/\`_P#_`/\`E0`"/R!%````````_P"%``,0_Q!$````````_P"&``+X
- M"/\`_P"B`/^_H;\``"`@,!@,_PP810``````_P``"!`0,@_\#`0P``````
- M_P``A1`#\!`0H`"("/\`_P#*``(@/X8``>#_`+```3"'(*@``F`PAA"8`(@0
- MH`"("/\`_P"B`/^_H;\`(*@`B!"8`(00!!\0$!!$`````/\```"$"`3X"`@(
- M_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%
- M``/@D)"0``$'0P````````#_AP`!@,\`B""H_P#.``H#1$0HA0`##03$A0`#
- M@("9AP`!!*``B""H`(@0H`"("/\`_P#_`.,`'2D1$1$0````).0$),0```"E
- MI*2DF`````2HJ%!0HP"&(`)@8*(``1^$``P!$!#_````/-,``/B$``&`F`"(
- M"/\`_P"B`/^_H;\``+```3"'(*@``F`PAA"8`(@0H`"("/\`_P"B`/^_H;\`
- M(*@`B!"8`(00!!\0$!!$`````/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&
- M((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%``/@D)"0``$'0P``````
- M``#_AP`!@,\`B""HS@`""`B&``(*BH8``@D!O@`"#PA#````````_P"&``R`
- M8``/##`P#`PP`/^%``,\`/^%``/``/^%``$$1`#_`````````P#_`84`"L#_
- M@,#@8"`@`/^'``'PAA"0``(&"(8`A!`,$A(2$V`0```\!(3.A``$<8J*BH0`
- M#,`@("<````X*"!P((<(`0^'``&`_P#_`,<``S\@((8``N`<B``1!`4%`@("
- M``"34E(B(B(``)N$20E(``"8)#P@))BR`(@(D0`Q`0$```$!`&"%A65EA85E
- M,`P,,#`,##`B(CPB(B(\`$!,4DY24DX`!&25AH:59```@(0``8"A`(@@A0`+
- M`0(*!!`0.$2"`0"`A@`"@$"0`(@0`H2$A@`"BG&&``(JRH8`#*"@`0$#`P4%
- M,,```(0%"&`8!`3R"@D1_P#_`)H`_[^AOP``````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M``````````````````````````````````"<``(!`T(```````#__P(``(0@
- M!N#@("`#`88`"/^`0"`0"`0$2O\``````````?B8`!<!`0```0$`986%966%
- MA64P#`\P,`P,,$8``/\```````,``/Z%`H@``3^'`!3X"P0"`0```(#G@(`!
- M@D0X(+]`@$(``````/\``(0``A#_AA!#`/\````````8!?P$`@(!`0"-B8G9
- M<7$`P.$A(2(2#`08_P#_`*T``@$#0@```````/__"P```"`@(.#@("`@B``$
- M`@$!`8@`'("`@$```'E$1'A$1```@("8I9VE```(",DJ#`S)`#<!`0```0$`
- M986%966%A64P#`PP,`P,,`!$"D0H*1$1$0`-!,0DY`0D`("`F:6DI*0````$
- M!*BH4)```0-#`````````/^'``'PAQ"8`(@0CP`!#(<``0B(``(X#X8(`F"`
- M_P#_`*``_[^AOP``````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M`````````````````````````````````````````````````````+``B""0
- M`(5`%7]`0$1X````_P``I9P```#_```JR4(```#_``````<```#_```'A`0;
- M_`0$_P`],3TQ,`#_`+.VL['G`/\`O#`\L#P`A8`#_X"`0@``````_P``-0`!
- M`0``_P``986%8&"````P#`PP/P```!````#_````Q````/\```"8````_P``
- M`%````#_AP`$X"`@(*@`B!"8`(00!!\0$!"$`!3_`````@$!`/\``0$("`B(
- MCX@("(0`!/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`
- M#A$0$!.%``-98D.%``.,4MZ%``/@D)"0``$'0P````````H`_X<``8#/`(@@
- MJ`"($)@`B!"(``L"#```GJ&AH0@("(D`!&"PD("("/\`_P"B`/^_H;\```$,
- MAP`!"(@``C@/A@@"8(#_`/\`H`#_OZ&_````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````````````````````````````````````````````````````
- M````````````L`"(((4`)@$"'`0($"!`_P``$1$.``#_``!"0D$``/\``!!2
- MC```_P``D)"04```_P``````#0``_P``("`P&`S_#!A%``````#_```($!`P
- M8.#_P,!#``````#_``"%$`/P$!"(``2AH:&>A``)`2(B(AP```#`A(`#````
- MB`C_`/\`R@`"(#^&``'@_P"P``,P(`"%(*@``F`PAA"8`(@0H`"("/\`_P"B
- M`/^_H;\`_P`!`0@("(B/B`@(A``$_P```(0(!/@("`C_`/\`P@`"__^&``+@
- MX(8@BP`-`0$!`@2`@(`.$1`0$X4``UEB0X4``XQ2WH4``^"0D)#_`.D`B""H
- M`(@0F``*B!"@`(@(_P#_`/\`_P"$`(@@J`"($)@`B!"@`(@(_P#_`*(`_[^A
- MOP``#0``_P``("`P&`S_#!A%``````#_```($!`P8.#_P,!#``````#_``"%
- M$`/P$!"(``2AH:&>A``)`2(B(AP```#`A(`#````B`C_`/\`R@`"(#^&``'@
- M_P"P``,P(`"%(*@``F`PAA"8`(@0H`"("/\`_P"B`/^_H;\`_P`!`0@("(B/
- MB`@(A``$_P```(0(!/@("`C_`/\`P@`"__^&``+@X(8@BP`-`0$!`@2`@(`.
- M$1`0$X4``UEB0X4``XQ2WH4``^"0D)#_`.D`B""H`(@0F`"($*``B`C_`/\`
- M_P#S``$#AP(8_P``>V-[8V'_``!G;&9CSO\``'A@>&!XB("8`(@0B`"(`1G_
- M```],3TQ,/\``+.VL['G_P``O#`\L#S`AT")``,?$!"$$Q@(_P``VQO;&P#_
- M```[8S,;`/P$!,0$Q`3_`/\`D@#_OZ&_`(8``>#_`+```S`@`(4@J``"8#"&
- M$)@`B!"@`(@(_P#_`*(`_[^AOP#_``$!"`@(B(^("`B$``3_````A`@$^`@(
- M"/\`_P#"``+__X8``N#@AB"+``T!`0$"!("`@`X1$!`3A0`#66)#A0`#C%+>
- MA0`#X)"0D/\`V0`#`@(#0@``````"@``_X40`P``_X4``X"`@)T`B!"(``,!
- M`0%"`````````/^%"`,``/^%``-`0,"-``03$!`?A``$#@``_X0$!',``/^$
- M``3$!`3\_P#_`/\`]P"($*@`B!"8`(@(H`"(!/\`_P"B`/^_H;\`!,0$Q`3_
- M`/\`D@#_OZ&_`(8``>#_`+```S`@`(4@J``"8#"&$)@`B!"@`(@(_P#_`*(`
- M_[^AOP#_``$!"`@(B(^("`B$``3_````A`@$^`@("/\`_P#"``+__X8``N#@
- MAB"+``T!`0$"!("`@`X1$!`3A0`#66)#A0`#C%+>A0`#X)"0D/\`Z0"($*@`
- MB!"8`(@(H`"(!/\`_P#_`/\`A`"($*@`B!"8`(@(H`"(!/\`_P"B`/^_H;\`
- M_X4``T!`P(T`!!,0$!^$``0.``#_A`0$<P``_X0`!,0$!/S_`/\`_P#W`(@0
- MJ`"($)@`B`B@`(@$_P#_`*(`_[^AOP`$Q`3$!/\`_P"2`/^_H;\`A@`!X/\`
- ML``#,"``A2"H``)@,(80F`"($*``B`C_`/\`H@#_OZ&_`/\``0$("`B(CX@(
- M"(0`!/\```"$"`3X"`@(_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0
- M$!.%``-98D.%``.,4MZ%``/@D)"0_P#I`(@0J`"($)@`B`B@``J(!/\`_P#_
- M`/@``P,$!(4``X%!0(00!``1$:*%``,.$9"4``0'"`@(A``,`H*!@1`0$``B
- M(D5%A``$'"(@((<`"P,````?$!`>P0@(B0`-<HN#@IH````N*2BHJ(4`$X"`
- M@`````$!!P$!`!\0$![!`1'_`/\`H@#_OZ&_`/\`L``#,"``A2"H``)@,(80
- MF`"($*``B`C_`/\`H@#_OZ&_`/\``0$("`B(CX@("(0`!/\```"$"`3X"`@(
- M_P#_`,(``O__A@`"X."&((L`#0$!`0($@("`#A$0$!.%``-98D.%``.,4MZ%
- M``/@D)"0_P#9``P$`P```P```$#`0("$``VBI$=$1````)!0T%%.DP`*!P`!
- M!@````^!@(4`$1!(CXB(`````Z"@HIP```#@A@`C`P(!$0X```#$(```("!`
- M````BHIR````0<)H:2X```#!(("%``+P((0`#`<$!`0.````B$!;2H0`!`$!
- M@4&$``3P``#@_P#_`/\`XP`3!P0$!`<$!`2(0%M*B@H*"@``@81!"4!@@`#@
- M$!`0X)``"P@("`\("`@`@+>4A!0#````A8`:`"`@0$"`@(```@(#`@("```M
- M)<4%!04``,"%(!X``$!`0$%"2P@0($"```'D!`A`X!`0$.````<$!`2$``2*
- >"@H*A``$0$`*04"$``00$!#@_P#_`)8`_[^AOP`*
- `
- end
-
- =============================================================================
- LZW Compression
- by Bill Lucier (Blucier@ersys.edmonton.ab.ca, b.lucier1 on Genie)
-
- LZW is perhaps the most widely used form of data compression today. It
- is simple to implement and achieves very decent compression at a fairly
- quick pace. LZW is used in PKZIP (shrink),PKARC (crunch), gifs,V.42bis
- and unix's compress. This article will attempt to explain how the
- compression works with a short example and 6502 source code in Buddy
- format.
-
- Originally named lz78, it was invented by Jacob Ziv and Abraham Lempel
- in 1978 , it was later modified by Terry Welch to its present format.
- The patent for the LZW compression method is presently held by Unisys.
-
- LZW compresses data by taking phrases and compressing them into codes.
- The size of the codes could vary from 9 bits to 16 bits. Although for
- this implementation we will be using only 12 bits. As byte are read in
- from a file they are added to a dictionary. For a 12-bit implementation
- a dictionary will be 4k (2^12=4096) . Each entry in the dictionary
- requires five bytes, which will be built in the form of a tree. It is
- not a binary tree because each node may have more than two offsprings.
- In fact, because our dictionary can hold up to 4096 different codes it
- is possible to have one node with 3800 children nodes, although this is
- not likely to happen. The five bytes that make up our tree will be:
-
- The parent code: Each node has one and only one parent node. When the parent
- code is less then 255 it is the end of a phrase. The codes
- 0-255 do not actually exist in the tree. The following
- values do not appear either as they have special meaning:
-
- 256 : End of Stream-This marks the end of one compressed file
- 257 : This tells the decompressor to increase the number
- of bits its reading by one.
- 258 : Wipe out dictionary
-
- The code value : This is the code that will be sent to the compressor.
- The character : The value contained at this node. It have a value of 0-255.
-
- Initially we send out codes that are 9 bits long, this will cover the values
- 0-511. Once we have reached 511, we will need to increase the number of
- bits to write by 1. This will give room for code numbers 512-1023, or
- (2^10)-1. At this point we must ensure that the decompressor knows how
- bits to read in at once so a code number 257 is sent to indicate that
- the number of bits to be read is to be bumped up by one. The size of the
- dictionary is finite so at some point we do have to be concerned with
- what we will do when it does fill up. We could stop compiling new
- phrases and just compress with the ones that are already in the
- dictionary. This is not a very good choice, files tend to change
- frequently (eg. program files as they change from code to data) so
- sticking with the same dictionary will actually increase the size of the
- file or at best, give poor compression. Another choice is to wipe the
- dictionary out and start building new codes and phrases, or wipe out
- some of the dictionary leaving behind only the newer codes and phrases.
- For the sake of simplicity this program will just wipe out the
- dictionary when it becomes full.
-
- To illustrate how LZW works a small phrase will be compressed : heher.
- To start the first two characters would be read in. The H would be
- treated as the parent code and E becomes the character code. By means of
- a hashing routine (the hashing routine will be explained more fully in
- the source code) the location where HE should be is located. Since we
- have just begun there will be nothing there,so the phrase will be added
- to the dictionary. The codes 0-258 are already taken so we start using
- 259 as our first code. The binary tree would look something like this:
-
- node # 72 - H
- |
- node #3200 259 - E
-
- The node # for E is an arbitrary one. The compressor may not choose
- that location, 3200 is used strictly for demonstration purposes. So at
- node #3200 the values would be:
-
- Parent code - 72
- code value - 259
- character - E
-
- The node #72 is not actually used. As soon as a value less than 255 is
- found it is assumed to be the actual value. We can't compress this yet
- so the value 72 is sent to the output file(remember that it is sent in 9
- bits). The E then becomes the parent code and a new character code ( H )
- is read in. After again searching the dictionary the phrase EH is not
- found. It is added to the dictionary as code number 260. Then we send
- the E to the disk and H becomes the new parent code and the next E
- becomes the new character code. After searching the dictionary we find
- that we can compress HE into the code 259,we want to compress as much as
- possible into one code so we make 259 the parent code. There may be a
- longer string then HE that can be compressed. The R is read in as the
- new character code. The dictionary is searched for the a 259 followed a
- R, since it is not found it is added to the dictioary and it looks like
- this:
-
- node #72 - H node #69 - E
- | |
- node #3200 259 - E node #1600 260 - H
- |
- node #1262 261 - R
-
- Then the value 259 is sent to the output file (to represent the HE) and
- since that is the EOF the R is sent as well,as well as a 256 to indicate
- the EOF has been reached.
-
- Decompression is extremely simple. As long as the decompressor maintains
- the dictionary as the compressor did, there will be no problems,except
- for one problem that can be handled as an exceptional case. All of the
- little details of increasing the number of bits to read, and when to
- flush the dictionary are taken care of by the compressor. So if the
- dictionary was increased to 8k, the compressor would have to be set up
- to handle a larger dictionary, but the decompressor only does as the
- compressed file tells it to and will work with any size dictionary. The
- only problem would be that a larger dictionary will creep into the ram
- under the rom or possibly even use all available memory, but assuming
- that the ram is available the decompressor will not change. The
- decompressor would start out reading 9 bits at a time, and starts it
- free code at 259 as the compressor did. To use the above input from the
- compressor as an example, the output was:
-
- 72 - For the First H
- 69 - For the First E
- 259 - For the Compressed HE
- 82 - For the R
- 256 - Eof indicator
-
- To begin decompressing, two values are needed. The H and E are read in,
- (note they will both be 9 bits long). As they are both below 256 they
- are at the end of the string and are sent straight to the output file.
- The first free code is 259 so that is the value assigned to the phrase
- HE. Note when decompressing there is no need for the hashing routine,
- the codes are the absolute locations in the dictionary (i.e. If the
- dictionary was considered to be an array then the entry number 259 would
- be dictionary[259]), because of this, the code value is no longer
- needed. So the decompressor would have an entry that looks like this:
-
- Node # 259
- Parent Code - H
- Character - E
-
- The decompressor will read in the next value (259). Because the node
- number is at the end of the compressed string we will have to take the
- code value and place it on a stack, and take them off in a
- Last-in,First-out (LIFO) fashion. That is to say that the first
- character to go on the stack (in this case the E) will be the last to
- come off. The size of the stack is dependent on the size of the
- dictionary, so for this implementation we need a stack that is 4k long.
- After all the characters from the string have been placed on the stack
- they are taken off and sent to the outputfile.
-
- There is one small error that is possible with LZW because of the way
- the compressor defines strings. Consider the compression dictionary that
- has the following in it:
-
- node # Code Parent character
- Value code
- ------ ------ ------ ---------
- 65 65 n/a A
- 723 259 65 C
- 1262 260 259 U
- 2104 261 260 T
- 2506 262 261 E
-
- Now if the compressor was to try to compress the string ACUTEACUTEA The
- compressor will find a match for the first five characters 'ACUTE' and
- will send a 262 to the output file. Then it will add the following entry
- to the dictionary:
-
- 3099 263 262 A
-
- Now it will try to compress the remaining characters, and it finds that
- it can compress the entire string with the code 263, but notice that the
- middle A, the one that was just added onto the end of the string 'ACUTE'
- was never sent to the output file. The decompressor will not have the
- code 263 defined in it's dictionary. The last code it will have defined
- will be 262. This problem is easily remedied though, when the
- decompressor does not have a code defined, it takes the first letter
- from the last phrase defined and tacks it onto the end of the last
- phrase. IE It takes the first letter (the A) from the phrase and adds it
- on to the end as code #263.
-
- This particular implementation is fairly slow because it reads a byte
- and then writes one, it could be made much faster with some buffering.
- It is also limited to compressing and decompressing one file at a time
- and has no error checking capabilities. It is meant strictly to teach
- LZW compression, not provide a full fledged compressor.
-
- And now for the code:
-
- SYS 4000 ; sys 999 on a 64
- .DVO 9 ; or whatever drive used for output
- .ORG 2500
- .OBJ "LZW.ML"
-
- TABLESIZE =5021
-
- ; THE TABLESIZE IS ACTUALLY 5021, ABOUT 20% LARGER THEN 4K. THIS GIVES
- ; THE HASHING ROUTINE SOME ROOM TO MOVE. IF THE TABLE WAS EXACTLY 4K
- ; THERE WOULD BE FREQUENT COLLISIONS WHERE DIFFERENT COMBINATIONS OF
- ; CHARACTERS WOULD HAVE THE SAME HASH ADDRESS. INCREASING THE TABLE SIZE
- ; REDUCES THE NUMBER OF COLLISIONS.
-
- EOS =256 ; eos = End of stream This marks the end of file
-
- FIRSTCODE =259
- MAXCODE =4096
-
- BUMPCODE =257 ; Whenever a 257 is encountered by the decompressor it
- ; increases the number of bits it reads by 1
-
- FLUSHCODE =258
-
- TABLEBASE =14336 ; The location that the dictionary is located at
-
- DECODESTACK =9300 ; The location of the 4k LIFO stack
-
- ; ORG = DECOMPRESS FILE
- ; ORG + 3 = COMPRESS FILE
-
- JMP EXPANDFILE
-
- ;********************************
- ; COMPRESSFILE
- ;********************************
-
- COMPRESSFILE JSR INITDIC ; EMPTY THE DICTIONARY
- LDA #128
- STA BITMASK
- LDA #0
- STA RACK
- JSR GETCHAR ; GET A CHAR FROM THE INPUT FILE
- STA STRINGCODE ; INITIALIZE THE STRINGCODE (PARENT CODE)
- LDA #0
- STA STRINGCODE+1
- NEXTCHAR JSR GETCHAR
- STA CHARACTER
- JSR FINDNODE ; FINDNODE CALCULATES THE HASHED LOCATION OF
- LDA ($FE),Y ; THE STRINGCODE AND CHARACTER IN THE DICT.
- INY ; AND SETS $FE/$FF POINTING TO IT. IF THE ENTRY
- AND ($FE),Y ; HAS TWO 255 IN IT THEN IT IS EMPTY AND SHOULD
- CMP #255 ; BE ADDED TO THE DICTIONARY.
- BEQ ADDTODICT
- LDA ($FE),Y ; IT HAS A DEFINED PHRASE. STORE THE CODE VALUE IN
- STA STRINGCODE+1; THE PARENT CODE
- DEY
- LDA ($FE),Y
- STA STRINGCODE
- JMP EOF
- ADDTODICT LDY #0
- - LDA NEXTCODE,Y
- STA ($FE),Y
- INY
- CPY #5
- BNE -
- INC NEXTCODE ; INCREASE THE NEXTCODE
- BNE +
- INC NEXTCODE+1
- + JSR OUTPUT
- LDA NEXTCODE+1 ; CHECK IF NEXTCODE=4096 IF SO THEN FLUSH THE
- CMP #>MAXCODE ; DICTIONARY AND START ANEW
- BNE CHECKBUMP
- LDA NEXTCODE
- CMP #<MAXCODE
- BNE CHECKBUMP
- LDA #<FLUSHCODE ; SEND THE FLUSH CODE TO THE COMPRESSED FILE SO
- STA STRINGCODE ; THE DECOMPRESSOR WILL KNOW TO FLUSH THE
- LDA #>FLUSHCODE ; DICTIONARY
- STA STRINGCODE+1
- JSR OUTPUT
- JSR INITDIC
- JMP CHECKEOF
- CHECKBUMP LDA NEXTBUMP+1
- CMP NEXTCODE+1 ; CHECKBUMP CHECK TO SEE IF THE NEXTCODE HAS
- BNE CHECKEOF ; REACHED THE MAXIMUM VALUE FOR THE CURRENT
- LDA NEXTBUMP ; NUMBER OF BITS BEING OUTPUT.
- CMP NEXTCODE ; FOR X BITS NEXTCODE HAS Y PHRASES
- BNE CHECKEOF ; -------- -----------------------
- LDA #>BUMPCODE ; 9 511
- STA STRINGCODE+1 ; 10 1023
- LDA #<BUMPCODE ; 11 2047
- STA STRINGCODE ; 12 4095
- JSR OUTPUT
- INC CURRENTBITS
- ASL NEXTBUMP
- ROL NEXTBUMP+1
- CHECKEOF LDA #0
- STA STRINGCODE+1
- LDA CHARACTER
- STA STRINGCODE
- EOF LDA 144
- BNE DONE
- JMP NEXTCHAR
- DONE JSR OUTPUT
- LDA #>EOS ; SEND A 256 TO INDICATE EOF
- STA STRINGCODE+1
- LDA #<EOS
- STA STRINGCODE
- JSR OUTPUT
- LDA BITMASK
- BEQ +
- JSR $FFCC
- LDX #3
- JSR $FFC9
- LDA RACK ; SEND WHAT BITS WEREN'T SEND WHEN OUTPUT
- JSR $FFD2
- + JSR $FFCC
- LDA #3
- JSR $FFC3
- LDA #2
- JMP $FFC3
-
- ;**********************************
- ; INITDIC
- ; INITIALIZES THE DICTIONARY, SETS
- ; THE NUMBER OF BITS TO 9
- ;**********************************
-
- INITDIC LDA #9
- STA CURRENTBITS
- LDA #>FIRSTCODE
- STA NEXTCODE+1
- LDA #<FIRSTCODE
- STA NEXTCODE
- LDA #>512
- STA NEXTBUMP+1
- LDA #<512
- STA NEXTBUMP
- LDA #<TABLEBASE
- STA $FE
- LDA #>TABLEBASE
- STA $FF
- LDA #<TABLESIZE
- STA $FC
- LDA #>TABLESIZE
- STA $FD
- - LDY #0
- LDA #255 ; ALL THE CODE VALUES ARE INIT TO 255+256*255
- STA ($FE),Y ; OR -1 IN TWO COMPLEMENT
- INY
- STA ($FE),Y
- CLC
- LDA #5 ; EACH ENTRY IN THE TABLE TAKES 5 BYTES
- ADC $FE
- STA $FE
- BCC +
- INC $FF
- + LDA $FC
- BNE +
- DEC $FD
- + DEC $FC
- LDA $FD
- ORA $FC
- BNE -
- RTS
-
- ;************************************
- ; GETCHAR
- ;************************************
-
- GETCHAR JSR $FFCC
- LDX #2
- JSR $FFC6
- JMP $FFCF
-
- ;************************************
- ; OUTPUT
- ;************************************
-
- OUTPUT LDA #0 ; THE NUMBER OF BITS OUTPUT CAN BE OF A VARIABLE
- STA MASK+1 ; LENGTH,SO THE BITS ARE ACCUMULATED TO A BYTE IS
- LDA #1 ; FULL AND THEN IT IS SENT TO THE OUTPUT FILE
- LDX CURRENTBITS
- DEX
- - ASL
- ROL MASK+1
- DEX
- BNE -
- STA MASK
- MASKDONE LDA MASK
- ORA MASK+1
- BNE +
- RTS
- + LDA MASK
- AND STRINGCODE
- STA 3
- LDA MASK+1
- AND STRINGCODE+1
- ORA 3
- BEQ NOBITON
- LDA RACK
- ORA BITMASK
- STA RACK
- NOBITON LSR BITMASK
- LDA BITMASK
- BNE +
- JSR $FFCC
- LDX #3
- JSR $FFC9
- LDA RACK
- JSR $FFD2
- LDA #0
- STA RACK
- LDA #128
- STA BITMASK
- + LSR MASK+1
- ROR MASK
- JMP MASKDONE
-
- ;******************************
- ; FINDNODE
- ; THIS SEARCHES THE DICTIONARY TILL IT FINDS A PARENT NODE THAT MATCHES
- ; THE STRINGCODE AND A CHILD NODE THAT MATCHES THE CHARACTER OR A EMPTY
- ; NODE.
- ;*******************************
-
- ; THE HASHING FUNCTION - THE HASHING FUNCTION IS NEEDED BECAUSE
- ; THERE ARE 4096 X 4096 (16 MILLION) DIFFERENT COMBINATIONS OF
- ; CHARACTER AND STRINGCODE. BY MULTIPLYING THE CHARACTER AND STRINGCODE
- ; IN A FORMULA WE CAN DEVELOP OF METHOD OF FINDING THEM IN THE
- ; DICTIONARY. IF THE STRINGCODE AND CHARACTER IN THE DICTIONARY
- ; DON'T MATCH THE ONES WE ARE LOOKING FOR WE CALCULATE AN OFFSET
- ; AND SEARCH THE DICTIONARY FOR THE RIGHT MATCH OR A EMPTY
- ; SPACE IS FOUND. IF AN EMPTY SPACE IS FOUND THEN THAT CHARACTER AND
- ; STRINGCODE COMBINATION IS NOT IN THE DICTIONARY
-
- FINDNODE LDA #0
- STA INDEX+1
- LDA CHARACTER ; HERE THE HASHING FUNCTION IS APPLIED TO THE
- ASL ; CHARACTER AND THE STRING CODE. FOR THOSE WHO
- ROL INDEX+1 ; CARE THE HASHING FORMULA IS:
- EOR STRINGCODE ; (CHARACTER << 1) ^ STRINGCODE
- STA INDEX ; FIND NODE WILL LOOP TILL IT FINDS A NODE
- LDA INDEX+1 ; THAT HAS A EMPTY NODE OR MATCHES THE CURRENT
- EOR STRINGCODE+1 ; PARENT CODE AND CHARACTER
- STA INDEX+1
- ORA INDEX
- BNE +
- LDX #1
- STX OFFSET
- DEX
- STX OFFSET+1
- JMP FOREVELOOP
- + SEC
- LDA #<TABLESIZE
- SBC INDEX
- STA OFFSET
- LDA #>TABLESIZE
- SBC INDEX+1
- STA OFFSET+1
-
- FOREVELOOP JSR CALCULATE
- LDY #0
- LDA ($FE),Y
- INY
- AND ($FE),Y
- CMP #255
- BNE +
- LDY #0
- RTS
- + INY
- - LDA ($FE),Y
- CMP STRINGCODE-2,Y
- BNE +
- INY
- CPY #5
- BNE -
- LDY #0
- RTS
- + SEC
- LDA INDEX
- SBC OFFSET
- STA INDEX
- LDA INDEX+1
- SBC OFFSET+1
- STA INDEX+1
- AND #128
- BEQ FOREVELOOP
- CLC
- LDA #<TABLESIZE
- ADC INDEX
- STA INDEX
- LDA #>TABLESIZE
- ADC INDEX+1
- STA INDEX+1
- JMP FOREVELOOP
-
- ;***************************
- ; CALCULATE
- ; TAKES THE VALUE IN INDEX AND CALCULATES ITS LOCATION IN THE DICTIONARY
- ;****************************
-
- CALCULATE LDA INDEX
- STA $FE
- LDA INDEX+1
- STA $FF
- ASL $FE
- ROL $FF
- ASL $FE
- ROL $FF
- CLC
- LDA INDEX
- ADC $FE
- STA $FE
- LDA INDEX+1
- ADC $FF
- STA $FF
- CLC
- LDA #<TABLEBASE
- ADC $FE
- STA $FE
- LDA #>TABLEBASE
- ADC $FF
- STA $FF
- LDY #0
- RTS
-
- ;******************************
- ; DECODESTRING
- ;******************************
-
- DECODESTRING TYA ; DECODESTRING PUTS THE STRING ON THE STACK
- STA COUNT ; IN A LIFO FASHION.
- LDX #>DECODESTACK
- CLC
- ADC #<DECODESTACK
- STA $FC
- STX $FD
- LDA #0
- STA COUNT+1
- - LDA INDEX+1
- BEQ +
- JSR CALCULATE
- LDY #4
- LDA ($FE),Y
- LDY #0
- STA ($FC),Y
- LDY #2
- LDA ($FE),Y
- STA INDEX
- INY
- LDA ($FE),Y
- STA INDEX+1
- JSR INFC
- JMP -
- + LDY #0
- LDA INDEX
- STA ($FC),Y
- INC COUNT
- BNE +
- INC COUNT+1
- + RTS
-
- ;******************************
- ; INPUT
- ;******************************
-
- INPUT LDA #0 ; THE INPUT ROUTINES IS USED BY THE DECOMPRESSOR
- STA MASK+1 ; TO READ IN THE VARIABLE LENGTH CODES
- STA RETURN
- STA RETURN+1
- LDA #1
- LDX CURRENTBITS
- DEX
- - ASL
- ROL MASK+1
- DEX
- BNE -
- STA MASK
- - LDA MASK
- ORA MASK+1
- BEQ INPUTDONE
- LDA BITMASK
- BPL +
- JSR GETCHAR
- STA RACK
- + LDA RACK
- AND BITMASK
- BEQ +
- LDA MASK
- ORA RETURN
- STA RETURN
- LDA MASK+1
- ORA RETURN+1
- STA RETURN+1
- + LSR MASK+1
- ROR MASK
- LSR BITMASK
- LDA BITMASK
- BNE +
- LDA #128
- STA BITMASK
- + JMP -
- INPUTDONE RTS
-
- ;*******************************
- ; EXPANDFILE
- ; WHERE THE DECOMPRESSION IS DONE
- ;*******************************
-
- EXPANDFILE LDA #0
- STA RACK
- LDA #128
- STA BITMASK
- START JSR INITDIC
- JSR INPUT
- LDA RETURN+1
- STA OLDCODE+1 ; Save the first character in OLDCODE
- LDA RETURN
- STA CHARACTER
- STA OLDCODE
- CMP #<EOS
- BNE +
- LDA RETURN+1 ; If return = EOS (256) then all done
- CMP #>EOS
- BNE +
- JMP CLOSE
- + JSR $FFCC
- LDX #3
- JSR $FFC9
- LDA OLDCODE ; Send oldcode to the output file
- JSR $FFD2
- NEXT JSR INPUT
- LDA RETURN
- STA NEWCODE
- LDA RETURN+1
- STA NEWCODE+1
- CMP #1 ; All of the special codes Flushcode,BumpCode & EOS
- BNE ++ ; Have 1 for a MSB.
- LDA NEWCODE
- CMP #<BUMPCODE
- BNE +
- INC CURRENTBITS
- JMP NEXT
- + CMP #<FLUSHCODE
- BEQ START
- CMP #<EOS
- BNE +
- JMP CLOSE
- + SEC ; Here we compare the newcode just read in to the
- LDA NEWCODE ; next code. If newcode is greater than it is a
- SBC NEXTCODE ; ACUTEACUTEA situation and must be handle differently.
- STA 3
- LDA NEWCODE+1
- SBC NEXTCODE+1
- ORA 3
- BCC +
- LDA CHARACTER
- STA DECODESTACK
- LDA OLDCODE
- STA INDEX
- LDA OLDCODE+1
- STA INDEX+1
- LDY #1
- BNE ++
- + LDA NEWCODE ; Point index to newcode spot in the dictionary
- STA INDEX ; So DECODESTRING has a place to start
- LDA NEWCODE+1
- STA INDEX+1
- LDY #0
- + JSR DECODESTRING
- LDY #0
- LDA ($FC),Y
- STA CHARACTER
- INC $FC
- BNE +
- INC $FD
- + JSR $FFCC
- LDX #3
- JSR $FFC9
- L1 LDA COUNT+1 ; Count contains the number of characters on the stack
- ORA COUNT
- BEQ +
- JSR DECFC
- LDY #0
- LDA ($FC),Y
- JSR $FFD2
- JMP L1
- + LDA NEXTCODE ; Calculate the spot in the dictionary for the string
- STA INDEX ; that was just entered.
- LDA NEXTCODE+1
- STA INDEX+1
- JSR CALCULATE
- LDY #2 ; The last character read in is tacked onto the end
- LDA OLDCODE ; of the string that was just taken off the stack
- STA ($FE),Y ; The nextcode is then incremented to prepare for the
- INY ; next entry.
- LDA OLDCODE+1
- STA ($FE),Y
- INY
- LDA CHARACTER
- STA ($FE),Y
- INC NEXTCODE
- BNE +
- INC NEXTCODE+1
- + LDA NEWCODE
- STA OLDCODE
- LDA NEWCODE+1
- STA OLDCODE+1
- JMP NEXT
- CLOSE JSR $FFCC
- LDA #2
- JSR $FFC3
- LDA #3
- JMP $FFC3
-
- DECFC LDA $FC
- BNE +
- DEC $FD
- + DEC $FC
- LDA COUNT
- BNE +
- DEC COUNT+1
- + DEC COUNT
- RTS
- INFC INC $FC
- BNE +
- INC $FD
- + INC COUNT
- BNE +
- INC COUNT+1
- + RTS
-
- NEXTCODE .WOR 0
- STRINGCODE .WOR 0
- CHARACTER .BYT 0
- NEXTBUMP .WOR 0
- CURRENTBITS .BYT 0
- RACK .BYT 0
- BITMASK .BYT 0
- MASK .WOR 0
- INDEX .WOR 0
- OFFSET .WOR 0
- RETURN .WOR 0
- COUNT .WOR 0
- NEWCODE .WOR 0
- OLDCODE .WOR 0
- TEST .BYT 0
-
- TO DRIVE THE ML I WROTE THIS SMALL BASIC PROGRAM. NOTE THAT CHANNEL TWO IS
- ALWAYS THE INPUT AND CHANNEL THREE IS ALWAYS THE OUTPUT. EX AND CO MAY BE
- CHANGED TO SUIT WHATEVER LOCATIONS THE PROGRAM IS REASSEMBLED AT.
-
- 1 IFA=.THENA=1:LOAD"LZW.ML",PEEK(186),1
- 10 EX=2500:CO=2503
- 15 PRINT"[E]XPAND OR [C]OMPRESS?"
- 20 GETA$:IFA$<>"C"ANDA$<>"E"THEN20
- 30 INPUT"NAME OF INPUT FILE";FI$:IFLEN(FI$)=.THEN30
- 40 INPUT"NAME OF OUTPUT FILE";FO$:IFLEN(FO$)=.THEN40
- 50 OPEN2,9,2,FI$+",P,R":OPEN3,9,3,FO$+",P,W"
- 60 IFA$="E"THENSYSEX
- 70 IFA$="C"THENSYSCO
- 80 END
-
- For those interested in learning more about data
- compression/decompression I recommend the book 'The Data Compression
- Book' written by Mark Nelson. I learned a great deal from reading this
- book. It explains all of the major data compression methods. (huffman
- coding, dictionary type compression such as LZW, arithmatic coding,
- speech compression and lossy graphics compression)
-
- Questions or comments are welcome, they may be directed to me at :
-
- Internet : Blucier@ersys.edmonton.ab.ca
- Genie : b.lucier1
-
- ------------------------------------------------------------------------------
-
- begin 644 lzw.ml
- MQ`E,N`P@GPJI@(WT#:D`C?,-(.L*C>T-J0"-[@T@ZPJ-[PT@6`NQ_L@Q_LG_
- M\`ZQ_HWN#8BQ_HWM#4QH"J``N>L-D?[(P`70]N[K#=`#[NP-(/8*K>P-R1#0
- M&JWK#<D`T!.I`HWM#:D!C>X-(/8*()\*3%T*K?$-S>P-T!ZM\`W-ZPW0%JD!
- MC>X-J0&-[0T@]@KN\@T.\`TN\0VI`(WN#:WO#8WM#:60T`-,WPD@]@JI`8WN
- M#:D`C>T-(/8*K?0-\`X@S/^B`R#)_ZWS#2#2_R#,_ZD#(,/_J0),P_^I"8WR
- M#:D!C>P-J0.-ZPVI`HWQ#:D`C?`-J0"%_JDXA?^IG87\J1.%_:``J?^1_LB1
- M_ABI!67^A?Z0`N;_I?S0`L;]QORE_07\T-Y@(,S_H@(@QO],S_^I`(WV#:D!
- MKO(-R@HN]@W*T/F-]0VM]0T-]@W0`6"M]0TM[0V%`ZWV#2WN#04#\`FM\PT-
- M]`V-\PU.]`VM]`W0&"#,_Z(#(,G_K?,-(-+_J0"-\PVI@(WT#4[V#6[U#4P+
- M"ZD`C?@-K>\-"B[X#4WM#8WW#:WX#4WN#8WX#=`1K?<-T`RB`8[Y#<J.^@U,
- MEPLXJ9WM]PV-^0VI$^WX#8WZ#2#C"Z``L?[(,?[)_]`#H`!@R+'^V>L-T`C(
- MP`70]*``8#BM]PWM^0V-]PVM^`WM^@V-^`TI@/`1&*F=;?<-C?<-J1-M^`V-
- M^`U,EPNM]PV%_JWX#87_!OXF_P;^)O\8K?<-9?Z%_JWX#67_A?\8J0!E_H7^
- MJ3AE_X7_H`!@F(W]#:(D&&E4A?R&_:D`C?X-K?@-\!X@XPN@!+'^H`"1_*`"
- ML?Z-]PW(L?Z-^`T@W`U,)@R@`*WW#9'\[OT-T`/N_@U@J0"-]@V-^PV-_`VI
- M`:[R#<H*+O8-RM#YC?4-K?4-#?8-\#NM]`T0!B#K"HWS#:WS#2WT#?`2K?4-
- M#?L-C?L-K?8-#?P-C?P-3O8-;O4-3O0-K?0-T`6I@(WT#4QT#&"I`(WS#:F`
- MC?0-()\*(%D,K?P-C0(.K?L-C>\-C0$.R0#0"JW\#<D!T`-,NPT@S/^B`R#)
- M_ZT!#B#2_R!9#*W[#8W_#:W\#8T`#LD!T!BM_PW)`=`&[O(-3/,,R0+PJ\D`
- MT`-,NPTXK?\-[>L-A0.M``[M[`T%`Y`6K>\-C50DK0$.C?<-K0(.C?@-H`'0
- M#JW_#8WW#:T`#HWX#:``(!0,H`"Q_(WO#>;\T`+F_2#,_Z(#(,G_K?X-#?T-
- M\`T@R`V@`+'\(-+_3&T-K>L-C?<-K>P-C?@-(.,+H`*M`0Z1_LBM`@Z1_LBM
- M[PV1_N[K#=`#[NP-K?\-C0$.K0`.C0(.3/,,(,S_J0(@P_^I`TS#_Z7\T`+&
- M_<;\K?T-T`/._@W._0U@YOS0`N;][OT-T`/N_@U@````````````````````
- *````````````````
- `
- end
-
- crc32 for lzw.ml = 2460116527
-
- begin 644 lzw.bas
- M`0@<"`$`BT&R+J=!LC$ZDR),6E<N34PB+#DL,0`P"`H`15BR,C4P,#I#3[(R
- M-3`S`%`(#P"9(I,219)84$%.1"!/4B`20Y)/35!215-3/R(`;`@4`*%!)#J+
- M022SL2)#(J]!)+.Q(D4BIS(P`)<('@"%(DY!344@3T8@24Y0550@1DE,12([
- M1DDD.HO#*$9))"FR+J<S,`##""@`A2).04U%($]&($]55%!55"!&24Q%(CM&
- M3R0ZB\,H1D\D*;(NIS0P`.L(,@"?,BPY+#(L1DDDJB(L4RQ2(CJ?,RPY+#,L
- M1D\DJB(L4"Q7(@#["#P`BT$DLB)%(J>>15@`"PE&`(M!)+(B0R*GGD-/````
- `
- end
-
- crc32 for lzw.bas = 100674089
-
- ===============================================================================
- THREE-KEY ROLLOVER for the C-128 and C-64.
- by Craig Bruce <csbruce@neumann.uwaterloo.ca>
-
- 1. INTRODUCTION
-
- This article examines a three-key rollover mechanism for the keyboards of the
- C-128 and C-64 and presents Kernal-wedge implementations for both machines.
- Webster's doesn't seem to know, so I'll tell you that this means that the
- machine will act sensibly if you are holding down one key and then press
- another without releasing the first (or even press a third key while holding
- down two others). This is useful to fast touch typers. In fact, fast typing
- without rollover can be quite annoying; you get a lot of missing letters.
-
- Another annoying property of the kernel keyscanning is joystick interference.
- If you move the joystick plugged into port #1, you will notice that some junk
- keystrokes result. The keyscanners here eliminate this problem by simply
- checking if the joystick is pressed and ignoring the keyboard if it is.
-
- The reason that a 3-key rollover is implemented instead of the more general
- N-key rollover is that scanning the keyboard becomes more and more unreliable
- as more keys are held down. Key "shaddows" begin to appear to make it look
- like you are holding down a certain key when you really are not. So, by
- limiting the number of keys scanned to 3, some of this can be avoided. You
- will get strange results if you hold down more than three keys at a time, and
- even sometimes when holding down 3 or less. The "shift" keys (Shift,
- Commodore, Control, Alternate, and CapsLock) don't count in the 3 keys of
- rollover, but they do make the keyboard harder to read correctly.
- Fortunately, three keys will allow you to type words like "AND" and "THE"
- without releasing any keys.
-
- 2. USER GUIDE
-
- Using these utilities is really easy - you just type away like normal. To
- install the C-128 version, enter:
-
- BOOT "KEYSCAN128"
-
- and you're in business. The program will display "Keyscan128 installed" and
- go to work. The program loads into memory at addresses $1500-$17BA (5376-6074
- decimal), so you'll want to watch out for conflicts with other utilities.
- This program also takes over the IRQ vector and the BASIC restart vector
- ($A00). The program will survive a RUN/STOP+RESTORE. To uninstall this
- program, you must reset the machine (or poke the kernel values back into the
- vectors); it does not uninstall itself.
-
- Loading the C-64 version is a bit trickier, so a small BASIC loader program is
- provided. LOAD and RUN the "KEYSCAN64.BOOT" program. It will load the
- "KEYSCAN64" program into memory at addresses $C500-$C77E (50432-51070 decimal)
- and execute it (with a SYS 50432). To uninstall the program, enter SYS 50435.
- The program takes over the IRQ and NMI vectors and only gives them back to the
- kernel upon uninstallation. The program will survive a RUN/STOP+RESTORE.
-
- Something that you may or may not know about the C-64 is that its keys can be
- made to repeat by poking to address 650 decimal. POKE650,128 will enable the
- repeating of all keys. POKE650,0 will enable only the repeating of the SPACE,
- DELETE, and CURSOR keys. POKE650,64 will disable the repeating of all keys.
- An unusual side effect of changing this to either full repeat or no repeat is
- that holding down SHIFT+COMMODORE (character set shift) will repeat rapidly.
-
- To see the rollover in action, hold down the "J" key for a while, and then
- press "K" without releasing "J". "K" will come out as expected, as it would
- with the kernal. Now, release the "J" key. If you are on a C-128, you will
- notice that the "K" key will now stop repeating (this is actually an important
- feature - it avoids problems if you don't release all of the keys you are
- holding down, at once). Now, press and hold the "J" key again without
- releasing the "K". "J" will now appear. It wouldn't using the Kernal key
- scanner. You can also try this with 3-key combinations. There will be some
- combinations that cause problems; more on this below.
-
- Also, take a spaz on the joystick plugged into port #1 and observe that no
- garbage gets typed in. This was an annoying problem with the kernel of both
- the 64 and 128 and has lead many different games to picking between joystick
- #1 and #2 as the primary controller. The joystick in port #2 is not a problem
- to either Keyscan-128/64 or the Kernal.
-
- 3. KEYBOARD SCANNING
-
- The Kernal scans the keyboard sixty times a second to see what keys you are
- holding down. Because of hardware peculiarities, there are multiple scanning
- techniques that will give different results.
-
- 3.1. SCANNING EXAMPLE
-
- An example program is included to demonstrate different keyboard scanning
- techniques possible. To run it from a C-128 in 40-column (slow) mode, enter:
-
- BOOT "KEYSHOW"
-
- On a C-64, you must:
-
- LOAD "KEYSHOW",8,1
-
- and then:
-
- SYS 4864
-
- The same program works on both machines. Four maps of the keyscanning matrix
- will be displayed on the 40-column screen, as scanned by different techniques.
- The leftmost one is scanned from top to bottom "quickly". The second from the
- left scans from bottom to top "quickly". The third from the left scans the
- keyboard sideways, and the rightmost matrix scans the keys from top to bottom
- "slowly".
-
- The mapping of keyscan matrix positions to keys is as follows:
-
- ROWS: \ COLUMNS: peek($DC01)
- poke \
- $DC00 \ 128 64 32 16 8 4 2 1
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-1 | DOWN | F5 | F3 | F1 | F7 | RIGHT | RETURN| DELETE|
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-2 |LEFT-SH| E | S | Z | 4 | A | W | 3 |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-4 | X | T | F | C | 6 | D | R | 5 |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-8 | V | U | H | B | 8 | G | Y | 7 |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-16 | N | O | K | M | 0 | J | I | 9 |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-32 | , | @ | : | . | - | L | P | + |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-64 | / | ^ | = |RGHT-SH| HOME | ; | * | \ |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-128 | STOP | Q |COMMODR| SPACE | 2 |CONTROL| _ | 1 |
- +-------+-------+-------+-------+-------+-------+-------+-------+
-
- The following table contains the additional keys which must be scanned on the
- C128 (but which are not displayed by the example scanning program).
-
- ROWS: \ COLUMNS: peek($DC01)
- poke \
- $D02F \ 128 64 32 16 8 4 2 1
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-1 | 1 | 7 | 4 | 2 | TAB | 5 | 8 | HELP |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-2 | 3 | 9 | 6 | ENTER | LF | - | + | ESC |
- +-------+-------+-------+-------+-------+-------+-------+-------+
- 255-4 |NO-SCRL| RIGHT | LEFT | DOWN | UP | . | 0 | ALT |
- +-------+-------+-------+-------+-------+-------+-------+-------+
-
- These tables are presented on page 642 of the Commodore 128 Programmer's
- Reference Guide. The scan codes that are stored in location 212 on the C128
- and location 197 on the C64 are calculated based on the above tables. The
- entry in the "1" bit position of the first line of the first table (DELETE)
- has a scan code of 0, the "2" entry (RETURN) has a scan code of 1, etc., the
- entry on the second scan line in the "1" position ("3") has a scan code of 8,
- etc., all the way down to code 63. The scan codes for the 128 go all the way
- to 87, continuing in the second table like the first.
-
- You will notice some strange effects of the different scanning techniques when
- you hold down multiple keys. More on this below. Also try pushing joystick
- #1 around.
-
- 3.2. SCANNING HARDWARE
-
- To scan the 128 keyboard, you must poke a value into $DC00 (CIA#1 port A) and
- $D02F (VIC chip keyboard select port) to select the row to be scanned. The
- Data Direction Register for this port will be set to all outputs by the
- Kernal, so you don't have to worry about it. Each bit of $DC00 and the three
- least significant bits of $D02F are used for selecting rows. A "0" bit means
- that a row IS selected, and a "1" means that a row IS NOT selected. The poke
- value to use for selecting among the various rows are given in the two tables
- in the previous section.
-
- Using one bit per row allows you to select multiple rows at the same time. It
- can be useful to select all rows at one time or no rows. To read the row that
- has been selected, simply peek at location $DC01 (CIA#1 port B). Each bit
- will tell you whether the corresponding key is currently being held down or
- not. Again, we have reverse logic; a "0" means that the key is being held
- down, and a "1" means that the key is not held down. The bit values
- corresponding to the keys are given as the column headings in the tables in
- the previous section. Since there is no such thing as a perfect mechanical
- switch, it is recommended that you "debounce" each key row read in the
- following way:
-
- again:
- lda $dc01
- cmp $dc01
- bne again
-
- So, to scan the entire keyboard, you simply select each scan row in some
- order, and read and remember the keys held down on the row. As it turns out,
- you have to be a bit careful of exactly how you "select" a row. Also, there
- is a shortcut that you can take. In order to find out if any key is being
- held down in one operation, all you have to do is select all rows at once and
- see if there are any "0" bits in the read value. If so, there is a key being
- held down somewhere; if not, then there is no key being held down, so you
- don't have to bother scanning the entire keyboard. This will reduce our
- keyscanning significantly, which is important, since the keyboard will be
- scanned every 1/60 of a second.
-
- As mentioned above, joystick #1 will interfere with the Kernal reading the
- keyboard. This is because the read value of joystick #1 is wired into CIA#1
- port A, the same place that the keyboard read is wired in. So, whenever a
- switch in the joystick is pushed, the corresponding bit of the keyboard scan
- register will be forced to "0", regardless of which keys are pressed and
- regardless of which scan row is selected. There's the catch. If we were to
- un-select all scan rows and still notice "0"s in the keyboard read register,
- then we would know that the joystick was being pushed and would interfere with
- our keyboard scanning, so we could abort keyboard scanning and handle this
- case as if no keys were being held down.
-
- It still would be possible but unlikely that the user could push the joystick
- in the middle of us scanning the keyboard and screw up our results, so to
- defend against this, we check for the joystick being pushed both before and
- after scanning the keyboard. If we find that the joystick is pushed at either
- of these times, then we throw out the results and assume that no keys are held
- down. This way, the only way that a user could screw up the scanning is if
- he/she/it were to press a switch after we begin scanning and release it before
- we finish scanning. Not bloody likely for a human.
-
- You get the same deal for keyboard scanning on the 64, except you only need to
- use $DC00 for selecting the scan rows. Also note that you will not be able to
- play with keyboard scanning from BASIC because of the interrupt reading of the
- keyboard. You must make sure that interrupts are disabled when playing with
- the keyboard hardware, or interrupt scanning can come along at any time and
- change all of the register settings.
-
- 3.3. SCANNING SOURCE CODE
-
- The four keyboard scanning techniques of the example program are presented
- below. The declarations required for all of them are:
-
- pa = $dc00 ;row select
- pb = $dc01 ;column read
- ddra = $dc02 ;ddr for row select
- ddrb = $dc03 ;ddr for column read
- scanTable .buf 8 ;storage for scan
- mask = $03 ;work location
-
- The code is as follows, in Buddy format. Each routine scans the keyboard and
- stores the results in the "scanTable" table.
-
- ------------------+------------------+------------------+------------------+
- Row forward fast | Row backward fast| Column right | Row forward slow
- ------------------+------------------+------------------+------------------+
- sei | sei | sei | sei
- ldx #0 | ldx #7 | lda #$00 | ldx #0
- lda #$fe | lda #$7f | sta ddra | lda #$fe
- sta pa | sta pa | lda #$ff | sta mask
- nextRow = * | nextRow = * | sta ddrb | nextRow = *
- - lda pb |- lda pb | ldy #7 | lda mask
- cmp pb | cmp pb | lda #$7f | sta pa
- bne - | bne - | sta mask |- lda pb
- eor #$ff | eor #$ff | nextCol = * | cmp pb
- sta scanTable,x | sta scanTable,x | lda mask | bne -
- sec | sec | sta pb | eor #$ff
- rol pa | ror pa |- lda pa | sta scanTable,x
- inx | dex | cmp pa | sec
- cpx #8 | bpl nextRow | bne - | rol mask
- bcc nextRow | cli | ldx #$ff | inx
- cli | rts | stx pb | cpx #8
- rts | | eor #$ff | bcc nextRow
- ------------------+------------------+ ldx #7 | cli
- |- asl | rts
- The forward "quick" scanning stores | rol scanTable,x +------------------+
- the scan row selection mask into | dex |
- the row selection register and | bpl - |
- shifts the "0" bit one position | sec |
- "forward" for each row, directly, | ror mask |
- using a "rol $dc00" instruction. | dey |
- This would probably be the obvious | bpl nextCol |
- solution to an optimizing assembler | lda #$ff |
- programmer. However, for some | sta ddra |
- reason not quite understood by this | lda #$00 |
- author, there are "shadowing" | sta ddrb |
- problems with this approach. If | cli |
- you were to hold down the two keys | rts |
- "H" and "K" at the same time, you +------------------+
- would notice that these two keys
- are on the same column of two successive rows. If you hold them both down,
- you will see the two positions become active, but so will the same column of
- all successive rows after the "H" and "K", even though these other keys are
- not actually held down. You will get an inaccurate reading if bad keys are
- held down simultaneously. You will notice the use of the term "active" above.
- This is because although the hardware returns a "0" for active, the routine
- converts that into a "1" for easier processing later. I am not sure if
- everyone will get this same result, but if your keyboard is wired the same as
- mine, you will.
-
- The backward "quick" scanning operates quite similarly to the forward
- scanning, except for the direction of the scan and the direction of the
- "shadow"; the shadow goes upwards. You might think that ANDing together the
- results of the forward and backward scan together would eliminate the shadow,
- but this will not work since any rows between the rows containing the two keys
- held down will be incorrectly read as being active.
-
- The columnwise right scanning is the most complicated because the rows must be
- converted into columns, to allow the scan matrix to be interpreted as before.
- Also, the Data Direction Registers have to be changed. You might think that
- combinging row-wise scanning with columnwise scanning would give better
- results, and it probably would, if it weren't for a bizarre hardware problem.
- If you hold down two or more keys on the same scan row, say "W" and "E", some
- of the keys will flicker or disappear, giving an inaccurate reading.
-
- The forward "slow" scanning is the best of the bunch. Incidentally, it is
- what the Kernal uses (as near as I can figure - their code is extremely
- convoluted). This technique is the same as the forward "quick scan," except
- that the row selection mask is shifted in a working storage location and poked
- into the CIA register, rather than being shifted in place. I don't know why
- this makes a difference, but it does. There is still a problem with this
- technique, but this problem occurs with all techniques. If you hold down
- three keys that form three "corners" of a rectangle in the scanning matrix,
- then the missing corner will be read as being held down also. For example, if
- you hold down "C", "N", and "M", then the keyboard hardware will also think
- that you are holding down the "X" key. This is why this article implements a
- "three-key" rollover rather than an "N-key" rollover. Many three-key
- combinations will still be interpreted correctly. Note, however, that shift
- keys such as SHIFT or CONTROL will add one more key to the hardware scanning
- (but will not be counted in the three-key rollover), making inaccurate results
- more likely if you are holding down multiple other keys at the same time.
-
- 4. THE C-128 KEYSCANNER
-
- This section gives the source code for the C-128 implementation of the
- three-key rollover. The forward "slow" key matrix scanning technique is used,
- extended to work with the extra keys of the 128. It was a bit of a pain
- wedging into the Kernal, since there is not a convenient indirect JMP into
- scanning the keyboard, like there are for decoding and buffering pressed keys.
- A rather lengthy IRQ "preamble" had to be copied from the ROM, up to the
- point where it JSRs to the keyscanning routine. This code in included in
- the form of a ".byte" table, to spare you the details.
-
- Before scanning the keyboard, we check to see if joystick #1 is pushed and if
- a key is actually pressed. If not, we abort scanning and JMP to the key
- repeat handling in the ROM. If a key is held down, we scan the keyboard and
- then examine the result. First we check for the shift keys (SHIFT, COMMODORE,
- CONTROL, ALT, and CAPS LOCK), put them into location $D3 (shift flags) in bit
- postitions 1, 2, 4, 8, and 16, respectively, and remove them from the scan
- matrix. The CAPS LOCK key is not on the main key matrix; it is read from the
- processor I/O port. This is good, because otherwise we could not abort
- scanning if it were the only key held down.
-
- Then we scan the keymatrix for the first three keys that are being held down,
- or as many as are held down if less than three. We store the scan codes of
- these keys into a 3-element array. We also retain a copy of the 3-element
- array from the previous scan and we check for different keys being in the two
- arrays. If the old array contains a key that is not present in the new array,
- then the use has released a key, so we set a flag to inhibit interpretation of
- keys and pretend that no keys are held down. This is to eliminate undesirable
- effects of having other keys held down repeat if you release the most recently
- pushed key first. PC keyboards do this. This inhibiting will be ignored if
- new keys are discovered in the next step.
-
- If there are keys in the new array that are not in the old, then the user has
- just pressed a new key, so that new key goes to the head of the old array and
- we stop comparing the arrays there. The key in the first position of the old
- array is poked into the Kernal "key held down" location for the Kernal to
- interpret later. If more than one new key is discovered at the same time,
- then each of the new keys will be picked up on successive keyboard scans and
- will be interpreted as just being pushed. So, if you press the "A", "N", and
- "D" keys all at the same time, some permutation of all three of these keys
- will appear on the screen.
-
- When we are done interpreting the keys, we check the joystick once more and if
- it is still inactive, we present the most recently pushed down key to the
- Kernal and JMP into the ROM keyboard decoding routine.
-
- Unlike in previous issues, this source code is here in literal form; just
- extract everything between the "-----=-----"s to nab the source for yourself.
- The source is in Buddy assembler format.
-
- -----=-----
- ;3-Key Rollover-128 by Craig Bruce 18-Jun-93 for C= Hacking magazine
-
- .org $1500
- .obj "@0:keyscan128"
-
- scanrows = 11
- rollover = 3
-
- pa = $dc00
- pb = $dc01
- pk = $d02f
-
- jmp initialInstall
-
- ;ugly IRQ patch code.
-
- irq = * ;$1503
- .byte $d8,$20,$0a,$15,$4c,$69,$fa,$38,$ad,$19,$d0,$29,$01,$f0,$07,$8d
- .byte $19,$d0,$a5,$d8,$c9,$ff,$f0,$6f,$2c,$11,$d0,$30,$04,$29,$40,$d0
- .byte $31,$38,$a5,$d8,$f0,$2c,$24,$d8,$50,$06,$ad,$34,$0a,$8d,$12,$d0
- .byte $a5,$01,$29,$fd,$09,$04,$48,$ad,$2d,$0a,$48,$ad,$11,$d0,$29,$7f
- .byte $09,$20,$a8,$ad,$16,$d0,$24,$d8,$30,$03,$29,$ef,$2c,$09,$10,$aa
- .byte $d0,$28,$a9,$ff,$8d,$12,$d0,$a5,$01,$09,$02,$29,$fb,$05,$d9,$48
- .byte $ad,$2c,$0a,$48,$ad,$11,$d0,$29,$5f,$a8,$ad,$16,$d0,$29,$ef,$aa
- .byte $b0,$08,$a2,$07,$ca,$d0,$fd,$ea,$ea,$aa,$68,$8d,$18,$d0,$68,$85
- .byte $01,$8c,$11,$d0,$8e,$16,$d0,$b0,$13,$ad,$30,$d0,$29,$01,$f0,$0c
- .byte $a5,$d8,$29,$40,$f0,$06,$ad,$11,$d0,$10,$01,$38,$58,$90,$07,$20
- .byte $aa,$15,$20,$e7,$c6,$38,$60
-
- ;keyscanning entry point
-
- main = *
- lda #0 ;check if any keys are held down
- sta pa
- sta pk
- - lda pb
- cmp pb
- bne -
- cmp #$ff
- beq noKeyPressed ;if not, then don't scan keyboard, goto Kernal
-
- jsr checkJoystick ;if so, make sure joystick not pressed
- bcc joystickPressed
- jsr keyscan ;scan the keyboard and store results
- jsr checkJoystick ;make sure joystick not pressed again
- bcc joystickPressed
- jsr shiftdecode ;decode the shift keys
- jsr keydecode ;decode the first 3 regular keys held down
- jsr keyorder ;see which new keys pressed, old keys released, and
- ; determine which key to present to the Kernal
- lda $033e ;set up for and dispatch to Kernal
- sta $cc
- lda $033f
- sta $cd
- ldx #$ff
- bit ignoreKeys
- bmi ++
- lda prevKeys+0
- cmp #$ff
- bne +
- lda $d3
- beq ++
- lda #88
- + sta $d4
- tay
- jmp ($033a)
-
- noKeyPressed = * ;no keys pressed; select default scan row
- lda #$7f
- sta pa
- lda #$ff
- sta pk
-
- joystickPressed = *
- lda #$ff ;record that no keys are down in old 3-key array
- ldx #rollover-1
- - sta prevKeys,x
- dex
- bpl -
- jsr scanCaps ;scan the CAPS LOCK key
- ldx #$ff
- lda #0
- sta ignoreKeys
-
- + lda #88 ;present "no key held" to Kernal
- sta $d4
- tay
- jmp $c697
-
- initialInstall = * ;install wedge: set restore vector, print message
- jsr install
- lda #<reinstall
- ldy #>reinstall
- sta $0a00
- sty $0a01
- ldx #0
- - lda installMsg,x
- beq +
- jsr $ffd2
- inx
- bne -
- + rts
-
- installMsg = *
- .byte 13
- .asc "keyscan128 installed"
- .byte 0
-
- reinstall = * ;re-install wedge after a RUN/STOP+RESTORE
- jsr install
- jmp $4003
-
- install = * ;guts of installation: set IRQ vector to patch code
- sei ; and initialize scanning variables
- lda #<irq
- ldy #>irq
- sta $0314
- sty $0315
- cli
- ldx #rollover-1
- lda #$ff
- - sta prevKeys,x
- dex
- bpl -
- lda #0
- sta ignoreKeys
- rts
-
- mask = $cc
-
- keyscan = * ;scan the (extended) keyboard using the forward
- ldx #$ff ; row-wise "slow" technique
- ldy #$ff
- lda #$fe
- sta mask+0
- lda #$ff
- sta mask+1
- jmp +
- nextRow = *
- - lda pb
- cmp pb
- bne -
- sty pa
- sty pk
- eor #$ff
- sta scanTable,x
- sec
- rol mask+0
- rol mask+1
- + lda mask+0
- sta pa
- lda mask+1
- sta pk
- inx
- cpx #scanrows
- bcc nextRow
- rts
-
- shiftValue = $d3
-
- shiftRows .byte $01,$06,$07,$07,$0a
- shiftBits .byte $80,$10,$20,$04,$01
- shiftMask .byte $01,$01,$02,$04,$08
-
- shiftdecode = * ;see which "shift" keys are held down, put them into
- jsr scanCaps ; proper positions in $D3 (shift flags), and remove
- ldy #4 ; them from the scan matrix
- - ldx shiftRows,y
- lda scanTable,x
- and shiftBits,y
- beq +
- lda shiftMask,y
- ora shiftValue
- sta shiftValue
- lda shiftBits,y
- eor #$ff
- and scanTable,x
- sta scanTable,x
- + dey
- bpl -
- rts
-
- scanCaps = * ;scan the CAPS LOCK key from the processor I/O port
- - lda $1
- cmp $1
- bne -
- eor #$ff
- and #$40
- lsr
- lsr
- sta shiftValue
- rts
-
- newpos = $cc
- keycode = $d4
- xsave = $cd
-
- keydecode = * ;get the scan codes of the first three keys held down
- ldx #rollover-1 ;initialize: $ff means no key held
- lda #$ff
- - sta newKeys,x
- dex
- bpl -
- ldy #0
- sty newpos
- ldx #0
- stx keycode
-
- decodeNextRow = * ;decode a row, incrementing the current scan code
- lda scanTable,x
- beq decodeContinue
- ;at this point, we know that the row has a key held
- ldy keycode
- - lsr
- bcc ++
- pha ;here we know which key it is, so store its scan code,
- stx xsave ; up to 3 keys
- ldx newpos
- cpx #rollover
- bcs +
- tya
- sta newKeys,x
- inc newpos
- + ldx xsave
- pla
- + iny
- cmp #$00
- bne -
-
- decodeContinue = *
- clc
- lda keycode
- adc #8
- sta keycode
- inx
- cpx #scanrows
- bcc decodeNextRow
- rts
-
- ;keyorder: determine what key to present to the Kernal as being logically the
- ;only one pressed, based on which keys previously held have been released and
- ;which new keys have just been pressed
-
- keyorder = *
- ;** remove old keys no longer held from old scan code array
- ldy #0
- nextRemove = *
- lda prevKeys,y ;get current old key
- cmp #$ff
- beq ++
- ldx #rollover-1 ;search for it in the new scan code array
- - cmp newKeys,x
- beq +
- dex
- bpl -
- tya ;here, old key no longer held; remove it
- tax
- - lda prevKeys+1,x
- sta prevKeys+0,x
- inx
- cpx #rollover-1
- bcc -
- lda #$ff
- sta prevKeys+rollover-1
- sta ignoreKeys
- + iny ;check next old key
- cpy #rollover
- bcc nextRemove
-
- ;** insert new keys at front of old scan code array
- + ldy #0
- nextInsert = *
- lda newKeys,y ;get current new key
- cmp #$ff
- beq ++
- ldx #rollover-1 ;check old scan code array for it
- - cmp prevKeys,x
- beq +
- dex
- bpl -
- pha ;it's not there, so insert new key at front, exit
- ldx #rollover-2
- - lda prevKeys+0,x
- sta prevKeys+1,x
- dex
- bpl -
- lda #0
- sta ignoreKeys
- pla
- sta prevKeys+0
- ldy #rollover ;(trick to exit)
- + iny
- cpy #rollover
- bcc nextInsert
- + rts ;now, the head of the old scan code array contains
- ; the scan code to present to the Kernal, and other
- ; positions represent keys that are also held down
- ; that have already been processed and therefore can
- ; be ignored until they are released
-
- checkJoystick = * ;check if joystick is pushed: un-select all keyboard
- lda #$ff ; rows and see if there are any "0"s in the scan
- sta pa ; status register
- sta pk
- - lda pb
- cmp pb
- bne -
- cmp #$ff
- lda #$7f ;restore to default Kernal row selected (to the one
- sta pa ; containing the STOP key)
- lda #$ff
- sta pk
- rts
-
- ;global variables
-
- scanTable .buf scanrows ;values of the eleven keyboard scan rows
- newKeys .buf rollover ;codes of up to three keys held simultaneously
- ignoreKeys .buf 1 ;flag: if an old key has been released and no
- ; new key has been pressed, stop all key
- ; repeating
- prevKeys .buf rollover+2 ;keys held on previous scan
- -----=-----
-
- And that's all there is to it. :-)
-
- 5. THE C-64 KEYSCANNER
-
- The boot program for the C-64 keyscanner is as follows:
-
- 10 d=peek(186)
- 20 if a=1 then 60
- 30 a=1
- 40 load"keyscan64",d,1
- 50 goto 10
- 60 sys 49152+5*256 : rem $c500
-
- It is very much like boot programs for other machine language programs that
- don't load at the start of BASIC. It will load the binary from the last
- device accessed, and activate it.
-
- A listing of the C-64 keyscanning code is not presented here because it is so
- similar to the C-128 listing. The only things that are different are the
- Kernal patches and the keyboard scanning (because the three extra rows don't
- have to be scanned). The IRQ had to be substantially copied from the ROM,
- again, to get at the call to the key scanning. Also, rather than taking
- over the BASIC reset vector (since there isn't one), the NMI vector is
- taken over to insure the survival of the key scanner after a RUN/STOP+RESTORE.
- A bit of its preamble also had to be copied out of ROM to get at the good
- stuff. If you want a copy of the C-64 listing, you can e-mail me.
-
- 6. UUENCODED FILES
-
- Here are the binary executables in uuencoded form. The CRC32s of the four
- files are as follows:
-
- crc32 = 3398956287 for "keyscan128"
- crc32 = 2301926894 for "keyscan64.boot"
- crc32 = 1767081474 for "keyscan64"
- crc32 = 1604419896 for "keyshow"
-
- begin 640 keyscan128
- M`!5,'A;8(`H53&GZ.*T9T"D!\`>-&="EV,G_\&\L$=`P!"E`T#$XI=CP+"38
- M4`:M-`J-$M"E`2G]"01(K2T*2*T1T"E_"2"HK1;0)-@P`RGO+`D0JM`HJ?^-
- M$M"E`0D"*?L%V4BM+`I(K1'0*5^HK1;0*>^JL`BB!\K0_>KJJFB-&-!HA0&,
- M$=".%M"P$ZTPT"D!\`REV"E`\`:M$=`0`3A8D`<@JA4@Y\8X8*D`C0#<C2_0
- MK0'<S0'<T/C)__`Z((D7D#\@<18@B1>0-R"W%B#L%B`L%ZT^`X7,K3\#A<VB
- M_RRT%S`QK;47R?_0!J73\":I6(74J&PZ`ZE_C0#<J?^-+]"I_Z("G;47RA#Z
- M(-T6HO^I`(VT%ZE8A=2H3)?&(%46J4^@%HT`"HP!"J(`O3D6\`8@TO_HT/5@
- M#4M%65-#04XQ,C@@24Y35$%,3$5$`"!5%DP#0'BI`Z`5C10#C!4#6*("J?^=
- MM1?*$/JI`(VT%V"B_Z#_J?Z%S*G_A<U,F!:M`=S-`=S0^(P`W(POT$G_G:87
- M.";,)LVES(T`W*7-C2_0Z.`+D-E@`08'!PJ`$"`$`0$!`@0((-T6H`2^J!:]
- MIA<YK1;P$KFR%@73A=.YK19)_SVF%YVF%X@0X&"E`<4!T/I)_RE`2DJ%TV"B
- M`JG_G;$7RA#ZH`"$S*(`AM2]IA?P'*342I`22(;-ILS@`[`&F)VQ%^;,ILUH
- MR,D`T.88I=1I"(74Z.`+D--@H`"YM1?)__`DH@+=L1?P&,H0^)BJO;87G;47
- MZ.`"D/6I_XVW%XVT%\C``Y#5H`"YL1?)__`FH@+=M1?P&LH0^$BB`;VU%YVV
- M%\H0]ZD`C;07:(VU%Z`#R,`#D--@J?^-`-R-+]"M`=S-`=S0^,G_J7^-`-RI
- 9_XTOT&``````````````````````````````
- `
- end
- begin 640 keyscan64.boot
- M`0@."`H`1++"*#$X-BD`'0@4`(L@0;(Q(*<@-C``)0@>`$&R,0`Z""@`DR)+
- M15E30T%.-C0B+$0L,0!#"#(`B2`Q,`!@"#P`GB`T.3$U,JHUK#(U-B`@.B"/
- )("1#-3`P````````
- `
- end
- begin 640 keyscan64
- M`,5,Q,5,$,9,ZL4@ZO^ES-`IQLW0):D4A<VDTT;/KH<"L=&P$>;/A<X@).JQ
- M\XV'`JZ&`J7.28`@'.JE`2D0\`J@`(3`I0$)(-`(I<#0!J4!*1^%`2!9Q4Q^
- MZJD`C0#<K0'<S0'<T/C)__`Y(%C'D#D@6,8@6,>0,2"-QB"[QB#[QJF!A?6I
- MZX7VHO\L>,<P+:UYQ\G_T`>MC0+P(:E`A<NH;(\"J7^-`-RI_Z("G7G'RA#Z
- M(+7&HO^I`(UXQZE`A<NH3";K(.K%H@"]U<7P!B#2_^C0]6!+15E30T%.-C0@
- M24Y35$%,3$5$#0!XJ0F@Q8T4`XP5`ZDGH,:-&`.,&0-8H@*I_YUYQ\H0^JD`
- MC7C'8'BI,:#JC10#C!4#J4>@_HT8`XP9`UA@2(I(F$BI?XT-W:P-W3`?(`+]
- MT`-L`H`@O/8@X?_0#R`5_2"C_2`8Y2#JQ6P"H$QR_J+_H/^I_H7U3';&K0'<
- MS0'<T/B,`-Q)_YUMQS@F]:7UC0#<Z.`(D.-@`08'!X`0(`0!`0($(+7&H`.^
- M@<:];<<YA<;P%+F)Q@V-`HV-`KF%QDG_/6W'G6W'B!#>8*D`C8T"8*("J?^=
- M=<?*$/J@`(3UH@"&R[UMQ_`<I,M*D!)(AO:F]>`#L`:8G77'YO6F]FC(R0#0
- MYABERVD(A<OHX`B0TV"@`+EYQ\G_\"2B`MUUQ_`8RA#XF*J]>L>=><?HX`*0
- M]:G_C7O'C7C'R,`#D-6@`+EUQ\G_\":B`MUYQ_`:RA#X2*(!O7G'G7K'RA#W
- MJ0"->,=HC7G'H`/(P`.0TV"I_XT`W*T!W,T!W-#XR?^I?XT`W&``````````
- *````````````````
- `
- end
- begin 640 keyshow
- M`!-,"Q,``````````*F3(-+_(#83H@`@%Q0@5A.B"B`7%"!T$Z(4(!<4(/03
- MHAX@%Q0@3!1,$!-XH@"I_HT`W*T!W,T!W-#X2?^=`Q,X+@#<Z.`(D.I88'BB
- M!ZE_C0#<K0'<S0'<T/A)_YT#$SAN`-S*$.Q88'BI`(T"W*G_C0/<H`>I?X4#
- MI0.-`=RM`-S-`-S0^*+_C@'<2?^B!PH^`Q/*$/DX9@.($-VI_XT"W*D`C0/<
- M6&!XJ0"-`MRI_XT#W*`'J7^%`Z4#C0'<K0#<S0#<T/BB_XX!W$G_H@<*/@,3
- MRA#Y.&8#B!#=J?^-`MRI`(T#W%A@>*(`J?Z%`Z4#C0#<K0'<S0'<T/A)_YT#
- M$S@F`^C@")#F6&"@!(8$A`6B`+T#$R`V%!BE!&DHA020`N8%Z.`(D.I@A0*@
- 6!T8"J0!I,)$$B!#U8````````*(`8```
- `
- end
- ================================================================================
- In the Next Issue:
-
- Next Issue:
-
- Tech-tech - more resolution to vertical shift
-
- One time half of the demos had pictures waving horizontally on the width
- of the whole screen. This effect is named tech-tech and it is done using
- character graphics. How exactly and is the same possible with sprites ?
-
- THE DESIGN OF ACE-128/64
-
- Design of ACE-128/64 command shell environment (and kernel replacement). This
- will cover the organization, internal operation, and the kernel interface of
- the still-under-development but possibly catching-on kernel replacement for
- the 128 and 64. The article will also discuss future directions and designs
- for the ACE environment. ACE has a number of definite design advantages over
- other kernel replacements, and a few disadvantages as well.
-