__ __ ______ | ||__| ______ / __ \ ______ __ __ ______ ____ __ __ ____| | __ / __ \ | | |__|_/ __ \ | || | / __ \ / \ | || | / __ / || || |__|__|| | |____/ | | || || || |__| || ||__|| || | | | | || |`\____ \ | | |____\ | | || || || _____|| | W | || | | |__| || || |__| || |__| || |__| || || || |__| || | a | || | `\_______||__|`\______/'`\______/'`\______/'`\____/'`\______/'| /' D `\ /' _ _ _ __ ___ ____ ______________________________|/________| | _ _ _ __ ___ ____ _________________________________________/' The Journal of the Commodore Enthusiast I s s u e 3 : March 26, 1997 P R E A M B L E Welcome to the third issue of disC=overy, the Journal of the Commodore Enthusiast. We greet you proudly, the ones who still hold the beloved Commodore 8-bit machines in high regard and respect. We thank you from the bottom of our hearts and look forward to forging a solid productive relationship with the C= 8-bit community. - Mike Gordillo, Steven Judd, Ernest Stokes, George Taylor, and the authors of disC=overy. :::::::::::d:i:s:C=:o:v:e:r:y:::::::::::::::::i:s:s:u:e::3:::::::::::::::::::: ::::::::::::::::::::::T A B L E O F C O N T E N T S::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -Software Section- /S01 - "Rommaging around : $A480-$A856" $a480 by Stephen L. Judd /S02 - "A Closer Look at the VIC II's Output" $d000 by Adrian Gonzalez and George Taylor /S03 - "Innovation in the 90s : Revisiting The Super Hi-Res Flexible Line $d000 Interpretation Technique" by Roland Toegel, 'Count Zero', and George Taylor /S04 - "Defeating Digital Corrosion (AKA "Cracking") - A beginner's guide $dd00 to software archiving" by Jan Lund Thomsen /S05 - "A possibility to be explored : Real Time Video with the Ram Expansion $df00 Unit" by Nate Dannenberg /S06 - "A look into 'The Fridge'" $f00d by Stephen L. Judd /S07 - "C128 CP/M, trailblazer in a jungle of formats" 0100h by Mike Gordillo -Hardware Section- /H01 - "The X-10 Powerhouse, What is it?" by Dan Barber /H02 - "The Metal Shop" with 'XmikeX', David Wood, Marc-Jano Knopp and Daniel Krug -Legal Section- /L01 - Articles of Operation, Distribution, et al. :::::::::::d:i:s:C=:o:v:e:r:y:::::::::::::::::i:s:s:u:e::3:::::::::::::::::::: /S01::$A480:::::::::::::::::::S O F T W A R E::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Rommaging around : $A480-$A856 ---------------- by Stephen L. Judd (sjudd@nwu.edu) "Disassemble? ...Disassemble!!!" -- No. 5, 'Short Circuit' This series has a simple goal: to completely disassemble and document the Commodore 64 ROMs. There is a nice ROM disassembly available on the internet, with the actual hex code displayed and HTML hypertexted. This version, on the other hand, is written in a more 'human readable' form, heavily commented and labeled, and is intended to complement the other. To that end, it does not duplicate very much information which is easily obtainable elsewhere (ftp.funet.fi, or Marko Makela's homepage, for instance). BLARG is a program which adds several hires graphics commands to BASIC, and was the main motivation to finally get started on this project. This article will thus focus on the BASIC routines from $A480 (Main Loop) to $A856 (END), with a goal of understanding how to add new commands to BASIC. The first part of the article gives an overview of BASIC and its internal workings, and sets up some of the things used later on. The second part discusses the parts of BASIC which relate specifically to the disassembled ROM, with a focus on the vectored routines. The third part gives a brief overview of BLARG. The ROM source, BLARG source, and BLARG binaires are all included. These files are all available at http://stratus.esam.nwu.edu/~judd/fridge About the ROM listings: I did them up in Merlinesque format of course. Probably the only unfamiliar thing is the concept of global and local lables. Local labels are prefixed with a colon, and their definition is only valid between two global lables. Otherwise they may be redefined. Thus a piece of code like PROC1 BNE :CONT INC TEMP1 :CONT RTS PROC2 BNE :CONT INY :CONT DEC TEMP1 RTS contains two global labels (PROC1 and PROC2). PROC1 will branch to the RTS instruction and PROC2 will branch to the DEC TEMP1 instruction, since the :CONT label is redefined once the global label PROC2 appears. The bottom line is that if you see a branch to a local label, that label is nearby and after the last global label. BASIC overview -------------- Before starting, there are some important things to know about BASIC. When you type in a line of text, and hit [RETURN], that text is entered into a text buffer located at $0200. The text from this buffer is then tokenized by the BASIC interpreter. If it is an immediate mode command (doesn't start with a line number) that command is then executed, otherwise it is stored in memory as a BASIC program line. BASIC lines are stored in memory as follows: ______________________________ _______________________ ...__ / \ / \ \ 0 [link] [line #] [Basic line] [0] [link] [line #] [Basic] [0] ... [0] [0] | | | | | | | | | | | Link to next line | | | | | | | | | End of line marker | | | | | | | Tokenized line of basic program | | | | | Two byte (lo,hi) line number | | | Two byte (lo,hi) pointer to next line in program | Beginning of program A zero marks the beginning of the program, which normally begins at $0800 (2048). The next two bytes are the address in (lo,hi) form of the next line in the program. The following two bytes are the line number for the current line. Then the tokenized line of BASIC follows; a null byte marks the end of the line. The next line follows immediately. A line link of value 0 (actually only the high byte needs to be 0) marks the end of the program. Thus a program like 10 PRINT "HELLO" will in memory look like 2048 0 ;Zero byte at beginning 2049 15 ;Next link = $080F = 15+8*256 = 2063 2050 8 2051 10 ;Line number = 10 + 0*256 = 10 2052 0 2053 153 ;PRINT token 2054 32 ;space 2055 34 " ;quote 2056 72 H ;PETSCII string 2057 69 E 2058 76 L 2059 76 L 2060 79 O 2061 34 " 2062 0 ;end of line 2063 0 ;end of program 2064 0 Variables begin immediately following the end of the program. Strings are stored beginning at the top of memory ($9FFF) and work their way downwards towards the variables. Another very important feature of BASIC is that the important routines are vectored. These are indeed filled vectors -- filled with the address of the routines in question. The BASIC indirect vector table is located at $0300-$030B: IERROR $0300-$0301 Vector to print BASIC error message routine IMAIN $0302-$0303 Vector to main BASIC program loop ICRNCH $0304-$0305 Vector to CRUNCH routine (tokenize ASCII text) IQPLOP $0306-$0307 Vector to QPLOP routine (tokens -> ASCII) IGONE $0308-$0309 Vector to GONE, executes BASIC tokens IEVAL $030A-$030B Vector to routine which evaluates single-term math expression Calls to these routines are vectored through these addresses via an indirect JMP -- they are called using JMP (IMAIN) for instance. These vectors may then be redirected to new routines, and so they provide a smooth way of adding new keywords to BASIC. CRUNCH is used to tokenize lines of text when they are entered. QPLOP is used by LIST to print tokens to ASCII text. GONE is used to execute tokens. Before modifying these vectors, keep in mind that many programs, such as JiffyDOS, have already modified them! Finally, the routines CHRGET and CHRGOT are very important in BASIC. They are copied into zero page when the system first starts up, and are located at $0073: $73 CHRGET INC TXTPTR ;Increment low byte $75 BNE CHRGOT $77 INC TXTPTR+1 ;High byte if necessary $79 CHRGOT LDA ;Entry here doesn't increment TXTPTR $7A TXTPTR $0207 ;low byte, high byte -- the LDA operand. ;Generally points at BASIC or points ;at input buffer at $0200 when in ;immediate mode. $7C POINTB CMP #$3A ;Set carry if > ASCII 9 $7E BCS EXIT ;Exit if not a numeral $80 CMP #$20 ;Check for ASCII space $82 BEQ CHRGET ;...skip space and move to next char $84 SEC $85 SBC #$30 ;Digits 0-9 are ASCII $30-$39 $87 SEC $88 SBC #$D0 ;Carry set if < ASCII 0 ($30) $8A EXIT RTS On exit, the accumulator contains the character that was read; Carry clear means character is ASCII digit 0-9, Carry set otherwise; Zero set if character is a statement terminator 0 or an ASCII colon ($3A), otherwise zero clear. Wedge programs make this their entry point -- by redirecting CHRGET and CHRGOT a program can look at the input and act accordingly. Scanning text can get awfully slow, which is why most wedge programs you see use a single character to prefix wedge commands. Finally, a list of BASIC tokens is a handy thing to have, along with the address of the routine which executes the statement: $80 END 43057 $A831 $81 FOR 42818 $A742 $82 NEXT 44318 $AD1E $83 DATA 43256 $A8F8 $84 INPUT# 43941 $ABA5 $85 INPUT 43967 $ABBF $86 DIM 45185 $B081 $87 READ 44038 $AC06 $88 LET 43429 $A9A5 $89 GOTO 43168 $A8A0 $8A RUN 43121 $A871 $8B IF 43304 $A928 $8C RESTORE 43037 $A81D $8D GOSUB 43139 $A883 $8E RETURN 43218 $A8D2 $8F REM 43323 $A93B $90 STOP 43055 $A82F $91 ON 43339 $A94B $92 WAIT 47149 $B82D $93 LOAD 57704 $E168 $94 SAVE 57686 $E156 $95 VERIFY 57701 $E165 $96 DEF 46002 $B3B3 $97 POKE 47140 $B824 $98 PRINT# 43648 $AA80 $99 PRINT 43680 $AAA0 $9A CONT 43095 $A857 $9B LIST 42652 $A69C $9C CLR 42590 $A65E $9D CMD 43654 $AA86 $9E SYS 57642 $E12A $9F OPEN 57790 $E1BE $A0 CLOSE 57799 $E1C7 $A1 GET 43899 $AB7B $A2 NEW 42562 $A642 $A3 TAB( ;Keywords which never begin a statement $A4 TO $A5 FN $A6 SPC( $A7 THEN $A8 NOT $A9 STEP $AA + 47210 $B86A ;Math operators $AB - 47187 $B853 $AC * 47659 $BA2B $AD / 47890 $BB12 $AE ^ 49019 $BF7B $AF AND 45033 $AFE9 $B0 OR 45030 $AFE6 $B1 > 49076 $BFB4 $B2 = 44756 $AED4 $B3 < 45078 $B016 $B4 SGN 48185 $BC39 ;Functions $B5 INT 48332 $BCCC $B6 ABS 48216 $BC58 $B7 USR 784 $0310 $B8 FRE 45949 $B37D $B9 POS 45982 $B39E $BA SQR 49009 $BF71 $BB RND 57495 $E097 $BC LOG 47594 $B9EA $BD EXP 49133 $BFED $BE COS 57956 $E264 $BF SIN 57963 $E26B $C0 TAN 58036 $E2B4 $C1 ATN 58126 $E30E $C2 PEEK 47117 $B80D $C3 LEN 46972 $B77C $C4 STR$ 46181 $B465 $C5 VAL 47021 $B7AD $C6 ASC 46987 $B78B $C7 CHR$ 46828 $B6EC $C8 LEFT$ 46848 $B700 $C9 RIGHT$ 46892 $B72C $CA MID$ 46903 $B737 $CB GO ;Makes GO TO legal. BASIC ROM --------- The BASIC ROM begins at $A000: $A000-$A001 Cold start vector $A002-$A003 Warm start vector $A004-$A00B ASCII text "CBMBASIC" $A00C-$A051 Statement dispatch table This is the first of several important tables used by BASIC. When a new BASIC statement is to be executed the interpreter looks here to find the address of the statement routine. Each entry is a two-byte vector containting the routine address minus one. The address is pushed onto the stack, so that the next RTS will jump to the correct location. The addresses are in token order, beginning with token $80 (END) and ending with token $A2 (NEW): $A052-$A07F Function dispatch vector table This is another table of two-byte vectors for BASIC functions -- that is, commands which are followed by an argument inside of parenthesis, for example INT(3.14159). The entries are again in token order beginning with token $B4 (SGN) and ending with token $CA (MID$). $A080-$A09D Operator dispatch vector table This table is for math operators, beginning with token $AA (+) and ending with token $B3 (<). $A09E-$A19D List of keywords This is a table of all the reserved BASIC keywords. The high bit of the last character set, so it is easy to detect the end of a keyword. The keywords are listed in token order, so to find the token corresponding to a given keyword the BASIC interpreter simply moves down this list, counting as it goes. If a match occurs, then the token value is simply the counter value. The table is searched using SBC instead of CMP. If the result of the subtraction is $80 -- keywords end with the high bit set -- then a valid keyword has been found. When input is placed into the input buffer at $0200, character codes 192-223 are used for shifted characters. From this it should be clear why the keyboard shortcuts work, for instance typing pO instead of poke. It should also be clear why poK will also work, but pokE will not work. $A19E-$A327 ASCII text of BASIC error messages (dextral character inverted) $A328-$A364 Error message vector table $A365-$A389 Miscellaneous messages (null-terminated) $A38A-$A47F Some BASIC routines, to be be disassembled at a later date $A480 Main loop Since the goal is to get a good enough understanding of BASIC to add new keywords, the main program loop is a good place to start. This routine is vectored through IMAIN at $0302. It gets a line of input from the keyboard, checks for a line number, and processes the line appropriately. When I started programming BLARG, I had no idea what routines like CRUNCH were expected to return -- is the Y register expected to have a certain value upon exit? Or maybe a certain variable needs to be set up so that another routine may reference it? Thus it is a good idea to begin at the main loop and see how a line is normally processed. The disassembly listing begins at this point. Other important vectored routines are: $A579 CRUNCH This routine goes through the BASIC text buffer at $0200 and tokenizes any keywords which aren't in quotes. As the routine begins to scan the input buffer it discards any characters which have their high bit set, such as shifted characters. (This is only in the initial search for keywords -- shifted characters within quotes or as part of a keyword are taken care of by another routine). As a practical matter, this means that any routine which adds extra tokens must call this routine _first_, since it will just skip over custom tokens otherwise! When it is finished processing the input buffer, the input buffer contains the tokenized line, terminated by a null byte, _and_ with another null byte at the end of the line +2 (much like the terminating byte which marks the end of a program). See the disassembly and blarg source for other things which are set up. $A717 QPLOP This is the part of the LIST routine (which begins at $A69C) which converts tokens into ASCII characters and prints them to the screen. $A7E4 GONE This is the routine which gets the next token (every statement begins with a token or an implied LET) and executes the appropriate command. Invalid tokens generate a SYNTAX ERROR. Character values less than 128 cause LET to be executed. The IF/THEN routine actually bypasses the IGONE vector and jumps directly into this routine, which means that lines like IF A=0 THEN MYCOMMAND will generate a syntax error. BLARG currently has no mechanism for getting around this (because I did not discover this until recently, and the article deadline was several days ago :), except to place a colon after the THEN, i.e. IF A=0 THEN:MYCOMMAND. Apparently some programs get around this by defining their own IF statement; probably the best way to get around it is to redirect the IERROR vector. When an error is generated, check to see if it is a custom token, then check to see if it was preceded by a THEN token. BLARG ----- By now, we have all the necessary information to add new keywords to BASIC. To that end I present BLARG, which adds several hires bitmap commands to BASIC. As a bonus, BLARG can take advantage of a SuperCPU if you have one. These commands are much faster than the 128's BASIC7.0 commands and in my quite biased opinion much more intuitive. The CIRCLE and LINE commands are adaptations of my routines from C=Hacking; look there for more info on the actual routines. Some things in BLARG are done a little differently than their corresponding BASIC routines, in large part because I wrote some of the routines before disassembling the BASIC ones. BLARG documentation is below. References ---------- I have found Mapping the 64 to be an invaluable reference, along with "Programming the Commodore 64", by Raeto West. ftp.funet.fi has some good documentation, and I use Marko Makela's web page quite often at http://www.hut.fi/~msmakela -- there are many useful documents there, including a complete html cross-referenced ROM listing. * * A480-A856 (MAIN-END) * Basic ROM disassembly starting with main loop * * Stephen L. Judd 1997 * * * Labels are at the end of the listing, along with a list * of major routines and their addresses. * * * MAIN -- Main loop, receives input and executes * immediately or stores as a program line. * * $A480 JMPMAIN JMP (IMAIN) ;Main loop, below ;Vectored through IMAIN, so it ;may be redirected. MAIN JSR INLIN ;Input a line from keyboard STX TXTPTR STY TXTPTR+1 JSR CHRGET ;Get 1st char out of buffer TAX BEQ JMPMAIN ;Empty line LDX #$FF ;Signal that BASIC is in immediate STX CURLIN+1 ;mode. BCC MAIN1 ;CHRGET clears C when digit is read JSR CRUNCH ;Tokenize line JMP JMPGONE ;Execute line * * MAIN1 -- Add or replace a line of program text. * * TEMP2 points to the current line to be deleted * TEMP1 will point to the start of memory to be copied * INDEX1 will point to the destination for the copy * $A49C MAIN1 JSR LINGET ;Convert ASCII to binary line number JSR CRUNCH ;Tokenize buffer STY COUNT ;Y=length of line JSR FINDLINE ;Find the address of the line number BCC :NEWLINE LDY #$01 ;Replace line of text LDA (TEMP2),Y ;$5F = pointer to line STA TEMP1+1 ;(TEMP1) = xx, Hi byte of next line LDA VARTAB ;End of BASIC program STA TEMP1 ;(TEMP1) = basic end, high next LDA TEMP2+1 STA INDEX1+1 ;(INDEX1) = xx, Hi byte current line LDA TEMP2 ;Compute -length of current line DEY SBC (TEMP2),Y ;Current address - next address CLC ADC VARTAB ;end - (length of line to be deleted) STA VARTAB STA INDEX1 ;(INDEX1) = bytes to delete, high current LDA VARTAB+1 ;Back up end of program if necessary ADC #$FF STA VARTAB+1 SBC TEMP2+1 ;Number of pages of memory to move TAX SEC LDA TEMP2 ;Pretty confusing, eh? SBC VARTAB TAY ;256 - number of bytes to move BCS :CONT1 INX DEC INDEX1+1 :CONT1 CLC ADC TEMP1 ;old basic end - number of bytes to move BCC :CONT2 DEC TEMP1+1 CLC :CONT2 LDA (TEMP1),Y ;Delete old line, fall through to create STA (INDEX1),Y ;new line. INY BNE :CONT2 INC TEMP1+1 INC INDEX1+1 DEX BNE :CONT2 :NEWLINE JSR RESCLR ;Reset variables and TXTPTR JSR LINKPRG ;Relink program lines LDA BUF BEQ JMPMAIN ;Empty statement -- nothing to do CLC LDA VARTAB ;Start of variables STA $5A ADC COUNT STA $58 ;Start of vars after line is added LDY VARTAB+1 STY $5B BCC :CONT3 INY :CONT3 STY $59 JSR MALLOC ;Open up some space in memory LDA LINNUM ;Number of line to be added LDY LINNUM+1 STA H01FE ;Rock bottom on the stack. STY H01FF LDA STREND ;Bottom of strings to top of variables LDY STREND+1 STA VARTAB STY VARTAB+1 LDY COUNT ;Number of chars in BUF DEY :LOOP LDA BUF-4,Y ;Copy buffer contents into program STA (TEMP2),Y DEY BPL :LOOP JSR RESCLR JSR LINKPRG JMP JMPMAIN ;Wheeeeeeee... * * LINKPRG -- Relink lines of program text * * $A533 LINKPRG LDA TXTTAB ;Start of BASIC text LDY TXTTAB+1 STA TEMP1 STY TEMP1+1 CLC :LOOP1 LDY #$01 LDA (TEMP1),Y BEQ :RTS ;0 means end of program LDY #$04 ;Skip link, line number, and 1st char :LOOP2 INY LDA (TEMP1),Y BNE :LOOP2 ;Find end of line INY TYA ADC TEMP1 ;Add offset to pointer to get address TAX ;of next line LDY #$00 STA (TEMP1),Y ;And store in link LDA TEMP1+1 ADC #$00 INY STA (TEMP1),Y STX TEMP1 ;Move to next line STA TEMP1+1 BCC :LOOP1 ;Keep on truckin'! :RTS RTS * * INLIN -- Input a line from keyboard to buffer * * $A560 INLIN LDX #$00 :LOOP JSR HE112 ;BASIC's way of calling Kernal routines CMP #$0D BEQ :DONE STA BUF,X INX CPX #$59 ;Maximum buffer size = 88 chars BCC :LOOP LDX #$17 ;STRING TOO LONG error JMP ERROR :DONE JMP HAACA ;Part of the PRINT routine * * CRUNCH -- Tokenize a line of text contained in the input buffer * * $A579 * * On exit: * Y = last character of crunched text + 4 * X = last char of input buffer read in. * TEXTPTR = $01FF * GARBFL = 00 if colon was read, $49 if DATA statement, * 04 otherwise. * ENDCHR = $22 (if quote was found) or 0 (if REM) * COUNT = Last token read - 128 (contrary to what * Mapping the 64 says) * TEMP1 = More or less random * BUF = Tokenized text, followed by a 00, random byte, and 00 * CRUNCH ;$A579 JMP (ICRNCH) LDX TXTPTR ;Low byte LDY #$04 STY GARBFL MAINLOOP LDA BUF,X ;Search input buffer for text BPL :GOTCHAR ;characters or #$FF CMP #$FF BEQ STALOOP INX BNE MAINLOOP :GOTCHAR CMP #$20 ;Is it a space? BEQ STALOOP STA ENDCHR CMP #$22 ;Is it a quote? BEQ QUOTE2 BIT GARBFL ;This either equals 00 if a ;colon was hit, $49 if a DATA ;statement was found, and #04 ;initially. Thus, it prints the ;text within DATA statements. BVS STALOOP CMP #'?' ;Short print BNE :CONT1 LDA #$99 ;Print token BNE STALOOP :CONT1 CMP #'0' ;Check for a number, BCC :NOTNUM CMP #'<' ;colon, or semi-colon BCC STALOOP :NOTNUM STY TEMP1 ;So, search for a keyword LDY #$00 STY COUNT DEY STX TXTPTR DEX FINDWORD INY ;Find keyword INX CMPWORD LDA BUF,X ;Match against keyword table SEC SBC RESLST,Y BEQ FINDWORD CMP #$80 ;High bit of last char is set ;Also means p shift-O gives short ;version of POKE (for instance), ;as should po shift-K. BNE NEXTWORD ORA COUNT ;A now contains token value STABUF LDY TEMP1 ;Crunched text index STALOOP INX ;Store text in crunched buffer INY STA BUF-5,Y LDA BUF-5,Y ;Goofy -- use AND #$FF or CMP #0 BEQ ALLDONE ;Zero byte terminates string SEC SBC #$3A ;Is it a colon? BEQ :COLON CMP #$49 ;Was it $83, a DATA statement? BNE :SKIP :COLON STA GARBFL :SKIP SEC SBC #$55 ;Was it $8F, a REM statement? BNE MAINLOOP STA ENDCHR ;If REM, then read rest of line QUOTE LDA BUF,X ;Look for matching quote char BEQ STALOOP ;or end of statement, and embed CMP ENDCHR ;text directly. BEQ STALOOP QUOTE2 INY STA BUF-5,Y INX BNE QUOTE NEXTWORD LDX TXTPTR ;Skip to next keyword INC COUNT ;Next token :LOOP INY LDA RESLST-1,Y ;Find last char BPL :LOOP LDA RESLST,Y BNE CMPWORD LDA BUF,X BPL STABUF ALLDONE STA BUF-3,Y DEC TXTPTR+1 LDA #$FF STA TXTPTR RTS * * FINDLINE * Search for the line number contained in $14. If found, set $5F * to link address and set carry. Carry clear means line number * not found. * * $A613 FINDLINE LDA TXTTAB ;Start of program text LDX TXTTAB+1 :LOOP LDY #$01 STA TEMP2 STX TEMP2+1 LDA (TEMP2),Y BEQ :EXIT ;Exit if at end of program INY INY LDA LINNUM+1 ;High byte CMP (TEMP2),Y BCC :RTS ;Less than -> line doesn't exist BEQ :CONT1 DEY BNE :CONT2 ;...always taken :CONT1 LDA LINNUM ;Compare low byte DEY CMP (TEMP2),Y BCC :RTS ;Punt when past line number BEQ :RTS ;Success! :CONT2 DEY ;Get next line link LDA (TEMP2),Y TAX DEY LDA (TEMP2),Y BCS :LOOP :EXIT CLC :RTS RTS * Perform NEW * $A642 NEW BNE *-2 ;RTS above LDA #$00 TAY STA (TXTTAB),Y ;Zero out first two bytes of program INY STA (TXTTAB),Y LDA TXTTAB CLC ADC #$02 STA VARTAB ;Move end of BASIC to begin+2 LDA TXTTAB+1 ADC #$00 STA VARTAB+1 RESCLR JSR RUNC ;Reset TXTPTR LDA #$00 * Perform CLR (Calcium Lime and Rust remover!) * $A65E CLEAR BNE CLEAREND JSR CLALL ;Close files LDA MEMSIZ ;Highest address used by BASIC LDY MEMSIZ+1 STA FRETOP ;Bottom of string text storage STY FRETOP+1 LDA VARTAB ;End of BASIC program/variable start LDY VARTAB+1 STA ARYTAB ;Start of array storage STY ARYTAB+1 STA STREND ;End of array storage/start of free RAM STY STREND+1 JSR RESTORE LDX #$19 STX TEMPPT ;Temporary string stack PLA TAY PLA LDX #$FA ;Reset stack TXS PHA ;Restore correct return address TYA PHA LDA #$00 STA OLDTXT+1 ;Address of current basic statement STA SUBFLG ; CLEAREND RTS * * RUNC -- reset current text character pointer to the * beginning of program text. * * $A68E RUNC CLC LDA TXTTAB ADC #$FF STA TXTPTR LDA TXTTAB+1 ADC #$FF STA TXTPTR+1 RTS * * LIST -- perform LIST * entered via return from CHRGET * * $A69C LIST BCC :SKIP ;Is next char an ASCII digit BEQ :SKIP ;Is next char ':' or 00 CMP #$AB ;Is next char '-' (token) BNE CLEAREND ;RTS, above :SKIP JSR LINGET ;Read decimal, convert to number JSR FINDLINE ;Find line number JSR CHRGOT ;Get char again BEQ :CONT ;statement terminator CMP #$AB BNE NEW-1 ;RTS JSR CHRGET ;Advance and get end of JSR LINGET ;range to list xx-xx BNE NEW-1 ;RTS :CONT PLA PLA LDA LINNUM ORA LINNUM+1 BNE LISTLOOP LDA #$FF ;Signal to list program to end STA LINNUM STA LINNUM+1 LISTLOOP LDY #$01 STY GARBFL LDA (TEMP2),Y BEQ ENDLIST JSR TESTSTOP ;Test for STOP key JSR HAAD7 ;part of PRINT routine INY LDA (TEMP2),Y TAX INY LDA (TEMP2),Y CMP LINNUM+1 ;Check to see if at last line BNE :CONT1 CPX LINNUM BEQ :CONT2 :CONT1 BCS ENDLIST :CONT2 STY FORPNT ;temporary storage JSR LINPRT ;Print line number LDA #$20 ;space LISTENT1 LDY FORPNT AND #$7F ;strip high bit LISTENT2 JSR HAB47 ;Prints char, AND #$FF CMP #$22 ;Look for a quote BNE :CONT LDA GARBFL EOR #$FF STA GARBFL :CONT INY ;$A700 BEQ ENDLIST ;256 character lines perhaps? LDA (TEMP2),Y BNE JMPPLOP ;Print the token TAY ;Get line link LDA (TEMP2),Y TAX INY LDA (TEMP2),Y STX TEMP2 STA TEMP2+1 BNE LISTLOOP ENDLIST JMP HE386 ;Exit through warm start * * QPLOP -- print BASIC tokens as ASCII characters. * * $A717 JMPPLOP JMP (IQPLOP) QPLOP BPL LISTENT2 ;Exit if not a token CMP #$FF BEQ LISTENT2 BIT GARBFL BMI LISTENT2 ;Exit if inside a quote SEC SBC #$7F ;Table offset+1 TAX STY FORPNT ;Temp storage LDY #$FF :LOOP1 DEX ;Traverse the keyword table BEQ :PLOOP :LOOP2 INY ;read a keyword LDA RESLST,Y BPL :LOOP2 BMI :LOOP1 :PLOOP INY ;Print out the keyword LDA RESLST,Y BMI LISTENT1 ;Exit if on last char JSR HAB47 ;Print char, AND #$FF BNE :PLOOP * * Perform FOR * * $A742 FOR LDA #$80 STA SUBFLG JSR LET JSR FINDFOR BNE :CONT TXA ADC #$0F TAX TXS :CONT PLA PLA LDA #$09 JSR CHKSTACK JSR ENDSTAT CLC TYA ADC TXTPTR PHA LDA TXTPTR+1 ADC #$00 PHA LDA CURLIN+1 PHA LDA CURLIN PHA LDA #$A4 JSR CHKCOM JSR HAD8D JSR FRMNUM LDA FACSGN ORA #$7F AND $62 STA $62 LDA #$8B LDY #$A7 STA TEMP1 STY TEMP1+1 JMP HAE43 LDA #$BC LDY #$B9 JSR MTOFAC JSR CHRGOT CMP #$A9 BNE HA79F JSR CHRGET JSR FRMNUM HA79F JSR SIGN JSR HAE38 LDA $4A PHA LDA FORPNT PHA LDA #$81 PHA * * NEWSTT -- Set up next statement for execution. * * $A7AE NEWSTT JSR TESTSTOP LDA TXTPTR LDY TXTPTR+1 CPY #$02 ;Text buffer? NOP BEQ :CONT STA OLDTXT STY OLDTXT+1 :CONT LDY #$00 ;End of last statement. LDA (TXTPTR),Y BNE HA807 ;branch into GONE LDY #$02 LDA (TXTPTR),Y ;End of program? CLC BNE :CONT2 JMP HA84B ;exit through END :CONT2 INY LDA (TXTPTR),Y ;Line number STA CURLIN INY LDA (TXTPTR),Y STA CURLIN+1 TYA ADC TXTPTR ;Advance pointer STA TXTPTR BCC JMPGONE INC TXTPTR+1 * * GONE -- Read and execute next statment * * $A7E1 JMPGONE JMP (IGONE) JSR CHRGET JSR GONE JMP NEWSTT * $A7ED GONE BEQ :RTS ;Exit if statement terminator SBC #$80 BCC :JMPLET ;If not a token then a variable CMP #$23 ;Tokens above $A3 never begin BCS CHKGOTO ;a statement (except GO TO) ASL ;Otherwise, get entry point TAY ;of the statement... LDA STATVEC+1,Y PHA LDA STATVEC,Y PHA JMP CHRGET ;... so CHRGET can RTS to it. :JMPLET JMP LET HA807 CMP #':' BEQ JMPGONE JMPSYN JMP SYNERR CHKGOTO CMP #$4B ;Is it "GO TO"? BNE JMPSYN JSR CHRGET LDA #$A4 ;Make sure next char is "TO" JSR CHKCOM ;token, and skip it. JMP GOTO * * Perform RESTORE * * $A81D RESTORE SEC LDA TXTTAB ;Set DATA pointer to start of program SBC #$01 LDY TXTTAB+1 BCS :CONT DEY :CONT STA DATPTR ;Address of current DATA item STY DATPTR+1 :RTS RTS TESTSTOP JSR STOP BCS END+1 * * END -- perform END * * $A831 END CLC BNE $A870 ;RTS LDA TXTPTR LDY TXTPTR+1 LDX CURLIN+1 ;Current line number INX BEQ :CONT1 ;Branch if in immediate mode STA OLDTXT ;Save statement address for CONT STY OLDTXT+1 LDA CURLIN LDY CURLIN+1 STA OLDLIN ;Restored by CONT STY OLDLIN+1 :CONT1 PLA ;Discard return address PLA * $A84B HA84B LDA #$81 ;Message at $A381 LDY #$A3 BCC :CONT2 JMP $A469 ;BREAK :CONT2 JMP $E386 ;BASIC warm start ENDCHR = $0008 ;See CRUNCH COUNT = $0B GARBFL = $000F ;Work byte SUBFLG = $0010 LINNUM = $14 TEMPPT = $0016 ;Next available space in temp string stack TEMP1 = $22 ;Temporary pointer/variable INDEX1 = $0024 TXTTAB = $002B ;Pointer to start of BASIC text ARYTAB = $002F ;Pointer to start of arrays STREND = $0031 ;End array storage/start free RAM FRETOP = $0033 ;End of string text/top free RAM MEMSIZ = $0037 ;Highest address used by BASIC CURLIN = $39 ;Current BASIC line number OLDTXT = $003D ;Pointer to current BASIC statement DATPTR = $0041 ;Pointer to current DATA item FORPNT = $0049 ;Temp pointer to FOR index variable TEMP2 = $5F FAC1 = $61 FACSGN = $0066 TEMP1 = $71 FBUFPT = $0071 CHRGET = $0073 ;Get next BASIC text char CHRGOT = $0079 ;Get current BASIC text char TXTPTR = $7A ;Text pointer H01FE = $01FE H01FF = $01FF BUF = $0200 ;Text input buffer IMAIN = $0302 ;System vectors ICRNCH = $0304 IQPLOP = $0306 IGONE = $0308 STATVEC = $A00C ;Statement dispatch vector table RESLST = $A09E ;Reserved keywords list FINDFOR = $A38A ;Find FOR on stack MALLOC = $A3B8 ;Make space for new line or var CHKSTACK = $A3FB ;Check for space on stack ERROR = $A437 ;General error handler END = $A831 ;Perform END GOTO = $A8A0 ;Perform GOTO ENDSTAT = $A906 ;Search for end of statement ;(00 or colon) LINGET = $A96B ;Convert ASCII decimal to 2-byte line number LET = $A9A5 ;Perform LET HAACA = $AACA HAAD7 = $AAD7 HAB47 = $AB47 FRMNUM = $AD8A ;Eval expression/check data type HAD8D = $AD8D HAE38 = $AE38 HAE43 = $AE43 CHKCOM = $AEFF ;Check for and skip comma SYNERR = $AF08 ;Print Syntax Error message MTOFAC = $BBA2 ;Move FP number from mem to FAC1 SIGN = $BC2B ;Sign of FAC1 in A LINPRT = $BDCD ;Print num X,A=lo,hi in ASCII HE112 = $E112 HE386 = $E386 STOP = $FFE1 ;Kernal CLALL = $FFE7 * * Major routine entry points * * $A480 JMPMAIN JMP (IMAIN) ;Main loop, below * $A483 MAIN JSR INLIN ;Input a line from keyboard * $A49C MAIN1 JSR LINGET ;Convert ASCII to binary line number * $A533 LINKPRG LDA TXTTAB ;Start of BASIC text * $A560 INLIN LDX #$00 * $A579 CRUNCH JMP (ICRNCH) * $A613 FINDLINE LDA TXTTAB ;Start of program text * $A642 NEW BNE *-2 ;RTS above * $A65E CLEAR BNE CLEAREND * $A68E RUNC CLC * $A69C LIST BCC :SKIP ;Is next char an ASCII digit * $A717 JMPPLOP JMP (IQPLOP) * $A71A QPLOP BPL LISTENT2 ;Exit if not a token * $A7AE NEWSTT JSR TESTSTOP * $A7E1 JMPGONE JMP (IGONE) * $A7ED GONE BEQ :RTS ;Exit if statement terminator * $A81D RESTORE SEC * $A831 END CLC BLARG -- Basic Language Graphics extension ----- version 1.0 2/10/97 BLARG is a little BASIC extension which adds some graphics commands to the normal C-64 BASIC. In addition it supports the SuperCPU optimization modes, as well as double buffering. Finally, it is free for use in your own programs, so feel free to do so! My goal was to write a BASIC extension which was compact, fast, and actually available for downloading :). Also, something that wasn't BASIC7.0. It doesn't have tons of features but I deem it to be Nifty. Anyways, these are adaptations of my algorithms from C=Hacking and such. They are not the most efficient implementations, but they are fairly zippy, and fairly well beat the snot out of BASIC7.0 commands! For instance, the times from moire3 (a line drawing test): Stock 64 1200 jiffies (1X) SCPU Mode 17 137 jiffies (9.1X) SCPU Mode 16 59 jiffies (20.2X) BASIC7.0 (1MHz) 4559 jiffies (1/3.5 X) So lines are nearly 4x faster on a stock 64 than a 128 running BASIC7.0. Running in mode16 on a SuperCPU is 77 times faster than BASIC7.0!!! Let's not even talk about BASIC7.0 circles. Well, what the heck, let's talk about them :) circletest1: Stock 64 360 jiffies (1x) SCPU Mode 17 50 jiffies (7x) SCPU Mode 16 22 jiffies (15.4x) BASIC7.0 17394 j :) 1/49x Bottom line: mode 16 circles are, if you can believe it, 790x faster than BASIC7.0 circles (and much better looking, especially at large radii -- BASIC7.0 circles are actually 128-sided polygons :-/ ). The total size of the program right now is a bit over 2k, and sits at $C000. To install the program, just load and run. To re-initialize the system (after a warm reset for instance) just type SYS49152. The command list is located near $C000, immediately followed by the routine addresses, in case you want to take a peek. A second program, BLARG$8000, is included in the archive. To use it, load ,8,1 and type SYS32768 to initialize. This program is included so that it may be loaded from a BASIC program. Several demo programs are included, and offer a good way of learning the commands (for instance, try typing ORIGIN 10,10 before running MOIRE3). Words to the wise: 1 - If you use a DOS extension like JiffyDOS then be sure to enclose filenames etc. in quotes (unless you want them to be tokenized, in which case feel free to omit quotes). 2 - If it looks like your machine has completely frozen try typing RUN-STOP, shift-clear, MODE 17 (Sometimes you can break the program before it can tell VIC where the screen is located). MODE16 and MODE17 will always fix stuff up, whereas run/stop-restore doesn't always do the trick. 3 - Always keep in mind that MODE 16,17, and 18 may hose string variables. 4 - IF/THEN bypasses the IGONE vector, so a statement like IF A=0 THEN GRON will fail. The statement IF A=0 THEN:GRON will work fine. Without further ado: GRON [COLOR] -- Turns graphics on. If the optional parameter COLOR is specified the bitmap is cleared and the colormap is initialized to that value, specifically, COLOR = 16*foreground color + background color Examples: GRON -- Turn on bitmap without clearing it. GRON20 -- Turn on bitmap, clear, purple bg, white fg GROFF -- Turns graphics off (sticks you back into text mode, or whatever mode you were in when you last called GRON) CLEAR [color] -- Clear current graphics buffer. CLEAR is part of the GRON routine, but will not set VIC or CIA#2. COLOR n -- COLOR 1 sets the drawing color to the foreground color; COLOR 0 sets it to background. ORIGIN CX, CY -- Sets the upper-left corner of the screen to have coordinates CX,CY. More precisely, commands will subtract CX,CY from coordinates passed into it. Among other things, this provides a mechanism for negative numbers to be handled -- LINE -10,0,40,99 will not work, but ORIGIN 10,0: LINE 0,0,50,99 will. This value is initialized to (0,0) whenever SYS49152 is called. PLOT X,Y -- Sticks a point at coordinates X,Y. (Actually at coordinates X-XC, Y-YC). X may be in the range 0..319 and Y may be 0..199; points outside this range will not be plotted. LINE X1,Y1,X2,Y2 -- Draws a line from X1,Y1 to X2,Y2 (subtracting XC,YC as necessary). X1 may be any 16-bit value and Y2 may be any 8-bit value. Coordinates off of the screen will simply not be plotted! CIRCLE XC,YC,R -- Draws a circle of radius R centered at XC,YC (translating as necessary). The algorithm is smart and can handle R=0..255 correctly (as far as I know!). I used a modified version of my algorithm, which makes very nice circles in my quite biased opinion, except for a few radii which come out a little ovalish. Oh well. MODE n -- New graphics MODE. MODE 16 -- SuperCPU mode. This moves the bitmap screen to $A000, the colormap to $8C00, the text screen to $8800, and sets the SuperCPU bank 2 optimization mode. $9000-$9FFF is totally unused :(. MODE 17 -- Normal mode. Bitmap->$E000, Colormap ->$CC00, text screen -> $0400. MODE 18 -- Double buffer mode. MODE16 memory configuration, no SCPU optimization. MODE16, MODE17, and MODE18 reset the BASIC memtop and stringtop pointers, so any defined strings may get hosed. (They also execute GROFF, and hence turn on the text screen). Any other MODE parameter will be set to the BITMASK parameter. What is BITMASK? Anything drawn to the screen is first ANDed with BITMASK. (Try MODE 85 sometime). (Although these numbers are reserved for future expansion). BUFFER n -- Set drawing buffer. When double-buffer mode is activated (MODE 18), both buffers are available for drawing and displaying. It is then possible to draw in one buffer while displaying the other. BUFFER n selects which buffer the PLOT,LINE,CIRCLE, and CLEAR commands will affect. If n=0 then it swaps the target buffer. Otherwise, n=odd references the buffer at $A000 and n=even selects the buffer at $E000. SWAP -- Swap displayed buffer. Assuming MODE18 is selected, SWAP simply selects which buffer is displayed on the screen; specifically, it flips between the two. SWAP only affects what is displayed on the screen; BUFFER only affects the target of the drawing commands. I think that's it :). ---------------------------------------------------------------------------- * * GRABAS * * A graphics extension for C-64 BASIC * * SLJ 12/29/96 (Completed 2/10/97) * v1.0 * ORG $0801 * Constants TXTPTR = $7A ;BASIC text pointer IERROR = $0300 ICRUNCH = $0304 ;Crunch ASCII into token IQPLOP = $0306 ;List IGONE = $0308 ;Execute next BASIC token CHRGET = $73 CHRGOT = $79 CHROUT = $FFD2 GETBYT = $B79E ;BASIC routine GETPAR = $B7EB ;Get a 16,8 pair of numbers CHKCOM = $AEFD NEW = $A642 CLR = $A65E LINNUM = $14 ;Number returned by GETPAR TEMP = $FF TEMP2 = $FB POINT = $FD Y1 = $05 X1 = LINNUM X2 = $02 Y2 = $04 DY = $26 DX = $27 BUF = $0200 ;Input buffer CHUNK1 = $69 ;Circle routine stuff OLDCH1 = $6A CHUNK2 = $6B OLDCH2 = $6C CX = $A3 CY = $A5 X = $6D Y = $6E RADIUS = $6F LCOL = $A6 ;Left column RCOL = $A7 TROW = $A8 ;Top row BROW = $A9 ;Bottom row DA :LINK ;link DA 1997 DFB $9E ;SYS TXT '2063:' DFB $A2 ;NEW DFB 00 ;End of line :LINK DA 0 ;end of program INSTALL LDA #PBEGIN STA POINT+1 LDA #PEND SBC #>PBEGIN STA TEMP2+1 LDA #$C0 ;Copy to $C000 STA X2+1 LDY #00 STY X2 :LOOP LDA (POINT),Y STA (X2),Y INY BNE :LOOP INC POINT+1 INC X2+1 DEC TEMP2+1 BNE :LOOP LDY TEMP2 :LOOP2 LDA (POINT),Y STA (X2),Y DEY CPY #$FF BNE :LOOP2 LDX #5 ;Copy CURRENT vectors :LOOP3 LDA ICRUNCH,X STA OLDCRNCH,X DEX BPL :LOOP3 JMP INIT TXT 'so, you want a secret message, eh? ' TXT 'narnia, narnia, narnia, awake.' TXT ' love. think. speak. be walking trees.' TXT ' be talking beasts. be divine waters.' TXT 'stephen l. judd wuz here 1/20/97' PBEGIN ORG $C000 * * Init routine -- modify vectors * and set up values. * INIT LDX #5 ;Copy vectors :LOOP LDA :TABLE,X STA ICRUNCH,X DEX BPL :LOOP INX STX ORGX STX ORGY JMP MODE17 ;Mode 17 :TEMP DFB 05 :TABLE DA CRUNCH DA LIST DA EXECUTE JMPCRUN DFB $4C ;JMP OLDCRNCH DS 2 ;Old CRUNCH vector OLDLIST DS 2 OLDEXEC DS 2 * * Keyword list * Keywords are stored as normal text, * followed by the token number. * All tokens are >128, * so they easily mark the end of the keyword * KEYWORDS TXT 'plot',E0 TXT 'line',E1 TXT 'circle',E2 TXT 'gr',91,E3 ;grON TXT 'groff',E4 ;Graphics off TXT 'mode',E5 DFB $B0 ;OR TXT 'igin',E6 TXT 'clear',E7 ;Clear bitmap TXT 'buffer',E8 ;Set draw buffer TXT 'swap',E9 ;Swap foreground and background TXT 'col',B0,EA ;Set color DFB 00 ;End of list * * Table of token locations-1 * Subtract $E0 first * Then check to make sure number isn't greater than NUMWORDS * TOKENLOC :E0 DA PLOT-1 :E1 DA LINE-1 :E2 DA CIRCLE-1 :E3 DA GRON-1 :E4 DA GROFF-1 :E5 DA MODE-1 :E6 DA ORIGIN-1 :E7 DA CLEAR-1 :E8 DA BUFFER-1 :E9 DA SWAP-1 :EA DA COLOR-1 HITOKEN EQU $EB * * CRUNCH -- If this is one of our keywords, then tokenize it * CRUNCH JSR JMPCRUN ;First crunch line normally LDY #05 ;Offset for KERNAL ;Y will contain line length+5 :LOOP STY TEMP JSR ISWORD ;Are we at a keyword? BCS :GOTCHA :NEXT JSR NEXTCHAR BNE :LOOP ;Null byte marks end STA BUF-3,Y ;00 line number LDA #$FF ;'tis what A should be RTS ;Buh-bye * Insert token and crunch line :GOTCHA LDX TEMP ;If so, A contains opcode STA BUF-5,X :MOVE INX LDA BUF-5,Y STA BUF-5,X ;Move text backwards BEQ :NEXT INY BPL :MOVE * * ISWORD -- Checks to see if word is * in table. If a word is found, then * C is set, Y is one past the last char * and A contains opcode. Otherwise, * carry is clear. * * On entry, TEMP must contain current * character position. * ISWORD LDX #00 :LOOP LDY TEMP :LOOP2 LDA KEYWORDS,X BEQ :NOTMINE CMP #$E0 BCS :RTS ;Tokens are >=$E0 CMP BUF-5,Y BNE :NEXT INY ;Success! Go to next char INX BNE :LOOP2 :NEXT INX LDA KEYWORDS,X ;Find next keyword CMP #$E0 BCC :NEXT INX BNE :LOOP ;And check again :NOTMINE CLC :RTS RTS * * NEXTCHAR finds the next char * in the buffer, skipping * spaces and quotes. On * entry, TEMP contains the * position of the last spot * read. On exit, Y contains * the index to the next char, * A contains that char, and Z is set if at end of line. * NEXTCHAR LDY TEMP :LOOP INY LDA BUF-5,Y BEQ :DONE CMP #$8F ;REM BNE :CONT LDA #00 :SKIP STA TEMP2 ;Find matching character :LOOP2 INY LDA BUF-5,Y BEQ :DONE CMP TEMP2 BNE :LOOP2 ;Skip to end of line BEQ :LOOP :CONT CMP #$20 ;Space BEQ :LOOP CMP #$22 ;Quote BEQ :SKIP :DONE RTS * * LIST -- patches the LIST routine * to list my tokens correctly. * LIST CMP #$E0 BCC :NOTMINE ;Not my token CMP #HITOKEN BCS :NOTMINE BIT $0F ;Check for quote mode BMI :NOTMINE SEC SBC #$DF ;Find the corresponding text TAX STY $49 LDY #00 :LOOP DEX BEQ :DONE :LOOP2 INY LDA KEYWORDS,Y CMP #$E0 BCC :LOOP2 INY BNE :LOOP :DONE LDA KEYWORDS,Y BMI :OUT JSR $FFD2 INY BNE :DONE :OUT CMP #$B0 ;OR BEQ :OR CMP #$E0 ;It might be BASIC token BCS :CONT ;e.g. GRON LDY $49 :NOTMINE AND #$FF JMP (OLDLIST) ;QPLOP :CONT LDY $49 JMP $A700 ;Normal exit :OR LDA #'o' ;For ORIGIN JSR CHROUT LDA #'r' JSR CHROUT INY BNE :DONE * * EXECUTE -- if this is one of my * tokens, then execute it. * EXECUTE JSR CHRGET PHP CMP #$E0 BCC :NOTMINE CMP #HITOKEN BCS :NOTMINE PLP JSR :DISP JMP $A7AE ;Exit through NEWSTT :DISP EOR #$E0 ASL ;Mult by two TAX LDA TOKENLOC+1,X PHA LDA TOKENLOC,X PHA JMP CHRGET ;Exit to routine :NOTMINE PLP JMP $A7E7 ;Normal routine * * PLOT -- plot a point! * ORGX DFB 00 ;Upper-left corner of the screen ORGY DFB 00 DONTPLOT DFB 01 ;0=Don't plot point, just compute ;coordinates (used by e.g. circles) PLOT JSR GETPAR ;Get coordinate pair LDA LINNUM ;Add in origin offset SEC SBC ORGX STA LINNUM BCS :CONT1 DEC LINNUM+1 BMI :ERROR ;Underflow SEC :CONT1 TXA SBC ORGY BCC :ERROR TAX CPX #200 ;Check range BCS :ERROR LDA LINNUM CMP #<320 LDA LINNUM+1 SBC #>320 BCC SETPOINT :ERROR RTS ;Just don't plot point *:ERROR LDX #14 * JMP (IERROR) SETPOINT ;Alternative entry point ;X=y-coord, LINNUM=x-coord * ;X is preserved * STX TEMP2 * STY TEMP2+1 ;On exit, X,Y are AND #$07 ;i.e. are set up correctly. TXA AND #248 STA POINT LSR LSR LSR ADC BASE ;Base of bitmap STA POINT+1 LDA #00 ASL POINT ROL ASL POINT ROL ASL POINT ROL ADC LINNUM+1 ADC POINT+1 STA POINT+1 TXA AND #7 TAY LDA LINNUM AND #248 CLC ;Overflow is possible! ADC POINT STA POINT BCC SETPIXEL INC POINT+1 SETPIXEL LDA LINNUM AND #$07 TAX LDA DONTPLOT BEQ :RTS LDA POINT+1 SEC SBC BASE ;Overflow check CMP #$20 BCS :RTS SEI ;Get underneath ROM LDA #$34 STA $01 LDA (POINT),Y EOR BITMASK AND BITTAB,X EOR (POINT),Y STA (POINT),Y LDA #$37 STA $01 CLI * LDX TEMP2 * LDY TEMP2+1 ;On exit, X,Y are AND #$07 ;i.e. are set up correctly. ;for more plotting :RTS RTS BITMASK DFB #$FF ;Set point BITTAB DFB $80,$40,$20,$10,$08,$04,$02,$01 *------------------------------- * Drawin' a line. A fahn lahn. * * To deal with off-screen coordinates, the current row * and column (40x25) is kept track of. These are set * negative when the point is off the screen, and made * positive when the point is within the visible screen. * Little bit position table BITCHUNK HEX FF7F3F1F0F070301 CHUNK EQU X2 OLDCHUNK EQU X2+1 * DOTTED -- Set to $01 if doing dotted draws (diligently) * X1,X2 etc. are set up above (x2=LINNUM in particular) * Format is LINE x2,y2,x1,y1 LINE JSR GETPAR STX Y2 LDA LINNUM STA X2 LDA LINNUM+1 STA X2+1 JSR CHKCOM JSR GETPAR STX Y1 :CHECK LDA X2 ;Make sure x1=y1? EOR #$FF ;Otherwise dy=y1-y2 ADC #$01 LDX #$88 ;DEY :DYPOS STA DY STX YINCDEC STX XINCDEC LDA X1 ;Sub origin from 1st point SEC SBC ORGX STA X1 LDA X1+1 SBC #00 STA X1+1 PHP ;Save carry flag STA TEMP ;Next compute column LDA X1 LSR TEMP ROR LSR TEMP ROR LSR TEMP ROR STA CX ;X-column PLP BCC :NEGX ;If negative, then fix up CMP #40 ;If past column 40, then punt! BCC :CONT1 RTS :NEGX LDA X1 ;coordinate start and count AND #$07 STA X1 LDA #00 STA X1+1 LDA CX :CONT1 LDA Y1 ;Now do the same for Y SEC SBC ORGY STA Y1 TAX ;X=y-coord PHP ;Save carry bit LSR LSR LSR STA CY ;Y-column (well, OK, row then) PLP BCC :NEGY ;If negative, then fix stuff up! SBC #25 ;Check if we are past bottom of BCC :CONT2 ;screen ORA #$80 ;Otherwise, 128+rows past 24 STA CY ;(for plot range checking) TXA AND #$07 ORA #8*24 ;Start in last row TAX BMI :CONT2 :NEGY ORA #$E0 ;Set high bits of column STA CY TXA AND #$07 TAX ;Start in 1st row :CONT2 LDA #00 STA DONTPLOT JSR SETPOINT ;Set up X,Y and POINT INC DONTPLOT LDA BITCHUNK,X STA OLDCHUNK STA CHUNK SEI ;Get underneath ROM LDA #$34 STA $01 LDX DY CPX DX ;Who's bigger: dy or dx? BCC STEPINX ;If dx, then... LDA DX+1 BNE STEPINX * * Big steps in Y * * To simplify my life, just use PLOT to plot points. * * No more! * Added special plotting routine -- cool! * * X is now counter, Y is y-coordinate * * On entry, X=DY=number of loop iterations, and Y= * Y1 AND #$07 STEPINY LDA #00 STA OLDCHUNK ;So plotting routine will work right LDA CHUNK SEC LSR ;Strip the bit EOR CHUNK STA CHUNK TXA BNE :CONT ;If dy=0 it's just a point INX :CONT LSR ;Init counter to dy/2 * * Main loop * YLOOP STA TEMP * JSR LINEPLOT LDA CX ;Range check ORA CY BMI :SKIP LDA (POINT),Y ;Otherwise plot EOR BITMASK AND CHUNK EOR (POINT),Y STA (POINT),Y :SKIP YINCDEC INY ;Advance Y coordinate CPY #8 BCC :CONT ;No prob if Y=0..7 JSR FIXY :CONT LDA TEMP ;Restore A SEC SBC DX BCC YFIXX YCONT DEX ;X is counter BNE YLOOP YCONT2 LDA (POINT),Y ;Plot endpoint EOR BITMASK AND CHUNK EOR (POINT),Y STA (POINT),Y YDONE LDA #$37 STA $01 CLI RTS YFIXX ;x=x+1 ADC DY LSR CHUNK BNE YCONT ;If we pass a column boundary... ROR CHUNK ;then reset CHUNK to $80 STA TEMP2 LDA CX BMI :CONT ;Skip if column is negative CMP #39 ;End if move past end of screen BCS YDONE LDA POINT ;And add 8 to POINT ADC #8 STA POINT BCC :CONT INC POINT+1 :CONT INC CX ;Increment column LDA TEMP2 DEX BNE YLOOP BEQ YCONT2 * * Big steps in X direction * * On entry, X=DY=number of loop iterations, and Y= * Y1 AND #$07 COUNTHI DFB 00 ;Temporary counter ;only used once STEPINX LDX DX LDA DX+1 STA COUNTHI LSR ;Need bit for initialization STA Y1 ;High byte of counter TXA BNE :CONT ;Could be $100 DEC COUNTHI :CONT ROR * * Main loop * XLOOP LSR CHUNK BEQ XFIXC ;If we pass a column boundary... XCONT1 SBC DY BCC XFIXY ;Time to step in Y? XCONT2 DEX BNE XLOOP DEC COUNTHI ;High bits set? BPL XLOOP XDONE LSR CHUNK ;Advance to last point JSR LINEPLOT ;Plot the last chunk EXIT LDA #$37 STA $01 CLI RTS * * CHUNK has passed a column, so plot and increment pointer * and fix up CHUNK, OLDCHUNK. * XFIXC STA TEMP JSR LINEPLOT LDA #$FF STA CHUNK STA OLDCHUNK LDA CX BMI :CONT ;Skip if column is negative CMP #39 ;End if move past end of screen BCS EXIT LDA POINT ADC #8 STA POINT BCC :CONT INC POINT+1 :CONT INC CX LDA TEMP JMP XCONT1 * * Check to make sure there isn't a high bit, plot chunk, * and update Y-coordinate. * XFIXY DEC Y1 ;Maybe high bit set BPL XCONT2 ADC DX STA TEMP LDA DX+1 ADC #$FF ;Hi byte STA Y1 JSR LINEPLOT ;Plot chunk LDA CHUNK STA OLDCHUNK LDA TEMP XINCDEC INY ;Y-coord CPY #8 ;0..7 is ok BCC XCONT2 STA TEMP JSR FIXY LDA TEMP JMP XCONT2 * * Subroutine to plot chunks/points (to save a little * room, gray hair, etc.) * LINEPLOT ;Plot the line chunk LDA CX ORA CY BMI :SKIP LDA (POINT),Y ;Otherwise plot EOR BITMASK ORA CHUNK AND OLDCHUNK EOR CHUNK EOR (POINT),Y STA (POINT),Y :SKIP RTS * * Subroutine to fix up pointer when Y decreases through * zero or increases through 7. * FIXY CPY #255 ;Y=255 or Y=8 BEQ :DECPTR :INCPTR ;Add 320 to pointer LDY #0 ;Y increased through 7 LDA CY BMI :CONT1 ;If negative, then don't update CMP #24 BCS :TOAST ;If at bottom of screen then quit LDA POINT ADC #<320 STA POINT LDA POINT+1 ADC #>320 STA POINT+1 :CONT1 INC CY RTS :DECPTR ;Okay, subtract 320 then LDY #7 ;Y decreased through 0 LDA CY BEQ :TOAST BMI :CONT2 CMP #$7F ;It is possible we just decreased BNE :C1 ;through row 25 LDA #24 STA CY ;In which case, set correct row :C1 LDA POINT SEC SBC #<320 STA POINT LDA POINT+1 SBC #>320 STA POINT+1 :CONT2 DEC CY RTS :TOAST PLA ;Remove old return address PLA JMP EXIT ;Restore interrupts, etc. * * CIRCLE draws a circle of course, using my * super-sneaky algorithm. * CIRCLE cx,cy,radius (16,8,8) * CIRCLE JSR GETPAR STX CY ;CX,CY = center LDA X1 SEC SBC ORGX STA CX STA X1 LDA X1+1 SBC #00 STA CX+1 STA X1+1 PHP ;Save carry LSR ;Compute which column we start LDA CX ;in ROR LSR LSR PLP BCS :CONT ;Underflow means negative column TAX LDA X1 ;Set X to first column AND #$07 STA X1 LDA #00 STA X1+1 TXA ORA #$E0 ;so set high bits :CONT STA RCOL STA LCOL BMI :SKIP CMP #40 ;Check for benefit of SETPOINT BCC :SKIP LDA X1 ;Set X in last column AND #$07 ORA #64-8 ;312+X AND 7 STA X1 LDA #1 STA X1+1 :SKIP JSR CHKCOM JSR GETBYT CIRCENT ;Alternative entry point STX Y STX RADIUS TXA BNE :C ;Skip R=0 LDX CY JMP SETPOINT ;Plot it as a point. :C CLC ADC CY BCS :BLAH SEC SBC ORGY BCS :C4 ;cy+y200 then set pointer to BCC :C2 ;last row, but set TROW CLC ;correctly :C3 TAY AND #$07 ORA #$C0 ;Last row, set Y1 correctly TAX TYA :C2 ROR LSR LSR STA TROW ;Top row LDA #00 STA DONTPLOT ;Don't plot points JSR SETPOINT ;Plot XC,YC+Y STY Y2 ;Y AND 07 LDA BITCHUNK,X STA CHUNK1 ;Forwards chunk STA OLDCH1 LSR EOR #$FF STA CHUNK2 ;Backwards chunk STA OLDCH2 LDA POINT STA TEMP2 ;TEMP2 = forwards high pointer STA X2 ;X2 = backwards high pointer LDA POINT+1 STA TEMP2+1 STA X2+1 LDA CY ;Now compute upper points SEC SBC ORGY BCS :CSET SEC ;We are so very negative SBC Y CLC BCC :BNEG :CSET SBC Y ;Compute CY-Y-ORGY :BNEG PHP TAX LSR ;Compute row LSR LSR STA BROW PLP BCS :CONT ORA #$E0 ;Make row negative STA BROW TXA AND #07 ;Handle underflow special! TAX :CONT JSR SETPOINT ;Compute new coords STY Y1 LDA POINT STA X1 ;X1 will be the backwards LDA POINT+1 ;low-pointer STA X1+1 ;POINT will be forwards SEI ;Get underneath ROM LDA #$34 STA $01 LDA Y LSR ;A=r/2 LDX #00 STX X ;y=0 * Main loop :LOOP INC X ;x=x+1 LSR CHUNK1 ;Right chunk BNE :CONT1 JSR UPCHUNK1 ;Update if we move past a column :CONT1 ASL CHUNK2 BNE :CONT2 JSR UPCHUNK2 :CONT2 ;LDA TEMP SEC SBC X ;a=a-x BCS :LOOP ADC Y ;if a<0 then a=a+y; y=y-1 TAX JSR PCHUNK1 JSR PCHUNK2 LDA CHUNK1 STA OLDCH1 LDA CHUNK2 STA OLDCH2 TXA DEC Y ;(y=y-1) DEC Y2 ;Decrement y-offest for upper BPL :CONT3 ;points JSR DECYOFF :CONT3 LDY Y1 INY STY Y1 CPY #8 BCC :CONT4 JSR INCYOFF :CONT4 LDY X CPY Y ;if y<=x then punt BCC :LOOP ;Now draw the other half * * Draw the other half of the circle by exactly reversing * the above! * NEXTHALF LSR OLDCH1 ;Only plot a bit at a time ASL OLDCH2 LDA RADIUS ;A=-R/2-1 LSR EOR #$FF :LOOP TAX JSR PCHUNK1 ;Plot points JSR PCHUNK2 TXA DEC Y2 ;Y2=bottom BPL :CONT1 JSR DECYOFF :CONT1 INC Y1 LDY Y1 CPY #8 BCC :CONT2 JSR INCYOFF :CONT2 LDX Y BEQ :DONE CLC ADC Y ;a=a+y DEC Y ;y=y-1 BCC :LOOP INC X SBC X ;if a<0 then x=x+1; a=a+x LSR CHUNK1 BNE :CONT3 TAX JSR UPCH1 ;Upchunk, but no plot :CONT3 LSR OLDCH1 ;Only the bits... ASL CHUNK2 ;Fix chunks BNE :CONT4 TAX JSR UPCH2 :CONT4 ASL OLDCH2 BCS :LOOP :DONE CIRCEXIT ;Restore interrupts LDA #$37 STA $01 CLI LDA #1 ;Re-enable plotting STA DONTPLOT RTS * * Decrement upper pointers * DECYOFF TAY LDA #7 STA Y2 LDA TROW ;First check to see if Y is in BEQ EXIT2 CMP #25 ;range (rows 0-24) BCS :SKIP LDA X2 ;If we pass through zero, then SEC SBC #<320 ;subtract 320 STA X2 LDA X2+1 SBC #>320 STA X2+1 LDA TEMP2 SEC SBC #<320 STA TEMP2 LDA TEMP2+1 SBC #>320 STA TEMP2+1 :SKIP TYA DEC TROW RTS EXIT2 PLA ;Grab return address PLA JMP CIRCEXIT ;Restore interrupts, etc. * Increment lower pointers INCYOFF TAY LDA #00 STA Y1 LDA BROW BMI :ISKIP ;If <0 then don't update pointer. CMP #24 ;If we hit bottom of screen then BEQ EXIT2 ;just quit LDA X1 CLC ADC #<320 STA X1 LDA X1+1 ADC #>320 STA X1+1 LDA POINT CLC ADC #<320 STA POINT LDA POINT+1 ADC #>320 STA POINT+1 :ISKIP TYA INC BROW RTS * * UPCHUNK1 -- Update right-moving chunk pointers * Due to passing through a column * UPCHUNK1 TAX JSR PCHUNK1 UPCH1 LDA #$FF ;Alternative entry point STA CHUNK1 STA OLDCH1 LDA RCOL BMI :DONE ;Can start negative LDA TEMP2 CLC ADC #8 STA TEMP2 BCC :CONT INC TEMP2+1 CLC :CONT LDA POINT ADC #8 STA POINT BCC :DONE INC POINT+1 :DONE TXA INC RCOL RTS * * UPCHUNK2 -- Update left-moving chunk pointers * UPCHUNK2 TAX JSR PCHUNK2 UPCH2 LDA #$FF STA CHUNK2 STA OLDCH2 LDA LCOL CMP #40 BCS :DONE LDA X2 SEC SBC #8 STA X2 BCS :CONT DEC X2+1 SEC :CONT LDA X1 SBC #8 STA X1 BCS :DONE DEC X1+1 :DONE TXA DEC LCOL RTS * * Plot right-moving chunk pairs for circle routine * PCHUNK1 LDA RCOL ;Make sure we're in range CMP #40 BCS :SKIP2 LDA CHUNK1 ;Otherwise plot EOR OLDCH1 STA TEMP LDA BROW ;Check for underflow BMI :SKIP LDY Y1 LDA (POINT),Y EOR BITMASK AND TEMP EOR (POINT),Y STA (POINT),Y :SKIP LDA TROW ;If CY+Y >= 200... CMP #25 BCS :SKIP2 LDY Y2 LDA (TEMP2),Y EOR BITMASK AND TEMP EOR (TEMP2),Y STA (TEMP2),Y :SKIP2 RTS * * Plot left-moving chunk pairs for circle routine * PCHUNK2 LDA LCOL ;Range check in X CMP #40 BCS :SKIP2 LDA CHUNK2 ;Otherwise plot EOR OLDCH2 STA TEMP LDA BROW ;Check for underflow BMI :SKIP LDY Y1 LDA (X1),Y EOR BITMASK AND TEMP EOR (X1),Y STA (X1),Y :SKIP LDA TROW ;If CY+Y >= 200... CMP #25 BCS :SKIP2 LDY Y2 LDA (X2),Y EOR BITMASK AND TEMP EOR (X2),Y STA (X2),Y :SKIP2 RTS * * GRON -- turn graphics on. If a number appears * afterwards, then initialize the colormap to that * number and clear the bitmap. * BASE DFB $E0 ;Address of bitmap, hi byte BANK DFB 0 ;Bank 3=default OLDBANK DFB $FF ;VIC old bank OLDD018 DFB 00 GRON LDA $D011 ;Skip if bitmap is already on. AND #$20 BNE CLEAR LDA $DD02 ;Set the data direction regs ORA #3 STA $DD02 LDA $DD00 PHA AND #$03 STA OLDBANK PLA AND #252 ORA BANK STA $DD00 LDA $D018 STA OLDD018 LDA #$38 ;Set color map to base+$1C00 STA $D018 ;bitmap to 2nd 8k LDA $D011 ;And turn on bitmap ORA #$20 STA $D011 CLEAR JSR CHRGOT ;See if there's a color BEQ GRONDONE JSR GETBYT ;Get the char CLEARCOL LDA #00 ;Low byte of base address STA POINT LDA BASE ;Colormap is at base-$14 SEC SBC #$14 STA POINT+1 TXA LDY #00 LDX #4 :LOOP STA (POINT),Y INY BNE :LOOP INC POINT+1 DEX BNE :LOOP LDA BASE ;Now clear bitmap STA POINT+1 LDX #32 TYA :LOOP2 STA (POINT),Y INY BNE :LOOP2 INC POINT+1 DEX BNE :LOOP2 GRONDONE RTS * GROFF -- Restore old values if graphics are on. GROFF LDA $D011 AND #$20 BEQ GDONE GSET LDA $DD02 ;Set the data direction regs ORA #3 STA $DD02 LDA $DD00 AND #$7C ORA OLDBANK STA $DD00 LDA OLDD018 STA $D018 LDA $D011 AND #$FF-$20 STA $D011 GDONE RTS * * COLOR -- Set drawing color * COLOR JSR GETBYT COLENT CPX #00 ;MODE enters here BEQ :C2 :C1 CPX #01 BNE :RTS LDX #$FF :C2 STX BITMASK :RTS RTS * * MODE -- catch-all command. Currently implemented: * 00 Erase (background color) * 01 Foreground color * 16 SuperCPU mode -- screen -> A000, etc. * 17 Normal mode * 18 Double buffer mode * * Anything else -> BITMASK * MODENUM DFB 17 ;Current mode MODE JSR GETBYT CPX #2 BCC COLENT :C16 CPX #16 BNE :C18 STX MODENUM :SET16 LDA #$A0 ;Bitmap -> $A000 STA BASE LDA #01 STA BANK ;Bank 2 STA OLDBANK LDA #$FF ;End of BASIC memory STA $37 STA $33 LDA #$87 STA $38 STA $34 LDA #$24 ;Screen mem -> $8800 STA OLDD018 JSR GSET ;Part of GROFF LDA #$88 STA 648 ;Tell BASIC where the screen is STA $D07E ;Enable SuperCPU regs STA $D074 ;Bank 2 optimization STA $D07F ;Disable regs RTS :C18 CPX #18 ;Double-buffer mode! BNE :C17 STX MODENUM JSR :SET16 ;Set up mode 16 STA $D07E STA $D077 ;Turn off optimization STA $D07F RTS :C17 CPX #17 BNE MODEDONE MODE17 STX MODENUM LDA #$E0 STA BASE LDA #00 ;Bank 3 STA BANK LDA #3 ;Bank 0 == normal bank STA OLDBANK LDA #$FF STA $37 STA $33 LDA #$9F STA $38 STA $34 LDA #$14 ;Screen mem -> $0400 STA OLDD018 JSR GSET ;Part of GROFF LDA #$04 STA 648 ;Tell BASIC where the screen is STA $D07E STA $D077 ;No optimization STA $D07F RTS MODEDONE STX BITMASK RTS * * BUFFER -- Sets the current drawing buffer to 1 or 2, * depending on arg being even or odd. If double- * buffer mode is not enabled then punt. * * Now, buffer=0 swaps draw buffers, even/odd otherwise. * BUFFER JSR GETBYT LDA MODENUM CMP #18 BNE :PUNT LDY #$A0 TXA BNE :CONT CPY BASE BNE :CONT LDA #1 :CONT LSR BCC :LOW ;even = low buffer LDY #$E0 ;odd = high buffer :LOW STY BASE :PUNT RTS * * SWAP -- Swap displayed buffers. MODE 18 must * be enabled first. * SWAP LDA MODENUM CMP #18 BNE :PUNT LDA $DD00 ;Ooooooohhh, real tough! EOR #$01 STA $DD00 :PUNT RTS * * ORIGIN -- Set upper-left corner of the screen to * new coordinate offset. * ORIGIN JSR GETBYT STX ORGX JSR CHKCOM JSR GETBYT STX ORGY RTS ORG ;re-org PEND ;To get that label right :) -- The BLARG distribution binary is available from 'The Fridge', Mr. Judd's archival web page on the internet at http://stratus.esam.nwu.edu/judd/fridge :::::::::::d:i:s:C=:o:v:e:r:y:::::::::::::::::i:s:s:u:e::3:::::::::::::::::::: /S02::$d000:::::::::::::::::::S O F T W A R E::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: A CLOSER LOOK AT THE VIC II'S OUTPUT by Adrian Gonzalez (DW/Style) & George Taylor (Repose/Style) ------------------------------------------------------------------------------ 1 2 3 4 5 6 7 123456789012345678901234567890123456789012345678901234567890123456789012345678 Contents -------- 0. Preface 1. Introduction 1.1 Target audience 1.2 The legal stuff 2. The NTSC Y/C video signal 2.1 A little history 2.2 A closer look at black and white TV 2.3 Let's talk about color 2.4 The chroma signal 3. Wrapup Bibliography 0. Preface ----------- This text was originally formatted to 78 columns for ease of use. This ensures compatibility with MSDOS "edit" with scrollbars, as well as 80 column readers that cause double spacing if the 81st character is a carriage return. The text is written in conversation style to ease the problem of dual authorship. Subsections not marked may be considered generic text and may have been written by either of us. You will not be able to view the ASCII diagrams in 40 columns. Major headings are marked with ---'s. 1. Introduction ---------------- DW: While working on an image conversion project, I stumbled upon the need to find RGB values for the c64's colors. My first idea was using two monitors and matching the colors "by eye", however, this turned out to be more difficult than I expected. After trying other methods I decided the next logical step was to analyze the c64's output and determine the RGB values from there. Simple enough, eh? The only problem was that I had no clue as to what the VIC-II's output looked like. After digging up some books on TV theory, I decided to embark on a quest to find RGB values for the c64's colors, and I found many interesting things along the way. This article is the result of many hours of research, programming, and taking measurements. I hope you find it interesting and perhaps even learn something new from it (!). Repose: Coincidently, I was working on exactly the same kind of image conversion project. So I got together with DW to discuss our common problem of finding the RGB colors of the 64. I also put out a request to comp.sys.cbm and was sent measurements from several people, including an excellent effort from Marko Makela. 1.1 Target audience This article should be very interesting for people doing emulators, image converters, and other similar projects. Due to the rather technical nature of its content, it is not meant for beginners, however, the end result could be very useful for anybody interested in using other platforms to do graphics work for the c64. If you're not sure whether this article is for you or not, here are some terms you should be familiar with: scanline, frame, refresh rate, CRT, RGB, vertical and horizontal blanking. These terms are used all throughout the article, and it is assumed you already have a basic knowledge of how a TV works. 1.2 The legal stuff Part of this article deals with taking measurements from your c64. If you decide to tinker with your c64 and something goes wrong, it's your fault, not ours. The authors will not be held responsible for damaged equipment, data loss, loss of sleep, loss of sanity, etc. Now, on with the show. 2. The NTSC Y/C video signal ----------------------------- There's basically three ways you can connect your c64 to a TV or monitor and get a color picture. The first and perhaps the most common is to use the RF modulated output with a TV tuned to channel 3 or 4. The second and third use the Video/Audio connector and require either a monitor or a TV/VCR with A/V inputs. We will get to each of these later on, but first, a little bit of history. 2.1 A little history. DW: In 1953, the National Television Systems Committee (NTSC) developed a standard that allowed the transmission of color images while remaining compatible with the large amount of black and white TV sets in widespread use at the time. In the US, public broadcasting (using the color NTSC system) began in 1954. The same system was adopted in Japan, where it came into service in 1960. Other countries favored modifications of the NTSC system, such as PAL (Phase Alternating Line) and SECAM (Systeme Electronique Couleur Avec Memoire). Repose: PAL is used in many western european countries, and has technical advantages to NTSC. SECAM is used in eastern and middle eastern europe. It is only a semi standard in a way because video production is always done in PAL format, and only converted to SECAM for final transmission. The main point of this was information control, as it offers no advantages over PAL. As far as I know, VIC IIs were only made to conform to PAL or NTSC standards. 2.2 A closer look at black and white TV. In designing a TV system, the engineers had to make several considerations. One had to do with bandwidth, which is the space that a TV channel takes in the radio frequency spectrum. To allow for many channels, and also to be easy for the ancient technology of that time, it was decided to split up the picture into two parts, and send each half sequentially. The display on your TV is made up of a several hundred scanlines, composed of two fields which are interlaced to form a complete display, called a frame. This interlacing doesn't happen on the c64, but we will get to that later. First let's look at the fields: Odd field Even field Scanline 1 +++++++++++++++++++++++ Scanline 2 ---------------------- Scanline 3 +++++++++++++++++++++++ Scanline 4 ---------------------- Scanline 5 +++++++++++++++++++++++ Scanline 6 ---------------------- . . . . Scanline 2*n-1 +++++++++++++++++++++++ Scanline 2*n ---------------------- Scanline 2*n+1 +++++++++++++++++++++++ Each of the fields is 262 1/2 lines long (NTSC), (312 1/2 PAL) which means each frame is 525 (625 PAL) lines long. Your TV displays odd fields and even fields one after another, and thanks to what is called "persistence of vision" we see something like: Scanline 1 +++++++++++++++++++++++ Scanline 2 ----------------------- Scanline 3 +++++++++++++++++++++++ Scanline 4 ----------------------- Scanline 6 +++++++++++++++++++++++ . . The second consideration the engineers had was how to represent a 2d image as a 1d voltage. To do this, they needed markers to separate a horizontal line and also odd and even fields. So, let's take a closer look at what the voltage waveform for black and white scanlines looks like: ^ Voltage | | Scanline n Scanline n+2 | ___________________________________ _________________.. | | White level | | | | | | | | | | | | | | | | | | |___ ____| Black level |___ ____| | | | | | | |__| <- Horizontal sync pulses ------------> |__| | -------------------------------------------------------------------------> Time The scanline in this example is a simple white line. If you were to feed enough of these to your TV plus some vertical sync signals, you would get a white screen. The horizontal sync pulses tell the TV receiver when a scanline starts. They make up 25% of the total height of the signal. The brightness at a particular point of the scanline is defined by the voltage of the waveform at that instant. With this in mind, if we wanted to create a display with a simple white vertical line at the middle of the screen, the waveform would look like this: ^ Voltage | | Scanline n Scanline n+2 | __ __ | White level | | | | | | | | | | | | | | | | | | | | Black level | | | | |___ __________________| |_____________________ __________________| | | | | | | | |__| <- Horizontal sync pulses ------------> |__| | -------------------------------------------------------------------------> Time Let us now turn our attention to the visible display area on your TV: ___ ___ ^ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6% of frame | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ___ Vert. blank | XXXXXXXX ^ | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | Field XXXXXXXX | | period XXXXXXXX | | (60hz) XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | | XXXXXXXX | _v_ XXXXXXXX _v_ |<---->|<---------------------------------------->| 17% of 83% of line line is visible Horiz. blank |<-------------------------------------------------->| Line period: 63.5 microseconds The X's in the graph represent areas in which the in which your screen is not visible. At the top we have the vertical blanking interval, and at the left we have the horizontal blanking interval. Please note that whether these intervals occur at the top or bottom, left or right or both, is not relevant. It is shown this way in the diagram for the sake of clarity. So, with that out of the way, let's see what happens in the vertical blank interval (vblank from now on): ^ Voltage | | last time ----> | scanline | __ _______ ___ ___ ___ ___ ___ __ _ ___ ___ _.. | | | | | | | | | | | | | | | | | | | | | | | | | | | |____|___|___|___|___|___|_| | | | ^ ^ ^ ^ Horizontal End of even field Serrated vertical sync 6 equalizing sync pulse 6 equalizing pulses pulse pulses Please note that the time scale has been compressed to be able to cover most of the vblank interval. Basically the vblank interval is composed of 6 equalizing pulses, one serrated vertical sync pulse, and 6 more equalizing pulses. The equalizing pulses and the serrations in the vertical sync pulse are spaced half a scanline apart. An interesting thing happens if we take away the equalizing pulses and serrations: ^ Voltage | | last time ----> | scanline | _________________________________ ___________.. | | | | |__________________________| Vertical sync pulse We end up with a vertical sync pulse that looks very much like the horizontal sync pulses, except it runs at a much lower frequency. In NTSC, the field frequency is 60 Hz, and the frame frequency is 30hz (PAL has a 50Hz field rate and 25Hz frame rate). In other words, we get one of these vertical pulses on every field, to let the TV know when the field starts (just like the horizontal sync pulses tell it when a line starts). According to an old book on TV theory, "serrations are placed in the vertical sync pulse to stabilize the operation of the horizontal scanning generator during vertical retrace time". One last thing remains before we move on to color television: interlacing. Interlacing is achieved by making the field size 262 1/2 lines long, which also changes the spacing between the equalizing pulses and the vertical sync pulse. It will not be discussed any further, though, because the c-64's output is not interlaced. Instead, it is composed of always fields, thus there are visible spaces between the scanlines. The field rate may be considered also the frame rate, since one field is a complete image in itself. Note: it is not known to us if the c64 produces even or odd fields. 2.3 Let's talk about color. Up until now, we've only talked about black and white TV signals, but if you remember the little bit of history in section 2.1, you know that color has to be introduced in a way that doesn't interfere with the BW signal. The VIC-II really outputs two signals (NTSC version only): the Luminance (Y) signal and the Chrominance (C) signal. The Y signal contains the brightness information of the output, while the C signal contains color information. The Y signal is just like the one described in the previous section, except it is not interlaced. The c64 has circuitry to mix the Y and C signals into a composite video signal which has both BW and color info. It then goes to an RF modulator which allows these signals (plus audio) to be viewed on a TV tuned to channel 3 or 4 (in the North American TV frequency). Before we move on to describe the color signal, we will talk briefly about RGB. RGB stands for Red Green Blue, or in other words, a set of three primary colors, which are mixed in different amounts in order to obtain a broad gammut (range) of colors. An interesting thing to note is that it is impossible to reproduce the entire spectrum of visible light with a set of three (or any finite number) of real primary colors, however, RGB and other color systems do a fair job of being able to represent typical images. RGB color is probably quite familiar to you, as it is the basis of the video circuitry in most computers and their monitors. However, NTSC/PAL signals use an encoding called YUV. Y is the brightness information, while UV are the C or color information. U and V are components of a vector. You can picture this vector as originating from the centre of a circle. It's angle is the tint of the color, and it's radius is the saturation of the color. The angle represents a full sprectrum, from red, to orange, yellow, green, blue, purple, back to red again. A highly satured color will be pure, while no saturation would give a grey. There are usually controls related to these properties on a TV, called tint and color, or hue and saturation. 2.4 The color signal Color was added to B/W NTSC in a very clever way. It was added as a carrier on top of the normal signal. It is basically a sinewave of 3.574545Mhz (NTSC) (4.43Mhz PAL) with a varying phase and amplitude. To a B/W TV, this will be seen as a fine wavey detail in the luminance. But, a color TV uses a filter to separate the high frequencies as the color signal, and pass on the lower frequencies as the normal B/W signal. This separation is not perfect, and can cause several strange effects on the picture, known as "crawling dots", "hanging dots", and "color moire patterns" (NTSC, similar effects may appear in PAL). That is why, coincidently, the S-VHS format was invented, to physically separate the color signal from the luma signal. One question, that may have occurred to you, is how do you measure phase without a reference point? Fortunately, at the start of every rasterline there is a calibration color signal known as the color burst, and from this sinewave, the phase of the color signal is measured. The sinewave then continues, at the same time as the luminence portion of rasterline is sent, both signals varying to specify a color and luminance. *** * * * * * * * * * *-------*-------*----> Time * * * * * * *** ColorBurst Reference Signal *** * * * * * * --*-------*-------*--> Time * * * * * * * * * *** An Orange color 30 degrees out of phase with ColorBurst 3. Wrapup ---------- For now, we will end here. Look forward to a second part, which will describe the signals as measured on the 64 in more detail, and finally provide the most accurate measurements of the 64's 16 colors known. One important point which will be explained is how gamma affects RGB color measurements, and why any measurement must be given with gamma information, and why putting the same RGB values into two different monitors will not cause the same colors to be seen. Also provided will be a program which can display the 64s colors on a PC and let you play with hue and saturation controls. When our measurements are finalized, we would expect all emulator writers to add our calibrated colors to their programs, so that the original feeling of the 64 can be retained. Please see http://nlaredo.globalpc.net/~agonzalez/index.html for up to date information. Bibliography ------------ Introduction to Digital Video, Charles Poynton. comp.graphics FAQ. Television Theory and Servicing, second edition. Clyde N. Herrick