User Tools

Site Tools


magazines:chacking10
no way to compare when less than two revisions

Differences

This shows you the differences between two versions of the page.


magazines:chacking10 [2015-04-17 04:34] (current) – created - external edit 127.0.0.1
Line 1: Line 1:
 +<code>
 +                   ########
 +             ##################
 +         ######            ######
 +      #####
 +    #####  ####  ####      ##      #####   ####  ####  ####  ####  ####   #####
 +  #####    ##    ##      ####    ##   ##   ##  ###     ##    ####  ##   ##   ##
 + #####    ########     ##  ##   ##        #####       ##    ## ## ##   ##
 +#####    ##    ##    ########  ##   ##   ##  ###     ##    ##  ####   ##   ##
 +#####  ####  ####  ####  ####  #####   ####  ####  ####  ####  ####   ######
 +#####                                                                     ##
 + ######            ######            Issue #10
 +   ##################              June 30, 1995
 +       ########
  
 +------------------------------------------------------------------------------
 +</code>
 +====== Editor's Notes ======
 +<code>
 +by Craig Taylor
 +
 +This is my last issue of Commodore Hacking (having finally gotten out the
 +door, but I couldn't break tradition and get it out on time :-) ). I'm having
 +to give it up because I've gradually lost interest in Commodore computers over
 +the years and with the search for a job (anyone wanna hire a csc graduate?)
 +and as I get older I seem to have less and less time.
 +
 +I'm gonna be handing the reigns of Commodore Hacking over to Jim Brain, who is
 +a very active member of the Commodore Internet community. He will also be
 +running a mailserver that will take the place of mine (Mine will become
 +unavailable after July 1st and will send pointers to Jim Brain's mailserver).
 +
 +It's been interesting to watch the Commodore computers evolve, take off like a
 +rocket and then have Commodore go into liquidation. Commodore computers have
 +been and still are, (with some exceptions - 1541 head-banging comes to mind),
 +technologically sound.  For a "hacking" machine they're wonderful.
 +
 +My email address has changed to duck@nando.net. I periodically still check
 +mail at duck@pembvax1.pembroke.edu but only every 2 weeks or so. I am still
 +going to try to be in the Commodore community but time will govern my ability
 +to do that. I'm going to miss editing this rag....
 +
 +And here is Jim Brain: 
 +
 +Mail Server Changes:
 +
 +With Issue 10, the address for the Commodore Hacking mail server has changed.
 +The new address is brain@mail.msen.com  The commands are the same as before.
 +Not all of the files have been moved yet, so please email the administrator
 +(Jim Brain, brain@mail.msen.com) if a file you need is not on the new site.
 +
 +Howdy:
 +Howdy, my name is Jim Brain, and I will be taking over the position of 
 +editor for Commodore Hacking starting with Issue 11.  Some of you may know
 +me as the Commodore Trivia Contest administrator, the USENET newsgroup
 +comp.sys.cbm FAQ Manitainer, or the keeper of a Commodore Information
 +WWW Site at http://www.msen.com/~brain/cbmhome.html.  Wherever you have 
 +heard of me from, or even if you haven't, I will state that I plan on
 +handling Commodore Hacking in the following way.  The next issue will
 +possibly look different cosmetically, as I edit somewhat differently than
 +Craig, but the content and basic layout will remain the same.  The types
 +of material will not change, and the structure for submitting articles will
 +change only in the address to mail them to: brain@mail.msen.com.  However,
 +I do have a few changes in mind:
 +
 +1) Try to stabilize the issue generation so that Commodore Hacking will
 +   become a quarterly publication.
 +
 +2) Attempt a fully "HTML"ized version of the magazine, while still providing a
 +   text version. 
 +   
 +3) Pursue the possibility of providing a printed version of these issues
 +   for those who have no online access to them.
 +   
 +4) Encourage User Groups and other CBM related organizations to carry this
 +   magazine for their members.
 +   
 +So, again I say howdy.  As always, Commodore Hacking will accept your 
 +articles at any time, so please help us keep this quality magazine running.
 +If you have any questions or comments about the change in editorship, the
 +possible changes, or other matters, please feel free to drop me a note.
 +I look forward to hearing from you and publishing your articles.
 +
 +Jim Brain
 +brain@mail.msen.com
 +===========================================================================
 +Legal Mumbo-Jumbo
 +
 +Permission is granted to re-distribute this "net-magazine", in whole,
 +freely for non-profit use. However, please contact individual authors for
 +permission to publish or re-distribute articles separately. A charge of no
 +greater than 5 US dollars or equivlent may be charged for library service /
 +diskettte costs for this "net-magazine".
 +
 +-------------------------------------------------------------------------------
 +
 +Please note that this issue and prior ones are available via anonymous FTP
 +from ccnga.uwaterloo.ca (among others) under /pub/cbm/hacking.mag and via a
 +mailserver which documentation can be obtained by sending mail to
 +"brain@mail.msen.com" with a subject line of "mailserver" and the
 +lines of "help" and "catalog" in the body of the message.
 +
 +-------------------------------------------------------------------------------
 +</code>
 +
 +====== In This Issue ======
 +<code>
 +Commodore Trivia 
 +
 +Trivia Edition #13-18 are in this article.  As you may know, these questions
 +form part of a contest in which the monthly winner gets a prize (Thanks to my
 +various prize donators). The whole thing is mainly just for fun, so please
 +enjoy.  Try your hand at Commodore trivia!!
 +
 +BFLI - New graphics modes 2
 +
 +FLI gave us more color to the screen, AFLI increased the horizontal
 +resolution and color selection by using the hires mode.  BFLI stands
 +for 'Big FLI' and gives us 400 lines instead of the usual two
 +hundred.  AFLI and BFLI can be combined, but we are not going into
 +that.
 +
 +Making stable raster routines (C64 and VIC-20)
 +
 +In this article, I document two methods of creating stable raster
 +routines on Commodore computers.  The principles apply for most 8-bit
 +computers, not only Commodores, but raster effects are very rarely
 +seen on other computers.
 +
 +A Differant Perspective - Part III.
 +
 +Yes!!!  It's yet another article on 3D graphics!  Even if you
 +haven't been following this series, you can use this program.  This
 +time around we will write a completely general polygon plotter --
 +if you can type basic data statements, you can create a three-dimensional
 +object out of polygons and rotate and project it to your heart's content.
 +For the more technically inclined we will look at optimizations to the
 +line routine, EOR-buffer filling, and more!  Yow!
 +
 +Second SID Chip Installation
 +
 +This article describes how to add a second sid chip for use in SidPlayer and
 +other programs. As always, be extra careful when making modifications to your
 +computer.
 +
 +Solving Large Systems of Linear Equations on a C64 Without Memory
 +
 +OK, now that I have your attention, I lied.  You can't solve dense
 +linear systems of equations by direct methods without using memory to
 +store the problem data.  However, I'll come back to this memory free
 +assertion later.  The main purpose of this article is to rescue a
 +usefull numerical algorithm, "Quartersolve", and also to provide a brief
 +look at the COMAL programming language and BLAS routines.
 +
 +The World of IRC - A New Life for the C64/128
 +
 +I've heard people talking about IRC. What is it? Why is it useful to me as a
 +Commodore user? Bill "Coolhand" Lueck explains the hows and whys in this 
 +article.
 +
 +SwiftLink-232 Application Notes (version 1.0b)
 +
 +This information is made available from a paper document published by CMD,
 +with CMD's permission. 
 +
 +Design and Implementation of a Simple/Efficient Upload/Download Protocol
 +
 +This article details how to implement a custom upload/download protocol that
 +is faster than most of the ones common to the C64/128 computers.
 +
 +Design and Implementation of a 'Real' Operating System for the 128: Part II
 +
 +There has been a slight change in plans.  I originally intended this 
 +article to give the design of a theoretical distributed multitasking 
 +microkernel operating systemfor the C128.  I have decided to go a 
 +different route: to take out the distributed component for now and implement
 +a real multitasking microkernel OS for a single machine and extend the system
 +to be distributed later.  The implementation so far is, of course, only in 
 +the prototype stage and the application for it is only a demo.  Part III of 
 +this series will extend this demo system into, perhaps, a usable distributed
 +operating system.
 +
 +========================================================================
 +</code>
 +====== Trivia ======
 +<code>
 +by Jim Brain (brain@mail.msen.com)
 +
 +Well, summer is upon the Brain household, and things are moving at a fast
 +clip at the house.  However, the trivia still keeps coming.  I appreciate
 +all the people who contribute to the trivia and all the people who take
 +part in the monthly contest.  I have collected Trivia Edition #13-18 in this
 +article.  As you may know, these questions form part of a contest in which
 +the monthly winner gets a prize (Thanks to my various prize donators).
 +The whole thing is mainly just for fun, so please enjoy.
 +
 +As the summer months start up, news on the trivia includes:
 +
 +1) I now have access to some more orphan machines (C65, C116), so expect
 +   some trivia questions on those models.
 +   
 +2) The new home now has a number of machines set up, so testing answers to
 +   the trivia is even easier.  I am still trying to get the old PET 
 +   machines in house, but the others are here.
 +   
 +3) The Commodore World Wide Web Pages (http://www.msen.com/~brain/cbmhome.html)
 +   that I maintain and place the trivia on caught the eye of USA Today and
 +   the Pheonix Gazette.  I was interviewed for both articles.  Look in the
 +   June 20th edition of USA Today for the segment, and possibly a picture of
 +   Jim Brain and the machines he uses to create the trivia.
 +
 +As always, I welcome any questions (with answers), and encourage people
 +to enter their responses to the trivia, now at #18. 
 +
 +
 +Jim.
 +
 +
 +The following article contains the answers to the December edition of trivia
 +($0C0 - $0CF), the questions and answers for January ($0D0 - $0DF), 
 +February ($0E0 - $0EF), March ($0F0 - $0FF), April ($100 - $10F), and the
 +questions for the May edition ($110 - $11F).  Enjoy them!
 +
 +
 +   Here are the answers to Commodore Trivia Edition #13 for December, 1994
 +
 +Q $0C0) The early 1541 drives used a mechanism developed by ______.  Name
 +        the company.
 +
 +A $0C0) Alps.
 +
 +Q $0C1) On later models, Commodore subsequently changed manufacturers
 +        for the 1541 drive mechanism.  Name the new manufacturer.
 +
 +A $0C1) Newtronics.
 +
 +Q $0C2) What is the most obvious difference(s).  (Only one difference is
 +        necessary)
 +
 +A $0C2) Alps:        push-type latch, round LED.
 +        Newtronics:  lever-type latch, rectangular LED.
 +                   
 +Q $0C3) On Commodore BASIC V2.0, what answer does the following give:
 +        PRINT (SQR(9)=3)
 +
 +A $0C3) 0.  According to Commodore BASIC, the answer should bby -1, which
 +        is the BASIC value of TRUE.  However, the above equation is NOT
 +        true.  Doing PRINT SQR(9) yields 3, but doing PRINT (SQR(9)-3)
 +        yields 9.31322575E-10 (C64).  This anomaly can be attributed to
 +        roundoff errors in the floating point math routines in Commodore BASIC.
 +
 +Q $0C4) In Commodore BASIC (Any version) what does B equal after the following
 +        runs: C=0:B=C=0 
 +           
 +A $0C4) B = -1.  The second statement is the one to look at.  The second
 +        equals sign is treated as a comparison, while the first is treated
 +        as a assignment.  B gets set to the outcome of the comparison, which
 +        is TRUE (-1).
 +
 +Q $0C5) The first PET cassette decks were actually _______ brand cassette 
 +        players, modified for the PET computers.  Name the comapny.
 +
 +A $0C5) Sanyo. Specifically, Model M1540A.  What a model number!
 +
 +Q $0C6) In Commodore BASIC (Any version), what happens if the following
 +        program is run:
 +        
 +        10 J=0
 +        20 IF J=0 GO TO 40
 +        30 PRINT "J<>0"
 +        40 PRINT "J=0"
 +
 +A $0C6) On BASIC 2.0 or greater:
 +   
 +   ?SYNTAX  ERROR IN 20
 +        READY.
 +        
 +        On BASIC 1.0:  (found on the PET 2001 series)
 +        
 +        J=0
 +        READY.
 +
 +        BASIC 1.0 totally ignored spaces, so line 20 became "IFJ=0GOTO40".
 +        That statement would be correctly parsed, sicne it contains the "GOTO"
 +        keyword.  
 +        
 + However, on BASIC 2.0 or greater, spaces weren't ignored so
 + completely, and the "TO" in "GO TO" would be tokenized separately, so
 + some code was added to BASIC to check to "GO" As the code that
 + accepts GOTO as a special case for THEN after an IF statement wasn't
 + patched this way, the above fails, because GO is not a valid keyword
 + after IF.  The statement SHOULD work correctly, but does not because
 + of this failure to fix the IF command parsing.
 +
 + On BASIC 2.0 or greater, substituting the following line for line 20
 + will cause the program to work:
 +
 + 20 IF J=0 THEN GO TO 40
 +
 +Q $0C7) In question $068, we learned how Jack Tramiel first happened upon the
 +        name "COMMODORE" According to the story, though, in what country
 +        was he in when he first saw it?
 +
 +A $0C7) Germany.  
 +
 +Q $0C8) On the Commodore user port connector, how many edge contacts are
 +        there?
 +
 +A $0C8) 24.  Two rows of 12 contacts each.
 +
 +Q $0C9) On most Commodore computers, a logical BASIC screen line can contain
 +        up to 80 characters.  On what Commodore computer(s) is this not true?
 +
 +A $0C9) According to Commodore documentation, a _physical_ screen line is 
 +        defined as one screen line of characters.  A _logical_ screen line is 
 +        defined as how many _physical_ lines can be chained together to 
 +        create a valid BASIC program line.  
 +
 +        With that in mind, most Commodore computers chose a _logical_
 +        screen line that was a multiple of the screen width.  This works fine
 +        for 40 and 80 column screens, but what do we do with the VIC-20, with
 +        its 22 column screen.  Solution:  make the _logical_ line length equal
 +        to 4 _physical_ lines, or 88 columns.
 +
 +        When the Commdore 128 was introduced, the number rose to 160
 +        characters, which is 4 _physical_ lines in 40 column mode, or
 +        2 _physical_ lines in 80 column mode.  However, you can only take
 +        advantage of this in 128 mode.  64 mode is limited to 80 characters.
 +        
 +        To add to all this confusion, a valid BASIC program line (in memory)
 +        can actually be 255 (tokenized) characters long, but creating such
 +        a long line cannot be done from the built-in editor in direct mode.
 +        
 +        The AmigaBASIC, available on the Amiga, also does not have the 80
 +        column line limit.  However, that BASIC is SOOO much different that
 +        I am not surprised.  The older CBM BASICs, on the other hand, were
 +        all derivatives of the original Level 1 BASIC for the PET.
 +
 +Q $0CA) If a file is saved to a Commodore Disk Drive with the following
 +        characters: chr$(65);chr$(160);chr$(66), what will the directory
 +        entry look like?
 +
 +A $0CA) The filename will show up as "A"B, with the 'B' showing up to the
 +        right of the '"' mark.  This could be used to make program loading
 +        easier.  A file that showed up as "filename",8,1 could be loaded
 +        by simply hitting shift-run/stop on that line.
 +
 +Q $0CB) What is the maximum length (in characters) of a CBM datasette
 +        filename?
 +
 +A $0CB) References I have on hand say 128 characters.  However, the actual
 +        code on the 8032 and the C64 acts as though 187 characters can
 +        actually be sent (tape buffer-5 control bytes = 192-5=187).  The
 +        references that claim 128 characters are Nick Hampshire's
 +        _The VIC Revealed_ and _The PET Revealed_.  ANyone care to lay
 +        this one to rest? 
 +
 +Q $0CC) How many keys are on a stock Commodore 64 keyboard?
 +
 +A $0CC) 66 keys.  This is the same number as found on the VIC-20 and the
 +        Commodore 16.
 +
 +Q $0CD) Commodore BASIC uses keyword "tokens" to save program space.  Token
 +        129 becomes "FOR" What two tokens expand to include a left
 +        parenthesis as well as a BASIC keyword?
 +
 +A $0CD) TAB( (163) and SPC( (166).
 +
 +Q $0CE) There are 6 wires in the Commodore serial bus.  Name the 6 wires.
 +
 +A $0CE) 1) Serial /SRQIN
 +        2) GND
 +        3) Serial ATN IN/OUT
 +        4) Serial CLK IN/OUT
 +        5) Serial DATA IN/OUT
 +        6) /RESET
 +
 +Q $0CF) On the Commodore datasette connector, how many logical connections are
 +        there?
 +
 +A $0CF) 6. Opposing pins on the connector are hooked together electrically.
 +
 +
 +   Here are the answers to Commodore Trivia Edition #14 for January, 1995
 +
 +Q $0D0) How many keys were there on the "original" PET and what was special
 +        about them?
 +
 +A $0D0) the original PET had 73 calculator-style keys that were laid out
 +        in a rectangular matrix, not typewriter-style.
 +
 +Q $0D1) How do you produce the "hidden" message(s) on the Commodore 128?
 +
 +A $0D1) SYS 32800,123,45,6.  The screen will clear, and the software
 +        and hardware developers on the 128 project will be named.
 +
 +        The exact text is as follows:
 +                
 +[RVS]   Brought to you by...
 +
 +Software:
 + Fred Bowen
 + Terry Ryan
 + Von Ertwine
 +
 +Herdware:
 + Bil Herd
 + Dave Haynie
 + Frank Palaia
 +
 +[RVS]Link arms,don't make them.
 +
 +Q $0D2) How much memory did the "original" PET show on bootup?
 +
 +A $0D2) The "original" PET came in two configurations, 4K and 8K, so:
 +         
 +          The PET 2001-4 had 3071 bytes.
 +          The PET 2001-8 had 7167 bytes.
 +
 +Q $0D3) We all know the "reboot" sys for the 64 is sys 64738, but who knows
 +        the same sys location to reboot the CBM 8032?
 +
 +A $0D3) sys 64790
 +
 +Q $0D4) Which computer(s) beeped at bootup?  (May be more than one, but only
 +        one required)
 +           
 +A $0D4) I know some of these are corect, but the sheer size of the
 +        list prevents me from checking them ALL out.
 +        
 +        FAT 40XX series
 +        80XX series
 +        PC-10  (I suspect a number of IBM clones did, and these things have
 +                no consistent naming convention across country boundaries.)
 +        PC-20
 +        Amiga 1000
 +        SP9000 (SuperPET)
 +        
 +Q $0D5) How much memory did the CBM 8032 show on bootup?
 +
 +A $0D5) 31743 bytes.
 +
 +Q $0D6) Certain Commodore computers provided emtpy EPROM sockets on the
 +        motherboard.  Give me the number of empty sockets on the following
 +        machines:
 +
 +        a)  CBM 30XX.
 +        b)  CBM 8XXX.
 +        c)  CBM C128.
 +        d)  Plus/4.
 +
 +A $0D6) a)  3 sockets.
 +        b)  2 sockets.
 +        c)  1 socket.
 +        d)  1 socket.
 +
 +Q $0D7) In Germany, the CBM 8032 came with a 4kB EPROM for the EXXX area,
 +        while the US version only had a 2kB EPROM.  Why?
 +
 +A $0D7) The German version had additional keybaord drivers for umlaut
 +        characters and dead keys.  
 +
 +Q $0D8) Who published the first PET memory map in the "PET Gazette"?
 +
 +A $0D8) None other than the infamous Jim Butterfield.
 +
 +Q $0D9) Which is faster to move the sursor on a PET/CBM or C64: SYS or 
 +        PRINT?
 +
 +A $0D9) PRINT is faster, since the sys approach must process the pokes
 +        before the sys, which are very slow.
 +
 +Q $0DA) On the Amiga 1000, where are the signatures of the first Amiga
 +        developers located?
 +
 +A $0DA) Inside the top case of the Amiga (1000).
 +
 +        There is an interesting footnote to this question.  It seems
 +        that at least some original Amiga machines were labeled as
 +        Amiga (with nu number).  Then, at some later point, the number was
 +        added.  In addition, Commodore produced some Amiga 1000 machines
 +        without the signatures, but most had the telltale handwriting on
 +        the inside of the case.  
 +
 +Q $0DB) On the 6502, what does the accumulator contain after the following
 +        is executed:
 +
 +        lda #$aa
 +        sed
 +        adc #01
 +
 +A $0DB) Assume carry was clear.  If so, then $11 is the correct answer. 
 +
 +Q $0DC) What is the model number of the US NTSC VIC-II chip?
 +
 +A $0DC) Its first number was 6567, and that is the number most people know
 +        it by, but Commodore produced a VIC-II using a new manufacturing 
 +        process that was numbered the 8562. 
 +
 +Q $0DD) What is the European PAL VIC-II chip's model number?
 +        (Not sure if that's its rightful term, but I hope you understand).
 +
 +A $0DD) Same here.  The part number 6569 is the most remembered number, but
 +        an 8565 will work as well.
 +
 +Q $0DE) Assume you have two computers, one with each of the above chips inside.
 +        Which chip draws more pixels on the screen per second?
 +
 +A $0DE) Note, for the purposes of the calculation I am performing, "pixels"
 +        refers to picture elements that can be adddress and modified using
 +        normal VIC modes, so there are 320*200 "pixels" on both the PAL
 +        and NTSC screens.  (I probably should have stated this, but it is
 +        too late now.)  Also, the screen refresh rates used in the 
 +        calculations are those defined by the respective television
 +        standards (60Hz U.S., 50Hz European), even though the actual
 +        frequencies are off by a small percentage. (for example, the actual
 +        50Hz refresh rate on European VIC-II chips was calculates as 
 +        50.124567Hz by Andreas Boose)
 +        
 +        So, the PAL draws 320*200*50 pixels per second = 3200000 pixels/s
 +        NTSC draws 320*200*60 pixels per second = 3840000 pixles/s
 +        
 +        Now, some people thought I meant the whole screen, not just the 
 +        display area provided by the VIC-II chip.  Well, I am not sure
 +        exactly you calculate pixels on a screen, since the numbers could
 +        vary from display to display, but if we measure in scanlines:
 +        
 +        PAL = 312 scanlines * 50 = 15600 scanlines/s
 +        NTSC = 262 scanlines * 60 = 15720 scanlines/s
 +        
 +        The NTSC machines wins both ways.  
 +
 +Q $0DF) In Commodore BASIC, which statement executes faster:
 +
 +        a = 2--2
 +
 +        or
 +
 +        a = 2+2
 +
 +A $0DF) b is the correct answer, and there are a couple of reasons why:
 +
 +        1) 2--2 takes longer to parse in the BASIC interpreter.
 +        2) Commodore BASIC subtracts by complementing the sign of the
 +           second number and adding.  This incurs extra time.
 +
 +        There are even more subtle ones, but I leave them as an
 +        exercise for the reader.  Send me your reason why.
 +
 +
 +   Here are the answers to Commodore Trivia Edition #15 for February, 1995
 +
 +Q $0E0) What is the difference(s) between the Newtronics 1541 and the 1541C?
 +        (only one difference is needed)
 +
 +A $0E0) (George Page, a noted authority on CBM Drives, indicated that Commodore
 +        made this a tough question to answer.)  By the time the 1541C was 
 +        introduced, Commodore threw a number of drives together and called
 +        them 1541Cs.  The theoretical 1541C exhibited the following
 +        features:
 +
 +        No head banging, and other problems fixed by modified ROMs.
 +        Case color matches C64C and C128 computers.
 +
 +Q $0E1) What happens when you type 35072121 in direct mode on the C64 and
 +        hit return?
 +
 +A $0E1) Simple answer:  Most likely, the screen clears and the word READY.
 +        is printed at screen top.  This is the behavior seen when pressing 
 +        RUN-STOP/RESTORE.  Alternately, nothing could happen, or the computer
 +        could lock up.
 +
 +        Involved answer:  There is a bug in BASIC 2.0.  Easily fixed, but 
 +        destined to live life immortal.  (long)
 +
 +        The bug is in the PETSCII number to binary conversion routine at
 +        $a69b (LINGET).  The routine basically reads in a character from the
 +        line, multiplies a partial result by 10 and adds the new character
 +        to the partial result.  Here is a code snippet:
 +
 +        a96a     rts
 +        a96b     ldx #$00  ; zero out partial result
 +        a96d     stx $14
 +        a96f     stx $15
 +        a971     bcs $a96a ; not a number, return
 +        a973     sbc #$2f  ; PETSCII to binary
 +        a975     sta $07   
 +        a977     lda $15   ; get hi byte or partial result
 +        a979     sta $22
 +        a97b     cmp #$19  ; partial > 6399
 +        a97d     bcs $a953 ; yes, goto error
 +        a97f     lda $14   ; load lo byte of result
 +        a981     asl       ; lo*2
 +        a982     rol $22   ; hi*2 + c
 +        a984     asl       ; lo*2
 +        a985     rol $22   ; hi*2 + c
 +        a987     adc $14   ; complete lo*5
 +        a989     sta $14
 +        a98b     lda $22  
 +        a98d     adc $15   ; complete hi*5
 +        a98f     sta $15
 +        a991     asl $14   ; lo*2 complete lo*10
 +        a993     rol $15   ; hi*2 complete hi*10
 +        a995     lda $14
 +        a997     adc $07   ; add new char
 +        a999     sta $14
 +        a99b     bcc $a99f ; did lo overflow?
 +        a99d     inc $15   ; yes, inc hi
 +        a99f     jsr $0073 ; get next char
 +        a9a2     jmp $a971 ; go through it again.
 +        
 +        The problem is at $a97d.  when the partial result is greater than 6399,
 +        (if partial > 6399, then new partial result will be over 63999)
 +        the routine needs to get to $af08 to print an error, but can't due to
 +        branch restrictions.  However, a branch that will get there is in the
 +        preceding function, which handles the ON GOTO/GOSUB keywords ($a94b,
 +        ONGOTO).  
 +
 +        So, the BASIC writers just branched to the code in ONGOTO; specifically
 +        $a953:
 +        
 +        a94b     jsr $b79e
 +        a94e     pha
 +        a94f     cmp #$8d  ; is the keyword GOSUB ($8d)
 +        a951     beq $a957 ; yes
 +        a953     cmp #$89  ; is the keyword GOTO ($89)
 +        a955     bne $a8e8 ; no, print SYNTAX ERROR.
 +        a957     ...       ; handle ON GOTO/GOSUB
 +
 +        This code is checking to make sure the ON (var) is followed with a
 +        GOTO or GOSUB keyword.
 +
 +        The LINGET error handler branches to $a953, which compares
 +        .A (which holds hi byte of partial result) to $89.  Normally, this
 +        fails, and the normal SYNTAX ERROR code is reached through the branch
 +        to $a8e8.  However, for partial results of the form $89XX, the check
 +        succeeds, and BASIC tries to execute an ON GOTO/GOSUB call.
 +
 +        By the way, it is no coincidence that this error occurs on 35072121,
 +        since one of the partial results is $8900 (hi byte is $89). In fact,
 +        350721 will achieve the same result.
 +
 +        If the check succeeds, the code limps along until $a96a:
 +
 +        a969     pla       ; complement to $a94e
 +        a96a     rts       ; return
 +
 +        But we never executed $a94e, the push, so the stack is now
 +        messed up.  Since the stack held $9e, $79, $a5 before the PLA,
 +        (The stack could hold other values, but I always saw these)
 +        the RTS gets address $a579 to return to, which usually holds a BRK
 +        opcode.  The break handler is invoked, and the screen clears with the
 +        READY. at the top.
 +
 +        Now, the BASIC 2.0 authors were justified in reusing the error
 +        handler code in ONGOTO for LINGET, but they calculated the branch
 +        offset wrong, according to my tests.  If you have the LINGET error
 +        handler branch to $a955, all these troubles disappear.  You can
 +        verify this procedure with the following BASIC program on a 64:
 +        
 +        10 for t=57344 to 65535:poke t,peek(t):next
 +        20 for t=40960 to 49151:poke t,peek(t):next
 +        30 poke 43390, 214
 +        40 poke 1, peek(1) and 254
 +
 +        Just to be complete, this error occurs when a 6 digit or greater line
 +        number is entered and the first 6 digits indicate a number in the
 +        range 35072-35327 ($8900-$89ff).  Also, it appears the error occurs
 +        on the VIC-20, but I didn't completely verify it.  It would be
 +        interesting to note if the error is found on all version of CBM BASIC.
 +
 +        Whew, what a mouthful.
 +
 +Q $0E2) If a SID chip is producing a "sawtooth waveform", does the waveform look
 +        like: 
 +
 +        a) "/|/|/|/|"  or
 +        b) "|\|\|\|\"  ?
 +
 +A $0E2) a is the correct answer.
 +
 +Q $0E3) On BASIC 2.0, what special precaution(s) must one take when working with
 +        relative files? (only one is needed)
 +
 +A $0E3) Because BASIC 2.0 doesn't handle positioning in relative files quite
 +        right, one must position the relative file pointer before AND AFTER
 +        a read or write to a relative file.
 +
 +Q $0E4) What incompatibility existed between C128 Rev. 0 ROMS and the REU?
 +           
 +A $0E4) OK, I admit it.  I placed this answer and its discussion somewhere
 +        in my store of information, and it must have fallen behind the 
 +        cabinet, because I cannot find it.  I will post an answer to this
 +        as soon as I can find it, but the answers really must go out, as
 +        they have been held up long enough.
 +
 +Q $0E5) What can trigger an NMI interrupt? (count all sources on one chip as
 +        one)
 +
 +A $0E5) The following sources can trigger an NMI interrupt:
 +
 +        1) The expansion port.
 +        2) CIA #2.
 +        3) The RESTORE key.
 +
 +Q $0E6) What can trigger an IRQ interrupt? (count all sources on one chip as
 +        one)
 +
 +A $0E6) The following sources can trigger an IRQ interrupt:
 +
 +        1) The VIC-II chip.
 +        2) CIA #1.
 +        3) The expansion port.
 +
 +Q $0E7) Where is the ROM in a 1541 located in the 64K memory map?
 +
 +A $0E7) The ROM is located from $C000 to $FFFF, yet the ROM code does not
 +        begin until $C100.
 +
 +Q $0E8) Which VIA on the 1541 is hooked to the read/write head?
 +
 +A $0E8) VIA #2, located in memory from $1C00 to $1C0E.
 +
 +Q $0E9) In the Commodore DOS, what bit in the file type byte denotes a "locked"
 +        file?
 +
 +A $0E9) bit 6.
 +
 +Q $0EA) If files are "locked" under Commodore DOS, under what condition(s) may
 +        the file be changed?
 +
 +A $0EA) Depending on the file, the following operations can be done on a 
 +        locked file:
 +        
 +        1) Rename will change file name, although not contents of file.
 +        2) Random access can be used to alter file.
 +        3) Formatting the disk will alter the file. (duh!)
 +        4) Save-with-replace (@0:) will replace file and unlock it.
 +        5) Opening file in append mode will allow it to be changed, and
 +           unlock it.
 +        6) Opening a relative file and adding or changing a record will
 +           succeed and unlock file.
 +
 +Q $0EB) How big can a program file be on a 1541 or similar?
 +
 +A $0EB) The file can be as large as a sequential file, since both are stored
 +        in the same way: 168656 bytes.  However, since a program contains its
 +        load address as bytes 0 and 1, the largest program size is 168654
 +        bytes.
 +
 +Q $0EC) Under BASIC 2.0, how does one open a random access file on a disk
 +        drive?
 +
 +A $0EC) Random access (or direct access) files are a misnomer.  What you
 +        really doing is opening the disk for reading and writing.  You need
 +        two open command to access a random file: (assume drive 8)
 +        
 +        open 15,8,15     and
 +        
 +        open 1,8,4,"#1" will open a random access file using buffer 1.
 +        open 1,8,4,"#" will open a random access file using the first
 +        available buffer
 +        
 +        Now, by using B-R, B-W, B-A or their replacements, you can write
 +        data to sectors on the disk.
 +        
 +        Note that Random access files are different from relative files.
 +        
 +Q $0ED) A file that has a '*' immediately before the filetype is called
 +        a _________ file.
 +
 +A $0ED) a splat file.  This is its correct term, believe it or not.
 +
 +Q $0EE) We know the 1541 and similar drives have 5 internal buffer areas, but
 +        how many does an 8050 drive have?
 +
 +A $0EE) Since the 8050 has twice the on-board RAM (4kB), it has 16 buffers, but
 +        only 13 are available.  (All CBM drives use one buffer for zero-page
 +        memory, one for stack memory, and one for temporary variables.) 
 +
 +Q $0EF) On a "save-with-replace", where is the location of the first track and
 +        sector of the new copy of the program saved in the directory entry for
 +        the old copy?
 +
 +A $0EF) The new first track is stored at location 26, and the new first sector
 +        is stored at location 27.  These values are copied to their
 +        correct locations after the save is completed.
 +
 +
 +   Here are the answers to Commodore Trivia Edition #16 for March, 1995
 +
 +Q $0F0) What size matrix of pixels comprises a character on a PET 2001
 +        computer?
 +
 +A $0F0) The matrix was 8 by 8.  
 +
 +Q $0F1) How many bytes did the opening screen on a CBM 4016 show as
 +        available for use by BASIC?
 +
 +A $0F1) 15359 bytes free.
 +
 +Q $0F2) The character set that produces uppercase letters on unshifted keys 
 +        is the ________________ character set.
 +
 +A $0F2) "standard mode".  
 +
 +Q $0F3) The character set that produces lowercase letters on unshifted keys
 +        is the ________________ character set.
 +
 +A $0F3) "alternate mode"
 +
 +Q $0F4) To get to the set mentioned in $F2, what character code would be
 +        printed to the screen?
 + 
 +A $0F4) chr$(142)
 +
 +Q $0F5) What character code would one print to the screen to invoke the 
 +        chararacter set in $F3?
 +
 +A $0F5) chr$(14)
 +
 +Q $0F6) If one does LIST 60-100, will line 100 get "listed"?
 +
 +A $0F6) Yes.  The above translates as: LIST 60 through to and including 100.
 +
 +Q $0F7) The abbreviation for the BASIC 4.0 command "COLLECT" is ________.
 +
 +A $0F7) coL. "C" "O" "SHIFT-L" For those who are interested, the 
 +        COLLECT command is analogous to the VALIDATE operation.
 +
 +Q $0F8) When you use a subscripted variable in BASIC, how many elements
 +        are created by default if no DIM statement is issued?
 +
 +A $0F8) 11 elements.  A(0) - A(10).  Almost everyone who has ever programmed 
 +        in Commodore BASIC has seen the "BAD SUBSCRIPT" error when they try 
 +        to use the 12th element in a un-DIMensioned array.
 +
 +Q $0F9) How large is the keyboard buffer in CBM computers?
 +
 +A $0F9) 10 bytes.  Since this area could be POKEd to, many boot programs
 +        would poke characters into this buffer to simulate keypresses.
 +
 +Q $0FA) On the Commodore 1581, how large is a physical sector in bytes?
 +
 +A $0FA) A physical sector is 512 bytes in length.  Internally, the 1581
 +        creates 2 256 "logical" sectors in a physical sector, to maintain
 +        compatibility with older Commodore drives.
 +
 +Q $0FB) You'll find BASIC 3.5 on the _____________ line of CBM computers.
 +
 +A $0FB) The X64 series.  That includes the Commodore 16, the Commodore 116,
 +        and the Commodore Plus/4.
 +
 +Q $0FC) On the Commodore 1351 mouse, what registers in the Commodore
 +        computer would the X and Y proportional information be read
 +        from?
 +
 +A $0FC) Even though you are looking for digital information (how far the
 +        mouse has traveled since the last movement in a particular axis), 
 +        the information is read from the "paddle" or potentiometer (POT)
 +        registers.  On the C64, the POT registers are part of the SID
 +        chip, and are at 54297 ($D419) for POTX, and 54298 ($D41A) for
 +        POTY.
 +
 +Q $0FD) What is the maximum size of a sequential file on a 1581 drive?
 +
 +A $0FD) 802640 bytes.
 +
 +Q $0FE) What flaw exists in the early Commodore 1670 modems?
 +
 +A $0FE) When the 1670 modem was first introduced, it powered up in auto-
 +        answer mode, which means it would answer incoming calls after
 +        the phong rang.  You could turn this feature off through software
 +        control, but if the power was reset, the modem would answer the
 +        phone.  So many people complained to Commodore that CBM revised
 +        the 1670 to include an extra DIP switch that turned this feature
 +        off.
 +
 +Q $0FF) What is the model number of the first modem for the VIC and C64?
 +
 +A $0FF) The 1600 manual dial/manual answer 0-300 bps modem.  The author 
 +        owns one, and used it for many years.  To operate, you must use
 +        a phone with a detachable handset cord.  You dialed the number
 +        on the phone, waited for the answer, unplugged the handset, and
 +        plugged the cord into the 1600.  A switch toggled between using
 +        originate or answer frequencies.  The 1600 was manufactured by
 +        Anchor Automation for Commodore.  (As an aside, this unit claimed
 +        300 bps, but I never could get 300 to work well.  Most of my
 +        telecommunications happened at 150 bps.)
 +
 +
 +-------Commodore Trivia Edition #17 Questions and Answers (BEGIN)--------
 +
 +Q $100) On the MOS Technology's KIM-1, how many keys were on the keypad?
 +
 +A $100) 23 keys.  The keypad has room for 24, but one spot is taken by
 +        a switch that puts the system into single-step mode.  Interestingly,
 +        some pictures have the switch on the upper left, some on the upper
 +        right.
 +
 +Q $101) The KIM-1 keypad had the common 0-9A-F keys on the keypad, but
 +        also had some special keys.  Name them.
 +
 +A $101) GO (Go) Executes an instruction and displays the address of next,
 +        ST (Stop) Stops execution of program and return control to monitor, 
 +        RS (Reset), 
 +        AD (Address) Address entry mode, 
 +        DA (Data) Data entry mode, 
 +        PC (Program Counter) Displays and restores program counter to values
 +                             in PCL and PCH,
 +        +  (Increment) Increments the address without changing the entry mode.
 +
 +Q $102) The KIM-1 was a set of modules that could be plugged together to
 +        expand the system.  Each module had a model number.  What was the 
 +        model number of the KIM-1 motherboard?
 +
 +A $102) The KIM-4.  
 +
 +Q $103) On the 1525 line of printers, if you wanted to create the following
 +        graphic, what bytes would you send to the printer after turning on
 +        graphics mode?
 +        
 +        ****
 +        *  *
 +        *  *
 +        *  *
 +        *  *
 +        *  *
 +        ****
 +
 +A $103) I guess I should have stipulated that this is a bitmap.  ASCII just
 +        has a few limitations.  Anyway, the correct bytes to send are:
 +        255, 193, 193, 255.  You got these by assigning each bit in a column
 +        a value, and adding 128 to the result for each column.  
 +
 +Q $104) What is the horizontal resolution of the 1525 line of printers?
 +
 +A $104) Character resolution:   80 chars, or 10 chars/inch (cpi).
 +        Graphics resolution:    480 dots, or 60 dots/inch (dpi).  
 +
 +Q $105) On Commodore drives, explain the difference between the B-R command
 +        and the U1 command.
 +
 +A $105) The two commands read in data from a disk sector.  However, the 
 +        U1 command always reads a full sector (255 bytes).  The B-R
 +        command reads the number of bytes specified in the first byte of
 +        the sector.  If the first byte is a 15, B-R will read 15 bytes
 +        from the sector.  (From the 1581 manual)
 +
 +Q $106) On the Commodore 1541 drive, what does the U: command do?
 +
 +A $106) This command has been traditionally used to reset Commodore drives,
 +        including the CBM 1541.  However, some early versions of the Drive
 +        DOS did not correctly handle this command.  In these versions, the 
 +        drive and computer failed to complete the command transaction 
 +        successfully, and what looked like a hung machine resulted.  
 +        Commodore later fixed this problem.  If U: seems to not work on
 +        your drive, try U; instead.  
 +
 +Q $107) What does the first routine in the 1541 drive ROM actually do?
 +
 +A $107) The function, called SETLDA and residing at $C100, turns on the
 +        drive active LED for the current drive.  The routine loads the
 +        current drive from $7F and sets bit 3 of DSKCNT ($1C00).
 +
 +Q $108) How many files will a 1581 disk drive hold?
 +
 +A $108) 296 files.  Note that it is not a multiple of 144.  
 +
 +Q $109) Commodore 1581 drives have a special "autoboot" feature that enables
 +        the drive to load and run a program off a disk upon drive bootup.
 +        What is the required name of the file?
 +
 +A $109) COPYRIGHT CBM 86
 +
 +Q $10A) What filetype must the file mentioned in $109 be?
 +
 +A $10A) USR.
 +
 +Q $10B) To power up a 1351 mouse in "joystick mode", what must the user do?
 +
 +A $10B) If one depresses the right mouse button during power-up, the 1351
 +        will behave just like a joystick.
 +
 +Q $10C) Describe the contents of the POTX or POTY registers when using a
 +        1351 mouse.
 +
 +A $10C) Each register holds the same type of information, just for a 
 +        separate axis, so we will describe just one register:
 +        
 +        Bit:   Function
 +        
 +        7      Don't care
 +        6-1    Mouse axis position mod 64.
 +        0      Noise Bit. (check this bit to see whether mouse has moved)
 +
 +Q $10D) Commodore computers typically use most of zero page for temporary
 +        variables and other items.  However, both the VIC-20 and the 64
 +        reserve 4 bytes for user programs that need zero page memory.  Where
 +        are these locations?
 +
 +A $10D) $FB-$FE (251-254).  I am not sure these were "reserved" for 
 +        programmers as much as they were just not utilized by the 
 +        CBM programmers.  
 +
 +Q $10E) Name the 16 colors available on the 64.
 +
 +A $10E) Black
 +        White
 +        Red
 +        Cyan (Light Blue-Green)
 +        Purple
 +        Green
 +        Blue
 +        Yellow
 +        Orange
 +        Brown
 +        Light Red
 +        Dark Gray (Gray 1)
 +        Medium Grey (Gray 2)
 +        Light Green 
 +        Light Blue
 +        Light Gray (Gray 3)
 +                         
 +Q $10F) Both the VIC-20 and the C64 emulate the operation of the 6551 UART.
 +        How many "mock 6551" registers are mapped into the memory map?
 +
 +A $10F) 5, from $293-$297 (659-663).  The register contents:
 +
 +        $293   6551 Control Register
 +        $294   6551 Command Register
 +        $295-6 6551 User Defined Baud Rate value.
 +        $297   6551 Status Register 
 +
 +
 +------------Commodore Trivia Edition #18 Questions (BEGIN)--------------
 +
 +Q $110) What is the name of the company that recently purchased the
 +        liquidated Commodore assets?
 +
 +Q $111) At one time, Commodore attempted to manufacture a dual drive
 +        version of the 1571 called the 1572.  For what technical reason
 +        did it utimately fail?
 +
 +Q $112) Over what computer system did a User Group sue Commodore and win?
 +
 +Q $113) In $103, the question asked how to create a graphic of a small box
 +        on the 1525.  In this quesrtion, we have made a different design.
 +        If you wanted to create the following graphic using individual
 +        dots on the printer, what bytes would you send to the printer after
 +        turning on graphics mode?
 +        
 +         **     * *
 +        *       ***
 +        *   **  ***
 +        *   * * * *
 +         ** **  * *
 +            * *
 +            **
 +
 +Q $114) (Some C65 questions)  How many SID chips does the the development
 +        Commodore 65 machine contain?
 +
 +Q $115) What CPU does the Commodore 65 use?
 +
 +Q $116) What is the alternate name for the Commodore 65?
 +
 +Q $117) How many processors does the internal 1581-compatible drive 
 +        on the C65 contain?
 +
 +Q $118) In the tradition of naming certian ICs after famous cartoon
 +        characters, one of the ICs in the C65 is named after a Warner
 +        Brothers cartoon character.  Which one?
 +
 +Q $119) What version of BASIC is included on the Commodore 65 in C65 mode?
 +
 +Q $11A) How many I/O ports does a Commodore 65 contain?
 +
 +Q $11B) What common Commodore 64 I/O port does the C65 NOT have?
 +
 +Q $11C) How many function keys are on a Commodore 65?
 +
 +Q $11D) What CBM disk drive DOS was used as the template for the internal
 +        C65 drive DOS?
 +
 +Q $11E) What resolution of text screen does the C65 power up in? (Please
 +        give answers in characters).
 +
 +Q $11F) What distinguishing non-textual characteristic in the C65 is not
 +        present in othe Commodore 8-bit computers? 
 +
 +The information in this between the lines marked by (BEGIN) and (END)
 +is copyright 1995 by Jim Brain.  Provided that the information
 +between the (BEGIN) and (END) lines is not changed except to correct
 +typographical errors, the so marked copyrighted information may be
 +reproduced in its entirety on other networks or in other mediums.  For 
 +more information about using this file, please contact the address 
 +shown below.
 +
 +Jim Brain
 +brain@mail.msen.com 
 +602 North Lemen
 +Fenton, MI  48430
 +(810) 737-7300 x8528
 +
 +Some are easy, some are hard, try your hand at:
 +    Commodore Trivia #18!
 +========================================================================
 +</code>
 +====== BFLI - New graphics modes 2 ======
 +<code>
 +by Pasi 'Albert' Ojala <albert@cs.tut.fi>
 +
 +One day I was watching some demos that used linecrunch routines for
 +whole-screen multicolor-graphics upscrollers.  I already had my
 +theories about how and why linecrunch worked, but because I had not
 +used it anywhere, the details were a bit vague.  In fact, I have
 +many times accidentally created linecrunch effects when trying to do
 +something else with $D011.  Probably every demo coder has.
 +
 +But you learn by doing.  I had the idea of using linecrunch for FLI
 +instead of a simple multicolor picture as it always seemed to be
 +used.  However, this has probably been done before and because I
 +don't like to do things that have been done before, I decided to use
 +linecrunch to show a two-screen-tall FLI picture.
 +
 +
 +_Linecrunch Basics_
 +
 +For those not familiar with linecrunch routines:  linecrunch is used
 +to scroll the screen UPWARDS by convincing VIC-II that it has
 +already showed more character rows than it in reality has shown.
 +Surprisingly (or then, maybe not :) this consists of fiddling with
 +$D011.  The timing is critical as always.
 +
 +Linecrunch works by setting $D011 equal the line before the current
 +line and VIC-II will happily think that it is time to move on to the
 +next character row - add 40 to the video matrix counter, 320 to the
 +graphics memory counter and be ready to start a bad line.  Or, maybe
 +'NOT to go back to the current row' would be a more suitable
 +description.  (Programming VIC-II is slowly becoming a science.)
 +
 +The required timing also does not cause bad lines so that you can
 +skip another line immediately on the successive line.  In addition,
 +lines can be skipped only after the first character row and half of
 +the second character row have been displayed.  This has something to
 +do with the way VIC-II decides when there is a bad line.
 +
 +Because linecrunch causes VIC-II to skip rows, it will run out of
 +video matrix and color memory (and graphics memory) before reaching
 +the end of the screen.  However, VIC-II does not stop displaying the
 +graphics nor does it reset the internal counters.  The counters keep
 +on running and wrap around instead.
 +
 +Normally, when VIC-II is displaying the last character row, it is
 +showing the memory from offsets $3c0 to $3e7.  If VIC-II has skipped
 +one character row, it is displaying from $3e8 to $40f instead.  But,
 +there are only 10 bits for the video matrix counter (0..1023), so it
 +wraps around to zero after $3ff.  This means that the beginning of
 +the video matrix is displayed at the bottom of the screen.  The
 +character rows become shifted by 24 character positions to the right
 +because there were originally 24 unused memory locations at the end
 +of the memory (1000..1023).  (To be honest, sprite image pointers
 +are not unused memory, but they are not used with normal FLI.)
 +
 +         ____________________    ____________________
 +        |abcdefghijklmnopqrst|  |abcdefghijklmnopqrst|
 +        |                    |  |--------------------| <- Skipped row
 +        :                    :  :                    :
 +        :                    :  :                    :
 +        :                    :  :                    :
 +        |                    |  |normally last line  |
 +        |normally last line  |  |XXXXXXXXZZZZabcdefgh|
 +        `--------------------'  `--------------------'
 +                                X = unused mem      (1000..1015)
 +                                Z = sprite pointers (1016..1023)
 +
 +        Figure 1: Linecrunch
 +
 +
 +The same thing happens for color memory because it uses the same
 +counter for addressing the memory (in fact, color memory access and
 +character data access are performed simultaneosly, 12 bits at a
 +time).  The graphics memory behaves the same way, except that the
 +counter has three bits more and it counts at eight times the speed,
 +so that it wraps at the exact same time as the other counter.
 +
 +The first character row can't be used for linecrunch and the second
 +one is also lost in the process.  The first usable line to display
 +is the third character row.  However, those two lost rows can still
 +be used as an extension at the end of the first screen.  You must
 +notice, however, that the alignment has been changed.  After these
 +two rows have been displayed, the video bank is switched to get new
 +fresh data on the screen.
 +
 +
 +_Back to BFLI_
 +
 +Wrapped data is nothing difficult to work with.  It is just the
 +matter of writing the right conversion program.  Also, the normal
 +FLI routine can be used, we just have to make sure VIC always has
 +the right bank visible - simple LDA bank,x:sta $DD00 can accomplish
 +that.  The more difficult aspect is to make the display freely
 +locatable.  We have 32 kilobytes of graphics data, this is the main
 +reason we can't even think about using copying.  Linecrunch combined
 +with the bad line delaying technique will do the job much more
 +nicely.
 +
 +Figure 2 shows the principles.  To make things simpler I have chosen
 +location 0 to mean that the top of the picture is visible, 1 means
 +that the picture is scrolled one line upwards and so on.  We can see
 +that linecrunch is not used at all for the location 0.  To make the
 +picture start at the same point whether linecrunch has crunched
 +lines or not we compensate the non-lost raster lines by delaying the
 +next bad line.  When the location is n*8 (n=0,1,2..), the sum of the
 +linecrunched and delayed lines is constant - the graphics display
 +always starts at the same point.
 +
 +Then how do we deal with the location values that are not evenly
 +dividable by eight ?  Now, lets assume that the location is L, and
 +we have C, which is the location divided by eight (C = L/8), and R,
 +which is the remainder (R = L%8).  To make the picture scroll to the
 +right position, we need to delay the bad line less than before - R
 +lines less for location L than for location C*8.  E.g.  for location
 +2 we delay the bad line two lines less than for location 0.  This
 +also shows that we need 7 lines more than is needed for to
 +compensate for the linecrunch.
 +
 +Determining the number of linecrunch lines is a recursive process,
 +because when you use more linecrunch lines, that decreases the
 +number of lines you have available for the display and you need
 +bigger range for the location value.  The linecrunch can be started
 +after 12 lines, and we need at least 7 lines to use the soft
 +y-scroll.  This makes 181 lines available for the display
 +originally.
 +
 +Because we need to show 400 lines of graphics, we would need
 +(400-181)/8=28 linecrunch lines.  However, this in turn reduces the
 +number of lines we have for graphics to 181-28=153 and we need
 +(400-153)/8=31 linecrunch lines.  Again, 181-31 is 150.  We get
 +(400-150)/8=32 and there it finally converges and we have 149 lines
 +for graphics, which makes location values 0..251 valid.
 +
 +
 +Location        0              ..          ..   251
 +
 +                ___________________..   ___________..   ________
 +                ___________________..   ___________..   ________
 +Linecrunch      -------------------..   ___________..
 +                ^             ^
 +                |                         ^
 +                |                         |
 +Bad line delayed|                         |
 +                |                               ========
 +                |                               244
 +                |             ___..               :
 +                v       ________0             ___..   :
 +Gfx Enabled     ________0_______1__..   ________8__..   250_____
 +                0                               251
 +                1                         10      252
 +                2                   10      11      253
 +                3                   11      12      254
 +                4                   12      13      255
 +                5                   13      14      256
 +                6                   14      15      257
 +                7                   15      16      258
 +                :       :       :       :       :       :
 +                :       :       :       :       :       :
 +                148     149     150..   156     157..   399
 +
 +        Figure 2: Linecrunch and DMA delay in BFLI
 +                  (Graphics lines not in scale)
 +
 +
 +_Clipping added_
 +
 +Now we can scroll the picture to any location we want, but the top
 +of the picture is not clipped and it is very annoying to watch.  We
 +need to enable the graphics at the same point regardless of the
 +y-scroll value.  The answer is in the extended color mode (ECM).
 +
 +When both ECM and multicolor mode (MCM) are selected, VIC-II will
 +turn the display to black.  This is because there is a conflicting
 +situation and it just can't decide which color scheme to use.  The
 +video accesses will continue to happen just like before, the data is
 +just not displayed.  When the ECM bit is cleared again, the normal
 +multicolor graphics is shown.
 +
 +So, we set the ECM bit and start to display the first eight lines of
 +the FLI.  Because the FLI routine already writes to $D011, we just
 +make sure the ECM bit is set in the first R number of writes to
 +$D011 and zero in all other.
 +
 +The viewer is now 'complete' You can take a look at the code below
 +or you can get C64Gfx1_4.lha and see it in action yourself and not
 +just rely on my word.  The package includes converter programs for
 +BFLI, FLI and Koala (ANSI-C), couple of example pictures and viewers
 +for PAL and NTSC machines.
 +
 +-Pasi 'Albert' Ojala    albert@cs.tut.fi
 +
 +--------------------------------------------------------------------------
 +
 +BFLI viewer program for PAL machines
 +
 +UPOS   = $C00   ; temporary area for tables
 +BANK   = $D00   ;  UPOS for linecrunch, BANK for FLI bank select
 +RASTER = 29     ; where to position the sprite -> IRQ 20 lines later
 +DUMMY  = $FFF   ; dummy location for timing purposes
 +FLISZ  = 19-1   ; visible FLI size in character rows - 1
 +
 +*= $810
 +        SEI
 +        LDA #$7F:STA $DC0D      ; IRQ setup
 +        LDA #1:STA $D01A
 +        STA $D015:STA KEYW+1
 +        LDA #<IRQ:STA $314
 +        LDA #>IRQ:STA $315
 +        LDA #RASTER:STA $D001:CLC:ADC #20:STA $D012
 +        LDA #0:STA $D017
 +        LDA #0:STA 2
 +        JSR NEWPOS              ; Init the FLI routines
 +        LDA #$A:STA $D011       ; Blank screen
 +        LDX #23                 ; Init tables
 +BLOOP   LDA #$94:STA BANK,X
 +        LDA #$96:STA BANK+24,X
 +        DEX:BPL BLOOP
 +        LDX #15
 +LOOP0   LDA YINIT,X:AND #$77    ; Change to $37 to better see the
 +        STA UPOS,             ;  workings of the routines
 +        STA UPOS+16,X
 +        STA UPOS+32,X
 +        DEX:BPL LOOP0
 +
 +        LDA #$34:STA 1          ; Copy to the last video bank
 +        LDA #$80:STA SRC+2      ; from $8000-$BFFF to $C000-$FFFF
 +        LDA #$C0:STA DST+2
 +        LDX #0:LDY #$40
 +SRC     LDA $8000,X
 +DST     STA $C000,X
 +        INX:BNE SRC
 +        INC SRC+2:INC DST+2
 +        DEY:BNE SRC
 +        LDA #$37:STA 1
 +
 +        LDX #0                  ; Init color memory
 +LP      LDA $3C00,X:STA $D800,X ; All 1024 bytes are used
 +        LDA $3D00,X:STA $D900,X ;  - some even twice!
 +        LDA $3E00,X:STA $DA00,X
 +        LDA $3F00,X:STA $DB00,X
 +        INX:BNE LP
 +        LDA $DC0D:CLI
 +
 +KEYW    LDX #0:BNE KEYW         ; Wait for space to be pressed
 +        SEI                     ; System to normal
 +        LDA #$37:STA 1
 +        JSR $FDA3
 +        LDA #$97:STA $DD00
 +        JSR $E5A0
 +        LDY #3
 +IRQL    LDA $FD30,Y:STA $314,Y
 +        DEY:BPL IRQL
 +
 +        LDX #0:LDA #1           ; Clear color memory
 +CLL     STA $D800,X:STA $D900,X
 +        STA $DA00,X:STA $DB00,X
 +        INX:BNE CLL
 +        CLI:RTS
 +
 +YINIT   BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F
 +        BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F
 +
 +*=*-<*+256
 +
 +IRQ     LDA #$18:STA $D016:LDX #0:LDA #$5A
 +        INC DUMMY:DEC DUMMY             ; Synchronization
 +        STX $D020:STX $D021:STA $D011
 +
 +        LDA #$15:STA $D018
 +        LDA #$97:STA $DD00
 +        LDX #44                 ; Wait for the 4th line
 +LL      DEX:BPL LL:NOP
 +        LDX #0
 +
 +LOOP3   NOP                     ; Linecrunch-part routine
 +        LDA UPOS+6,X:INC DUMMY:STA $D011
 +        NOP:NOP:INC DUMMY
 +        NOP:NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP:NOP
 +        INX
 +E1      CPX #$10:BNE LOOP3      ; Skip that many character rows-4
 +        BIT $EA
 +LOOP4   LDA UPOS,X:INC DUMMY:STA $D011
 +        NOP:NOP:NOP:INC DUMMY
 +        NOP:NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP:LDA #0
 +        INX
 +E2      CPX #$1F:BNE LOOP4      ; Delay DMA until we are at the
 +                                ;  'same place' each time
 +
 +        LDA #0:STA $D020        ; Now wait for the bad line and start FLI
 +        BIT $EA:NOP
 +        NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP
 +        NOP:NOP:NOP:NOP
 +B0      LDA #$92:STA $DD00:NOP  ; The right video bank
 +
 +        ; Wait for 0-7 lines to set the ECM mode off
 +        ;  (makes the graphics visible)
 +
 +F0      LDA #0:STA $D011:LDA #$08:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F1      LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F2      LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F3      LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F4      LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F5      LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F6      LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +F7      LDA #0:STA $D011:LDA #$78:STA $D018
 +        LDX #FLISZ:NOP:NOP:NOP:BIT $EA
 +
 +        ; Do FLI 18 more character rows
 +
 +F8      LDA #0:STA $D011:LDA #$08:STA $D018
 +B1      LDA BANK,X:STA $DD00:BIT $EA
 +F9      LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FA      LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FB      LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FC      LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FD      LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FE      LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
 +FF      LDA #0:STA $D011:LDA #$78:STA $D018:NOP:NOP:DEX:BMI EFLI:JMP F8
 +EFLI    NOP
 +        LDA #$FC
 +WL      CMP $D012:BNE WL
 +        INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY
 +        INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY
 +        INC DUMMY:INC DUMMY:STA $D020
 +
 +        JSR NEWPOS      ; Update the location
 +        JSR CHPOS       ; Change to a new location
 +        LDA $DC01:AND #$10:BNE OV3      ; Check for the space bar
 +        LDA #0:STA KEYW+1
 +OV3     LDX #$53:STX $D011:INC $D019:JMP $EA81
 +
 +NEWPOS  LDA #0          ; Init the IRQ routine for this position
 +        LSR:LSR:LSR:CLC:ADC #4:STA E1+1
 +        LDA #7:SEC:SBC NEWPOS+1:AND #7:TAX:TAY:CLC:ADC #35:STA E2+1
 +        LDA UPOS+3+7,Y:DEX:BMI J0:AND #$3F
 +J0      STA F7+1:AND #$3F:STA FF+1
 +        LDA UPOS+3+6,Y:DEX:BMI J1:AND #$3F
 +J1      STA F6+1:AND #$3F:STA FE+1
 +        LDA UPOS+3+5,Y:DEX:BMI J2:AND #$3F
 +J2      STA F5+1:AND #$3F:STA FD+1
 +        LDA UPOS+3+4,Y:DEX:BMI J3:AND #$3F
 +J3      STA F4+1:AND #$3F:STA FC+1
 +        LDA UPOS+3+3,Y:DEX:BMI J4:AND #$3F
 +J4      STA F3+1:AND #$3F:STA FB+1
 +        LDA UPOS+3+2,Y:DEX:BMI J5:AND #$3F
 +J5      STA F2+1:AND #$3F:STA FA+1
 +        LDA UPOS+3+1,Y:DEX:BMI J6:AND #$3F
 +J6      STA F1+1:AND #$3F:STA F9+1
 +        LDA UPOS+3+0,Y:DEX:BMI J7:AND #$3F
 +J7      STA F0+1:AND #$3F:STA F8+1
 +        LDA #$96:STA B0+1:LDA #199:SEC:SBC NEWPOS+1:BCC OV2
 +        LSR:LSR:LSR:CLC:ADC #5:STA B1+1
 +        RTS
 +OV2     LDA #0:STA B1+1:LDX #$94:STX B0+1:RTS
 +
 +CHPOS   LDX NEWPOS+1
 +        LDA $DC00:TAY           ; Get joystick
 +        AND #$10:BNE DIR        ; If no button pressed
 +        TYA:AND #1:BEQ UP       ; If joy up
 +        TYA:AND #2:BEQ DOWN     ; If joy down
 +        RTS
 +DIR     LDA #0:BEQ UP
 +DOWN    DEX:CPX #$FF:BNE DOK
 +        LDX #0:STX DIR+1        ; Change direction
 +DOK     STX NEWPOS+1:RTS
 +UP      INX:CPX #$FD:BCC UOK    ; 251(locations)+149(visible)=400
 +        LDX #$FC:STX DIR+1      ; Change direction
 +UOK     STX NEWPOS+1:RTS
 +
 +
 +--------------------------------------------------------------------------
 +
 +The BFLI file format:
 +
 +                        File            BFLI Display
 +        Lines           Offset          Offset          Lines     Size
 +Colors  0-1.3           0..55           944..999        22.7-24   56
 +  I     1.3-2           56..79          -                         24
 +        2-24            80..999         0..919          0-22      920
 +        24-24.7         1000..1023      920..943        22-22.7   24
 +
 +  II    0-1.3           0..55           1968..2024      49.3-50.6 56
 +        1.3-24.7        56..1023        1000..1967      24-49.3   968
 +
 +
 +Gfx     0-1.3           0..447          7552..7999      22.7-24   448
 +  I     1.3-2           448..639        -                         192
 +        2-24            640..7999       0..7359         0-22      7360
 +        24-24.7         8000..8191      7360..7551      22-22.7   192
 +
 +  II    0-1.3           0..447          15744..16192    49.3-50.6 448
 +        1.3-24.7        448..8191       8000..15743     24-49.3   7744
 +
 +========================================================================
 +</code>
 +====== Making stable raster routines (C64 and VIC-20) ======
 +<code>
 +by Marko Makela (Marko.Makela@HUT.FI)
 +
 +Preface
 +
 +Too many graphical effects, also called raster effects, have been
 +coded in a very sloppy way.  For instance, if there are any color bars
 +on the screen in a game or demo, the colors often jitter a bit,
 +e.g. they are not stable.  And also, it is far too easy to make
 +virtually any demo crash by hitting the Restore key, or at least cause
 +visual distortions on the screen.
 +
 +As late as a year ago I still hadn't coded a stable raster interrupt
 +routine myself.  But then I had to do it, since I was researching the
 +video chip timing details together with my German friend Andreas
 +Boose.  It was ashaming that we had the same level of knowledge when
 +it came to the hardware, but he was the only of us who had written a
 +stable raster routine.  Well, finally I made me to start coding.  I
 +used the same double-interrupt idea as Andreas used in his routine.
 +
 +After a couple of errors my routine worked, and I understood how it
 +works exactly.  (This is something that separates us normal coders
 +from demo people: They often code by instinct; by patching the routine
 +until it works, without knowing exactly what is happening.  That's why
 +demos often rely on weird things, like crash if the memory is not
 +initialized properly.)
 +
 +In this article, I document two methods of creating stable raster
 +routines on Commodore computers.  The principles apply for most 8-bit
 +computers, not only Commodores, but raster effects are very rarely
 +seen on other computers.
 +
 +
 +Background
 +
 +What are raster effects?  They are effects, where you change the
 +screen appearance while it is being drawn.  For instance, you can set
 +the screen color to white in the top of the screen, and to black in
 +the middle of the screen.  In that way, you will get a picture whose
 +top half is white and bottom half black.  Normally such effects are
 +implemented with interrupt routines that are executed synchronized
 +with the screen refresh.
 +
 +The video chip on the Commodore 64 and many other videochips have a
 +special interrupt feature called the Raster interrupt.  It will
 +generate an IRQ in the beginning of a specified raster line.  On other
 +computers, like the VIC-20, there is no Raster interrupt, but you can
 +generate the interrupts with a timer, provided that the timer and the
 +videochip are clocked from the same source.
 +
 +Even if the processor gets an interrupt signal at the same position on
 +each video frame, it won't always be executing the first instruction
 +of the interrupt routine at the same screen position.  The NMOS 6502
 +machine instructions can take 2 to 9 machine cycles to execute, and if
 +the main program contains instructions of very varying lengths, the
 +beginning position of the interrupt can jump between 7 different
 +positions.  This is why you need to synchronize the raster routine
 +when doing serious effects.
 +
 +Also, executing the interrupt sequence will take 7 additional cycles,
 +and the interrupt sequence will only start after the current
 +instruction if the interrupt arrived at least two cycles before the
 +end of the current instruction.  It is even possible that an interrupt
 +arrives while interrupts are disabled and the processor is just
 +starting to execute a CLI instruction.  Alas, the processor will not
 +jump to the interrupt right after the CLI, but it will execute the
 +next instruction before jumping to it.  This is natural, since the CLI
 +takes only two cycles.  But anyway, this is only a constant in our
 +equation, and actually out of the scope of this article.
 +
 +How to synchronize a raster interrupt routine?  The only way is to
 +check the current screen position and delay appropriately many cycles.
 +There are several ways of doing this, some of which are very awful and
 +inefficient.  The ugliest ways of doing this on the Commodore 64 I
 +know are busy-waiting several raster lines and polling the raster line
 +value, or using the Light pen feature, which will fail if the user
 +presses the fire button on Joystick port 1.  Here I will present two
 +ways, both very elegant in my opinion.
 +
 +
 +Using an auxiliary timer
 +
 +On the VIC-20, there is no Raster interrupt feature in the video chip.
 +All you can do is to use a timer for generating raster interrupts.
 +And if you use two timers running at a constant phase difference, you
 +can get full synchronization.  The first timer generates the raster
 +interrupt, and the second timer, the auxiliary timer, tells the raster
 +routine where it is running.  Actually you could even use the first
 +timer also for the checking, but the code will look nicer in the way I
 +will be presenting now.  Besides, you can use the auxiliary timer idea
 +even when real raster interrupts are available.
 +
 +The major drawback of using an auxiliary timer is initializing it.
 +The initialization routine must synchronize with the screen, that is,
 +wait for the beginning of the wanted raster line.  To accomplish this,
 +the routine must first wait for a raster line that occurs a bit
 +earlier.  About the only way to do this is with a loop like
 +
 +                LDA #value
 +        loop    CMP raster
 +                BNE loop
 +
 +One round of this loop will take 4+3=7 cycles to execute, assuming
 +that absolute addressing is being used.  The loop will be finished if
 +the raster register contains the wanted value while the processor
 +reads it on the last cycle of the CMP instruction.  The raster
 +register can actually have changed already on the first cycle of the
 +BNE instruction on the previous run of the loop, that is 7 cycles
 +earlier!
 +
 +Because of this, the routine must poll the raster register for several
 +raster lines, always consuming one cycle more if the raster register
 +changed too early.  As the synchronization can be off at most by 7
 +cycles, a loop of 7 raster register value changes would do, but I made
 +the loop a bit longer in my VIC-20 routine.  (Well, I have to admit it,
 +I was too lazy to make it work only with 7 rounds.)
 +
 +After the initialization routine is fully synchronized the screen, it
 +can set up the timer(s) and interrupts and exit.  The auxiliary timer
 +in my VIC-20 demo routine is several dozens of cycles after the
 +primary timer, see the source code for comments.  It is arranged so
 +that the auxiliary timer will be at least 0 when it is being read in
 +the raster routine.  The raster routine will wait as many extra cycles
 +as the auxiliary timer reads, however at most 15 cycles.
 +
 +
 +Using double raster interrupt
 +
 +On the Commodore 64, I have never seen the auxiliary timer scheme
 +being used.  Actually I haven't seen it being used anywhere, I was
 +probably the first one who made a stable raster interrupt routine on
 +the VIC-20.  Instead, the double interrupt method is becoming the
 +standard on the C64 side.
 +
 +The double interrupt method is based entirely on the Raster interrupt
 +feature of the video chip.  In the first raster interrupt routine, the
 +program sets up another raster interrupt on a further line, changes
 +the interrupt vector and enables interrupts.
 +
 +In the place where the second raster interrupt will occur, there will
 +be 2-byte instructions in the first interrupt routine.  In this way,
 +the beginning of the next raster interrupt will be off at most by one
 +cycle.  Some coders might not care about this one cycle, but if you
 +can do it right, why wouldn't you do it right until the end?
 +
 +At the beginning of the second raster interrupt routine, you will read
 +the raster line counter register at the point where it is about to
 +change.  When the raster routine is being executed, there are two
 +possibilities: Either the raster counter has just changed, or it will
 +change on the next cycle.  So, you just need to compare if the
 +register changed one cycle too early or not, and delay a cycle when
 +needed.  This is easily accomplished with a branch to the next address.
 +
 +Of course, somewhere in your second raster interrupt routine you must
 +restore the original raster interrupt position and set the interrupt
 +vector to point to the first interrupt routine.
 +
 +
 +Applying in practice
 +
 +I almost forgot my complaints about demos crashing when you actively
 +hit the Restore key.  On the VIC-20, you can disable NMI interrupts
 +generated by the Restore key, and on the C64, you can generate an NMI
 +interrupt with the CIA2 timer and leave the NMI-line low, so that no
 +further high-to-low transitions will be recognized on the line.  The
 +example programs demonstrate how to do this.
 +
 +So far, this article has been pretty theoretical.  To apply these
 +results in practice, you must definitely know how many CPU clock
 +cycles the video chip consumes while drawing a scan line.  This is
 +fairly easy to measure with a timer interrupt, if you patch the
 +interrupt handler so that it changes the screen color on each run.
 +Set the timer interval to LINES*COLUMNS cycles, where LINES is the
 +amount of raster lines and COLUMNS is your guess for the amount of
 +clock cycles spent in a raster line.
 +
 +If your guess is right, the color will always be changed in the same
 +screen position (neglecting the 7-cycle jitter).  When adjusting the
 +timer, remember that the timers on the 6522 VIA require 2 cycles for
 +re-loading, and the ones on the 6526 CIA need one extra cycle.  Keep
 +trying different timer values until you the screen color changes at
 +one fixed position.
 +
 +Commodore used several different values for LINES and COLUMNS on its
 +videochips.  They never managed to make the screen refresh rate
 +exactly 50 or 60 Hertz, but they didn't hesitate to claim that their
 +computers comply with the PAL-B or NTSC-M standards.  In the following
 +tables I have gathered some information of some Commodore video chips.
 +
 +
 +  NTSC-M systems:
 +
 +            Chip      Crystal  Dot      Processor Cycles/ Lines/
 +    Host    ID        freq/Hz  clock/Hz clock/Hz  line    frame
 +    ------  --------  -------- -------- --------- ------- ------
 +    VIC-20  6560-101  14318181  4090909   1022727      65    261
 +    C64     6567R56A  14318181  8181818   1022727      64    262
 +    C64     6567R8    14318181  8181818   1022727      65    263
 +
 +  Later NTSC-M video chips were most probably like the 6567R8.  Note
 +  that the processor clock is a 14th of the crystal frequency on all
 +  NTSC-M systems.
 +
 +  PAL-B systems:
 +
 +            Chip      Crystal  Dot      Processor Cycles/ Lines/
 +    Host    ID        freq/Hz  clock/Hz clock/Hz  line    frame
 +    ------  --------  -------- -------- --------- ------- ------
 +    VIC-20  6561-101   4433618  4433618   1108405      71    312
 +    C64     6569      17734472  7881988    985248      63    312
 +
 +  On the PAL-B VIC-20, the crystal frequency is simultaneously the dot
 +  clock, which is BTW a 4th of the crystal frequency used on the C64.
 +  On the C64, the crystal frequency is divided by 18 to generate the
 +  processor clock, which in turn is multiplied by 8 to generate the
 +  dot clock.
 +
 +  The basic timings are the same on all 6569 revisions, and also on
 +  any later C64 and C128 video chips.  If I remember correctly, these
 +  values were the same on the C16 videochip TED as well.
 +
 +Note that the dot clock is 4 times the processor clock on the VIC-20,
 +and 8 times that on the C64.  That is, one processor cycle is half a
 +character wide on the VIC-20, and a full character on a C64.  I don't
 +have exact measurements of the VIC-20 timing, but it seems that while
 +the VIC-20 videochips draw the characters on the screen, it first
 +reads the character code, and then, on the following video cycle, the
 +appearance on the current character line.  There are no bad lines,
 +like on the C64, where the character codes (and colors) are fetched on
 +every 8th raster line.
 +
 +Those ones who got upset when I said that Commodore has never managed
 +to make a fully PAL-B or NTSC-M compliant 8-bit computer should take a
 +closer look at the "Lines/frame" columns.  If that does not convince
 +you, calculate the raster line rate and the screen refresh rate from
 +the values in the table and see that they don't comply with the
 +standards.  To calculate the line rate, divide the processor clock
 +frequency by the amount of cycles per line.  To get the screen refresh
 +rate, divide that frequency by the amount of raster lines.
 +
 +
 +The Code
 +
 +OK, enough theory and background.  Here are the two example programs,
 +one for the VIC-20 and one for the C64.  In order to fully understand
 +them, you need to know the exact execution times of NMOS 6502
 +instructions.  (All 8-bit Commodore computers use the NMOS 6502
 +processor core, except the C65 prototype, which used a inferior CMOS
 +version with all nice poorly-documented features removed.)  You should
 +check the 64doc document, available on my WWW pages at
 +http://www.hut.fi/~msmakela/cbm/emul/x64/64doc.html, or via FTP at
 +ftp.funet.fi:/pub/cbm/documents/64doc.  I can also e-mail it to you on
 +request.
 +
 +Also, I have written a complete description of the video timing on the
 +6567R56A, 6567R8 and 6569 video chips, which could maybe be turned
 +into another C=Hacking article.  The document is currently partially
 +in English and partially in German.  The English part is available
 +from ftp.funet.fi as /pub/cbm/documents/pal.timing, and I can send
 +copies of the German part (screen resolution, sprite disturbance
 +measurements, and more precise timing information) via e-mail.
 +
 +The code is written for the DASM assembler, or more precisely for a
 +extended ANSI C port of it made by Olaf Seibert.  This excellent
 +cross-assembler is available at ftp.funet.fi in /pub/cbm/programming.
 +
 +First the raster demo for the VIC-20.  Note that on the VIC-20, the
 +$9004 register contains the upper 8 bits of the raster counter.  So,
 +this register changes only on every second line.  I have tested the
 +program on my 6561-101-based VIC-20, but not on an NTSC-M system.
 +
 +It was hard to get in contact with NTSC-M VIC-20 owners.  Daniel
 +Dallmann, who has a NTSC-M VIC-20, although he lives in Germany, ran
 +my test to determine the amount of cycles per line and lines per frame
 +on the 6560-101.  Unfortunately, the second VIA of his VIC-20 is
 +partially broken, and because of this, this program did not work on
 +his computer.  Craig Bruce ran the program once, and he reported that
 +it almost worked.  I corrected a little bug in the code, so that now
 +the display should be stable on an NTSC-M system, too.  But the actual
 +raster effect, six 16*16-pixel boxes centered at the top border, are
 +very likely to be off their position.
 +
 +
 +  processor 6502
 +
 +NTSC    = 1
 +PAL     = 2
 +
 +;SYSTEM = NTSC  ; 6560-101: 65 cycles per raster line, 261 lines
 +SYSTEM  = PAL   ; 6561-101: 71 cycles per raster line, 312 lines
 +
 +#if SYSTEM & PAL
 +LINES = 312
 +CYCLES_PER_LINE = 71
 +#endif
 +#if SYSTEM & NTSC
 +LINES = 261
 +CYCLES_PER_LINE = 65
 +#endif
 +TIMER_VALUE = LINES * CYCLES_PER_LINE - 2
 +
 +  .org $1001    ; for the unexpanded Vic-20
 +
 +; The BASIC line
 +
 +basic:
 +  .word 0$      ; link to next line
 +  .word 1995    ; line number
 +  .byte $9E     ; SYS token
 +
 +; SYS digits
 +
 +  .if (* + 8) / 10000
 +  .byte $30 + (* + 8) / 10000
 +  .endif
 +  .if (* + 7) / 1000
 +  .byte $30 + (* + 7) % 10000 / 1000
 +  .endif
 +  .if (* + 6) / 100
 +  .byte $30 + (* + 6) % 1000 / 100
 +  .endif
 +  .if (* + 5) / 10
 +  .byte $30 + (* + 5) % 100 / 10
 +  .endif
 +  .byte $30 + (* + 4) % 10
 +0$:
 +  .byte 0,0,0   ; end of BASIC program
 +
 +start:
 +  lda #$7f
 +  sta $912e     ; disable and acknowledge interrupts
 +  sta $912d
 +  sta $911e     ; disable NMIs (Restore key)
 +
 +;synchronize with the screen
 +sync:
 +  ldx #28       ; wait for this raster line (times 2)
 +0$:
 +  cpx $9004
 +  bne 0$        ; at this stage, the inaccuracy is 7 clock cycles
 +                ; the processor is in this place 2 to 9 cycles
 +                ; after $9004 has changed
 +  ldy #9
 +  bit $24
 +1$:
 +  ldx $9004
 +  txa
 +  bit $24
 +#if SYSTEM & PAL
 +  ldx #24
 +#endif
 +#if SYSTEM & NTSC
 +  bit $24
 +  ldx #21
 +#endif
 +  dex
 +  bne *-1       ; first spend some time (so that the whole
 +  cmp $9004     ; loop will be 2 raster lines)
 +  bcs *+2       ; save one cycle if $9004 changed too late
 +  dey
 +  bne 1$
 +                ; now it is fully synchronized
 +                ; 6 cycles have passed since last $9004 change
 +                ; and we are on line 2(28+9)=74
 +
 +;initialize the timers
 +timers:
 +  lda #$40      ; enable Timer A free run of both VIAs
 +  sta $911b
 +  sta $912b
 +
 +  lda #<TIMER_VALUE
 +  ldx #>TIMER_VALUE
 +  sta $9116     ; load the timer low byte latches
 +  sta $9126
 +
 +#if SYSTEM & PAL
 +  ldy #7        ; make a little delay to get the raster effect to the
 +  dey           ; right place
 +  bne *-1
 +  nop
 +  nop
 +#endif
 +#if SYSTEM & NTSC
 +  ldy #6
 +  dey
 +  bne *-1
 +  bit $24
 +#endif
 +
 +  stx $9125     ; start the IRQ timer A
 +                ; 6560-101: 65 cycles from $9004 change
 +                ; 6561-101: 77 cycles from $9004 change
 +  ldy #10       ; spend some time (1+5*9+4=55 cycles)
 +  dey           ; before starting the reference timer
 +  bne *-1
 +  stx $9115     ; start the reference timer
 +
 +pointers:
 +  lda #<irq     ; set the raster IRQ routine pointer
 +  sta $314
 +  lda #>irq
 +  sta $315
 +  lda #$c0
 +  sta $912e     ; enable Timer A underflow interrupts
 +  rts           ; return
 +
 +irq:
 +; irq (event)   ; > 7 + at least 2 cycles of last instruction (9 to 16 total)
 +; pha           ; 3
 +; txa           ; 2
 +; pha           ; 3
 +; tya           ; 2
 +; pha           ; 3
 +; tsx           ; 2
 +; lda $0104,  ; 4
 +; and #xx       ; 2
 +; beq           ; 3
 +; jmp ($314)    ; 5
 +                ; ---
 +                ; 38 to 45 cycles delay at this stage
 +
 +  lda $9114     ; get the NMI timer A value
 +                ; (42 to 49 cycles delay at this stage)
 +; sta $1e00     ; uncomment these if you want to monitor
 +; ldy $9115     ; the reference timer on the screen
 +; sty $1e01
 +  cmp #8        ; are we more than 7 cycles ahead of time?
 +  bcc 0$
 +  pha           ; yes, spend 8 extra cycles
 +  pla
 +  and #7        ; and reset the high bit
 +0$:
 +  cmp #4
 +  bcc 1$
 +  bit $24       ; waste 4 cycles
 +  and #3
 +1$:
 +  cmp #2        ; spend the rest of the cycles
 +  bcs *+2
 +  bcs *+2
 +  lsr
 +  bcs *+2       ; now it has taken 82 cycles from the beginning of the IRQ
 +
 +effect:
 +  ldy #16       ; perform amazing video effect
 +  lda $900f
 +  tax
 +  eor #$f7
 +0$:
 +  sta $900f
 +  stx $900f
 +  sta $900f
 +  stx $900f
 +  sta $900f
 +  stx $900f
 +  sta $900f
 +  stx $900f
 +  sta $900f
 +  stx $900f
 +  sta $900f
 +  stx $900f
 +  pha
 +  pla
 +#if SYSTEM & PAL
 +  pha
 +  pla
 +  nop
 +#endif
 +#if SYSTEM & NTSC
 +  bit $24
 +#endif
 +  nop
 +  dey
 +  bne 0$        ; end of amazing video effect
 +
 +  jmp $eabf     ; return to normal IRQ
 +
 +
 +And after you have recovered from the schock of seeing a VIC-20
 +program, here is an example for the C64.  It does also something
 +noteworthy; it removes the side borders on a normal screen while
 +displaying all eight sprites.  Well, it cannot remove the borders on
 +bad lines, and the bad lines look pretty bad.  But I could use the
 +program for what I wanted: I measured the sprite distortions on all
 +videochip types I had at hand.  (FYI: the sprites 0-2 get distorted at
 +the very right of the screen, and the sprites 6 and 7 are invisible at
 +the very left of the screen.  You will need a monitor with horizontal
 +size controls to witness these effects.)
 +
 +This program is really robust, it installs itself nicely to the
 +interrupt routine chain.  It even has an entry point for deinstalling
 +itself.  But in its robustness it uses self-modifying code to store
 +the original interrupt routine address. :-)
 +
 +The code also relies on the page boundaries in being where they are.
 +The cycles are counted so that the branches "irqloop" must take 4
 +cycles.  If the "irqloop" comes to the same CPU page with the branch
 +instructions, you must add one cycle to the loop in a way or another.
 +When coding the routine, I noticed again how stupid assembly coding
 +can be, especially conditional assembling.  In a machine language
 +monitor you have far better control on page boundaries.  BTW, you
 +might wonder why I disable the Restore key in a subroutine at the end
 +and not in the beginning of the program.  Well, the routine was so
 +long that it would have affected the "irqloop" page boundaries.  And I
 +didn't want to risk the modified programs working on all three
 +different videochip types on the first try.
 +
 +
 +In the code, there are some comments that document the video timing,
 +like this one:
 +
 +;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
 +;ssssssssss                                               ||ssssss Phi-2 VIC-II
 +;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
 +;          ^ now we are here
 +
 +The two vertical bars "|" denote optional cycles.  On PAL-B systems
 +(63 cycles per line), they are not present.  On 6567R56A, which has 64
 +cycles per line, there is one additional cycle on this position, and
 +the 6567R8 has two additional cycles there.
 +
 +The numbers 0 through 7 are sprite pointer fetches (from the end of
 +the character matrix, e.g. the text screen), the "s" characters denote
 +sprite image fetches, the "r"s are memory refresh, and the "g" are
 +graphics fetches.  The two idle video chip cycles are marked with "-".
 +On the processor timing line, the "=" signs show halted CPU, "x" means
 +free bus, and "X" means that the processor will be halted at once,
 +unless it is performing write cycles.
 +
 +  processor 6502
 +
 +; Select the video timing (processor clock cycles per raster line)
 +CYCLES = 65     ; 6567R8 and above, NTSC-M
 +;CYCLES = 64    ; 6567R5 6A, NTSC-M
 +;CYCLES = 63    ; 6569 (all revisions), PAL-B
 +
 +cinv = $314
 +cnmi = $318
 +raster = 52     ; start of raster interrupt
 +m = $fb         ; zero page variable
 +
 +  .org $801
 +basic:
 +  .word 0$      ; link to next line
 +  .word 1995    ; line number
 +  .byte $9E     ; SYS token
 +
 +; SYS digits
 +
 +  .if (* + 8) / 10000
 +  .byte $30 + (* + 8) / 10000
 +  .endif
 +  .if (* + 7) / 1000
 +  .byte $30 + (* + 7) % 10000 / 1000
 +  .endif
 +  .if (* + 6) / 100
 +  .byte $30 + (* + 6) % 1000 / 100
 +  .endif
 +  .if (* + 5) / 10
 +  .byte $30 + (* + 5) % 100 / 10
 +  .endif
 +  .byte $30 + (* + 4) % 10
 +
 +0$:
 +  .byte 0,0,0   ; end of BASIC program
 +
 +start:
 +  jmp install
 +  jmp deinstall
 +
 +install:        ; install the raster routine
 +  jsr restore   ; Disable the Restore key (disable NMI interrupts)
 +checkirq:
 +  lda cinv      ; check the original IRQ vector
 +  ldx cinv+1    ; (to avoid multiple installation)
 +  cmp #<irq1
 +  bne irqinit
 +  cpx #>irq1
 +  beq skipinit
 +irqinit:
 +  sei
 +  sta oldirq    ; store the old IRQ vector
 +  stx oldirq+1
 +  lda #<irq1
 +  ldx #>irq1
 +  sta cinv      ; set the new interrupt vector
 +  stx cinv+1
 +skipinit:
 +  lda #$1b
 +  sta $d011     ; set the raster interrupt location
 +  lda #raster
 +  sta $d012
 +  ldx #$e
 +  clc
 +  adc #3
 +  tay
 +  lda #0
 +  sta m
 +0$:
 +  lda m
 +  sta $d000,  ; set the sprite X
 +  adc #24
 +  sta m
 +  tya
 +  sta $d001,  ; and Y coordinates
 +  dex
 +  dex
 +  bpl 0$
 +  lda #$7f
 +  sta $dc0d     ; disable timer interrupts
 +  sta $dd0d
 +  ldx #1
 +  stx $d01a     ; enable raster interrupt
 +  lda $dc0d     ; acknowledge CIA interrupts
 +  lsr $d019     ; and video interrupts
 +  ldy #$ff
 +  sty $d015     ; turn on all sprites
 +  cli
 +  rts
 +
 +deinstall:
 +  sei           ; disable interrupts
 +  lda #$1b
 +  sta $d011     ; restore text screen mode
 +  lda #$81
 +  sta $dc0d     ; enable Timer A interrupts on CIA 1
 +  lda #0
 +  sta $d01a     ; disable video interrupts
 +  lda oldirq
 +  sta cinv      ; restore old IRQ vector
 +  lda oldirq+1
 +  sta cinv+1
 +  bit $dd0d     ; re-enable NMI interrupts
 +  cli
 +  rts
 +
 +; Auxiliary raster interrupt (for syncronization)
 +irq1:
 +; irq (event)   ; > 7 + at least 2 cycles of last instruction (9 to 16 total)
 +; pha           ; 3
 +; txa           ; 2
 +; pha           ; 3
 +; tya           ; 2
 +; pha           ; 3
 +; tsx           ; 2
 +; lda $0104,  ; 4
 +; and #xx       ; 2
 +; beq           ; 3
 +; jmp ($314)    ; 5
 +                ; ---
 +                ; 38 to 45 cycles delay at this stage
 +  lda #<irq2
 +  sta cinv
 +  lda #>irq2
 +  sta cinv+1
 +  nop           ; waste at least 12 cycles
 +  nop           ; (up to 64 cycles delay allowed here)
 +  nop
 +  nop
 +  nop
 +  nop
 +  inc $d012     ; At this stage, $d012 has already been incremented by one.
 +  lda #1
 +  sta $d019     ; acknowledge the first raster interrupt
 +  cli           ; enable interrupts (the second interrupt can now occur)
 +  ldy #9
 +  dey
 +  bne *-1       ; delay
 +  nop           ; The second interrupt will occur while executing these
 +  nop           ; two-cycle instructions.
 +  nop
 +  nop
 +  nop
 +oldirq = * + 1  ; Placeholder for self-modifying code
 +  jmp *         ; Return to the original interrupt
 +
 +; Main raster interrupt
 +irq2:
 +; irq (event)   ; 7 + 2 or 3 cycles of last instruction (9 or 10 total)
 +; pha           ; 3
 +; txa           ; 2
 +; pha           ; 3
 +; tya           ; 2
 +; pha           ; 3
 +; tsx           ; 2
 +; lda $0104,  ; 4
 +; and #xx       ; 2
 +; beq           ; 3
 +; jmp (cinv)    ; 5
 +                ; ---
 +                ; 38 or 39 cycles delay at this stage
 +  lda #<irq1
 +  sta cinv
 +  lda #>irq1
 +  sta cinv+1
 +  ldx $d012
 +  nop
 +#if CYCLES - 63
 +#if CYCLES - 64
 +  nop           ; 6567R8, 65 cycles/line
 +  bit $24
 +#else
 +  nop           ; 6567R56A, 64 cycles/line
 +  nop
 +#endif
 +#else
 +  bit $24       ; 6569, 63 cycles/line
 +#endif
 +  cpx $d012     ; The comparison cycle is executed CYCLES or CYCLES+1 cycles
 +                ; after the interrupt has occurred.
 +  beq *+2       ; Delay by one cycle if $d012 hadn't changed.
 +                ; Now exactly CYCLES+3 cycles have passed since the interrupt.
 +  dex
 +  dex
 +  stx $d012     ; restore original raster interrupt position
 +  ldx #1
 +  stx $d019     ; acknowledge the raster interrupt
 +  ldx #2
 +  dex
 +  bne *-1
 +  nop
 +  nop
 +  lda #20       ; set the amount of raster lines-1 for the loop
 +  sta m
 +  ldx #$c8
 +irqloop:
 +  ldy #2
 +  dey
 +  bne *-1       ; delay
 +  dec $d016     ; narrow the screen (exact timing required)
 +;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
 +;ssssssssss                                               ||ssssss Phi-2 VIC-II
 +;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
 +;          ^ now we are here
 +  stx $d016     ; expand the screen
 +#if CYCLES - 63
 +#if CYCLES - 64
 +  bit $24       ; 6567R8
 +#else
 +  nop           ; 6567R56A
 +#endif
 +#else
 +  nop           ; 6569
 +#endif
 +  dec m
 +  bmi endirq
 +  clc
 +  lda $d011
 +  sbc $d012
 +  and #7
 +  bne irqloop   ; This instruction takes 4 cycles instead of 3,
 +                ; because the page boundary is crossed.
 +badline:
 +  dec m
 +  nop
 +  nop
 +  nop
 +  nop
 +  dec $d016
 +;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
 +;ssssssssss    cccccccccccccccccccccccccccccccccccccccc   ||ssssss Phi-2 VIC-II
 +;==========xXXX========================================||***====== Phi-2 6510
 +;          ^ we are here
 +  stx $d016
 +;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
 +;ssssssssss                                               ||ssssss Phi-2 VIC-II
 +;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
 +;          ^ ^^- we are here (6569)
 +;          | \- or here (6567R56A)
 +;          \- or here (6567R8)
 +  ldy #2
 +  dey
 +  bne *-1
 +  nop
 +  nop
 +#if CYCLES - 63
 +#if CYCLES - 64
 +  nop           ; 6567R8, 65 cycles/line
 +  nop
 +  nop
 +#else
 +  bit $24       ; 6567R56A, 64 cycles/line
 +#endif
 +#else
 +  nop           ; 6569, 63 cycles/line
 +#endif
 +  dec m
 +  bpl irqloop   ; This is a 4-cycle branch (page boundary crossed)
 +endirq:
 +  jmp $ea81     ; return to the auxiliary raster interrupt
 +
 +restore:        ; disable the Restore key
 +  lda cnmi
 +  ldy cnmi+1
 +  pha
 +  lda #<nmi     ; Set the NMI vector
 +  sta cnmi
 +  lda #>nmi
 +  sta cnmi+1
 +  ldx #$81
 +  stx $dd0d     ; Enable CIA 2 Timer A interrupt
 +  ldx #0
 +  stx $dd05
 +  inx
 +  stx $dd04     ; Prepare Timer A to count from 1 to 0.
 +  ldx #$dd
 +  stx $dd0e     ; Cause an interrupt.
 +nmi = * + 1
 +  lda #$40      ; RTI placeholder
 +  pla
 +  sta cnmi
 +  sty cnmi+1    ; restore original NMI vector (although it won't be used)
 +  rts
 +
 +
 +Binaries
 +
 +Here are the programs in uuencoded format.  First the VIC-20 programs:
 +
 +Color boxes for the VIC-20, NTSC-M version (probably distorted display):
 +
 +begin 644 copper.6560
 +M`1`*$,L'GC0Q,#D```"I?XTND8TMD8T>D:(<[`20T/N@"20DK@20BB0D)"2B
 +M%<K0_<T$D+``B-#KJ4"-&Y&-*Y&I0Z)"C1:1C2:1H`:(T/TD)(XED:`*B-#]
 +MCA61J6R-%`.I$(T5`ZG`C2Z18*T4D<D(D`1(:"D'R020!"0D*0/)`K``L`!*
 +ML`"@$*T/D*I)]XT/D(X/D(T/D(X/D(T/D(X/D(T/D(X/D(T/D(X/D(T/D(X/
 +,D$AH)"3JB-#43+_J
 +`
 +end
 +
 +
 +Color boxes for the VIC-20, PAL-B version:
 +
 +begin 644 copper.6561
 +M`1`*$,L'GC0Q,#D```"I?XTND8TMD8T>D:(<[`20T/N@"20DK@20BB0DHAC*
 +MT/W-!)"P`(C0[:E`C1N1C2N1J8:B5HT6D8TFD:`'B-#]ZNJ.)9&@"HC0_8X5
 +MD:EJC10#J1"-%0.IP(TND6"M%)')")`$2&@I!\D$D`0D)"D#R0*P`+``2K``
 +MH!"M#Y"J2?>-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y!(
 ++:$AHZNJ(T--,O^J.
 +`
 +end
 +
 +
 +Removed sideborders with 8 sprites and bad lines, PAL-B version:
 +
 +begin 644 raster.63
 +M`0@*",L'GC(P-C$```!,$PA,=`@@'0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
 +ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
 +M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
 +M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
 +MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZB0D[!+0\`#*RHX2T*(!CAG0H@+*T/WJ
 +MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,",8K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6
 +MT*`"B-#]ZNKJQOL0S4R!ZJT8`ZP9`TBI0HT8`ZD)C1D#HH&.#=VB`(X%W>B.
 +1!-VBW8X.W:E`:(T8`XP9`V`8
 +`
 +end
 +
 +
 +Removed sideborders with 8 sprites and bad lines, 6567R56A version
 +(very old NTSC-M C64s):
 +
 +begin 644 raster.64
 +M`0@*",L'GC(P-C$```!,$PA,=`@@'@FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
 +ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
 +M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
 +M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
 +MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNKJ[!+0\`#*RHX2T*(!CAG0H@+*T/WJ
 +MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,"08K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6
 +MT*`"B-#]ZNHD),;[$,Q,@>JM&`.L&0-(J4.-&`.I"8T9`Z*!C@W=H@".!=WH
 +2C@3=HMV.#MVI0&B-&`.,&0-@
 +`
 +end
 +
 +Removed sideborders with 8 sprites and bad lines, 6567R8 and above
 +(not too old NTSC-M C64s and all C128s)
 +
 +begin 644 raster.65
 +M`0@*",L'GC(P-C$```!,$PA,=`@@(0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
 +ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
 +M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
 +M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
 +MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNHD).P2T/``RLJ.$M"B`8X9T*("RM#]
 +MZNJI%(7[HLB@`HC0_<X6T(X6T"0DQOLP)1BM$=#M$M`I!]#DQOOJZNKJSA;0
 +MCA;0H`*(T/WJZNKJZL;[$,I,@>JM&`.L&0-(J4:-&`.I"8T9`Z*!C@W=H@".
 +5!=WHC@3=HMV.#MVI0&B-&`.,&0-@
 +`
 +end
 +
 +
 +That was all, folks!  I hope you learned something from this article.
 +Feel free to e-mail me at Marko.Makela@HUT.FI, should anything remain
 +unclear.
 +
 +========================================================================
 +</code>
 +====== A Different Perspective, part III ======
 +<code>
 +by Stephen Judd --- sjudd@nwu.edu    
 +   George Taylor --- aa601@cfn.cs.dal.ca
 +
 +        Whew!  What a busy time it's been -- research to get done,
 +conferences, classes... between getting things done and blowing other
 +things off, I one day reflected for a moment and realized that I had
 +three days left to get the next article together for C=Hacking!  So
 +everything has been slapped together at the last minute, and I hope
 +you'll forgive any bugs or unclear concepts.
 +                >>> ANECDOTE ALERT <<<
 +        And that reminds me: I just got JiffyDOS and an FD-2000 drive --
 +what a wonderful device.  I have a 1.6 megabyte disk formatted into
 +three partitions.  The first contains my Merlin 128 assembler, the
 +second is some 4000 blocks large and I use it for all my various
 +versions of code while debugging, and the third is maybe 1000 blocks,
 +and contains only finished code -- no more swapping disks, no more
 +deleting old versions that I hope I don't need to make room on the
 +disk.  Also, when I installed JiffyDOS I found a serious bug in my
 +128D -- a cricket, dead among the IC's.
 +
 +        This time we will cover a lot of ground which isn't so much
 +cutting-edge as it is very useful.  Let's face it: cubes are getting
 +more than a little dull.  A worthy end goal is to have a completely
 +general routine for plotting a series of polygons -- that is, you supply
 +a list of (x,y,z) coordinates from which the program can form a list of
 +polygons.  These polygons may then be displayed in 2D, rotated, magnified,
 +filled, etc.  And, much to my three-day astonishment, that is exactly
 +what we are going to do.
 +        But first, a little excursion.  One thing we are of course always
 +thinking about is optimization possibilities: in the shower, while
 +sleeping/dreaming, out on dates, etc.  So, where to begin?  The biggest
 +cycle hogs in the program are line drawing and face filling -- well,
 +filling faces is pretty straightforward.  What about line drawing?
 +        Well, one downer of the routine is that every single pixel is
 +plotted.  But as we know, on a computer any given line is made up of
 +several smaller vertical and horizontal lines -- wouldn't it be neat
 +if we could think of a way to plot these line chunks all at once,
 +instead of a pixel at a time?
 +        Heck yes it would!  So here we go:
 +        
 +Neat-o Enhanced Chunky Line Drawing Routine
 +-------------------------------------------
 +
 +        First we need to be in the right mindframe.  Let's say you're
 +drawing a line where you move three pixels in x before it's time to take
 +a step in y.  Instead of plotting all three pixels it would of course
 +be much more efficient to just stick a number like %00011100 in the
 +drawing buffer.  But somehow we need to keep track of a) how large the
 +chunk needs to be, and b) where exactly the chunk is.
 +        In the above example, we started at a particular x-value:
 +
 +        %00010000
 +
 +and we want to keep adding ones to the right of the starting point; three,
 +to be exact.  Hmmm... we need to somehow rotate the starting bit in a way
 +that leaves a trail of ones behind it.  Maybe rotate and ORA with the
 +original bit?  But what happens when you take a step in Y?
 +        No, we need something far sneakier.  Let's say that instead of
 +%00010000 we start with
 +
 +        x = %00011111
 +
 +Now, with each step in the x direction, we do an arithmetic shift on x.  So
 +after one step we have
 +
 +        x = %00001111
 +
 +and after two steps
 +
 +        x = %00000111
 +
 +and at the third step of course
 +
 +        x = %00000011
 +
 +Now it is time to take a step in Y.  But now look: if we EOR x with its
 +
 +original value xold = %00011111, we get
 +
 +        x EOR xold = %00011100
 +
 +which is exactly the chunk we wanted.  Moreover, x still remembers where it
 +is, so we don't have to do anything special each time a step is taken in
 +the y-direction.
 +
 +        So here is the algorithm for drawing a line in the x-direction:
 +
 +        initialize x, dx, etc.
 +        xold = x
 +        take a step in x: LSR X
 +        have we hit the end of a column?  If so, then plot and check on y
 +        is it time to take a step in y?
 +        if not, take another step in x
 +        if it is, then let a=x EOR xold
 +                       plot a into the buffer
 +                       let xold=x
 +        keep on going until we're finished
 +
 +        This simple modification gives us a substantial speed increase --
 +on the old filled hires cube3d program, I measured a gain of one frame per
 +second.  Not earth-shattering, but not bad either!  When faces are not
 +filled, the difference is of course much more noticable.
 +        There are a few things to be careful of.  There was a bug in the
 +old routine when the line was a single point.  In that case dx=dy=0, and
 +the program would draw a vertical line on the screen.  There are probably
 +some other things to be careful of, but since I wrote this part of the
 +code three months ago I really don't remember any of them!
 +        This takes care of horizontal line chunks -- what about vertical
 +chunks?  Well, because of the way points are plotted there is nothing
 +we can do about them.  But, as we shall soon see, if we use an EOR-buffer
 +to fill faces we will be forced to take care of the vertical chunks!
 +
 +General Polygon Routine
 +-----------------------
 +
 +        Now we can begin thinking about a general polygon routine.  First
 +we need a list of sets of points, where each set corresponds to a
 +polygon.  The first number in a set could be the number of (x,y,z) points
 +in that set, and the points could then follow.  So a triangle could
 +be given by the data set:
 +
 +        3 -1 0 0 0 1 0 1 0 0
 +
 +This would be a triangle with vertices at (-1,0,0), (0,1,0), and (1,0,0).
 +We can mash a bunch of these sets together, but somehow we have to know
 +when we've hit the end -- for this we can use a zero, since we don't
 +want to plot polygons with zero points in them.
 +        For that matter, how many points should there be in a polygon?
 +There must be at least three, otherwise it makes no sense.  Since we
 +want our polygons to be closed, the computer should be smart enough to
 +connect the last point to the first point -- in our triangle above,
 +the computer would join (-1,0,0) to (0,1,0), (0,1,0) to (1,0,0), and
 +(1,0,0) to (-1,0,0).
 +        Now that we have a polygon, we want to rotate it.  You will
 +recall that we have calculated a rotation matrix M, which acts on
 +points.  So we need apply our rotation transform to each of the
 +points in the polygon, i.e. multiply M times each point of the
 +polygon.  Furthermore, we need to project each of these points.
 +        Uh-oh: matrix multiplication.  In the past we have avoided this
 +issue by putting the vertices of our cube at 1 or -1.  So we need to
 +use our multiplication routine from last time.  But wait!  As you recall,
 +the last program used a specially modified multiplication table.  To get
 +a wider range of numbers to multiply we will need another set of
 +multiplication tables -- no big whoop.
 +        Now, if you review the multiplication routine from last time,
 +it adds two numbers and subtracts two numbers.  What kinds of numbers
 +will we be dealing with?  The matrix elements vary between -64..64.
 +This then fixes our range of polygon coordinates from -64..64.  Why?
 +If the matrix element is 64, and we multiply it by 64, the multiplication
 +routine will add 64 and 64 and get 128, which is right on the edge of
 +our multiplication table.
 +        Can we improve this rotation process in any way?  In fact, we can
 +cut down on the number of multiplications (i.e. do eight or even seven
 +instead of nine multiplications).  However, there is a fair amount of
 +overhead involved in doing so, and our multiply routine is fast enough
 +that the extra overhead and complexity really gain us very little in all
 +but the most complicated of polygons.  In other words, I didn't bother.
 +
 +        What about hidden faces?  Again, from last time you may recall
 +that a method was described which used the cross-product of the projected
 +vectors.  How do we implement this in the program?  Well, if we take
 +the first three points of the polygon, we have two vectors.  Let's say
 +these points are P1 P2 and P3.  Then V1=P1-P2 and V2=P3-P2 are two
 +vectors in the plane of the polygon which are connected at the point P2
 +(this analysis will of course only work if the polygon lies in some plane).
 +Depending on how we take the cross product, the sign will be positive or
 +negative, and this will tell us if the polygon is visible.
 +        Depending on how we take the cross product?  Absolutely.
 +v1 x v2 = -v2 x v1.  What it really boils down to is how you define the
 +points in your polygon.  Specifically, what order they are in.  Points
 +that are specified in a clockwise manner will give a face pointing in
 +the opposite direction of a polygon with the same points specified in
 +a counter-clockwise order.  In my program, the polygons must be entered
 +in counter-clockwise order (with you facing the polygon) for hidden
 +faces to work the way you want them to ;-).
 +
 +        One other neat thing to have is the ability to zoom in and out.
 +We know from the very first article that zooming corresponds to multiplying
 +the projected points by a number, so that's what we'll do.  The multiplication
 +routine returns A=A*Y/64, so a zoom factor of 64 would be like multiplying
 +the point by one.  All the program does is multiply the projected points
 +by a number zoom, unless zoom=64, in which case the program skips the
 +zoom multiply.  Be warned!  No checks of any sort are made in the program,
 +so you can zoom at your own risk!
 +
 +        The important things to remember are: when entering polygons,
 +make sure the numbers range from -64 to 64, and that you enter points
 +in counterclockwise.  Our triangle example above really should have been
 +entered as, say,
 +
 +        3 -64 0 0 64 0 0 0 64 0
 +
 +Filled Faces -- Using an EOR buffer
 +-----------------------------------
 +
 +        Well we still have one thing left, which was alluded to in the
 +previous article: using EOR to make a filled face.  Some possible
 +difficulties were raised, but when you plot a single polygon at a
 +time, the problem becomes vastly simplified.
 +        First I should perhaps remind you what exclusive-or is: either
 +A or B, but not both.  So 1 EOR 0 = 1, as does 0 EOR 1, but 0 EOR 0 = 0
 +and 1 EOR 1 = 0.  As a simple introduction to using this for filling
 +faces, consider the following piece of the drawing buffer:
 +
 +        00001011 M1
 +        00000000 M2
 +        00000001 M3
 +        00001010 M4
 +
 +Lets say we move down memory, EORing as we go.  Let M2 = M1 EOR M2.  Then
 +let M3 = M2 EOR M3.  Then let M4 = M3 EOR M4.  Our little piece of memory
 +is now:
 +
 +        00001011 M1
 +        00001011 M2
 +        00001010 M3
 +        00000000 M4
 +
 +What just happened?  Imagine that the original memory was a series of
 +pieces of line segments.  We have just filled in the area between the
 +two line segments, like magic!
 +        If you still aren't getting it, draw a large section of memory,
 +and then draw an object in it, like a triangle, or a trapazoid, and
 +EOR the memory by hand, starting from the top and moving downwards.
 +        EOR flips bits.  If you start with a zero, it stays zero until
 +it hits a one.  It will then stay one until it hits another one.  So
 +you can see that if you have an object bounded by ones, EORing
 +successive memory locations will automagically fill the object.
 +        Right?  Well, we have to be careful.  One major problem is
 +a vertical line:
 +
 +        1                       1
 +        1       goes to         0
 +        1                       1
 +        1                       0
 +
 +Not only is the resultant line dashed, but if there are an odd number of
 +points in the line segment, the last one will happily move downwards in
 +memory, and give you a much longer vertical line than you expected!  Since
 +any line with slope greater than one is made up of a series of line
 +segments, this is a major consideration.
 +        Another problem arises with single points: a one just sitting all
 +by itself will also generate a nice streak down your drawing area.
 +        If you think about it, what we ideally want to have is an object
 +that at any given value of x there are exactly two points, one defining
 +the top of the object, and the other defining the bottom.  This gives us
 +the insight to solve the above two problems.
 +        First let's think about vertical lines.  In principle we could
 +plot the first and last endpoints of each vertical line chunk, but that
 +is exactly what we don't want!  Remember that these are closed polygons,
 +which means that there are _two_ lines we need to think about.  If I
 +plot just a single point in each vertical line segment, there must
 +be another point somehwere, either above or below it, from another
 +line segment, which will close the point to EOR-filling.  Remember, we
 +want exactly two points at each value of x: one will come from the
 +line, and the other will come from the other line which must lie above
 +or below the current one.
 +        Furthermore, with any convex polygon there are exactly two
 +lines which come together at each vertex of the polygon.  This means
 +that there are only certain cases which we need to worry about.
 +For instance, two lines might join in any of the following ways:
 +
 +        \                  /    \    /
 +                        /      \  /
 +          \_____    _____/        \/  etc.
 +
 +If you draw out the different cases involving vertical lines, you can see
 +that you have to be careful about plotting the lines.  One tricky one
 +is where two vertical lines with different slopes overlap at the point
 +of intersection.
 +        So after staring at these pictures for a while, you can find
 +a consistent method which solves these difficulties.  As long as you
 +follow the following rules, the problems all disappear; the line routine
 +needs to be modified slightly:
 +
 +        1) When plotting a vertical line (i.e. big steps in Y direction),
 +           don't plot the endpoints (i.e. x1,y1 and x2,y2).
 +        2) When plotting a vertical line, consistently plot either the
 +           first part of each chunk or the last part of each chunk
 +           (excluding the endpoints of course).  In other words, only
 +           plot a point when you take a step in x, and then plot one
 +           and only one point.
 +
 +Now I deduced these by staring at pictures for a few hours and trying
 +different things like top/bottom of chunk, left/right, first/last, etc.
 +You can see that in some cases this ensures that only one point appears
 +on a given line segment.  But to me the only way to convince yourself
 +that this really does work is to draw a bunch of pictures, and try it
 +out!  You have cases where two vertical lines intersect, and where
 +a vertical line intersects a horizontal line.
 +        But there is still one thing which we have forgotten -- the
 +case of a single point.  This can happen in, for instance, a pointy
 +triangle, pointing in the x-direction.  How do we fix this?  By
 +simply avoiding the point: in the line drawing routine, use EOR
 +to plot the points instead of ORA.  Since vertical lines skip the
 +endpoints, vertical-horizontal intersections are OK.  Horizontal-
 +horizontal intersections will force the point of intersection to
 +be zero.
 +        Uh-oh, what about intersections like -----*------.  Quite frankly
 +I just thought of it, and I think my program will fail on intersections
 +like these.  Drat.  Well, that just gives us something for next time!
 +        One other thing needs to be mentioned: for EOR-filling to be useful
 +you need to draw the polygon in a special buffer, and then EOR this buffer
 +into the main display buffer.  If you try to EOR the display buffer directly
 +you are going to have a whole heap of trouble, such as the concerns raised
 +last time.
 +        Finally, this gives a simple way of filling with patterns instead
 +of boring monocolor.  Instead of EOR (EORBUF),Y : ORA (DRAWBUF),Y you can
 +use EOR (EORBUF),Y : AND PATTERN,Y : ORA (DRAWBUF),Y (as long as you
 +preserve the original EOR (EORBUF),Y).
 +
 +        Well I am extremely tired and I hope Craig hasn't sent out C=Hacking
 +without me!  I hope you have fun playing with the program, and I would be
 +very interested in seeing any neat geometric shapes you might design!
 +
 +Program notes:
 +--------------
 +
 +        - Hidden faces defaults to "on" If you enter a shape and a black
 +          screen comes up, hit 'h' to turn off hidden faces (you probably
 +          entered the polygon clockwise).
 +        - There is no pattern filling -- just simple EOR with a twist:
 +          the EOR buffer is EOR'd into the drawing buffer.
 +        - You might start hosing memory if you zoom too large.
 +
 +SLJ 6/15/95
 +
 +Addendum
 +--------
 +Stephen Judd sjudd@nwu.edu
 +
 +        Last time we put a circle into the 2D graphics toolbox.  Chris
 +McBride has pointed something out to me about the algorithm, which makes
 +it complete.  As you may recall, the algorithm gave a very squarish
 +circle for small radii.  Chris told me that setting the initial counter
 +value to R/2, instead of R, gave a perfect circle.  What is going on?
 +If you recall the algorithm, we are computing a fractional quantity,
 +and when that quantity becomes larger than one, we decrease X.  Wouldn't
 +it be a whole lot smarter to round off that fraction instead of
 +truncate it?  Of course it would, and that is what starting the counter
 +at R/2 does.
 +        So, to update the previous algorithm, A should be initialized to
 +R/2 instead of R, which means that we change
 +
 +        LDA R
 +
 +to
 +
 +        LDA R
 +        LSR
 +
 +for a perfect circle every time.
 +
 +begin 666 cube3d3.2.s
 +M ' J*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*@TJH*"@H*"@H*"@
 +MH*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'-415!(14Z@:E5$1*"@H*"@H*"@
 +MH*"@H*"@H*"@*@TJH&=%3U)'1:!T05E,3U*@H*"@H*"@H*"@H*"@H*"@*@TJ
 +MH'-405)4140ZH#<O,3$O.32@H*"@H*"@H*"@H*"@*@TJH&9)3DE32$5$.J W
 +M+S$Y+SDTH*"@H*"@H*"@H*"@*@TJH%8R+C"@8T]-4$Q%5$5$.J Q,B\Q-R\Y
 +M-*"@H*"@*@TJH%8S+C"@8T]-4$Q%5$5$.J S+S(P+SDUH*"@H*"@*@TJH%8S
 +M+C&@8T]-4$Q%5$5$.J V+S$T+SDUH*"@H*"@*@TJH%8S+C*@8T]-4$Q%5$5$
 +M.J V+S$U+SDUH*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@
 +MH*"@*@TJH'=%3$PLH$E&H$%,3*!'3T53H%=%3$R@5$A)4Z"@*@TJH%!23T=2
 +M04V@5TE,3*!23U1!5$6@0:!#54)%+J"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@
 +MH*"@H*"@H*"@H*"@*@TJH%8R+C"@*Z!N15>@04Y$H&E-4%)/5D5$(:"@H*"@
 +M*@TJH&Y/5Z!7251(H$9!4U1%4J!23U5424Y%4RR@H*"@*@TJH$A)1$1%3J!3
 +M55)&04-%4RR@1DE,3$5$H*"@H*"@*@TJH$9!0T53+*!!3D2@15A44D&@5$]0
 +MH%-%0U)%5*"@*@TJH%1%6%2@34534T%'15,AH*"@H*"@H*"@H*"@H*"@*@TJ
 +MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C"@*Z!F05-4
 +MH$-(54Y+6:!,24Y%H*"@H*"@*@TJH%)/551)3D4NH*"@H*"@H*"@H*"@H*"@
 +MH*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S
 +M+C&@*Z!G14Y%4D%,H%!/3%E'3TZ@4$Q/5*"@*@TJH%=)5$B@2$E$1$5.H$9!
 +M0T53H"AX+5!23T150U0I*@TJH$%.1*!:3T]-H$9%05154D4NH*"@H*"@H*"@
 +MH*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C*@
 +M*Z!E;W(M0E5&1D52H$9)3$Q)3D>@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@
 +MH*"@H*"@H*"@H*"@*@TJH'1(25.@4%)/1U)!3:!)4Z!)3E1%3D1%1*!43Z"@
 +M*@TJH$%#0T]-4$%.6:!42$6@05)424-,1:!)3J"@H*"@*@TJH&,]:$%#2TE.
 +M1RR@:E5.+J Y-:!)4U-512Z@H*"@*@TJH&9/4J!$151!24Q3H$].H%1(25.@
 +M4%)/1U)!32R@*@TJH%)%042@5$A%H$%25$E#3$4AH*"@H*"@H*"@H*"@*@TJ
 +MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'=2251%H%1/H%53
 +M(:"@H*"@H*"@H*"@H*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@
 +MH*"@H*"@*@TJH&U94T5,1J!72$5.H%E/54Y'H$1)1*"@H*"@H*"@*@TJH$5!
 +M1T523%F@1E)%455%3E2@H*"@H*"@H*"@H*"@*@TJH&1/0U1/4J!!3D2@<T%)
 +M3E0LH$%.1*!(14%21*"@*@TJH$=214%4H&%21U5-14Y4H*"@H*"@H*"@H*"@
 +MH*"@*@TJH*!A0D]55*!)5*!!3D2@04)/550ZH$)55*"@H*"@*@TJH*!%5D52
 +M34]21:"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH&-!346@3U54H$)9H%1(1:!3
 +M04U%H&1/3U*@H*"@*@TJH$%3H$E.H&F@5T5.5"Z@H*"@H*"@H*"@H*"@H*"@
 +M*@TJH*"@H"V@<E5"04E9052@H*"@H*"@H*"@H*"@H*"@*@TJH*"@H*"@H*"@
 +MH*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'1(3U5'2*!IH%-014%+H%=)5$B@
 +M5$A%H*"@H*"@*@TJH%1/3D=515.@3T:@345.H$%.1*!/1J!!3D=,15.@*@TJ
 +MH$%.1*!(059%H$Y/5*!,3U9%+*!IH$%-H*"@H*"@*@TJH$)%0T]-1:!!4Z!3
 +M3U5.1$E.1Z!"4D%34RR@3U*@*@TJH$&@5$E.2TQ)3D>@0UE-0D%,+J"@H*"@
 +MH*"@H*"@*@TJH*"@H"V@,:!C3U))3E1(24%.4Z Q,Z"@H*"@H*"@*@TJH*"@
 +MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH' N<RZ@=$A)4Z!705.@
 +M5U))5%1%3J!54TE.1Z"@*@TJH*"@H*"@;4523$E.H#$R."Z@H*"@H*"@H*"@
 +MH*"@*@TJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*@T-(&]R9R D
 +M.# P, T-*J!C3TY35$%.5%,-#6)U9F8Q(&5Q=2 D,S P," [9DE24U2@0TA!
 +M4D%#5$52H%-%5 UB=69F,B!E<74@)#,X,# @.W-%0T].1*!#2$%204-415*@
 +M4T54#65O<F)U9B!E<74@)#0P,# @.V5O<BU"549&15(-8G5F9F5R(&5Q=2 D
 +M83,@.W!215-534%"3%F@5$A%H%1!4$6@5T].)U2@0D6@4E5.3DE.1PUX,2!E
 +M<74@)&9B(#MP3TE.5%.@1D]2H$1205=)3D>@0:!,24Y%#7DQ(&5Q=2 D9F,@
 +M.W1(15-%H%I%4D^@4$%'1:!!1$1215-315,->#(@97%U("1F9" [1$].)U2@
 +M0T].1DQ)0U2@5TE42*!B87-I8PUY,B!E<74@)&9E#6]L9'@@97%U("1F9 UC
 +M:'5N:R!E<74@)&9E#61X(&5Q=2 D-C<@.W1(25.@25.@4TA!4D5$H%=)5$B@
 +M=#&@0D5,3U<-9'D@97%U("0V. UT96UP,2!E<74@)&9B(#MO1J!#3U524T4L
 +MH$-/54Q$H$-/3D9,24-4H%=)5$B@6#$-=&5M<#(@97%U("1F8R [=$5-4$]2
 +M05)9H%9!4DE!0DQ%4PUZ=&5M<"!E<74@)# R(#MU4T5$H$9/4J!"549&15*@
 +M4U=!4"Z@H&1/3B=4H%1/54-(+@UZ,2!E<74@)#(R(#MU4T5$H$)9H$U!5$B@
 +M4D]55$E.10UZ,B!E<74@)#(T(#MD3TXG5*!43U5#2*!42$531:!%251(15(A
 +M#7HS(&5Q=2 D,C8->C0@97%U("0R. UK(&5Q=2 D8C8@.V-/3E-404Y4H%53
 +M142@1D]2H$A)1$1%3@T@(" [4U521D%#1:!$151%0U1)3TZ@+:!$3TXG5*!4
 +M3U5#2 UH:61E(&5Q=2 D8C4@.V%21:!355)&04-%4Z!(241$14X_#69I;&P@
 +M97%U("0U," [85)%H%=%H%5324Y'H&5O<BU&24Q,/PUA;F=M87@@97%U(#$R
 +M," [=$A%4D6@05)%H#(J4$DO04Y'34%8H$%.1TQ%4PT-*J!V:6,-#79M8W-B
 +M(&5Q=2 D9# Q. UB:V=N9"!E<74@)&0P,C -8F]R9&5R(&5Q=2 D9# R,0US
 +M<W1A<G0@97%U(#$S-#0@.U)/5Z YH$E.H%-#4D5%3J!-14U/4EF@052@,3 R
 +M- T-#2J@:T523D%,#0UC:')O=70@97%U("1F9F0R#6=E=&EN(&5Q=2 D9F9E
 +M- T-*J!S3TU%H%9!4DE!0DQ%4PT-9VQO8GAM:6X@/2 D,V8@.W1(15-%H$%2
 +M1:!54T5$H$E.H$-,14%224Y'H%1(10UG;&]B>&UA>" ]("0T," [1%)!5TE.
 +M1Z H1TQ/0D%,*:!"549&15(-9VQO8GEM:6X@/2 D-#$-9VQO8GEM87@@/2 D
 +M-#(-;&]C>&UI;B ]("0U-R [=$A%4T6@05)%H%53142@24Z@0TQ%05))3D>@
 +M5$A%#6QO8WAM87@@/2 D-3@@.V5O<J H3$]#04PIH$)51D9%4@UL;V-Y;6EN
 +M(#T@)#4Y#6QO8WEM87@@/2 D-C -<#%X(#T@)#DR(#MT2$531:!!4D6@5$5-
 +M4$]205)9H%-43U)!1T4-<#%Y(#T@)#DS(#MU4T5$H$E.H%!,3U1424Y'H%1(
 +M1:!04D]*14-424].#7 Q>B ]("0Y- UP,G@@/2 D.34@.W1(15F@05)%H$A%
 +M4D6@4T^@5$A!5*!710UP,GD@/2 D.38@.T1/3B=4H$A!5D6@5$^@4D5#04Q#
 +M54Q!5$6@5$A%32X-<#)Z(#T@)&%E#7 S>" ]("1A9B [=$A%6:!-04M%H$Q)
 +M1D6@14%362X-<#-Y(#T@)&(P#7 S>B ]("1B,2 [=TA9H$%21:!93U6@3$]/
 +M2TE.1Z!!5*!-1:!,24M%H%1(050_#7 Q=" ]("1B,B [9$].)U2@64]5H%12
 +M55-4H$U%/PUP,G0@/2 D8C,-<#-T(#T@)&(T(#MH059)3D>@04Y/5$A%4J!#
 +M2$E,1*!705-.)U2@35F@241%02X-:6YD97@@/2 D-3$-8V]U;G1P=',@/2 D
 +M-3(->F]O;2 ]("0W,2 [>D]/3:!&04-43U(-9'-X(#T@)#8Q(#MD<WB@25.@
 +M5$A%H$E.0U)%345.5*!&3U(-(" @.U)/5$%424Y'H$%23U5.1*!8#61S>2 ]
 +M("0V,B [<TE-24Q!4J!&3U*@9'-Y+*!D<WH-9'-Z(#T@)#8S#7-X(#T@)#8T
 +M(#MT2$531:!!4D6@5$A%H$%#5%5!3*!!3D=,15.@24Z@6*!9H$%.1*!:#7-Y
 +M(#T@)#8U#7-Z(#T@)#8V#70Q(#T@)#8W(#MT2$531:!!4D6@55-%1*!)3J!4
 +M2$6@4D]4051)3TX-=#(@/2 D-C@-=#,@/2 D-CD@.W-%1:!42$6@05)424-,
 +M1:!&3U*@34]21:!$151!24Q3#70T(#T@)#9A#70U(#T@)#9B#70V(#T@)#9C
 +M#70W(#T@)#9D#70X(#T@)#9E#70Y(#T@)#9F#70Q," ]("0W, UA,3$@/2 D
 +M834@.W1(15-%H$%21:!42$6@14Q%345.5%.@3T:@5$A%H%)/5$%424].H$U!
 +M5%))6 UB,3(@/2 D838@.WAY>@UC,3,@/2 D83<-9#(Q(#T@)&$X(#MT2$6@
 +M3E5-0D52H$1%3D]415.@*%)/5RQ#3TQ534XI#64R,B ]("1A.0UF,C,@/2 D
 +M86$-9S,Q(#T@)&%B#6@S,B ]("1A8PUI,S,@/2 D860-#0TJ*BJ@;4%#4D]3
 +M#0UM;W9E(&UA8PT@;&1A(%TQ#2!S=&$@73(-(#P\/ T-9V5T:V5Y(&UA8R @
 +M.W=!252@1D]2H$&@2T594%)%4U,-=V%I="!J<W(@9V5T:6X-(&-M<" C,# -
 +M(&)E<2!W86ET#2 \/#P-#61E8G5G(&UA8R @.W!224Y4H$&@0TA!4D%#5$52
 +M#2!D;Z PH* [9$].)U2@05-314U"3$4-#2!L9&&@(UTQ#2!J<W*@8VAR;W5T
 +M#2!C;&D-(#X^/B!G971K97D@.V%.1*!704E4H%1/H$-/3E1)3E5%#2!C;7 @
 +M(R=3)R [;5F@4T5#4D5#5*!35TE40TB@2T59#2!B;F4@;#$-(&IS<B!C;&5A
 +M;G5P#2!J;7 @9&]N90UL,2!C;7 @(R=8)R [;5F@4T5#4D54H$%"3U)4H$M%
 +M60T@8FYE(&1O;F4-(&IM<"!C;&5A;G5P#2!F:6X-9&]N92 \/#P-#61E8G5G
 +M82!M86,-(&1OH# -(&QD82!=,0T@<W1A(#$P,C0-(&9I;@UD;VYE82 \/#P-
 +M#2HM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM#0T@;&1A(",D,# -
 +M('-T82!B:V=N9 T@<W1A(&)O<F1E<@T@;&1A('9M8W-B#2!A;F0@(R4P,# P
 +M,3$Q,2 [<T-2145.H$U%34]26:!43Z Q,#(T#2!O<F$@(R4P,# Q,# P, T@
 +M<W1A('9M8W-B#0T@;&1Y(",P, T@;&1A(",\='1E>'0-('-T82!T96UP,0T@
 +M;&1A(",^='1E>'0-('-T82!T96UP,@T@:FUP('1I=&QE#71T97AT(&AE>" Y
 +M,S U,3$Q,3$Q(#M#3$5!4J!30U)%14XLH%=(251%+*!#4E-2H$1.#2!T>'0@
 +M)Z"@H*"@H*"@H*"@H*!#54)%,T2@5C,N,B<L,$0L,$0-('1X=" GH*"@H*"@
 +MH*"@H*"@H*"@H*"@0EDG+#!$#2!H97@@.68@.T-904X-('1X=" GH*"@H%-4
 +M15!(14Z@2E5$1"<-(&AE>" Y.0T@='AT(">@H*"@1T5/4D=%H%1!64Q/4B<L
 +M,$0L,$0-(&AE>" Y8@T@='AT(">@H$-(14-+H$]55*!42$6@2D%.+J Y-:!)
 +M4U-51:!/1B<L,$0-(&AE>" Y-@T@='AT(">@H$,]2$%#2TE.1R<-(&AE>" Y
 +M8@T@='AT(">@1D]2H$U/4D6@1$5404E,4R$G+#!$#2!H97@@,&0Q9#%D.64Q
 +M,@T@='AT("=&,2]&,B<L.3(-('1X=" GH"V@24Y#+T1%0Z!8+5)/5$%424].
 +M)RPP1 T@:&5X(#%D,60Q,@T@='AT("=&,R]&-"<L.3(-('1X=" GH"V@24Y#
 +M+T1%0Z!9+5)/5$%424].)RPP1 T@:&5X(#%D,60Q,@T@='AT("=&-2]&-B<L
 +M.3(-('1X=" GH"V@24Y#+T1%0Z!:+5)/5$%424].)RPP1 T@:&5X(#%D,60Q
 +M,@T@='AT(">@1C>@H"<L.3(-('1X=" GH"V@4D53150G+#!$#2!H97@@,60Q
 +M9#$R#2!T>'0@)Z K+RV@)RPY,@T@='AT(">@+:!:3T]-H$E.+T]55"<L,$0-
 +M(&AE>" Q9#%D,3(-('1X=" GH*!(H* G+#DR#2!T>'0@)Z MH%1/1T=,1:!(
 +M241$14Z@4U521D%#15,G+#!$#2!H97@@,60Q9#$R#2!T>'0@)U-004-%)RPY
 +M,@T@='AT(">@+:!43T='3$6@4U521D%#1:!&24Q,24Y')RPP1"PP1 T@='AT
 +M(">@H%!215-3H%&@5$^@455)5"<L,$0-(&AE>" P9# U#2!T>'0@)Z"@H*"@
 +MH%!215-3H$%.6:!+15F@5$^@0D5'24XG+#!$#2!H97@@,# -=&ET;&4@;&1A
 +M("AT96UP,2DL>0T@8F5Q(#IC;VYT#2!J<W(@8VAR;W5T#2!I;GD-(&)N92!T
 +M:71L90T@:6YC('1E;7 R#2!J;7 @=&ET;&4-.F-O;G0@/CX^(&=E=&ME>0T-
 +M*BHJ*J!S152@55"@5$%"3$53*#\I#0TJH'1!0DQ%4Z!!4D6@0U524D5.5$Q9
 +MH%-%5*!54*!)3J!B87-I8PTJH$%.1*!"6:!42$6@05-314U"3$52+@T-=&%B
 +M;&5S(&QD82 C/G1M871H,0T@<W1A('HQ*S$-('-T82!Z,BLQ#2!L9&$@(SYT
 +M;6%T:#(-('-T82!Z,RLQ#2!S=&$@>C0K,0T-*BHJ*J!C3$5!4J!30U)%14Z@
 +M04Y$H%-%5*!54* B0DE434%0(@US971U<"!L9&$@(R0P,2 [=TA)5$4-('-T
 +M82 D9# R,2 [=$A)4Z!)4Z!$3TY%H%-/H%1(052@3TQ$15(-(&QD82 C,30W
 +M(#M-04-(24Y%4Z!724Q,H%-%5*!54 T@:G-R(&-H<F]U= T@;&1A(",D,# @
 +M.T-/4E)%0U1,60T@<W1A("1D,#(Q#2!L9&$@(SQS<W1A<G0-(&%D8R C,3(@
 +M.W1(1:!'3T%,H$E3H%1/H$-%3E1%4J!42$6@1U)!4$A)0U,-('-T82!T96UP
 +M,2 [8T],54U.H#$R#2!L9&$@(SYS<W1A<G0@.W)/5Z Y#2!S=&$@=&5M<#$K
 +M,2 [<W-T87)TH%!/24Y44Z!43Z!23U>@.0T@;&1A(",P, T@;&1Y(",P, T@
 +M;&1X(",P," [6*!724Q,H$-/54Y4H#$VH%)/5U.@1D]2H%53#2!C;&,-#3IL
 +M;V]P('-T82 H=&5M<#$I+'D-(&EN>0T@861C(",Q-@T@8F-C(#IL;V]P#2!C
 +M;&,-(&QD82!T96UP,0T@861C(",T," [;D5%1*!43Z!!1$2@-#"@5$^@5$A%
 +MH$)!4T6@4$])3E1%4@T@<W1A('1E;7 Q(#MT3Z!*54U0H%1/H%1(1:!.15A4
 +MH%)/5PT@;&1A('1E;7 Q*S$-(&%D8R C,# @.W1!2T6@0T%21:!/1J!#05)2
 +M2453#2!S=&$@=&5M<#$K,0T@;&1Y(",P, T@:6YX#2!T>&$@(#MXH$E3H$%,
 +M4T^@04Z@24Y$15B@24Y43Z!42$6@0TA!4D%#5$52H$Y534)%4@T@8W!X(",Q
 +M-@T@8FYE(#IL;V]P(#MN145$H%1/H$1/H$E4H#$VH%1)3453#0TJ*BHJH&-,
 +M14%2H$)51D9%4E,-#2!L9&$@(SQB=69F,0T@<W1A(&)U9F9E<@T@;&1A(",^
 +M8G5F9C$-('-T82!B=69F97(K,0T@;&1Y(",D,# -(&QD>" C,C0@.V%34U5-
 +M24Y'H$%,3*!42%)%1:!"549&15)3H$%210T@;&1A(",D,# @.T)!0TLM5$\M
 +M0D%#2PTZ8FQO;W @<W1A("AB=69F97(I+'D-(&EN>0T@8FYE(#IB;&]O< T@
 +M:6YC(&)U9F9E<BLQ#2!D97@-(&)N92 Z8FQO;W -#2HJ*BJ@<T54H%50H$)5
 +M1D9%4E,-#2!L9&$@(SQB=69F,0T@<W1A(&)U9F9E<@T@;&1A(",^8G5F9C$-
 +M('-T82!B=69F97(K,0T@<W1A('IT96UP(#M:5$5-4*!724Q,H$U!2T6@3$E&
 +M1:!324U03$6@1D]2H%53#2!L9&$@=FUC<V(-(&%N9" C)3$Q,3$P,# Q(#MS
 +M5$%25*!(15)%H%-/H%1(052@4U=!4*!"549&15)3H%=)3$R@5T]22Z!224=(
 +M5 T@;W)A(",E,# P,#$Q,3 -('-T82!V;6-S8@T-*BHJ*J!S152@55"@24Y)
 +M5$E!3*!604Q515,-#6EN:70@;&1A(",P, T@<W1A(&QO8WAM:6X-('-T82!L
 +M;V-X;6%X#2!S=&$@;&]C>6UI;@T@<W1A(&QO8WEM87@-('-T82!G;&]B>&UI
 +M;@T@<W1A(&=L;V)Y;6EN#2!S=&$@9VQO8GAM87@-('-T82!G;&]B>6UA> T@
 +M<W1A(&1S> T@<W1A(&1S>0T@<W1A(&1S>@T@<W1A('-X#2!S=&$@<WD-('-T
 +M82!S>@T@<W1A(&9I;&P-(&QD82 C,#$-('-T82!H:61E#2!L9&$@(S8T#2!S
 +M=&$@>F]O;0T-*BTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-*J!M
 +M04E.H$Q/3U -#2HJ*BJ@9T54H$M%65!215-3#0UM86EN#2!C;&D-:W!R97-S
 +M(&IS<B!G971I;@T@8VUP(",Q,S,@.V8Q/PT@8FYE(#IF,@T@;&1A(&1S> T@
 +M8VUP("-A;F=M87@O,B [;D^@34]21:!42$%.H%!)#2!B97$@.F-O;G0Q#2!I
 +M;F,@9'-X(#M/5$A%4E=)4T6@24Y#4D5!4T6@6"U23U1!5$E/3@T@:FUP(#IC
 +M;VYT#3IF,B!C;7 @(S$S-R [9C(_#2!B;F4@.F8S#2!L9&$@9'-X#2!B97$@
 +M.F-O;G0Q#2!D96,@9'-X#2!J;7 @.F-O;G0-.F8S(&-M<" C,3,T#2!B;F4@
 +M.F8T#2!L9&$@9'-Y#2!C;7 @(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@
 +M9'-Y(#MI3D-214%31:!9+5)/5$%424].#2!J;7 @.F-O;G0-.F8T(&-M<" C
 +M,3,X#2!B;F4@.F8U#2!L9&$@9'-Y#2!B97$@.F-O;G0Q#2!D96,@9'-Y#2!J
 +M;7 @.F-O;G0-.F8U(&-M<" C,3,U#2!B;F4@.F8V#2!L9&$@9'-Z#2!C;7 @
 +M(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@9'-Z(#M:+5)/5$%424].#2!J
 +M;7 @.F-O;G0-.F8V(&-M<" C,3,Y#2!B;F4@.F8W#2!L9&$@9'-Z#2!B97$@
 +M.F-O;G0Q#2!D96,@9'-Z#2!J;7 @.F-O;G0-.F8W(&-M<" C,3,V#2!B;F4@
 +M.G!L=7,-(&IM<"!I;FET#3IC;VYT,2!J;7 @.F-O;G0-.G!L=7,@8VUP(",G
 +M*R<-(&)N92 Z;6EN=7,-(&EN8R!Z;V]M(#MB04@LH%=(3Z!.145$4Z!%4E)/
 +M4J!#2$5#2TE.1S\-(&EN8R!Z;V]M#2!J;7 @.F-O;G0-.FUI;G5S(&-M<" C
 +M)RTG#2!B;F4@.F@-(&1E8R!Z;V]M#2!D96,@>F]O;0T@8G!L(#IC;VYT#2!I
 +M;F,@>F]O;0T@:6YC('IO;VT-(&IM<" Z8V]N= TZ:"!C;7 @(R=()PT@8FYE
 +M(#IS<&%C90T@;&1A(&AI9&4-(&5O<B C)# Q#2!S=&$@:&ED90T@:FUP(#IC
 +M;VYT#3IS<&%C92!C;7 @(R>@)PT@8FYE(#IQ#2!L9&$@9FEL; T@96]R(",D
 +M,#$-('-T82!F:6QL#2!J;7 @.F-O;G0-.G$@8VUP(",G42<@.U&@455)5%,-
 +M(&)N92 Z8V]N= T@:FUP(&-L96%N=7 -#3IC;VYT('-E:2 @.W-0145$H%1(
 +M24Y'4Z!54*!!H$))5 T-*BHJ*J!U4$1!5$6@04Y'3$53#0UU<&1A=&4@8VQC
 +M#2!L9&$@<W@-(&%D8R!D<W@-(&-M<" C86YG;6%X(#MA4D6@5T6@/CV@34%8
 +M24U53:!!3D=,13\-(&)C8R Z8V]N=#$-('-B8R C86YG;6%X(#II1B!33RP@
 +M4D53150-.F-O;G0Q('-T82!S> T@8VQC#2!L9&$@<WD-(&%D8R!D<WD-(&-M
 +M<" C86YG;6%X#2!B8V,@.F-O;G0R#2!S8F,@(V%N9VUA>" [<T%-1:!$14%,
 +M#3IC;VYT,B!S=&$@<WD-(&-L8PT@;&1A('-Z#2!A9&,@9'-Z#2!C;7 @(V%N
 +M9VUA> T@8F-C(#IC;VYT,PT@<V)C("-A;F=M87@-.F-O;G0S('-T82!S>@T-
 +M*BHJ*J!R3U1!5$6@0T]/4D1)3D%415,-#7)O=&%T90T-*BHJH&9)4E-4+*!#
 +M04Q#54Q!5$6@5#$L5#(L+BXN+%0Q, T-*BJ@=%=/H$U!0U)/4Z!43Z!324U0
 +M3$E&6:!/55*@3$E&10UA9&1A(&UA8R @.V%$1*!45T^@04Y'3$53H%1/1T54
 +M2$52#2!C;&,-(&QD82!=,0T@861C(%TR#2!C;7 @(V%N9VUA>" [:5.@5$A%
 +MH%-53: ^H#(J4$D_#2!B8V,@9&]N90T@<V)C("-A;F=M87@@.VE&H%-/+*!3
 +M54)44D%#5* R*E!)#61O;F4@/#P\#0US=6)A(&UA8R @.W-50E1204-4H%17
 +M3Z!!3D=,15,-('-E8PT@;&1A(%TQ#2!S8F,@73(-(&)C<R!D;VYE#2!A9&,@
 +M(V%N9VUA>" [;T]04RR@5T6@3D5%1*!43Z!!1$2@,BI020UD;VYE(#P\/ T-
 +M*BJ@;D]7H$-!3$-53$%41:!4,2Q4,BQ%5$,N#0T@/CX^('-U8F$L<WD[<WH-
 +M('-T82!T,2 [5#$]4UDM4UH-(#X^/B!A9&1A+'-Y.W-Z#2!S=&$@=#(@.U0R
 +M/5-9*U-:#2 ^/CX@861D82QS>#MS>@T@<W1A('0S(#M4,SU36"M36@T@/CX^
 +M('-U8F$L<W@[<WH-('-T82!T-" [5#0]4U@M4UH-(#X^/B!A9&1A+'-X.W0R
 +M#2!S=&$@=#4@.U0U/5-8*U0R#2 ^/CX@<W5B82QS>#MT,0T@<W1A('0V(#M4
 +M-CU36"U4,0T@/CX^(&%D9&$L<W@[=#$-('-T82!T-R [5#<]4U@K5#$-(#X^
 +M/B!S=6)A+'0R.W-X#2!S=&$@=#@@.U0X/50R+5-8#2 ^/CX@<W5B82QS>3MS
 +M> T@<W1A('0Y(#M4.3U362U36 T@/CX^(&%D9&$L<W@[<WD-('-T82!T,3 @
 +M.U0Q,#U36"M360T-*J!E5*!63TE,02$-#2HJ*J!N15A4+*!#04Q#54Q!5$6@
 +M82QB+&,L+BXN+&D-#2HJH&%.3U1(15*@55-%1E5,H$Q)5%1,1:!-04-23PUD
 +M:78R(&UA8R @.V1)5DE$1:!!H%-)1TY%1*!.54U"15*@0EF@,@T[:52@25.@
 +M05-354U%1*!42$%4H%1(1:!.54U"15(-(&)P;"!P;W,@.TE3H$E.H%1(1:!!
 +M0T-5355,051/4@T@8VQC#2!E;W(@(R1F9B [=T6@3D5%1*!43Z!53BU.14=!
 +M5$E61:!42$6@3E5-0D52#2!A9&,@(S Q(#M"6:!404M)3D>@250G4Z!#3TU0
 +M3$5-14Y4#2!L<W(@(#M$259)1$6@0EF@5%=/#2!C;&,-(&5O<B C)&9F#2!A
 +M9&,@(S Q(#MM04M%H$E4H$Y%1T%4259%H$%'04E.#2!J;7 @9&]N961I=@UP
 +M;W,@;'-R(" [;E5-0D52H$E3H%!/4TE4259%#61O;F5D:78@/#P\#0UM=6PR
 +M(&UA8R @.VU53%1)4$Q9H$&@4TE'3D5$H$Y534)%4J!"6: R#2!B<&P@<&]S
 +M;0T@8VQC#2!E;W(@(R1F9@T@861C(",D,#$-(&%S; T@8VQC#2!E;W(@(R1F
 +M9@T@861C(",D,#$-(&IM<"!D;VYE;75L#7!O<VT@87-L#61O;F5M=6P@/#P\
 +M#0TJ*J!N3U1%H%1(052@5T6@05)%H$-54E)%3E1,6:!-04M)3D>@0:!-24Y/
 +M4J!,14%0#2HJH$]&H$9!251(H%1(052@3D^@3U9%4D9,3U=3H%=)3$R@3T-#
 +M55(N#0TZ8V%L8V$@8VQC#2!L9'@@=#$-(&QD82!C;W,L> T@;&1X('0R#2!A
 +M9&,@8V]S+'@-('-T82!A,3$@.V$]*$-/4RA4,2DK0T]3*%0R*2DO,@TZ8V%L
 +M8V(@;&1X('0Q#2!L9&$@<VEN+'@-('-E8PT@;&1X('0R#2!S8F,@<VEN+'@-
 +M('-T82!B,3(@.V(]*%-)3BA4,2DM4TE.*%0R*2DO,@TZ8V%L8V,@;&1X('-Y
 +M#2!L9&$@<VEN+'@-(#X^/B!M=6PR#2!S=&$@8S$S(#MC/5-)3BA362D-.F-A
 +M;&-D('-E8PT@;&1X('0X#2!L9&$@8V]S+'@-(&QD>"!T-PT@<V)C(&-O<RQX
 +M#2!S96,-(&QD>"!T-0T@<V)C(&-O<RQX#2!C;&,-(&QD>"!T-@T@861C(&-O
 +M<RQX(#MD23TH0T]3*%0X*2U#3U,H5#<I*T-/4RA4-BDM0T]3*%0U*2DO,@T@
 +M/CX^(&1I=C(-(&-L8PT@;&1X('0S#2!A9&,@<VEN+'@-('-E8PT@;&1X('0T
 +M#2!S8F,@<VEN+'@-('-T82!D,C$@.V0]*%-)3BA4,RDM4TE.*%0T*2MD22DO
 +M,@TZ8V%L8V4@<V5C#2!L9'@@=#4-(&QD82!S:6XL> T@;&1X('0V#2!S8F,@
 +M<VEN+'@-('-E8PT@;&1X('0W#2!S8F,@<VEN+'@-('-E8PT@;&1X('0X#2!S
 +M8F,@<VEN+'@@.V5)/2A324XH5#4I+5-)3BA4-BDM4TE.*%0W*2U324XH5#@I
 +M*2\R#2 ^/CX@9&EV,@T@8VQC#2!L9'@@=#,-(&%D8R!C;W,L> T@8VQC#2!L
 +M9'@@=#0-(&%D8R!C;W,L> T@<W1A(&4R,B [93TH0T]3*%0S*2M#3U,H5#0I
 +M*V5)*2\R#3IC86QC9B!L9'@@=#D-(&QD82!S:6XL> T@<V5C#2!L9'@@=#$P
 +M#2!S8F,@<VEN+'@-('-T82!F,C,@.V8]*%-)3BA4.2DM4TE.*%0Q,"DI+S(-
 +M.F-A;&-G(&QD>"!T-@T@;&1A('-I;BQX#2!S96,-(&QD>"!T. T@<V)C('-I
 +M;BQX#2!S96,-(&QD>"!T-PT@<V)C('-I;BQX#2!S96,-(&QD>"!T-0T@<V)C
 +M('-I;BQX(#MG23TH4TE.*%0V*2U324XH5#@I+5-)3BA4-RDM4TE.*%0U*2DO
 +M,@T@/CX^(&1I=C(-(&-L8PT@;&1X('0T#2!A9&,@8V]S+'@-('-E8PT@;&1X
 +M('0S#2!S8F,@8V]S+'@-('-T82!G,S$@.V<]*$-/4RA4-"DM0T]3*%0S*2MG
 +M22DO,@TZ8V%L8V@@8VQC#2!L9'@@=#8-(&QD82!C;W,L> T@;&1X('0W#2!A
 +M9&,@8V]S+'@-('-E8PT@;&1X('0U#2!S8F,@8V]S+'@-('-E8PT@;&1X('0X
 +M#2!S8F,@8V]S+'@@.VA)/2A#3U,H5#8I*T-/4RA4-RDM0T]3*%0U*2U#3U,H
 +M5#@I*2\R#2 ^/CX@9&EV,@T@8VQC#2!L9'@@=#,-(&%D8R!S:6XL> T@8VQC
 +M#2!L9'@@=#0-(&%D8R!S:6XL> T@<W1A(&@S,B [:#TH4TE.*%0S*2M324XH
 +M5#0I*VA)*2\R#3IW:&5W(&-L8PT@;&1X('0Y#2!L9&$@8V]S+'@-(&QD>"!T
 +M,3 -(&%D8R!C;W,L> T@<W1A(&DS,R [:3TH0T]3*%0Y*2M#3U,H5#$P*2DO
 +M,@T-*BJ@:50G4Z!!3$R@1$]73DA)3$R@1E)/3:!(15)%+@T-9&]W;FAI;&P-
 +M*BHJ*J!C3$5!4J!"549&15(-*J!AH$Q)5%1,1:!-04-23PT-<V5T8G5F(&UA
 +M8R @.W!55*!"549&15)3H%=(15)%H%1(15F@0T%.H$)%H$A54E0-(&QD82 C
 +M,# -('-T82!B=69F97(-(&QD82!Z=&5M<" [:$E'2*!"651%#7-T86)U9B!S
 +M=&$@8G5F9F5R*S$-(#P\/ T-(#X^/B!S971B=68-8VQR9')A=R!L9'@@(S X
 +M#2!L9&$@(S P#3IF;V]L(&QD>2 C,# -.F1O<&4@<W1A("AB=69F97(I+'D-
 +M(&EN>0T@8FYE(#ID;W!E#2!I;F,@8G5F9F5R*S$-(&1E> T@8FYE(#IF;V]L
 +M#0TJ*BHJH&U9H$=/3T1.15-3H$)55*!I)TV@0:!$3U!%#2IC;')D<F%WH&QD
 +M8:!G;&]B>&UI;@TJH&QS<J"@.VY%142@5$^@1T54H$E.5$^@5$A%H%))1TA4
 +MH$-/3%5-3@TJH&)C8Z Z979E;J [95A03$%)3D5$H$E.H$U/4D6@1$5404E,
 +MH$)%3$]7#2J@;&1YH",D.# -*J!S='F@8G5F9F5RH#MP4D5354U!0DQ9H%1(
 +M25.@5TE,3*!"1:!!H$Q)5%1,10TJH&-L8Z"@.TU/4D6@149&24-)14Y4+@TJ
 +M.F5V96Z@861CH&)U9F9E<BLQ#2J@<W1AH&)U9F9E<BLQ#2J@;&1AH&=L;V)X
 +M;6%X#2J@<V5C#2J@<V)CH&=L;V)X;6EN#2J@=&%X#2J@:6YX#2J@;&1YH&=L
 +M;V)Y;6%X#2J@8F5QH#IR97-E= TJ.GEA>:!L9&&@(R0P, TJH&QD>:!G;&]B
 +M>6UA> TJ.F)L86B@<W1AH"AB=69F97(I+'D-*J!D97D-*J!C<'F@9VQO8GEM
 +M:6X-*J!B8W.@.F)L86@-*J!L9&&@8G5F9F5R#2J@96]RH",D.# -*J!S=&&@
 +M8G5F9F5R#2J@8FYEH#IW:&]P964-*J!I;F.@8G5F9F5R*S$-*CIW:&]P966@
 +M9&5X#2J@8FYEH#IY87D-*CIR97-E=*!L9&&@(S"@.VY%142@5$^@4D53152@
 +M5$A%4T6@1U594PTJH'-T8:!G;&]B>&UA> TJH'-T8:!G;&]B>6UA> TJH&QD
 +M8: C)&9F#2J@<W1AH&=L;V)X;6EN#2J@<W1AH&=L;V)Y;6EN#0TJ*BHJH&Y%
 +M6%0LH%)%042@04Y$H$1205>@4$],64=/3E,-#7)E861D<F%W(&QD>2 C,# -
 +M('-T>2!I;F1E> UO8FIL;V]P(&QD>2!I;F1E> T@;&1A('!O;'EL:7-T+'D@
 +M.V9)4E-4+*!42$6@3E5-0D52H$]&H%!/24Y44PT@8FYE(#IC;VYT(#MB552@
 +M24:@3E5-4$])3E13H$E3H%I%4D^@5$A%3@T@:FUP(&]B:F1O;F4@.U=%H$%2
 +M1:!!5*!42$6@14Y$H$]&H%1(1:!,25-4#3IC;VYT('-T82!C;W5N='!T<PT@
 +M:6YC(&EN9&5X#0TJH')/5$%41:!04D]*14-4H$%.1*!$4D%7H%1(1:!03TQ9
 +M1T].#2J@;4%+1:!355)%H$)51D9%4J!"14E.1Z!$4D%73J!43Z!)4Z!#3$5!
 +M4B$-#3ID;VET(&IS<B!R;W1P<F]J#0TJH&-/3E9%4E2@6$U)3J!!3D2@6$U!
 +M6*!43Z!#3TQ534Y3#0T@;&1A(&QO8WAM:6X-(&QS<@T@;'-R#2!L<W(@(#M8
 +MH$U/1* X#2!S=&$@;&]C>&UI;@T@8VUP(&=L;V)X;6EN#2!B8W,@.FYA: T@
 +M<W1A(&=L;V)X;6EN#3IN86@@;&1A(&QO8WEM:6X-(&-M<"!G;&]B>6UI;@T@
 +M8F-S(#IU:'5H#2!S=&$@9VQO8GEM:6X-.G5H=6@@;&1A(&QO8WAM87@-(&QS
 +M<@T@;'-R#2!L<W(-('-T82!L;V-X;6%X#2!C;7 @9VQO8GAM87@-(&)C8R Z
 +M;F]W87D-('-T82!G;&]B>&UA> TZ;F]W87D@;&1A(&QO8WEM87@-(&-M<"!G
 +M;&]B>6UA> T@8F-C(&5O<F9I;&P-('-T82!G;&]B>6UA> T-*J!I1J!54TE.
 +M1Z!42$6@96]R+4)51D9%4BR@0T]06:!)3E1/H$1205=)3D>@0E5&1D52#2J@
 +M84Y$H%1(14Z@0TQ%05*@5$A%H&5O<BU"549&15(-#65O<F9I;&P@;&1A(&9I
 +M;&P-(&)E<2!O8FIL;V]P#0T@/CX^('-E=&)U9@T@;&1A(",\96]R8G5F#2!S
 +M=&$@=&5M<#$-(&QD82 C/F5O<F)U9@T@<W1A('1E;7 Q*S$-#2!L9&$@;&]C
 +M>&UI;B [;&]C>&UI;J!.3U>@0T].5$%)3E.@0T],54U.#2!L<W(@(#ME04-(
 +MH$-/3%5-3J!)4Z Q,CB@0EE415,-(&)C8R Z979E;B [<T^@5$A%4D6@34E'
 +M2%2@0D6@0:!#05)260T@;&1Y(",D.# -('-T>2!B=69F97(-('-T>2!T96UP
 +M,0T@8VQC#3IE=F5N('-T82!T,@T@861C(&)U9F9E<BLQ#2!S=&$@8G5F9F5R
 +M*S$@.V5!0TB@0T],54U.H$E3H#$R.*!"651%4PT@;&1A('0R#2!A9&,@=&5M
 +M<#$K,2 [;D]7H%=%H%=)3$R@4U1!4E2@052@5$A%#2!S=&$@=&5M<#$K,2 [
 +M0T],54U.#0T@;&1A(&QO8WAM87@-('-E8PT@<V)C(&QO8WAM:6X-('1A>" [
 +M=$]404R@3E5-0D52H$]&H$-/3%5-3E.@5$^@1$\-(&EN>" @.T4N1RZ@1DE,
 +M3*!#3TQ534Y3H#$N+C,-(&QD>2!L;V-Y;6%X#2!B;F4@.F9O;W -(&EN8R!L
 +M;V-Y;6%X#3IF;V]P(&QD>2!L;V-Y;6%X#2!L9&$@(S P#3IG;V]P(&5O<B H
 +M=&5M<#$I+'D@.V5O<BU"549&15(-('!H80TJH&U!64)%H%!55*!!3J!E;W*@
 +M0D5,3U<_#2!E;W(@*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-(&QD82 C
 +M,# @.VU)1TA4H$%3H%=%3$R@0TQ%05*@252@3D]7#2!S=&$@*'1E;7 Q*2QY
 +M#2!P;&$-(&1E>0T@8W!Y(&QO8WEM:6X-(&)C<R Z9V]O< T@;&1A(&)U9F9E
 +M<@T@96]R(",D.# -('-T82!B=69F97(-('-T82!T96UP,0T@8FYE(#IB;V]P
 +M#2!I;F,@8G5F9F5R*S$-(&EN8R!T96UP,2LQ#3IB;V]P(&1E> T@8FYE(#IF
 +M;V]P#2!J;7 @;V)J;&]O< T-;V)J9&]N90TJ*BHJH'-705"@0E5&1D524PT-
 +M<W=A<&)U9B!L9&$@=FUC<V(-(&5O<B C)# R(#MP4D545%F@5%))0TM9+*!%
 +M2#\-('-T82!V;6-S8@T@;&1A(",D,#@-(&5O<B!Z=&5M<" [6E1%35 ]2$E'
 +M2*!"651%H$I54U2@1DQ)4%,-('-T82!Z=&5M<" [0D545T5%3J D,S"@04Y$
 +MH"0S. T-(&IM<"!M86EN(#MA4D]53D2@04Y$H$%23U5.1*!71:!'3RXN+@T-
 +M('1X=" G9T5%H&)204E.+*!72$%4H$1/H%E/5:!704Y4H%1/H$1/H"<-('1X
 +M=" G5$].24=(5#\G#0TJ*J!R3U1!5$4LH%!23TI%0U0LH$%.1*!35$]21:!4
 +M2$6@4$])3E13#2H-*J!T2$E3H%!!4E2@25.@0:!324=.249)0T%.5*!#2$%.
 +M1T6@4TE.0T4-*J!6,BXP+J"@;D]7H$E4H$E3H$&@0T]-4$Q%5$5,6:!'14Y%
 +M4D%,H%!/3%E'3TZ@4$Q/5%1%4BX-*J!AH%-%5*!/1J!03TE.5%.@25.@4D5!
 +M1*!)3BR@4D]4051%1*!!3D2@4%)/2D5#5$5$+*!!3D0-*J!03$]45$5$H$E.
 +M5$^@5$A%H$1205=)3D>@0E5&1D52H"AE;W*@3U*@3D]234%,*2X-#7)O='!R
 +M;VH-#2J@8:!.14%4H$U!0U)/#6YE9R!M86,@(#MC2$%.1T6@5$A%H%-)1TZ@
 +M3T:@0:!45T\G4Z!#3TU03$5-14Y4#2!C;&,-(&QD82!=,2 [3E5-0D52+@T@
 +M96]R(",D9F8-(&%D8R C)# Q#2 \/#P-#2HM+2TM+2TM+2TM+2TM+2TM+2TM
 +M+2TM+2TM+2TM+2TM#2J@=$A%4T6@34%#4D]3H%)%4$Q!0T6@5$A%H%!2159)
 +M3U53H%!23TI%0U1)3TX-*J!354)23U5424Y%+@T-<VUU;'2@;6%CH#MM54Q4
 +M25!,6:!45T^@4TE'3D5$H#@M0DE4#2 [3E5-0D524SJ@82IY+S8TH"T^H&$-
 +M('-T8:!Z,PT@8VQCH* [=$A)4Z!-54Q425!,6:!)4Z!&3U*@3D]234%,#2!E
 +M;W*@(R1F9J [3E5-0D524RR@22Y%+J!8/2TV-"XN-C0-(&%D8Z C)# Q#2!S
 +M=&&@>C0-(&QD8: H>C,I+'D-('-E8PT@<V)CH"AZ-"DL>0T@/#P\H* [84Q,
 +MH$1/3D6@.BD-#7-M=6QT>B!M86,@.VU53%1)4$Q9H%173Z!324=.142@."U"
 +M250-(" @.TY534)%4E,ZH&$J>2\V-* M/J!A#2!S=&$@>C$-(&-L8R @.V%.
 +M1*!42$E3H$U53%1)4$Q9H$E3H%-014-)1DE#04Q,60T@96]R(",D9F8@.T9/
 +M4J!42$6@4%)/2D5#5$E/3J!005)4+*!72$5210T@861C(",D,#$@.TY534)%
 +M4E.@05)%H"TQ,3 N+C$Q,*!!3D2@,"XN-# -('-T82!Z,@T@;&1A("AZ,2DL
 +M>0T@<V5C#2!S8F,@*'HR*2QY#2 \/#P@(#MA3$R@1$].1: Z*0T-<')O:F5C
 +M="!M86,@(#MT2$6@04-454%,H%!23TI%0U1)3TZ@4D]55$E.10T[=$A%H%)/
 +M551)3D6@5$%+15.@5$A%H%!/24Y4#3M=,:!=,J!=,RR@4D]4051%4Z!!3D0-
 +M.U!23TI%0U13H$E4+*!!3D2@4U1/4D53H%1(10T[4D5354Q4H$E.H%TQH%TR
 +MH%TS+@T-(&QD>2!=,2 [;55,5$E03%F@1DE24U2@4D]4051)3TZ@0T],54U.
 +M#2!L9&$@83$Q#2 ^/CX@<VUU;'0-('-T82!P,70-(&QD82!D,C$-(#X^/B!S
 +M;75L= T@<W1A(' R= T@;&1A(&<S,0T@/CX^('-M=6QT#2!S=&$@<#-T#2!L
 +M9'D@73(@.W-%0T].1*!#3TQ534X-(&QD82!B,3(-(#X^/B!S;75L= T@8VQC
 +M#2!A9&,@<#%T#2!S=&$@<#%T#2!L9&$@93(R#2 ^/CX@<VUU;'0-(&-L8PT@
 +M861C(' R= T@<W1A(' R= T@;&1A(&@S,@T@/CX^('-M=6QT#2!C;&,-(&%D
 +M8R!P,W0-('-T82!P,W0-(&QD>2!=,R [=$A)4D2@0T],54U.#2!L9&$@8S$S
 +M#2 ^/CX@<VUU;'0-(&-L8PT@861C(' Q= T@<W1A(' Q= T@;&1A(&8R,PT@
 +M/CX^('-M=6QT#2!C;&,-(&%D8R!P,G0-('-T82!P,G0-(&QD82!I,S,-(#X^
 +M/B!S;75L= T@8VQC#2!A9&,@<#-T#2!S=&$@73,@.W)/5$%4142@>@T@=&%X
 +M#2!L9'D@>F1I=BQX(#MT04),1:!/1J!$+RA:*UHP*0T@(" [;D]7H'F@0T].
 +M5$%)3E.@4%)/2D5#5$E/3J!#3TY35 T-(&QD82!P,70-(#X^/B!S;75L='H-
 +M(&QD>"!Z;V]M#2!C<'@@(S8T#2!B97$@8V]N='@-('-T>2!T96UP,0T@;&1Y
 +M('IO;VT-(#X^/B!S;75L= T@;&1Y('1E;7 Q#6-O;G1X(&-L8PT@861C(",V
 +M-" [;T9&4T54H%1(1:!#3T]21$E.051%#2!S=&$@73$@.W)/5$%4142@04Y$
 +MH%!23TI%0U1%1 T@8VUP(&QO8WAM:6X@.W-%1:!)1J!)5*!)4Z!!H$Q/0T%,
 +MH$U)3DE-54T-(&)C<R!N;W1X;6EN#2!S=&$@;&]C>&UI;@UN;W1X;6EN(&-M
 +M<"!L;V-X;6%X#2!B8V,@;F]T>&UA> T@<W1A(&QO8WAM87@-#6YO='AM87@@
 +M;&1A(' R= T@/CX^('-M=6QT>@T@8W!X(",V- T@8F5Q(&-O;G1Y#2!L9'D@
 +M>F]O;0T@/CX^('-M=6QT#6-O;G1Y(&-L8PT@861C(",V- T@<W1A(%TR(#MR
 +M3U1!5$5$H$%.1*!04D]*14-4142@>0T@8VUP(&QO8WEM:6X-(&)C<R!N;W1Y
 +M;6EN#2!S=&$@;&]C>6UI;@UN;W1Y;6EN(&-M<"!L;V-Y;6%X#2!B8V,@;F]T
 +M>6UA> T@<W1A(&QO8WEM87@-#6YO='EM87@@/#P\(" [84Q,H$1/3D4-#2J@
 +M;&1AH",\96]R8G5FH#MF25)35*!71:!.145$H%1/H$-,14%2H%1(10TJH'-T
 +M8:!B=69F97*@.V5O<J!"549&15(-*J!L9&&@(SYE;W)B=68-*J!S=&&@8G5F
 +M9F5R*S$-#2!L9&$@(S @.W)%4T54H'E-24Z@04Y$H'E-05@-('-T82!L;V-Y
 +M;6%X#2!S=&$@;&]C>&UA> T@;&1A(",D9F8-('-T82!L;V-Y;6EN#2!S=&$@
 +M;&]C>&UI;@T-<F5A9'!T<R!L9'D@:6YD97@-(&QD82!P;VQY;&ES="QY#2!S
 +M=&$@<#%X#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#%Y#2!I;GD-(&QD
 +M82!P;VQY;&ES="QY#2!S=&$@<#%Z#2!I;GD-(&1E8R!C;W5N='!T<PT@;&1A
 +M('!O;'EL:7-T+'D-('-T82!P,G@-(&EN>0T@;&1A('!O;'EL:7-T+'D-('-T
 +M82!P,GD-(&EN>0T@;&1A('!O;'EL:7-T+'D-('-T82!P,GH-(&EN>0T@9&5C
 +M(&-O=6YT<'1S#2!L9&$@<&]L>6QI<W0L>0T@<W1A(' S> T@:6YY#2!L9&$@
 +M<&]L>6QI<W0L>0T@<W1A(' S>0T@:6YY#2!L9&$@<&]L>6QI<W0L>0T@<W1A
 +M(' S>@T@:6YY#2!S='D@:6YD97@-(#X^/B!P<F]J96-T+' Q>#MP,7D[<#%Z
 +M#2 ^/CX@<')O:F5C="QP,G@[<#)Y.W R>@T@/CX^('!R;VIE8W0L<#-X.W S
 +M>3MP,WH-#2!L9&$@:&ED90T@8F5Q(#ID;VET#2!L9&$@<#)X(#MH241$14Z@
 +M1D%#1:!#2$5#2PT@<V5C#2!S8F,@<#%X#2!T87D@(#MY/2A8,BU8,2D-(&QD
 +M82!P,WD-('-E8PT@<V)C(' R>2 [83TH63,M63(I#2 ^/CX@<VUU;'0-('-T
 +M82!T96UP,0T@;&1A(' S> T@<V5C#2!S8F,@<#)X#2!T87D-(&QD82!P,GD-
 +M('-E8PT@<V)C(' Q>0T@/CX^('-M=6QT#2!C;7 @=&5M<#$@.VE&H%@Q*EDR
 +M+5DQ*E@RH#Z@,*!42$5.H$9!0T4-(&)M:2 Z9&]I=" [25.@5DE324),10T@
 +M9&5C(&-O=6YT<'1S(#MO5$A%4E=)4T6@4D5!1*!)3J!214U!24Y)3D<-(&)E
 +M<2 Z86)O<G0@.U!/24Y44Z!!3D2@4D5455).#3IP;V]P(&EN8R!I;F1E> T@
 +M:6YC(&EN9&5X#2!I;F,@:6YD97@-(&1E8R!C;W5N='!T<PT@8FYE(#IP;V]P
 +M#3IA8F]R="!R=',-#3ID;VET(&QD82!P,7@-('-T82!X,0T@;&1A(' Q>0T@
 +M<W1A('DQ#2!L9&$@<#)X#2!S=&$@>#(-(&QD82!P,GD-('-T82!Y,@T@:G-R
 +M(&1R87<-(&QD82!P,G@-('-T82!X,0T@;&1A(' R>0T@<W1A('DQ#2!L9&$@
 +M<#-X#2!S=&$@>#(-(&QD82!P,WD-('-T82!Y,@T@:G-R(&1R87<-#2!D96,@
 +M8V]U;G1P=',-(&)N92!P;VQY;&]O<" [:5.@252@2E535*!!H%1224%.1TQ%
 +M/PT@:FUP('!O;'ED;VYE#0UP;VQY;&]O<"!L9'D@:6YD97@-(&QD82!P;VQY
 +M;&ES="QY#2!S=&$@<#)X#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#)Y
 +M#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#)Z#2!I;GD-('-T>2!I;F1E
 +M> T@/CX^('!R;VIE8W0L<#)X.W R>3MP,GH-#2!L9&$@<#)X#2!S=&$@>#$-
 +M(&QD82!P,GD-('-T82!Y,0T@;&1A(' S> T@<W1A('@R#2!L9&$@<#-Y#2!S
 +M=&$@>3(-(&IS<B!D<F%W#0T@;&1A(' R> T@<W1A(' S> T@;&1A(' R>0T@
 +M<W1A(' S>0T@9&5C(&-O=6YT<'1S#2!B97$@<&]L>61O;F4-(&IM<"!P;VQY
 +M;&]O< UP;VQY9&]N92!L9&$@<#%X(#MC3$]31:!42$6@4$],64=/3@T@<W1A
 +M('@R#2!L9&$@<#%Y#2!S=&$@>3(-(&QD82!P,W@-('-T82!X,0T@;&1A(' S
 +M>0T@<W1A('DQ#2!J<W(@9')A=PT@<G1S#0T@='AT("=S04U%H%1(24Y'H%=%
 +MH$1/H$5615)9H$Y)1TA4+*!P24Y+63J@)PT@='AT("=44EF@5$^@5$%+1:!/
 +M5D52H%1(1:!73U),1"$G#0T-*BTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM
 +M+2TM+2T-*J!G14Y%4D%,H%%515-424].04),12U604Q51:!%4E)/4J!04D]#
 +M14154D4-#2IC:&]K9:!L9'B@(S P#2HZ;&]O<*!L9&&@.F-T97AT+'@-*J!B
 +M97&@.F1O;F4-*J!J<W*@8VAR;W5T#2J@:6YX#2J@:FUPH#IL;V]P#2HZ9&]N
 +M9:!R=',-*CIC=&5X=*!H97B@,&2@.V-R#2J@='ATH"=33TU%5$A)3D>@0TA/
 +M2T5$H#HH)PTJH&AE>* P9# P#2H-('1X=" G;D%21B$G#0TJ+2TM+2TM+2TM
 +M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH&1205=)3B>@0:!,24Y%+J"@8:!&
 +M04A.H$Q!2$XN#0TJ*BJ@<T]-1:!54T5&54R@34%#4D]3#0UC:6YI="!M86,@
 +M(#MM04-23Z!43Z!)3DE424%,25I%H%1(1:!#3U5.5$52#2!L9&$@73$@.T18
 +MH$]2H$19#2!L<W(-(#P\/" @.W1(1:!$6"\RH$U!2T53H$&@3DE#15*@3$]/
 +M2TE.1Z!,24Y%#0TJ*BHJ*J!M04-23Z!43Z!404M%H$&@4U1%4*!)3J!X#0UX
 +M<W1E<"!M86,-(&QD>"!D>" [;E5-0D52H$]&H$Q/3U"@251%4D%424].4PT@
 +M/CX^(&-I;FET+&1X#7AL;V]P(&QS<B!C:'5N:PT@8F5Q(&9I>&,@.W501$%4
 +M1:!#3TQ534X-('-B8R!D>0T@8F-C(&9I>'D@.W1)346@5$^@4U1%4*!)3J!Y
 +M#2!D97@-(&)N92!X;&]O< UD;VYE(&QD82!O;&1X(#MP3$]4H%1(1:!,05-4
 +MH$-(54Y+#2!E;W(@8VAU;FL-(&]R82 H8G5F9F5R*2QY#2!S=&$@*&)U9F9E
 +M<BDL>0T@<G1S#0UF:7AC('!H80T@;&1A(&]L9'@-(&]R82 H8G5F9F5R*2QY
 +M(#MP3$]4#2!S=&$@*&)U9F9E<BDL>0T@;&1A(",D9F8@.W501$%41:!#2%5.
 +M2PT@<W1A(&]L9'@-('-T82!C:'5N:PT@;&1A(",D.# @.VE.0U)%05-%H%1(
 +M1:!#3TQ534X-(&5O<B!B=69F97(-('-T82!B=69F97(-(&)N92!C,@T@:6YC
 +M(&)U9F9E<BLQ#6,R#2!P;&$-('-B8R!D>0T@8F-S(&-O;G0-(&%D8R!D> T@
 +M:68@:2Q=,2 [9$^@5T6@55-%H&EN>:!/4J!D97D_#2!I;GD-(&5L<V4-(&1E
 +M>0T@9FEN#6-O;G0@9&5X#2!B;F4@>&QO;W -(&IM<"!D;VYE#0UF:7AY(&%D
 +M8R!D> T@<&AA#2!L9&$@;VQD> T@96]R(&-H=6YK#2!O<F$@*&)U9F9E<BDL
 +M>0T@<W1A("AB=69F97(I+'D-(&QD82!C:'5N:PT@<W1A(&]L9'@-('!L80T@
 +M:68@:2Q=,2 [=5!$051%H'D-(&EN>0T@96QS90T@9&5Y#2!F:6X-(&1E> T@
 +M8FYE('AL;V]P#2!R=',-(#P\/" @.V5.1*!/1J!M04-23Z!84U1%4 T-*BHJ
 +M*BJ@=$%+1:!!H%-415"@24Z@>0T->7-T97 @;6%C#2!L9'@@9'D@.VY534)%
 +M4J!/1J!,3T]0H$E415)!5$E/3E,-(&)E<2!D;VYE(#MI1J!$63TPH$E4)U.@
 +M2E535*!!H%!/24Y4#2 ^/CX@8VEN:70L9'D-('-E8PUY;&]O<"!P:&$-(&QD
 +M82!O;&1X#2!O<F$@*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-('!L80T@
 +M:68@:2Q=,0T@:6YY#2!E;'-E#2!D97D-(&9I;@T@<V)C(&1X#2!B8V,@9FEX
 +M> T@9&5X#2!B;F4@>6QO;W -9&]N92!L9&$@;VQD> T@;W)A("AB=69F97(I
 +M+'D-('-T82 H8G5F9F5R*2QY#2!R=',-#69I>'@@861C(&1Y#2!L<W(@;VQD
 +M> T@<V5C(" [:4U03U)404Y4(0T@8F5Q(&9I>&,-(&1E> T@8FYE('EL;V]P
 +M#2!J;7 @9&]N90T-9FEX8R!P:&$-(&QD82 C)#@P#2!S=&$@;VQD> T@96]R
 +M(&)U9F9E<@T@<W1A(&)U9F9E<@T@8FYE(&,R#2!I;F,@8G5F9F5R*S$-8S(@
 +M<&QA#2!D97@-(&)N92!Y;&]O< T@:FUP(&1O;F4-(#P\/" @.V5.1*!/1J!M
 +M04-23Z!94U1%4 T-*J!T04M%H$%.H%B@4U1%4*!)3J!42$6@96]RH$)51D9%
 +M4@TJH'1(1:!33TQ%H$-(04Y'1:!)4Z!43Z!54T6@96]RH$E.4U1%042@3T:@
 +M;W)A#0UE;W)X<W1E<"!M86,-(&QD>"!D>" [;E5-0D52H$]&H$Q/3U"@251%
 +M4D%424].4PT@/CX^(&-I;FET+&1X#7AL;V]P(&QS<B!C:'5N:PT@8F5Q(&9I
 +M>&,@.W501$%41:!#3TQ534X-('-B8R!D>0T@8F-C(&9I>'D@.W1)346@5$^@
 +M4U1%4*!)3J!Y#2!D97@-(&)N92!X;&]O< UD;VYE(&QD82!O;&1X(#MP3$]4
 +MH%1(1:!,05-4H$-(54Y+#2!E;W(@8VAU;FL-(&5O<B H8G5F9F5R*2QY#2!S
 +M=&$@*&)U9F9E<BDL>0T@<G1S#0UF:7AC('!H80T@;&1A(&]L9'@-(&5O<B H
 +M8G5F9F5R*2QY(#MP3$]4#2!S=&$@*&)U9F9E<BDL>0T@;&1A(",D9F8@.W50
 +M1$%41:!#2%5.2PT@<W1A(&]L9'@-('-T82!C:'5N:PT@;&1A(",D.# @.VE.
 +M0U)%05-%H%1(1:!#3TQ534X-(&5O<B!B=69F97(-('-T82!B=69F97(-(&)N
 +M92!C,@T@:6YC(&)U9F9E<BLQ#6,R#2!P;&$-('-B8R!D>0T@8F-S(&-O;G0-
 +M(&%D8R!D> T@:68@:2Q=,2 [9$^@5T6@55-%H&EN>:!/4J!D97D_#2!I;GD-
 +M(&5L<V4-(&1E>0T@9FEN#6-O;G0@9&5X#2!B;F4@>&QO;W -(&IM<"!D;VYE
 +M#0UF:7AY(&%D8R!D> T@<&AA#2!L9&$@;VQD> T@96]R(&-H=6YK#2!E;W(@
 +M*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-(&QD82!C:'5N:PT@<W1A(&]L
 +M9'@-('!L80T@:68@:2Q=,2 [=5!$051%H'D-(&EN>0T@96QS90T@9&5Y#2!F
 +M:6X-(&1E> T@8FYE('AL;V]P#2!R=',-(#P\/" @.V5.1*!/1J!M04-23Z!8
 +M4U1%4 T-#2J@=$%+1:!!H%DM4U1%4*!)3J!42$6@96]R+4)51D9%4@TJH&-(
 +M04Y'15.@1E)/3:!!0D]61:!!4D4ZH$].3%F@4$Q/5*!,05-4H%!!4E2@3T:@
 +M14%#2 TJH%9%4E1)0T%,H$-(54Y++*!$3TXG5*!03$]4H$Q!4U2@4$])3E0L
 +MH%!,3U2@5TE42*!E;W(-#65O<GES=&5P(&UA8PT@;&1X(&1Y(#MN54U"15*@
 +M3T:@3$]/4*!)5$52051)3TY3#2!B97$@9&]N92 [:4:@1%D],*!)5"=3H$I5
 +M4U2@0:!03TE.5 T@/CX^(&-I;FET+&1Y#2!S96,-*GEL;V]PH'!H80TJH&QD
 +M8:!O;&1X#2J@;W)AH"AB=69F97(I+'D-*J!S=&&@*&)U9F9E<BDL>0TJH'!L
 +M80UY;&]O<"!I9B!I+%TQ#2!I;GD-(&5L<V4-(&1E>0T@9FEN#2!S8F,@9'@-
 +M(&)C8R!F:7AX#2!D97@-(&)N92!Y;&]O< TJ9&]N9:!L9&&@;VQD> TJH&]R
 +M8: H8G5F9F5R*2QY#2J@<W1AH"AB=69F97(I+'D-9&]N92!R=',-#69I>'@@
 +M861C(&1Y#2!P:&$@(#MW1:!/3DQ9H%!,3U2@5$A%H$Q!4U2@4$%25*!/1J!%
 +M04-(H$-(54Y+#2!L9&$@;VQD> T@96]R("AB=69F97(I+'D-('-T82 H8G5F
 +M9F5R*2QY#2!P;&$-(&QS<B!O;&1X#2!S96,@(#MI35!/4E1!3E0A#2!B97$@
 +M9FEX8PT@9&5X#2!B;F4@>6QO;W -(&IM<"!D;VYE#0UF:7AC('!H80T@;&1A
 +M(",D.# -('-T82!O;&1X#2!E;W(@8G5F9F5R#2!S=&$@8G5F9F5R#2!B;F4@
 +M8S(-(&EN8R!B=69F97(K,0UC,B!P;&$-(&1E> T@8FYE('EL;V]P#2!J;7 @
 +M9&]N90T@/#P\(" [94Y$H$]&H&U!0U)/H%E35$50#2HJ*BJ@:4Y)5$E!3*!,
 +M24Y%H%-%5%50#0TJ*J!T2$6@0T]-345.5$5$H$Q)3D53H$)%3$]7H$%21:!.
 +M3U>@5$%+14Z@0T%21:!/1J!"6:!42$4-*BJ@0T%,3$E.1Z!23U5424Y%+@TJ
 +M9')A=Z ^/CZ@;6]V92QT>#$[>#&@H#MM3U9%H%-4549&H$E.5$^@6D523Z!0
 +M04=%#2J@/CX^H&UO=F4L='@R.W@RH* [=TA%4D6@252@0T%.H$)%H$U/1$E&
 +M245$#2J@/CX^H&UO=F4L='DQ.WDQ#2J@/CX^H&UO=F4L='DR.WDR#0UD<F%W
 +M(&QD82!F:6QL#2!B;F4@.G-E=&5O<@T@/CX^('-E=&)U9@T@:FUP(#IS971U
 +M< TZ<V5T96]R(&QD82 C/&5O<F)U9B [=5-%H&5O<J!"549&15*@24Y35$5!
 +M1*!/1@T@<W1A(&)U9F9E<B [1$E34$Q!6:!"549&15*@1D]2H$1205=)3D<-
 +M(&QD82 C/F5O<F)U9@T@<W1A(&)U9F9E<BLQ#0TZ<V5T=7 @<V5C(" [;4%+
 +M1:!355)%H%@Q/%@R#2!L9&$@>#(-('-B8R!X,0T@8F-S(#IC;VYT#2!L9&$@
 +M>3(@.VE&H$Y/5"R@4U=!4*!P,:!!3D2@<#(-(&QD>2!Y,0T@<W1A('DQ#2!S
 +M='D@>3(-(&QD82!X,0T@;&1Y('@R#2!S='D@>#$-('-T82!X,@T-('-E8PT@
 +M<V)C('@Q(#MN3U>@83U$6 TZ8V]N="!S=&$@9'@-(&QD>"!X,2 [<%54H%@Q
 +MH$E.5$^@>"R@3D]7H%=%H$-!3J!44D%32*!X,0T-8V]L=6UN('1X82 [9DE.
 +M1*!42$6@1DE24U2@0T],54U.H$9/4J!X#2!L<W(-(&QS<B @.W1(15)%H$%2
 +M1:!8,2\XH#$R.*!"651%H$),3T-+4PT@;'-R(" [=TA)0TB@345!3E.@6#$O
 +M,3:@,C4VH$)95$6@0DQ/0TM3#2!L<W(-(&)C8R Z979E;B [=TE42*!!H%!/
 +M4U-)0DQ%H$585%)!H#$R.*!"651%H$),3T-+#2!L9'D@(R0X," [24:@4T\L
 +MH%-%5*!42$6@2$E'2*!"250-('-T>2!B=69F97(-(&-L8PTZ979E;B!A9&,@
 +M8G5F9F5R*S$@.V%$1*!)3J!42$6@3E5-0D52H$]&H#(U-J!"651%H$),3T-+
 +M4PT@<W1A(&)U9F9E<BLQ#0T@<V5C#2!L9&$@>3(@.V-!3$-53$%41:!$60T@
 +M<V)C('DQ#2!B8W,@.F-O;G0R(#MI4Z!9,CY9,3\-(&5O<B C)&9F(#MO5$A%
 +M4E=)4T6@1%D]63$M63(-(&%D8R C)# Q#3IC;VYT,B!S=&$@9'D-(&-M<"!D
 +M>" [=TA/)U.@0DE'1T52.J!$6:!/4J!$6#\-(&)C8R!S=&5P:6YX(#MI1J!$
 +M6"R@5$A%3BXN+@T@:FUP('-T97!I;GD-#7-T97!I;G@@;&1Y('DQ#2!C<'D@
 +M>3(-(&QD82!B:71P+'@@.WB@0U524D5.5$Q9H$-/3E1!24Y3H%@Q#2!S=&$@
 +M;VQD> T@<W1A(&-H=6YK#2!B8V,@>&EN8WD@.V1/H%=%H%-415"@1D]25T%2
 +M1%.@3U*@0D%#2U=!4D13H$E.H'D_#2!J;7 @>&1E8WD-#7AI;F-Y(&QD82!F
 +M:6QL#2!B97$@;F]R;7AI;F,-(#X^/B!E;W)X<W1E<"QI;GD-;F]R;7AI;F,@
 +M/CX^('AS=&5P+&EN>0T->&1E8WD@;&1A(&9I;&P-(&)E<2!N;W)M>&1E8PT@
 +M/CX^(&5O<GAS=&5P+&1E>0UN;W)M>&1E8R ^/CX@>'-T97 L9&5Y#0US=&5P
 +M:6YY(&QD>2!Y,0T@;&1A(&)I=' L>" [>#U8,0T@<W1A(&]L9'@-(&QS<B @
 +M.WF@1$]%4TXG5*!54T6@0TA53DM3#2!E;W(@;VQD>" [<T^@5T6@2E535*!7
 +M04Y4H%1(1:!"250-('-T82!O;&1X#2!C<'D@>3(-(&)C<R!Y9&5C>0T->6EN
 +M8WD@;&1A(&9I;&P-(&)E<2!N;W)M:6YC#2 ^/CX@96]R>7-T97 L:6YY#6YO
 +M<FUI;F,@/CX^('ES=&5P+&EN>0T->61E8WD@;&1A(&9I;&P-(&)E<2!N;W)M
 +M9&5C#2 ^/CX@96]R>7-T97 L9&5Y#6YO<FUD96,@/CX^('ES=&5P+&1E>0T-
 +M#2HM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM#2J@8TQ%04Z@55 -
 +M#6-L96%N=7 @;&1A('9M8W-B(#MS5TE40TB@0TA!4J!23TV@0D%#2Z!)3@T@
 +M86YD(",E,3$Q,3 Q,#$@.T1%1D%53%0-('-T82!V;6-S8@T-(')T<R @.T)9
 +M12$-#2!T>'0@)U-024Y!3*!#4D%#2T52H"<-('1X=" G4TQ*H#8O.34G#0TJ
 +M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH'-%5*!54*!"252@
 +M5$%"3$4-#2!D<R!>(#MC3$5!4J!43Z!%3D2@3T:@4$%'10T@(" [<T^@5$A!
 +M5*!404),15.@4U1!4E2@3TZ@0:!004=%H$)/54Y$05)9#6)I=' @;'5P(#$V
 +M(#LQ,CB@94Y44DE%4Z!&3U*@> T@9&9B("4Q,3$Q,3$Q,0T@9&9B("4P,3$Q
 +M,3$Q,0T@9&9B("4P,#$Q,3$Q,0T@9&9B("4P,# Q,3$Q,0T@9&9B("4P,# P
 +M,3$Q,0T@9&9B("4P,# P,#$Q,0T@9&9B("4P,# P,# Q,0T@9&9B("4P,# P
 +M,# P,0T@+2U>#0US:6X@.W1!0DQ%H$]&H%-)3D53+* Q,C"@0EE415,-8V]S
 +M(&5Q=2!S:6XK,3(X(#MT04),1:!/1J!#3U-)3D53#2 @(#MB3U1(H$]&H%1(
 +M15-%H%1224>@5$%"3$53H$%210T@(" [0U524D5.5$Q9H%-%5*!54*!&4D]-
 +MH&)A<VEC#7ID:78@97%U(&-O<RLQ,C@@.V1)5DE324].H%1!0DQ%#71M871H
 +M,2!E<74@>F1I=BLS.#0@.VU!5$B@5$%"3$6@3T:@1BA8*3U8*E@O,C4V#71M
 +M871H,B!E<74@=&UA=&@Q*S4Q,B [<T5#3TY$H$U!5$B@5$%"3$4-<&]L>6QI
 +M<W0@97%U('1M871H,BLU,3(@.VQ)4U2@3T:@4$],64=/3E,-&AH:&AH:&AH:
 +8&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 + 
 +end
 +
 +begin 666 cube3d3.2.o
 +M ("I (T@T(TAT*T8T"D/"1"-&-"@ *D?A?NI@(7\3+R!DP41$1$@(" @(" @
 +M(" @(" @0U5"13-$(%8S+C(-#2 @(" @(" @(" @(" @(" @($)9#9\@(" @
 +M4U1%4$A%3B!*541$F2 @("!'14]21T4@5$%93$]2#0V;("!#2$5#2R!/550@
 +M5$A%($I!3BX@.34@25-3544@3T8-EB @0SU(04-+24Y'FR!&3U(@34]212!$
 +M151!24Q3(0T-'1V>$D8Q+T8RDB M($E.0R]$14,@6"U23U1!5$E/3@T='1)&
 +M,R]&-)(@+2!)3D,O1$5#(%DM4D]4051)3TX-'1T21C4O1C:2("T@24Y#+T1%
 +M0R!:+5)/5$%424].#1T=$B!&-R @DB M(%)%4T54#1T=$B K+RT@DB M(%I/
 +M3TT@24XO3U54#1T=$B @2" @DB M(%1/1T=,12!(241$14X@4U521D%#15,-
 +M'1T24U!!0T62("T@5$]'1TQ%(%-54D9!0T4@1DE,3$E.1PT-("!04D534R!1
 +M(%1/(%%5250-#04@(" @("!04D534R!!3ED@2T59(%1/($)%1TE.#0"Q^_ +
 +M(-+_R-#VYOQ,O($@Y/_) /#YJ9*%(X4EJ92%)X4IJ0&-(="IDR#2_ZD C2'0
 +MJ4!I#(7[J06%_*D H "B !B1^\AI$)#Y&*7[:2B%^Z7\:0"%_*  Z(K@$-#D
 +MJ0"%HZDPA:2@ *(8J0"1H\C0^^:DRM#VJ0"%HZDPA:2% JT8T"GQ"0Z-&-"I
 +M (57A5B%685@A3^%085 A4*%885BA6.%9(5EA6:%4*D!A;6I0(5Q6"#D_\F%
 +MT NE8<D\\%'F84P-@\F)T FE8?!$QF%,#8/)AM +I6+)// UYF),#8/)BM )
 +MI6+P*,9B3 V#R8?0"Z5CR3SP&>9C3 V#R8O0":5C\ S&8TP-@\F(T 9,1H),
 +M#8/)*] 'YG'F<4P-@\DMT W&<<9Q$"CF<>9Q3 V#R4C0":6U20&%M4P-@\D@
 +MT FE4$D!A5!,#8/)4= #3)F.>!BE9&5AR7B0 NEXA608I65E8LEXD +I>(5E
 +M&*5F96/)>) "Z7B%9CBE9>5FL )I>(5G&*5E96;)>) "Z7B%:!BE9&5FR7B0
 +M NEXA6DXI63E9K ":7B%:ABE9&5HR7B0 NEXA6LXI63E9[ ":7B%;!BE9&5G
 +MR7B0 NEXA6TXI6CE9+ ":7B%;CBE9>5DL )I>(5O&*5D967)>) "Z7B%<!BF
 +M9[T D*9H?0"0A:6F9[V CSBF:/V CX6FIF6]@(\0#AA)_VD!"AA)_VD!3-V#
 +M"H6G.*9NO0"0IFW] ) XIFO] ) 8IFQ] ) 0#AA)_VD!2AA)_VD!3 >$2ABF
 +M:7V CSBF:OV CX6H.*9KO8"/IFS]@(\XIFW]@(\XIF[]@(\0#AA)_VD!2AA)
 +M_VD!3#V$2ABF:7T D!BF:GT D(6IIF^]@(\XIG#]@(^%JJ9LO8"/.*9N_8"/
 +M.*9M_8"/.*9K_8"/$ X82?]I 4H82?]I 4R A$H8IFI] ) XIFG] )"%JQBF
 +M;+T D*9M?0"0.*9K_0"0.*9N_0"0$ X82?]I 4H82?]I 4RVA$H8IFE]@(\8
 +MIFI]@(^%K!BF;[T D*9P?0"0A:VI (6CI0*%I*((J0"@ )&CR-#[YJ3*T/2@
 +M (11I%&Y );0 TR*A852YE$@Q86E5TI*2H57Q3^P H4_I5G%0; "A4&E6$I*
 +M2H58Q4"0 H5 I6#%0I "A4*E4/#!J0"%HZ4"A:2I (7[J4"%_*572I 'H("$
 +MHX3[&(5H9:2%I*5H9?R%_*58..57JNBD8- "YF"D8*D 4?M(4:.1HZD D?MH
 +MB,19L.^EHTF A:.%^] $YJ3F_,K0VDSMA*T8T$D"C1C0J0A% H4"3&Z"9T5%
 +M(&)204E.+"!72$%4($1/(%E/52!704Y4(%1/($1/(%1/3DE'2%0_J0"%8(58
 +MJ?^%6857I%&Y ):%DLBY ):%D\BY ):%E,C&4KD EH65R+D EH66R+D EH6N
 +MR,92N0"6A:_(N0"6A;#(N0"6A;'(A%&DDJ6EA2882?]I 84HL28X\2B%LJ6H
 +MA2882?]I 84HL28X\2B%LZ6KA2882?]I 84HL28X\2B%M*23I::%)AA)_VD!
 +MA2BQ)CCQ*!AELH6RI:F%)AA)_VD!A2BQ)CCQ*!AELX6SI:R%)AA)_VD!A2BQ
 +M)CCQ*!AEM(6TI)2EIX4F&$G_:0&%*+$F./$H&&6RA;*EJH4F&$G_:0&%*+$F
 +M./$H&&6SA;.EK84F&$G_:0&%*+$F./$H&&6TA92JO("0I;*%(AA)_VD!A22Q
 +M(CCQ)*9QX$#P%(3[I'&%)AA)_VD!A2BQ)CCQ**3[&&E A9+%5[ "A5?%6) "
 +MA5BELX4B&$G_:0&%)+$B./$DX$#P$*1QA2882?]I 84HL28X\2@8:4"%D\59
 +ML *%6<5@D *%8*25I:6%)AA)_VD!A2BQ)CCQ*(6RI:B%)AA)_VD!A2BQ)CCQ
 +M*(6SI:N%)AA)_VD!A2BQ)CCQ*(6TI):EIH4F&$G_:0&%*+$F./$H&&6RA;*E
 +MJ84F&$G_:0&%*+$F./$H&&6SA;.EK(4F&$G_:0&%*+$F./$H&&6TA;2DKJ6G
 +MA2882?]I 84HL28X\2@89;*%LJ6JA2882?]I 84HL28X\2@89;.%LZ6MA288
 +M2?]I 84HL28X\2@89;2%KJJ\@)"ELH4B&$G_:0&%)+$B./$DIG'@0/ 4A/ND
 +M<84F&$G_:0&%*+$F./$HI/L8:4"%E<57L *%5\58D *%6*6SA2(82?]I 84D
 +ML2(X\23@0/ 0I'&%)AA)_VD!A2BQ)CCQ*!AI0(66Q5FP H59Q6"0 H5@I*^E
 +MI84F&$G_:0&%*+$F./$HA;*EJ(4F&$G_:0&%*+$F./$HA;.EJX4F&$G_:0&%
 +M*+$F./$HA;2DL*6FA2882?]I 84HL28X\2@89;*%LJ6IA2882?]I 84HL28X
 +M\2@89;.%LZ6LA2882?]I 84HL28X\2@89;2%M*2QI:>%)AA)_VD!A2BQ)CCQ
 +M*!AELH6RI:J%)AA)_VD!A2BQ)CCQ*!AELX6SI:V%)AA)_VD!A2BQ)CCQ*!AE
 +MM(6QJKR D*6RA2(82?]I 84DL2(X\22F<>! \!2$^Z1QA2882?]I 84HL28X
 +M\2BD^QAI0(6OQ5>P H57Q5B0 H58I;.%(AA)_VD!A22Q(CCQ).! \!"D<84F
 +M&$G_:0&%*+$F./$H&&E A;#%6; "A5G%8) "A6"EM?!'I94XY9*HI; XY9:%
 +M)AA)_VD!A2BQ)CCQ*(7[I:\XY96HI98XY9.%)AA)_VD!A2BQ)CCQ*,7[, _&
 +M4O *YE'F4>91QE+0]F"EDH7[I9.%_*65A?VEEH7^(-"+I96%^Z66A?REKX7]
 +MI;"%_B#0B\92T -,=XND4;D EH65R+D EH66R+D EH6NR(11I)6EI84F&$G_
 +M:0&%*+$F./$HA;*EJ(4F&$G_:0&%*+$F./$HA;.EJX4F&$G_:0&%*+$F./$H
 +MA;2DEJ6FA2882?]I 84HL28X\2@89;*%LJ6IA2882?]I 84HL28X\2@89;.%
 +MLZ6LA2882?]I 84HL28X\2@89;2%M*2NI:>%)AA)_VD!A2BQ)CCQ*!AELH6R
 +MI:J%)AA)_VD!A2BQ)CCQ*!AELX6SI:V%)AA)_VD!A2BQ)CCQ*!AEM(6NJKR 
 +MD*6RA2(82?]I 84DL2(X\22F<>! \!2$^Z1QA2882?]I 84HL28X\2BD^QAI
 +M0(65Q5>P H57Q5B0 H58I;.%(AA)_VD!A22Q(CCQ).! \!"D<84F&$G_:0&%
 +M*+$F./$H&&E A9;%6; "A5G%8) "A6"EE87[I9:%_*6OA?VEL(7^(-"+I96%
 +MKZ66A;#&4O #3!&*I9*%_:63A?ZEKX7[I;"%_"#0BV!S04U%(%1(24Y'(%=%
 +M($1/($5615)9($Y)1TA4+"!P24Y+63H@5%)9(%1/(%1!2T4@3U9%4B!42$4@
 +M5T]23$0A;D%21B&E4- +J0"%HZ4"A:1,YXNI (6CJ4"%I#BE_>7[L!.E_J3\
 +MA?R$_J7[I/V$^X7]..7[A6>F^XI*2DI*D 6@@(2C&&6DA:0XI?[E_+ $2?]I
 +M 85HQ6>0 TR-C:3\Q/Z] (^%_87^D -,XXRE4/!3IF>E9TI&_O 0Y6B0,<K0
 +M]:7]1?Y1HY&C8$BE_5&CD:.I_X7]A?ZI@$6CA:/0 N:D:.5HL -E9\C*T,I,
 +M38QE9TBE_47^4:.1HZ7^A?UHR,K0LV"F9Z5G2D;^\!#E:) QRM#UI?U%_A&C
 +MD:-@2*7]$:.1HZG_A?V%_JF 1:.%H] "YJ1HY6BP V5GR,K0RDR@C&5G2*7]
 +M1?X1HY&CI?Z%_6C(RM"S8*50\%.F9Z5G2D;^\!#E:) QRM#UI?U%_E&CD:-@
 +M2*7]4:.1HZG_A?V%_JF 1:.%H] "YJ1HY6BP V5GB,K0RDSWC&5G2*7]1?Y1
 +MHY&CI?Z%_6B(RM"S8*9GI6=*1O[P$.5HD#'*T/6E_47^$:.1HV!(I?T1HY&C
 +MJ?^%_87^J8!%HX6CT +FI&CE:+ #96>(RM#*3$J-96=(I?U%_A&CD:.E_H7]
 +M:(C*T+-@I/R] (^%_4I%_87]Q/ZP?J50\#JF:/ ,I6A*.,CE9Y $RM#X8&5H
 +M2*7]4:.1HVA&_3CP!LK0Y4RQC4BI@(7]1:.%H] "YJ1HRM#13+&-IFCP%*5H
 +M2CA(I?T1HY&C:,CE9Y *RM#PI?T1HY&C8&5H1OTX\ ;*T-],\XU(J8"%_46C
 +MA:/0 N:D:,K0RTSSC:50\#JF:/ ,I6A*.(CE9Y $RM#X8&5H2*7]4:.1HVA&
 +M_3CP!LK0Y4POCDBI@(7]1:.%H] "YJ1HRM#13"^.IFCP%*5H2CA(I?T1HY&C
 +M:(CE9Y *RM#PI?T1HY&C8&5H1OTX\ ;*T-],<8Y(J8"%_46CA:/0 N:D:,K0
 +MRTQQCJT8T"GUC1C08%-024Y!3"!#4D%#2T52(%-,2B V+SDU            
 +M                                                            
 +M                      #_?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?
 +M#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_
 +M?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?#P<# ?]_/Q\/!P,!_W\_'P\'
 +M P'_?S\?#P<# 1H:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +!&AH:
 + 
 +end
 +
 +begin 666 shape3.2
 +M 1PA'   CR!#54)%,T0@4TA!4$4@24Y)5"!04D]'4D%- $<<!0"7-3$L,C4U
 +M.I<U,BPQ,C<ZES4U+#(U-3J7-38L,3(W.IP ;AQB (\@34%)3B!04D]'4D%-
 +M(%-405)44R!!5"!,24Y%(#4P,#  =!QC #H B!QD (\@4%)/1U)!32!.3U1%
 +M4P":'&X CR!33$H@-B\Q-B\Y-0"P''@ CR!$051!($9/4DU!5"!)4SH UAQ]
 +M (\@3E5-4$])3E13+%@Q+%DQ+%HQ+%@R+%DR+%HR+"XN+@#W'(( CR!42$4@
 +M4$],64=/3B!,25-4("I-55-4*B!"10 5'8< CR!415)-24Y!5$5$(%=)5$@@
 +M02!:15)/(0 ;'8D .@ ^'8P CR!/1B!#3U524T4L(%1(15)%($E3($$@3$]4
 +M($]& %X=D0"/($154$Q)0T%424].($1205=)3D<@3$E.15, @QV3 (\@5TA%
 +M3B!&04-%4R!!4D5.)U0@0D5)3D<@1DE,3$5$ *8=E@"/($$@1D%35"!04D]'
 +M4D%-(%=/54Q$3B=4($1205< RAV@ (\@5$A%(%-!344@3$E.12!45TE#12P@
 +M0E54(%1(25, Z!VE (\@25,@1T]/1"!%3D]51T@@1D]2($Y/5R$ "AZG (\@
 +M3T8@0T]54E-%+"!&3U(@1DE,3$5$($9!0T53 "L>J "/(%1(25,@25,@3D\@
 +M3$].1T52($%.($E34U5% #$>J@ Z %8>M "/($9%14P@1E)%12!43R!0550@
 +M64]54B!/5TX@1$%400!\'KD CR!)3BP@0E54($-(04Y'12!42$4@4$]+12!)
 +M3B!,24Y% )@>O@"/(#4P,#4@5$\@55-%($,Q($%.1"!#,@">'L@ .@##'M( 
 +MCR!33U)262!!0D]55"!33TU%($]&(%1(12!,25143$4 WA[7 (\@0E5'4RP@
 +M15(N+BX@1D5!5%5215, _A[< (\@5$A)4R!705,@02!214%,(%)54T@@2D]"
 +M+@ $'^8 .@ G' CR!!3D0@248@64]5($9%14P@4T\@24Y#3$E.140L $0?
 +M^@"/(%=2251%(%1/(%-*541$0$Y752Y%1%4 2A\$ 3H :1\. 8\@04Y$($%"
 +M3U9%($%,3"P@2$%612!&54XA &\?& $Z ($?Y@./($9)4E-4(%-(05!% )<?
 +MYP-!,;+"*#8Q*3I!,K+"*#8R*0#''^@#@R T+"TR-BPM,C8L-C0L,C8L+3(V
 +M+#8T+#(V+#8T+#8T+"TR-BPV-"PV- #S'_(#@R T+"TR-BPM,C8L,"PR-BPM
 +M,C8L,"PR-BPV-"PP+"TR-BPV-"PP !<@_ .#(#,L,"PM,C8L,S(L,38L,S(L
 +M,S(L+3$V+#,R+#,R !\@!@2#(#  ,B!*!(\@4T5#3TY$(%-(05!% $@@2P1"
 +M,;+"*#8Q*3I",K+"*#8R*0!\($P$@R T+"TQ-2PS,"PM,3(L+3$U+"TS,"PM
 +M,3(L+34W+"TS,"PM,RPM-3$L+38L+3, K"!6!(,@-"PQ-2PS,"PM,3(L-3$L
 +M+38L+3,L-3<L+3,P+"TS+#$U+"TS,"PM,3( X"!@!(,@-"PQ-2PS,"PM,3(L
 +M,34L+3,P+"TQ,BPM,34L+3,P+"TQ,BPM,34L,S L+3$R "0A:@2#(#<L,34L
 +M+3,P+"TQ,BPU-RPM,S L+3,L,S,L+3,P+#8L,"PM,S L,3(L+3,S+"TS,"PV
 +M+"TU-RPM,S L+3, -B%L!(,@+3$U+"TS,"PM,3( 6R%T!(,@,RPS,RPM,S L
 +M,"PS,RPM,S L+30L,SDL+3,P+"TR (,A?@2#(#,L+3,S+"TS,"PP+"TS.2PM
 +M,S L+3(L+3,S+"TS,"PM- "O(8@$@R T+#8L+3,P+#,L-BPM,S L+38L,3@L
 +M+3,P+"TT+#$X+"TS,"PQ -\AD@2#(#0L+38L+3,P+#,L+3$X+"TS,"PQ+"TQ
 +M."PM,S L+30L+38L+3,P+"TV  <BG 2#(#,L+34W+"TS,"PM,RPM,S,L+3,P
 +M+#8L+34Q+"TQ,BPM,P O(J8$@R S+"TS,RPM,S L-BPM,34L,S L+3$R+"TU
 +M,2PM,3(L+3, 4R*P!(,@,RPM,34L,S L+3$R+"TS,RPM,S L-BPP+#8L,3( 
 +M=2*Z!(,@,RPP+#8L,3(L+3,S+"TS,"PV+# L+3,P+#$R )DBQ 2#(#,L,34L
 +M,S L+3$R+"TQ-2PS,"PM,3(L,"PV+#$R +HBS@2#(#,L,"PM,S L,3(L,S,L
 +M+3,P+#8L,"PV+#$R -PBV 2#(#,L,"PV+#$R+#,S+"TS,"PV+#$U+#,P+"TQ
 +M,@ !(^($@R S+#$U+#,P+"TQ,BPS,RPM,S L-BPU,2PM,3(L+3, )B/L!(,@
 +M,RPS,RPM,S L-BPU-RPM,S L+3,L-3$L+3$R+"TS "XC]@2#(#  0",2!8\@
 +M5$A)4D0@4TA!4$4 5B,3!4,QLL(H-C$I.D,RLL(H-C(I ' C% 6/(%E/55(@
 +M1$%402!'3T53($A%4D4 >"/0!X,@, ",(X@30E"R,C4VK"@YK#$VJC8I + C
 +MC!./($-(04Y'12!"14Q/5R!43R!03TE.5"!43R!$051! ,(CC1.7-C4L0C$Z
 +MES8V+$(R .$CDA.'3CJ70E L3CI"4+)"4*HQ.HM.LC"G-3 U, #I(Y<3F2!.
 +M  8DG!.!2;(QI#.L3CJ'4#J+4+,PIU"R,C4VJE  &"2F$Y="4"Q0.D)0LD)0
 +MJC$ (B2K$YDB+B([ "XDL!.".HDU,#$P #\DNA.>,S(W-C@ZG#J,.H     :
 +M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +0&AH:&AH:&AH:&AH:&AH:&AH:
 + 
 +end
 +
 +begin 666 tables$8f80-$95ff
 +M@(\  @,%!P@*"PT/$!$3%!47&!D:&QP='1X>'Q\@(" @(" @'Q\>'AT='!L:
 +M&1@7%103$1 /#0L*" <% P( _OW[^?CV]?/Q\._M[.OIZ.?FY>3CX^+BX>'@
 +MX.#@X.#@X>'BXN/CY.7FY^CIZ^SM[_#Q\_7V^/G[_?X _P    #_\" @(" ?
 +M'QX>'1T<&QH9&!<5%!,1$ \-"PH(!P4# @#^_?OY^/;U\_'P[^WLZ^GHY^;E
 +MY./CXN+AX>#@X.#@X.#AX>+BX^/DY>;GZ.GK[.WO\/'S]?;X^?O]_@ " P4'
 +M" H+#0\0$1,4%1<8&1H;'!T='AX?'R @("  _____P  (B(B(B(C(R,C(R,C
 +M(R,D)"0D)"0D)"4E)24E)24E)B8F)B8F)B8G)R<G)R<G*"@H*"@H*"DI*2DI
 +M*2HJ*BHJ*BLK*RLK*RPL+"PL+"TM+2TM+BXN+BXN+R\O+S P,# P,3$Q,3$R
 +M,C(R,S,S,S0T-#0U-34U-C8V-C<W-S@X.#@8&!@8&1D9&1D9&1D9&1D9&1D9
 +M&1D9&AH:&AH:&AH:&AH:&AH:&AL;&QL;&QL;&QL;&QL;&QP<'!P<'!P<'!P<
 +M'!P<'1T='1T='1T='1T='1X>'AX>'AX>'AX>'A\?'Q\?'Q\?'Q\?(" @(" @
 +M(" @(" A(2$A(2$A(2$A(B(B(O__     /____\ \   _P#__P    #_____
 +M     /____\     _____P    #_ /__     /_P__\     _____P    #_
 +M____     /____\     _____P    #_____     /____\     _____P  
 +M  #_____     /_P                 0$! 0$! 0$" @(" @(# P,#! 0$
 +M! 4%!04&!@8'!P<(" @)"0D*"@L+"PP,#0T.#@\/$! 1$1(2$Q,4%!45%A<7
 +M&!@9&AH;'!P='AX?(" A(B,C)"4F)B<H*2DJ*RPM+BXO,#$R,S0U-38W.#DZ
 +M.SP]/C] 04)#1$5&1TA)2DM-3D]045)35%976"LJ*2DH)R8F)20C(R(A(" ?
 +M'AX='!P;&AH9&!@7%Q85%104$Q,2$A$1$! /#PX.#0T,# L+"PH*"0D)" @(
 +M!P<'!@8&!04%!00$! 0# P,# @(" @(" 0$! 0$! 0$                 
 +M              $! 0$! 0$! @(" @(" P,# P0$! 0%!04%!@8&!P<'" @(
 +M"0D)"@H+"PL,# T-#@X/#Q 0$1$2$A,3%!05%187%Q@8&1H:&QP<'1X>'R @
 +M(2(C(R0E)B8G*"DI*BLL+2XN+S Q,C,T-34V-S@Y.CL\/3X_0$%"0T1%1D=(
 +M24I+34Y/4%%24U165U@K*BDI*"<F)B4D(R,B(2 @'QX>'1P<&QH:&1@8%Q<6
 +M%144%!,3$A(1$1 0#P\.#@T-# P+"PL*"@D)"0@(" <'!P8&!@4%!04$! 0$
 +M P,# P(" @(" @$! 0$! 0$!                               ! 0$!
 +M 0$! 0(" @(" @,# P,$! 0$!04%!08&!@<'!P@(" D)"0H*"PL+# P-#0X.
 +M#P\0$!$1$A(3$Q04%146%Q<8&!D:&AL<'!T>'A\@("$B(R,D)28F)R@I*2HK
 +M+"TN+B\P,3(S-#4U-C<X.3H[/#T^/T _/CT\.SHY.#<V-34T,S(Q,"\N+BTL
 +M*RHI*2@G)B8E)",C(B$@(!\>'AT<'!L:&AD8&!<7%A45%!03$Q(2$1$0$ \/
 +M#@X-#0P,"PL+"@H)"0D(" @'!P<&!@8%!04%! 0$! ,# P," @(" @(! 0$!
 +M 0$! 0                               0$! 0$! 0$" @(" @(# P,#
 +M! 0$! 4%!04&!@8'!P<(" @)"0D*"@L+"PP,#0T.#@\/$! 1$1(2$Q,4%!45
 +M%A<7&!@9&AH;'!P='AX?(" A(B,C)"4F)B<H*2DJ*RPM+BXO,#$R,S0U-38W
 +M.#DZ.SP]/C] /SX]/#LZ.3@W-C4U-#,R,3 O+BXM+"LJ*2DH)R8F)20C(R(A
 +M(" ?'AX='!P;&AH9&!@7%Q85%104$Q,2$A$1$! /#PX.#0T,# L+"PH*"0D)
 +M" @(!P<'!@8&!04%!00$! 0# P,# @(" @(" 0$! 0$! 0$             
 +M !H:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 +E&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
 + 
 +end
 +
 +
 +********************************
 +*``````````````````````````````*
 +*`Stephen`Judd`````````````````*
 +*`George`Taylor````````````````*
 +*`Started:`7/11/94`````````````*
 +*`Finished:`7/19/94````````````*
 +*`v2.0`Completed:`12/17/94`````*
 +*`v3.0`Completed:`3/20/95``````*
 +*`v3.1`Completed:`6/14/95``````*
 +*`v3.2`Completed:`6/15/95``````*
 +*``````````````````````````````*
 +*`Well,`if`all`goes`well`this``*
 +*`program`will`rotate`a`cube.``*
 +*``````````````````````````````*
 +*`v2.0`+`New`and`Improved!`````*
 +*`Now`with`faster`routines,````*
 +*`hidden`surfaces,`filled``````*
 +*`faces,`and`extra`top`secret``*
 +*`text`messages!```````````````*
 +*``````````````````````````````*
 +*`v3.0`+`Fast`chunky`line``````*
 +*`routine.`````````````````````*
 +*``````````````````````````````*
 +*`v3.1`+`General`polygon`plot``*
 +*`with`hidden`faces`(X-product)*
 +*`and`zoom`feature.````````````*
 +*``````````````````````````````*
 +*`v3.2`+`EOR-buffer`filling````*
 +*``````````````````````````````*
 +*`This`program`is`intended`to``*
 +*`accompany`the`article`in`````*
 +*`C=Hacking,`Jun.`95`issue.````*
 +*`For`details`on`this`program,`*
 +*`read`the`article!````````````*
 +*``````````````````````````````*
 +*`Write`to`us!`````````````````*
 +*``````````````````````````````*
 +*`Myself`when`young`did````````*
 +*`eagerly`frequent`````````````*
 +*`Doctor`and`Saint,`and`heard``*
 +*`great`Argument```````````````*
 +*``About`it`and`about:`but`````*
 +*``evermore````````````````````*
 +*`Came`out`by`the`same`Door````*
 +*`as`in`I`went.````````````````*
 +*````-`Rubaiyat````````````````*
 +*``````````````````````````````*
 +*`Though`I`speak`with`the``````*
 +*`tongues`of`men`and`of`angles`*
 +*`and`have`not`love,`I`am``````*
 +*`become`as`sounding`brass,`or`*
 +*`a`tinkling`cymbal.```````````*
 +*````-`1`Corinthians`13````````*
 +*``````````````````````````````*
 +*`P.S.`This`was`written`using``*
 +*``````Merlin`128.`````````````*
 +********************************
 +
 + ORG $8000
 +
 +*`Constants
 +
 +BUFF1 EQU $3000 ;First`character`set
 +BUFF2 EQU $3800 ;Second`character`set
 +EORBUF EQU $4000 ;EOR-buffer
 +BUFFER EQU $A3 ;Presumably`the`tape`won't`be`running
 +X1 EQU $FB ;Points`for`drawing`a`line
 +Y1 EQU $FC ;These`zero`page`addresses
 +X2 EQU $FD ;don't`conflict`with`BASIC
 +Y2 EQU $FE
 +OLDX EQU $FD
 +CHUNK EQU $FE
 +DX EQU $67 ;This`is`shared`with`T1`below
 +DY EQU $68
 +TEMP1 EQU $FB ;Of`course,`could`conflict`with`x1
 +TEMP2 EQU $FC ;Temporary`variables
 +ZTEMP EQU $02 ;Used`for`buffer`swap.``Don't`touch.
 +Z1 EQU $22 ;Used`by`math`routine
 +Z2 EQU $24 ;Don't`touch`these`either!
 +Z3 EQU $26
 +Z4 EQU $28
 +K EQU $B6 ;Constant`used`for`hidden
 +   ;surface`detection`-`don't`touch
 +HIDE EQU $B5 ;Are`surfaces`hidden?
 +FILL EQU $50 ;Are`we`using`EOR-fill?
 +ANGMAX EQU 120 ;There`are`2*pi/angmax`angles
 +
 +*`VIC
 +
 +VMCSB EQU $D018
 +BKGND EQU $D020
 +BORDER EQU $D021
 +SSTART EQU 1344 ;row`9`in`screen`memory`at`1024
 +
 +
 +*`Kernal
 +
 +CHROUT EQU $FFD2
 +GETIN EQU $FFE4
 +
 +*`Some`variables
 +
 +GLOBXMIN = $3F ;These`are`used`in`clearing`the
 +GLOBXMAX = $40 ;drawing`(global)`buffer
 +GLOBYMIN = $41
 +GLOBYMAX = $42
 +LOCXMIN = $57 ;These`are`used`in`clearing`the
 +LOCXMAX = $58 ;EOR`(local)`buffer
 +LOCYMIN = $59
 +LOCYMAX = $60
 +P1X = $92 ;These`are`temporary`storage
 +P1Y = $93 ;Used`in`plotting`the`projection
 +P1Z = $94
 +P2X = $95 ;They`are`here`so`that`we
 +P2Y = $96 ;don't`have`to`recalculate`them.
 +P2Z = $AE
 +P3X = $AF ;They`make`life`easy.
 +P3Y = $B0
 +P3Z = $B1 ;Why`are`you`looking`at`me`like`that?
 +P1T = $B2 ;Don't`you`trust`me?
 +P2T = $B3
 +P3T = $B4 ;Having`another`child`wasn't`my`idea.
 +INDEX = $51
 +COUNTPTS = $52
 +ZOOM = $71 ;Zoom`factor
 +DSX = $61 ;DSX`is`the`increment`for
 +   ;rotating`around`x
 +DSY = $62 ;Similar`for`DSY,`DSZ
 +DSZ = $63
 +SX = $64 ;These`are`the`actual`angles`in`x`y`and`z
 +SY = $65
 +SZ = $66
 +T1 = $67 ;These`are`used`in`the`rotation
 +T2 = $68
 +T3 = $69 ;See`the`article`for`more`details
 +T4 = $6A
 +T5 = $6B
 +T6 = $6C
 +T7 = $6D
 +T8 = $6E
 +T9 = $6F
 +T10 = $70
 +A11 = $A5 ;These`are`the`elements`of`the`rotation`matrix
 +B12 = $A6 ;XYZ
 +C13 = $A7
 +D21 = $A8 ;The`number`denotes`(row,column)
 +E22 = $A9
 +F23 = $AA
 +G31 = $AB
 +H32 = $AC
 +I33 = $AD
 +
 +
 +***`Macros
 +
 +MOVE MAC
 + LDA ]1
 + STA ]2
 + <<<
 +
 +GETKEY MAC  ;Wait`for`a`keypress
 +WAIT JSR GETIN
 + CMP #00
 + BEQ WAIT
 + <<<
 +
 +DEBUG MAC  ;Print`a`character
 + DO`0``;Don't`assemble
 +
 + LDA`#]1
 + JSR`CHROUT
 + CLI
 + >>> GETKEY ;And`wait`to`continue
 + CMP #'s' ;My`secrect`switch`key
 + BNE L1
 + JSR CLEANUP
 + JMP DONE
 +L1 CMP #'x' ;My`secret`abort`key
 + BNE DONE
 + JMP CLEANUP
 + FIN
 +DONE <<<
 +
 +DEBUGA MAC
 + DO`0
 + LDA ]1
 + STA 1024
 + FIN
 +DONEA <<<
 +
 +*-------------------------------
 +
 + LDA #$00
 + STA BKGND
 + STA BORDER
 + LDA VMCSB
 + AND #%00001111 ;Screen`memory`to`1024
 + ORA #%00010000
 + STA VMCSB
 +
 + LDY #00
 + LDA #<TTEXT
 + STA TEMP1
 + LDA #>TTEXT
 + STA TEMP2
 + JMP TITLE
 +TTEXT HEX 9305111111 ;clear`screen,`white,`crsr`dn
 + TXT '`````````````cube3d`v3.2',0d,0d
 + TXT '``````````````````by',0d
 + HEX 9F ;cyan
 + TXT '````stephen`judd'
 + HEX 99
 + TXT '````george`taylor',0d,0d
 + HEX 9B
 + TXT '``check`out`the`jan.`95`issue`of',0d
 + HEX 96
 + TXT '``c=hacking'
 + HEX 9B
 + TXT '`for`more`details!',0d
 + HEX 0D1D1D9E12
 + TXT 'f1/f2',92
 + TXT '`-`inc/dec`x-rotation',0d
 + HEX 1D1D12
 + TXT 'f3/f4',92
 + TXT '`-`inc/dec`y-rotation',0d
 + HEX 1D1D12
 + TXT 'f5/f6',92
 + TXT '`-`inc/dec`z-rotation',0d
 + HEX 1D1D12
 + TXT '`f7``',92
 + TXT '`-`reset',0d
 + HEX 1D1D12
 + TXT '`+/-`',92
 + TXT '`-`zoom`in/out',0d
 + HEX 1D1D12
 + TXT '``h``',92
 + TXT '`-`toggle`hidden`surfaces',0d
 + HEX 1D1D12
 + TXT 'space',92
 + TXT '`-`toggle`surface`filling',0d,0d
 + TXT '``press`q`to`quit',0d
 + HEX 0D05
 + TXT '``````press`any`key`to`begin',0d
 + HEX 00
 +TITLE LDA (TEMP1),Y
 + BEQ :CONT
 + JSR CHROUT
 + INY
 + BNE TITLE
 + INC TEMP2
 + JMP TITLE
 +:CONT >>> GETKEY
 +
 +****`Set`up`tables(?)
 +
 +*`Tables`are`currently`set`up`in`BASIC
 +*`and`by`the`assembler.
 +
 +TABLES LDA #>TMATH1
 + STA Z1+1
 + STA Z2+1
 + LDA #>TMATH2
 + STA Z3+1
 + STA Z4+1
 +
 +****`Clear`screen`and`set`up`"bitmap"
 +SETUP LDA #$01 ;White
 + STA $D021 ;This`is`done`so`that`older
 + LDA #147 ;machines`will`set`up
 + JSR CHROUT
 + LDA #$00 ;correctly
 + STA $D021
 + LDA #<SSTART
 + ADC #12 ;The`goal`is`to`center`the`graphics
 + STA TEMP1 ;Column`12
 + LDA #>SSTART ;Row`9
 + STA TEMP1+1 ;SSTART`points`to`row`9
 + LDA #00
 + LDY #00
 + LDX #00 ;x`will`count`16`rows`for`us
 + CLC
 +
 +:LOOP STA (TEMP1),Y
 + INY
 + ADC #16
 + BCC :LOOP
 + CLC
 + LDA TEMP1
 + ADC #40 ;Need`to`add`40`to`the`base`pointer
 + STA TEMP1 ;To`jump`to`the`next`row
 + LDA TEMP1+1
 + ADC #00 ;Take`care`of`carries
 + STA TEMP1+1
 + LDY #00
 + INX
 + TXA  ;X`is`also`an`index`into`the`character`number
 + CPX #16
 + BNE :LOOP ;Need`to`do`it`16`times
 +
 +****`Clear`buffers
 +
 + LDA #<BUFF1
 + STA BUFFER
 + LDA #>BUFF1
 + STA BUFFER+1
 + LDY #$00
 + LDX #24 ;Assuming`all`three`buffers`are
 + LDA #$00 ;back-to-back
 +:BLOOP STA (BUFFER),Y
 + INY
 + BNE :BLOOP
 + INC BUFFER+1
 + DEX
 + BNE :BLOOP
 +
 +****`Set`up`buffers
 +
 + LDA #<BUFF1
 + STA BUFFER
 + LDA #>BUFF1
 + STA BUFFER+1
 + STA ZTEMP ;ztemp`will`make`life`simple`for`us
 + LDA VMCSB
 + AND #%11110001 ;Start`here`so`that`swap`buffers`will`work`right
 + ORA #%00001110
 + STA VMCSB
 +
 +****`Set`up`initial`values
 +
 +INIT LDA #00
 + STA LOCXMIN
 + STA LOCXMAX
 + STA LOCYMIN
 + STA LOCYMAX
 + STA GLOBXMIN
 + STA GLOBYMIN
 + STA GLOBXMAX
 + STA GLOBYMAX
 + STA DSX
 + STA DSY
 + STA DSZ
 + STA SX
 + STA SY
 + STA SZ
 + STA FILL
 + LDA #01
 + STA HIDE
 + LDA #64
 + STA ZOOM
 +
 +*-------------------------------
 +*`Main`loop
 +
 +****`Get`keypress
 +
 +MAIN
 + CLI
 +KPRESS JSR GETIN
 + CMP #133 ;F1?
 + BNE :F2
 + LDA DSX
 + CMP #ANGMAX/2 ;No`more`than`pi
 + BEQ :CONT1
 + INC DSX ;otherwise`increase`x-rotation
 + JMP :CONT
 +:F2 CMP #137 ;F2?
 + BNE :F3
 + LDA DSX
 + BEQ :CONT1
 + DEC DSX
 + JMP :CONT
 +:F3 CMP #134
 + BNE :F4
 + LDA DSY
 + CMP #ANGMAX/2
 + BEQ :CONT1
 + INC DSY ;Increase`y-rotation
 + JMP :CONT
 +:F4 CMP #138
 + BNE :F5
 + LDA DSY
 + BEQ :CONT1
 + DEC DSY
 + JMP :CONT
 +:F5 CMP #135
 + BNE :F6
 + LDA DSZ
 + CMP #ANGMAX/2
 + BEQ :CONT1
 + INC DSZ ;z-rotation
 + JMP :CONT
 +:F6 CMP #139
 + BNE :F7
 + LDA DSZ
 + BEQ :CONT1
 + DEC DSZ
 + JMP :CONT
 +:F7 CMP #136
 + BNE :PLUS
 + JMP INIT
 +:CONT1 JMP :CONT
 +:PLUS CMP #'+'
 + BNE :MINUS
 + INC ZOOM ;Bah,`who`needs`error`checking?
 + INC ZOOM
 + JMP :CONT
 +:MINUS CMP #'-'
 + BNE :H
 + DEC ZOOM
 + DEC ZOOM
 + BPL :CONT
 + INC ZOOM
 + INC ZOOM
 + JMP :CONT
 +:H CMP #'h'
 + BNE :SPACE
 + LDA HIDE
 + EOR #$01
 + STA HIDE
 + JMP :CONT
 +:SPACE CMP #'`'
 + BNE :Q
 + LDA FILL
 + EOR #$01
 + STA FILL
 + JMP :CONT
 +:Q CMP #'q' ;q`quits
 + BNE :CONT
 + JMP CLEANUP
 +
 +:CONT SEI  ;Speed`things`up`a`bit
 +
 +****`Update`angles
 +
 +UPDATE CLC
 + LDA SX
 + ADC DSX
 + CMP #ANGMAX ;Are`we`>=`maximum`angle?
 + BCC :CONT1
 + SBC #ANGMAX :If so, reset
 +:CONT1 STA SX
 + CLC
 + LDA SY
 + ADC DSY
 + CMP #ANGMAX
 + BCC :CONT2
 + SBC #ANGMAX ;Same`deal
 +:CONT2 STA SY
 + CLC
 + LDA SZ
 + ADC DSZ
 + CMP #ANGMAX
 + BCC :CONT3
 + SBC #ANGMAX
 +:CONT3 STA SZ
 +
 +****`Rotate`coordinates
 +
 +ROTATE
 +
 +***`First,`calculate`t1,t2,...,t10
 +
 +**`Two`macros`to`simplify`our`life
 +ADDA MAC  ;Add`two`angles`together
 + CLC
 + LDA ]1
 + ADC ]2
 + CMP #ANGMAX ;Is`the`sum`>`2*pi?
 + BCC DONE
 + SBC #ANGMAX ;If`so,`subtract`2*pi
 +DONE <<<
 +
 +SUBA MAC  ;Subtract`two`angles
 + SEC
 + LDA ]1
 + SBC ]2
 + BCS DONE
 + ADC #ANGMAX ;Oops,`we`need`to`add`2*pi
 +DONE <<<
 +
 +**`Now`calculate`t1,t2,etc.
 +
 + >>> SUBA,SY;SZ
 + STA T1 ;t1=sy-sz
 + >>> ADDA,SY;SZ
 + STA T2 ;t2=sy+sz
 + >>> ADDA,SX;SZ
 + STA T3 ;t3=sx+sz
 + >>> SUBA,SX;SZ
 + STA T4 ;t4=sx-sz
 + >>> ADDA,SX;T2
 + STA T5 ;t5=sx+t2
 + >>> SUBA,SX;T1
 + STA T6 ;t6=sx-t1
 + >>> ADDA,SX;T1
 + STA T7 ;t7=sx+t1
 + >>> SUBA,T2;SX
 + STA T8 ;t8=t2-sx
 + >>> SUBA,SY;SX
 + STA T9 ;t9=sy-sx
 + >>> ADDA,SX;SY
 + STA T10 ;t10=sx+sy
 +
 +*`Et`voila!
 +
 +***`Next,`calculate`A,B,C,...,I
 +
 +**`Another`useful`little`macro
 +DIV2 MAC  ;Divide`a`signed`number`by`2
 +;It`is`assumed`that`the`number
 + BPL POS ;is`in`the`accumulator
 + CLC
 + EOR #$FF ;We`need`to`un-negative`the`number
 + ADC #01 ;by`taking`it's`complement
 + LSR  ;divide`by`two
 + CLC
 + EOR #$FF
 + ADC #01 ;Make`it`negative`again
 + JMP DONEDIV
 +POS LSR  ;Number`is`positive
 +DONEDIV <<<
 +
 +MUL2 MAC  ;Multiply`a`signed`number`by`2
 + BPL POSM
 + CLC
 + EOR #$FF
 + ADC #$01
 + ASL
 + CLC
 + EOR #$FF
 + ADC #$01
 + JMP DONEMUL
 +POSM ASL
 +DONEMUL <<<
 +
 +**`Note`that`we`are`currently`making`a`minor`leap
 +**`of`faith`that`no`overflows`will`occur.
 +
 +:CALCA CLC
 + LDX T1
 + LDA COS,X
 + LDX T2
 + ADC COS,X
 + STA A11 ;A=(cos(t1)+cos(t2))/2
 +:CALCB LDX T1
 + LDA SIN,X
 + SEC
 + LDX T2
 + SBC SIN,X
 + STA B12 ;B=(sin(t1)-sin(t2))/2
 +:CALCC LDX SY
 + LDA SIN,X
 + >>> MUL2
 + STA C13 ;C=sin(sy)
 +:CALCD SEC
 + LDX T8
 + LDA COS,X
 + LDX T7
 + SBC COS,X
 + SEC
 + LDX T5
 + SBC COS,X
 + CLC
 + LDX T6
 + ADC COS,X ;Di=(cos(t8)-cos(t7)+cos(t6)-cos(t5))/2
 + >>> DIV2
 + CLC
 + LDX T3
 + ADC SIN,X
 + SEC
 + LDX T4
 + SBC SIN,X
 + STA D21 ;D=(sin(t3)-sin(t4)+Di)/2
 +:CALCE SEC
 + LDX T5
 + LDA SIN,X
 + LDX T6
 + SBC SIN,X
 + SEC
 + LDX T7
 + SBC SIN,X
 + SEC
 + LDX T8
 + SBC SIN,X ;Ei=(sin(t5)-sin(t6)-sin(t7)-sin(t8))/2
 + >>> DIV2
 + CLC
 + LDX T3
 + ADC COS,X
 + CLC
 + LDX T4
 + ADC COS,X
 + STA E22 ;E=(cos(t3)+cos(t4)+Ei)/2
 +:CALCF LDX T9
 + LDA SIN,X
 + SEC
 + LDX T10
 + SBC SIN,X
 + STA F23 ;F=(sin(t9)-sin(t10))/2
 +:CALCG LDX T6
 + LDA SIN,X
 + SEC
 + LDX T8
 + SBC SIN,X
 + SEC
 + LDX T7
 + SBC SIN,X
 + SEC
 + LDX T5
 + SBC SIN,X ;Gi=(sin(t6)-sin(t8)-sin(t7)-sin(t5))/2
 + >>> DIV2
 +
 + CLC
 + LDX T4
 + ADC COS,X
 + SEC
 + LDX T3
 + SBC COS,X
 + STA G31 ;G=(cos(t4)-cos(t3)+Gi)/2
 +:CALCH CLC
 + LDX T6
 + LDA COS,X
 + LDX T7
 + ADC COS,X
 + SEC
 + LDX T5
 + SBC COS,X
 + SEC
 + LDX T8
 + SBC COS,X ;Hi=(cos(t6)+cos(t7)-cos(t5)-cos(t8))/2
 + >>> DIV2
 + CLC
 + LDX T3
 + ADC SIN,X
 + CLC
 + LDX T4
 + ADC SIN,X
 + STA H32 ;H=(sin(t3)+sin(t4)+Hi)/2
 +:WHEW CLC
 + LDX T9
 + LDA COS,X
 + LDX T10
 + ADC COS,X
 + STA I33 ;I=(cos(t9)+cos(t10))/2
 +
 +**`It's`all`downhill`from`here.
 +
 +DOWNHILL
 +****`Clear`buffer
 +*`A`little`macro
 +
 +SETBUF MAC  ;Put`buffers`where`they`can`be`hurt
 + LDA #00
 + STA BUFFER
 + LDA ZTEMP ;High`byte
 +STABUF STA BUFFER+1
 + <<<
 +
 + >>> SETBUF
 +CLRDRAW LDX #08
 + LDA #00
 +:FOOL LDY #00
 +:DOPE STA (BUFFER),Y
 + INY
 + BNE :DOPE
 + INC BUFFER+1
 + DEX
 + BNE :FOOL
 +
 +****`My`goodness`but`I'm`a`dope
 +*CLRDRAW`LDA`GLOBXMIN
 +*`LSR``;Need`to`get`into`the`right`column
 +*`BCC`:EVEN`;Explained`in`more`detail`below
 +*`LDY`#$80
 +*`STY`BUFFER`;Presumably`this`will`be`a`little
 +*`CLC``;more`efficient.
 +*:EVEN`ADC`BUFFER+1
 +*`STA`BUFFER+1
 +*`LDA`GLOBXMAX
 +*`SEC
 +*`SBC`GLOBXMIN
 +*`TAX
 +*`INX
 +*`LDY`GLOBYMAX
 +*`BEQ`:RESET
 +*:YAY`LDA`#$00
 +*`LDY`GLOBYMAX
 +*:BLAH`STA`(BUFFER),Y
 +*`DEY
 +*`CPY`GLOBYMIN
 +*`BCS`:BLAH
 +*`LDA`BUFFER
 +*`EOR`#$80
 +*`STA`BUFFER
 +*`BNE`:WHOPEE
 +*`INC`BUFFER+1
 +*:WHOPEE`DEX
 +*`BNE`:YAY
 +*:RESET`LDA`#0`;Need`to`reset`these`guys
 +*`STA`GLOBXMAX
 +*`STA`GLOBYMAX
 +*`LDA`#$FF
 +*`STA`GLOBXMIN
 +*`STA`GLOBYMIN
 +
 +****`Next,`read`and`draw`polygons
 +
 +READDRAW LDY #00
 + STY INDEX
 +OBJLOOP LDY INDEX
 + LDA POLYLIST,Y ;First,`the`number`of`points
 + BNE :CONT ;But`if`numpoints`is`zero`then
 + JMP OBJDONE ;we`are`at`the`end`of`the`list
 +:CONT STA COUNTPTS
 + INC INDEX
 +
 +*`Rotate`project`and`draw`the`polygon
 +*`Make`sure`buffer`being`drawn`to`is`clear!
 +
 +:DOIT JSR ROTPROJ
 +
 +*`Convert`xmin`and`xmax`to`columns
 +
 + LDA LOCXMIN
 + LSR
 + LSR
 + LSR  ;x`mod`8
 + STA LOCXMIN
 + CMP GLOBXMIN
 + BCS :NAH
 + STA GLOBXMIN
 +:NAH LDA LOCYMIN
 + CMP GLOBYMIN
 + BCS :UHUH
 + STA GLOBYMIN
 +:UHUH LDA LOCXMAX
 + LSR
 + LSR
 + LSR
 + STA LOCXMAX
 + CMP GLOBXMAX
 + BCC :NOWAY
 + STA GLOBXMAX
 +:NOWAY LDA LOCYMAX
 + CMP GLOBYMAX
 + BCC EORFILL
 + STA GLOBYMAX
 +
 +*`If`using`the`EOR-buffer,`copy`into`drawing`buffer
 +*`And`then`clear`the`EOR-buffer
 +
 +EORFILL LDA FILL
 + BEQ OBJLOOP
 +
 + >>> SETBUF
 + LDA #<EORBUF
 + STA TEMP1
 + LDA #>EORBUF
 + STA TEMP1+1
 +
 + LDA LOCXMIN ;LOCXMIN`now`contains`column
 + LSR  ;Each`column`is`128`bytes
 + BCC :EVEN ;So`there`might`be`a`carry
 + LDY #$80
 + STY BUFFER
 + STY TEMP1
 + CLC
 +:EVEN STA T2
 + ADC BUFFER+1
 + STA BUFFER+1 ;Each`column`is`128`bytes
 + LDA T2
 + ADC TEMP1+1 ;Now`we`will`start`at`the
 + STA TEMP1+1 ;column
 +
 + LDA LOCXMAX
 + SEC
 + SBC LOCXMIN
 + TAX ;Total`number`of`columns`to`do
 + INX  ;e.g.`fill`columns`1..3
 + LDY LOCYMAX
 + BNE :FOOP
 + INC LOCYMAX
 +:FOOP LDY LOCYMAX
 + LDA #00
 +:GOOP EOR (TEMP1),Y ;EOR-buffer
 + PHA
 +*`Maybe`put`an`EOR`below?
 + EOR (BUFFER),Y
 + STA (BUFFER),Y
 + LDA #00 ;Might`as`well`clear`it`now
 + STA (TEMP1),Y
 + PLA
 + DEY
 + CPY LOCYMIN
 + BCS :GOOP
 + LDA BUFFER
 + EOR #$80
 + STA BUFFER
 + STA TEMP1
 + BNE :BOOP
 + INC BUFFER+1
 + INC TEMP1+1
 +:BOOP DEX
 + BNE :FOOP
 + JMP OBJLOOP
 +
 +OBJDONE
 +****`Swap`buffers
 +
 +SWAPBUF LDA VMCSB
 + EOR #$02 ;Pretty`tricky,`eh?
 + STA VMCSB
 + LDA #$08
 + EOR ZTEMP ;ztemp=high`byte`just`flips
 + STA ZTEMP ;between`$30`and`$38
 +
 + JMP MAIN ;Around`and`around`we`go...
 +
 + TXT 'Gee`Brain,`what`do`you`want`to`do`'
 + TXT 'tonight?'
 +
 +**`Rotate,`project,`and`store`the`points
 +*
 +*`This`part`is`a`significant`change`since
 +*`v2.0.``Now`it`is`a`completely`general`polygon`plotter.
 +*`A`set`of`points`is`read`in,`rotated`and`projected,`and
 +*`plotted`into`the`drawing`buffer`(EOR`or`normal).
 +
 +ROTPROJ
 +
 +*`A`neat`macro
 +NEG MAC  ;Change`the`sign`of`a`two's`complement
 + CLC
 + LDA ]1 ;number.
 + EOR #$FF
 + ADC #$01
 + <<<
 +
 +*-------------------------------
 +*`These`macros`replace`the`previous`projection
 +*`subroutine.
 +
 +SMULT`MAC`;Multiply`two`signed`8-bit
 + ;numbers:`A*Y/64`->`A
 + STA`Z3
 + CLC``;This`multiply`is`for`normal
 + EOR`#$FF`;numbers,`i.e.`x=-64..64
 + ADC`#$01
 + STA`Z4
 + LDA`(Z3),Y
 + SEC
 + SBC`(Z4),Y
 + <<<``;All`done`:)
 +
 +SMULTZ MAC ;Multiply`two`signed`8-bit
 +   ;numbers:`A*Y/64`->`A
 + STA Z1
 + CLC  ;And`this`multiply`is`specifically
 + EOR #$FF ;for`the`projection`part,`where
 + ADC #$01 ;numbers`are`-110..110`and`0..40
 + STA Z2
 + LDA (Z1),Y
 + SEC
 + SBC (Z2),Y
 + <<<  ;All`done`:)
 +
 +PROJECT MAC  ;The`actual`projection`routine
 +
 +;The`routine`takes`the`point
 +;]1`]2`]3,`rotates`and
 +;projects`it,`and`stores`the
 +;result`in`]1`]2`]3.
 +
 + LDY ]1 ;Multiply`first`rotation`column
 + LDA A11
 + >>> SMULT
 + STA P1T
 + LDA D21
 + >>> SMULT
 + STA P2T
 + LDA G31
 + >>> SMULT
 + STA P3T
 + LDY ]2 ;Second`column
 + LDA B12
 + >>> SMULT
 + CLC
 + ADC P1T
 + STA P1T
 + LDA E22
 + >>> SMULT
 + CLC
 + ADC P2T
 + STA P2T
 + LDA H32
 + >>> SMULT
 + CLC
 + ADC P3T
 + STA P3T
 + LDY ]3 ;Third`column
 + LDA C13
 + >>> SMULT
 + CLC
 + ADC P1T
 + STA P1T
 + LDA F23
 + >>> SMULT
 + CLC
 + ADC P2T
 + STA P2T
 + LDA I33
 + >>> SMULT
 + CLC
 + ADC P3T
 + STA ]3 ;Rotated`Z
 + TAX
 + LDY ZDIV,X ;Table`of`d/(z+z0)
 +   ;Now`Y`contains`projection`const
 +
 + LDA P1T
 + >>> SMULTZ
 + LDX ZOOM
 + CPX #64
 + BEQ CONTX
 + STY TEMP1
 + LDY ZOOM
 + >>> SMULT
 + LDY TEMP1
 +CONTX CLC
 + ADC #64 ;Offset`the`coordinate
 + STA ]1 ;Rotated`and`projected
 + CMP LOCXMIN ;See`if`it`is`a`local`minimum
 + BCS NOTXMIN
 + STA LOCXMIN
 +NOTXMIN CMP LOCXMAX
 + BCC NOTXMAX
 + STA LOCXMAX
 +
 +NOTXMAX LDA P2T
 + >>> SMULTZ
 + CPX #64
 + BEQ CONTY
 + LDY ZOOM
 + >>> SMULT
 +CONTY CLC
 + ADC #64
 + STA ]2 ;Rotated`and`projected`Y
 + CMP LOCYMIN
 + BCS NOTYMIN
 + STA LOCYMIN
 +NOTYMIN CMP LOCYMAX
 + BCC NOTYMAX
 + STA LOCYMAX
 +
 +NOTYMAX <<<  ;All`done
 +
 +*`LDA`#<EORBUF`;First`we`need`to`clear`the
 +*`STA`BUFFER`;EOR`buffer
 +*`LDA`#>EORBUF
 +*`STA`BUFFER+1
 +
 + LDA #0 ;Reset`Ymin`and`Ymax
 + STA LOCYMAX
 + STA LOCXMAX
 + LDA #$FF
 + STA LOCYMIN
 + STA LOCXMIN
 +
 +READPTS LDY INDEX
 + LDA POLYLIST,Y
 + STA P1X
 + INY
 + LDA POLYLIST,Y
 + STA P1Y
 + INY
 + LDA POLYLIST,Y
 + STA P1Z
 + INY
 + DEC COUNTPTS
 + LDA POLYLIST,Y
 + STA P2X
 + INY
 + LDA POLYLIST,Y
 + STA P2Y
 + INY
 + LDA POLYLIST,Y
 + STA P2Z
 + INY
 + DEC COUNTPTS
 + LDA POLYLIST,Y
 + STA P3X
 + INY
 + LDA POLYLIST,Y
 + STA P3Y
 + INY
 + LDA POLYLIST,Y
 + STA P3Z
 + INY
 + STY INDEX
 + >>> PROJECT,P1X;P1Y;P1Z
 + >>> PROJECT,P2X;P2Y;P2Z
 + >>> PROJECT,P3X;P3Y;P3Z
 +
 + LDA HIDE
 + BEQ :DOIT
 + LDA P2X ;Hidden`face`check
 + SEC
 + SBC P1X
 + TAY  ;Y=(x2-x1)
 + LDA P3Y
 + SEC
 + SBC P2Y ;A=(y3-y2)
 + >>> SMULT
 + STA TEMP1
 + LDA P3X
 + SEC
 + SBC P2X
 + TAY
 + LDA P2Y
 + SEC
 + SBC P1Y
 + >>> SMULT
 + CMP TEMP1 ;If`x1*y2-y1*x2`>`0`then`face
 + BMI :DOIT ;is`visible
 + DEC COUNTPTS ;Otherwise`read`in`remaining
 + BEQ :ABORT ;points`and`return
 +:POOP INC INDEX
 + INC INDEX
 + INC INDEX
 + DEC COUNTPTS
 + BNE :POOP
 +:ABORT RTS
 +
 +:DOIT LDA P1X
 + STA X1
 + LDA P1Y
 + STA Y1
 + LDA P2X
 + STA X2
 + LDA P2Y
 + STA Y2
 + JSR DRAW
 + LDA P2X
 + STA X1
 + LDA P2Y
 + STA Y1
 + LDA P3X
 + STA X2
 + LDA P3Y
 + STA Y2
 + JSR DRAW
 +
 + DEC COUNTPTS
 + BNE POLYLOOP ;Is`it`just`a`triangle?
 + JMP POLYDONE
 +
 +POLYLOOP LDY INDEX
 + LDA POLYLIST,Y
 + STA P2X
 + INY
 + LDA POLYLIST,Y
 + STA P2Y
 + INY
 + LDA POLYLIST,Y
 + STA P2Z
 + INY
 + STY INDEX
 + >>> PROJECT,P2X;P2Y;P2Z
 +
 + LDA P2X
 + STA X1
 + LDA P2Y
 + STA Y1
 + LDA P3X
 + STA X2
 + LDA P3Y
 + STA Y2
 + JSR DRAW
 +
 + LDA P2X
 + STA P3X
 + LDA P2Y
 + STA P3Y
 + DEC COUNTPTS
 + BEQ POLYDONE
 + JMP POLYLOOP
 +POLYDONE LDA P1X ;Close`the`polygon
 + STA X2
 + LDA P1Y
 + STA Y2
 + LDA P3X
 + STA X1
 + LDA P3Y
 + STA Y1
 + JSR DRAW
 + RTS
 +
 + TXT 'Same`thing`we`do`every`night,`Pinky:`'
 + TXT 'try`to`take`over`the`world!'
 +
 +
 +*-------------------------------
 +*`General`questionable-value`error`procedure
 +
 +*CHOKE`LDX`#00
 +*:LOOP`LDA`:CTEXT,X
 +*`BEQ`:DONE
 +*`JSR`CHROUT
 +*`INX
 +*`JMP`:LOOP
 +*:DONE`RTS
 +*:CTEXT`HEX`0D`;CR
 +*`TXT`'something`choked`:('
 +*`HEX`0D00
 +*
 + TXT 'Narf!'
 +
 +*-------------------------------
 +*`Drawin'`a`line.``A`fahn`lahn.
 +
 +***`Some`useful`macros
 +
 +CINIT MAC  ;Macro`to`initialize`the`counter
 + LDA ]1 ;dx`or`dy
 + LSR
 + <<<  ;The`dx/2`makes`a`nicer`looking`line
 +
 +*****`Macro`to`take`a`step`in`X
 +
 +XSTEP MAC
 + LDX DX ;Number`of`loop`iterations
 + >>> CINIT,DX
 +XLOOP LSR CHUNK
 + BEQ FIXC ;Update`column
 + SBC DY
 + BCC FIXY ;Time`to`step`in`Y
 + DEX
 + BNE XLOOP
 +DONE LDA OLDX ;Plot`the`last`chunk
 + EOR CHUNK
 + ORA (BUFFER),Y
 + STA (BUFFER),Y
 + RTS
 +
 +FIXC PHA
 + LDA OLDX
 + ORA (BUFFER),Y ;Plot
 + STA (BUFFER),Y
 + LDA #$FF ;Update`chunk
 + STA OLDX
 + STA CHUNK
 + LDA #$80 ;Increase`the`column
 + EOR BUFFER
 + STA BUFFER
 + BNE C2
 + INC BUFFER+1
 +C2
 + PLA
 + SBC DY
 + BCS CONT
 + ADC DX
 + IF I,]1 ;Do`we`use`INY`or`DEY?
 + INY
 + ELSE
 + DEY
 + FIN
 +CONT DEX
 + BNE XLOOP
 + JMP DONE
 +
 +FIXY ADC DX
 + PHA
 + LDA OLDX
 + EOR CHUNK
 + ORA (BUFFER),Y
 + STA (BUFFER),Y
 + LDA CHUNK
 + STA OLDX
 + PLA
 + IF I,]1 ;Update`Y
 + INY
 + ELSE
 + DEY
 + FIN
 + DEX
 + BNE XLOOP
 + RTS
 + <<<  ;End`of`Macro`xstep
 +
 +*****`Take`a`step`in`Y
 +
 +YSTEP MAC
 + LDX DY ;Number`of`loop`iterations
 + BEQ DONE ;If`dy=0`it's`just`a`point
 + >>> CINIT,DY
 + SEC
 +YLOOP PHA
 + LDA OLDX
 + ORA (BUFFER),Y
 + STA (BUFFER),Y
 + PLA
 + IF I,]1
 + INY
 + ELSE
 + DEY
 + FIN
 + SBC DX
 + BCC FIXX
 + DEX
 + BNE YLOOP
 +DONE LDA OLDX
 + ORA (BUFFER),Y
 + STA (BUFFER),Y
 + RTS
 +
 +FIXX ADC DY
 + LSR OLDX
 + SEC  ;Important!
 + BEQ FIXC
 + DEX
 + BNE YLOOP
 + JMP DONE
 +
 +FIXC PHA
 + LDA #$80
 + STA OLDX
 + EOR BUFFER
 + STA BUFFER
 + BNE C2
 + INC BUFFER+1
 +C2 PLA
 + DEX
 + BNE YLOOP
 + JMP DONE
 + <<<  ;End`of`Macro`ystep
 +
 +*`Take`an`x`step`in`the`EOR`buffer
 +*`The`sole`change`is`to`use`EOR`instead`of`ORA
 +
 +EORXSTEP MAC
 + LDX DX ;Number`of`loop`iterations
 + >>> CINIT,DX
 +XLOOP LSR CHUNK
 + BEQ FIXC ;Update`column
 + SBC DY
 + BCC FIXY ;Time`to`step`in`Y
 + DEX
 + BNE XLOOP
 +DONE LDA OLDX ;Plot`the`last`chunk
 + EOR CHUNK
 + EOR (BUFFER),Y
 + STA (BUFFER),Y
 + RTS
 +
 +FIXC PHA
 + LDA OLDX
 + EOR (BUFFER),Y ;Plot
 + STA (BUFFER),Y
 + LDA #$FF ;Update`chunk
 + STA OLDX
 + STA CHUNK
 + LDA #$80 ;Increase`the`column
 + EOR BUFFER
 + STA BUFFER
 + BNE C2
 + INC BUFFER+1
 +C2
 + PLA
 + SBC DY
 + BCS CONT
 + ADC DX
 + IF I,]1 ;Do`we`use`INY`or`DEY?
 + INY
 + ELSE
 + DEY
 + FIN
 +CONT DEX
 + BNE XLOOP
 + JMP DONE
 +
 +FIXY ADC DX
 + PHA
 + LDA OLDX
 + EOR CHUNK
 + EOR (BUFFER),Y
 + STA (BUFFER),Y
 + LDA CHUNK
 + STA OLDX
 + PLA
 + IF I,]1 ;Update`Y
 + INY
 + ELSE
 + DEY
 + FIN
 + DEX
 + BNE XLOOP
 + RTS
 + <<<  ;End`of`Macro`xstep
 +
 +
 +*`Take`a`y-step`in`the`EOR-buffer
 +*`Changes`from`above`are:`only`plot`last`part`of`each
 +*`vertical`chunk,`don't`plot`last`point,`plot`with`EOR
 +
 +EORYSTEP MAC
 + LDX DY ;Number`of`loop`iterations
 + BEQ DONE ;If`dy=0`it's`just`a`point
 + >>> CINIT,DY
 + SEC
 +*YLOOP`PHA
 +*`LDA`OLDX
 +*`ORA`(BUFFER),Y
 +*`STA`(BUFFER),Y
 +*`PLA
 +YLOOP IF I,]1
 + INY
 + ELSE
 + DEY
 + FIN
 + SBC DX
 + BCC FIXX
 + DEX
 + BNE YLOOP
 +*DONE`LDA`OLDX
 +*`ORA`(BUFFER),Y
 +*`STA`(BUFFER),Y
 +DONE RTS
 +
 +FIXX ADC DY
 + PHA  ;We`only`plot`the`last`part`of`each`chunk
 + LDA OLDX
 + EOR (BUFFER),Y
 + STA (BUFFER),Y
 + PLA
 + LSR OLDX
 + SEC  ;Important!
 + BEQ FIXC
 + DEX
 + BNE YLOOP
 + JMP DONE
 +
 +FIXC PHA
 + LDA #$80
 + STA OLDX
 + EOR BUFFER
 + STA BUFFER
 + BNE C2
 + INC BUFFER+1
 +C2 PLA
 + DEX
 + BNE YLOOP
 + JMP DONE
 + <<<  ;End`of`Macro`ystep
 +****`Initial`line`setup
 +
 +**`The`commented`lines`below`are`now`taken`care`of`by`the
 +**`calling`routine.
 +*DRAW`>>>`MOVE,TX1;X1``;Move`stuff`into`zero`page
 +*`>>>`MOVE,TX2;X2``;Where`it`can`be`modified
 +*`>>>`MOVE,TY1;Y1
 +*`>>>`MOVE,TY2;Y2
 +
 +DRAW LDA FILL
 + BNE :SETEOR
 + >>> SETBUF
 + JMP :SETUP
 +:SETEOR LDA #<EORBUF ;Use`EOR`buffer`instead`of
 + STA BUFFER ;display`buffer`for`drawing
 + LDA #>EORBUF
 + STA BUFFER+1
 +
 +:SETUP SEC  ;Make`sure`x1<x2
 + LDA X2
 + SBC X1
 + BCS :CONT
 + LDA Y2 ;If`not,`swap`P1`and`P2
 + LDY Y1
 + STA Y1
 + STY Y2
 + LDA X1
 + LDY X2
 + STY X1
 + STA X2
 +
 + SEC
 + SBC X1 ;Now`A=dx
 +:CONT STA DX
 + LDX X1 ;Put`x1`into`X,`now`we`can`trash`X1
 +
 +COLUMN TXA ;Find`the`first`column`for`X
 + LSR
 + LSR  ;There`are`x1/8`128`byte`blocks
 + LSR  ;Which`means`x1/16`256`byte`blocks
 + LSR
 + BCC :EVEN ;With`a`possible`extra`128`byte`block
 + LDY #$80 ;if`so,`set`the`high`bit
 + STY BUFFER
 + CLC
 +:EVEN ADC BUFFER+1 ;Add`in`the`number`of`256`byte`blocks
 + STA BUFFER+1
 +
 + SEC
 + LDA Y2 ;Calculate`dy
 + SBC Y1
 + BCS :CONT2 ;Is`y2>y1?
 + EOR #$FF ;Otherwise`dy=y1-y2
 + ADC #$01
 +:CONT2 STA DY
 + CMP DX ;Who's`bigger:`dy`or`dx?
 + BCC STEPINX ;If`dx,`then...
 + JMP STEPINY
 +
 +STEPINX LDY Y1
 + CPY Y2
 + LDA BITP,X ;X`currently`contains`x1
 + STA OLDX
 + STA CHUNK
 + BCC XINCY ;Do`we`step`forwards`or`backwards`in`Y?
 + JMP XDECY
 +
 +XINCY LDA FILL
 + BEQ NORMXINC
 + >>> EORXSTEP,INY
 +NORMXINC >>> XSTEP,INY
 +
 +XDECY LDA FILL
 + BEQ NORMXDEC
 + >>> EORXSTEP,DEY
 +NORMXDEC >>> XSTEP,DEY
 +
 +STEPINY LDY Y1
 + LDA BITP,X ;X=x1
 + STA OLDX
 + LSR  ;Y`doesn't`use`chunks
 + EOR OLDX ;So`we`just`want`the`bit
 + STA OLDX
 + CPY Y2
 + BCS YDECY
 +
 +YINCY LDA FILL
 + BEQ NORMINC
 + >>> EORYSTEP,INY
 +NORMINC >>> YSTEP,INY
 +
 +YDECY LDA FILL
 + BEQ NORMDEC
 + >>> EORYSTEP,DEY
 +NORMDEC >>> YSTEP,DEY
 +
 +
 +*-------------------------------
 +*`Clean`up
 +
 +CLEANUP LDA VMCSB ;Switch`char`rom`back`in
 + AND #%11110101 ;default
 + STA VMCSB
 +
 + RTS  ;bye!
 +
 + TXT 'spinal`cracker`'
 + TXT 'slj`6/95'
 +
 +*-------------------------------
 +*`Set`up`bit`table
 +
 + DS ^ ;Clear`to`end`of`page
 +   ;So`that`tables`start`on`a`page`boundary
 +BITP LUP 16 ;128`Entries`for`X
 + DFB %11111111
 + DFB %01111111
 + DFB %00111111
 + DFB %00011111
 + DFB %00001111
 + DFB %00000111
 + DFB %00000011
 + DFB %00000001
 + --^
 +
 +SIN ;Table`of`sines,`120`bytes
 +COS EQU SIN+128 ;Table`of`cosines
 +   ;Both`of`these`trig`tables`are
 +   ;currently`set`up`from`BASIC
 +ZDIV EQU COS+128 ;Division`table
 +TMATH1 EQU ZDIV+384 ;Math`table`of`f(x)=x*x/256
 +TMATH2 EQU TMATH1+512 ;Second`math`table
 +POLYLIST EQU TMATH2+512 ;List`of`polygons
 +
 +========================================================================
 +</code>
 +====== Second SID Chip Installation ======
 +<code>
 +Copyright 1988 Mark A. Dickenson
 +
 +This information and software is COPYRIGHTED and made available on a 
 +SHAREWARE basis.  This file can be freely copied and distributed as long 
 +as it is not SOLD.  This information cannot be used to construct and sell a
 +hardware device without receiving prior permission from the author. There is
 +not a set fee for the use of this information.  Just send in whatever you feel
 +the information is worth.
 +
 +If you have any gripes, complaints, suggestions, COMPLIMENTS or DONATIONS of
 +any sort please send them to:
 +
 + Mark Dickenson
 + 600 South West Street
 + Nevada, Missouri  64772
 +
 +Adding an extra SID 6581/6582 chip
 +
 +This is not a project to be tackled by the sqeamish or people who are deathly
 +afraid of opening their computer just to take a peek inside.
 +
 +Now let's get rid of the nasty stuff first.  No liability is assumed with
 +respect to the use of the following information.  In other words if you
 +screw-up trying to install this modification, then it's your responsability.
 +
 +  YOU DO THIS AT YOUR OWN RISK!!!!
 +
 +If you do not feel up to it PLEASE take it to a Commodore repair center 
 +or a repair service that can work on computers and let them do the 
 +installation.  I will warn you that most Commodore Repair Centers will not or
 +do not like to do this modification.  When they do, it can be expensive. If
 +you belong to a Users Group, tell them about the project and ask if there is
 +anyone there that could perform the operation.  This modification will NOT
 +hurt the computer in any way, unless it is installed WRONG.
 +
 +You can make your own piggy back board or you can do what I am going to
 +describe (since it is a little hard to put a schematic in a text file).
 +
 +You should ground yourself with a static guard wristband (such as what 
 +Radio Shack sells).  Even though the chip is quite durable, just the right
 +static discharge can ruin all or part of the SID chip.
 +
 +For those of you that are not familier with the way pins are numbered on an
 +IC chip here is a short explanation.  On one end of the IC you should find a
 +little notch, looking at the chip with the notch at the top the numbering goes
 +this way.  The upper left corner of the chip is pin 1 and they are numbered
 +consecutively, counter-clockwise around the chip. Some chips do not have a
 +notch in one end, but instead dot is placed in one of the chip corners to
 +designate that pin 1 starts in that location.
 +
 +            notch
 +          ----,,----
 +        1-!.       !-8
 +        2-! dot    !-7
 +        3-!        !-6
 +        4-!        !-5
 +          ----------
 +
 +
 +I have included the information that is needed to install this modification
 +on the Commodore 64, 64C and 128.  I haven't been able to look inside the
 +128D, so I cannot provide the information with any accuracy.
 +
 +There are TWO different 64C circuit boards and both use DIFFERENT SID 
 +chips.  You can tell the difference by opening the 64C.  If you see a 64-pin
 +chip on the board and the board is only 5.5-6 inches wide then you have the
 +narrow board 64C and must use the 9 volt 6582 SID chip.  The number of the
 +chip in the 64C narrow is an 8520 and is the same as the 6582.
 +
 +----------------------------------
 +
 +Parts Commodore 64, 64C (wide) & 128
 +
 +1 - 6581 SID chip from Jamco or Kassara Microsystems
 +1 - 2N2222 transistor  Radio Shack 276-1617
 +2 - 220pf capacitors  Radio Shack 272-124
 +
 +-----------------------------------
 +
 +Parts Commodore 64C Narrow Board
 +
 +1 - 6582 SID Chip  From Jamco or Kassara Microsystems
 +1 - 2222A transistor  Radio Shack 276-2009
 +2 - .022uf capacitors  Radio Shack 272-1066
 +2 - 1k ohm 1/4 watt resistors  Radio Shack 271-1321
 +
 +-----------------------------------
 +
 +Parts 64, 64C (all) & 128
 +
 +2 - 1k ohm 1/4 watt resistors  Radio Shack 271-1321
 +1 - 1000 pf capacitor  Radio Shack 272-126 listed as .001 mf this is 
 +    the same as 1000pf
 +1 - 10k ohm 1/4 watt resistor  Radio Shack 271-1335
 +1 - 10 uf electrolitic capacitor  Radio Shack 272-1025
 +1 - 5 inch length of wire
 +1 - 5 inch length of shielded cable
 +1 - surface mount female RCA plug (this is what you normally find on the back
 +    of your stereo.
 +
 +
 +On the C-64 and 64C (wide) the SID is IC U18 (the IC number will be marked in
 +white on the circuit board).  It is usually located in the middle of the
 +circuit board, next to the metal video chip case or up between and just
 +below the serial and monitor jacks.
 +
 +On the C-64C (narrow board) the SID chip is IC U9.  It is located in the
 +middle of the board, just a little to the right of center) and called 520.
 +
 +On the C-128 the SID is IC U5.  It is located at the back of the circuit 
 +board just to the right of the metal housing for the 40 and 80 column video
 +chips.
 +
 +First bend out pins 23, 24 and 26 and cut them off of the 6581/6582 SID 
 +chip.  These are for the two analog and one audio input lines.  They will
 +cause problems if connected and since they will not be used it is best to
 +remove them.
 +
 +Now bend out pins 1, 2, 3, 4, 8, and 27.
 +
 +Solder one of the 220pf capacitors (64C narrow uses .022 uf) to pins 1 
 +and 2 then solder the other 220pf (64C narrow - .022uf) capacitor to pins 3
 +and 4.  The capacitors control the upper and lower frequency range and
 +filters of the SID chip.
 +
 +The reason I am using 220pf capacitors is because of problems with the 
 +filters in the SID chip.  The C-64 first came out with 2200pf capacitors, but
 +they were changed to 470pf.  The reason for this was because the filters of
 +the SID vary from chip to chip and using 2200pf caused a lot of them to sound
 +muffeled when the filters were on.  I have found that by lowering the
 +capacitor value to 220 pf helps even more.  If you wish, you can use 470s if
 +you feel it would be better, but DO NOT use 2200pf.
 +
 +The 6582 SID chip for the 64C narrow must use the .022uf capacitors, as the
 +filter range is much different.
 +
 +Solder one end of your wire to pin 8 of the SID chip.  This is for the chip
 +select line.  We will connect this to the cartridge port.  This tells the
 +computer where in memory the chip resides (described later).
 +
 +Now solder the remaining pins (excluding the ones we have bent out 
 +and/or removed 1, 2, 3, 4, 8, 23, 24, 26 and 27) to the sid chip currently in
 +your computer.  You may have to bend those pins inward just a little for them
 +to get a good grip on the SID chip.  Be very careful not leave the soldering
 +iron on the chip TOO long as you could ruin BOTH SID chips.  I would put some
 +heat sink (silicon grease) between the two chips before soldering them
 +together.  This will provide better heat dispersal on the bottom chip.
 +
 +Now that you have the chips soldered together (place the SID chips back in 
 +the socket if you removed them), solder the wire from pin 8 (on the SID chip)
 +to pin 7 of the cartridge port on the back of the computer.  Set the computer
 +infront of you like to are getting ready to type, with the back of the
 +computer away from you.  Look at the cartridge port (located in the upper
 +right corner of the circuit board).  You will see two rows of pins connecting
 +the cartridge port to the circuit board.  You want the row of pins closest to
 +the front of the computer.  Now, count the pins starting at the LEFT side and
 +counting to the right.  You want to solder the wire from pin 8 of the extra
 +SID chip to pin number 7 of the cartridge port. This is the same place on all
 +of the models C-64, 64C and 128.
 +
 +This will tell the computer that the extra SID chip is at address $DE00 hex
 +or 56832 decimal.  You will access it just like you would the regular sid
 +chip but starting at this address.
 +
 +I am no longer describing how to connect for address $DF00.  This 
 +address causes problems with the RAM Expansion Units and numerous other 
 +cartridges.  From now on address $DE00 is the ONLY address for the SID chip.
 +
 +Now partially reassemble your computer (be careful that nothing shorts out 
 +the pins still sticking out).  Turn the computer on and load the player 
 +program provided and tell it to load in 'TEST' If you get sound then so far
 + so good.  Turn off the computer and disassemble the case.
 +
 +Drill a hole in the back end of the computer just large enough to anchor 
 +the RCA plug.  Then solder the center wire of the shielded cable to the 
 +center post of the RCA plug.  Insert the wire through the hole you have 
 +just drilled and anchor the plug to the case.  Now solder the ground wire to 
 +the ground tab on the RCA plug.
 +
 +Here comes the difficult part to explain.  This is the coupling circuit 
 +for the audio output.  Here is a rough schematic.
 +
 +
 +Pin 27 on             12volts dc, 
 +9volts 64C (narrow)
 +SID chip  resistor
 +    !--.  10k ohm      !collector
 +27!----.--/!/!/--.-----O 2n2222 or 2222A
 +--'    !             !emitter
 +                   !
 +       <resistor !     !
 +       >1k           ! +
 +
 +===========================================================================
 +</code>
 +====== SOLVING LARGE SYSTEMS OF LINEAR EQUATIONS ON A C64 WITHOUT MEMORY ======
 +<code>
 +by Alan Jones  (alan.jones@qcs.org)
 +
 +OK, now that I have your attention, I lied.  You can't solve dense
 +linear systems of equations by direct methods without using memory to
 +store the problem data.  However, I'll come back to this memory free
 +assertion later.  The main purpose of this article is to rescue a
 +usefull numerical algorithm, "Quartersolve", and also to provide a brief
 +look at the COMAL programming language and BLAS routines.
 +
 +Linear systems of equations, A(,)*x()=b(), where A is a square matrix
 +and x and b are vectors (or arrays), must often be solved for x in the
 +solution of a variety of problems.  The size or dimension of the problem
 +is n and just storing A requires 5*n*n bytes of memory, assuming C64/128
 +5 byte real variables.  The prefered solution method is a form of
 +Gaussian Elimination which requires 1/3 n*n*n multiplications.  I'll
 +ignore the additional n*n and n multiplies.  For large problems our C64
 +has two serious limitations, small memory size and slow floating point
 +arithmetic.  Problems with n=10 can be computed easily.  Problems with
 +n=100 will require 100 times more memory and 1000 times more computing
 +time.  The computing time is not a real problem.  I don't mind letting
 +my computer run while I watch a movie, sleep, or go on a trip.
 +Calculating or setting up the problem may take much longer than its
 +solution anyway.  Available memory is the practical limiting factor.
 +After we use up available RAM we have to resort to other algorithms that
 +will use the disk drive to move data in and out of the computer.  The
 +1541 drive is particularly slow and I would not want to subject it to
 +undue wear and tear.
 +
 +How big a problem do we need to be able to solve?  In many cases the
 +problem itself will fix n and there is no way to reduce it.  In other
 +cases you might be modeling a real continuous problem with a discrete
 +number of elements.  N should be infinity but for problem solution n=50
 +might be big enough.  Consider calculating the aerodynamic potential
 +flowfield around a body of revolution.  You could fix points on the
 +surface of the body (a meridian) and have a series of sort line segments
 +make up elements to approximate the shape.  The lager n is the closer
 +the smooth shape is aproximated and the more accurate the computed
 +solution becomes.  n=100 might be a good choice for a simple shape.  We
 +could also use a "higher order" menthod.  In this case we can substitute
 +a curved line element for the straight line segment.  Calculating the
 +matrix elements will be more difficult but n=40 curved elements might
 +give a more accurate solution than 100 flat elements.  Another
 +consideration is the resolution of the solution.  You might want to plot
 +the solution on the 200 x 320 pixel hi-res C64 screen.  40 points might
 +be too coarse and 320 might be overkill.  We might also need to
 +calculate the slope or derivatives from the calculated solution which
 +will require more closely spaced solution points.  There are often
 +choices that you can make in modeling a system and selecting a solution
 +algorithm so that a problem can be solved within the limits of a C64.
 +There are often interesting tradeoffs in memory requirements and
 +execution speed.
 +
 +How big a problem can we solve with a C64?  Using Quartersolve with
 +assembly language we can probably do n=200 or more.  If we are going to
 +store the problem data on a single 1541 diskette and read it in a row at
 +time we can only do n=182 or so.  Actually I think n should be well
 +under 100.  Different operating systems and languages limit the amount
 +of useable RAM; BASIC 40K, COMAL 2.0 30K, GEOS 23K, the initial disk
 +loaded COMAL 0.14 10K...  Solving a linear system may only be a small
 +subproblem inside a large application program.  The idea is to be able
 +to solve reasonable sized problems using your prefered  computing
 +environment without having to do a lot of chaining or loading of
 +separate programs.  Quartersolve can free up a lot of memory for other
 +routines or allow n to be doubled.
 +
 +SPEED
 +
 +There are a few things that we can do to speed up the calculations.
 +First we can select a fast programming language.  I prefere COMAL 2.0
 +which is a fast three pass interpreter.  Using an assembler could be the
 +fastest and provide the most useable memory.  A true compiler such as C
 +or Pascal could also be a good choice.  BASIC is a poor choice except
 +that it is built in and free.  In most cases execution can be sped up
 +with some machine language routines like the BLAS (Basic Linear Algebra
 +Subroutines).  Calculation speed is measured in FLOPS/sec  (Floating
 +Point OPerationS) where, c(i#,j#):=c(i#,j#) + a(i#,k#)*b(k#,j#) is the
 +operation.  It is one FP multiply, one FP add, and some indexing
 +overhead.  With some interpreters the indexing and interpreting overhead
 +can far exceed the actual FP multiply time.  With assembled code the FP
 +multiply time should dominate.  I use a ML level 1 BLAS package with
 +COMAL 2.0.  For example:
 +
 +    c(i#,J#):+sdot(n#,a(i#,1),1,b(1,j#),sdb#)
 +    FOR k#:=1 to n# do c(i#,j#):+a(i#,k#)*b(k#,j#)
 +
 +both calculate the same thing, a dot product with n# FLOPS.  For large
 +n# on a C64 the BLAS approach about 320 FLOPS/sec., The overhead of
 +calling the procedure from the interpreter is about the equivalent of 4
 +FLOPS.  Of course modern computer performance is measured in
 +MegaFLOPS/sec. with 8 byte reals (super computers run hundreds or
 +thousands of MFLOPS/sec.).  They also inflate the performance by
 +counting the multiply and add as two FLOPS.  In his article I use the
 +"old flops" or number of multiplies.
 +
 +It may also be possible to code 6502 FP arithmetic routines using lookup
 +tables that may perform faster than the built in routines.  We could
 +also use the CPU in the disk drives to do distributed processing.  But
 +this is beyond the scope of this article.
 +
 +SOLUTION METHODS
 +
 +Consider the following choices for numerical solution algorithms:
 +
 +METHOD                  MEMORY       FLOPS
 +Gaussian Elimination       n*n     1/3 n*n*n
 +Cholesky Decomposition 1/2 n*n     1/6 n*n*n
 +QR decomposition           n*n     2/3 n*n*n
 +QR updating            1/2 n*n      2  n*n*n
 +Gauss-Jordan               n*n     1/2 n*n*n
 +Quartersolve           1/4 n*n     1/2 n*n*n
 +
 +
 +Gaussian Elimination is the prefered method when enough memory is
 +available.  In modern terminology this is LU decomposition where A is
 +decomposed or factored into a lower triangular matrix and an upper
 +triangular matrix.  Partial pivoting of rows or columns is an additional
 +complication often required for a stable solution.  After the LU
 +decompostion you can readily solve for any number of right hand side
 +vectors in n*n flops each.  In addition you can calculate matrix
 +condition number estimates and use iterative improvement techniques.
 +The LU decomposition is done in place overwriting the problem matrix A.
 +
 +Cholesky Decomposition is a specialized version of Gaussian Elimination
 +for symetric positive definite matrices only.  Since A is symetric we
 +only need n*(n+1)/2 memory storage locations.  The L and U triangular
 +matrices are simply transposes of the other so only one needs to be
 +stored and is computed in place overwriting the original storage used
 +for A.  No pivoting is required.  This algorithm cannot solve general
 +nonsymetric problems and is included only for comparison.
 +
 +QR decomposition factors A into an orthogonal matrix Q and a triangular
 +matrix R.  QR decomposition is very stable and can be performed without
 +pivoting.  Since Q is orthogonal its inverse is just Q transpose.  To
 +solve the linear system we multiply the right hand side vector by Q
 +transpose then solve the triangular system R.  Q is computed in a
 +special compact form and stored in the space below R.  The decomposition
 +is done in place in the storage used for A, plus an additional n storage
 +locations.  QR decomposition requires about twice as many flops as
 +Gaussian Elimination.
 +
 +There is a variation of the QR solution known as QR updating.  The
 +problem is solved a row at a time.  A Row of A can be read in from disk
 +storage or calculated as needed.  Only R needs to be stored in main
 +memory, n*(n+1)/2 memory locations.  R is initialy the identity matrix
 +and is updated as each row of A and its right hand side element are
 +processed.  Q is not stored, but the right hand side vector is
 +sequentialy multiplied by Q transpose.  After all n rows have been
 +processed the solution is found by simply solving the triangular system
 +R.  Since this method only needs half as much memory storage as LU
 +decomposition, we can solve problems 40% larger in a limited memory
 +space.  However, the cost in flops is high.  Actually QR updating is
 +best used for solving large overdetermined least squares problems.
 +
 +Gauss-Jordan is a variation of Gaussian Elimination that reduces A to
 +the Identity matrix instead of to LU factors.  By applying the same
 +transformations to to the right hand side that reduce A to the identity
 +matrix, the right hand side becomes the solution at completion.
 +Pivoting is requiered.  Gauss-Jordan requires about 50% more flops than
 +Gaussian Elimination and most codes use n*n memory storage.  Since the
 +LU factors are not computed we can't solve additional right hand side
 +vectors later, or estimate the matrix condition number, or use iterative
 +improvement techniques.  It will solve multiple right hand sides that
 +are available from the start.
 +
 +Quartersolve is a clever implementation of Gauss-Jordan(?) that solves
 +the problem a row at a time like QR updating but only requires 1/4 n*n
 +memory storage.  With fixed available memory Quartersolve can solve a
 +problem twice as large as Gaussian Elimination but with a modest
 +performance penalty.  Solving a 2n problem with Quartersolve would take
 +12 times longer (instead of 8) than Gaussian Elimination on a size n
 +problem.
 +
 +My recommendation is to use Gaussian elimination for solving dense
 +general systems of linear equations when enough main memory is available
 +and switch to Quartersolve for larger problems.  For solving huge
 +problems requiering external storage a blocked version of QR
 +decomposition might work best.  Cholesky decomposition should be used
 +for symetric positive definite problems.  Large problems are often
 +sparse, containing lots of zeros that need not be stored.  Specialized
 +code exists for solving many sparce problems, particularly banded
 +matrices, and many of these methods can be used on a C64.  Codes for
 +solving unstructured sparce problems are not very suitable for the C64
 +since they are complex and reduce the amount of memory available for
 +solving the problem.  However, large sparce problems can also be solved
 +on the C64 by iterative methods such as Gauss-Siedel and Conjugate
 +Gradient algorithms.
 +
 +QUARTERSOLVE
 +
 +Quartersolve is a useful method for solving general dense systems of
 +linear equations that I discovered almost by accident while doing random
 +research in the library.  I have not seen any recent texts or papers
 +mentioning this algorithm.  I have not seen any reference to it in the
 +C64 literature either.  At least one older text mentioned it in passing
 +saying that the code was too long or complex.  This is a valid point
 +since usualy the code size directly subtracts from the problem storage.
 +The code is longer than the Gaussian Elimination code but in my
 +implementation it only takes about 2K of main memory storage and it is a
 +real advantage on the C64.  With a C64 we can also put the entire code
 +in an EPROM on a cartridge so the code size is of little concern.
 +
 +I found Quartersolve in Ref. 1 (R. A. Zamberdino, 1974), which credited
 +the algorithm to Ref. 2 (A. Orden, 1960).  I am a little uneasy
 +describing the algorithm since I have not seen Ref. 2 or analyzed the
 +algorithm.  I have coded the algorithm, tested it, and used it to solve
 +some large problems on a C64, up to n=90.  Zambardino makes two
 +interesting statements in Ref 1.  "The number of arithmetic operations
 +is the same as for the Gaussian Elimination method."  I am reasonably
 +sure from the description that he meant Gauss-Jordan which requires
 +about 50% more arithmetic than Gaussian Elimination.  After processing
 +the ith row only i(n-i) storage locations are required to store the
 +reduced matrix.  Max[i(n-i)] = n*n/4.  This maximum memory requirement
 +occurs at i = n/2.  As i increases further memory required is reduced.
 +Although n*n/4 memory locations must be allocated and dimensioned in an
 +array at the start, Quartersolve always uses the first portion of the
 +array continuously and does not free up memory in holes scattered
 +throughout the array.  The C language could possibly use "heap storage"
 +and release the memory for any other use as the procedure advances.
 +
 +Now back to my initial memory free claim.  The large problem that I
 +actually wanted to solve was: A*x=b, B*x=r, for r given b and the square
 +matrices A and B.  Elements of A and B are most efficiently calculated
 +at the same time.  I could write B to the drive and read it back in
 +after x is computed to calculate r, but I actually wanted to solve this
 +repeatedly inside another loop and I did not want to read and write to a
 +lousy 1541 that much.  Using Gaussian elimination would require 2n*n
 +storage.  Using Quartersolve could require 1.25n*n storage.  However,
 +only n*n storage is needed, that for B.  At the ith step the ith row of
 +A and B are calculated.  The row of A is processed into the the n*n
 +dimensioned array B filling it from the front.  The corresponding row of
 +B is stored in the same array B filling from from the end of array B.
 +As the process continues Quartersolve "dissuses" array B so that rows of
 +B never overwrite storage needed by Quartersolve.  At the end we have
 +computed x and  all of B is stored in the array B.  Simple
 +multiplication produces r.  So I can say with pride, at the expense of
 +honesty, that I have solved A*x=b without any additional memory storage
 +for A.
 +
 +PROC slv(n#,nr#,i#,REF a(),REF c(),REF b(,),sdb#,REF sw#(),REF fail#) CLOSED
 +  // This routine solves a system of equations using the quartersolve
 +  // algorithm with partial pivoting.
 +  // It is called a "line at a time" and uses only
 +  // 0.25*nn memory locations which enables larger problems to be solved.
 +  // The LU factorization is not available, nor a condition estimate.
 +  // n# is the dimension of the problem
 +  // nr# is the number of right hand vectors to be solved for.
 +  // b(,) is the right hand side columns
 +  // sdb# is the second dimension of the array b(,)
 +  USE blas
 +  USE strings
 +  q#:=i#-1; m#:=n#-q#; mm1#:=m#-1; fail#:=TRUE; ip1#:=i#+1
 +  IF i#=1 THEN //initialize pivot array
 +    FOR j#:=1 TO n# DO sw#(j#):=j#
 +  ENDIF 
 +  FOR j#:=1 TO q# DO //adjust for previous pivoting
 +    k#:=sw#(j#)
 +    WHILE k#<j# DO k#:=sw#(k#)
 +    IF k#>j# THEN swap'real(c(j#),c(k#))
 +  ENDFOR j#
 +  FOR j#:=i# TO n# DO c(j#):-sdot(q#,c(1),1,a(j#-q#),m#)
 +  p#:=q#+isamax#(m#,c(i#),1)
 +  r:=ABS(c(p#))
 +  IF r=0 THEN RETURN
 +  fail#:=FALSE
 +  IF p#<>i# THEN
 +    swap'real(c(i#),c(p#))
 +    swap'integer(sw#(i#),sw#(p#))
 +    sswap(q#,a(1),m#,a(p#-q#),m#)
 +  ENDIF 
 +  r:=1/c(i#)
 +  IF mm1#<>0 THEN sscal(mm1#,r,c(ip1#),1)
 +  FOR j#:=1 TO nr# DO b(i#,j#):=r*(b(i#,j#)-sdot(q#,c(1),1,b(1,j#),sdb#))
 +  FOR k#:=1 TO nr# DO saxpy(q#,-b(i#,k#),a(1),m#,b(1,k#),sdb#)
 +  IF mm1#>0 THEN
 +    t#:=1
 +    FOR j#:=1 TO q# DO
 +      r:=a(t#); t#:+1
 +      scopy(mm1#,a(t#),1,a(t#-j#),1)
 +      saxpy(mm1#,-r,c(ip1#),1,a(t#-j#),1)
 +      t#:+mm1#
 +    ENDFOR j#
 +    scopy(mm1#,c(ip1#),1,a(mm1#*q#+1),1)
 +  ELSE //unscramble solution from pivoting
 +    FOR j#:=1 TO nr# DO
 +      FOR k#:=1 TO n# DO c(sw#(k#)):=b(k#,j#)
 +      scopy(n#,c(1),1,b(1,j#),sdb#)
 +    ENDFOR j#
 +  ENDIF 
 +ENDPROC slv
 +//
 +n#:=8; sdrh#:=1; nrh#:=1; nr#:=1
 +// a is of  dimension n*n/4
 +DIM a(16), b(n#), rhs(n#,nrh#), sw#(n#)
 +FOR i#:=1 TO n# DO
 +  FOR j#:=1 TO n# DO b(j#):=2297295/(i#+j#-1)
 +  s:=0
 +  FOR j#:=n# TO 1 STEP -1 DO s:+b(j#)
 +  rhs(i#,1):=s
 +  slv(n#,nr#,i#,a(),b(),rhs(,),sdrh#,sw#(),fail#)
 +  IF fail# THEN
 +    PRINT "singularity detected at i=";i#
 +    STOP 
 +  ENDIF 
 +ENDFOR i#
 +FOR j#:=1 TO n# DO PRINT rhs(j#,1);
 +END 
 +
 +The Quartersolve algorithm is presented here as a COMAL 2.0 procedure
 +"slv" COMAL is pretty much a dead language and I don't expect anyone
 +to run this code.  However, COMAL is a structured algorithmic language
 +that is easy to read.  You can readily translate it into the programming
 +language of your choice.  Slv is coded as a CLOSED procedure as a
 +personal matter of style.  An open procedure would execute faster.  The
 +arrays are passed by REFERENCE and do not allocate additional local
 +storage.  The main program is just an example for testing.  It calls slv
 +n times to solve the linear system.  The test problem solves a scaled
 +Hilbert matrix which is ill conditioned.  In the absence of roundoff
 +error the solution should be a vector of ones.  I usually dimension a()
 +to n#*(n#+1)/4.  Slv is presented in its full generality, but you may
 +want to make some simplifications.
 +
 +Slv can handle multiple right hand side vectors with the two dimensional
 +array b(,) in most applications you will only use a single vector,
 +nr#=1, and you can make some simplifications by just using a one
 +dimensional array.
 +
 +Pivoting also complicates the code.  Problems which are positive
 +definite, or  diagonally dominant, or sometimes just well conditioned
 +can be safely solved without pivoting.  Stripping out the pivoting code
 +is straight forward and will shorten the code and speed execution.
 +
 +Anything following // is a comment and can be deleted from your running
 +code.
 +
 +In COMAL 2.0 you can also "protect" the code which will strip out
 +comments and other information to make a shorter running version.
 +
 +The remaining discussion will concern COMAL 2.0 and the BLAS.
 +
 +COMAL 2.0
 +
 +COMAL 2.0 is an excellent programming language for the C64/128 and I
 +can't describe all of its virtues here.  It has one serious limitation.
 +It does not use "continuation" lines, so line length is limited.  This
 +is most restrictive in function and procedure lines where it limits the
 +number of parameters that can be passed.  Line length is limited to 80
 +characters.  However, if you use a separate text editor or word
 +processor you can enter 120 character lines.  Comal will actually
 +execute tokenized lines up to 256 characters so the limitation is really
 +in the editor rather than COMAL.  Procedure and variable names can be
 +quite long in Comal, but are kept short because of the line length
 +limitation.  "Quartersolve" was shortened to "slv" for this reason.
 +
 +a:+t is a shorter faster version a:=a+t, and a:-t is a shorter faster
 +version of a:=a-t.  This is most usefull when "a" is an array element or
 +an integer.
 +
 +Comal 2.0 supports ML packages.  A package is a collection of functions
 +or procedures that can be called and executed.  A packaged can be ROMMED
 +and stored in EPROM on the Comal 2.0 cartridge.  A package can also be
 +loaded from disk and will normally be stored in a RAM location that
 +COMAL does not use for normal programs.  LINK "filename" will load and
 +link the ML package to a Comal program.  It will stay attached to the
 +program when the program is saved and loaded, unless it is marked as
 +ROMMED.  The entire slv procedure could be coded in assembly language
 +and be placed in a package.  The slv procedure uses two packages,
 +strings and blas.  The command USE packagename makes all of the
 +functions and procedures of the package known.  Alternatively, you could
 +place the USE packagename command in the main program and put IMPORT
 +procedurename inside all of the closed procedures that call
 +procedurename.
 +
 +Slv calls the swap'real and swap'integer proocedures from the strings
 +package.  The strings package is a ROMMED package on the Super Chip ROM.
 +
 +It does exactly what it says, e.g.  swap'real(a,b) is the same as:
 +t:=a; a:=b; b:=t.
 +
 +Slv calls the sdot, isamax#, sswap, sscal, saxpy, and scopy routines
 +from the blas package.  The blas package is LINKed to the program, but
 +it could, and should, be placed on EPROM.
 +
 +Basic Linear Algebra Subroutines, BLAS
 +
 +The BLAS were originally written for the Fortran language to speed
 +execution and streamline code used for solving linear algebra and other
 +matrix problems.  The LINPACK routines, Ref. 3, use the BLAS and are
 +perhaps the best known.  The idea is that the BLAS routines will be
 +highly optimized for a particular computer, coded in ML or a High Order
 +Language.  Some operating systems even include BLAS like routines.
 +Writing fast efficient programs is then a simple matter of selecting the
 +best solution algorithm and coding it in a manner that makes best use of
 +the blas routines.  There are blas routines for single precision, double
 +precision, and complex numbers.  The level 1 BLAS perform operations on
 +rows or columns of an array and typicaly do n scalar operations
 +replacing the inner most loop of code.  There are also level 2 BLAS that
 +perform n*n operations and Level 3 BLAS that perform n*n*n operations.
 +Nicholas Higham has coded most of the single precision level 1 blas
 +routines and put them in a Comal 2.0 package.  The Comal blas package is
 +included on the Packages Library Volume 2 disk.  I am not aware of ML
 +blas routines coded for any other C64/128 languages although this is
 +certainly possible and recommended.
 +
 +The Comal blas routines behave exactly the same way that the Fortran
 +blas routines do except that Fortran can pass the starting address of an
 +array with just "a", while Comal requires "a(1)" The Comal blas will
 +allow you pass an array, by reference, of single or multiple dimensions
 +and start from any position in the array.  If you code the blas routines
 +as ordinary Comal routines you have to pass additional parameters and
 +have separate routines for single dimensioned arrays and two dimensional
 +arrays.  Note also that Fortran stores two dimensional arrays by
 +columns, and Comal (like many other languages) stores two dimensional
 +arrays by rows.  If you translate code between Fortran and Comal using
 +blas routines you will have to change the increment variables.
 +
 +            Fortran                          Comal
 +    dimension c(n), a(ilda,isda)     DIM c(n#), a(lda#,sda#)
 +    scopy(n,c,1,a(i,1),ilda)         scopy(n#,c(1),1,a(i#,1),1)
 +    scopy(n,c,1,a(1,j),1)            scopy(n#,c(1),1,a(1,j#),sda#)
 +
 +The first scopy copies array c into the ith row of array a.  The second
 +scopy copies array c into the jth column of array a.
 +
 +This is what scopy does in Fortran:
 +
 +    subroutine scopy(n,sx,incx,sy,incy)
 +    real sx(1),sy(1)
 +    ix=1
 +    iy=1
 +    do 10 i = 1,n
 +      sy(iy) = sx(ix)
 +      ix = ix + incx
 +      iy = iy + incy
 + 10 continue
 +    return
 +    end
 +
 +The Comal BLAS does exactly the same thing.  If coded entirely in COMAL
 +rather than as a package it would have to be different.  The call would
 +change.
 +
 +scopy(n#,c(1),1,a(1,j#),sda#) would have to become,
 +scopy(n#,c(),1,1,a(,),1,j#,sda#,sda#) and the Comal procedure might be:
 +
 +PROC scopy(n#, REF x(), ix#, incx#, REF y(,), iy#, jy#, sdy#, incy#) CLOSED
 +  iyinc#:=incy# DIV sdy#  //assuming y is dimensioned y(?,sdy#)
 +  jyinc#:=incy# MOD sdy#
 +  FOR i#=1 TO n# DO
 +    y(iy#,jy#):=x(ix#)
 +    ix#:+incx#; iy#:+iyinc#; jy#:+jyinc#
 +  ENDFOR
 +ENDPROC scopy
 +
 +Note that more information has to be passed to the procedure and used
 +that the ML blas picks up automatically.  Also we would need separate
 +procedures to handle every combination of single and multi dimensional
 +arrays.  The Comal ML blas are indeed wonderful.  For speed
 +considerations this should also be left as an open procedure or better
 +yet just use in line code.
 +
 +Here is a very simplified description of what each of the routines in
 +the Comal BLAS package does.
 +
 +sum:=sasum(n#,x(1),1)  Returns sum of absolute values in x().
 +  sum:=0
 +  FOR i#:=1 TO n# DO sum:+ABS(x(i#))
 +
 +saxpy(n#,sa,x(1),1,y(1),1)  Add a multiple of x() to y().
 +  FOR i#:=1 TO n# DO y(i#):+sa*x(i#)
 +
 +prod:=sdot(n#,x(1),1,y(1),1)  Returns dot product of x() and y().
 +  prod:=0
 +  FOR i#:=1 TO n# DO prod:+x(i#)*y(i#)
 +
 +sswap(n#,x(1),1,y(1),1)  Swaps x() and y().
 +  FOR i#:=1 TO n# DO t:=x(i#); x(i#):=y(i#); y(i#):=t
 +
 +scopy(n#,x(1),1,y(1),1)  Copy x() to y().
 +  For i#:=1 TO n# DO y(i#):=x(i#)
 +
 +max#:=isamax#(n,x(1),1)  Returns index of the element of x() with the
 +                         largest absolute value.
 +  t:=0; max#:=1
 +  FOR i#:=1 TO n#
 +    IF ABS(x(i#))>t THEN t:=ABS(x(i#)); max#:=i#
 +  ENDFOR i#
 +
 +sscal(n#,sa,x(1),1)  Scale x() by a constant sa.
 +  FOR i#:=1 TO n# DO x(i#):=sa*x(i#)
 +
 +snrm2(n#,x(1),1)  Returns the 2 norm of x().
 +  norm2:=0
 +  FOR i#:=1 TO n# DO norm2:+x(i#)*x(i#)
 +  norm2:=SQR(norm2)
 +
 +srot(n#,x(1),1,y(1),1,c,s)  Apply Givens rotation.
 +  FOR i#:=1 TO n# DO
 +    t:=c*x(i#) + s*y(i#)
 +    y(i#):=s*x(i#) + c*y(i#)
 +    x(i#):=t
 +  ENDFOR i#
 +
 +
 +Bear in mind that each of these simple examples can be more complex as
 +was given for scopy.  You now have enough information to write your own
 +BLAS routines in ML or the programming language of your choice, or to
 +expand the BLAS routine calls in slv to ordinary in line code.
 +
 +You can also apply the BLAS routines in creative ways besides just
 +operating on rows or columns.  For example you could create the identity
 +matrix with:
 +
 +  DIM a(n#,n#)
 +  a(1,1):=1; a(1,2):=0
 +  scopy(n#*n#-2,a(1,2),0,a(1,3),1) // zero the rest of the matrix
 +  scopy(n#-1,a(1,1),0,a(2,2),n#+1) // copy ones to the diagonal.
 +
 +References
 +
 +1.  Zambardino, R. A., "Solutions of Systems of Linear Equations with
 +Partial Pivoting and Reduced Storage Requirements", The Computer Journal
 +Vol. 17, No. 4, 1974, pp. 377-378.
 +
 +2.  Orden A., "Matrix Inversion and Related Topics by Direct Methods",
 +in Mathematical Methods for Digital Computers, Vol. 1, Edited by A.
 +Ralston and H. Wilf, John Wiley and Sons Inc.,  1960.
 +
 +3.  Dongarra, J. J., Moeler, C. B., Bunch, J. R., Stewart, G. W.,
 +Linpack Users' Guide, SIAM Press, Philadelphia, 1979.
 +
 +========================================================================
 +</code>
 +====== The World of IRC - A New Life for the C64/128 ======
 +<code>
 +by Bill Lueck (coolhand on IRC)
 +
 +1)  Introduction
 +
 +With the mysterious and magnificent world of the Internet growing
 +at an astounding rate - like doubling every year - readers of this
 +magazine should find that the Internet is actually available to them now -
 +or at least very soon.  In fact, most readers of C= Hacking probably
 +get there copies of this magazine on the Internet.
 +
 +The Internet is not simple.  It has complexities and intricacies that
 +can baffle the most erudite and experienced computer scientists in the
 +world.  But, for the purposes of this article, maybe you can just accept
 +that the Internet is a worldwide connection of data lines that let
 +computers all over the world talk to each other.. and more importantly,
 +that allow the PEOPLE using the computers all over the world to talk to
 +other computers.. and to talk to other PEOPLE!  Here, then, lies the
 +foundation for IRC: it is the mechanism on the Internet that allows
 +PEOPLE to talk to other PEOPLE.
 +
 +2)  Getting on the Net
 +
 +If you obtained this magazine via the Internet, then you have passed
 +Step 1 (finding a site)!  If you do not have access to the Internet
 +(and have not tried), then you need to look around.  Possible sites may
 +be a college/university, your employer (use with care), or a commercial
 +provider.
 +
 +If you are enrolled in college, then you probably have an account, or
 +you may be entitled to one, with no or little cost.  The policies on
 +student accounts vary a lot from institution to institution, and from
 +country to country.  But check into it.. it is one of the most common
 +methods of Internet access.
 +
 +If you are employed, and your company has access to the Internet, it
 +may be possible for you to use their facilities.  Just a word of
 +caution - make sure that it is ok with your employer to use his
 +facilities... and not on "company time".
 +
 +Another way that is becoming increasingly more common is to use
 +commercial "Internet providers" These are companies whose sole
 +purpose is to offer you an "account" and give you access to the
 +Internet.  The cost, time on line, storage, access, etc., can vary
 +greatly..  you must shop around a bit.. if you have this choice at
 +all.. for the best deal.
 +
 +These commercial sites are not always easy to find.  There may be
 +several commercial providers in an area, but, strangely, they tend not
 +to advertise.  Word-of-mouth through friends, BBSs, or User Groups seem
 +to be the best way to locate the site possibilities.  But they CAN
 +provide a very good solution.
 +
 +Another variation on commercial sites are national companies such as
 +Compuserve, Genie, America Online and Delphi.  They provide varying degrees of
 +access.. and possibly at somewhat higher costs than local providers.
 +But, again, it is another option.
 +
 +There is MUCH to do on the Internet, once you have access to it: telnet,
 +ftp, usenet, archie, gopher, www...  These may be just names to you
 +now..  but the are all fascinating parts of the Internet.  But this
 +article is intended as an introduction to IRC - a fabulous Internet
 +resource which allows users who have access to a client program called
 +IRCII (most often invoked as "irc") to talk to each other (and often to
 +exchange files) in world-wide conversational "channels" (like "party
 +lines", often called "rooms" on some BBSs).  Why is this important to
 +readers of this magazine?  Well, there is a channel for c64/128 users on
 +IRC called #c-64, a place where c64/128 users are able to meet and
 +exchange all sorts of information, opinions, and files.  More on this
 +later.
 +
 +3)  The IRC Client
 +
 +First, to use IRC it is necessary to have access to an IRC client.  A
 +client is a program, usually available on your local site, which
 +actually interprets and responds to your commands, accepts your typing,
 +and shows you the conversation on the channel(s) you have joined.
 +
 +The most common way to access IRC from a site is to use the IRCII
 +client that your site makes available.  This is most often done by
 +simply typing "irc" at your prompt or invoking the irc option from your
 +menu if you don't have a shell account.  The first thing you will
 +notice is that your client is attempting to connect to a "server" A
 +server is a special program, run only on certain sites, that actually
 +provides the backbone of the IRC network.
 +
 +Most sites have several servers pre-defined.  You should see the client
 +trying one or more servers until it connects with one.
 +
 +With Unix irc clients you can define your own unique set of servers by
 +starting IRC with:
 +
 +irc nick server1 server2 ... serverN
 +
 +where "serverX" is the alpha or numeric IP address of the server.  This
 +will automatically set your irc nick (handle) and will establish a
 +series of servers that your client will switch to if your connection to
 +IRC gets broken (or if a server is not available when you invoke
 +"irc").
 +
 +What is an IP address, you ask?  Well, a basic premise of the Internet
 +is that each computer on the net (at all sites) has a UNIQUE address -
 +a computer code - that allows other computers to send specific data
 +just to that computer.  In that way, computers can make sure that the
 +messages and data files that they want (and YOU want) to send to
 +certain places get to their proper destinations.
 +
 +IP addresses may be used in an alphabetic or numeric form.  In most
 +cases they can be used interchangeably.  So, all irc servers have a
 +unique alphabetic (and an equivalent numeric) IP address.
 +
 +Once an IRC session is in progress, Unix users can change servers by
 +typing:/server newserver where "newserver" is as above, the alpha or
 +numeric IP address of the server you want to switch to.  More on servers
 +later; but just to mention few now:  irc.indiana.edu (midwest);
 +irc.virginia.edu (east); irc.ctr.columbia.edu (east); irc.math.byu.edu
 +(west); irc.colorado.edu (midwest); irc.texas.net (southwest).  There
 +are dozens more.  Just ask someone on IRC...or do a few  /whois nick
 +commands.  You will spot many more.
 +
 +If your site does not have an irc client, it should be possible to
 +install one yourself.  This means that you need to ftp the source code
 +for an irc client to your account on your site, make some usually minor
 +edits, then compile the code in your home directory or a subdirectory
 +below it.
 +
 +One good site for obtaining the necessary irc client code is
 +cs.bu.edu.  cd to /irc/clients.  Unix users  will find the IRCII client
 +source code in two forms:  IRC2.2.9.tar.Z (Unix tar and compress at
 +471k) or IRC2.2.9.tar.gz (Unix tar and GNU compress at 306k).  Both
 +files are the same (except for the compression).  Be sure to use
 +"BINARY" mode for the ftp transfer.
 +
 +Move the file to its own subdirectory if you have not ftp'd it to one
 +already.  Then uncompress and untar the file.  You should now find a
 +small subdirectory tree of files.  Be sure and read the INSTALL file in
 +the top subdirectory.
 +
 +Also in the main subdirectory, there should be two files that need
 +editing to make the client work with you site.  One is "Makefile" In
 +it there are at least two edits.  Make INSTALL_EXECUTABLE the path name
 +that u want the executable to reside in.  This is most often your home
 +directory or the "bin" subdirectory under your home directory.  The
 +other is IRCII_LIBRARY.  Set this to the top subdirectory where the
 +IRCII code resides.  You also must read through the computer system
 +options and set them for the type of computer and Operating System that
 +your site uses.
 +
 +The other file is "config.h" Change the #define DEFAULT_SERVER line
 +to the alpha or numeric addy of your primary default server.  Be sure
 +to enclose the server in quotes ("server").
 +
 +For VMS users, there is a subdirectory in "clients" named vms.  cd to
 +it.  There are two versions - irc176  and ircII-for-vms.  The first is
 +a more native VMS version, the second is a Unix-like version.  They are
 +both executables, and should run on VMS systems.  Try both.. see which
 +you like best.
 +
 +Another fairly new area of IRC clients is the personal client, running
 +on your own computer which would be connected to the Internet through a
 +version of SLIP or PPP, protocols that move much of the overhead of a
 +normal Internet provider down to your own machine.  There are IRC clients
 +available for the PC, Amiga, MAC... and even the rumor of one to be
 +produced for the c64/128.  This type of client is expanding very
 +rapidly and will be a significant option for an ever increasing number
 +of Internet users.
 +
 +If you have Telnet only access from your site, there are some sites
 +which offer a "public" irc client, ones which you can use without
 +having an account at that site.. sorta like anonymous ftp for those of
 +you who know what that is.  There are drawbacks, though.  There are not
 +many of these public clients, they are often slow in response time, you
 +cannot exchange files with other users (DCC), and many of the sites are
 +not always up.  Still, it is one possibility that might work for
 +certain situations.  Actually, it is the way that I started on IRC and
 +used it for several months (my site did not have a local client, and I
 +did not know how to install one myself).
 +
 +The public IRC sites I know about now are tiger.itc.univie.ac.at 6668,
 +sci.dixie.edu 6667, irc.nsysu.edu.tw, and irc.demon.co.uk.  They are
 +not available from all sites, and usage is limited.  But try them if
 +you need to.
 +
 +Another variation of the "public" options is to apply for a free Unix
 +account at nyx.cs.du.edu.  You will have to be validated, which involves
 +a little paperwork.  But once completed, you will have a FREE Unix
 +account with full IRC privileges, including DCC file exchange.  Of
 +course, you need a "local" account somewhere with telnet and ftp
 +privileges, but this is often easier to obtain than an account with all
 +options locally.
 +
 +4)  Basics of IRC
 +
 +Well, hopefully, you will now have an Internet site with a method of
 +accessing IRC.  Next, we want to give some tips on using and enjoying
 +IRC and introduce DCC, the command for transferring files between people
 +on IRC... and between "bots" and people.
 +
 +A "bot", you say?  Some of you may laugh; sure of course, a bot.  What
 +else is new?  But... I remember that it was ages before I finally
 +figured out.. or someone gave me a clue.. as to what a "bot" really was.
 +Before we go on, let me give you a VERY brief description of a bot.  We
 +can say that a bot may be a "script", a series of IRC language
 +statements understood by your IRC client; or it may be a separate
 +program (typically written in "C"); which, in either case, runs without
 +any help from its "owner" - YOU.
 +
 +Instead, a bot is intended to respond to others on IRC who "talk" to it
 +by "/msg", "/dcc chat", or even "on-channel" commands like "!list" or
 +"&help" One bot even lists the c64 files it has on-line in response to
 +someone typing "load "$",8".
 +
 +What a bot does and how you command it varies a LOT.  There really is no
 +standard way to talk to a bot.  Try "/msg <botname>" help as a starting
 +point and see what happens.  Most often there will be instructions that
 +tell you what to do next.  Experiment a little - you will get the hang
 +of it.
 +
 +Back to the main plot.  The first thing to do after you get connected to
 +IRC is to choose a "nick" This is the handle that you will be known by
 +and talked to on IRC.  Do this by typing:
 +
 +/nick <nick>
 +
 +It's your choice.. unless someone else is already using it.  IRC does
 +not let two people use the same nick at the same time.  It will tell you
 +about this if you try - sometimes in a rather active way - like "kick"
 +you off.  Don't worry - just reconnect.. but try a different nick.  Try
 +just changing the nick a little - like even putting a "1" or "2" behind
 +it.
 +
 +Any number of people, however, can use the same nick at different times.
 +This CAN cause a little confusion.. make sure you know you are talking
 +to who you think you are.. check a nick's whole address with:
 +
 +/whois <nick>
 +
 +Next, you will want to join a channel.  Do this by typing:
 +
 +/join #<channel>
 +
 +A channel is a logical connection of all IRC users anywhere in the world
 +that have typed the same /join command.  All lines typed to the channel
 +by anyone on the channel are spread by IRC to all other people who are
 +on the channel.  This is the real power of IRC...  a world-wide
 +"conference" or "party line", where people with the same interests can
 +communicate with each other.
 +
 +Because of different delays in different parts of the Internet, all the
 +lines typed by everyone will not always appear at the same time or even
 +in the same order at everyone's terminal.  This usually does not cause
 +much of a problem - just be aware that it happens.
 +
 +If the channel name does not exist at the time you type /join, it will
 +be created for you!  Yes, anyone can "create" a channel.  But #c-64 is
 +almost always there.  Give it a try!
 +
 +After you get on a channel, you can type:
 +
 +/who *
 +
 +This will give you a list of who (which nicks) is on the channel and
 +what their home sites are.  This address may or may not be the correct
 +email address for the nick - so check with the person first (perhaps a
 +"/msg <nick>" - see below) if you want to email him.
 +
 +As mentioned before, normal channel conversation is seen by everyone who
 +has joined the channel.  This is great most of the time.  Occasionally,
 +though, you may want to tell just one person (or bot) something that the
 +entire channel would not want to hear.  In this case, use the command:
 +
 +/msg <nick> <message>
 +
 +Type it on a line of its own, and just <nick> will see your <message>.
 +Quite handy for the more "personal" or "specialized" conversations.
 +Careful, though... use the wrong <nick> or leave out the "/" and people
 +other than you intended will see your <message>.
 +
 +If you find you are doing a lot of /msg's to the same nick, try:
 +
 +/query <nick>
 +
 +This will put you in a sort of 'permanent' /msg <nick> mode, so that
 +everything you type that would normally go to the channel will not act
 +like a "/msg <nick>" preceded it, and it will go just to <nick> Type
 +just "/query" to cancel this mode.
 +
 +Let's jump, now, to /dcc, the command that allows most IRC users to
 +transfer files.  DCC stands for "Direct Client to Client" What it does
 +is allow two nicks to transfer files *directly* between their sites, not
 +going through either of their servers.  One of the nicks can even be a
 +bot; IRC does not make a distinction.
 +
 +When two nicks exchange files, the sender must always start by typing:
 +
 +/dcc send <nick> <filename>
 +
 +The recipient will get a message telling what file is being offered and must
 +type:
 +
 +/dcc get <nick> [filename]
 +
 +The [filename] is optional, but must be used if more than one file is to
 +be transferred simultaneously.  Yes, simultaneous transfer of multiple
 +files CAN be done.  Many people do not realize this.  Just use the
 +[filename] option with the "/dcc get" command.
 +
 +The files that you send and the files that you receive with DCC are
 +always in the directory you are in when you start IRC.  You can type "/cd"
 +to see what that directory is and you can type:
 +
 +/cd <pathname>
 +
 +to change that directory.  Or, you can give the absolute or relative
 +pathname of the file you want to send if it is not in your "local"
 +directory.
 +
 +There are often a couple of bots on #c-64 that can give you c-64 files.
 +"coolhand" is partly a script bot that currently has a lot of c-64 files
 +available for DCC.  If coolhand is on IRC, type:
 +
 +/msg coolhand xdcc list
 +
 +to see a list of lists (of files).  To see the individual files on list n,
 +type:
 +
 +/msg coolhand xdcc list #n
 +
 +To have coolhand's script dcc you file #n, type:
 +
 +/msg coolhand xdcc send #n   
 +
 +followed by:
 +
 +/dcc get coolhand
 +
 +when you get the dcc offer message.
 +
 +There are many scripts that you can use that will autoget a file that is
 +DCC'd to you.  The xdcc script that coolhand uses is one such script.
 +(Yes, coolhand will also autoget a file that you send to it.)
 +
 +5)  What/Who is on IRC?
 +
 +Ok, now you are on IRC.  So what will you find?  Who is on the #c-64?
 +The answers are quite varied..  and constantly changing.  I personally
 +have been on IRC for over 2 years.. (or is it 3?)  And I have yet to
 +ascertain an absolute pattern of people or topics.  Frustrating?  Well,
 +maybe to some.  But interesting?  Yes, most certainly.  IRC, and the
 +#c-64 channel, is a microcosm of the world, with all its variety of
 +people, personalities, projects, propaganda, and priorities.  It is a
 +capability, a tool for communications, that is unexcelled in its scope
 +and possibilities.
 +
 +IRC is totally international, and so is the #c-64 channel.  Besides the
 +U.S. and Canada, Europe is very well represented.  There is also a
 +smaller but increasingly active contingent in Australia, as the net
 +becomes more accessible there.  You will also find a few c-64 users in
 +S. America, Africa, and Asia.  Russia and other former Soviet Union
 +countries also have a presence.  English is the accepted language for
 +use on #c-64, although you will occasionally see a few other language
 +used for brief times.
 +
 +What is the channel used for?  Just about anything you can imagine that
 +normal conversation would be used for.  With a special emphasis for the
 +special interest of most channel participants - the c64/128.  For the
 +most part, almost everyone on the channel has had or still has a c64 or
 +c128.  Some are active users on a real c64/128, while others use one of
 +the several emulators that exist for various platforms.  Many former and
 +current 64 "scene" members are finding their way to the channel, but all
 +members of the c64 community are always welcome, and all are treated
 +equally.
 +
 +Many people find IRC and #c-64 a very useful way to exchange information
 +quickly without having to wait for email to pass back and forth.  As was
 +mentioned before, the DCC capability allows for immediate transfer of
 +files, another quick and effective way to pass information and things
 +
 +like utilities and coding examples.  Such capabilities have encouraged
 +many people to either return to the c64 or take up using and programming
 +it for the first time.  Yes, the c64 community is actually growing
 +again, thanks in part to the growing presence of the Internet, IRC, and
 +#c-64!
 +
 +So, when you first get on the channel for the first time, don't be
 +afraid to ask for help.  You will probably find that the people on there
 +are either new themselves, or were once new at one time and had the same
 +uncertainties and questions that you do.  Most everyone is very willing
 +to help new people.  So ask.  Also, if you have knowledge or a talent to
 +offer or a willingness to help somehow, just make that known.  The
 +channel is full of people, some of whom probably need exactly what you
 +have to give.
 +
 +A key thing:  be patient!  When you are new on the channel, you may not
 +be noticed right away, especially if there are several conversations
 +already going on.  In other cases, you may find that there is really no
 +one on the channel, except maybe a few bots.  So hang in there or come
 +back a bit later.  Believe me, the IS a lot of action on #c-64 most of
 +the time.
 +
 +Besides being patient yourself, be patient with other people on the
 +channel.  Like in the non-cyber world, misunderstandings CAN occur, since
 +your total communication with other people is via the typed word.  But
 +the same rules of courteousness that common society utilizes also apply
 +on IRC.  Treat people with respect and kindness, and they will most
 +likely respond in a like manner.  Sounds like the golden rule?  I think
 +so, and I think you will find that its works pretty well on IRC as it
 +does in other life.
 +
 +Hopefully, this article will help you get started enjoying IRC and
 +particularly #c-64.  There's a lot to be gained there... information,
 +files, and even new friends.  It's a way to give our c64 community new
 +life and spirit.  Give it a try!  See you there.
 +
 +*****************
 +
 +Some of the material in this article was previously published in "Driven"
 +and is used here by permission.
 +
 +========================================================================
 +</code>
 +====== SwiftLink-232 Application Notes (version 1.0b) ======
 +<code>
 +
 +This information is made available from a paper document published by CMD,
 +with CMD's express written permission.  [This version includes a couple of
 +grammatical corrections and minor changes, plus, the source code has been
 +debugged and extended by Craig Bruce <csbruce@ccnga.uwaterloo.ca>.]
 +
 +1. INTRODUCTION
 +
 +The SwiftLink-232 ACIA cartridge replaces the Commodore Kernal RS-232 routines
 +with a hardware chip.  The chip handles all the bit-level processing now done
 +in software by the Commodore Kernal.  The ACIA may be accessed by polling
 +certain memory locations in the I/O block ($D000 - $DFFF) or through
 +interrupts.  The ACIA may be programmed to generate interrupts in the
 +following situations:
 +
 +1) when a byte of data is received
 +2) when a byte of data may be transmitted (i.e., the data register is empty)
 +3) both (1) and (2)
 +4) never
 +
 +The sample code below sets up the ACIA to generate an interrupt each time a
 +byte of data is received.  For transmitting, two techniques are shown.  The
 +first technique consists of an interrupt handler which enables transmit
 +interrupts when there are bytes ready to be sent from a transmit buffer.
 +There is a separate routine given that manages the transmit buffer.  In the
 +second technique, which can be found at the very end of the sample code,
 +neither a transmit buffer or transmit interrupts are used.  Instead, bytes of
 +data are sent to the ACIA directly as they are generated by the terminal
 +program.
 +
 +NOTE: The ACIA will _always_ generate an interrupt when a change of state
 +occurs on either the DCD or DSR line (unless the lines are not connected in
 +the device's cable).
 +
 +The 6551 ACIA was chosen for several reasons, including the low cost and
 +compatibility with other Commodore (MOS) integrated circuits.  Commodore used
 +the 6551 as a model for the Kernal software.  Control, Command, and Status
 +registers in the Kernal routines partially mimic their hardware counterparts
 +in the ACIA.
 +
 +NOTE: If you're using the Kernal software registers in your program, be sure
 +to review the enclosed 6551 data sheet carefully.  Several of the hardware-
 +register locations do _not_ perform the same function as their software
 +counterparts.  You may need to make a few changes in your program to
 +accommodate the differences.
 +
 +2. BUFFERS
 +
 +Bytes received are placed in "circular" or "ring" buffers by the sample
 +routine below, and also by the first sample transmit routine.  To keep things
 +similar to the Kernal RS-232 implementation, we've shown 256-byte buffers.
 +You may want to use larger buffers for file transfers or to allow more
 +screen-processing time.  Bypassing the Kernal routines free many zero-page
 +locations, which could improve performance of pointers to large buffers.
 +
 +If your program already directly manipulates the Kernal RS-232 buffers, you'll
 +find it very easy to adapt to the ACIA.  If you use calls to the Kernal RS-232
 +file routines instead, you'll need to implement lower-level code to get and
 +store buffer data.
 +
 +Briefly, each buffer has a "head" and "tail" pointer.  The head points to the
 +next byte to be removed from the buffer.  The tail points to the next free
 +location in which to store a byte.  If the head and tail both point to the
 +same location, the buffer is empty.  If (tail+1)==head, the buffer is full.
 +
 +The interrupt handler described below will place received bytes at the tail of
 +the receive buffer.  Your program should monitor the buffer, either by
 +comparing the head and tail pointers (as the Commodore Kernal routines do), or
 +by maintaining a byte count through the interrupt handler (as the attached
 +sample does).  When bytes are available, your program can process them, move
 +the head pointer to the next character, and decrement the counter if you use
 +one.
 +
 +You should send a "Ctrl-S" (ASCII 19) to the host when the buffer is nearing
 +capacity.  At higher baud rates, this "maximum size" point may need to be
 +lowered.  We found 50 to 100 bytes worked fairly well at 9600 baud.  You can
 +probably do things more efficiently (we were using a _very_ rough
 +implementation) and set a higher maximum size.  At some "maximum size", a
 +"Ctrl-Q" (ASCII 17) can be sent to the host to resume transmission.
 +
 +To transmit a byte using the logic of the first transmit routine below, first
 +make sure that the transmit buffer isn't full.  Then store the byte at the
 +tail of the transmit buffer, point the tail to the next available location,
 +and increment the transmit buffer counter (if used).
 +
 +The 6551 transmit interrupt occurs when the transmit register is empty and
 +available for transmitting another byte.  Unless there are bytes to transmit,
 +this creates unnecessary interrupts and wastes a lot of time.  So, when the
 +last byte is removed from the buffer, the interrupt handler in the first
 +transmit routine below disables transmit interrupts.
 +
 +Your program's code that stuffs new bytes into the transmit buffer must
 +re-enable transmit interrupts, or your bytes may never be sent.  A model for a
 +main code routine for placing bytes into the transmit buffer follows the
 +sample interrupt handler.
 +
 +Using a transmit buffer allows  your main program to perform other takes while
 +the NMI interrupt routine takes care of sending bytes to the ACIA.  If the
 +buffer has more than a few characters, however, you may find that most of the
 +processor time is spent servicing the interrupt.  Since the ACIA generates NMI
 +interrupts, you can't "mask" them from the processor, and you may have timing
 +difficulties in your program.
 +
 +One solution is to eliminate the transmit buffer completely.  Your program can
 +decide when to send each byte and perform any other necessary tasks in between
 +bytes as needed.  A model for the main-code routine for transmitting bytes
 +without a transmit buffer is also shown following the sample interrupt-handler
 +code.  Feedback from developers to date is that many of them have better luck
 +_not_ using transmit interrupts or a transmit buffer.
 +
 +Although it's possible to eliminate the receive buffer also, we strongly
 +advise that you don't.  The host computer, not your program, decides when
 +a new byte will arrive.  Polling the ACIA for received bytes instead of
 +using an interrupt-driven buffer just waste's your program's time and
 +risks missing data.
 +
 +For a thorough discussion of the use of buffers, the Kernal RS-232 routines,
 +and the Commodore NMI handler, see "COMPUTE!'s VIC-20 and Commodore 64 Tool
 +Kit: Kernal", by Dan Heeb (COMPUTE! Books) and "What's Really Inside the
 +Commodore 64", by Milton Bathurst (distributed in the US by Schnedler
 +Systems).
 +
 +3. ACIA REGISTERS
 +
 +The four ACIA registers are explained in detail in the enclosed data sheets.
 +The default location for them in our cartridge is address $DE00--$DE03
 +(56832--56836).
 +
 +3.1. DATA REGISTER ($DE00)
 +
 +This register has dual functionality: it is used to receive and transmit all
 +data bytes (i.e., it is a read/write register).
 +
 +Data received by the ACIA is placed in this register.  If receive interrupts
 +are enabled, an interrupt will be generated when all bits for a received
 +byte have been assembled and the byte is ready to read.
 +
 +Transmit interrupts, if enabled, are generated when this register is empty
 +(available for transmitting).  A byte to be transmitted can be placed in this
 +register.
 +
 +3.2. STATUS REGISTER ($DE01)
 +
 +This register has dual functionality: it shows the various ACIA settings when
 +read, but when written to (data = anything [i.e., don't care]), this register
 +triggers a reset of the chip.
 +
 +As the enclosed data sheet shows, the ACIA uses bits in this register to
 +indicate data flow and errors.
 +
 +If the ACIA generates an interrupt, bit #7 is set.  There are four possible
 +sources of interrupts:
 +
 +1) receive (if programmed)
 +2) transmit (if programmed)
 +3) if a connected device changes the state of the DCD line
 +4) if a connected device changes the state of the DSR line
 +
 +Some programmers have reported problems with using bit #7 to verify ACIA
 +interrupts.  At 9600 bps and higher, the ACIA generates interrupts properly,
 +and bits #3--#6 (described below) are set to reflect the cause of the
 +interrupt, as they should.  But, bit #7 is not consistently set.  At speeds
 +under 9600 bps, bit #7 seems to work as designed.  To avoid any difficulties,
 +the sample code below ignores bit #7 and tests the four interrupt source bits
 +directly.
 +
 +Bit #5 indicates the status of the DSR line connected to the RS-232 device
 +(modem, printer, etc.), while bit #6 indicates the status of the DCD line.
 +NOTE: The function of these two bits is _reversed_ from the standard
 +implementation.  Unlike many ACIAs, the 6551 was designed to use the DCD
 +(Data Carrier Detect) signal from the modem to activate the receiver section
 +of the chip.  If DCD is inactive (no carrier detected), the modem messages
 +and echos of commands would not appear on the user's screen.  We wanted the
 +receiver active at all times.  We also wanted to give the you access to the
 +DCD signal from the modem.  So, we exchanged the DCD and DSR signals at the
 +ACIA.  Both lines are pulled active internally by the cartridge if left
 +unconnected by the user (i.e., in an null-modem cable possibility).
 +
 +Bit #4 is set if the transmit register is empty.  Your program must monitor
 +this bit and not write to the data register until the bit i sset (see the
 +sample XMIT code below).
 +
 +Bit #3 is set if the receive register is full.
 +
 +Bits #2, #1, and #0, when set, indicate overrun, framing, and parity errors in
 +received bytes.  The next data byte received erases the error information for
 +the preceding byte.  If you wish to use these bits, store them for processing
 +by your program.  The sample code below does not implement any error checking,
 +but the Kernal software routines do, so adding features to your code might be
 +a good idea.
 +
 +3.3. COMMAND REGISTER ($DE02)
 +
 +The Command Register control parity checking, echo mode, and transmit/receive
 +interrupts.  It is a read/write register, but reading the register simply
 +tells you what the settings of the various parameters are.
 +
 +You use bits #7, #6, and #5 to choose the parity checking desired.
 +
 +Bit #4 should normally be cleared (i.e., no echo)
 +
 +Bits #3 and #2 should reflect whether or not you are using transmit
 +interrupts, and if so, what kind.  In the first sample transmit routine below,
 +bit #3 is set and bit #2 is cleared to disable transmit interrupts (with RTS
 +low [active]) on startup.  However, when a byte is placed in the transmit
 +buffer, bit #3 is cleared and bit #2 is set to enable transmit interrupts
 +(with RTS low).  When all bytes in the buffer have been transmitted, the
 +interrupt handler disables transmit interrupts.  NOTE: If you are connected to
 +a RS-232 device that uses CTS/RTS handshaking, you can tell the device to stop
 +temporarily by bringing RTS high (inactive): clear both bits #2 and #3.
 +
 +Bit #1 should reflect whether or not you are using receive interrupts.  In
 +the sample code below, it is set to enable receive interrupts.
 +
 +Bit #0 acts as a "master control switch" for all interrupts on the chip
 +itself.  It _must_ be set to enable any interrupts -- if it is cleared, all
 +interrupts are turned off and the receiver section of the chip is disabled.
 +This bit also pulls the DTR line low to enable communication with the
 +connected RS-232 device.  Clearing this bit causes most Hayes-compatible
 +modems to hang up (by bringing DTR high).  This bit should be cleared when a
 +session is over and the user exits the terminal program to insure that no
 +spurious interrupts are generated.  One fairly elegant way to do this is to
 +perform a software reset of the chip (writing any value to the Status
 +register).
 +
 +NOTE: In the figures on the 6551 data sheet, there are small charts at the
 +bottom of each of the labelled "Hardware Reset/Program Reset" These charts
 +indicate what values the bits of these registers contain after a hardware
 +reset (like toggling the computer's power) and a program reset (a write to the
 +Status register).
 +
 +3.4. CONTROL REGISTER ($DE03)
 +
 +You use this register to control the number of stop bits, the word length,
 +switch on the internal baud-rate generator, and set the baud rate.  It is a
 +read/write register, but reading the register simply tells you what the
 +various parameters are.  See the figure in the data sheet for a complete list
 +of parameters.
 +
 +Be sure that bit #4, the "clock source" bit, is always set to use the on-chip
 +crystal-controlled baud-rate generator.
 +
 +You use the other bits to choose the baud rate, word length, and number of
 +stop bits.  Note that our cartridge uses a double-speed crystal, so values
 +given on the data sheet are doubled [this is how they are shown below] (the
 +minimum speed is 100 bps and the maximum speed is 38,400 bps).
 +
 +4. ACIA HARDWARE INTERFACING
 +
 +The ACIA is mounted on a circuit board designed to plug into the expansion
 +(cartridge) port.  The board is housed in a cartridge shell with a male DB-9
 +connector at the rear.  The "IBM(R) PC/AT(TM) standard" DB-9 RS-232 pinout is
 +implemented.  Commercial DB-9 to DB-25 patch cords are readily available, and
 +are sold by us as well.
 +
 +Eight of the nine lines from the AT serial port are implemented: TxD, RxD,
 +DTR, DSR, RTS, CTS, DCD, & GND.  RI (Ring Indicator) is not implemented
 +because the 6551 does not have a pin to handle it.  CTS and RTS are not
 +normally used by 2400 bps or slower Hayes-compatible modems, but these lines
 +are being used by several newer, faster modems (MNP modems in particular).
 +Note that although CTS is connected to the 6551, there is no way to monitor
 +what state it is -- the value does not appear in any register.  The 6551
 +handles CTS automatically: if it is pulled high (inactive) by the connected
 +RS-232 device, the 6551 stops transmitting (clears the "transmit data register
 +empty" bit [#4] in the status register).
 +
 +The output signals are standard RS-232 level compatible.  We've tested units
 +with several commercial modems and with various computers using null-modem
 +cables up to 38,400 bps without difficulties.  In addition, there are pull-up
 +resistors on three of the four input lines (DCD, DSR, CTS) so that if these
 +pins are not connected in a cable, those three lines will pull to the active
 +state.  For example, if you happen to use a cable that is missing the DCD
 +line, the pull-up resistor will pull the line active, so that bit #6 in the
 +status register would be cleared (DCD is active low).
 +
 +An on-board crystal provides the baud rate clock signal, with a maximum of
 +38.4 Kbaud, because we are using a double-speed crystal.  If possible, test
 +your program at 38.4 Kbaud as well as lower baud rates.  Users may find this
 +helpful for local file transfers using the C-64/C-128 as a dumb terminal on
 +larger systems.  And, after all, low-cost 28.8 Kb modems for the masses are
 +just around the corner.
 +
 +Default decoding for the ACIA addresses is done by the I/O #1 line (pin 7) on
 +the cartridge port.  This line is infrequently used on either the C-64 or
 +C-128 and should allow compatibility with most other cartridge products,
 +including the REU.  The circuit board also has pads for users with special
 +needs to change the decoding to I/O #2 (pin 10).  This change moves the ACIA
 +base address to $DF00, making it incompatible with the REU.
 +
 +C-128 users may also elect to decode the ACIA at $D700 (this is a SID-chip
 +mirror on the C-64).  Since a $D700 decoding line is not available at the
 +expansion port, the user would need to run a clip lead into the computer and
 +connect to pin 12 of U2 (a 74LS138).  We have tried this and it works.  $D700
 +is an especially attractive location for C-128 BBS authors, because putting
 +the SwiftLink there will free up the other two memory slots for devices that
 +many BBS sysops use: IEEE and hard-drive interfaces.
 +
 +Although we anticipate relatively few people changing ACIA decoding, you
 +should allow your software to work with a SwiftLink at any of the three
 +locations.  You could either (1) test for the ACIA automatically by writing a
 +value to the control register and then attempting to read it back or (2)
 +provide a user-configurable switch/poke/menu option.
 +
 +The Z80 CPU used for CP/M mode in the C-128 is not connected to the NMI line,
 +which poses a problem since the cleanest software interface for C-64/C-128-
 +mode programming is with this interrupt.  We have added a switch to allow the
 +ACIA interrupt to be connected to either NMI or IRQ, which the Z80 does use.
 +The user can move this switch without opening the cartridge.
 +
 +5. SAMPLE CODE
 +
 +This section has been translated into ACE-assembler format.  Cut on the dotted
 +lines to extract the code, and assemble it using the ACE assembler (ACE is a
 +public-domain program).  This program will work on both the C64 and C128.
 +To use from BASIC:
 +
 +LOAD"SAMPLE",8,1
 +SYS8192
 +
 +It is a very simple terminal program.  Press the STOP key to exit from it.
 +
 +%%%---8<---cut-here---8<---%%%
 +;Sample NMI interrupt handler for 6551 ACIA on Commodore 64/128
 +
 +;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
 +;Geoduck Development Systems, and Dr. Evil Labs.
 +
 +;    ---=== EQUATES ===---
 +
 +base      =    $DE00    ;base ACIA address
 +data      =    base
 +status    =    base+1
 +command      base+2
 +control      base+3
 +
 +;Using the ACIA frees many addresses in zero page normally used by
 +;Kernel RS-232 routines.  The addresses for the buffer pointers were
 +;chosen arbitrarily.  The buffer vector addresses are those used by
 +;the Kernal routines.
 +
 +rhead       $A7      ;pointer to next byte to be removed from
 +                       ;receive buffer
 +rtail       $A8      ;pointer to location to store next byte received
 +rbuff       $F7      ;receive-buffer vector
 +
 +thead       $A9      ;pointer to next byte to be removed from
 +                       ;transmit buffer
 +ttail       $AA      ;pointer to location to store next byte
 +                       ;in transmit buffer
 +tbuff       $F9      ;transmit buffer
 +
 +xmitcount =   $AB      ;count of bytes remaining in transmit (xmit) buffer
 +recvcount =   $B4      ;count of bytes remaining in receive buffer
 +
 +errors    =   $B5      ;DSR, DCD, and received data errors information
 +
 +xmiton    =   $B6      ;storage location for model of command register
 +                       ;which turn both receive and transmit interrupts on
 +xmitoff     $BD      ;storage location for model of command register
 +                       ;which turns the receive interrupt on and the
 +                       ;transmit interrupts off
 +
 +NMINV       $0318    ;Commodore Non-Maskable Interrupt vector
 +OLDVEC    =   $03fe    ;innocuous location to store old NMI vector (two bytes)
 +
 +;    ---=== INITIALIZATION ===---
 +
 +;Call the following code as part of system initialization.
 +
 +;clear all buffer pointers, buffer counters, and errors location
 +
 +      org   $2000      ;change to suit your needs
 +      lda   #$00
 +      sta   rhead
 +      sta   rtail
 +      sta   thead
 +      sta   ttail
 +
 +      sta   xmitcount
 +      sta   recvcount
 +      sta   errors
 +
 +;store the addresses of the buffers in the zero-page vectors
 +
 +      lda   #<TRANSMIT_BUFFER
 +      sta   tbuff
 +      lda   #>TRANSMIT_BUFFER
 +      sta   tbuff + 1
 +
 +      lda   #<RECEIVE_BUFFER
 +      sta   rbuff
 +      lda   #>RECEIVE_BUFFER
 +      sta   rbuff + 1
 +
 +;the next four instructions initialize the ACIA to arbitrary values.
 +;These could be program defaults, or replaced by code that picks up
 +;the user's requirements for baud rate, parity, etc.
 +
 +;The ACIA "control" register controls stop bits, word length, the
 +;choice of internal or external baud-rate generator, and the baud
 +;rate when the internal generator is used.  The value below sets the
 +;ACIA for one stop bit, eight-bit word length, and 4800 baud using the
 +;internal generator.
 +;             .------------------------- 0 = one stop bit
 +;             :
 +;             :.-------------------- word length, bits 6-7
 +;             ::.------------------- 00 = eight-bit word
 +;             :::
 +;             :::.------------- clock source, 1 = internal generator
 +;             ::::
 +;             :::: .----- baud
 +;             :::: :.---- rate
 +;             :::: ::.--- bits   ;1010 == 4800 baud, change to what you want
 +;             :::: :::.-- 0-3
 +      lda   #%0001_1010
 +      sta   control
 +
 +;The ACIA "command" register controls the parity, echo mode, transmit and
 +;receive interrupt enabling, hardware "BRK", and (indirectly) the "RTS"
 +;and "DTR" lines.  The value below sets the ACIA for no parity check,
 +;no echo, disables transmit interrupts, and enables receive interrupts
 +;(RTS and DTR low).
 +;             .------------------------- parity control,
 +;             :.------------------------ bits 5-7
 +;             ::.----------------------- 000 = no parity
 +;             :::
 +;             :::.------------------- echo mode, 0 = normal (no echo)
 +;             ::::
 +;             :::: .----------- transmit interrupt control, bits 2-3
 +;             :::: :.---------- 10 = xmit interrupt off, RTS low
 +;             :::: ::
 +;             :::: ::.------ receive interrupt control, 0 = enabled
 +;             :::: :::
 +;             :::: :::.--- DTR control, 1=DTR low
 +      lda   #%0000_1001
 +      sta   command
 +
 +;Besides initialization, also call the following code whenever the user
 +;changes parity of echo mode.
 +;It creates the "xmitoff" and "xmiton" models used by the interrupt
 +;handler and main-program transmit routine to control the ACIA
 +;interrupt enabling.  If you don't change the models' parity bits,
 +;you'll revert to "default" parity on the next NMI.
 +
 +                        ;initialize with transmit interrupts off since
 +                        ;buffer will be empty
 +
 +      sta   xmitoff     ;store as a model for future use
 +      and   #%1111_0000 ;mask off interrupt bits, keep parity/echo bits
 +      ora   #%0000_0101 ;and set bits to enable both transmit and
 +                        ;receive interrupts
 +      sta   xmiton      ;store also for future use
 +
 +;The standard NMI routine tests th <RESTORE> key, CIA #2, and checks
 +;for the presence of an autostart cartridge.
 +
 +;You can safely bypass the normal routine unless:
 +;        you want to keep the user port active
 +;        you want to use the TOD clock in CIA #2
 +;        you want to detect an autostart cartridge
 +;        you want to detect the RESTOR key
 +;
 +;If you need any of these functions, you can wedge the ACIA
 +;interrupt handler in ahead of the Kernal routines.  It's probably
 +;safer to replicate in your own program only the Kernal NMI functions
 +;that you need.  We'll illustrate bypassing all Kernal tests.
 +
 +;BE SURE THE "NEWNMI" ROUTINE IS IN PLACE BEFORE EXITING THIS CODE!
 +;A "stray" NMI that occurs after the vector is changed to NEWNMI's address
 +;will probably cause a system crash if NEWNMI isn't there.  Also, it would
 +;be best to initialize the ACIA to a "no interrupts" state until the
 +;new vector is stored.  Although a power-on reset should disable all
 +;ACIA interrupts, it pays to be sure.
 +
 +;If the user turns the modem off and on, an interrupt will probably be
 +;generated.  At worst, this may leave a stray character in teh receive
 +;buffer, unless you don't have NEWNMI in place.
 +
 +NEWVEC:
 +      sei               ;A stray IRQ shouldn't cause any problems
 +                        ;while we're changing the NMI vector, but
 +                        ;why take chances?
 +
 +;If you want all the normal NMI tests to occur after the ACIA check,
 +;save the old vector.  If you don't need the regular stuff, you can
 +;skip the next four lines.  Note that the Kernal NMI routine pushes
 +;the CPU registers to the stack.  If you call it at the normal address,
 +;you should pop the registers first (see EXITINT below).
 +
 +      lda   NMINV      ;get low byte of present vector
 +      sta   OLDVEC     ;and store it for future use
 +      lda   NMINV+1    ;do the same
 +      sta   OLDVEC+1   ;with the high byte
 +
 +                       ;come here from the SEI if you're not saving
 +                       ;the old vector
 +      lda   #<NEWNMI   ;get low byte of new NMI routine
 +      sta   NMINV      ;store in vector
 +      lda   #>NEWNMI   ;and do the same with
 +      sta   NMINV+1    ;the high byte
 +
 +      cli              ;allow IRQs again
 +
 +;continue initializing your program
 +
 +;     :::   ::::::     ;program initialization continues
 +      jmp   TERMINAL   ;go to the example dumb-terminal subroutine
 +
 +;Save two bytes to store the old vector only if you need it
 +
 +
 +;    ---=== New NMI Routine Starts Here ===---
 +
 +;The code below is a simple interrupt patch to control the ACIA.  When
 +;the ACIA generates an interrupt, this routine examines the status
 +;register which contains the following data.
 +
 +;             .---------------------------- high if ACIA caused interrupt;
 +;             :                             not used in code below
 +;             :
 +;             :.------------------------- reflects state of DCD line
 +;             ::
 +;             ::.---------------------- reflects state of DSR line
 +;             :::
 +;             :::.------------------ high if xmit-data register is empty
 +;             ::::
 +;             :::: .--------------- high if receive-data register full
 +;             :::: :
 +;             :::: :.----------- high if overrun error
 +;             :::: ::
 +;             :::: ::.------- high if framing error
 +;             :::: :::
 +;             :::: :::.--- high if parity error
 +;     status  xxxx_xxxx
 +
 +NEWNMI:
 +;     sei              ;the Kernal routine already does this before jumping
 +                       ;through the NMINV vector
 +      pha              ;save A register
 +      txa
 +      pha              ;save X register
 +      tya
 +      pha              ;save Y register
 +
 +;As discussed above, the ACIA can generate an interrupt from one of four
 +;different sources.  We'll first check to see if the interrupt was
 +;caused by the receive register being full (bit #3) or the transmit
 +;register being empty (bit #4) since these two activities should receive
 +;priority.  A BEQ (Branch if EQual) tests the status register and branches
 +;if the interrupt was not caused by the data register.
 +
 +;Before testing for the source of the interrupt, we'll prevent more
 +;interrupts from the ACIA by disabling them at the chip.  This prevents
 +;another NMI from interrupting this one.  (SEI won't work because the
 +;CPU can't disable non-maskable interrupts).
 +
 +;At lower baud rates (2400 baud and lower) this may not be necessary.  But,
 +;it's safe and doesn't take much time, either.
 +
 +;The same technique should be used in parts of your program where timing
 +;is critical.  Disk access, for example, uses SEI to mask IRQ interrupts.
 +;You should turn off the ACIA interrupts during disk access also to prevent
 +;disk errors and system crashes.
 +
 +;First, we'll load the status register which contains all the interrupt
 +;and any received-data error information in the 'A' register.
 +
 +      lda   status
 +
 +;Now prevent any more NMIs from the ACIA
 +
 +      ldx   #%0000_0011   ;disable all interrupts, bring RTS inactive, and
 +                          ;leave DTR active
 +      stx   command       ;send to ACIA-- code at end of interrupt handler
 +                          ;will re-enable interrupts
 +
 +;Store the status-register data only if needed for error checking.
 +;The next received byte will clear the error flags.
 +
 +;     sta   errors        ;only if error checking implemented
 +
 +      and   #%0001_1000   ;mask out all but transmit and
 +                          ;receive interrupt indicators
 +
 +;If you don't use a transmit buffer you can use
 +;
 +;     and   #%0000_1000
 +;
 +;to test for receive interrupts only and skip the receive test shown
 +;below.
 +
 +      beq   TEST_DCD_DSR
 +
 +;if the 'A' register=0, either the interrupt was not caused by the
 +;ACIA or the ACIA interrupt was caused by a change in the DCD or
 +;DSR lines, so we'll branch to check those sources.
 +
 +;If your program ignores DCD and DSR, you can branch to
 +;the end of the interrupt handler instead:
 +;
 +;     beq   NMIEXIT
 +;
 +
 +;Test the status register information to see if a received byte is ready
 +;If you don't use a transmit buffer, skip the next two instructions.
 +
 +RECEIVE:                  ;process received byte
 +      and   #%0000_1000   ;mask all but bit #3
 +      beq   XMITCHAR      ;if not set, no received byte - if you're using
 +                          ;a transmit buffer, the interrupt must have been
 +                          ;caused by transmit.  So, branch to handle.
 +      lda   data          ;get received byte
 +      ldy   rtail         ;index to buffer
 +      sta   (rbuff),    ;and store it
 +      inc   rtail         ;move index to next slot
 +      inc   recvcount     ;increment count of bytes in receive buffer
 +                          ;(if used by your program)
 +
 +;Skip the "XMIT" routines below if you decide not to use a transmit buffer.
 +;In that case, the next code executed starts at TEST_DCD_DSR or NMIEXIT.
 +
 +;After processing a received byte, this sample code tests for bytes
 +;in the transmit buffer and sends on if present.  Note that, in this
 +;sample, receive interrupts take precedence.  You may want to reverse the
 +;order in your program.
 +
 +;If the ACIA generated a transmit interrupt and no received byte was
 +;ready, status bit #4 is already set.  The ACIA is ready to accept
 +;the byte to be transmitted and we've branched directly to XMITCHAR below.
 +
 +;If only bit #3 was set on entry to the interrupt handler, the ACIA may have
 +;been in the process of transmitting the last byte, and there may still be
 +;characters in the transmit buffer.  We'll check for that now, and send the
 +;next character if there is one.  Before sending a character to the ACIA to
 +;be transmitted, we must wait until bit #4 of the status register is set.
 +
 +XMIT:
 +      lda   xmitcount     ;if not zero, characters still in buffer
 +                          ;fall through to process xmit buffer
 +      beq   TEST_DCD_DSR  ;no characters in buffer-- go to next check
 +;or
 +;
 +;     beq   NMIEXIT
 +;
 +;if you don't check DCD or DSR in your program.
 +
 +XMITBYTE:
 +      lda   status        ;test bit #4
 +      and   #%00010000
 +      beq   TEST_DCD_DSR  ;skip if transmitter still busy
 +
 +XMITCHAR:                 ;transmit a character
 +      ldy   thead
 +      lda   (tbuff),    ;get character at head of buffer
 +      sta   data          ;place in ACIA for transmit
 +
 +                          ;point to next character in buffer
 +      inc   thead         ;and store new index
 +      dec   xmitcount     ;subtract one from count of bytes
 +                          ;in xmit buffer
 +      lda   xmitcount
 +      beq   TEST_DCD_DSR
 +;or
 +;
 +;     beq   NMIEXIT
 +;
 +;if you don't check DCD or DSR in your program
 +
 +;If xmitcount decrements to zero, there are no more characters to
 +;transmit.  The code at NMIEXIT turns ACIA transmit interrupts off.
 +
 +;If there are more bytes in the buffer, set up the 'A' register with
 +;the model that turns both transmit and receive interrupts on.  We felt
 +;that was safer, and not much slower, than EORing bits #3 and #4.  Note
 +;that the status of the parity/echo bits is preserved in the way "xmiton"
 +;and "xmitoff" were initialized earlier.
 +
 +      lda   xmiton        ;model to leave both interrupts enabled
 +
 +;If you don't use DCD or DSR
 +
 +      bne   NMICOMMAND    ;branch always to store model in command register
 +
 +;If your program uses DCD and/or DSR, you'll want to know when the state
 +;of those lines changes.  You can do that outside the interrupt handler
 +;by polling the ACIA status register, but if either of the lines changes,
 +;the chip will generate an NMI anyway.  So, you can let the interrupt
 +;handler do teh work for you.  The cost is the added time required to
 +;execute the DCD_DSR code on each NMI.
 +
 +TEST_DCD_DSR:
 +
 +;     pha                 ;only if you use a transmit buffer, 'A' holds
 +                          ;the proper mask to re-enable interrupts on
 +                          ;the ACIA
 +;     ::
 +;     ::                  ;appropriate code here to compare bit #6 (DCD)
 +;     ::                  ;and/or bit #5 (DSR) with their previous states
 +;     ::                  ;which you've already stored in memory and take
 +;     ::                  ;appropriate action
 +;     ::
 +;     pla                 ;only if you pushed it at the start of the
 +                          ;DCD/DSR routine
 +;     bne   NMICOMMAND    ;'A' holds the xmiton mask if it's not zero,
 +                          ;implying that we arrived here from xmit routine
 +                          ;not used if you're not using a transmit buffer.
 +
 +;If the test for ACIA interrupt failed on entry to the handler, we branch
 +;directly to here.  If you don't use additional handlers, the RESTORE key
 +;(for example) will fall through here and have no effect on your program
 +;or the machine, except for some wasted cycles.
 +
 +NMIEXIT:
 +      lda   xmitoff       ;load model to turn transmit interrupts off
 +
 +;and this line sets the interrupt status to whatever is in the 'A' register.
 +
 +NMICOMMAND:
 +      sta   command
 +
 +;That's all we need for the ACIA interrupt handler.  Since we've pushed the
 +;CPU registers to the stack, we need to pop them off.  Note that you must
 +;do this EVEN IF YOU JUMP TO THE KERNAL HANDLER NEXT, since it will push
 +;them again immediately.  You can skip this step only if you're proceeding
 +;to a custom handler.
 +
 +EXITINT:                  ;restore things and exit
 +      pla                 ;restore 'Y' register
 +      tay
 +      pla                 ;restore 'X' register
 +      tax
 +      pla                 ;restore 'A' register
 +
 +;If you want to continue processing the interrupt with the Kernal routines,
 +
 +      jmp   (OLDVEC)      ;continue processing interrupt with Kernal handler
 +
 +;Or, if you add your own interrupt routine,
 +
 +;     jmp   YOURCODE      ;continue with your own handler
 +
 +;If you use your own routine, or if you don't add anything, BE SURE to do
 +;this last (C64 only):
 +
 +;     rti                 ;return from interrupt instruction
 +
 +;to restore the flags register the CPU pushes to the stack before jumping
 +;to the Kernal code.  It also returns you to the interrupted part of
 +;your program
 +
 +;-----------------------------------------------------------------------------
 +;Sample routine to store a character in the buffer to be transmitted
 +;by the ACIA.
 +
 +;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
 +;Geoduck Developmental Systems, and Dr. Evil Labs.
 +
 +;Assumes the character is in the 'A' register on entry.  Destroys 'Y'--
 +;push to stack if you need to preserve it.
 +
 +SENDBYTE:                 ;adds a byte to the xmit buffer and sets
 +                          ;the ACIA to enable transmit interrupts (the
 +                          ;interrupt handler will disable them again
 +                          ;when the buffer is empty)
 +
 +      ldy   xmitcount     ;count of bytes in transmit buffer
 +      cpy   #255          ;max buffer size
 +      beq   NOTHING       ;buffer is full, don't add byte
 +
 +      ldy   ttail         ;pointer to end of buffer
 +      sta   (tbuff),    ;store byte in 'A' at end of buffer
 +      inc   ttail         ;point to next slot in buffer
 +      inc   xmitcount     ;and add one to count of bytes in buffer
 +
 +      lda   xmiton        ;get model for turning on transmit interrupts
 +      sta   command       ;tell ACIA to do it
 +
 +      rts                 ;return to your program
 +
 +NOTHING:
 +      lda   #$00          ;or whatever flag your program uses to tell that the
 +                          ;byte was not transmitted
 +      rts                 ;and return
 +
 +;Alternative routine to transmit a character from main program when not using
 +;a transmit buffer.
 +;
 +;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
 +;Geoduck Developmental Systems, and Dr. Evil Labs.
 +;
 +;Assumes the character to be transmitted is in the 'A' register on entry.
 +;Destroys 'Y'; push to stack if you need to preserve it.
 +;
 +;SENDBYTE:
 +;     tay                 ;remember byte to be transmitted
 +;
 +;TESTACIA:
 +;     lda   status        ;bit #4 of the status register is set if
 +;                         ;the ACIA is ready to transmit another byte,
 +;                         ;even if transmit interrupts are disabled.
 +;     and   #%0001_0000
 +;     beq   TESTACIA      ;wait for bit #4 to be set
 +;     sty   data          ;give byte to ACIA
 +;     rts
 +
 +;Sample routine to fetch a character that has been received, from the
 +;receive buffer.
 +
 +;by Craig Bruce, 1995, adapted from above
 +
 +;Will return the character in the 'A' register and the carry flag cleared if
 +;a character was available.  If no character was available, will return with
 +;the carry flag set.  Destroys the 'Y' register.
 +
 +RECVBYTE:                 ;fetches a byte from the receive buffer.
 +                          ;there is no need to fiddle with any interrupts
 +
 +      lda   recvcount     ;count of bytes in receive buffer
 +      beq   RECVEMPTY     ;buffer is empty, indicate to caller
 +
 +      ldy   rhead         ;pointer to start of buffer
 +      lda   (rbuff),    ;fetch byte out of buffer into 'A' register
 +      inc   rhead         ;point to next slot in buffer
 +      dec   recvcount     ;and add one to count of bytes in buffer
 +
 +      clc                 ;indicate that we have a character
 +      rts                 ;return to your program
 +
 +RECVEMPTY:
 +      sec                 ;or whatever flag your program uses to tell that the
 +                          ;receive buffer was empty
 +      rts                 ;and return
 +
 +;-----------------------------------------------------------------------------
 +;Dumb -- very dumb -- terminal emulator.  Simply polls the receive buffer and
 +;the keyboard and puts received data to the screen and typed data to the send
 +;buffer (thus, it assumes a full-duplex, echoing link).  There is no
 +;PETSCII->ASCII conversion, no cursor, nor any other fancy features.  Press
 +;STOP to exit.
 +;
 +;by Craig Bruce, 1995.
 +
 +TERMINAL:
 +      jsr   RECVBYTE      ;see if there is a received byte in the recv buffer
 +      bcs   TERMTRYSEND   ;if not, continue
 +      jsr   $FFD2         ;if received byte, print it to the screen (CHROUT)
 +TERMTRYSEND:
 +      jsr   $FFE4         ;try to get a character from the keyboard (GETIN)
 +      cmp   #$00          ;was there a keystroke available?
 +      beq   TERMINAL      ;no--go back to top of polling loop
 +      cmp   #$03          ;check for STOP key
 +      beq   TERMEXIT      ;  exit if pressed
 +      jsr   SENDBYTE      ;have char--put it into the transmit buffer and then
 +      jmp   TERMINAL      ;  go back to top of polling loop
 +TERMEXIT:
 +      lda   #%0000_0010   ;disable transmitter and receiver and all interrupts
 +      sta   command
 +      sei
 +      lda   OLDVEC        ;restore the NMI vector to its original value
 +      sta   NMINV
 +      lda   OLDVEC+1
 +      sta   NMINV+1
 +      cli
 +      rts                 ;exit
 +
 +TRANSMIT_BUFFER = *+0
 +RECEIVE_BUFFER  = *+256
 +%%%---8<---cut-here---8<---%%%
 +
 +------------------------------------------------------------------------------
 +</code>
 +====== APPENDIX: 6551 ACIA HARDWARE SPECS (DATA SHEET) ======
 +<code>
 +                     C= Commodore Semiconductor Group
 +              a division of Commodore Business Machines, Inc.
 + 950 Rittenhouse Road, Nornstown, PA 19400 * 215/666-7950 * TWX 510-660-4168
 +                                (July 1987)
 +
 +             6551 ASYNCHRONOUS COMMUNICATION INTERFACE ADAPTER
 +
 +CONCEPT:
 +
 +The 6551 is an Asynchronous Communication Adapter (ACIA) intended to provide
 +for interfacing the 6500/6800 microprocessor families to serial communication
 +data sets and modems.  A unique feature is the inclusion of an on-chip
 +programmable baud-rate generator, with a crystal being the only external
 +component required.
 +
 +FEATURES:
 +
 +* On-chip baud-rate generator: 15 programmable baud rates derived from a
 +  standard standard 1.8432 MHz external crystal (50 to 19,200 baud) [these
 +  rates are doubled in the SwiftLink].
 +
 +* Programmable interrupt and status register to simplify software design.
 +
 +* Single +5 volt power supply.
 +
 +* Serial echo mode.
 +
 +* False start bit detection.
 +
 +* 8-bit bi-directional data bus for direct communication with the
 +  microprocessor.
 +
 +* External 16x clock input for non-standard baud rates (up to 125 Kbaud).
 +
 +* Programmable: word lengths; number of stop bits; and parity-bit generation
 +  and detection.
 +
 +* Data set and modem control signals provided.
 +
 +* Parity: (odd, even, none, mark, space).
 +
 +* Full-duplex or half-duplex operation.
 +
 +* 5,6,7 and 8-bit transmission.
 +
 +* 1-MHz, 2-MHz, and 3-MHz operation.
 +
 +ORDER NUMBER
 +
 +MXS 6551 ___
 +        |
 +        +---- Frequency range
 +                  Plain = 1 MHz
 +                      A = 2 MHz
 +                      B = 3 MHz
 + |
 + +----------- Package Designator
 +                     C = Ceramic
 +                     P = Plastic
 +
 +6551 PIN CONFIGURATION
 +
 +                +---------------+
 +          GND --| 1          28 |-- R-/W
 +          CS0 --| 2          27 |-- o2
 +         /CS1 --| 3          26 |-- /IRQ
 +         /RES --| 4          25 |-- DB7
 +          RxC --| 5          24 |-- DB6
 +        XTAL1 --| 6          23 |-- DB5
 +        XTAL2 --| 7          22 |-- DB4
 +         /RTS --| 8          21 |-- DB3
 +         /CTS --| 9          20 |-- DB2
 +          TxD --| 10         19 |-- DB1
 +         /DTR --| 11         18 |-- DB0
 +          RxD --| 12         17 |-- /DSR
 +          RS0 --| 13         16 |-- /DCD
 +          RS1 --| 14         15 |-- Vcc
 +                +---------------+
 +
 +BLOCK DIAGRAM                                        +----------+
 +                                                     | TRANSMIT |
 +                                                     | CONTROL  |<------- CTS
 +                                                     +----------+
 +                                                           |
 +                                                           v
 +                               +----------+          +----------+
 +                               | TRANSMIT |          | TRANSMIT |
 +                         /|===>  DATA   |=========> SHIFT   |-------> TxD
 +                         ||    | REGISTER |          | REGISTER |
 +                         ||    +----------+          +----------+
 +          +---------+    ||
 +   o2 --->           ||    +----------+          +----------+
 + R-/W ---> SELECT |    ||====|  STATUS  |          | INTERRUPT|-------> /IRQ
 +  CS0 --->  AND      ||    | REGISTER |<-------->  LOGIC  |<------- /DCD
 + /CS1 --->| CONTROL |    ||    +----------+          +----------+<------- /DSR
 +  RS0 ---> LOGIC  |    ||
 +  RS1 --->           ||    +----------+          +----------+
 + /RES --->           ||===>| CONTROL  |          | BAUD-RATE|<------> RxC
 +          +---------+    ||    | REGISTER |          | GENERATOR|<------- XTAL1
 +                         ||    +----------+          +----------+<------- XTAL2
 +                         ||
 +          +---------+    ||    +----------+          +----------+
 +  DB0 <--> DATA-  |    ||    |  RECEIVE |          |  RECEIVE |
 +  ...       BUS   |<===||====|   DATA   |<=========|   SHIFT  |<---+--- RxD
 +  DB7 <-->| BUFFERS |    ||    | REGISTER |          | REGISTER |    |
 +          +---------+    ||    +----------+          +-----.----+    |
 +                         ||                                |         |
 +                         ||    +----------+          +----------+    |
 +   LEGEND:               \|===>| COMMAND  |          |  RECEIVE |    |
 +                               | REGISTER |          |  CONTROL |<---+
 +   ===> : 8-bit line           +----------+          +----------+
 +                                  |    |
 +   ---> : 1-bit line              |    +--------------------------------> /DTR
 +                                  +-------------------------------------> /RTS
 +
 +MAXIMUM RATINGS
 +
 +<not included here>
 +
 +ELECTRICAL CHARACTERISTICS
 +
 +<not included here>
 +
 +POWER DISSIPATION vs TEMPERATURE
 +
 +<not included here>
 +
 +TIMING CHARACTERISTICS
 +
 +<not included here>
 +
 +INTERFACE SIGNAL DESCRIPTION
 +
 +/RES (Reset)
 +
 +During system initialization a low on the /RES input will cause internal
 +registers to be cleared.
 +
 +o2 (Input Clock)
 +
 +The input clock is the system o2 clock and is used to trigger all data
 +transfers between the system microprocessor and the 6551.
 +
 +R-/W (Read/Write)
 +
 +The R-/W is generated by the microprocessor and is used to control the
 +direction of data transfers.  A high on the R-/W pin allows the processor
 +to read the data supplied by the 6551.  A low on the R-/W pin allows a write
 +to the 6551.
 +
 +/IRQ (Interrupt Request)
 +
 +The /IRQ pin is an interrupt signal from the interrupt-control logic.  It is
 +an open drain output, permitting several devices to be connected to the common
 +/IRQ microprocessor input.  Normally a high level, /IRQ goes low when an
 +interrupt occurs.
 +
 +DB0--DB7 (Data Bus)
 +
 +The DB0--DB7 pins are the eight data lines used for transfer of data between
 +the processor and the 6551.  These lines are bi-directional and are normally
 +high-impedance except during Read cycles when selected.
 +
 +CS0, /CS1 (Chip Selects)
 +
 +The two chip-select inputs are normally connected to the processor-address
 +lines either directly or through decoders.  The 6551 is selected when CS0 is
 +high and /CS1 is low.
 +
 +RS0, RS1 (Register Selects)
 +
 +The two register-select lines are normally connected to the processor-address
 +lines to allow the processor to select the various 6551 internal registers.
 +The following table indicates the internal register-select coding:
 +
 +RS1   RS0   WRITE                     READ                    SL-Addr
 +---   ---   ----------------------    ---------------------   -------
 +  0       Transmit Data Register    Receive Data Register     $DE00
 +  0       Programmed Reset*         Status Register           $DE01
 +  1       Command Register          Command Register          $DE02
 +  1       Control Register          Control Register          $DE03
 +
 +                * for programmed reset, data is "don't care".
 +
 +The table shows that only the Command and Control registers are read/write.
 +The Programmed Reset operation does not cause any data transfer, but is used
 +to clear the 6551 registers.  The Programmed Reset is slightly different from
 +the Hardware Reset (/RES) and these differences are described in the
 +individual register definitions.
 +
 +ACIA/MODEM INTERFACE SIGNAL DESCRIPTION
 +
 +XTAL1, XTAL2 (Crystal Pins)
 +
 +These pins are normally directly connected to the external crystal (1.8432
 +MHz) used to derive the various baud rates.  Alternatively, an externally
 +generated clock may be used to drive the XTAL1 pin, in which case the XTAL2
 +pin must float.  XTAL1 is the input pin for the transmit clock.
 +
 +TxD (Transmit Data)
 +
 +The TxD output line is used to transfer serial NRZ (non-return-to-zero) data
 +to the modem.  The LSB (least-significant bit) of the Transmit Data Register
 +is the first data bit transmitted and the reate of data transmission is
 +determined by the baud rate selected.
 +
 +RxD (Receive Data)
 +
 +The RxD input line is used to transfer serial NRZ data into the ACIA from the
 +modem, LSB first.  The receiver data rate is either the programmed baud rate
 +or the rate of an externally generated receiver clock.  This selection is made
 +by programming the Control Register.
 +
 +RxC (Receive Clock)
 +
 +The RxC is a bi-directional pin which serves as either the receiver 16x clock
 +input or the receiver 16x clock output.  The latter mode results if the
 +internal baud rate generator is selected for receiver data clocking.
 +
 +/RTS (Request to Send)
 +
 +The /RTS output pin is used to control the modem from the processor.  The
 +state of the /RTS pin is determined by the contents of the Command Register.
 +
 +/CTS (Clear to Send)
 +
 +The /CTS input pin is used to control the transmitter operation.  The enable
 +state is with /CTS low.  The transmitter is automatically disabled if /CTS is
 +high.
 +
 +/DTR (Data Terminal Ready)
 +
 +The output pin is used to indicate the status of the 6551 to the modem.  A low
 +of /DTR indicates the 6551 is enabled and a high indicates it is disabled.
 +The processor controls this pin via bit 0 of the Command Register.
 +
 +/DSR (Data Set Ready)
 +
 +The /DSR input pin is used to indicate to the 6551 the status of the modem.  A
 +low indicates the "ready" state and a high, "not-ready" /DSR is a high-
 +impedance input and must not be a no-connect.  If unused, it should be driven
 +high or low, but not switched.
 +
 +Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs,
 +/IRQ will be set and Status Register Bit #[5] will reflect the new level.  The
 +state of /DSR does not affect Transmitter operation [but must be low for the
 +Receiver to operate].  [This statement reflects the SwiftLink implementation].
 +
 +/DCD (Data Carrier Detect)
 +
 +The /DCD input pin is used to indicate to the 6551 the status of the carrier-
 +detect output of the modem.  A low indicates that the modem carrier signal is
 +present and a high, that it is not.  /DCD, like /DSR, is a high-impedance
 +input and must not be a no-connect.
 +
 +Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs,
 +/IRQ will be set and Status Register Bit #[6] will reflect the new level.  The
 +state of /DCD does not affect either Transmitter or Receiver operation.
 +
 +INTERNAL ORGANIZATION
 +
 +<not included here>
 +
 +TRANSMIT AND RECEIVE DATA REGISTERS (SL-Addr: $DE00 / 56832)
 +
 +These registers are used as temporary data storage for the 6551 Transmit and
 +Receive circuits.  The Transmit Data Register is characterized as follows:
 +
 +* Bit 0 is the leading bit to be transmitted.
 +
 +* Unused data bits are the high-order bits and are "don't care" for
 +  transmission.
 +
 +The Receive Data Register is characterized in a similar fashion:
 +
 +* Bit 0 is the leading bit received.
 +
 +* Unused data bits are the high-order bits and are "0" for the receiver.
 +
 +* Parity bits are not contained in the Receive Data Register, but are stripped
 +  off after being used for external parity checking.  Parity and all unused
 +  high-order bits are "0".
 +
 +           Transmit / Receive Data Register
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |                     data                      |
 +
 +  The following figure illustrates a single transmitted or received data
 +  word, for the example of 8 data bits, parity, and 1 stop bit:
 +
 +  "MARK"____    ___________________________________________________"MARK"
 +              | 0  | 1  | 2  | 3  | 4  | 5  | 6  | 7  | P  | S  .
 +           |____|____|____|____|____|____|____|____|____|____|
 +            start                                            parity  stop
 +             bit                ...data bits...               bit     bit
 +
 +
 +STATUS REGISTER (SL-Addr: $DE01 / 56833)
 +
 +The Status Register is used to indicate to the processor the status of various
 +6551 functions and is outlined here:
 +
 +                   Command Register
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  | irq | dcd | dsr | txr | rxr | ovr | fe  | pe  |
 +
 +  +---+
 +  | 7 |   /IRQ*** : cleared by reading status register
 +  +---+   --------------------------------------------
 +    0     No interrupt
 +    1     Interrupt
 +
 +  +---+
 +  | 6 |   /DCD : non-resetable, indicates /DCD status
 +  +---+   --------------------------------------------
 +    0     /DCD low
 +    1     /DCD high
 +
 +  +---+
 +  | 5 |   /DSR : non-resetable, indicates /DSR status
 +  +---+   --------------------------------------------
 +    0     /DSR low
 +    1     /DSR high
 +
 +  +---+
 +  | 4 |   Transmit Data Register Empty: Cleared by write to Tx Data reg
 +  +---+   -------------------------------------------------------------
 +    0     Not empty
 +    1     Empty
 +
 +  +---+
 +  | 3 |   Receive Data Register Full: Cleared by read from Rx Data reg
 +  +---+   -------------------------------------------------------------
 +    0     Not full
 +    1     Full
 +
 +  +---+
 +  | 2 |   Overrun*: Self-clearing**
 +  +---+   -------------------------
 +    0     No error
 +    1     Error
 +
 +  +---+
 +  | 1 |   Framing Error*: Self-clearing**
 +  +---+   -------------------------------
 +    0     No error
 +    1     Error
 +
 +  +---+
 +  | 0 |   Parity Error*: Self-clearing**
 +  +---+   ------------------------------
 +    0     No error
 +    1     Error
 +
 +  Notes:   * No interrupt generated for these conditions
 +          ** Cleared automatically after a read of RDR and the next error-
 +               free receipt of data
 +         *** Reading status reg. will clear the /IRQ bit except when
 +               transmit intr. enabled
 +
 +    7               0
 +  +---+---+---+---+---+---+---+---+
 +  | 0 | x | x | 1 | 0 | 0 | 0 | 0 |  After Hardware reset
 +  +---+---+---+---+---+---+---+---+
 +  | x | x | x | x | x | 0 | x | x |  After Software reset
 +  +---+---+---+---+---+---+---+---+
 +
 +
 +COMMAND REGISTER (SL-Addr: $DE02 / 56834)
 +
 +The Command Register is used to control specific Transmit/Receive functions
 +and is shown here:
 +
 +                   Command Register
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |     parity      | echo|  tx ctrl  | rxi | dtr |
 +
 +  +---+---+---+
 +  | 7 | 6 | 5 |   PARITY CHECK CONTROLS
 +  +---+---+---+   ----------------------
 +    x         parity disabled--no parity bit generated or received
 +    0         odd parity receiver and transmitter
 +    0         even parity receiver and transmitter
 +    1         mark parity transmitted, parity check disabled
 +    1         space parity transmitted, parity check disabled
 +
 +  +---+
 +  | 4 |   NORMAL/ECHO MODE FOR RECEIVER
 +  +---+   ------------------------------
 +    0     Normal
 +    1     Echo (bits 2 and 3 must be "0")
 +
 +  +---+---+
 +  | 3 | 2 |   Tx INTERRUPT    RTS LEVEL    TRANSMITTER
 +  +---+---+   ------------    ---------    ------------
 +    0         Disabled         High           Off
 +    0         Enabled          Low            On
 +    1         Disabled         Low            On
 +    1         Disabled         Low       Transmit BRK
 +
 +  +---+
 +  | 1 |   RECEIVE INTERRUPT ENABLE
 +  +---+   -------------------------
 +    0     /IRQ interrupt Enabled from bit 3 of Status Register
 +    1     /IRQ interrupt Disabled
 +
 +  +---+
 +  | 0 |   DATA TERMINAL READY
 +  +---+   --------------------
 +    0     Disable Receiver and all interrupts (/DTR high)
 +    1     Enable Receiver and all interrupts  (/DTR low)
 +
 +    7               0
 +  +---+---+---+---+---+---+---+---+
 +  | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  After Hardware reset
 +  +---+---+---+---+---+---+---+---+
 +  | x | x | x | 0 | 0 | 0 | 0 | 0 |  After Software reset
 +  +---+---+---+---+---+---+---+---+
 +
 +
 +CONTROL REGISTER (SL-Addr: $DE03 / 56835 / cpm: 0001xxxx)
 +
 +The Control Register is used to select the desired mode for the 6551.  The
 +word length, number of stop bits, and clock controls are all determined
 +by the Control Register, which is shown here:
 +
 +                   Control Register
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
 +  +-----+-----+-----+-----+-----+-----+-----+-----+
 +  |stops|  word len | src |       baud rate       |
 +
 +  +---+
 +  | 7 |   STOP BITS
 +  +---+   ----------
 +    0     1 stop bit
 +    1     2 stop bits
 +    1     1 stop bit if word length== 8 bits and parity
 +              this allows for 9-bit transmission (8 data bits plus parity)
 +    1     1.5 stop bits if word length== 5 bits and no parity
 +
 +  +---+---+
 +  | 6 | 5 |   WORD LENGTH
 +  +---+---+   ------------
 +    0       8 bits
 +    0       7 bits
 +    1       6 bits
 +    1       5 bits
 +
 +  +---+
 +  | 4 |   RECEIVER CLOCK SOURCE
 +  +---+   ----------
 +    0     external receiver clock
 +    1     baud rate generator
 +
 +  +---+---+---+---+
 +  | 3 | 2 | 1 | 0 |   BAUD RATE GENERATOR
 +  +---+---+---+---+   --------------------
 +    0           16x external clock
 +    0           100 baud
 +    0           150 baud
 +    0           219.84 baud
 +    0           269.16 baud
 +    0           300 baud
 +    0           600 baud
 +    0           1200 baud
 +    1           2400 baud
 +    1           3600 baud
 +    1           4800 baud
 +    1           7200 baud
 +    1           9600 baud
 +    1           14400 baud
 +    1           19200 baud
 +    1           38400 baud
 +
 +    7               0
 +  +---+---+---+---+---+---+---+---+
 +  | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  After Hardware reset
 +  +---+---+---+---+---+---+---+---+
 +  | x | x | x | x | x | x | x | x |  After Software reset
 +  +---+---+---+---+---+---+---+---+
 +
 +========================================================================
 +</code>
 +====== Design and Implementation of a Simple/Efficient Upload/Download Protocol ======
 +<code>
 +by Craig Bruce  <csbruce@ccnga.uwaterloo.ca>
 +
 +1. INTRODUCTION
 +
 +If you use your Commodore for telecommunications, then you are basically
 +interested in two things: using your C= to emulate a terminal for interactive
 +stuff, and using modem-file-transfer protocols to upload and download files
 +from and to your Commodore.
 +
 +This document describes a custom upload/download protocol that was designed
 +for use with the ACE-128/64 system and is freely available to anyone who wants
 +it (well, when I finish with the Release #14 of ACE).  While this protocol
 +non-standard, it blows the doors off of all other protocols available for
 +Commodore computers, even though it uses a simple "stop-and-wait"
 +acknowledgement scheme.  There are two reasons for its speed: the fast device
 +drivers available with ACE, and its large packet size, up to about 18K
 +(although this could be significantly larger is ACE's memory usage were
 +reorganized).
 +
 +The name of the protocol is "Craig's File eXchange Protocol", or just "FX" for
 +short.  It is "file exchange" rather than "upload" or "download" because you
 +will use the same activation of the program to both upload and download all of
 +the files you name.
 +
 +2. USAGE
 +
 +The current implementation of FX consists of a "client" program for you to run
 +on your Commodore computer and a "server" program that you run on your Unix
 +host.  There is currently no server program for any other platform, but the
 +necessary changes to the C-language program wouldn't be too hard.  The client
 +program is written in 6502 assembler, of course (for the ACE-assembler to be
 +specific).
 +
 +FX is an external program from the terminal program, so (for now) to activate
 +FX, you have to exit from the terminal program and enter the FX command line,
 +exchange the files, and then re-enter the terminal program from the command
 +line.
 +
 +When you run FX, you will activate the Server program first on your Unix host
 +and then exit the terminal program and run the Client program on your
 +Commodore.  You run the command "fx" on both the client and server machines,
 +which may be a little confusing (but I think you'll get used to it), and name
 +the files that you want to have transferred as arguments to the command on the
 +machine that you want to transfer the files FROM.  The usage of the "fx"
 +command is as follows:
 +
 +fx [-dlvV7] [-m maximums] [-f argfile] [[-b] binfile ...] [-t textfile ...]
 +
 +-d = debug mode
 +-l = write to log file ("fx.log")
 +-v = verbose log/debug mode
 +-V = extremely verbose log/debug mode
 +-7 = use seven-bit encoding
 +-m = set maximum packet sizes; maximums = ulbin/ultxt/dlbin/dltxt (bytes)
 +-f = take arguments one-per-line from given argfile
 +-b = binary files prefix
 +-t = text files prefix
 +-help = help
 +
 +well, for the server, anyway.  The client program doesn't have the more
 +exotic options.  The "-d", "-l", "-v", and "-V" options are available only
 +on the Server program, and are for debugging purposes only.
 +
 +The "-7" option tells the protocol to use only 7-bit data.  I.e., it tells it
 +to not use the 8th bit position in the data is transmitted.  This is useful if
 +you are forced into the humiliation of only being able to use a 7-bit channel
 +to your Unix host.  You need only need to give this option on either the
 +client or the host command line and the other side will be informed.  It may
 +be useful to create an alias for this command with all of your options set to
 +what you want them to be.
 +
 +The protocol has the capacity to use different packet sizes for four types of
 +file-transfer situations: uploading binary data, uploading text, downloading
 +binary data, and downloading text.  These are useful distinctions, since your
 +host may or may not be able to handle the larger packet sizes without losing
 +bytes (your Commodore, of course, can handle the larger packet sizes with no
 +problems).
 +
 +In determining which packet size to use for a file transfer (where the type of
 +transfer is known), the protocol finds that largest packet size that both the
 +client and the server can handle and then take the minimum of these two
 +values.  The defaults for the client are all the same: the maximum amount of
 +program-area memory that it can use, about 18K.  For the server program, I
 +have programmed in default maximum uploading packet sizes of 1K and maximum
 +downloading packet sizes of 64K-1.  You can change these defaults in the C
 +program easily by changing some "#define"s.
 +
 +The "-m" option allows you to manually set the default packet sizes for a
 +transfer.  The argument following the "-m" flag should have four numbers with
 +slashes between them, which give the maximum ulbin/ultxt/dlbin/dltxt packet
 +sizes, respectively.  Note that the packet sizes only include the size of the
 +user data encoded into packets and not the control or quoting information
 +(below).
 +
 +The "-f" option on the server allows you to read arguments from a file rather
 +than the command line.  This is useful if want to generate and edit the list
 +of files to download before you run the FX command.  It's also useful if you
 +don't want other users to see the names of the files that you are
 +downloading.  The name of the file comes in the first argument following the
 +"-f" flag and the arguments are put into this file one-per-line.  You can put
 +in "-" options in addition to filenames if you wish (like "-t" and "-b").
 +This option is not supported on the client program.
 +
 +Finally come the "-b", "-t", and filename arguments.  The "-b" argument tells
 +FX that all of the following filenames (until the next "-t" option) are binary
 +files and the "-t" argument says that the following filenames are all of text
 +files.  You can use as many "-b" and "-t" arguments as you want.  If you don't
 +use any, then all of the files you name will be assumed to be binary files.
 +
 +For each filename you give on a command line, that file will be transferred
 +from that machine to the other machine.  On both Unix and ACE, you can use
 +wildcards in your filenames, of course, to transfer groups of files.
 +
 +The client program controls the file exchange, and it uploads all of its files
 +first and then asks the server if the server has any files to be downloaded.
 +When the exchange is completed, both the client and server FX programs will
 +exit and you will find yourself back on the command lines in both
 +environments.  Re-enter the terminal program to continue with your online
 +session.  If something goes very wrong during a transfer or if you decide that
 +you don't really want to transfer any files after activating the server
 +program, you can type three Ctrl-X's to abort the server.  This is the same as
 +for the X-modem protocol.
 +
 +3. DESIGN DECISIONS
 +
 +There are a number of design decisions to be made about our protocol.  But
 +first, we want to recognize and appreciate that since we have a license to
 +design a completely new protocol, we are not bound, shackled, gagged, and
 +tortured by the "hysterical raisins" and bad design decisions of existing
 +compromised and bloated standard protocols... such as Z-modem.
 +
 +We want the protocol to understand whether a file is text or binary data and
 +to translate them appropriately during downloading.  We want the protocol to
 +be aware of filenames, dates, permissions, and we do not want our file
 +contents to get mangled like they do with X-modem (it pads them with Ctrl-Z's,
 +since it was designed for CP/M), and we want it to translate to/from PETSCII
 +if the file is text.  We will require that the user tell us whether the file
 +is binary or text (although we may be able to statistically determine this
 +from snooping through the file), and we will use a "canonical form" for
 +encoding the text data during transfer.  A convenient canonical form to use is
 +Unix-ASCII (ASCII-LF).
 +
 +We want our protocol to be simultaneously simple and fast.  To make it simple,
 +we will use a stop-and-wait acknowledgement scheme.  This means that after
 +each packet is uploaded or downloaded, the transfer will pause and wait for
 +the receiving host to acknowledge that the packet has been transferred
 +correctly, and only then will the protocol continue to transfer more data.
 +
 +In fact, this scheme fits well with the Commodore hardware, since it is not
 +possible to send or receive serial data while doing disk I/O (in the general
 +case), so we would have to stop listening anyway; the protocol makes it so
 +that there will be no bytes that we end up ignoring while doing I/O.
 +
 +To make the protocol be fast even though we are using a stop-and-wait
 +acknowledgement scheme, we will use the largest data-packet sizes that we
 +possibly can.  In the (current) ACE environment, this means about 18K.  This
 +will maximize the amount of time of transferring data over the modem between
 +pauses to do I/O.  If the I/O is to the ACE ramdisk, then the length of this
 +pause will be very short and we will achieve a very high link utilization.
 +(The ACE ramdisk can process an 18K read/write request in about 20
 +milliseconds on a Fast-mode C128 using an REU --- RAMDOS in the same
 +environment would require about 9 _seconds_ (450x slower)).
 +
 +To allow for future use with other platforms, we will make the protocol define
 +the packet sizes using 32-bit fields.  There isn't much data overhead, and
 +this allows us to change implementations to be able to transfer entire files
 +in one large packet.  Also, the size of an individual packet should be
 +flexible: be from one to N bytes.  This eliminates the X-modem padding problem
 +and the Y-modem crufty hack of using the small packet size when less than 1K
 +of user data remains to be transferred.
 +
 +We also want our data to be well protected against corruption.  Detecting
 +transmission errors efficiently on Commodore computers is already a well
 +solved problem: we will use a table-driven CRC-32 algorithm, the same one that
 +ZMODEM, PKZIP, and CRC32 use.  To hide the computation costs of the CRC even
 +more (the cost is very low anyway), we will compute it WHILE sending or
 +receiving packets.  Oh, actually, I guess that I forgot to mention an a-priori
 +design decision: we will be using a packet-oriented approach for transferring
 +data (described below); packetization offers so many advantages that this
 +decision is really a no-brainer.
 +
 +Also, to make the process interaction as straightforward as possible, we want
 +to use the Client/Server programming paradigm.  This paradigm combines well
 +with the stop-and-wait acknowledgement scheme to produce a Remote Procedure
 +Call (RPC) type of interaction between the machines.  For those not familiar
 +with this Interprocess Communication (IPC) scheme, you can read a couple
 +issues of C= hacking ago where I talked about it for use with a multitasking
 +operation system.  RPC is a very useful, powerful, simple, and widely
 +applicable IPC scheme.
 +
 +To recover from packet corruption, we will be using a timeout+retransmission
 +scheme, and to be consistent with the RPC scheme, the client will do all
 +timeouts and retransmissions.  This means that after sending a request RPC
 +packet out, if we don't receive the reply within a certain period of time, we
 +will timeout and send the request again.  Or, to be more precise, since we
 +will be working with large packet sizes, we will timeout if we don't receive
 +any bytes from the server for a certain period of time, say 5 seconds, while
 +we are expecting more bytes from him.
 +
 +The way that corrupted packets are dealt with is very simple: they are
 +ignored.  The server could possibly send back a negative acknowledgement,
 +but we won't try that for now.
 +
 +In order to make retransmissions work out correctly, we will be using sequence
 +numbers and internal-state variables inside of the server to insure that
 +requests aren't carried out more than once.  We need these mechanisms because
 +when an RPC fails, we won't know if we got no response because the original
 +request was lost and the operations wasn't carried out, or whether the request
 +was received and carried out but the reply message was lost.
 +
 +For example, if we request that packet #123 be downloaded and the server
 +carries out that request but the reply message is lost, then the client will
 +time out and retransmit the request.  The server remembers the last request
 +number that the client sent it (123 here), so if the client asks for packet
 +#123 again, the server will simply retransmit the reply that it gave last
 +time.  If, on the other hand, the client were to request packet #124 (or
 +simply "not 123"), then the server reads the next chunk of data from the file
 +and sends it as the reply.  Our protocol will use an 8-bit sequence number
 +even though it only needs a 1-bit sequence number (since eight bits will allow
 +for the future expansion of having multiple requests being processed
 +concurrently: asynchronous RPC).
 +
 +We also want to be able to both upload and download as conveniently as
 +possible.  To me, this means doing both operations by calling only one command
 +(as described in the previous section).  This arrangement also allows for the
 +future expansion of uploading and downloading files _simultaneously_ (the
 +protocol as designed places no restrictions on this possibility).
 +
 +We also want to make use of an eight-bit clean link between the Unix host and
 +your Commodore, but this may not always be possible.  Sometimes you may have
 +only a 7-bit connection, and even if you do have an 8-bit connection, there
 +may still be some software-flow-control problems with intermediate devices
 +between your Commodore and your Unix host.  So, we want our protocol to not
 +make use of the X-on and X-off characters, and to use only 7-bit characters if
 +it cannot use eight.  The way to achieve this is called "escaping", "quoting"
 +or "byte stuffing", and will be discussed in the next section.  It turns out
 +that supporting 7-bit characters is pretty simple and the mechanism is
 +required by other aspects of the packetization.
 +
 +There, that should take care of most of the major design decisions.
 +
 +4. PACKETIZATION
 +
 +Packetization refers to the process of taking a stream of data and breaking it
 +up into discrete chunks of data.  Each packet is easily identified and is
 +processed as a single unit.  There are many general advantages to using
 +packets.  If there is a transmission error, then only a single packet is
 +corrupted, and the recovery will be easier since the packet is well
 +identified, and only it needs to be recovered.  Packetization also means that
 +a link can be shared between multiple (logical) communication streams fairly
 +and efficiently, and means that a single communication stream can utilize
 +multiple physical links where facilities exist.
 +
 +Packets also integrate well with many IPC schemes, including Remote Procedure
 +Calls.  In fact, you end up emulating a packet-oriented scheme even if you are
 +using RPC over a stream-oriented transport system.  Packets also take into
 +account the limited buffering capacity of both end systems and intermediate
 +systems, and allow for the convenient implementation of flow control (even if
 +said flow control consists of simply dropping packets on the floor).  Packets
 +are very useful things indeed!  And just think that back in the early 1970s
 +packets were dismissed as being infeasible and unusable.
 +
 +Each packet used in the FX system has four parts to it: the start character,
 +the user data (payload), the error-check characters, and the end character.
 +Graphically, a packet has the following format:
 +
 ++------------------------+-----------+--------------+----------------------+
 +|  Start-of-packet Char  |  Payload  |  ErrorCheck  |  End-of-packet Char  |
 ++------------------------+-----------+--------------+----------------------+
 +
 +The payload can be arbitrarily long, up to whatever limit the two computers
 +involved in the transfer can handle.
 +
 +The error check is a 32-bit (4-byte) Cyclic-Redundancy-Check value that
 +occupies the last four bytes before the End-of-packet character.  The
 +implementation, which is based on a table-lookup method, is so efficient that
 +it is as fast as a simple add-up checksum, except much more reliable.  Using
 +this error check, there will be approximately a one-in-4,000,000,000 chance
 +that a packet with an error in it will be accepted has being error-free.
 +These are pretty good odds for our purposes.  The CRC is calculated
 +exclusively on the raw payload data.
 +
 +The following special characters used by packets are defined:
 +
 +NAME         HEX   DEC   Control   Meaning
 +---------   ----   ---   -------   --------
 +CHR_START   0x01       Ctrl-A    Packet-start indicator
 +CHR_END     0x19    25   Ctrl-Y    Packet-end indicator
 +CHR_ESC     0x05       Ctrl-E    Escape character for next code
 +CHR_ABORT   0x18    24   Ctrl-X    Abort transfer if repeated three times
 +CHR_XON     0x11    17   Ctrl-Q    Software flow-start: avoided
 +CHR_XOFF    0x13    19   Ctrl-S    Software flow-stop: avoided
 +CHR_QUOTE8  0x14    20   Ctrl-T    Quote-8 the next 7-bit sequence
 +
 +CHR_START is used to signify the start of a new packet.  This character is
 +not allowed to be used anywhere else for any other purpose.
 +
 +CHR_END is used to signify the end of the current packet, and cannot be used
 +anywhere else.  The reason for using special characters to mark the beginning
 +and the ending of a packet is to allow for easy error recovery after a
 +communication failure.  All you do is search for the next CHR_START character
 +after you toss away a garbled packet and you're back in business.  I am
 +unaware of any reasonable alternatives to framing packets with a CHR_START
 +character.  Using a CHR_END special character is a convenience.
 +
 +CHR_ESC is used to "escape" the next character.  Since there are special
 +character codes that cannot be used in any other way than their intended
 +function (including CHR_START and CHR_ESC itself), this character is needed.
 +The character following the CHR_ESC character must be between "@" and "_"
 +(0x40 and 0x5f) in the ASCII chart, or be the character "?" (0x3f).  The
 +character following the CHR_ESC is then "and"ed with the value 0x1f to mask
 +off the "letter" bits and turn it into a control character in the range of
 +0x00 to 0x1f (the same range as the special control characters) and the
 +"escape sequence" is treated as a single character of user data.  If the
 +character following the CHR_ESC is a "?", then a code of 0x7f is interpreted
 +instead.  Using a character following the escape that is different from the
 +character being represented allows for greater resiliance of the protocol in
 +the presence of bits being garbled or bytes being dropped.  All special
 +characters in a packet except for the starting and ending characters are
 +escaped as described above.
 +
 +CHR_ABORT can be typed by the user into a terminal program at any time to shut
 +down the server.
 +
 +CHR_XON and CHR_XOFF can cause problems with intermediate devices on some
 +systems, so the FX protocol does not use these character codes at all; it
 +purposely avoids them and uses escape sequences (CHR_ESC) for them instead.
 +
 +CHR_QUOTE8 is used to re-generate 8-bit data over a 7-bit link.  Kermit uses
 +this same technique.  When this character is encountered in the receive
 +stream, the next character is extracted and is "or"ed with a value of 0x80 to
 +give it a "1" in the high-bit position.  The CHR_QUOTE8 character can also be
 +followed by a CHR_ESC code, which is interpreted as above and then "or"ed with
 +the 0x80 value.
 +
 +One of the disadvantages of using this scheme is that each byte in the range
 +of 0x80 and 0xff takes at least two bytes to transmit and some of them three.
 +If fact, for many binary files it may be faster to uuencode the file and
 +transfer the resulting text, since uucode has a static encoding overhead of
 +33% whereas this quoting scheme has an expected overhead of 50% (plus the
 +CHR_ESC overhead).  Of course, this feature is intended to be used as a last
 +resort if you cannot get an 8-bit connection.
 +
 +So there you have it.  Every message sent between the client and the server
 +is encapsulated in a packet as specified above.  Packetization allows for
 +convenient error detection and recovery and works well with our interprocess
 +communication scheme.
 +
 +One implementation note about the packetization has to do with buffering.  On
 +the Unix host, it is advantageous to encode a packet into a memory buffer and
 +then send out that buffer in a single "write" operation.  This less operating-
 +system overhead (which may or may not be significant) but more importantly,
 +it means that the packet will be sent between intermediate communication
 +devices as efficiently as possible.  On my local Unix system, I connect to
 +a terminal server and to my Unix host through that.  Performing single-byte
 +writes on the Unix host means that the bytes are sent in individual Ethernet
 +packets between the Unix host and the terminal server, and encounter more
 +overhead and communication delays.  When I changed the program to send the
 +FX packet in a single operation, a significant performance gain was realized.
 +
 +For receiving data on the Unix host, there isn't much you can do other than
 +reading one byte at a time, since the receiver doesn't know when a packet is
 +going to end.  However, the same problem is not encountered here that was
 +encountered with sending data because data that is received by the Unix host
 +but not "read" by the user program are buffered and collected, smoothing out
 +the system overhead, which is insignificant compared to the modem speed.  The
 +Unix program used the "stdin" and "stdout" file streams for receiving and
 +transmitting data, and sets the tty driver to turn off all line-editing
 +features to get at the raw bytes.
 +
 +On the Commodore end, it is advantageous to read data from the modem driver in
 +chunks, since the system overhead is significant compared to the modem speed.
 +These are small computers that we are driving to the max, you know.  Data is
 +read from the modem in chunks of up to 255 bytes (whatever is available at the
 +time) and processed a byte at a time from the read buffer.  The CRC is
 +calculated during processing, to avoid doing this on the critical path.  The
 +CRC calculation is performed as an operation by itself since the overhead is
 +very small on fast processors.  The character-set translation for text files
 +will be performed on the critical path (on the Commodore) since it is more
 +convenient to do it at a higher layer in the IPC scheme.  The packet- handling
 +software is logically at a distinct layer that doesn't have to worry about
 +higher layers.  The next layer up is logically the RPC layer and then the
 +file-transfer layer.
 +
 +5. CLIENT/SERVER OPERATION
 +
 +As discussed previously, the client/server interaction is based on a Remote
 +Procedure Call paradigm.  Thus, for each operation, the client sends a request
 +packet (message) to the server, and the server performs the requested
 +operation and sends back a reply (acknowledgement) message to the client.
 +
 +There are eight request/ack interactions that are defined for the protocol:
 +two for connection management, three for uploading files, and three for
 +downloading files.  The client is in charge of the file-exchange session
 +and of the error handling.
 +
 +4.1. CONNECTION MANAGEMENT
 +
 +When the client starts up, the first thing that it does is connect to the
 +server.  The format of the message that it sends is as follows:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_CONNECT ('C')
 +  1       protocol version := 0x01
 +  2       transmit byte size: '7' or '8' bits
 +  3       SIZE
 +
 +This is what gets put into the the "payload" portion of the packet.  All of
 +the messages used in the protocol have an ASCII letter in the first byte that
 +identifies what the message type is.  Each request has an uppercase letter and
 +each acknowledgement has the corresponding lowercase letter.
 +
 +The connection-request message is fairly simple: it includes the protocol
 +version number and the number of bits wide that the client thinks that the
 +communication channel is.  The version number is currently always 0x01 and is
 +included for cross-compatibility with future versions of the protocol.  The
 +channel width is encoded into either a '7' or an '8' ASCII character.  The
 +client will think that the channel width is seven bits only if you tell it
 +this on the command line.
 +
 +When the server receives the connection request, it replies with the following
 +message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_CONNECT ('c')
 +  1       protocol version := 0x01
 +  2       transmit byte size: '7' or '8' bits
 +  3       recommended request byte size: '7' or '8' bits
 +  4       server maximum text-upload data size: H/M/M/L word
 +  8       server maximum binary-upload data size: H/M/M/L word
 + 12       server maximum text-download data size: H/M/M/L word
 + 16       server maximum binary-download data size: H/M/M/L word
 + 20       SIZE
 +
 +The "protocol version" is what the server is using, currently always 0x01.
 +The "transmit byte size" is the size that the user has specified on the
 +command line that activated the server, and the "recommended request byte
 +size" is a '7' if either the "transmit byte size" of the either the client or
 +server is seven bits, or '8' otherwise.  This is what should be used for the
 +all subsequent messages that are exchanged.
 +
 +The server's reply also includes the maximum packet sizes that it can handle
 +for uploading and downloading binary and text files.  The client then takes
 +the "min" of the server's maximum packet sizes and its own, and uses the
 +resulting maximum packet sizes for the rest of the file exchange session.  The
 +maximum packet sizes in the server's reply are all 32-bit unsigned integers
 +that are stored from most-significant to least-significant bytes (big endian
 +order).  I picked big-endian order because that is the order used most
 +commonly in inter-machine protocols.
 +
 +The reason that the client doesn't have to inform the server of the client's
 +maximum packet sizes in its connection message is that the maximum packet
 +size to use is included with each request to get the next packet of a download
 +file.  It is sufficient that the client knows the full max-packet information.
 +Really, the "transmit byte size" field isn't needed in the server reply
 +message either, but I wanted the packet-size fields to be size-aligned.
 +
 +After all of the file exchanging is completed, the client sends the following
 +message to terminate the connection and return the server back to its command-
 +line mode:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_DISCONNECT ('Q')
 +  1       SIZE
 +
 +When the server receives this request, it replies with:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_DISCONNECT ('q')
 +  1       SIZE
 +
 +And then exits like it should.  Note that once the server exits, it cannot
 +accept any more packets, since they would be sent to whatever command shell
 +you use on your Unix system, and wouldn't do anything useful, so if the client
 +sends the disconnect message but doesn't receive any reply, it will time out
 +and tell the user that it couldn't disconnect cleanly from the server.  This
 +should be a rare occurrence.  Anyway, what the user would do then is re-enter
 +his terminal program and send Ctrl-X's at the server until it exits like it
 +should have.
 +
 +This arrangement allows us to avoid the famous(?) "two armies" problem that is
 +inherent in disconnecting two connected processes: there is no "clean" way to
 +do it.  What systems like Z-Modem and Berkeley Sockets do is to have the
 +server wait for a period of time that is longer than N times the timeout
 +period of the client so that if there is a retransmission of the disconnection
 +request, it likely that it will be received and processed correctly by the
 +server.  This is the reason (presumably) that Z-Modem does an annoying pause
 +of 15 seconds or so after you finish transferring files.  I think that my
 +solution is much nicer, since the server can exit immediately (even though my
 +server delays for 1 second, just so that your shell prompt will be cleanly in
 +your modem's ARQ buffer when you re-enter your terminal program, if you have a
 +hardware-flow-control modem).
 +
 +4.2. FILE UPLOADING
 +
 +Okay, so between connecting to and disconnecting from the server, actual
 +
 +useful stuff happens, including uploading and downloading files.  The
 +uploading and downloading requests operate much like the regular file
 +operations of open, close, read, and write.  Really, the FX protocol makes the
 +server program a special kind of file server.
 +
 +When the client decides that it wants to upload a file, it first informs the
 +server about this by sending the following message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_UPLOAD_OPEN ('U')
 +  1       data type: 't'=text file, 'b'=binary file: 'd'=directory
 +  2       estimated file size: H/M/M/L word
 +  6       permissions ("-----sgr:wxrwxrwx"), like Unix, H:L
 +  8    12   modified date: BCD format: <YY:YY:MM:DD:hh:mm:ss:tt:tw:GG:gg:aa>
 + 20       filename, null-terminated
 +20+n    -   SIZE
 +
 +The "data type" field tells whether a text or binary file will be uploaded.
 +There is a provision for "uploading" a directory entry (as part of uploading
 +and downloading entire directory hierarchies), but support for this is not
 +implemented yet.  Also, it makes no difference to a Unix system whether a file
 +contains text or binary data, but it may make a difference to other operating
 +systems (like Mess-DOS).  The "estimated file size" field isn't really used
 +either, but it allows the server to make intelligent decisions about
 +pre-allocating space, buffering, etc., if it needed to.  However, it is
 +currently not filled in by the client, since file-size information is
 +difficult to extract from Commodore-DOS.  The file size is an unsigned 32-bit
 +quantity.
 +
 +The permissions field is currently not supported by the server, but it is
 +intended to allow file permissions to be preserved when passing files from one
 +system to another.  The interpretation of the 16 bits of this field is like it
 +is with the Unix operating system: "rwx" bits for the owner, group, and other,
 +and execute-as-owner, execute-as-group bits.  The owner-id and group-id fields
 +aren't included since they are generally not portable across systems, and even
 +if they were, we usually want to receive files as our own owner-id and our own
 +group-id.
 +
 +The "modification date" field is not currently filled in either, since this
 +information is even harder to come across with Commodore-DOS, but when it is,
 +it will have a 12-byte BCD format.  The "YY:YY:MM:DD:hh:mm:ss" sub-fields
 +should be easy enough to figure out, and the "tt:t" fields contain thousandths
 +of seconds.  The "w" field contains the day of the week, coded as 0-6 for
 +Sunday to Saturday, and 7 for "unknown" The "GG:gg" fields contain the
 +number of hours and minutes that your time zone is off from GMT.  If the
 +number is negative (in the western hemisphere), then the regular positive
 +number of hours will be used, execept that the 0x80 bit of the hours byte will
 +be set.  Finally, the "aa" sub-field is used to encode the accuracy of the
 +timestamp.  The way that it is interpreted is that the time value is accurate
 +to plus/minus 2^aa milliseconds.  For example, if my clock were accurate to
 +within one second, then this field would be set to 10 in BCD (2^10 ==
 +1024ms).  A value of 99 means "unknown" (or that the clock could be off by
 +many billions of billions of years).
 +
 +I decided to go all out in defining the date field so that it will be useful
 +in the future when "world consciousness" will be much more important than
 +it is today.
 +
 +And last but certainly not least, the filename is encoded in ASCII with a
 +trailing zero byte.
 +
 +Upon receiving this request, the server will attempt to create a file
 +according to your specifications, and will send back a reply of the form:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_UPLOAD_OPEN ('u')
 +  1       error code: 'y'=successful, 'n'=open unsuccessful
 +  2       SIZE
 +
 +The "error code" field tells whether the open operation was successful or
 +not.  If it was, then the client can continue with uploading its file; if not,
 +then that file cannot be uploaded (and that the upload channel doesn't need to
 +be closed).  It's up to the client whether to go on to the next file, abort,
 +or ask the user for help.  The client will currently report an error to the
 +user and then go onto the next file.  Of course, it's likely that whatever
 +caused the error in creating the current file will also cause an error in
 +creating subsequent files (insufficient access permissions on the current
 +directory, disk full, etc.).  The server will overwrite any existing file
 +with the same name (since asking permission, etc., would require extra
 +mechanism, and would probably be a nuisance anyway).
 +
 +If the upload channel is opened successfully, then the packets of upload
 +data should be sent to the server one at a time, until all of the data is
 +uploaded.  The client sends the following message to the server to upload
 +a packet of data:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code; REQ_UPLOAD_PACKET ('R')
 +  1       upload sequence number
 +  2       data length: H/M/M/L word
 +  6       data
 +6+n       SIZE
 +
 +The "upload sequence number", which was described before, is used to make sure
 +that retransmissions of packets are detected and handled properly, so that
 +each packet of data only appears in the file once.  The "data length" field
 +tells the number of user data bytes that follow in the packet, and then the
 +actual user data bytes appear.  The "data length" field is actually redundant,
 +but I figured that it would make programming a little easier, and allows
 +additional error checking.  Normally, each upload-data packet will contain
 +the maximum-packet-size number of bytes of user data (according to whether
 +text or binary data is being uploaded), except for the last packet, which
 +will contain the number of data bytes that are left in the file.  However,
 +each packet is allowed to contain anywhere from 1 to the maximum-packet-
 +size number of bytes: whatever the client wishes to use.  Variable-sized
 +packets are a Good Thing (TM, Pat. Pend.).  You will note that the data-
 +size values are also what will be used for the "read" and "write" system
 +calls on the client and server, respectively.  I/O will be done in big,
 +efficient chunks.
 +
 +Upon receiving each upload packet, the server replies with the following
 +acknowledgement message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_UPLOAD_PACKET ('r')
 +  1       upload sequence number
 +  2       SIZE
 +
 +I don't think that the "sequence number" field is actually necessary here, but
 +it is included to allow for future expansion and to provide redundancy for
 +protocol-error checking.
 +
 +When the client has uploaded all of the packets of the file currently being
 +uploaded, it then sends the following message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_UPLOAD_CLOSE ('V')
 +  1       SIZE
 +
 +This will close the upload channel and will finish writing the uploaded file
 +to the Unix file system.  The server will then respond with the following
 +message to acknowledge the request:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_UPLOAD_CLOSE ('v')
 +  1       number of bytes uploaded: H/M/M/L word
 +  5       SIZE
 +
 +The "number of bytes" field is actually redundant, but is used for additional
 +error checking.
 +
 +4.3. FILE DOWNLOADING
 +
 +Downloading files is analogous to uploading them: first we open the download
 +channel/file, then we download the packets, and then we close the download
 +channel.
 +
 +To open the download channel, the client sends the following request to the
 +server:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_DOWNLOAD_OPEN ('D')
 +  1       SIZE
 +
 +To which the server replies with:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_DOWNLOAD_OPEN ('d')
 +  1       data type: '0'=no more files (eom),'t'=text,'b'=bin,'e'=err,'d'=dir
 +  2       estimated file size: H/M/M/L word
 +  6       permissions ("-----sgr:wxrwxrwx"), like Unix, H:L
 +  8    12   modified date: BCD format: <YY:YY:MM:DD:hh:mm:ss:tt:tw:GG:gg:aa>
 + 20       filename, null-terminated
 +20+n    -   SIZE
 +
 +The file information is the same as for opening an upload file, except that
 +there are more possible return conditions, and all of the "meta data" fields
 +are actually filled in by the Unix host (since this information is actually
 +conveniently available via the "stat" system call).
 +
 +If the server replies with a '0' "data type" code, then this means that the
 +server has no more files to offer for downloading.  The filenames to download
 +are taken one at a time, from left to right, from the command line that was
 +used to start the server.  When the server runs out, then the downloading
 +session is complete and the client disconnects (since the client uploads
 +its files first).
 +
 +Alternatively, the server could reply with a 'e' code, which means that
 +it could not open the next filename given on its command line.  An error
 +return is generated so that the client can inform the user that the file
 +could not be downloaded.  This will normally result from the user giving
 +a bad filename on the command line.  The client will continue the downloading
 +process by closing the download channel (below) asking for the next file by
 +re-opening the download channel.  The download channel needs to be closed
 +on this condition since otherwise there would be no way of distinguishing
 +retransmissions from new requests at the server.
 +
 +Finally, the server can reply with a 't' or 'b' code ('d' for directories is
 +not currently implemented) indicating that the file was correctly opened and
 +is either text or binary (as specified on the server's command line).  Of the
 +meta information about the file, only the filename and file size are currently
 +used: the file is named according to the given name, translated to PETSCII and
 +truncated to 16 characters, and the file size is reported to the user so that
 +he can monitor downloading progress.  I am not sure what to do yet about name
 +collisions on the Commodore end: either ask the user whether to overwrite the
 +file, automatically overwrite the file anyway, or automatically give the file
 +a slightly different name and download normally.  I think that for the time
 +being, I will just overwrite the existing file.  This will mean that you'll
 +want to be extra careful in putting the filenames onto the correct command
 +line (the client's or the server's), although there won't be a problem if the
 +file doesn't exist on the machine whose command line you put the name on.
 +
 +When the file handling is all squared away and the download channel is opened,
 +the client then sucks packets out of the file until the end of the file is
 +reached.  The packets are sucked out with the following request:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_DOWNLOAD_PACKET ('S')
 +  1       download sequence number
 +  2       maximum acceptable data length: H/M/M/L word
 +  6       SIZE
 +
 +The "download sequence number" is used to distinguish retransmissions from
 +requests for new packets, and the client tells the server the "maximum
 +acceptable data length" for the reply packet.  Although the max-packet
 +information is actually static during the connection, I included it here in
 +every "read" request since I didn't really want the server to keep that
 +particular bit of "state" internally.
 +
 +The server replies to the download-packet request with the following message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_DOWNLOAD_PACKET ('s')
 +  1       download sequence number
 +  2       data length: H/M/M/L word, 0==EOF
 +  6       data
 +6+n       SIZE
 +
 +This is the only "large" message that the server can produce.  It includes the
 +sequence number, the number of bytes that are actually included, and the user
 +data.  The number of data bytes in the packet is allowed to be smaller than
 +the number of bytes requested, but this is normally only the case for the last
 +packet of the file.
 +
 +To indicate that the end of file has been reached and that no more user data
 +is available, the server will return a download packet with zero bytes of user
 +data in it.  Upon receiving this, the client will close the download channel
 +with the following message:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: REQ_DOWNLOAD_CLOSE ('E')
 +  1       SIZE
 +
 +And the server will reply with:
 +
 +OFF   SIZ   DESC
 +---   ---   -----
 +  0       code: ACK_DOWNLOAD_CLOSE ('e')
 +  1       number of file bytes downloaded: H/M/M/L word
 +  5       SIZE
 +
 +The "number of file bytes downloaded" field is redundant but included for
 +additional error checking.  After closing a file, the client will then ask
 +for the next file, or will disconnect if the last file to download was just
 +closed.
 +
 +4.4. ERROR HANDLING
 +
 +With all of the server calls except for disconnecting (discussed earlier), the
 +is the possibility that either the request message from the client or the
 +reply message from the server will become garbled and be dropped by the
 +packet-delivery layer of the software.  To recover from this, if the client
 +detects an extended period of inactivity on the serial line for received data
 +(where "extended period" is defined as being "about five seconds"), then the
 +client will assume that something went wrong and it will retransmit the
 +request.
 +
 +As pointed out way above, there are two possible reasons for a retransmission
 +being needed: either the request packet was corrupted and dropped, or the
 +reply packet was corrupted and dropped.  In the format case, the request
 +wasn't processed by the server, but in the latter case, it was.  Since we
 +don't want the server to perform an file operation twice (this is really
 +what the six file-transfer client operations really boil down to from the
 +server's perspective), the server must keep four pieces of internal state:
 +the last upload sequence number, the last download sequence number, whether
 +the upload file is open, and whether the download file is open.
 +
 +If an upload-open request is received and the file to be uploaded is not open,
 +the the request must be a new one and the server processes it and sends back a
 +reply like normal.  If an upload-open request is receive and the upload file
 +IS currently open, then it must be the case that the current request is a
 +retransmission, so all theat the server needs to do is to give a positive
 +reply without performing any internal file operations.  The same holds true
 +for the download-open call and for both of the close calls (except that the
 +operation has already been processed if the file is CLOSED).
 +
 +For the packet-upload and packet-download requests, sequence numbers are used
 +to detect duplicates.  You will note that these sequence numbers are distinct
 +from one another, and, in fact, that the entire upload and download file-
 +transfer channels are distinct and independent from each another.  This is to
 +allow for the future possibility of simultaneous file uploading and
 +downloading.  In fact, if stream numbers (file descriptors) were added to the
 +open/read/write/close requests, then we could have us a full-blown remote-host
 +over-the-phone interactive file server.  But anywho, sequence numbers start
 +from 0x00 for the first packet transferred and increment modulo 256 from
 +there.
 +
 +Note that for high-speed data-compression modems (like I have) that already
 +include error detection and recovery at a level hidden from the user, the FX
 +protocol will work particularly well: there will never be an error, never be a
 +timeout delay, and never be a retransmission.  And, really, the CRC-32 error
 +computation and checking is pretty much a zero cost.  But, if something does
 +go wrong, outside of the modem-to-modem connection, the FX protocol is right
 +there to pick up the pieces and carry on.
 +
 +6. CONCLUSION
 +
 +You'll have to wait to get your hands on the program.  The Unix Server
 +program is almost 100% (except for a few design changes that I made while
 +writing this document), and the ACE program is implemented except for
 +the error handling and text conversion.  Both programs will be released
 +with the next release of ACE, which will be Real Soon Now (TM).
 +
 +Here is my performance testing so far, using my USR Sportster modem over a
 +14.4-kbps phone connection, with a 38.4-kbps link to my modem from my C128, to
 +my usual Unix host:
 +
 +Using FX to/from the ACE ramdisk, REU:
 +
 +Download 156,260 bytes of ~text:        time= 54.1 sec, rate=2888 cps.
 +Download 151,267 bytes of tabular text: time= 45.9 sec, rate=3296 cps.
 +Download 141,299 bytes of JPEG image:   time= 92.5 sec, rate=1528 cps.
 +Upload   156,260 bytes of ~text:        time= 57.4 sec, rate=2722 cps.
 +Upload   151,267 bytes of tabular text: time= 45.3 sec, rate=3339 cps.
 +Upload   141,299 bytes of JPEG image:   time= 95.0 sec, rate=1487 cps.
 +
 +Using FX to/from my CMD Hard Drive:
 +
 +Download 156,260 bytes of ~text:        time= 83.4 sec, rate=1874 cps.
 +Download 151,267 bytes of tabular text: time= 75.4 sec, rate=2006 cps.
 +Download 141,299 bytes of JPEG image:   time=118.2 sec, rate=1195 cps.
 +Upload   156,260 bytes of ~text:        time= 77.9 sec, rate=2006 cps.
 +Upload   151,267 bytes of tabular text: time= 66.2 sec, rate=2285 cps.
 +Upload   141,299 bytes of JPEG image:   time=114.2 sec, rate=1237 cps.
 +
 +Using DesTerm-128 v2.00 to/from my CMD Hard Drive, Y-Modem:
 +
 +Download 156,260 bytes of ~text:        time=189.5 sec, rate= 824 cps.
 +Download 151,267 bytes of tabular text: time=180.4 sec, rate= 839 cps.
 +Download 141,299 bytes of JPEG image:   time=199.9 sec, rate= 707 cps.
 +Upload   156,260 bytes of ~text:        time=255.1 sec, rate= 611 cps.
 +Upload   151,267 bytes of tabular text: time=238.6 sec, rate= 634 cps.
 +Upload   141,299 bytes of JPEG image:   time=233.0 sec, rate= 606 cps.
 +
 +Using NovaTerm-64 v9.5 to my CMD Hard Drive, Z-Modem, C64 mode:
 +
 +Download 156,260 bytes of ~text:        time=245.8 sec, rate= 636 cps.
 +Download 151,267 bytes of tabular text: time=230.0 sec, rate= 658 cps.
 +Download 141,299 bytes of JPEG image:   time=262.6 sec, rate= 538 cps.
 +
 +(There is no Z-Modem uploading support)
 +
 +So there you have it: my simple protocol blows the others away.  QED.
 +========================================================================
 +</code>
 +====== DESIGN AND IMPLEMENTATION OF A 'REAL' OPERATING SYSTEM FOR THE 128: PART II ======
 +<code>
 +
 +by Craig S. Bruce  <csbruce@ccnga.uwaterloo.ca>
 +
 +0. PREFACE
 +
 +There has been a slight change in plans.  I originally intended this article
 +to give the design of a theoretical distributed multitasking microkernel
 +operating system for the C128.  I have decided to go a different route: to
 +take out the distributed component for now and implement a real multitasking
 +microkernel OS for a single machine and extend the system to be distributed
 +later.  The implementation so far is, of course, only in the prototype stage
 +and the application for it is only a demo.  Part III of this series will
 +extend this demo system into, perhaps, a usable distributed operating system.
 +
 +1. INTRODUCTION
 +
 +The previous article talked about the general approach to building a
 +multitasking microkernel OS for the C128.  It is assumed here that you have
 +read and understood the previous article.  This article goes into the grungy
 +details of implementing such a beast.  The prototype kernel implementation
 +provides system calls to create and "exit" user processes, obtain status
 +information, delay execution of a process for a specified period of time,
 +and to perform message-passing interprocess communication.
 +
 +Currently, there is no real memory management, no real device drivers, and no
 +process-resource reclamation.  More "infrastructure" features need to be added
 +before a command-shell environment or any such thing could be supported,
 +though not toooo many more; the Commodore-Kernal Server in the demo system
 +makes the $FFD2 (CHROUT) routine of the Commodore Kernal available to all
 +other processes in the demo system.  It could easily be modified to provide
 +all of the Commodore-Kernal features to the other processes, thereby giving
 +us a basic I/O sub-system.
 +
 +There is also no way to dynamically load external programs, so the test
 +programs have to be assembled with the kernel code.  Loading external programs
 +in this type of environment has the requirement that the program will have to
 +be relocated upon being loaded to an address that would only be known at load
 +time.  There are two ways to go on this: load all programs to a fixed address
 +or load them to dynamic addresses.  If you load them to a fixed address, then
 +you can only have one processes loaded and concurrently running on each bank
 +of internal memory of the C128 and then demand-swap all of the other processes
 +into and out of these (two) slots, presumably from REU memory (any other type
 +would be much too slow).  IHMO, even with REU memory, this would be too slow,
 +especially for a microkernel environment.  So, programs will need to be
 +loaded to dynamic addresses.  Fortunately, I have a program-relocation
 +mechanism in the works.
 +
 +The entire kernel and the demo program fits in C128 memory in the slot between
 +$1300 and $1BFF of RAM0.  The kernel uses storage from $C00-$CFF and
 +$2000-$BFFF.  The latter section of memory is used up in 768-byte chunks by
 +each new process, so there can be a total of 53 concurrently executing user
 +processes in the system.
 +
 +2. TEST PROGRAM
 +
 +The test program includes no provisions for user interaction, so it may not
 +be something that you can impress your friends with, but I can assure you
 +that all kinds multitasking stuff is going on behind the scenes to make
 +everything happen.
 +
 +The demo test program creates ten processes.  There are five "delay"
 +processes, two "blabbering" processes, a Commodore-Kernal-Server process, one
 +SID-banging process, and the Null process.  (Note that when I use the word
 +"kernel" I am referring to the OS that I have written, and when I use the word
 +"Kernal", I am referring to the Commodore Kernal in ROM).
 +
 +The purpose of the Commodore-Kernal Server is to receive requests from the
 +worker processes to call the CHROUT routine to print a given line of text out
 +to the screen.  The Kernal server is the only processes that is allowed to
 +call the Commodore-Kernal routines.  Since it can only process one request
 +from a client process at a time, calls to the Commodore Kernal are effectively
 +"serialized" (made to happen one after another in time), which is good, since
 +things would blow up pretty badly if two accesses the Commodore Kernal were to
 +happen concurrently.
 +
 +The "delay" processes, numbered from 1 to 5, each delay for a period of N
 +seconds and then request the Kernal Server to print a "I'm alive" message to
 +the screen.  The number N of seconds to delay is the number of the process.
 +You should be able to observe from watching the execution that each delay
 +process prints a message to the screen with approximately the correct period
 +between messages.  Note that while these processes are delaying, they don't
 +use any CPU time, so the CPU time is allocated to other processes.  If you
 +try holding down the C= key to slow the scrolling or run the system in Slow
 +mode, you will still notice that the delay processes generate their output
 +at approximately the right time.
 +
 +The two "blabbering" processes, named "blabber" and "spinner", continually
 +send print messages to the Commodore-Server process.  You will observe that
 +the messages from each comes pretty much prefectly interleaved with each
 +other, and with the output of the delay process, because of the interprocess
 +communication scheduling policy: FIFO (first-in, first-out).
 +
 +The SID-banging process runs continuously in the background.  It increments
 +the 16-bit frequency of voice #1 from $0000 to $ffff and then suspends its
 +execution for two seconds and then repeats.  A square wave with even pulse
 +widths is used.  When the SID process delays for two seconds, you will notice
 +that the printing operation of the other processes speeds up a bit.  This is
 +because the SID process is a heavy CPU user while it is active.  The amount of
 +slow-down of the rest of the system is limited a bit because the SID process
 +runs with a slightly lower priority than the other processes in the system
 +(which makes the sound increment slightly slower than it otherwise would --
 +there's only so much CPU to go around).
 +
 +The Null process is not actually needed in this exercise, but it would
 +normally be used to insure that the system always had some process to
 +schedule.  All that it does is increment the binary value in locations
 +$0400-$0403 (on the 40-column screen) whenever it is active.  It has the
 +lowest priority in the system and never gets to execute unless all of the
 +other processes are blocked (i.e., are suspended for some reason).
 +
 +When you get tired of watching the demo, you can just hit the RESTORE key.
 +This will cause an NMI which will make the system exit back to BASIC.  The
 +system does not have the ability to handle external events (like key strokes)
 +at this time.  A couple of locations on the 40-column screen are used for
 +status information, so you will want to run the demo on the 80-column screen.
 +
 +3. PROCESS CONTROL
 +
 +A process is a user program that is in an active state of execution.  A
 +process is periodically given a certain amount of CPU time to execute its
 +code, and then CPU attention is taken away from it to execute other
 +processes.  This may sound like you're simply making N processes run N times
 +slower, and this is true in the worst case, but the normal case is that many
 +processes in the system will be blocked (for whatever reason) and will not
 +require any more CPU time until they wake up again (for whatever reason).
 +Therefore, multitasking is a "winnable" proposition.
 +
 +In our system, the process that the CPU is currently executing is changed
 +every 1/60 of a second.  This is a convenient "quantum" period for a number of
 +reasons, including the fact that, thanks to the MMU of the C128, "context
 +switching" can be efficiently performed this quickly.
 +
 +3.1. PROCESS-CONTROL CALLS
 +
 +There are six kernel calls that deal with process control:
 +
 +CALL NAME     INPUT ARGUMENTS                  RETURN VALUES
 +-----------   ------------------------------   -------------------------------
 +Create        ( .AY=address, .X=priority )     .AY=newPid, .CS=err(.A=errcode)
 +Exit          ( .A=code, .X=$00 )              <none>
 +MyPid         ( )                              .AY=pid
 +MyParentPid   ( )                              .AY=parentPid
 +Suspend       ( )                              <none>
 +Delay         ( .AY=jiffies )                  <none>
 +
 +3.1.1. CREATE
 +
 +The Create() kernel call is used to create a new process.  The first input
 +argument is the code address, and it is passed in the .AY register (.A is
 +loaded with the low byte of the address, and the .Y register is loaded with
 +the high byte of the address--.AY for short).  The code must be present in
 +memory and be ready to be executed, since there is no facility for loading
 +external programs.  Also, if the code is not re-entrant, then there must be no
 +other process already executing it or things will likely blow up.  Re-entrant
 +code is code that can be executed by multiple processes simultaneously without
 +conflicts, essentially because there are no global variables that could be
 +banged on by more than one process at a time.
 +
 +The priority argument is the priority to execute the new process at.  Valid
 +values for this argument are on the range 0 to 127.  The system keeps a list
 +at all times of all the processes that are ready to execute, called the "ready
 +list" The way that the scheduling works is that a pointer to the "active"
 +process is kept (the one that is currently executing) and this pointer cycles
 +through the ready list, trying to activate each process in turn, every 1/60 of
 +a second.  This is roundrobin scheduling.  The priority of a process
 +determines the number of cycles that the active-process pointer has to take
 +through the list before the process is activated.  So, if a process has
 +priority 1, then it will be activated on every round; if it has a priority of
 +2, then it will be activated on every second round; and if it has a priority
 +of 86, then it will be activated only on every 86th round through the ready
 +list.  The higher the priority value, the slower the process executes.  This
 +policy gives a fair allocation of the CPU to the various processes in the
 +system.
 +
 +Normally, foreground processes (ones that perform actions right in front of
 +the user's face) should have a priority of 1, and background processes should
 +have a relatively lower priority.  A priority value of 0 for a process means
 +that when the process is activated, it will not be deactivated again until it
 +blocks for some reason.  This priority level should be reserved for urgent
 +computations that block often, since it has the potential to starve out the
 +rest of the system.  The Null process executes at a special priority level
 +(255) that makes it so that it will only be activated if there are no other
 +processes in the ready list.
 +
 +The Create() call returns with the carry flag clear and the process id of the
 +newly created process in the .AY register upon success, or returns with the
 +carry flag set and an error code in the .A register upon failure.  I do not
 +have the complete list of error conditions figured out a this time, but errors
 +will usually happen on a call like this because of a lack of resources
 +(memory) for the kernel's internal data structures.  Upon successful return,
 +the child process is created, made ready, and may be activated by the system
 +at any time.  The first instruction to be executed by the child will be at the
 +value given for the code address.
 +
 +The newly created process will have a clear individual stack, except for a
 +couple of bytes on the very bottom of it (high addresses), and a clear
 +individual zeropage.  Processes are allowed to make full use of every location
 +in their zeropage, except for the I/O registers at locations 0 and 1, and full
 +use of their stack, except that they must make sure that about a dozen bytes
 +are available on the stack at all times in case an interrupt happens.
 +
 +3.1.2. EXIT
 +
 +The Exit() kernel call is used to remove the current process from the system.
 +There are two input arguments: the .A register contains the return code that
 +will be made available to the parent process if it is interested (though not
 +in the current implementation), and a value in the .X register, which must
 +currently be $00.  I haven't figured out exactly how the exit mechanism should
 +work yet and it currently only has a minimal implementation.  The call does
 +make the kernel reclaim the resources that were allocated to the process yet,
 +although this functionality will be needed in any real operating system.
 +
 +There is no return value from the Exit() call, because the call never returns.
 +The semantics of the call is the the process calling Exit() will never be made
 +active again.  All processes should call Exit() when they are finished
 +executing, or they can achieve the same result by executing an RTS instruction
 +at the end of their main routine.  The kernel pushes the address of an Exit()
 +stub routine onto the top of the stack of a user process when it is created,
 +and the user process will exit with a return code of $00 in this case.
 +
 +3.1.3. MY_PID
 +
 +The MyPid() kernel call is used to return the process identifier of the
 +current process.  This call is very simple, takes no arguments, and executes
 +very quickly.  The return value is the process id of the calling process and
 +is returned in the .AY register.  This call cannot fail, because the current
 +process must exist in order to make the call in the first place, so there is
 +no error-return condition.
 +
 +3.1.4. MY_PARENT_PID
 +
 +The MyParentPid() kernel call is used to return the process identifier of the
 +parent process of the current process (i.e., the process that created the
 +current process).  This call is simple, takes no arguments, and executes
 +quickly, very much like the MyPid() call.  No error returns are possible, and
 +the process id of the parent to the current process is returned in the .AY
 +register.  But note: it is not guaranteed that the parent process will still
 +exist either before or after the current process makes this call; it may have
 +Exit()ed.  I may re-think this semantic.
 +
 +This call is useful for setting up interprocess communication between a child
 +process and its parent.
 +
 +3.1.5. SUSPEND
 +
 +The Suspend() kernel call is used to suspend the execution of the currently
 +executing process for an indefinite period of time.  Currently, this period of
 +time is forever, since there is no corresponding "Resume" system call that
 +another process can call in order to wake up the process that suspended
 +itself.  The reason that this call is made available is because the guts of
 +what it does is required by other kernel operations, and the cost of making
 +this call user-accessible was three 6502 instructions.  This call may be
 +retracted in the future, since it may cause programmers to do bad things.
 +
 +The call takes no arguments, returns no values, and currently, will never
 +return at all, much like Exit().
 +
 +3.1.6. DELAY
 +
 +The Delay() kernel call is used to suspend the execution of the current
 +process for a user-specified period of time.  The delay period is given in
 +units of jiffies (1/60ths of a second).  The unsigned 16-bit delay period is
 +passed in in the .AY registers, giving a maximum possible delay period of
 +about 18 minutes.  If a user process requests to delay for a period of zero
 +jiffies, its execution will not be suspended at all and the Delay() primitive
 +will return immediately.
 +
 +Since there may be other processes in the system doing things when the current
 +processes wakes up after doing a delay, you can think of the process delaying
 +for "at least" the period of time that you specify.  Actually, to muddy things
 +even more, your process will always go to sleep at a moment in time that is
 +inbetween two ticks of the jiffy clock, so the first "jiffy" that your process
 +waits may actually be any period between a couple of microseconds to almost a
 +full jiffy, with a statistical average of half a jiffy.  This is an artifact
 +of any coarse-tick-based mechanism.
 +
 +To muddy things again, the jiffy ticks, which are currently based on VIC
 +raster interrupts (one per screen update), may not be processed immediately
 +when they occur, since the IRQ may be delayed by a small period of time if
 +interrupts are disabled in the processor status register when the jiffy tick
 +happens.  And finally, you should note that you will have a difficult time
 +using this call for true "real time" periodic operations, like performing some
 +specific task precisely every tenth of a second, since the call specifies a
 +period to delay for, rather than a time to wake up at.  The actual period of
 +your process' activations will be determined by the waiting time plus the time
 +skew caused by the processing that your process does.  A DelayUntil() call
 +easily could be implemented, if I figure that it will be needed for anything.
 +
 +Currently, the scheduling policy is to make processes active immediately after
 +they are awakened, so this makes the activities of other processes less of a
 +worry to accurate timing.  Unix does a similar thing by giving a freshly
 +awakened process a temporarily high priority, since it is probably likely that
 +the process will do some small think and then block again.  This policy
 +statistically improves concurrency.
 +
 +3.2. PROCESS CONTROL BLOCKS
 +
 +A Process Control Block (PCB) is the data structure that the kernel keeps the
 +information that it needs to know about a process in.  A Process identifier
 +(pid) is actually the RAM0 address of the process control block of a process,
 +for convenience, though this will have to change later.  The fields of the
 +process control block are shown here, organized into classes:
 +
 +OFF   SIZ   CLASS   LABEL
 +---   ---   -----   ------
 +  0       queue   pcbNext
 +  2       queue   pcbPrev
 +  4       queue   pcbIsHead
 +  5       queue   pcbQCount
 +  6       ctxt    pcbSP
 +  7       ctxt    pcbStackPage
 +  8       ctxt    pcbZeroPage
 +  9       ctxt    pcbD506
 + 10       sched   pcbPriority
 + 11       sched   pcbCountdown
 + 12       sched   pcbWakeupTime
 + 12       ipc     pcbSendMsgPtr  (overlap)
 + 12       ipc     pcbRecvMsgPtr  (overlap)
 + 14       ipc/  pcbSendQHead
 + 16       ipc/  pcbSendQTail
 + 18       ipc/  pcbSendQFlag
 + 19       ipc/  pcbSendQCount
 + 20       ipc     pcbBlockedOn
 + 22       ipc     pcbReceiveFrom
 + 24       proc    pcbParent
 + 26       proc    pcbState
 + 27             SIZE
 +
 +3.2.1. QUEUE-CLASS FIELDS
 +
 +The first four fields, of the class "queue" are used for maintaining a process
 +control block in queues with other PCBs.  Some general-purpose queue-handling
 +routines have been written to make queue management easier:  QueueInit(),
 +QueueInsert(), and QueueUnlink().  Each queue has a head node, and the nodes
 +in a doubly linked circular order.  This means that each node in the queue has
 +a forward ("pcbNext") and a backward ("pcbPrev") pointer and that the first
 +node points back to the head and the last node in a list points forward to the
 +head.  This organization removes all of the quirks of handling null pointers
 +from the code.  Using a doubly linked organization makes it easy to remove an
 +arbitrary node from the middle of a queue.
 +
 +Each node also has a "pcbIsHead" field which is always False (zero) and a
 +"pcbQCount" field which is always zero.  The head is the same as an entry in
 +the queue, except that its "pcbIsHead" field is set to True ($ff) and its
 +"pcbQCount" field records the number of nodes that are in the queue at any
 +time.  The "pcbIsHead" field is checked when scanning a list to tell if you've
 +bumped back into the head node again, indicating the end of the list.  The
 +"pcbQCount" field is very convenient to check to see whether the queue is
 +empty or not.
 +
 +All of the processes that are ready to execute in the system are kept in the
 +ready queue.  The PCB of the Null process acts as the head for this queue, and
 +is also an active node in the queue (a small but harmless kluge).  The pointer
 +to the active process is kept in a kernel-zero-page variable, and sweeps
 +through the circularly linked ready-process list to activate new processes.
 +The active PCB is not removed from the ready list while it is active.
 +
 +3.2.2. CONTEXT-CLASS FIELDS
 +
 +The next four fields, of the class "ctxt", store the "context" of a process
 +that is not stored on the process' stack when it is not executing.  These
 +fields include space for the stack pointer, the stack page, the zeropage, and
 +the contents of the MMU register at location $d506.  The stack pointer is
 +what was in the SP register of the CPU when the process last paused.  The
 +stack page and the zeropage values are the values in MMU registers $d505 and
 +$d507, respectively; these are the page numbers of the pages in RAM0 memory
 +that are allocated to a process.  These pages can only be in RAM0 unless
 +common memory is disabled, for hardware reasons.  I may allow these pages to
 +be in either RAM0 or RAM1 if there is a need later.  The $d506 register of
 +the MMU stores the most-significant bits of the RAM bank that is selected if
 +you have expanded internal memory on your 128 (a la TwinCities-128) and the
 +bank selection for REU (DMA) operations.
 +
 +The rest of a process' context is stored on its stack.  Here is what a
 +process' stack looks like just after it has been created:
 +
 +ADDR   SP-REL   DESCRIPTION
 +----   ------   ------------
 +$ff    sp+09    exitaddr-1.h
 +$fe    sp+08    exitaddr-1.l
 +$fd    sp+07    pc.h
 +$fc    sp+06    pc.l
 +$fb    sp+05    status register
 +$fa    sp+04    .A
 +$f9    sp+03    .X
 +$f8    sp+02    .Y
 +$f7    sp+01    $ff00 save
 +$f6    sp+00    -empty-
 +
 +The "exitaddr" is the address of the routine in the kernel that will terminate
 +a process if it executes an RTS from its main routine.  The address is in the
 +regular low-high order (although it is pushed on high-low since the stack
 +grows downward in memory) but the value pushed is actually one less than the
 +address of the routine, because this is what JSR pushes onto the stack and
 +this is what RTS expects to find.  The "pc" low and high fields give the
 +address of the next instruction to be executed by the process when it is
 +activated.  When the process is first created, this will be the address of the
 +first instruction.  The "pc" value is the actual address, not one before,
 +because this is what a hardware interrupt pushes onto the stack, and this is
 +what the RTI instruction expects to find.
 +
 +The "status register", ".A", ".X", and ".Y" fields contain the values to be
 +loaded into the corresponding registers inside of the CPU when the process is
 +activated.  For a new process, these values are all zero.
 +
 +The "$ff00 save" is the value to be loaded into the $ff00 "shadow" register of
 +the MMU when the process is activated.  This gives the memory context that the
 +process is to execute in.  As the kernel currently only works with one bank
 +configuration (RAM0, NO BASIC ROM, I/O enabled, KERNAL ROM enabled), this is
 +the value put here when a process is created.
 +
 +The final field is "-empty-" because the stack pointer in the 6502 points to
 +the next location in stack memory that will be used.  All other values in the
 +stack are relative to this.  Upon startup, the stack-pointer field of the
 +process control block will be set to $f6, which is what the table above
 +shows.
 +
 +The stack contents look exactly the same after a process has been interrupted
 +by a hardware interrupt, except that the stack pointer will likely be lower in
 +the stack memory, so the absolute addresses in stack memory in the above table
 +no longer apply and the "exitaddr" bytes are not part of the interrupt
 +context.  That things look the same is no coincidence; on startup, we set up
 +the stack to make things look as if an interrupt had just occurred, and to
 +start a process executing, we execute the code that returns from an interrupt,
 +which loads the "context" that is on the stack into the process registers, and
 +we are ready to rock.
 +
 +The above stack organization is exactly the same as it is for processing
 +interrupts normally using the Commodore-Kernal environment on the 128, and
 +this too is no coincidence because the code that sets up the stack like this
 +upon an interrupt is burned into the Kernal ROM and there is very little that
 +I can do about it.  Fortunately, the organization is just fine for our
 +purposes.
 +
 +3.2.3. SCHEDULE-CLASS FIELDS
 +
 +The next three fields, of class "sched", are used to schedule the process.
 +The "pcbPriority" field gives the relative priority of the process according
 +to the scheme already discussed.  The "pcbCountdown" field is used to keep
 +count of the number of remaining times that the process will have to be
 +bypassed in cycling through the ready queue before the process will be
 +activated again.  When a process gives up the CPU upon the expiration of its
 +time quantum, the "pcbCountdown" field is loaded with the pcbPriority of the
 +process.  When the "pcbCountdown" value reaches zero, the process is selected
 +for activation.
 +
 +The "pcbWakeupTime" field is used with the Delay() kernel call to indicate the
 +absolute system time when the process should be activated again.  The current
 +time in the system is kept in a 16-bit kernel variable, and wraps around every
 +18.2 minutes.  If the process is not currently time-delayed, then the
 +WakeupTime field is not used (in fact, the memory may be used to record other
 +status information).
 +
 +3.2.4. IPC-CLASS FIELDS
 +
 +The eight eight fields, of classes "ipc" and "ipc/q" are used for interprocess
 +communication (message passing).  The first two fields, "pcbSendMsgPtr" and
 +"pcbRecvMsgPtr", are used for storing temporary values for handling message
 +requests; the four "ipc/q"-class fields are used to implement the head of a
 +queue of processes that are waiting to communicate with the current process,
 +and the "pcbBlockedOn" field indicates which process this process is waiting
 +to communicate with, if the current process is waiting.  The first two fields
 +actually overlap with each other and with the "pcbWakeupTime" field discussed
 +earlier.  This is okay since none of the fields will store active status
 +information at the same time.  The last field, "pcbReceiveFrom" is not used at
 +this time, but will be used in the future for an primitive to receive a
 +message only from a specific process.  The interprocess communication is
 +discussed in much greater detail later.
 +
 +3.2.5. PROC-CLASS FIELDS
 +
 +The final two fields, of class "proc", store information about the status of
 +the process.  I guess the same can be said of all the other fields.  Anyway,
 +the "pcbParent" field indicates which process is the process that created the
 +current process, and the return value for the MyParentPid() kernel call is
 +taken from this field.
 +
 +The "pcbState" field gives the current state of the process.  Here are the
 +different possible process states:
 +
 +STATE NAME        CODE
 +---------------   ----
 +STATE_READY       $c0
 +STATE_SEND        $c1
 +STATE_RECEIVE     $c2
 +STATE_REPLY       $c3
 +STATE_DELAY       $c4
 +STATE_SUSPENDED   $c5
 +
 +The STATE_READY state means that the process is in the ready queue.  The
 +STATE_SEND, STATE_RECEIVE, and STATE_REPLY states mean that a process is
 +waiting for some interprocess communication primitive to be called by the
 +process that it is communicating with.  The STATE_DELAY state means that the
 +process has called the Delay() primitive and is waiting for some period of
 +real time to pass before it can be activated again.  The STATE_SUSPENDED state
 +means that a process has called the Suspend() or Exit() primitive and will
 +never be made active again (currently, Suspend() and Exit() mean the same
 +thing).
 +
 +The state information is needed for some operations, and will definitely be
 +needed by a Kill()-process operation, to find out what state a process is in
 +so that it can be removed from any queue or whatever it is in, in order to
 +obliterate all information about the process from the system.  Currently,
 +there is no Kill() call.
 +
 +3.3. TASK CREATION
 +
 +When the user calls for the creation of a new process in the system, lots of
 +stuff has to happen.  First, memory for the process control block, zero page,
 +and stack page must be allocated.  Currently, this allocation is performed
 +very simply, by keeping a page pointer and incrementing it every time a page
 +is allocated.  The process control block ends up getting allocated 256 bytes
 +even though it actually requires much less than that.  Thus, each new process
 +chews up 768 bytes of memory space (plus code).  Also, there is currently no
 +mechanism for recovering the memory allocated to a process, which is okay
 +since the mechanism for Exit()ing a process is incomplete too.
 +
 +The address that a PCB gets allocated at is used for the process' PID (process
 +identifier).  This is particularly useful since the real purpose of a PID is
 +to conveniently locate the process control block.  This will have to change
 +in the future, however, since the PIDs will also have to locate the machine
 +that a process is on.
 +
 +Then the process control block must be initialized.  It is initialized as
 +follows:
 +
 +FIELD            SIZ   CLASS   INITIAL VALUE
 +--------------   ---   -----   --------------
 +pcbNext            2   queue   0
 +pcbPrev            2   queue   0
 +pcbIsHead          1   queue   0
 +pcbQCount          1   queue   0
 +pcbSP              1   ctxt    $f6
 +pcbStackPage         ctxt    set to newly allocated space
 +pcbZeroPage        1   ctxt    set to newly allocated space
 +pcbD506            1   ctxt    $04
 +pcbPriority        1   sched   set to the given argument
 +pcbCountdown         sched   set to the same value as "pcbPriority"
 +pcbWakeupTime      2   sched   0 (overloaded field)
 +pcbSendQHead         ipc/  set by the QueueInit() function for empty queue
 +pcbSendQTail         ipc/  set by the QueueInit() function for empty queue
 +pcbSendQFlag         ipc/  set by the QueueInit() function for empty queue
 +pcbSendQCount      1   ipc/  set by the QueueInit() function for empty queue
 +pcbBlockedOn         ipc     0
 +pcbReceiveFrom       ipc     0
 +pcbParent          2   proc    set to the id of the creator process
 +pcbState             proc    STATE_SUSPENDED
 +
 +The values on the top of the stack page are also initialized for the new
 +process, as follows:
 +
 +ADDR   SP-REL   DESCRIPTION       INITIAL VALUE
 +----   ------   ---------------   --------------
 +$ff    sp+09    exitaddr-1.h      high byte of ExitAddr-1: the exit routine
 +$fe    sp+08    exitaddr-1.l      low byte of ExitAddr-1: the exit routine
 +$fd    sp+07    pc.h              high byte of the code-execution address
 +$fc    sp+06    pc.l              high byte of the code-execution address
 +$fb    sp+05    status register   $00
 +$fa    sp+04    .A                $00
 +$f9    sp+03    .X                $00
 +$f8    sp+02    .Y                $00
 +$f7    sp+01    $ff00 save        $0e  (Kernal ROM, I/O, rest RAM0)
 +$f6    sp+00    -empty-           --
 +
 +And now, the new process is ready for action.  We insert it into the ready
 +queue in the next position after the current process, set its state to
 +STATE_READY (actually, both of these operations are performed by the
 +MakeReady() function, which is generally useful and is called from a number of
 +places) and then we exit back to the calling process, returning the process id
 +of the calling process.  I should change this a little bit in the future, to
 +make it exit to the newly created child process if the priority of the child
 +process is greater than the priority of the parent.
 +
 +3.4. CONTEXT SWITCHING
 +
 +Context switching describes the procedure of switching control of the
 +processor from a user process to the kernel and then switching control back to
 +a user process.  Normally, there is only one "style" of context switching in a
 +system, but for a couple of design reasons, BOS actually has three "styles" of
 +context switching: IRQ switching, JSR switching, and quick JSR switching.
 +IRQ-style switching is the one type normally implemented in operating systems
 +for other architectures, so it will be the one that we cover first.
 +
 +IRQ-style context switching involves saving the full context of a process onto
 +its stack and into its process control block, switching into the kernel, doing
 +work, switching back out of the kernel, and reloading the full context of a
 +user process and activating to it.  All of the work of saving and restoring
 +the the stack portion of a process' context is handled by the ROM routines for
 +IRQ (and NMI and BRK) handling.  All we have to do is locate the current
 +process control block, save the zero-page, stack-page, stack-pointer, and
 +$d506 registers into the PCB, and load a $00 into the zero-page MMU register
 +to switch to the kernel's zeropage (where some of the kernel's variables are
 +stored).  Note that the interrupt will be executed using the user process'
 +stack; therefore, enough space should always be available on user stacks to
 +handle this system overhead.
 +
 +When we are done processing the interrupt, we execute the priority-management
 +algorithm that was described earlier to select the next process to activate,
 +and then restore the zero-page, stack-page, stack-pointer, and $d506 registers
 +and execute the ROM stack-handling code for exiting from an interrupt.  Note
 +that there's a chance that we might well be exiting to a different user
 +process from the one that was active when the interrupt occurred.  There
 +aren't many registers to save and restore, so context switching has a fairly
 +low overhead, so there is no problem in doing it (at least) sixty times a
 +second.
 +
 +JSR-style context switching is pretty much the same as IRQ-style context
 +switching, except that the stack will not have most of the processor registers
 +already saved on it; it will only have the return address that performed the
 +JSR.  Immediately upon entering the kernel, interrupts are disabled to prevent
 +all sorts of bad things from happening.  Then a function is called,
 +EnterKernel(), which will pull the return address of the process that called
 +the JSR off the stack and increment it by one (since we will be exiting by
 +using an RTI instruction rather than an RTS) and saves the other processor
 +registers onto the stack in the same way that the interrupt-handling code in
 +ROM would.  Then we save the four additional registers into the PCB as before,
 +activate the kernel zeropage, and we are switched in.
 +
 +This style of context switching is used for kernel calls that will cause the
 +calling process to block (like a non-zero Delay()).  It would have been
 +possible to organize the kernel calls to be entered by executing a BRK
 +instruction, which would have caused the stack to be already set up in the
 +same way as with IRQ interrupts, but I decided against this for two reasons:
 +efficiency, it would have been slower to do this, and debugging (security?),
 +since I only want the BRK condition to signal a bug in the code.  The exit
 +from this type of context switch is the same as for the IRQ style of context
 +switch, since things are rigged to end up looking the same on the stack.  This
 +is a good thing, since the action that will cause a Delay()ed process to be
 +re-activated will, in fact, be an IRQ interrupt.
 +
 +Quick JSR-style context switching is used for kernel calls that will not block
 +or cause a new process to be activated when they finish, such as MyPid() or
 +(currently) Create().  No context has to be saved since the function will get
 +in and out very quickly; all we have to do is switch to the kernel's zeropage
 +and then switch back to the user's zeropage before exit.
 +
 +There's one more note to make about return values.  For the quick JSR-style
 +context switch, there is no problem with return values, since we just have to
 +load them into the processor registers and exit.  With the full JSR-style
 +context switch, the return values have to be put onto the user stack into the
 +positions in the stack memory the hold the processor register contents, since
 +these values will be what are restored into the processor immediately upon the
 +return to the user process.  There are no return values associated with the
 +IRQ style of context switching (and there'd better not be), since an interrupt
 +can happen at any point in the execution of a user process.
 +
 +3.5. DELAY PRIMITIVE
 +
 +There are two complementary halves to the implemention of the Delay()
 +primitive: the half that is called by the user and causes a process to go to
 +sleep, and the half that wakes up a sleeping process at the correct time.
 +This latter half is executed by the 60-Hz system interrupt.
 +
 +3.5.1. USER HALF OF THE DELAY PRIMITIVE
 +
 +The first thing that the user half of the Delay() primitive does is check to
 +see if the delay period is zero jiffies.  If it is, then the primitive returns
 +immediately to the calling process without rescheduling (without skipping to
 +the next ready process in line).  I may change this semantic, because it is
 +often useful to have a primitive that yeilds process execution to the next
 +ready process without actually blocking the current process.
 +
 +If the delay period is longer than zero jiffies, then the current process is
 +suspended and removed from the ready queue, and the absolute time that the
 +process is to be reawakened is calculated and put into the "pcbWakeupTime"
 +field of the PCB for the current process.  The absolute wakeup time is
 +calculated, of course, by adding the number of jiffies to delay to the current
 +absolute time, which is maintained by the system and incremented on every
 +(60 Hz) system interrupt.
 +
 +Then the current process control block is inserted into the delay queue at the
 +correct position.  The delay queue is a queue (implemented in the standard
 +way) of process control blocks for processes which are asleep, ordered by the
 +absolute wakeup time of each process such that the process that will be
 +awakened at the nearest time in the future is at the head of the list and that
 +the process which will be awakened at the farthest point in the future is at
 +the tail.  The following diagram gives an example:
 +
 +CurrentTime = 2016
 +
 +    +---------+      +---------+      +---------+      +---------+
 +--->| Proc A  |----->| Proc B  |----->| Proc C  |----->| Proc D  |
 +    | wakeup: |      | wakeup: |      | wakeup: |      | wakeup: |
 +<---| @ 2345  |<-----| @ 2765  |<-----| @ 54999 |<-----| @ 441   |
 +    +---------+      +---------+      +---------+      +---------+
 +    (ct+5.5sec)      (ct+12.5sec)     (ct+14.7min)     (ct+17.8min)
 +
 +There is a rub here: only 16 bits are used for storing times, which equals
 +about 18.2 minutes, so we have to worry about time quantities overflowing and
 +wrapping around.  For example, if the current time is 48232 and a process
 +wants to sleep for 18000 jiffies (5 minutes), then its wakeup time would be at
 +696 jiffies, accounting for the 16-bit wraparound, which is a lower numerical
 +value than the current time, or than the wakeup time of any other process that
 +will wake up before the current-time wraparound.  In fact, all timers have
 +this wraparound problem (although with 64-bit times, wraparound periods would
 +be expressed in millions of millennia rather than in minutes).  Sixteen bits
 +is a good number of bits to use, however, because that is the maximum delay
 +period (2^16-1).
 +
 +When we insert a new process into the delay queue, we scan the delay queue
 +from the head and continue until we find a record that has a time that is
 +higher than or equal to the wakeup time of the new process (or we hit the end
 +of the queue).  Then, we insert the new process immediately before this
 +point.  To handle the wraparound problem, all comparisons of wakeup times are
 +done using 17 bits (well, really 24 bits).  For each value in the comparison,
 +we add 65536 to it (set its 17th bit) if the value is less than the current
 +time.  We don't have to worry about the current time changing while we are
 +doing this, because interrupts will be disabled for the entire time that
 +we are executing the system call, as per usual.  Things could go horribly
 +wrong anyway if interrupts were not disabled.
 +
 +Okay, so now our delaying process is removed from the ready queue, its
 +complete context is saved, and it is put into the delay queue at the right
 +spot.  So, set the active process pointer to the next ready process in the
 +system and finish by activating the next ready process.
 +
 +3.5.2. SYSTEM HALF OF THE DELAY PRIMITIVE
 +
 +During each 60-Hz system interrupt, the current time (jiffy counter) is
 +incremented by one.  Note that since this timer is only 16 bits wide, it is
 +not suitable for keeping track of the current time of day; for this purpose,
 +the TOD clocks in the CIA chips should (and will) be used.  The jiffy counter
 +may also be inaccurate if interrupts are disabled for a long period of time,
 +such as they are during some Commodore-Kernal I/O operations.
 +
 +After incrementing the time, the kernel checks to see if any Delay()ed
 +processes need to be woken up.  If there are no processes in the delay queue,
 +then this is a quick check.  If there are any processes in the queue, then if
 +the wakeup time of the head process is equal to the current time, then that
 +process is woken up and this check is performed repeatedly until the condition
 +fails, since there may be multiple processes that want to be woken up at the
 +same jiffy of absolute time.  Note that because of the scheduling for a
 +freshly unblocked process, the process that Delay()ed first will be the
 +first one activated after it is woken up, if there are multiple processes
 +woken up at the start of the same jiffy.
 +
 +3.6. SYSTEM BOOTSTRAPPING
 +
 +Operating systems always have a bootstrapping problem, because you always need
 +to use the services of the operating system in order to start it up, but, of
 +course, it's not started up yet, chicken and egg, catch-22.  So, what usually
 +ends up happening is that you just "fake it", start from somewhere, get the
 +ball rolling, and snowball up to a fully running system.
 +
 +The first thing that the kernel does is change all of the interrupt vectors
 +(IRQ, NMI, and BRK) to my custom routines.  I need to cover all of the
 +interrupts, since I chave the zero page during the execution of the system,
 +and if a BRK or NMI were to happen and be serviced by the Commodore-Kernal ROM
 +routines, all hell would break loose.  Currently, the NMI and BRK routines
 +just clean things up and return to BASIC.
 +
 +Then we initialize the kernel variables, including the delay queue and the
 +jiffy counter.
 +
 +And then we fake the creation of the Null process.  For the purposes of
 +bootstrapping, the Null process doubles as the "Boot" process.  Its process
 +control block is not allocated in the normal way, either; it is at a fixed
 +location, and its PCB doubles as the head of the process list.  A kluge here
 +and a hack there and the Null process is initialized and "joined in
 +progress" Then, the Null process creates the Init process, using a standard
 +call to the Create() primitive, and then the Null process goes into an endless
 +loop of incrementing the 32-bit value at addresses $400-$403, the first four
 +locations of the 40-column screen memory.  It doesn't matter whether you run
 +BOS with the clock in Fast or Slow mode, except in terms of performance.
 +
 +It is the responsibility of the Init process to start up all of the user
 +processes in the user application after Init starts running.  In the current
 +implementation, Init starts up all of the other processes in the test
 +application and then becomes the Commodore-Kernal Server, which is a
 +convenient organization, since all of the other processes can find out the pid
 +of the Kernal Server merely by calling MyParentPid().
 +
 +4. INTERPROCESS COMMUNICATION
 +
 +In this system, processes are not strictly independent and competitive; many
 +must cooperate and comunicate to get work done.  To facilitiate this
 +interprocess communication (IPC), a particular paradigm was chosen: the Remote
 +Procedure Call (RPC) paradigm.  RPC is a message-passing scheme that is used
 +with the much-hyped Client/Server system-architecture model.  Its operation
 +parallels the implicit operations that take place when you call a local
 +procedure (a subroutine).
 +
 +The RPC message-passing paradigm is also coupled with a shared-memory paradigm
 +to offer greater performance for passing around massive amounts of data.  All
 +processes in the system (and in the entire distributed system when this OS is
 +extended) have global access to all of the memory in the system.  The coupling
 +of the two paradigms is such that you get the best of both worlds: the
 +convenence and natural interprocess *coordination* (synchronization) semantics
 +of RPC and the convenience and raw performance of shared storage.
 +
 +4.1. MESSAGE-PASSING CALLS
 +
 +The kernel provides three primitives for message passing:
 +
 +CALL NAME     INPUT ARGUMENTS                  RETURN VALUES
 +-----------   ------------------------------   -------------------------------
 +Send          ( .AY=msgHead )                  .CS=err(.A=errcode)
 +Receive       ( .AY=msgHead )                  .AY=senderPid
 +Reply         ( .AY=msgHead[msgRet,msgData] )  .CS=err(.A=errcode)
 +
 +These calls will send a message from one process (the client) to another
 +process (the server) and wait for a reply, receive a message from another
 +process (a client), and reply to a message sent from another process (a
 +client) that has been received, respectively.
 +
 +4.1.1. MESSAGE-HEADER DATA STRUCTURE
 +
 +Each of the message-passing primitives requires a pointer to a message-header
 +data structure that is stored in the user program's data space.  The message
 +header must be initialized with appropriate values before a message can be
 +sent.  Note that this scheme of passing a pointer to a message header allows
 +you to have multiple message headers lying around, initialized and ready for
 +action, and you can easily pick between them.  Here is what a message header
 +looks like:
 +
 +OFF   SIZ   CLASS   LABEL
 +---   ---   -----   ------
 +  0       pid     msgTo
 +  2       pid     msgFrom
 +  4       buf     msgBuf
 +  8       buf     msgLen
 + 10       buf     msgRepBuf
 + 14       buf     msgRepLen
 + 16       data    msgOp
 + 17       data    msgRet
 + 18       data    msgObj
 + 20       data    msgData
 + 24             SIZE
 +
 +You should not put too much faith in the offsets of the fields in the data
 +structure remaining static; you should always use the label to access the
 +fields of the structure, as in:
 +
 +sta myMessageHeader+msgTo+0
 +sty myMessageHeader+msgTo+1
 +
 +4.1.1.1. PID-CLASS FIELDS
 +
 +The first two fields, of class "pid", are used to identify the processes
 +involved in an RPC interaction.  The "msgTo" field is the pid of the process
 +that a message is to be/has been sent to, and the "pcbFrom" field is the id of
 +the process which a message has been received from.  For security reasons, the
 +sender does not fill in the "pcbFrom" field; the kernel does after the message
 +has been sent and the sender is blocked.  (Or else the sender could fake being
 +someone else).  The "pcbTo" field is used as the destination for when a
 +message is being sent and must be filled in with a legitimate value on a send
 +operation, and the "pcbFrom" field is used as the destination when a message
 +is being replied to, and must be filled in with a legitimate value on a reply
 +operation.  The "pcbTo" field is the only field of the message header that
 +actually needs to have a legitimate value before a message can be sent.
 +
 +4.1.1.1. BUF-CLASS FIELDS
 +
 +The next four fields, of class "buf", point out the send and reply buffers in
 +memory and the sizes of each.  The send buffer ("msgBuf"/"msgLen") is expected
 +to point to a region of near/far memory that contains valid data for a send
 +operation, and the reply buffer ("msgRepBuf"/"msgRepLen") is expected to point
 +to a valid area of memory for the server to fill in with any bulky result data
 +from an RPC request.  Each of the message-buffer pointers is four bytes in
 +size to allow for future expansion when the kernel will support "far" memory
 +that will be accessed through 32-bit pointers.  User processes are expected to
 +access these "far" buffers directly themselves, through the global shared
 +memory.  This eliminates the system overhead of uselessly copying bulky data
 +from place to place.
 +
 +There are two special notes to make about there "buf" fields.  First, they
 +don't actually have to be used how they're intended to be used. as long as
 +both the client and the server agree on what the contents of these fields are
 +supposed to mean.  In this respect, the fields can be used to quickly pass
 +twelve bytes of completely arbitrary information.  This is useful because many
 +RPCs only require that a small amount of information be transferred from one
 +process to another, or at least that bulky data be passed in only one
 +direction (like read or write), so that one of the buffer pointers is free to
 +be used quick, tiny data.
 +
 +Second, on the sending side, the "buffer" that is pointed to does not have to
 +be a "buffer" at all; it can be an arbitrary data structure that has an
 +arbitrary number of pieces, scattered throughout the global memory of the
 +system.  The only responsibility of the sender is to insure that no one else
 +will be attempting to modify the shared data simultaneously while the server
 +is accessing it.  This scheme is quite ingenious, I think (thank you, thank
 +you).  (The scheme may appear to have a security leak in the design, but our
 +system has no real hardware security anyway).
 +
 +The expected usage of buffers will be for the sender to use near memory for
 +the request and reply buffers and access them as regular near memory to
 +construct and interpret request and reply messages.  The receiver will (in the
 +future) access the buffers as far memory (which they may very well be since
 +processes will be allowed to execute on different banks of internal memory and
 +even on different machines), and may wish to fetch parts of messages into near
 +memory for processing.  The use of far pointers makes it so that data is
 +copied only when necessary, and copied only once.
 +
 +4.1.1.3. DATA-CLASS FIELDS
 +
 +The final four fields, of class "data", are intended to be used to
 +conveniently pass small amounts of arbitrary data.  This data can be
 +arbitrary, but the fields do have a convention that should usually be
 +followed, unless both parties agree to an alternative usage.
 +
 +The "msgOp" field is intended to be the "operation code" that a client process
 +wishes a server to execute.  The "msgRet" field is intended to be the return/
 +error code that is returned from the server to the client upon completion of
 +an operation.  The "msgObj" field is intended to be used by the client to
 +indicate which of the server's "objects" the client wishes to perform the
 +operation on.  And the "msgData" field is intended to contain four bytes of
 +arbitrary user data that is passed in with an operation and is passed back
 +from the server to give return values.  In the spirit of these semantics, the
 +data in all of the fields is send with a request, but only the data in the
 +"msgRet" and "msgData" fields is passed back in a reply operation.  None of
 +the other fields are passed back in a reply operation (the field values will
 +remain how they were before the send, for the sender).  Take special note that
 +the "msgRepLen" field will not be passed back; if there is less data returned
 +than was asked for by an operation, you will have to encode the "actual"
 +reply-buffer length into the "msgData" field.
 +
 +4.1.2. SEND
 +
 +Send() is used to transmit a message to a remote process and get back a reply
 +message.  The .AY register contains the near-memory address of the message
 +header, which must have its "msgTo" field filled in to be the pid of the
 +process that the message is being sent to.  The sending process will suspend
 +its execution while it is waiting for remote process to process its request.
 +If there is to be bulky reply data for the request (such as there would be for
 +a "read" request to a file server), then space for the reply buffer must be
 +allocated and indicated in the message header.  The reply-buffer space should
 +normally be owned by the sender.
 +
 +If there is an error in passing the message, the the error return will be
 +indicated by the carry flag being set and the error code will be returned in
 +the .A register.  Some possible errors will be, in the future:  destination
 +process is not valid, and that destination process died before receiving/
 +replying to your message.  (These conditions are not currently checked).  Also
 +in the future, this call will work completely transparently for passing
 +messages between machines in a network.
 +
 +4.1.3. RECEIVE
 +
 +Receive() is used to receive a message transmitted by a remote process to the
 +current process.  The receiver will block until another process does a
 +corresponding Send() operation, and then the message header sent by the sender
 +will be retrieved into the message-header buffer pointed to by the .AY
 +register, for this call.  No error returns are possible.  The pid of the
 +sending process will be returned in the .AY register as well as in the
 +"msgFrom" field of the receive-message-header buffer.  The receiver is then
 +expected to eventually call the Reply() primitive to re-awaken the sender.
 +The receiver is free to do anything it wants to after receiving a message from
 +a process, including receiving messages from other processes.  Messages are
 +received from other processes in FIFO order.
 +
 +A similar ReceiveSpecific() primitive may be provided in the future.  It would
 +only accept a message from a specifically named process and would enqueue all
 +other messages that are received before the specific message, to be received
 +later.
 +
 +4.1.4. REPLY
 +
 +Reply() is used to re-awaken a process that sent a message that was Receive()d
 +by the current process.  The current process is expected to have set up the
 +return information in the reply-message-header buffer and the reply buffer
 +area according to the client's wishes before calling the Reply() primitive.
 +The near address of the reply-message-header buffer is loaded into the .AY
 +register as an argument to the call.  Only the "msgFrom", "msgRet", and
 +"msgData" fields need to have values.  The "msgFrom" field identifies the
 +process to send the reply message to, and that process must be in the state of
 +waiting for a reply from the Reply()ing process, or an error will be
 +returned.  An error is indicated by the carry flag being set on return and the
 +error code is loaded in the .A register.  In the case of an error, no action
 +will have been performed by the system.
 +
 +4.2. IMPLEMENTATION
 +
 +The fields of the process control block that are used for message passing
 +are restated here:
 +
 +OFF   SIZ   CLASS   LABEL
 +---   ---   -----   ------
 + 12       ipc     pcbSendMsgPtr  (overlap)
 + 12       ipc     pcbRecvMsgPtr  (overlap)
 + 14       ipc/  pcbSendQHead
 + 16       ipc/  pcbSendQTail
 + 18       ipc/  pcbSendQFlag
 + 19       ipc/  pcbSendQCount
 + 20       ipc     pcbBlockedOn
 + 22       ipc     pcbReceiveFrom
 +
 +The "pcbBlockedOn" field is used to allow Reply() to verify that the pid it is
 +instructed to send a reply message to is indeed waiting for a reply from the
 +task calling Reply().  The "pcbSendQ*" fields constitute a queue head for a
 +list of process control blocks that are waiting to send a message to the
 +current process.  The "pcbSendMsgPtr" and "pcbRecvMsgPtr" fields are used to
 +save the message data parameters of a Send() or Receive() call, respectively,
 +when it has to be suspended without a transfer of the message header.  When
 +the other process involved performs the corresponding operation, the first
 +process' header buffer pointer is recovered from its process control block.
 +The "pcbReceiveFrom" field is unused at this time.
 +
 +The process states of STATE_SEND, STATE_REPLY, and STATE_RECEIVE are used with
 +message passing.  The STATE_SEND state means that the current process has sent
 +a message to a server process and is waiting for it to do a Receive().  The
 +STATE_REPLY state means that the current process has sent a message to a
 +server process, the message has been Receive()d, and that the current process
 +is waiting for the server process to perform a Reply().  The STATE_RECEIVE
 +state means that the current process has performed a Receive() and is waiting
 +for some other process to perform a corresponding Send().  These state
 +names/meanings may be a bit inconsistent; deal with it.
 +
 +The implementation of the actual Send(), Receive(), and Reply() operations is
 +actually quite straight-forward.  Both Send() and Receive() have to handle two
 +possibile situations: either the other process involved has already performed
 +its corresponding operation and is waiting, or it has not.  Reply() is
 +simplified in that it knows that the sender is already waiting for its reply
 +so it can proceed to copy the reply-message-header contents directly.
 +
 +The Send() primitive (will) checks the given destination pid for validity and
 +then checks the state of the recipient process.  If the recipient process is
 +in STATE_RECEIVE, the Send() function copies the message-header contents
 +directly to the receive-header buffer of the recipient.  The address of the
 +receive-header buffer is taken from the "pcbRecvMsgPtr" field of the
 +receiver's process control block in this case.  The receiver's return value
 +(the sending process' pid) is set up (on the receiving process' stack) and the
 +receiver is awakened while the sender is put to sleep, in STATE_REPLY state
 +(since the receive has already happened, it is waiting for the corresponding
 +Reply()).
 +
 +If the recipient process is not in the STATE_RECEIVE state, then the sending
 +process will have to wait for the recipient to perform a Receive().  The
 +sender's message-header buffer address is stored into its process control
 +block, the sender's process control block is linked into the recipient
 +process' "pcbSendQ*", and the sender is put to sleep, in the STATE_SEND
 +state.
 +
 +The Send() function does not set up the return value for the user's
 +system call since that will not be known until another process performs the
 +corresponding Reply().  A return value is set up immediately only in the case
 +of an error.  The possible error returns from Send() are: invalid pid and
 +reply too long (in which case the reply is truncated).
 +
 +The Receive() primitive first checks its "pcbSendQ*" to see if any processes
 +have already tried to send a message to the receiver.  If there is a process
 +there, the sender's process control block is removed from the head of the send
 +queue then the sender process' state is changed to STATE_REPLY and the sent
 +message-header contents (dereferenced by the sender's "pcbSendMsgPtr" pointer)
 +are copied into the receiver's message-header buffer.  The Receive() primitive
 +then exits returning the pid of the sender.  No error returns are possible.
 +
 +If there is no process enqueued in the recipient process' "pcbSendQ*", then
 +the receiving process is put to sleep in the STATE_RECEIVE state and its
 +message-header buffer pointer is copied into its process control block.
 +
 +The Reply() primitive verifies that the destination process is valid (but not
 +in the current implementation) and is actually awaiting a reply from the
 +replying process.  If not, it craps out.  Otherwise, it copies the two
 +message-header fields and awakens the sender.  The return value of the sender
 +is (already) set up to be carry-clear (no error) and the Reply() primitive
 +returns error-free too.
 +
 +The Exit() kernel call does not currently recover from a process performing a
 +Receive() and then Exit()ing before performing the corresponding Reply().
 +Some care will have to be taken to insure that all process involved in IPC can
 +consistently recover if one of the processes gets blown away, for whatever
 +reason (including Exit()).  Such consistent recovery has to be carefully
 +thought out for any kind of operating system; however, since there are only a
 +small number of kernel concepts in this one, consistent recovery is that much
 +easier to insure.
 +
 +5. CONCLUSION
 +
 +So there ya have it; the start of a real operating system for the Commodore
 +128.  What the operating system needs in terms of features is to be extended
 +to execute processes on any bank of internal memory, to access far memory, and
 +to be distributed so that it will work across multiple hosts.  What it needs
 +in terms of software is: device drivers, a command shell, utility programs,
 +and an assembler that can produce relocatable code.  Oh where, oh where shall
 +I ever find such software???  ;-)
 +------------------------------------------------------------------------------
 +APPENDIX A. SOURCE-CODE LISTING
 +
 +The source code follows.  Extract everything between the "-----=-----" lines
 +and save into a file named "bos.s" (or whatever) and then run it through the
 +ACE assembler to generate the executable program (which is also included below
 +for your convenience).  The ACE assembler is available for free with the
 +ACE-128/64 system.
 +
 +I have not gone through and fully documented the source code, since I have
 +been sitting on this program for quite a while and am in a rush to get it out
 +the door.  Besides, the functionality of each important component has already
 +been discussed.
 +
 +-----=-----
 +;simple multitasking kernel by Craig Bruce, started 25-Oct-1994.
 +
 +;This program is written in the ACE-Assembler format.
 +
 +   org $1300
 +   jmp main
 +
 +;======== declarations ========
 +
 +pcbNext         = 00 ;(2) mgmt
 +pcbPrev         = 02 ;(2) mgmt
 +pcbIsHead       = 04 ;(1) mgmt
 +pcbQCount       = 05 ;(1) mgmt
 +pcbSP           = 06 ;(1) ctxt
 +pcbStackPage    = 07 ;(1) ctxt
 +pcbZeroPage     = 08 ;(1) ctxt
 +pcbD506         = 09 ;(1) ctxt
 +pcbPriority     = 10 ;(1) sche
 +pcbCountdown    = 11 ;(1) sche
 +pcbWakeupTime   = 12 ;(2) sche (overlap)
 +pcbWaitEvent    = 12 ;(1) sche (overlap)
 +pcbSendMsgPtr   = 12 ;(2) sche (overlap)
 +pcbRecvMsgPtr   = 12 ;(2) sche (overlap)
 +pcbSendQHead    = 14 ;(2) ipc
 +pcbSendQTail    = 16 ;(2) ipc
 +pcbSendQFlag    = 18 ;(1) ipc
 +pcbSendQCount   = 19 ;(1) ipc
 +pcbBlockedOn    = 20 ;(2) ipc
 +pcbReceiveFrom  = 22 ;(2) ipc
 +pcbParent       = 24 ;(2) proc
 +pcbState        = 26 ;(1) proc
 +pcbSize         = 27
 +
 +STATE_READY     = $c0
 +STATE_SEND      = $c1
 +STATE_RECEIVE   = $c2
 +STATE_REPLY     = $c3
 +STATE_DELAY     = $c4
 +STATE_SUSPENDED = $c5
 +STATE_EVENT     = $c6
 +
 +KERN_ERR_OK            = $e0
 +KERN_ERR_PID_NOT_REPLY = $e1
 +
 +msgTo            0 ;(2)
 +msgFrom          2 ;(2)
 +msgBuf          =  4 ;(4)
 +msgLen          =  8 ;(2)
 +msgRepBuf       = 10 ;(4)
 +msgRepLen       = 14 ;(2)
 +msgOp           = 16 ;(1)
 +msgRet          = 17 ;(1)
 +msgObj          = 18 ;(2)
 +msgData         = 20 ;(4)
 +msgSize         = 24
 +
 +queueHeadSize   = 6
 +
 +nullPcb    : buf pcbSize
 +delayQueue : buf queueHeadSize
 +jiffyTime  : buf 2
 +
 +activePid  = 02 ;(2)
 +p          = 04 ;(2)
 +q          = 06 ;(2)
 +pcbPtr     = 08 ;(2)
 +msgPtr     = 10 ;(2)
 +pageAlloc  = 12 ;(1)
 +
 +;Stack: ($ff)      : exitaddr-1.h
 +;       ($fe)      : exitaddr-1.l
 +;       ($fd) sp+07: pc.h
 +;       ($fc) sp+06: pc.l
 +;       ($fb) sp+05: status register
 +;       ($fa) sp+04: .A
 +;       ($f9) sp+03: .X
 +;       ($f8) sp+02: .Y
 +;       ($f7) sp+01: $ff00 save
 +;       ($f6) sp+00: -empty-
 +
 +bkBOS    = $0e
 +bkUser   = $0e
 +bkSelect = $ff00
 +vic      = $d000
 +sid      = $d400
 +mmuZeroPage  = $d507
 +mmuStackPage = $d509
 +IrqExit  = $ff33
 +
 +; Create  ( .AY=address, .X=priority ) : .AY=pid
 +; Exit    ( .A=code, .X=$00 )
 +; MyPid   ( ) : .AY=pid
 +; MyParentPid ( ) : .AY=parentPid
 +; Suspend ( )
 +; Delay   ( .AY=jiffies ) : .CS=err
 +; Send    ( .AY=msgBuf ) : .CS:.A=err
 +; Receive ( .AY=msgBuf ) : .AY=senderPid
 +; Reply   ( .AY=msgBuf[msgRet,msgData] ) : .CS:.A=err
 +
 +;======== kernel code ========
 +
 +main = *
 +   sei
 +   ;** entry
 +   lda #bkBOS
 +   sta bkSelect
 +   ;** set interrupt vectors
 +   lda #<IrqHandler
 +   ldy #>IrqHandler
 +   sta $0314
 +   sty $0315
 +   lda #<BrkHandler
 +   ldy #>BrkHandler
 +   sta $0316
 +   sty $0317
 +   lda #<NmiHandler
 +   ldy #>NmiHandler
 +   sta $0318
 +   sty $0319
 +   ;** initialize delay queue
 +   lda #0
 +   sta jiffyTime+0
 +   sta jiffyTime+1
 +   lda #<delayQueue
 +   ldy #>delayQueue
 +   sta q+0
 +   sty q+1
 +   jsr QueueInit
 +   ;** initialize null/boot process
 +   lda #<nullPcb
 +   ldy #>nullPcb
 +   sta nullPcb+pcbNext+0
 +   sty nullPcb+pcbNext+1
 +   sta nullPcb+pcbPrev+0
 +   sty nullPcb+pcbPrev+1
 +   sta activePid+0
 +   sty activePid+1
 +   lda #$ff
 +   sta nullPcb+pcbIsHead
 +   lda #0
 +   sta nullPcb+pcbQCount
 +   lda #>$2000
 +   sta pageAlloc
 +   lda #STATE_READY
 +   sta nullPcb+pcbState
 +   lda #2
 +   sta nullPcb+pcbPriority
 +   cli
 +   jmp Null
 +
 +Null = *
 +   ;** create init process
 +   lda #<Init
 +   ldy #>Init
 +   ldx #1
 +   jsr Create
 +   ;** go into endless loop
 +-  inc $0400
 +   bne +
 +   inc $0401
 +   bne +
 +   inc $0402
 +   bne +
 +   inc $0403
 ++  jmp -
 +
 +NmiHandler = *
 +BrkHandler = *
 +Shutdown = *
 +   ;** restore interrupt vectors
 +   sei
 +   lda #<$fa65
 +   ldy #>$fa65
 +   sta $0314
 +   sty $0315
 +   lda #<$fa40
 +   ldy #>$fa40
 +   sta $0318
 +   sty $0319
 +   ldx #250
 +   txs
 +   lda #$00
 +   sta mmuZeroPage
 +   sta mmuZeroPage+1
 +   ldx #$01
 +   stx mmuStackPage
 +   sta mmuStackPage+1
 +   lda #%00000100
 +   sta $d506
 +   cli
 +   jmp $4db7
 +   
 +zpSave : buf 1
 +
 +createAddr     : buf 2
 +createPriority : buf 1
 +createZeropage : buf 1
 +createStack    : buf 1
 +createPcb      : buf pcbSize
 +
 +Create = *  ;( .AY=address, .X=priority ) : .AY=pid
 +   sei
 +   ;** switch in
 +   sta createAddr+0
 +   sty createAddr+1
 +   stx createPriority
 +   lda mmuZeroPage
 +   sta zpSave
 +   lda #$00
 +   sta mmuZeroPage
 +   ;** allocate resources
 +   lda #$00
 +   ldy pageAlloc
 +   sta pcbPtr+0
 +   sty pcbPtr+1
 +   iny
 +   sty createZeropage
 +   iny
 +   sty createStack
 +   iny
 +   sty pageAlloc
 +   cpy #>$c000
 +   bcc +
 +   brk   ; recover gracefully from the condition of running out of memory
 ++
 +   ;** initialize pcb
 +   ;** pcbNext         ;(2) mgmt := 0
 +   ;** pcbPrev         ;(2) mgmt := 0
 +   ;** pcbIsHead       ;(1) mgmt := 0
 +   ;** pcbQCount       ;(1) mgmt := 0
 +   ;** pcbSP           ;(1) ctxt := $f6
 +   ;** pcbStackPage    ;(1) ctxt := new
 +   ;** pcbZeroPage     ;(1) ctxt := new
 +   ;** pcbD506         ;(1) ctxt := $04
 +   ;** pcbPriority     ;(1) sche := given
 +   ;** pcbCountdown    ;(1) sche := priority
 +   ;** pcbWakeupTime   ;(2) sche := 0
 +   ;** pcbSendQHead    ;(2) ipc  := QueueInit
 +   ;** pcbSendQTail    ;(2) ipc  := QueueInit
 +   ;** pcbSendQFlag    ;(1) ipc  := QueueInit
 +   ;** pcbSendQCount   ;(1) ipc  := QueueInit
 +   ;** pcbBlockedOn    ;(2) ipc  := 0
 +   ;** pcbReceiveFrom  ;(2) ipc  := 0
 +   ;** pcbParent       ;(2) proc := creator
 +   ;** pcbState        ;(1) proc := STATE_SUSPENDED
 +   ldx #pcbSize-1
 +   lda #$00
 +-  sta createPcb,x
 +   dex
 +   bpl -
 +   lda #$f6
 +   sta createPcb+pcbSP
 +   lda createStack
 +   sta createPcb+pcbStackPage
 +   lda createZeropage
 +   sta createPcb+pcbZeroPage
 +   lda #$04
 +   sta createPcb+pcbD506
 +   lda createPriority
 +   sta createPcb+pcbPriority
 +   sta createPcb+pcbCountdown
 +   lda activePid+0
 +   ldy activePid+1
 +   sta createPcb+pcbParent+0
 +   sty createPcb+pcbParent+1
 +   lda #STATE_SUSPENDED
 +   sta createPcb+pcbState
 +   ldy #pcbSize-1
 +-  lda createPcb,y
 +   sta (pcbPtr),y
 +   dey
 +   bpl -
 +   lda pcbPtr+0
 +   clc
 +   adc #pcbSendQHead
 +   sta q+0
 +   lda pcbPtr+1
 +   adc #0
 +   sta q+1
 +   jsr QueueInit
 +
 +   ;** initialize new stack
 +   ;** Stack: ($ff)      : exitaddr-1.h     := >ExitAddr
 +   ;**        ($fe)      : exitaddr-1.l     := <ExitAddr
 +   ;**        ($fd) sp+07: pc.h             := >Addr
 +   ;**        ($fc) sp+06: pc.l             := <Addr
 +   ;**        ($fb) sp+05: status register  := $00
 +   ;**        ($fa) sp+04: .A               := $00
 +   ;**        ($f9) sp+03: .X               := $00
 +   ;**        ($f8) sp+02: .Y               := $00
 +   ;**        ($f7) sp+01: $ff00 save       := $0e
 +   ;**        ($f6) sp+00: -empty-
 +   lda #$00
 +   ldy createStack
 +   sta p+0
 +   sty p+1
 +   ldy #$f6+1
 +   lda #bkUser
 +   sta (p),y  ;$ff00
 +   iny
 +   ldx #4
 +   lda #$00
 +-  sta (p),y
 +   iny
 +   dex
 +   bne -
 +   lda createAddr+0
 +   sta (p),y
 +   iny
 +   lda createAddr+1
 +   sta (p),y
 +   iny
 +   lda #<DefaultExit-1
 +   sta (p),y
 +   iny
 +   lda #>DefaultExit-1
 +   sta (p),y
 +
 +   ;** make new process ready
 +   jsr MakeReady
 +
 +   ;** switch out
 +   lda pcbPtr+0
 +   ldy pcbPtr+1
 +   ldx zpSave
 +   stx mmuZeroPage
 +   clc
 +   cli
 +   rts
 +
 +MakeReady = *  ;( (pcbPtr)=pcb ) ;after activePid
 +   ldy #pcbState
 +   lda #STATE_READY
 +   sta (pcbPtr),y
 +   lda #<nullPcb
 +   ldy #>nullPcb
 +   sta q+0
 +   sty q+1
 +   lda activePid+0
 +   ldy activePid+1
 +   sta p+0
 +   sty p+1
 +   jsr QueueInsert
 +   rts
 +
 +QueueInit = *  ;( (q)=queueHead )
 +   lda q+0
 +   ldy q+1
 +   sta queueInitVals+pcbNext+0
 +   sty queueInitVals+pcbNext+1
 +   sta queueInitVals+pcbPrev+0
 +   sty queueInitVals+pcbPrev+1
 +   lda #$ff
 +   sta queueInitVals+pcbIsHead
 +   lda #0
 +   sta queueInitVals+pcbQCount
 +   ldy #queueHeadSize-1
 +-  lda queueInitVals,y
 +   sta (q),y
 +   dey
 +   bpl -
 +   rts
 +   queueInitVals : buf queueHeadSize
 +
 +QueueInsert = *  ;( (q)=queueHead, (p)=nodeToInsertAfter, (pcbPtr)=newItem )
 +   ;** q->count +:= 1
 +   clc
 +   ldy #pcbQCount
 +   lda (q),y
 +   adc #1
 +   sta (q),y
 +
 +   ;** pcbPtr->next := p->next
 +   ldy #pcbNext
 +   lda (p),y
 +   sta (pcbPtr),y
 +   iny
 +   lda (p),y
 +   sta (pcbPtr),y
 +
 +   ;** pcbPtr->prev := p
 +   iny
 +   lda p+0
 +   sta (pcbPtr),y
 +   iny
 +   lda p+1
 +   sta (pcbPtr),y
 +
 +   ;** p->next->prev := pcbPtr
 +   ldy #pcbNext
 +   lda (p),y
 +   sta q+0
 +   iny
 +   lda (p),y
 +   sta q+1
 +   ldy #pcbPrev
 +   lda pcbPtr+0
 +   sta (q),y
 +   iny
 +   lda pcbPtr+1
 +   sta (q),y
 +
 +   ;** p->next := pcbPtr
 +   ldy #pcbNext
 +   lda pcbPtr+0
 +   sta (p),y
 +   iny
 +   lda pcbPtr+1
 +   sta (p),y
 +   rts
 +
 +QueueUnlink = *  ;( (q)=queueHead, (pcbPtr)=node )  ;uses p
 +   ;** pcbPtr->next->prev := pcbPtr->prev
 +   ldy #pcbNext
 +   lda (pcbPtr),y
 +   sta p+0
 +   iny
 +   lda (pcbPtr),y
 +   sta p+1
 +   ldy #pcbPrev
 +   lda (pcbPtr),y
 +   sta (p),y
 +   iny
 +   lda (pcbPtr),y
 +   sta (p),y
 +
 +   ;** pcbPtr->prev->next := pcbPtr->next
 +   ldy #pcbPrev
 +   lda (pcbPtr),y
 +   sta p+0
 +   iny
 +   lda (pcbPtr),y
 +   sta p+1
 +   ldy #pcbNext
 +   lda (pcbPtr),y
 +   sta (p),y
 +   iny
 +   lda (pcbPtr),y
 +   sta (p),y
 +
 +   ;** q->count -:= 1
 +   ldy #pcbQCount
 +   lda (q),y
 +   sec
 +   sbc #1
 +   sta (q),y
 +   rts
 +
 +IrqHandler = *
 +   cld
 +   lda #bkBOS
 +   sta bkSelect
 +   lda vic+$19
 +   bpl +
 +   and #1
 +   bne Sixty
 ++  lda $dc0d
 +
 +Sixty = *
 +   sta vic+$19
 +   ;** save full context
 +   lda mmuZeroPage
 +   ldx #$00
 +   stx mmuZeroPage
 +   ldy #pcbZeroPage
 +   sta (activePid),y
 +   ldy #pcbSP
 +   tsx
 +   txa
 +   sta (activePid),y
 +   ldy #pcbStackPage
 +   lda mmuStackPage
 +   sta (activePid),y
 +   ldy #pcbD506
 +   lda $d506
 +   sta (activePid),y
 +
 +   ;** process interrupt
 +   inc jiffyTime+0
 +   bne +
 +   inc jiffyTime+1
 ++  lda delayQueue+pcbQCount
 +   beq +
 +   jsr DelayIrqAwake
 ++  nop
 +
 +   ;** select new process
 +-  ldy #pcbPriority   ;give cur full count
 +   lda (activePid),y
 +   iny
 +   sta (activePid),y
 +   beq ++
 +-  ldy #pcbNext       ;find next proc
 +   lda (activePid),y
 +   tax
 +   iny
 +   lda (activePid),y
 +   stx activePid+0
 +   sta activePid+1
 +ExitKernel = *
 +   ldy #pcbCountdown
 +   lda (activePid),y
 +   beq ++
 +   sec
 +   sbc #1
 +   sta (activePid),y
 +   beq +
 +   jmp -
 ++  ;check if null process
 +   ldy #pcbIsHead
 +   lda (activePid),y
 +   bpl +
 +   iny
 +   lda (activePid), ;only run null if only proc
 +   bne --
 ++  ;we've got a winner
 +
 +   ;** restore full context and exit
 +   ldy #pcbD506
 +   lda (activePid),y
 +   sta $d506
 +   ldy #pcbStackPage
 +   lda (activePid),y
 +   sta mmuStackPage
 +   ldy #pcbSP
 +   lda (activePid),y
 +   tax
 +   txs
 +   ldy #pcbZeroPage
 +   lda (activePid),y
 +   sta mmuZeroPage
 +   jmp IrqExit
 +
 +DefaultExit = *
 +   lda #$00
 +   ldx #$00
 +Exit = *  ;( .A=code, .X=$00 )
 +   jmp Suspend
 +   brk
 +
 +MyPid = *  ;( ) : .AY=pid
 +   lda #$00
 +   ldx mmuZeroPage
 +   sta mmuZeroPage
 +   lda activePid+0
 +   ldy activePid+1
 +   stx mmuZeroPage
 +   clc
 +   rts
 +
 +MyParentPid = *  ;( ) : .AY=parentPid
 +   lda #$00
 +   ldx mmuZeroPage
 +   sta mmuZeroPage
 +   ldy #pcbParent
 +   lda (activePid),y
 +   pha
 +   iny
 +   lda (activePid),y
 +   tay
 +   pla
 +   stx mmuZeroPage
 +   clc
 +   rts
 +
 +enterKernSave : buf 4
 +
 +EnterKernel = *
 +   ;** set up process stack as if it had performed an interrupt
 +   ;** necessary if process will block
 +   ;** called as a one-level-deep subroutine of the system call
 +   sta enterKernSave+2
 +   ;** save system-call return address
 +   pla
 +   sta enterKernSave+0
 +   pla
 +   sta enterKernSave+1
 +   ;** increment user-process return address (rts -> rti)
 +   pla
 +   clc
 +   adc #1
 +   sta enterKernSave+3
 +   pla
 +   adc #0
 +   pha
 +   lda enterKernSave+3
 +   pha
 +   ;** set up processor registers as-is, status $00
 +   lda #$00
 +   pha
 +   lda enterKernSave+2
 +   pha
 +   txa
 +   pha
 +   tya
 +   pha
 +   lda $ff00  ;xxx change for multi-banks
 +   pha
 +   ;** save info into pcb
 +   lda mmuZeroPage
 +   ldx #$00
 +   stx mmuZeroPage
 +   ldy #pcbZeroPage
 +   sta (activePid),y
 +   dey
 +   lda mmuStackPage
 +   sta (activePid),y
 +   dey
 +   tsx
 +   txa
 +   sta (activePid),y
 +   ldy #pcbD506
 +   lda $d506
 +   sta (activePid),y
 +   ;** restore system-call return address
 +   ;** (continue to use user-process stack)
 +   lda enterKernSave+1
 +   pha
 +   lda enterKernSave+0
 +   pha
 +   rts
 +
 +Suspend = *  ;( )    ;suspend self
 +   sei
 +   jsr EnterKernel
 +   jsr SuspendSub
 +   jmp ExitKernel
 +
 +SuspendSub = *  ;( activePid ) : activePid, pcbPtr, q=nullPcb
 +   ;** Remove the active pid from the ready queue and set another pid to
 +   ;** active; set pcbPtr to point to the suspended process; and set the
 +   ;** process state to "suspended".
 +   lda activePid+0
 +   sta pcbPtr+0
 +   lda activePid+1
 +   sta pcbPtr+1
 +   lda #<nullPcb
 +   ldy #>nullPcb
 +   sta q+0
 +   sty q+1
 +   jsr QueueUnlink
 +   ldy #pcbNext
 +   lda (pcbPtr),y
 +   sta activePid+0
 +   iny
 +   lda (pcbPtr),y
 +   sta activePid+1
 +   ldy #pcbState
 +   lda #STATE_SUSPENDED
 +   sta (pcbPtr),y
 +   rts
 +
 +Delay = *  ;( .AY=jiffies ) : .CS=err
 +   cmp #0
 +   bne +
 +   cpy #0
 +   bne +
 +   clc
 +   rts
 ++  sei
 +   sta delayTime+0
 +   sty delayTime+1
 +   jsr EnterKernel
 +   jsr SuspendSub
 +   ldy #pcbState
 +   lda #STATE_DELAY
 +   ldy #pcbWakeupTime
 +   clc
 +   lda delayTime+0
 +   adc jiffyTime+0
 +   sta delayTime+0
 +   sta (pcbPtr),y
 +   iny
 +   lda delayTime+1
 +   adc jiffyTime+1
 +   sta delayTime+1
 +   sta (pcbPtr),y
 +   lda #0
 +   rol
 +   sta delayTime+2
 +   lda #<delayQueue
 +   ldy #>delayQueue
 +   sta q+0
 +   sty q+1
 +   sta p+0
 +   sty p+1
 +   jsr DelayFindSpot
 +   jsr QueueInsert
 +   jmp ExitKernel
 +   delayTime : buf 3
 +   pTimeHi   : buf 1
 +
 +DelayFindSpot = *  ;( (q)=queue, (p)=queueHead, (pcbPtr) ) : p=prevNode
 +   jsr IncPtrP
 +   ldy #pcbIsHead
 +   lda (p),y
 +   bne DelayFindSpotExit
 +   ldy #pcbWakeupTime
 +   lda (p),y
 +   cmp jiffyTime+0
 +   iny
 +   lda (p),y
 +   sbc jiffyTime+1
 +   ldx #0
 +   bcs +
 +   inx
 ++  stx pTimeHi
 +   dey
 +   lda delayTime+0
 +   cmp (p),y
 +   iny
 +   lda delayTime+1
 +   sbc (p),y
 +   lda delayTime+2
 +   sbc pTimeHi
 +   bcs DelayFindSpot
 +
 +   DelayFindSpotExit = *
 +   ;xx fall through
 +
 +DecPtrP = *  ;( (p) ) : (p):=(p)->prev
 +   ldy #pcbPrev
 +   lda (p),y
 +   tax
 +   iny
 +   lda (p),y
 +   stx p+0
 +   sta p+1
 +   rts
 +
 +IncPtrP = *  ;( (p) ) : (p):=(p)->next
 +   ldy #pcbNext
 +   lda (p),y
 +   tax
 +   iny
 +   lda (p),y
 +   stx p+0
 +   sta p+1
 +   rts
 +
 +DelayIrqAwake = *
 +   lda delayQueue+pcbNext+0
 +   ldy delayQueue+pcbNext+1
 +   sta pcbPtr+0
 +   sty pcbPtr+1
 +   ldy #pcbWakeupTime
 +   lda (pcbPtr),y
 +   cmp jiffyTime+0
 +   beq +
 +   rts
 ++  iny
 +   lda (pcbPtr),y
 +   cmp jiffyTime+1
 +   beq +
 +   rts
 ++  lda #<delayQueue
 +   ldy #>delayQueue
 +   sta q+0
 +   sty q+1
 +   jsr QueueUnlink
 +   jsr MakeReady
 +   jmp DelayIrqAwake
 +
 +msgPtrSave : buf 2
 +
 +Send = *  ;( .AY=msgBuf ) : .CS:.A=err
 +   sei
 +   sta msgPtrSave+0
 +   sty msgPtrSave+1
 +   jsr EnterKernel
 +   jsr SuspendSub
 +   lda msgPtrSave+0
 +   ldy msgPtrSave+1
 +   sta msgPtr+0
 +   sty msgPtr+1
 +   ldy #msgTo
 +   lda (msgPtr),y
 +   sta q+0
 +   iny
 +   lda (msgPtr),y
 +   sta q+1
 +   ;xx should verify that receiver is a process
 +   ldy #pcbSendMsgPtr
 +   lda msgPtr+0
 +   sta (pcbPtr),y
 +   iny
 +   lda msgPtr+1
 +   sta (pcbPtr),y
 +   ldy #pcbBlockedOn
 +   lda q+0
 +   sta (pcbPtr),y
 +   iny
 +   lda q+1
 +   sta (pcbPtr),y
 +   ldy #pcbState
 +   lda (q),y
 +   cmp #STATE_RECEIVE
 +   beq SendToReceiverBlocked
 +   lda #STATE_SEND
 +   sta (pcbPtr),y
 +   clc
 +   lda q+0
 +   adc #pcbSendQHead
 +   sta q+0
 +   bcc +
 +   inc q+1
 ++  ldy #pcbPrev
 +   lda (q),y
 +   sta p+0
 +   iny
 +   lda (q),y
 +   sta p+1
 +   jsr QueueInsert
 +   jmp ExitKernel
 +
 +SendToReceiverBlocked = *
 +   lda #STATE_REPLY
 +   sta (pcbPtr),y
 +   ldy #pcbRecvMsgPtr
 +   lda (q),y
 +   sta p+0
 +   iny
 +   lda (q),y
 +   sta p+1
 +   jsr CopyMessage
 +   lda pcbPtr+0
 +   ldy pcbPtr+1
 +   ldx q+0
 +   stx pcbPtr+0
 +   ldx q+1
 +   stx pcbPtr+1
 +   ldx #$00
 +   clc
 +   jsr SetReturn 
 +   jsr MakeReady
 +   jmp ExitKernel
 +
 +setretSave : buf 4
 +
 +SetReturn = *  ;( (pcbPtr)=proc, .AXY=regvals, .C=cval ) : (p)=junk
 +   sta setretSave+2
 +   stx setretSave+1
 +   sty setretSave+0
 +   php
 +   pla
 +   and #$01
 +   sta setretSave+3
 +   ldy #pcbStackPage
 +   lda (pcbPtr),y
 +   sta p+1
 +   ldy #pcbSP
 +   lda (pcbPtr),y
 +   clc
 +   adc #2
 +   sta p+0
 +   ldy #3
 +-  lda setretSave,y
 +   sta (p),y
 +   dey
 +   bpl -
 +   rts
 +
 +CopyMessage = *  ;( (pcbPtr)=sender, (msgPtr)=sendmsg, (p)=recvmsg )
 +   ldy #msgFrom
 +   lda pcbPtr+0
 +   sta (msgPtr),y
 +   iny
 +   lda pcbPtr+1
 +   sta (msgPtr),y
 +   ldy #msgSize-1
 +-  lda (msgPtr),y
 +   sta (p),y
 +   dey
 +   bpl -
 +   rts
 +
 +Receive = *  ;( .AY=msgBuf ) : .AY=senderPid
 +   sei
 +   sta msgPtrSave+0
 +   sty msgPtrSave+1
 +   lda mmuZeroPage
 +   pha
 +   lda #$00
 +   sta mmuZeroPage
 +   ldy #pcbSendQCount
 +   lda (activePid),y
 +   bne ReceiveFromSender
 +   pla
 +   sta mmuZeroPage
 +   jsr EnterKernel
 +   jsr SuspendSub
 +   lda #STATE_RECEIVE
 +   sta (pcbPtr),y
 +   ldy #pcbRecvMsgPtr
 +   lda msgPtrSave+0
 +   sta (pcbPtr),y
 +   iny
 +   lda msgPtrSave+1
 +   sta (pcbPtr),y
 +   jmp ExitKernel
 +
 +ReceiveFromSender = *  ;( (activePid), (msgPtrSave) )
 +   lda activePid+0
 +   ldy activePid+1
 +   clc
 +   adc #pcbSendQHead
 +   bcc +
 +   iny
 ++  sta q+0
 +   sty q+1
 +   ldy #pcbSendQHead
 +   lda (activePid),y
 +   sta pcbPtr+0
 +   iny
 +   lda (activePid),y
 +   sta pcbPtr+1
 +   jsr QueueUnlink  ;( (q)=queueHead, (pcbPtr)=node )  ;uses p
 +   ldy #pcbSendMsgPtr
 +   lda (pcbPtr),y
 +   sta msgPtr+0
 +   iny
 +   lda (pcbPtr),y
 +   sta msgPtr+1
 +   lda msgPtrSave+0
 +   ldy msgPtrSave+1
 +   sta p+0
 +   sty p+1
 +   jsr CopyMessage  ;( (pcbPtr)=sender, (msgPtr)=sendmsg, (p)=recvmsg )
 +   ldy #pcbState
 +   lda #STATE_REPLY
 +   sta (pcbPtr),y
 +   ldx pcbPtr+0
 +   ldy pcbPtr+1
 +   pla
 +   sta mmuZeroPage
 +   txa
 +   cli
 +   clc
 +   rts
 +
 +zpPtrSave : buf 1
 +
 +Reply = *  ;( .AY=msgBuf[msgRet,msgData] ) : .CS:.A=err
 +   sei
 +   ;** switch to kernel
 +   ldx mmuZeroPage
 +   stx zpPtrSave
 +   ldx #$00
 +   stx mmuZeroPage
 +   sta msgPtr+0
 +   sty msgPtr+1
 +   ;** find and check the sender
 +   ldy #msgFrom
 +   lda (msgPtr),y
 +   sta pcbPtr+0
 +   iny
 +   lda (msgPtr),y
 +   sta pcbPtr+1
 +   ;xx verify that receiver is a pcb here
 +   ldy #pcbState
 +   lda (pcbPtr),y
 +   cmp #STATE_REPLY
 +   beq +
 +-  lda #KERN_ERR_PID_NOT_REPLY
 +   ldx zpPtrSave
 +   stx mmuZeroPage
 +   sec
 +   cli
 +   rts
 ++  ldy #pcbBlockedOn
 +   lda (pcbPtr),y
 +   cmp activePid+0
 +   bne -
 +   iny
 +   lda (pcbPtr),y
 +   cmp activePid+1
 +   bne -
 +   ;** copy the reply contents
 +   ldy #pcbSendMsgPtr
 +   lda (pcbPtr),y
 +   sta p+0
 +   iny
 +   lda (pcbPtr),y
 +   sta p+1
 +   ldy #msgRet
 +   lda (msgPtr),y
 +   sta (p),y
 +   ldy #msgData
 +-  lda (msgPtr),y
 +   sta (p),y
 +   iny
 +   cpy #msgData+4
 +   bcc -
 +   ;** wake up the sender and exit
 +   jsr MakeReady
 +   ldx zpPtrSave
 +   stx mmuZeroPage
 +   clc
 +   cli
 +   rts
 +
 +;======== test application ========
 +
 +testNumber : buf 1
 +
 +Init = *
 +   lda #1
 +   sta testNumber
 +   lda #<TestSid1
 +   ldy #>TestSid1
 +   ldx #2
 +   jsr Create
 +   lda #<TestDelay1
 +   ldy #>TestDelay1
 +   ldx #1
 +   jsr Create
 +   lda #<TestDelay2
 +   ldy #>TestDelay2
 +   ldx #1
 +   jsr Create
 +   lda #<TestDelay3
 +   ldy #>TestDelay3
 +   ldx #1
 +   jsr Create
 +   lda #<TestDelay4
 +   ldy #>TestDelay4
 +   ldx #1
 +   jsr Create
 +   lda #<TestDelay5
 +   ldy #>TestDelay5
 +   ldx #1
 +   jsr Create
 +   lda #<Blabber1
 +   ldy #>Blabber1
 +   ldx #1
 +   jsr Create
 +   lda #<Spinner1
 +   ldy #>Spinner1
 +   ldx #1
 +   jsr Create
 +   jmp KernelServer
 +
 +TestSid1 = *
 +   ldx #$1c-1
 +   lda #$00
 +-  sta $d400,x
 +   dex
 +   bpl -
 +   lda #$50
 +   sta 2
 +   sta 3
 +   lda #$08
 +   sta $d418
 +   lda #$00
 +   ldy #$08
 +   sta $d402
 +   sty $d403
 +   lda #$41
 +   sta $d404
 +   lda #$00
 +   sta $d405
 +   lda #$f0
 +   sta $d406
 +-  lda 2
 +   ldy 3
 +   sta $d400
 +   sty $d401
 +   lda 2
 +   ora 3
 +   bne +
 +   lda #120
 +   ldy #0
 +   jsr Delay
 ++  inc 2
 +   bne +
 +   inc 3
 ++  inc $d020
 +   tsx
 +   jmp -
 +
 +TestDelay1 = *
 +   jsr MyParentPid
 +   sta testDelay1Msg+msgTo+0
 +   sty testDelay1Msg+msgTo+1
 +   lda #<testDelay1Txt
 +   ldy #>testDelay1Txt
 +   sta testDelay1Msg+msgBuf+0
 +   sty testDelay1Msg+msgBuf+1
 +-  lda #<60
 +   ldy #>60
 +   jsr Delay
 +   inc $581
 +   lda #<testDelay1Msg
 +   ldy #>testDelay1Msg
 +   jsr Send
 +   jmp -
 +   testDelay1Txt : db "Hi, this is delay process 1 *\n",0
 +
 +TestDelay2 = *
 +   jsr MyParentPid
 +   sta testDelay2Msg+msgTo+0
 +   sty testDelay2Msg+msgTo+1
 +   lda #<testDelay2Txt
 +   ldy #>testDelay2Txt
 +   sta testDelay2Msg+msgBuf+0
 +   sty testDelay2Msg+msgBuf+1
 +-  lda #<120
 +   ldy #>120
 +   jsr Delay
 +   inc $582
 +   lda #<testDelay2Msg
 +   ldy #>testDelay2Msg
 +   jsr Send
 +   jmp -
 +   testDelay2Txt : db "Hi, this is delay process 2\n",0
 +
 +TestDelay3 = *
 +   jsr MyParentPid
 +   sta testDelay3Msg+msgTo+0
 +   sty testDelay3Msg+msgTo+1
 +   lda #<testDelay3Txt
 +   ldy #>testDelay3Txt
 +   sta testDelay3Msg+msgBuf+0
 +   sty testDelay3Msg+msgBuf+1
 +-  lda #<180
 +   ldy #>180
 +   jsr Delay
 +   inc $583
 +   lda #<testDelay3Msg
 +   ldy #>testDelay3Msg
 +   jsr Send
 +   jmp -
 +   testDelay3Txt : db "Hi, this is delay process 3\n",0
 +
 +TestDelay4 = *
 +   jsr MyParentPid
 +   sta testDelay4Msg+msgTo+0
 +   sty testDelay4Msg+msgTo+1
 +   lda #<testDelay4Txt
 +   ldy #>testDelay4Txt
 +   sta testDelay4Msg+msgBuf+0
 +   sty testDelay4Msg+msgBuf+1
 +-  lda #<240
 +   ldy #>240
 +   jsr Delay
 +   inc $584
 +   lda #<testDelay4Msg
 +   ldy #>testDelay4Msg
 +   jsr Send
 +   jmp -
 +   testDelay4Txt : db "Hi, this is delay process 4\n",0
 +
 +TestDelay5 = *
 +   jsr MyParentPid
 +   sta testDelay5Msg+msgTo+0
 +   sty testDelay5Msg+msgTo+1
 +   lda #<testDelay5Txt
 +   ldy #>testDelay5Txt
 +   sta testDelay5Msg+msgBuf+0
 +   sty testDelay5Msg+msgBuf+1
 +-  lda #<300
 +   ldy #>300
 +   jsr Delay
 +   inc $585
 +   lda #<testDelay5Msg
 +   ldy #>testDelay5Msg
 +   jsr Send
 +   jmp -
 +   testDelay5Txt : db "Hi, this is delay process 5\n",0
 +
 +Blabber1 = *
 +   jsr MyParentPid
 +   sta blabber1Msg+msgTo+0
 +   sty blabber1Msg+msgTo+1
 +   lda #<blabber1Txt
 +   ldy #>blabber1Txt
 +   sta blabber1Msg+msgBuf+0
 +   sty blabber1Msg+msgBuf+1
 +-  inc $580
 +   lda #<blabber1Msg
 +   ldy #>blabber1Msg
 +   jsr Send
 +   jmp -
 +   blabber1Txt : db "Hi, this is blabber\n",0
 +
 +Spinner1 = *
 +   jsr MyParentPid
 +   sta spinner1Msg+msgTo+0
 +   sty spinner1Msg+msgTo+1
 +   lda #<spinner1Txt
 +   ldy #>spinner1Txt
 +   sta spinner1Msg+msgBuf+0
 +   sty spinner1Msg+msgBuf+1
 +-  inc $580
 +   lda #<spinner1Msg
 +   ldy #>spinner1Msg
 +   jsr Send
 +   jmp -
 +   spinner1Txt : db "Hi, this is spinner +\n",0
 +
 +KernelServer = *
 +   lda #$00
 +   sta mmuZeroPage
 +   lda #14
 +   jsr $ffd2
 +-  lda #<ksMsg
 +   ldy #>ksMsg
 +   jsr Receive
 +   lda ksMsg+msgBuf+0
 +   ldy ksMsg+msgBuf+1
 +   sta $80
 +   sty $81
 +   ldy #0
 +-  lda ($80),y
 +   beq +
 +   jsr $ffd2
 +   iny
 +   bne -
 ++  lda #<ksMsg
 +   ldy #>ksMsg
 +   jsr Reply
 +   jmp --
 +
 +bss = *
 +testDelay1Msg = $c00  ;** put these here to save pgm memory
 +testDelay2Msg = testDelay1Msg+msgSize
 +testDelay3Msg = testDelay2Msg+msgSize
 +testDelay4Msg = testDelay3Msg+msgSize
 +testDelay5Msg = testDelay4Msg+msgSize
 +blabber1Msg   = testDelay5Msg+msgSize
 +spinner1Msg   = blabber1Msg+msgSize
 +ksMsg         = spinner1Msg+msgSize
 +-----=-----
 +
 +APPENDIX B. UUENCODED DEMO PROGRAM
 +
 +The uuencoded demo system follows.  You can extract it with any uudecoder or
 +with version 2.00 or higher of "unbcode" (ACE has only version 1.00).
 +
 +-nucode-begin 1 bos
 +begin 640 bos
 +M`!-,)A,``````````````````````````````````````````````'BI#HT`
 +M_ZEVH!6-%`.,%0.IJZ`3C18#C!<#J:N@$XT8`XP9`ZD`C203C243J1Z@$X4&
 +MA`<@U12I`Z`3C0,3C`03C043C`83A0*$`ZG_C0<3J0"-"!.I((4,J<"-'1.I
 +M`HT-$UA,C1.I.Z`9H@$@_1/N``30#>X!!-`([@($T`/N`P1,EA-XJ66@^HT4
 +M`XP5`ZE`H/J-&`.,&0.B^IJI`(T'U8T(U:(!C@G5C0K5J02-!M583+=-````
 +M````````````````````````````````````````>(W=$XS>$X[?$ZT'U8W<
 +M$ZD`C0?5J0"D#(4(A`G(C.`3R(SA$\B$#,#`D`$`HAJI`)WB$\H0^JGVC>@3
 +MK>$3C>D3K>`3C>H3J02-ZQ.MWQ.-[!.-[1.E`J0#C?H3C/L3J<6-_!.@&KGB
 +M$Y$(B!#XI0@8:0Z%!J4):0"%!R#5%*D`K.$3A02$!:#WJ0Z1!,BB!*D`D03(
 +MRM#ZK=T3D03(K=X3D03(J0F1!,BI%I$$(+L4I0BD":[<$XX'U1A88*`:J<"1
 +M"*D#H!.%!H0'I0*D`X4$A`4@`!5@I0:D!XWZ%(S[%(W\%(S]%*G_C?X4J0"-
 +M_Q2@!;GZ%)$&B!#X8````````!B@!;$&:0&1!J``L021",BQ!)$(R*4$D0C(
 +MI061"*``L02%!LBQ!(4'H`*E")$&R*4)D0:@`*4(D03(I0F1!&"@`+$(A03(
 +ML0B%!:`"L0B1!,BQ")$$H`*Q"(4$R+$(A06@`+$(D03(L0B1!*`%L08XZ0&1
 +M!F#8J0Z-`/^M&=`0!"D!T`.M#=R-&="M!]6B`(X'U:`(D0*@!KJ*D0*@!ZT)
 +MU9$"H`FM!M61`NXD$]`#[B43K2,3\`,@71?JH`JQ`LB1`O`GH`"Q`JK(L0*&
 +M`H4#H`NQ`O`5..D!D0+P`TS%%:`$L0(0!<BQ`M#0H`FQ`HT&U:`'L0*-"=6@
 +M!K$"JIJ@"+$"C0?53#/_J0"B`$R.%@"I`*X'U8T'U:4"I`..!]488*D`K@?5
 +MC0?5H!BQ`DC(L0*H:(X'U1A@`````(T\%FB-.A9HC3L6:!AI`8T]%FAI`$BM
 +M/19(J0!(K3P62(I(F$BM`/](K0?5H@".!]6@")$"B*T)U9$"B+J*D0*@":T&
 +MU9$"K3L62*TZ%DA@>"`^%B"8%DS1%:4"A0BE`X4)J0.@$X4&A`<@0!6@`+$(
 +MA0+(L0B%`Z`:J<61"&#)`-`&P`#0`AA@>(T-%XP.%R`^%B"8%J`:J<2@#!BM
 +M#1=M)!.-#1>1",BM#A=M)1.-#A>1"*D`*HT/%ZD>H!.%!H0'A02$!2`1%R``
 +M%4S1%0`````@4!>@!+$$T"F@#+$$S203R+$$[243H@"P`>B.$!>(K0T7T03(
 +MK0X7\02M#Q?M$!>PSJ`"L02JR+$$A@2%!6"@`+$$JLBQ!(8$A05@K1X3K!\3
 +MA0B$":`,L0C-)!/P`6#(L0C-)1/P`6"I'J`3A0:$!R!`%2"[%$Q=%P``>(V+
 +M%XR,%R`^%B"8%JV+%ZR,%X4*A`N@`+$*A0;(L0J%!Z`,I0J1",BE"Y$(H!2E
 +M!I$(R*4'D0B@&K$&R<+P(*G!D0@8I09I#H4&D`+F!Z`"L0:%!,BQ!H4%(``5
 +M3-$5J<.1"*`,L0:%!,BQ!H4%($48I0BD":8&A@BF!X8)H@`8(!L8(+L43-$5
 +M`````(T9&(X8&(P7&`AH*0&-&AB@![$(A06@!K$(&&D"A02@`[D7&)$$B!#X
 +M8*`"I0B1"LBE"9$*H!>Q"I$$B!#Y8'B-BQ>,C!>M!]5(J0"-!]6@$[$"T!YH
 +MC0?5(#X6()@6J<*1"*`,K8L7D0C(K8P7D0A,T16E`J0#&&D.D`'(A0:$!Z`.
 +ML0*%",BQ`H4)($`5H`RQ"(4*R+$(A0NMBQ>LC!>%!(0%($48H!JIPY$(I@BD
 +M"6B-!]6*6!A@`'BN!]6.U!BB`(X'U84*A`N@`K$*A0C(L0J%":`:L0C)P_`+
 +MJ>&NU!B.!]4X6&"@%+$(Q0+0[<BQ",4#T.:@#+$(A03(L0B%!:`1L0J1!*`4
 +ML0J1!,C`&)#W(+L4KM08C@?5&%A@`*D!C3H9J8N@&:("(/T3J=V@&:(!(/T3
 +MJ2.@&J(!(/T3J6>@&J(!(/T3J:N@&J(!(/T3J>^@&J(!(/T3J3.@&Z(!(/T3
 +MJ6B@&Z(!(/T33)\;HANI`)T`U,H0^JE0A0*%`ZD(C1C4J0"@"(T"U(P#U*E!
 +MC034J0"-!=2I\(T&U*4"I`.-`-2,`=2E`@4#T`>I>*``(+T6Y@+0`N8#[B#0
 +MNDRY&2`C%HT`#(P!#*D$H!J-!`R,!0RI/*``(+T6[H$%J0"@#""-%TSP&<A)
 +M+"!42$E3($E3($1%3$%9(%!23T-%4U,@,2`J#0`@(Q:-&`R,&0RI2J`:C1P,
 +MC!T,J7B@`""]%NZ"!:D8H`P@C1=,-AK(22P@5$A)4R!)4R!$14Q!62!04D]#
 +M15-3(#(-`"`C%HTP#(PQ#*F.H!J--`R,-0RIM*``(+T6[H,%J3"@#""-%TQZ
 +M&LA)+"!42$E3($E3($1%3$%9(%!23T-%4U,@,PT`(",6C4@,C$D,J=*@&HU,
 +M#(Q-#*GPH``@O1;NA`6I2*`,((T73+X:R$DL(%1(25,@25,@1$5,05D@4%)/
 +M0T534R`T#0`@(Q:-8`R,80RI%J`;C60,C&4,J2R@`2"]%NZ%!:E@H`P@C1=,
 +M`AO(22P@5$A)4R!)4R!$14Q!62!04D]#15-3(#4-`"`C%HUX#(QY#*E3H!N-
 +M?`R,?0SN@`6I>*`,((T73$8;R$DL(%1(25,@25,@0DQ!0D)%4@T`(",6C9`,
 +MC)$,J8B@&XV4#(R5#.Z`!:F0H`P@C1=,>QO(22P@5$A)4R!)4R!34$E.3D52
 +M("L-`*D`C0?5J0X@TO^IJ*`,(%H8K:P,K*T,A8"$@:``L8#P!B#2_\C0]JFH
 +(H`P@U1A,J1L`
 +`
 +end
 +-nucode-end 1 2258 1430bdc2
 +
 +========================================================================END===
 +</code>
magazines/chacking10.txt · Last modified: 2015-04-17 04:34 by 127.0.0.1