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: | ||
+ | < | ||
+ | ######## | ||
+ | ################## | ||
+ | ###### | ||
+ | ##### | ||
+ | ##### #### #### ## ##### #### | ||
+ | ##### ## ## #### ## ## | ||
+ | ##### | ||
+ | ##### ## ## ######## | ||
+ | ##### #### #### #### #### ##### #### | ||
+ | ##### ## | ||
+ | ###### | ||
+ | ################## | ||
+ | ######## | ||
+ | ------------------------------------------------------------------------------ | ||
+ | </ | ||
+ | ====== Editor' | ||
+ | < | ||
+ | by Craig Taylor | ||
+ | |||
+ | This is my last issue of Commodore Hacking (having finally gotten out the | ||
+ | door, but I couldn' | ||
+ | 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' | ||
+ | |||
+ | 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. | ||
+ | |||
+ | 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 | ||
+ | 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, | ||
+ | comp.sys.cbm FAQ Manitainer, or the keeper of a Commodore Information | ||
+ | WWW Site at http:// | ||
+ | heard of me from, or even if you haven' | ||
+ | handling Commodore Hacking in the following way. The next issue will | ||
+ | possibly look different cosmetically, | ||
+ | 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. | ||
+ | I do have a few changes in mind: | ||
+ | |||
+ | 1) Try to stabilize the issue generation so that Commodore Hacking will | ||
+ | | ||
+ | |||
+ | 2) Attempt a fully " | ||
+ | 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 | ||
+ | | ||
+ | |||
+ | So, again I say howdy. | ||
+ | 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 " | ||
+ | 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 " | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | |||
+ | Please note that this issue and prior ones are available via anonymous FTP | ||
+ | from ccnga.uwaterloo.ca (among others) under / | ||
+ | mailserver which documentation can be obtained by sending mail to | ||
+ | " | ||
+ | lines of " | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | </ | ||
+ | |||
+ | ====== In This Issue ====== | ||
+ | < | ||
+ | Commodore Trivia | ||
+ | |||
+ | Trivia Edition #13-18 are in this article. | ||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | that. | ||
+ | |||
+ | Making stable raster routines (C64 and VIC-20) | ||
+ | |||
+ | In this article, I document two methods of creating stable raster | ||
+ | routines on Commodore computers. | ||
+ | computers, not only Commodores, but raster effects are very rarely | ||
+ | seen on other computers. | ||
+ | |||
+ | A Differant Perspective - Part III. | ||
+ | |||
+ | Yes!!! | ||
+ | haven' | ||
+ | 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' | ||
+ | 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. | ||
+ | usefull numerical algorithm, " | ||
+ | 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 " | ||
+ | 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/ | ||
+ | |||
+ | This article details how to implement a custom upload/ | ||
+ | is faster than most of the ones common to the C64/128 computers. | ||
+ | |||
+ | Design and Implementation of a ' | ||
+ | |||
+ | There has been a slight change in plans. | ||
+ | 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 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. | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== Trivia ====== | ||
+ | < | ||
+ | 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. | ||
+ | all the people who contribute to the trivia and all the people who take | ||
+ | part in the monthly contest. | ||
+ | article. | ||
+ | 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. | ||
+ | | ||
+ | |||
+ | 3) The Commodore World Wide Web Pages (http:// | ||
+ | that I maintain and place the trivia on caught the eye of USA Today and | ||
+ | the Pheonix Gazette. | ||
+ | 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). | ||
+ | |||
+ | |||
+ | Here are the answers to Commodore Trivia Edition #13 for December, 1994 | ||
+ | |||
+ | Q $0C0) The early 1541 drives used a mechanism developed by ______. | ||
+ | the company. | ||
+ | |||
+ | A $0C0) Alps. | ||
+ | |||
+ | Q $0C1) On later models, Commodore subsequently changed manufacturers | ||
+ | for the 1541 drive mechanism. | ||
+ | |||
+ | A $0C1) Newtronics. | ||
+ | |||
+ | Q $0C2) What is the most obvious difference(s). | ||
+ | necessary) | ||
+ | |||
+ | A $0C2) Alps: push-type latch, round LED. | ||
+ | Newtronics: | ||
+ | |||
+ | 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). | ||
+ | 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: | ||
+ | |||
+ | 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. | ||
+ | is TRUE (-1). | ||
+ | |||
+ | Q $0C5) The first PET cassette decks were actually _______ brand cassette | ||
+ | players, modified for the PET computers. | ||
+ | |||
+ | A $0C5) Sanyo. Specifically, | ||
+ | |||
+ | 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 " | ||
+ | 40 PRINT " | ||
+ | |||
+ | A $0C6) On BASIC 2.0 or greater: | ||
+ | |||
+ | ? | ||
+ | READY. | ||
+ | | ||
+ | On BASIC 1.0: (found on the PET 2001 series) | ||
+ | | ||
+ | J=0 | ||
+ | READY. | ||
+ | |||
+ | BASIC 1.0 totally ignored spaces, so line 20 became " | ||
+ | That statement would be correctly parsed, sicne it contains the " | ||
+ | keyword. | ||
+ | | ||
+ | However, on BASIC 2.0 or greater, spaces weren' | ||
+ | completely, | ||
+ | some code was added to BASIC to check to " | ||
+ | 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 " | ||
+ | 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. | ||
+ | |||
+ | A $0C9) According to Commodore documentation, | ||
+ | defined as one screen line of characters. | ||
+ | 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. | ||
+ | for 40 and 80 column screens, but what do we do with the VIC-20, with | ||
+ | its 22 column screen. | ||
+ | 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. | ||
+ | I am not surprised. | ||
+ | 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); | ||
+ | entry look like? | ||
+ | |||
+ | A $0CA) The filename will show up as " | ||
+ | right of the '"' | ||
+ | easier. | ||
+ | by simply hitting shift-run/ | ||
+ | |||
+ | Q $0CB) What is the maximum length (in characters) of a CBM datasette | ||
+ | filename? | ||
+ | |||
+ | A $0CB) References I have on hand say 128 characters. | ||
+ | code on the 8032 and the C64 acts as though 187 characters can | ||
+ | actually be sent (tape buffer-5 control bytes = 192-5=187). | ||
+ | references that claim 128 characters are Nick Hampshire' | ||
+ | _The VIC Revealed_ and _The PET Revealed_. | ||
+ | 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 " | ||
+ | 129 becomes " | ||
+ | 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 " | ||
+ | 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 " | ||
+ | |||
+ | A $0D1) SYS 32800, | ||
+ | and hardware developers on the 128 project will be named. | ||
+ | |||
+ | The exact text is as follows: | ||
+ | | ||
+ | [RVS] | ||
+ | |||
+ | Software: | ||
+ | Fred Bowen | ||
+ | Terry Ryan | ||
+ | Von Ertwine | ||
+ | |||
+ | Herdware: | ||
+ | Bil Herd | ||
+ | Dave Haynie | ||
+ | Frank Palaia | ||
+ | |||
+ | [RVS]Link arms, | ||
+ | |||
+ | Q $0D2) How much memory did the " | ||
+ | |||
+ | A $0D2) The " | ||
+ | |||
+ | The PET 2001-4 had 3071 bytes. | ||
+ | The PET 2001-8 had 7167 bytes. | ||
+ | |||
+ | Q $0D3) We all know the " | ||
+ | the same sys location to reboot the CBM 8032? | ||
+ | |||
+ | A $0D3) sys 64790 | ||
+ | |||
+ | Q $0D4) Which computer(s) beeped at bootup? | ||
+ | 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. | ||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | that at least some original Amiga machines were labeled as | ||
+ | Amiga (with nu number). | ||
+ | added. | ||
+ | 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. | ||
+ | |||
+ | 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, " | ||
+ | refers to picture elements that can be adddress and modified using | ||
+ | normal VIC modes, so there are 320*200 " | ||
+ | and NTSC screens. | ||
+ | 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 | ||
+ | | ||
+ | |||
+ | There are even more subtle ones, but I leave them as an | ||
+ | exercise for the reader. | ||
+ | |||
+ | |||
+ | 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.) | ||
+ | introduced, Commodore threw a number of drives together and called | ||
+ | them 1541Cs. | ||
+ | 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: | ||
+ | is printed at screen top. This is the behavior seen when pressing | ||
+ | RUN-STOP/ | ||
+ | could lock up. | ||
+ | |||
+ | Involved answer: | ||
+ | destined to live life immortal. | ||
+ | |||
+ | The bug is in the PETSCII number to binary conversion routine at | ||
+ | $a69b (LINGET). | ||
+ | line, multiplies a partial result by 10 and adds the new character | ||
+ | to the partial result. | ||
+ | |||
+ | 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 | ||
+ | a982 rol $22 ; hi*2 + c | ||
+ | a984 | ||
+ | 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. | ||
+ | (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. | ||
+ | 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 | ||
+ | |||
+ | 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. | ||
+ | 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 | ||
+ | a96a | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | handler branch to $a955, all these troubles disappear. | ||
+ | verify this procedure with the following BASIC program on a 64: | ||
+ | | ||
+ | 10 for t=57344 to 65535:poke t, | ||
+ | 20 for t=40960 to 49151:poke t, | ||
+ | 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). | ||
+ | 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 " | ||
+ | like: | ||
+ | |||
+ | a) "/ | ||
+ | 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' | ||
+ | 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, | ||
+ | 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 " | ||
+ | file? | ||
+ | |||
+ | A $0E9) bit 6. | ||
+ | |||
+ | Q $0EA) If files are " | ||
+ | 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 | ||
+ | | ||
+ | 6) Opening a relative file and adding or changing a record will | ||
+ | | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | really doing is opening the disk for reading and writing. | ||
+ | two open command to access a random file: (assume drive 8) | ||
+ | | ||
+ | open 15, | ||
+ | | ||
+ | open 1, | ||
+ | open 1, | ||
+ | available buffer | ||
+ | | ||
+ | Now, by using B-R, B-W, B-A or their replacements, | ||
+ | data to sectors on the disk. | ||
+ | | ||
+ | Note that Random access files are different from relative files. | ||
+ | | ||
+ | Q $0ED) A file that has a ' | ||
+ | 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. | ||
+ | memory, one for stack memory, and one for temporary variables.) | ||
+ | |||
+ | Q $0EF) On a " | ||
+ | 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) " | ||
+ | |||
+ | Q $0F3) The character set that produces lowercase letters on unshifted keys | ||
+ | is the ________________ character set. | ||
+ | |||
+ | A $0F3) " | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | A $0F6) Yes. The above translates as: LIST 60 through to and including 100. | ||
+ | |||
+ | Q $0F7) The abbreviation for the BASIC 4.0 command " | ||
+ | |||
+ | A $0F7) coL. " | ||
+ | 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. | ||
+ | in Commodore BASIC has seen the "BAD SUBSCRIPT" | ||
+ | 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. | ||
+ | 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. | ||
+ | creates 2 256 " | ||
+ | compatibility with older Commodore drives. | ||
+ | |||
+ | Q $0FB) You'll find BASIC 3.5 on the _____________ line of CBM computers. | ||
+ | |||
+ | A $0FB) The X64 series. | ||
+ | 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 " | ||
+ | registers. | ||
+ | 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. | ||
+ | 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. | ||
+ | owns one, and used it for many years. | ||
+ | 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. | ||
+ | Anchor Automation for Commodore. | ||
+ | 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' | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | has a few limitations. | ||
+ | 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: | ||
+ | Graphics resolution: | ||
+ | |||
+ | 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. | ||
+ | U1 command always reads a full sector (255 bytes). | ||
+ | command reads the number of bytes specified in the first byte of | ||
+ | the sector. | ||
+ | from the sector. | ||
+ | |||
+ | 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. | ||
+ | drive and computer failed to complete the command transaction | ||
+ | successfully, | ||
+ | Commodore later fixed this problem. | ||
+ | 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. | ||
+ | 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. | ||
+ | |||
+ | Q $109) Commodore 1581 drives have a special " | ||
+ | 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 " | ||
+ | |||
+ | 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, | ||
+ | separate axis, so we will describe just one register: | ||
+ | | ||
+ | Bit: | ||
+ | | ||
+ | 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. | ||
+ | reserve 4 bytes for user programs that need zero page memory. | ||
+ | are these locations? | ||
+ | |||
+ | A $10D) $FB-$FE (251-254). | ||
+ | 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). | ||
+ | |||
+ | $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) | ||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | 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! | ||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== BFLI - New graphics modes 2 ====== | ||
+ | < | ||
+ | by Pasi ' | ||
+ | |||
+ | One day I was watching some demos that used linecrunch routines for | ||
+ | whole-screen multicolor-graphics upscrollers. | ||
+ | theories about how and why linecrunch worked, but because I had not | ||
+ | used it anywhere, the details were a bit vague. | ||
+ | many times accidentally created linecrunch effects when trying to do | ||
+ | something else with $D011. | ||
+ | |||
+ | But you learn by doing. | ||
+ | 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: | ||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | graphics nor does it reset the internal counters. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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). | ||
+ | are not unused memory, but they are not used with normal FLI.) | ||
+ | |||
+ | | ||
+ | |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, | ||
+ | time). | ||
+ | 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. | ||
+ | is the third character row. However, those two lost rows can still | ||
+ | be used as an extension at the end of the first screen. | ||
+ | notice, however, that the alignment has been changed. | ||
+ | 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. | ||
+ | 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. | ||
+ | reason we can't even think about using copying. | ||
+ | with the bad line delaying technique will do the job much more | ||
+ | nicely. | ||
+ | |||
+ | Figure 2 shows the principles. | ||
+ | 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, | ||
+ | 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. | ||
+ | after 12 lines, and we need at least 7 lines to use the soft | ||
+ | y-scroll. | ||
+ | originally. | ||
+ | |||
+ | Because we need to show 400 lines of graphics, we would need | ||
+ | (400-181)/ | ||
+ | number of lines we have for graphics to 181-28=153 and we need | ||
+ | (400-153)/ | ||
+ | (400-150)/ | ||
+ | for graphics, which makes location values 0..251 valid. | ||
+ | |||
+ | |||
+ | Location | ||
+ | |||
+ | ___________________.. | ||
+ | ___________________.. | ||
+ | Linecrunch | ||
+ | ^ | ||
+ | | | ||
+ | | | ||
+ | Bad line delayed| | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | v | ||
+ | Gfx Enabled | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | 5 | ||
+ | 6 | ||
+ | 7 | ||
+ | : : | ||
+ | : : | ||
+ | 148 | ||
+ | |||
+ | 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. | ||
+ | need to enable the graphics at the same point regardless of the | ||
+ | y-scroll value. | ||
+ | |||
+ | When both ECM and multicolor mode (MCM) are selected, VIC-II will | ||
+ | turn the display to black. | ||
+ | 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. | ||
+ | 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 ' | ||
+ | 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 ' | ||
+ | |||
+ | -------------------------------------------------------------------------- | ||
+ | |||
+ | BFLI viewer program for PAL machines | ||
+ | |||
+ | UPOS = $C00 ; temporary area for tables | ||
+ | BANK = $D00 ; | ||
+ | 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 #< | ||
+ | LDA #> | ||
+ | LDA #RASTER:STA $D001: | ||
+ | LDA #0:STA $D017 | ||
+ | LDA #0:STA 2 | ||
+ | JSR NEWPOS | ||
+ | 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, | ||
+ | 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, | ||
+ | BYT $78, | ||
+ | |||
+ | *=*-< | ||
+ | |||
+ | 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 | ||
+ | LDA UPOS+6, | ||
+ | NOP:NOP:INC DUMMY | ||
+ | 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: | ||
+ | NOP: | ||
+ | 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: | ||
+ | B0 LDA #$92:STA $DD00: | ||
+ | |||
+ | ; Wait for 0-7 lines to set the ECM mode off | ||
+ | ; (makes the graphics visible) | ||
+ | |||
+ | F0 LDA #0:STA $D011:LDA #$08:STA $D018: | ||
+ | F1 LDA #0:STA $D011:LDA #$18:STA $D018: | ||
+ | F2 LDA #0:STA $D011:LDA #$28:STA $D018: | ||
+ | F3 LDA #0:STA $D011:LDA #$38:STA $D018: | ||
+ | F4 LDA #0:STA $D011:LDA #$48:STA $D018: | ||
+ | F5 LDA #0:STA $D011:LDA #$58:STA $D018: | ||
+ | F6 LDA #0:STA $D011:LDA #$68:STA $D018: | ||
+ | F7 LDA #0:STA $D011:LDA #$78:STA $D018 | ||
+ | LDX # | ||
+ | |||
+ | ; 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: | ||
+ | FA LDA #0:STA $D011:LDA #$28:STA $D018: | ||
+ | FB LDA #0:STA $D011:LDA #$38:STA $D018: | ||
+ | FC LDA #0:STA $D011:LDA #$48:STA $D018: | ||
+ | FD LDA #0:STA $D011:LDA #$58:STA $D018: | ||
+ | FE LDA #0:STA $D011:LDA #$68:STA $D018: | ||
+ | FF LDA #0:STA $D011:LDA #$78:STA $D018: | ||
+ | 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 | ||
+ | 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 | ||
+ | LSR: | ||
+ | LDA #7:SEC:SBC NEWPOS+1: | ||
+ | LDA UPOS+3+7, | ||
+ | J0 STA F7+1:AND #$3F:STA FF+1 | ||
+ | LDA UPOS+3+6, | ||
+ | J1 STA F6+1:AND #$3F:STA FE+1 | ||
+ | LDA UPOS+3+5, | ||
+ | J2 STA F5+1:AND #$3F:STA FD+1 | ||
+ | LDA UPOS+3+4, | ||
+ | J3 STA F4+1:AND #$3F:STA FC+1 | ||
+ | LDA UPOS+3+3, | ||
+ | J4 STA F3+1:AND #$3F:STA FB+1 | ||
+ | LDA UPOS+3+2, | ||
+ | J5 STA F2+1:AND #$3F:STA FA+1 | ||
+ | LDA UPOS+3+1, | ||
+ | J6 STA F1+1:AND #$3F:STA F9+1 | ||
+ | LDA UPOS+3+0, | ||
+ | J7 STA F0+1:AND #$3F:STA F8+1 | ||
+ | LDA #$96:STA B0+1:LDA # | ||
+ | LSR: | ||
+ | RTS | ||
+ | OV2 LDA #0:STA B1+1:LDX #$94:STX B0+1:RTS | ||
+ | |||
+ | CHPOS LDX NEWPOS+1 | ||
+ | LDA $DC00: | ||
+ | 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: | ||
+ | UP INX:CPX #$FD:BCC UOK ; 251(locations)+149(visible)=400 | ||
+ | LDX #$FC:STX DIR+1 ; Change direction | ||
+ | UOK STX NEWPOS+1: | ||
+ | |||
+ | |||
+ | -------------------------------------------------------------------------- | ||
+ | |||
+ | The BFLI file format: | ||
+ | |||
+ | File BFLI Display | ||
+ | Lines | ||
+ | Colors | ||
+ | I | ||
+ | 2-24 80..999 | ||
+ | 24-24.7 | ||
+ | |||
+ | II 0-1.3 | ||
+ | 1.3-24.7 | ||
+ | |||
+ | |||
+ | Gfx | ||
+ | I | ||
+ | 2-24 640..7999 | ||
+ | 24-24.7 | ||
+ | |||
+ | II 0-1.3 | ||
+ | 1.3-24.7 | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== Making stable raster routines (C64 and VIC-20) ====== | ||
+ | < | ||
+ | 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. | ||
+ | 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. | ||
+ | video chip timing details together with my German friend Andreas | ||
+ | Boose. | ||
+ | it came to the hardware, but he was the only of us who had written a | ||
+ | stable raster routine. | ||
+ | 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. | ||
+ | from demo people: They often code by instinct; by patching the routine | ||
+ | until it works, without knowing exactly what is happening. | ||
+ | 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. | ||
+ | computers, not only Commodores, but raster effects are very rarely | ||
+ | seen on other computers. | ||
+ | |||
+ | |||
+ | Background | ||
+ | |||
+ | What are raster effects? | ||
+ | screen appearance while it is being drawn. | ||
+ | the screen color to white in the top of the screen, and to black in | ||
+ | the middle of the screen. | ||
+ | top half is white and bottom half black. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | arrives while interrupts are disabled and the processor is just | ||
+ | starting to execute a CLI instruction. | ||
+ | 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. | ||
+ | equation, and actually out of the scope of this article. | ||
+ | |||
+ | How to synchronize a raster interrupt routine? | ||
+ | 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. | ||
+ | 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. | ||
+ | interrupt, and the second timer, the auxiliary timer, tells the raster | ||
+ | routine where it is running. | ||
+ | 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. | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | cycles, a loop of 7 raster register value changes would do, but I made | ||
+ | the loop a bit longer in my VIC-20 routine. | ||
+ | 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. | ||
+ | that the auxiliary timer will be at least 0 when it is being read in | ||
+ | the raster routine. | ||
+ | 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' | ||
+ | probably the first one who made a stable raster interrupt routine on | ||
+ | the VIC-20. | ||
+ | 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. | ||
+ | the beginning of the next raster interrupt will be off at most by one | ||
+ | cycle. | ||
+ | can do it right, why wouldn' | ||
+ | |||
+ | 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. | ||
+ | possibilities: | ||
+ | change on the next cycle. | ||
+ | register changed one cycle too early or not, and delay a cycle when | ||
+ | needed. | ||
+ | |||
+ | 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. | ||
+ | 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). | ||
+ | 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. | ||
+ | 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. | ||
+ | exactly 50 or 60 Hertz, but they didn't hesitate to claim that their | ||
+ | computers comply with the PAL-B or NTSC-M standards. | ||
+ | tables I have gathered some information of some Commodore video chips. | ||
+ | |||
+ | |||
+ | NTSC-M systems: | ||
+ | |||
+ | Chip Crystal | ||
+ | Host ID freq/ | ||
+ | ------ | ||
+ | VIC-20 | ||
+ | C64 | ||
+ | C64 | ||
+ | |||
+ | Later NTSC-M video chips were most probably like the 6567R8. | ||
+ | that the processor clock is a 14th of the crystal frequency on all | ||
+ | NTSC-M systems. | ||
+ | |||
+ | PAL-B systems: | ||
+ | |||
+ | Chip Crystal | ||
+ | Host ID freq/ | ||
+ | ------ | ||
+ | VIC-20 | ||
+ | C64 | ||
+ | |||
+ | 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. | ||
+ | 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 " | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | processor core, except the C65 prototype, which used a inferior CMOS | ||
+ | version with all nice poorly-documented features removed.) | ||
+ | check the 64doc document, available on my WWW pages at | ||
+ | http:// | ||
+ | ftp.funet.fi:/ | ||
+ | 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. | ||
+ | in English and partially in German. | ||
+ | from ftp.funet.fi as / | ||
+ | copies of the German part (screen resolution, sprite disturbance | ||
+ | measurements, | ||
+ | |||
+ | The code is written for the DASM assembler, or more precisely for a | ||
+ | extended ANSI C port of it made by Olaf Seibert. | ||
+ | cross-assembler is available at ftp.funet.fi in / | ||
+ | |||
+ | First the raster demo for the VIC-20. | ||
+ | $9004 register contains the upper 8 bits of the raster counter. | ||
+ | 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. | ||
+ | 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. | ||
+ | partially broken, and because of this, this program did not work on | ||
+ | his computer. | ||
+ | it almost worked. | ||
+ | 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 | ||
+ | |||
+ | #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) | ||
+ | |||
+ | ; | ||
+ | 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 #< | ||
+ | ldx #> | ||
+ | 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 #< | ||
+ | sta $314 | ||
+ | lda #>irq | ||
+ | sta $315 | ||
+ | lda #$c0 | ||
+ | sta $912e ; enable Timer A underflow interrupts | ||
+ | rts ; return | ||
+ | |||
+ | irq: | ||
+ | ; irq (event) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp ($314) | ||
+ | ; --- | ||
+ | ; 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. | ||
+ | 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. | ||
+ | size controls to witness these effects.) | ||
+ | |||
+ | This program is really robust, it installs itself nicely to the | ||
+ | interrupt routine chain. | ||
+ | itself. | ||
+ | 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 " | ||
+ | cycles. | ||
+ | instructions, | ||
+ | When coding the routine, I noticed again how stupid assembly coding | ||
+ | can be, especially conditional assembling. | ||
+ | monitor you have far better control on page boundaries. | ||
+ | might wonder why I disable the Restore key in a subroutine at the end | ||
+ | and not in the beginning of the program. | ||
+ | long that it would have affected the " | ||
+ | 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: | ||
+ | |||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ now we are here | ||
+ | |||
+ | The two vertical bars " | ||
+ | (63 cycles per line), they are not present. | ||
+ | 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 " | ||
+ | sprite image fetches, the " | ||
+ | graphics fetches. | ||
+ | On the processor timing line, the " | ||
+ | free bus, and " | ||
+ | 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: | ||
+ | jsr restore | ||
+ | checkirq: | ||
+ | lda cinv ; check the original IRQ vector | ||
+ | ldx cinv+1 | ||
+ | cmp #<irq1 | ||
+ | bne irqinit | ||
+ | cpx #>irq1 | ||
+ | beq skipinit | ||
+ | irqinit: | ||
+ | sei | ||
+ | sta oldirq | ||
+ | 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, | ||
+ | adc #24 | ||
+ | sta m | ||
+ | tya | ||
+ | sta $d001, | ||
+ | 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) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp ($314) | ||
+ | ; --- | ||
+ | ; 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) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp (cinv) | ||
+ | ; --- | ||
+ | ; 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) | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ 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 | ||
+ | ; because the page boundary is crossed. | ||
+ | badline: | ||
+ | dec m | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | dec $d016 | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ we are here | ||
+ | stx $d016 | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ ^^- 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 | ||
+ | endirq: | ||
+ | jmp $ea81 ; return to the auxiliary raster interrupt | ||
+ | |||
+ | restore: | ||
+ | lda cnmi | ||
+ | ldy cnmi+1 | ||
+ | pha | ||
+ | lda #< | ||
+ | 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 | ||
+ | rts | ||
+ | |||
+ | |||
+ | Binaries | ||
+ | |||
+ | Here are the programs in uuencoded format. | ||
+ | |||
+ | Color boxes for the VIC-20, NTSC-M version (probably distorted display): | ||
+ | |||
+ | begin 644 copper.6560 | ||
+ | M`1`*$, | ||
+ | M%< | ||
+ | MCA61J6R-%`.I$(T5`ZG`C2Z18*T4D< | ||
+ | ML`" | ||
+ | , | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | Color boxes for the VIC-20, PAL-B version: | ||
+ | |||
+ | begin 644 copper.6561 | ||
+ | M`1`*$, | ||
+ | MT/ | ||
+ | MD: | ||
+ | MH!" | ||
+ | +: | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | Removed sideborders with 8 sprites and bad lines, PAL-B version: | ||
+ | |||
+ | begin 644 raster.63 | ||
+ | M`0@*", | ||
+ | ME: | ||
+ | M[ZE_C0W< | ||
+ | M" | ||
+ | MZNKJZNI, | ||
+ | MZJD4A? | ||
+ | MT*`" | ||
+ | 1!-VBW8X.W: | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | Removed sideborders with 8 sprites and bad lines, 6567R56A version | ||
+ | (very old NTSC-M C64s): | ||
+ | |||
+ | begin 644 raster.64 | ||
+ | M`0@*", | ||
+ | ME: | ||
+ | M[ZE_C0W< | ||
+ | M" | ||
+ | MZNKJZNI, | ||
+ | MZJD4A? | ||
+ | MT*`" | ||
+ | 2C@3=HMV.# | ||
+ | ` | ||
+ | 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@*", | ||
+ | ME: | ||
+ | M[ZE_C0W< | ||
+ | M" | ||
+ | MZNKJZNI, | ||
+ | MZNJI%(7[HLB@`HC0_< | ||
+ | MCA; | ||
+ | 5!=WHC@3=HMV.# | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | That was all, folks! | ||
+ | Feel free to e-mail me at Marko.Makela@HUT.FI, | ||
+ | unclear. | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== A Different Perspective, | ||
+ | < | ||
+ | by Stephen Judd --- sjudd@nwu.edu | ||
+ | | ||
+ | |||
+ | Whew! What a busy time it's been -- research to get done, | ||
+ | conferences, | ||
+ | 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! | ||
+ | everything has been slapped together at the last minute, and I hope | ||
+ | you'll forgive any bugs or unclear concepts. | ||
+ | >>> | ||
+ | And that reminds me: I just got JiffyDOS and an FD-2000 drive -- | ||
+ | what a wonderful device. | ||
+ | three partitions. | ||
+ | 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. | ||
+ | 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. | ||
+ | filled, etc. And, much to my three-day astonishment, | ||
+ | what we are going to do. | ||
+ | But first, a little excursion. | ||
+ | thinking about is optimization possibilities: | ||
+ | sleeping/ | ||
+ | cycle hogs in the program are line drawing and face filling -- well, | ||
+ | filling faces is pretty straightforward. | ||
+ | Well, one downer of the routine is that every single pixel is | ||
+ | plotted. | ||
+ | several smaller vertical and horizontal lines -- wouldn' | ||
+ | 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! | ||
+ | | ||
+ | Neat-o Enhanced Chunky Line Drawing Routine | ||
+ | ------------------------------------------- | ||
+ | |||
+ | First we need to be in the right mindframe. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | %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. | ||
+ | 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? | ||
+ | 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. | ||
+ | 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. | ||
+ | the program would draw a vertical line on the screen. | ||
+ | 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? | ||
+ | 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. | ||
+ | we need a list of sets of points, where each set corresponds to a | ||
+ | polygon. | ||
+ | in that set, and the points could then follow. | ||
+ | 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. | ||
+ | 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. | ||
+ | points in the polygon, i.e. multiply M times each point of the | ||
+ | polygon. | ||
+ | Uh-oh: matrix multiplication. | ||
+ | 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. | ||
+ | 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. | ||
+ | will we be dealing with? The matrix elements vary between -64..64. | ||
+ | This then fixes our range of polygon coordinates from -64..64. | ||
+ | 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). | ||
+ | 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. | ||
+ | |||
+ | What about hidden faces? | ||
+ | that a method was described which used the cross-product of the projected | ||
+ | vectors. | ||
+ | the first three points of the polygon, we have two vectors. | ||
+ | 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? | ||
+ | v1 x v2 = -v2 x v1. What it really boils down to is how you define the | ||
+ | points in your polygon. | ||
+ | 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 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. | ||
+ | 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. | ||
+ | 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? | ||
+ | pieces of line segments. | ||
+ | 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? | ||
+ | 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! | ||
+ | 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. | ||
+ | the insight to solve the above two problems. | ||
+ | First let's think about vertical lines. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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, | ||
+ | lines which come together at each vertex of the polygon. | ||
+ | that there are only certain cases which we need to worry about. | ||
+ | For instance, two lines might join in any of the following ways: | ||
+ | |||
+ | \ / \ / | ||
+ | | ||
+ | \_____ | ||
+ | |||
+ | If you draw out the different cases involving vertical lines, you can see | ||
+ | that you have to be careful about plotting the lines. | ||
+ | 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. | ||
+ | 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), | ||
+ | | ||
+ | 2) When plotting a vertical line, consistently plot either the | ||
+ | first part of each chunk or the last part of each chunk | ||
+ | | ||
+ | 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. | ||
+ | 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. | ||
+ | triangle, pointing in the x-direction. | ||
+ | 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 -----*------. | ||
+ | I just thought of it, and I think my program will fail on intersections | ||
+ | like these. | ||
+ | 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. | ||
+ | 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. | ||
+ | use EOR (EORBUF),Y : AND PATTERN,Y : ORA (DRAWBUF),Y (as long as you | ||
+ | preserve the original EOR (EORBUF), | ||
+ | |||
+ | 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 " | ||
+ | screen comes up, hit ' | ||
+ | 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. | ||
+ | McBride has pointed something out to me about the algorithm, which makes | ||
+ | it complete. | ||
+ | circle for small radii. | ||
+ | value to R/2, instead of R, gave a perfect circle. | ||
+ | If you recall the algorithm, we are computing a fractional quantity, | ||
+ | and when that quantity becomes larger than one, we decrease X. Wouldn' | ||
+ | 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*" | ||
+ | MH*" | ||
+ | MH*" | ||
+ | MH' | ||
+ | M+S$Y+SDTH*" | ||
+ | M-*" | ||
+ | M+C& | ||
+ | M.J V+S$U+SDUH*" | ||
+ | MH*" | ||
+ | M04V@5TE, | ||
+ | MH*" | ||
+ | M*@TJH& | ||
+ | M55)& | ||
+ | MH%-%0U)%5*" | ||
+ | MH*" | ||
+ | MH$-(54Y+6: | ||
+ | MH*" | ||
+ | M+C& | ||
+ | M0T53H" | ||
+ | MH*" | ||
+ | M*Z!E; | ||
+ | MH*" | ||
+ | M*@TJH$%# | ||
+ | M1RR@:E5.+J Y-: | ||
+ | M4%)/ | ||
+ | MH*" | ||
+ | M(:" | ||
+ | MH*" | ||
+ | M1T523%F@1E)%455%3E2@H*" | ||
+ | M3E0LH$%.1*!(14%21*" | ||
+ | MH*" | ||
+ | M34]21:" | ||
+ | M04U%H& | ||
+ | M*@TJH*" | ||
+ | MH*" | ||
+ | M5$A%H*" | ||
+ | MH$%.1*!(059%H$Y/ | ||
+ | M3U5.1$E.1Z!" | ||
+ | MH*" | ||
+ | MH*" | ||
+ | M5U))5%1%3J!54TE.1Z" | ||
+ | MH*" | ||
+ | M.# P, T-*J!C3TY35$%.5%, | ||
+ | M4D%# | ||
+ | M4T54# | ||
+ | M83, | ||
+ | M< | ||
+ | M.W1(15-%H%I%4D^@4$%' | ||
+ | M0T].1DQ)0U2@5TE42*!B87-I8PUY, | ||
+ | M:' | ||
+ | M=#& | ||
+ | MH$-/ | ||
+ | M05)9H%9!4DE!0DQ%4PUZ=& | ||
+ | M4U=!4" | ||
+ | M4D]55$E.10UZ, | ||
+ | M# | ||
+ | M142@1D]2H$A)1$1%3@T@(" | ||
+ | M3U5#2 UH: | ||
+ | M97%U(" | ||
+ | M," [=$A%4D6@05)%H# | ||
+ | M(&5Q=2 D9# Q. UB: | ||
+ | M< | ||
+ | M- T-# | ||
+ | M- T-*J!S3TU%H%9!4DE!0DQ%4PT-9VQO8GAM: | ||
+ | M1: | ||
+ | M1Z H1TQ/ | ||
+ | M-# | ||
+ | M5$A%# | ||
+ | M(# | ||
+ | M4$]205)9H%-43U)!1T4-<# | ||
+ | M1: | ||
+ | M4D6@4T^@5$A!5*!710UP, | ||
+ | M54Q!5$6@5$A%32X-<# | ||
+ | M1D6@14%362X-<# | ||
+ | M2TE.1Z!!5*!-1: | ||
+ | M55-4H$U%/ | ||
+ | M2$E, | ||
+ | M-3(-> | ||
+ | M5$A%H$E.0U)%345.5*!& | ||
+ | M(" | ||
+ | M(# | ||
+ | M(# | ||
+ | M2$6@4D]4051)3TX-=# | ||
+ | M1: | ||
+ | M# | ||
+ | M834@.W1(15-%H$%21: | ||
+ | M5%))6 UB,3(@/2 D838@.WAY> | ||
+ | M3E5-0D52H$1%3D]415.@*%)/ | ||
+ | M86$-9S, | ||
+ | M# | ||
+ | M.W=!252@1D]2H$& | ||
+ | M(& | ||
+ | M#2!D;Z PH* [9$].)U2@05-314U" | ||
+ | M# | ||
+ | M(R=3)R [; | ||
+ | M;G5P#2!J;7 @9& | ||
+ | M60T@8FYE(& | ||
+ | M82!M86, | ||
+ | M# | ||
+ | M(' | ||
+ | M,3$Q,2 [< | ||
+ | M< | ||
+ | M;& | ||
+ | M,S U, | ||
+ | M)Z" | ||
+ | MH*" | ||
+ | M15!(14Z@2E5$1"< | ||
+ | M, | ||
+ | M4U-51: | ||
+ | M8@T@=' | ||
+ | M, | ||
+ | M)RPP1 T@:& | ||
+ | M+T1%0Z!9+5)/ | ||
+ | M.3(-(' | ||
+ | M, | ||
+ | M9# | ||
+ | M(& | ||
+ | M241$14Z@4U521D%# | ||
+ | M, | ||
+ | M("> | ||
+ | MH%!215-3H$%.6: | ||
+ | M(" | ||
+ | M: | ||
+ | M*BHJ*J!S152@55" | ||
+ | MH%-%5*!54*!)3J!B87-I8PTJH$%.1*!" | ||
+ | M;& | ||
+ | M; | ||
+ | M04Y$H%-%5*!54* B0DE434%0(@US971U<" | ||
+ | M82 D9# R,2 [=$A)4Z!)4Z!$3TY%H%-/ | ||
+ | M(# | ||
+ | M.T-/ | ||
+ | M.W1(1: | ||
+ | M,2 [8T], | ||
+ | M,2 [< | ||
+ | M;& | ||
+ | M; | ||
+ | M;&, | ||
+ | MH$)!4T6@4$])3E1%4@T@< | ||
+ | MH%)/ | ||
+ | M2453# | ||
+ | M4T^@04Z@24Y$15B@24Y43Z!42$6@0TA!4D%# | ||
+ | M-@T@8FYE(# | ||
+ | M14%2H$)51D9%4E, | ||
+ | M8G5F9C$-(' | ||
+ | M24Y' | ||
+ | M0D%# | ||
+ | M: | ||
+ | M1D9%4E, | ||
+ | M(' | ||
+ | M1: | ||
+ | M5$%25*!(15)%H%-/ | ||
+ | M5 T@; | ||
+ | M5$E!3*!604Q515, | ||
+ | M; | ||
+ | M; | ||
+ | M< | ||
+ | M82!S> | ||
+ | M=& | ||
+ | M04E.H$Q/3U -# | ||
+ | M(& | ||
+ | M8VUP(" | ||
+ | M; | ||
+ | M; | ||
+ | M.F-O; | ||
+ | M.F8T# | ||
+ | M9' | ||
+ | M, | ||
+ | M;7 @.F-O; | ||
+ | M(V%N9VUA>" | ||
+ | M;7 @.F-O; | ||
+ | M.F-O; | ||
+ | M.G!L=7, | ||
+ | M*R< | ||
+ | M4J!# | ||
+ | M)RTG# | ||
+ | M; | ||
+ | M(# | ||
+ | M; | ||
+ | M,# | ||
+ | M(&)N92 Z8V]N= T@: | ||
+ | M24Y' | ||
+ | M# | ||
+ | M24U53: | ||
+ | M4D53150-.F-O; | ||
+ | M<" C86YG; | ||
+ | M# | ||
+ | M9VUA> T@8F-C(# | ||
+ | M*BHJ*J!R3U1!5$6@0T]/ | ||
+ | M04Q# | ||
+ | M3$E& | ||
+ | M2$52# | ||
+ | MH%-53: ^H# | ||
+ | M54)44D%#5* R*E!)# | ||
+ | M3Z!!3D=, | ||
+ | M(V%N9VUA>" | ||
+ | M*BJ@; | ||
+ | M(' | ||
+ | M/5-9*U-:#2 ^/ | ||
+ | M(' | ||
+ | M# | ||
+ | M-CU36" | ||
+ | M/ | ||
+ | M> T@< | ||
+ | M.U0Q,# | ||
+ | M82QB+&, | ||
+ | M: | ||
+ | M05-354U%1*!42$%4H%1(1: | ||
+ | M0T-5355, | ||
+ | M5$E61: | ||
+ | M3$5-14Y4# | ||
+ | M9&,@(S Q(# | ||
+ | M; | ||
+ | M(&UA8R @.VU53%1)4$Q9H$& | ||
+ | M; | ||
+ | M9@T@861C(", | ||
+ | M# | ||
+ | M4J!, | ||
+ | M55(N# | ||
+ | M9&, | ||
+ | M8V(@;& | ||
+ | M(' | ||
+ | M# | ||
+ | M;& | ||
+ | M# | ||
+ | M< | ||
+ | M/ | ||
+ | M# | ||
+ | M, | ||
+ | M< | ||
+ | M8F, | ||
+ | M*2\R#2 ^/ | ||
+ | M9' | ||
+ | M*V5)*2\R# | ||
+ | M# | ||
+ | M.F-A;& | ||
+ | M; | ||
+ | M(' | ||
+ | M, | ||
+ | M(' | ||
+ | M22DO, | ||
+ | M9&, | ||
+ | M# | ||
+ | M5#@I*2\R#2 ^/ | ||
+ | M# | ||
+ | M5# | ||
+ | M,3 -(& | ||
+ | M, | ||
+ | M*BHJ*J!C3$5!4J!" | ||
+ | M8R @.W!55*!" | ||
+ | M,# -(' | ||
+ | M=& | ||
+ | M# | ||
+ | M(& | ||
+ | M# | ||
+ | M8: | ||
+ | MH$-/ | ||
+ | MH$)%3$]7# | ||
+ | M25.@5TE, | ||
+ | M.F5V96Z@861CH& | ||
+ | M; | ||
+ | M; | ||
+ | M> | ||
+ | M: | ||
+ | M8G5F9F5R# | ||
+ | M9& | ||
+ | M5$A%4T6@1U594PTJH' | ||
+ | M8: C)& | ||
+ | M6%0LH%)%042@04Y$H$1205> | ||
+ | M(' | ||
+ | M.V9)4E-4+*!42$6@3E5-0D52H$]& | ||
+ | M24: | ||
+ | M1: | ||
+ | M: | ||
+ | M1T].# | ||
+ | M4B$-# | ||
+ | M6*!43Z!# | ||
+ | MH$U/1* X# | ||
+ | M< | ||
+ | M8F-S(# | ||
+ | M< | ||
+ | M; | ||
+ | M;& | ||
+ | M1Z!42$6@96]R+4)51D9%4BR@0T]06: | ||
+ | M84Y$H%1(14Z@0TQ%05*@5$A%H& | ||
+ | M;& | ||
+ | M=& | ||
+ | M>& | ||
+ | MH$-/ | ||
+ | M2%2@0D6@0: | ||
+ | M, | ||
+ | M*S$@.V5!0TB@0T], | ||
+ | M<#$K,2 [; | ||
+ | M0T], | ||
+ | M=$]404R@3E5-0D52H$]& | ||
+ | M3*!# | ||
+ | M; | ||
+ | M=& | ||
+ | M0D5, | ||
+ | M,# @.VU)1TA4H$%3H%=%3$R@0TQ%05*@252@3D]7# | ||
+ | M# | ||
+ | M< | ||
+ | M# | ||
+ | M;V]P#2!J;7 @; | ||
+ | M< | ||
+ | M2# | ||
+ | M2*!" | ||
+ | MH"0S. T-(& | ||
+ | M(' | ||
+ | M=" G5$].24=(5# | ||
+ | M2$6@4$])3E13# | ||
+ | M1T6@4TE.0T4-*J!6, | ||
+ | M4D%, | ||
+ | M1*!)3BR@4D]4051%1*!!3D2@4%)/ | ||
+ | M5$^@5$A%H$1205=)3D> | ||
+ | M; | ||
+ | M3T: | ||
+ | M96]R(", | ||
+ | M+2TM+2TM+2TM+2TM# | ||
+ | M3U53H%!23TI%0U1)3TX-*J!354)23U5424Y%+@T-< | ||
+ | M25!, | ||
+ | M(' | ||
+ | M;W*@(R1F9J [3E5-0D524RR@22Y%+J!8/ | ||
+ | M=&& | ||
+ | MH$1/ | ||
+ | M250-(" | ||
+ | M1*!42$E3H$U53%1)4$Q9H$E3H%-014-)1DE# | ||
+ | M4J!42$6@4%)/ | ||
+ | M4E.@05)%H" | ||
+ | M> | ||
+ | M=" | ||
+ | M551)3D6@5$%+15.@5$A%H%!/ | ||
+ | M.U!23TI%0U13H$E4+*!!3D2@4U1/ | ||
+ | MH%TS+@T-(& | ||
+ | M# | ||
+ | M;75L= T@< | ||
+ | M9' | ||
+ | M# | ||
+ | M861C(' | ||
+ | M8R!P, | ||
+ | M#2 ^/ | ||
+ | M/ | ||
+ | M/B!S;75L= T@8VQC# | ||
+ | M# | ||
+ | M5$%)3E.@4%)/ | ||
+ | M(& | ||
+ | M(' | ||
+ | M-" [; | ||
+ | MH%!23TI%0U1%1 T@8VUP(& | ||
+ | MH$U)3DE-54T-(& | ||
+ | M<" | ||
+ | M;& | ||
+ | M> | ||
+ | M3U1!5$5$H$%.1*!04D]*14-4142@> | ||
+ | M; | ||
+ | M> | ||
+ | M;& | ||
+ | M8: | ||
+ | M9F5R*S$-# | ||
+ | M; | ||
+ | M;& | ||
+ | M=& | ||
+ | M82!P; | ||
+ | M(' | ||
+ | M82!P, | ||
+ | M(& | ||
+ | M<& | ||
+ | M(' S> | ||
+ | M#2 ^/ | ||
+ | M> | ||
+ | M1D%# | ||
+ | M82!P, | ||
+ | M82!T96UP, | ||
+ | M(' | ||
+ | M+5DQ*E@RH# | ||
+ | M9& | ||
+ | M<2 Z86)O< | ||
+ | M: | ||
+ | M# | ||
+ | M< | ||
+ | M(& | ||
+ | M<# | ||
+ | M8V]U; | ||
+ | M/ | ||
+ | M;& | ||
+ | M# | ||
+ | M> T@/ | ||
+ | M(& | ||
+ | M=& | ||
+ | M< | ||
+ | M;& | ||
+ | M(' | ||
+ | M> | ||
+ | MH$1/ | ||
+ | M5D52H%1(1: | ||
+ | M+2TM+2T-*J!G14Y%4D%, | ||
+ | M14154D4-# | ||
+ | M97& | ||
+ | M9: | ||
+ | M2T5$H# | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH& | ||
+ | M04A.H$Q!2$XN# | ||
+ | M(# | ||
+ | MH$]2H$19# | ||
+ | M2TE.1Z!, | ||
+ | M< | ||
+ | M/ | ||
+ | M1: | ||
+ | M# | ||
+ | MH$-(54Y+# | ||
+ | M< | ||
+ | M(# | ||
+ | M2PT@< | ||
+ | M1: | ||
+ | M(& | ||
+ | M:68@:2Q=,2 [9$^@5T6@55-%H& | ||
+ | M> | ||
+ | M8R!D> T@<& | ||
+ | M> | ||
+ | M:68@:2Q=,2 [=5!$051%H' | ||
+ | M8FYE(' | ||
+ | M*BJ@=$%+1: | ||
+ | M4J!/ | ||
+ | M2E535*!!H%!/ | ||
+ | M82!O;& | ||
+ | M: | ||
+ | M> T@9& | ||
+ | M+' | ||
+ | M> T@< | ||
+ | M#2!J;7 @9& | ||
+ | M(& | ||
+ | M<& | ||
+ | M04-23Z!94U1%4 T-*J!T04M%H$%.H%B@4U1%4*!)3J!42$6@96]RH$)51D9% | ||
+ | M4@TJH' | ||
+ | M; | ||
+ | M4D%424].4PT@/ | ||
+ | M>&, | ||
+ | M4U1%4*!)3J!Y# | ||
+ | MH%1(1: | ||
+ | M=& | ||
+ | M8G5F9F5R*2QY(# | ||
+ | M1$%41: | ||
+ | M0U)%05-%H%1(1: | ||
+ | M92!C, | ||
+ | M(& | ||
+ | M(& | ||
+ | M# | ||
+ | M*& | ||
+ | M9' | ||
+ | M: | ||
+ | M4U1%4 T-# | ||
+ | M04Y' | ||
+ | M14%#2 TJH%9%4E1)0T%, | ||
+ | MH%!, | ||
+ | M3T: | ||
+ | M4U2@0: | ||
+ | M8: | ||
+ | M80UY;& | ||
+ | M(& | ||
+ | M8: H8G5F9F5R*2QY# | ||
+ | M861C(& | ||
+ | M04-(H$-(54Y+# | ||
+ | M9F5R*2QY# | ||
+ | M9FEX8PT@9& | ||
+ | M(", | ||
+ | M8S(-(& | ||
+ | M9& | ||
+ | M24Y%H%-%5%50# | ||
+ | M3U> | ||
+ | M9' | ||
+ | M04=%# | ||
+ | M245$# | ||
+ | M(& | ||
+ | M< TZ< | ||
+ | M1*!/ | ||
+ | M(&QD82 C/ | ||
+ | M1: | ||
+ | M> | ||
+ | M=' | ||
+ | M< | ||
+ | MH$E.5$^@>" | ||
+ | M1*!42$6@1DE24U2@0T], | ||
+ | M1: | ||
+ | M, | ||
+ | M4U-)0DQ%H$585%)!H# | ||
+ | MH%-%5*!42$6@2$E' | ||
+ | M8G5F9F5R*S$@.V%$1*!)3J!42$6@3E5-0D52H$]& | ||
+ | M4PT@< | ||
+ | M< | ||
+ | M4E=)4T6@1%D]63$M63(-(& | ||
+ | M>" [=TA/ | ||
+ | M6" | ||
+ | M> | ||
+ | M;VQD> T@< | ||
+ | M1%.@3U*@0D%# | ||
+ | M: | ||
+ | M/ | ||
+ | M/ | ||
+ | M: | ||
+ | M.WF@1$]%4TXG5*!54T6@0TA53DM3# | ||
+ | M04Y4H%1(1: | ||
+ | M8WD@;& | ||
+ | M< | ||
+ | M9&5C#2 ^/ | ||
+ | M# | ||
+ | M#6-L96%N=7 @;& | ||
+ | M86YD(", | ||
+ | M12$-# | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH' | ||
+ | M5$%" | ||
+ | M5*!404), | ||
+ | M(# | ||
+ | M, | ||
+ | M, | ||
+ | M,# P, | ||
+ | M(& | ||
+ | M15-%H%1224> | ||
+ | MH& | ||
+ | M, | ||
+ | M871H, | ||
+ | M< | ||
+ | 8& | ||
+ | |||
+ | end | ||
+ | |||
+ | begin 666 cube3d3.2.o | ||
+ | M ("I (T@T(TAT*T8T" | ||
+ | M(" @(" @0U5" | ||
+ | M4U1%4$A%3B!*541$F2 @(" | ||
+ | M5$A%($I!3BX@.34@25-3544@3T8-EB @0SU(04-+24Y' | ||
+ | M151!24Q3(0T-' | ||
+ | M, | ||
+ | M0R!: | ||
+ | M3TT@24XO3U54# | ||
+ | M' | ||
+ | M(%1/ | ||
+ | M(-+_R-# | ||
+ | MJ4!I# | ||
+ | MJ0" | ||
+ | M (57A5B%685@A3^%085 A4*%885BA6.%9(5EA6: | ||
+ | MT NE8< | ||
+ | MI6+P*,9B3 V# | ||
+ | M#8/)*] ' | ||
+ | MT FE4$D!A5!,# | ||
+ | M& | ||
+ | M NEXA6DXI63E9K ": | ||
+ | MR7B0 NEXA6TXI6CE9+ ": | ||
+ | M9[T D*9H? | ||
+ | M" | ||
+ | M:7V CSBF:OV CX6H.*9KO8"/ | ||
+ | M_VD!3# | ||
+ | M.*9M_8"/ | ||
+ | M;+T D*9M? | ||
+ | MIFI]@(^%K!BF; | ||
+ | M (11I%&Y );0 TR*A852YE$@Q86E5TI*2H57Q3^P H4_I5G%0; " | ||
+ | M2H58Q4" | ||
+ | MHX3[& | ||
+ | MB, | ||
+ | M(& | ||
+ | MJ? | ||
+ | MR, | ||
+ | MA2882?]I 84HL28X\2B%LZ6KA2882? | ||
+ | MA2BQ)CCQ*!AELH6RI: | ||
+ | M)CCQ*!AEM(6TI)2EIX4F& | ||
+ | M./ | ||
+ | M(CCQ)*9QX$# | ||
+ | MA5BELX4B& | ||
+ | ML *%6<5@D *%8*25I: | ||
+ | M*(6SI: | ||
+ | MJ84F& | ||
+ | MA2882?]I 84HL28X\2@89; | ||
+ | M2?]I 84HL28X\2@89; | ||
+ | M< | ||
+ | ML2(X\23@0/ 0I'& | ||
+ | MI84F& | ||
+ | M*+$F./ | ||
+ | M\2@89; | ||
+ | M*!AELH6RI: | ||
+ | MM(6QJKR D*6RA2(82? | ||
+ | M\2BD^QAI0(6OQ5> | ||
+ | M& | ||
+ | M)AA)_VD!A2BQ)CCQ*(7[I: | ||
+ | M4O *YE' | ||
+ | MI;" | ||
+ | M: | ||
+ | MA; | ||
+ | MLZ6LA2882? | ||
+ | MI: | ||
+ | MD*6RA2(82? | ||
+ | M0(65Q5> | ||
+ | M*+$F./ | ||
+ | MKZ66A;#& | ||
+ | M($1/ | ||
+ | M5T]23$0A; | ||
+ | MA? | ||
+ | M 85HQ6>0 TR-C:3\Q/Z] (^%_87^D -, | ||
+ | M]: | ||
+ | M38QE9TBE_47^4: | ||
+ | MD: | ||
+ | M1? | ||
+ | M2*7]4: | ||
+ | MHY& | ||
+ | MJ? | ||
+ | M: | ||
+ | M2*7]4: | ||
+ | M2CA(I? | ||
+ | MA:/0 N: | ||
+ | M_3CP!LK0Y4POCDBI@(7]1: | ||
+ | M:(CE9Y *RM# | ||
+ | MRTQQCJT8T" | ||
+ | M | ||
+ | M # | ||
+ | M#P<# ? | ||
+ | M? | ||
+ | M P' | ||
+ | M& | ||
+ | M& | ||
+ | !&AH: | ||
+ | |||
+ | end | ||
+ | |||
+ | begin 666 shape3.2 | ||
+ | M 1PA' | ||
+ | M.I< | ||
+ | M(%-405)44R!!5" | ||
+ | M4P":'& | ||
+ | M (\@3E5-4$])3E13+%@Q+%DQ+%HQ+%@R+%DR+%HR+" | ||
+ | M4$], | ||
+ | M02!:15)/(0 ;'8D .@ ^'8P CR!/ | ||
+ | M($]& %X=D0"/ | ||
+ | M3B!& | ||
+ | M4D%-(%=/ | ||
+ | M0E54(%1(25, | ||
+ | M3T8@0T]54E-%+" | ||
+ | M3$].1T52($%.($E34U5% #$>J@ Z %8>M "/ | ||
+ | M64]54B!/ | ||
+ | M3B!,24Y% )@> | ||
+ | MCR!33U)262!!0D]55" | ||
+ | M15(N+BX@1D5!5%5215, | ||
+ | M+@ $'^8 .@ G' | ||
+ | M^@"/ | ||
+ | M3U9%($%, | ||
+ | MYP-!,; | ||
+ | M+# | ||
+ | M, | ||
+ | M, | ||
+ | M,; | ||
+ | M, | ||
+ | M+38L+3, | ||
+ | M, | ||
+ | M+3, | ||
+ | M+" | ||
+ | M," | ||
+ | M,S L+3(L+3, | ||
+ | M+3, | ||
+ | M." | ||
+ | M+# | ||
+ | M, | ||
+ | M=2*Z!(, | ||
+ | M,S L+3$R+" | ||
+ | M+3, | ||
+ | M,@ !(^($@R S+# | ||
+ | M,RPS,RPM,S L-BPU-RPM,S L+3, | ||
+ | M5$A)4D0@4TA!4$4 5B, | ||
+ | M1$%402!' | ||
+ | MC!./ | ||
+ | MES8V+$(R .$CDA.' | ||
+ | M 8DG!.!2; | ||
+ | MJC$ (B2K$YDB+B([ " | ||
+ | M& | ||
+ | 0& | ||
+ | |||
+ | end | ||
+ | |||
+ | begin 666 tables$8f80-$95ff | ||
+ | M@(\ @, | ||
+ | M& | ||
+ | MX.# | ||
+ | M' | ||
+ | MY./ | ||
+ | M" H+# | ||
+ | M(R, | ||
+ | M*2HJ*BHJ*BLK*RLK*RPL+" | ||
+ | M, | ||
+ | M& | ||
+ | M' | ||
+ | M(" @(" A(2$A(2$A(2$A(B(B(O__ | ||
+ | M / | ||
+ | M____ / | ||
+ | M # | ||
+ | M! 4%!04& | ||
+ | M& | ||
+ | M.SP]/C] 04)# | ||
+ | M' | ||
+ | M!P<' | ||
+ | M $! 0$! 0$! @(" @(" P,# P0$! 0%!04%!@8& | ||
+ | M" | ||
+ | M(2(C(R0E)B8G*" | ||
+ | M24I+34Y/ | ||
+ | M%144%!, | ||
+ | M P,# P(" @(" @$! 0$! 0$! ! 0$! | ||
+ | M 0$! 0(" @(" @,# P,$! 0$!04%!08& | ||
+ | M# | ||
+ | M+" | ||
+ | M*RHI*2@G)B8E)", | ||
+ | M# | ||
+ | M 0$! 0 0$! 0$! 0$" @(" @(# P,# | ||
+ | M! 0$! 4%!04& | ||
+ | M%A< | ||
+ | M.# | ||
+ | M(" ?' | ||
+ | M" @(!P<' | ||
+ | M !H:& | ||
+ | M& | ||
+ | E& | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | ******************************** | ||
+ | *``````````````````````````````* | ||
+ | *`Stephen`Judd`````````````````* | ||
+ | *`George`Taylor````````````````* | ||
+ | *`Started: | ||
+ | *`Finished: | ||
+ | *`v2.0`Completed: | ||
+ | *`v3.0`Completed: | ||
+ | *`v3.1`Completed: | ||
+ | *`v3.2`Completed: | ||
+ | *``````````````````````````````* | ||
+ | *`Well, | ||
+ | *`program`will`rotate`a`cube.``* | ||
+ | *``````````````````````````````* | ||
+ | *`v2.0`+`New`and`Improved!`````* | ||
+ | *`Now`with`faster`routines, | ||
+ | *`hidden`surfaces, | ||
+ | *`faces, | ||
+ | *`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, | ||
+ | *`For`details`on`this`program, | ||
+ | *`read`the`article!````````````* | ||
+ | *``````````````````````````````* | ||
+ | *`Write`to`us!`````````````````* | ||
+ | *``````````````````````````````* | ||
+ | *`Myself`when`young`did````````* | ||
+ | *`eagerly`frequent`````````````* | ||
+ | *`Doctor`and`Saint, | ||
+ | *`great`Argument```````````````* | ||
+ | *``About`it`and`about: | ||
+ | *``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, | ||
+ | *`become`as`sounding`brass, | ||
+ | *`a`tinkling`cymbal.```````````* | ||
+ | *````-`1`Corinthians`13````````* | ||
+ | *``````````````````````````````* | ||
+ | *`P.S.`This`was`written`using``* | ||
+ | *``````Merlin`128.`````````````* | ||
+ | ******************************** | ||
+ | |||
+ | ORG $8000 | ||
+ | |||
+ | *`Constants | ||
+ | |||
+ | BUFF1 EQU $3000 ; | ||
+ | BUFF2 EQU $3800 ; | ||
+ | EORBUF EQU $4000 ;EOR-buffer | ||
+ | BUFFER EQU $A3 ; | ||
+ | X1 EQU $FB ; | ||
+ | Y1 EQU $FC ; | ||
+ | X2 EQU $FD ; | ||
+ | Y2 EQU $FE | ||
+ | OLDX EQU $FD | ||
+ | CHUNK EQU $FE | ||
+ | DX EQU $67 ; | ||
+ | DY EQU $68 | ||
+ | TEMP1 EQU $FB ; | ||
+ | TEMP2 EQU $FC ; | ||
+ | ZTEMP EQU $02 ; | ||
+ | Z1 EQU $22 ; | ||
+ | Z2 EQU $24 ; | ||
+ | Z3 EQU $26 | ||
+ | Z4 EQU $28 | ||
+ | K EQU $B6 ; | ||
+ | ; | ||
+ | HIDE EQU $B5 ; | ||
+ | FILL EQU $50 ; | ||
+ | ANGMAX EQU 120 ; | ||
+ | |||
+ | *`VIC | ||
+ | |||
+ | VMCSB EQU $D018 | ||
+ | BKGND EQU $D020 | ||
+ | BORDER EQU $D021 | ||
+ | SSTART EQU 1344 ; | ||
+ | |||
+ | |||
+ | *`Kernal | ||
+ | |||
+ | CHROUT EQU $FFD2 | ||
+ | GETIN EQU $FFE4 | ||
+ | |||
+ | *`Some`variables | ||
+ | |||
+ | GLOBXMIN = $3F ; | ||
+ | GLOBXMAX = $40 ; | ||
+ | GLOBYMIN = $41 | ||
+ | GLOBYMAX = $42 | ||
+ | LOCXMIN = $57 ; | ||
+ | LOCXMAX = $58 ; | ||
+ | LOCYMIN = $59 | ||
+ | LOCYMAX = $60 | ||
+ | P1X = $92 ; | ||
+ | P1Y = $93 ; | ||
+ | P1Z = $94 | ||
+ | P2X = $95 ; | ||
+ | P2Y = $96 ; | ||
+ | P2Z = $AE | ||
+ | P3X = $AF ; | ||
+ | P3Y = $B0 | ||
+ | P3Z = $B1 ; | ||
+ | P1T = $B2 ; | ||
+ | P2T = $B3 | ||
+ | P3T = $B4 ; | ||
+ | INDEX = $51 | ||
+ | COUNTPTS = $52 | ||
+ | ZOOM = $71 ; | ||
+ | DSX = $61 ; | ||
+ | ; | ||
+ | DSY = $62 ; | ||
+ | DSZ = $63 | ||
+ | SX = $64 ; | ||
+ | SY = $65 | ||
+ | SZ = $66 | ||
+ | T1 = $67 ; | ||
+ | T2 = $68 | ||
+ | T3 = $69 ; | ||
+ | T4 = $6A | ||
+ | T5 = $6B | ||
+ | T6 = $6C | ||
+ | T7 = $6D | ||
+ | T8 = $6E | ||
+ | T9 = $6F | ||
+ | T10 = $70 | ||
+ | A11 = $A5 ; | ||
+ | B12 = $A6 ;XYZ | ||
+ | C13 = $A7 | ||
+ | D21 = $A8 ; | ||
+ | E22 = $A9 | ||
+ | F23 = $AA | ||
+ | G31 = $AB | ||
+ | H32 = $AC | ||
+ | I33 = $AD | ||
+ | |||
+ | |||
+ | ***`Macros | ||
+ | |||
+ | MOVE MAC | ||
+ | LDA ]1 | ||
+ | STA ]2 | ||
+ | <<< | ||
+ | |||
+ | GETKEY MAC ; | ||
+ | WAIT JSR GETIN | ||
+ | CMP #00 | ||
+ | BEQ WAIT | ||
+ | <<< | ||
+ | |||
+ | DEBUG MAC ; | ||
+ | | ||
+ | |||
+ | | ||
+ | | ||
+ | CLI | ||
+ | >>> | ||
+ | CMP #' | ||
+ | BNE L1 | ||
+ | JSR CLEANUP | ||
+ | JMP DONE | ||
+ | L1 CMP #' | ||
+ | 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 ; | ||
+ | ORA #%00010000 | ||
+ | STA VMCSB | ||
+ | |||
+ | LDY #00 | ||
+ | LDA #<TTEXT | ||
+ | STA TEMP1 | ||
+ | LDA #>TTEXT | ||
+ | STA TEMP2 | ||
+ | JMP TITLE | ||
+ | TTEXT HEX 9305111111 ; | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 9F ;cyan | ||
+ | TXT ' | ||
+ | HEX 99 | ||
+ | TXT ' | ||
+ | HEX 9B | ||
+ | TXT ' | ||
+ | HEX 96 | ||
+ | TXT ' | ||
+ | HEX 9B | ||
+ | TXT ' | ||
+ | HEX 0D1D1D9E12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 1D1D12 | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | TXT ' | ||
+ | HEX 0D05 | ||
+ | TXT ' | ||
+ | HEX 00 | ||
+ | TITLE LDA (TEMP1),Y | ||
+ | BEQ :CONT | ||
+ | JSR CHROUT | ||
+ | INY | ||
+ | BNE TITLE | ||
+ | INC TEMP2 | ||
+ | JMP TITLE | ||
+ | :CONT >>> | ||
+ | |||
+ | ****`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`" | ||
+ | SETUP LDA #$01 ;White | ||
+ | STA $D021 ; | ||
+ | LDA #147 ; | ||
+ | JSR CHROUT | ||
+ | LDA #$00 ;correctly | ||
+ | STA $D021 | ||
+ | LDA #<SSTART | ||
+ | ADC #12 ; | ||
+ | STA TEMP1 ;Column`12 | ||
+ | LDA #>SSTART ;Row`9 | ||
+ | STA TEMP1+1 ; | ||
+ | LDA #00 | ||
+ | LDY #00 | ||
+ | LDX #00 ; | ||
+ | CLC | ||
+ | |||
+ | :LOOP STA (TEMP1),Y | ||
+ | INY | ||
+ | ADC #16 | ||
+ | BCC :LOOP | ||
+ | CLC | ||
+ | LDA TEMP1 | ||
+ | ADC #40 ; | ||
+ | STA TEMP1 ; | ||
+ | LDA TEMP1+1 | ||
+ | ADC #00 ; | ||
+ | STA TEMP1+1 | ||
+ | LDY #00 | ||
+ | INX | ||
+ | | ||
+ | CPX #16 | ||
+ | BNE :LOOP ; | ||
+ | |||
+ | ****`Clear`buffers | ||
+ | |||
+ | LDA #<BUFF1 | ||
+ | STA BUFFER | ||
+ | LDA #>BUFF1 | ||
+ | STA BUFFER+1 | ||
+ | LDY #$00 | ||
+ | LDX #24 ; | ||
+ | LDA #$00 ; | ||
+ | :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 ; | ||
+ | LDA VMCSB | ||
+ | AND #%11110001 ; | ||
+ | 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 ; | ||
+ | BEQ :CONT1 | ||
+ | INC DSX ; | ||
+ | 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 ; | ||
+ | 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 ; | ||
+ | INC ZOOM | ||
+ | JMP :CONT | ||
+ | :MINUS CMP #' | ||
+ | BNE :H | ||
+ | DEC ZOOM | ||
+ | DEC ZOOM | ||
+ | BPL :CONT | ||
+ | INC ZOOM | ||
+ | INC ZOOM | ||
+ | JMP :CONT | ||
+ | :H CMP #' | ||
+ | BNE :SPACE | ||
+ | LDA HIDE | ||
+ | EOR #$01 | ||
+ | STA HIDE | ||
+ | JMP :CONT | ||
+ | :SPACE CMP #' | ||
+ | BNE :Q | ||
+ | LDA FILL | ||
+ | EOR #$01 | ||
+ | STA FILL | ||
+ | JMP :CONT | ||
+ | :Q CMP #' | ||
+ | BNE :CONT | ||
+ | JMP CLEANUP | ||
+ | |||
+ | :CONT SEI ; | ||
+ | |||
+ | ****`Update`angles | ||
+ | |||
+ | UPDATE CLC | ||
+ | LDA SX | ||
+ | ADC DSX | ||
+ | CMP #ANGMAX ; | ||
+ | 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, | ||
+ | |||
+ | **`Two`macros`to`simplify`our`life | ||
+ | ADDA MAC ; | ||
+ | CLC | ||
+ | LDA ]1 | ||
+ | ADC ]2 | ||
+ | CMP #ANGMAX ; | ||
+ | BCC DONE | ||
+ | SBC #ANGMAX ; | ||
+ | DONE <<< | ||
+ | |||
+ | SUBA MAC ; | ||
+ | SEC | ||
+ | LDA ]1 | ||
+ | SBC ]2 | ||
+ | BCS DONE | ||
+ | ADC #ANGMAX ; | ||
+ | DONE <<< | ||
+ | |||
+ | **`Now`calculate`t1, | ||
+ | |||
+ | >>> | ||
+ | STA T1 ;t1=sy-sz | ||
+ | >>> | ||
+ | STA T2 ;t2=sy+sz | ||
+ | >>> | ||
+ | STA T3 ;t3=sx+sz | ||
+ | >>> | ||
+ | STA T4 ;t4=sx-sz | ||
+ | >>> | ||
+ | STA T5 ;t5=sx+t2 | ||
+ | >>> | ||
+ | STA T6 ;t6=sx-t1 | ||
+ | >>> | ||
+ | STA T7 ;t7=sx+t1 | ||
+ | >>> | ||
+ | STA T8 ;t8=t2-sx | ||
+ | >>> | ||
+ | STA T9 ;t9=sy-sx | ||
+ | >>> | ||
+ | STA T10 ;t10=sx+sy | ||
+ | |||
+ | *`Et`voila! | ||
+ | |||
+ | ***`Next, | ||
+ | |||
+ | **`Another`useful`little`macro | ||
+ | DIV2 MAC ; | ||
+ | ; | ||
+ | BPL POS ; | ||
+ | CLC | ||
+ | EOR #$FF ; | ||
+ | ADC #01 ; | ||
+ | | ||
+ | CLC | ||
+ | EOR #$FF | ||
+ | ADC #01 ; | ||
+ | JMP DONEDIV | ||
+ | POS LSR ; | ||
+ | DONEDIV <<< | ||
+ | |||
+ | MUL2 MAC ; | ||
+ | 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 ; | ||
+ | :CALCB LDX T1 | ||
+ | LDA SIN,X | ||
+ | SEC | ||
+ | LDX T2 | ||
+ | SBC SIN,X | ||
+ | STA B12 ; | ||
+ | :CALCC LDX SY | ||
+ | LDA SIN,X | ||
+ | >>> | ||
+ | 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 ; | ||
+ | >>> | ||
+ | CLC | ||
+ | LDX T3 | ||
+ | ADC SIN,X | ||
+ | SEC | ||
+ | LDX T4 | ||
+ | SBC SIN,X | ||
+ | STA D21 ; | ||
+ | :CALCE SEC | ||
+ | LDX T5 | ||
+ | LDA SIN,X | ||
+ | LDX T6 | ||
+ | SBC SIN,X | ||
+ | SEC | ||
+ | LDX T7 | ||
+ | SBC SIN,X | ||
+ | SEC | ||
+ | LDX T8 | ||
+ | SBC SIN,X ; | ||
+ | >>> | ||
+ | CLC | ||
+ | LDX T3 | ||
+ | ADC COS,X | ||
+ | CLC | ||
+ | LDX T4 | ||
+ | ADC COS,X | ||
+ | STA E22 ; | ||
+ | :CALCF LDX T9 | ||
+ | LDA SIN,X | ||
+ | SEC | ||
+ | LDX T10 | ||
+ | SBC SIN,X | ||
+ | STA F23 ; | ||
+ | :CALCG LDX T6 | ||
+ | LDA SIN,X | ||
+ | SEC | ||
+ | LDX T8 | ||
+ | SBC SIN,X | ||
+ | SEC | ||
+ | LDX T7 | ||
+ | SBC SIN,X | ||
+ | SEC | ||
+ | LDX T5 | ||
+ | SBC SIN,X ; | ||
+ | >>> | ||
+ | |||
+ | CLC | ||
+ | LDX T4 | ||
+ | ADC COS,X | ||
+ | SEC | ||
+ | LDX T3 | ||
+ | SBC COS,X | ||
+ | STA G31 ; | ||
+ | :CALCH CLC | ||
+ | LDX T6 | ||
+ | LDA COS,X | ||
+ | LDX T7 | ||
+ | ADC COS,X | ||
+ | SEC | ||
+ | LDX T5 | ||
+ | SBC COS,X | ||
+ | SEC | ||
+ | LDX T8 | ||
+ | SBC COS,X ; | ||
+ | >>> | ||
+ | CLC | ||
+ | LDX T3 | ||
+ | ADC SIN,X | ||
+ | CLC | ||
+ | LDX T4 | ||
+ | ADC SIN,X | ||
+ | STA H32 ; | ||
+ | :WHEW CLC | ||
+ | LDX T9 | ||
+ | LDA COS,X | ||
+ | LDX T10 | ||
+ | ADC COS,X | ||
+ | STA I33 ; | ||
+ | |||
+ | **`It' | ||
+ | |||
+ | DOWNHILL | ||
+ | ****`Clear`buffer | ||
+ | *`A`little`macro | ||
+ | |||
+ | SETBUF MAC ; | ||
+ | LDA #00 | ||
+ | STA BUFFER | ||
+ | LDA ZTEMP ;High`byte | ||
+ | STABUF STA BUFFER+1 | ||
+ | <<< | ||
+ | |||
+ | >>> | ||
+ | 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' | ||
+ | *CLRDRAW`LDA`GLOBXMIN | ||
+ | *`LSR``; | ||
+ | *`BCC`: | ||
+ | *`LDY`#$80 | ||
+ | *`STY`BUFFER`; | ||
+ | *`CLC``; | ||
+ | *: | ||
+ | *`STA`BUFFER+1 | ||
+ | *`LDA`GLOBXMAX | ||
+ | *`SEC | ||
+ | *`SBC`GLOBXMIN | ||
+ | *`TAX | ||
+ | *`INX | ||
+ | *`LDY`GLOBYMAX | ||
+ | *`BEQ`: | ||
+ | *: | ||
+ | *`LDY`GLOBYMAX | ||
+ | *: | ||
+ | *`DEY | ||
+ | *`CPY`GLOBYMIN | ||
+ | *`BCS`:BLAH | ||
+ | *`LDA`BUFFER | ||
+ | *`EOR`#$80 | ||
+ | *`STA`BUFFER | ||
+ | *`BNE`: | ||
+ | *`INC`BUFFER+1 | ||
+ | *: | ||
+ | *`BNE`:YAY | ||
+ | *: | ||
+ | *`STA`GLOBXMAX | ||
+ | *`STA`GLOBYMAX | ||
+ | *`LDA`#$FF | ||
+ | *`STA`GLOBXMIN | ||
+ | *`STA`GLOBYMIN | ||
+ | |||
+ | ****`Next, | ||
+ | |||
+ | READDRAW LDY #00 | ||
+ | STY INDEX | ||
+ | OBJLOOP LDY INDEX | ||
+ | LDA POLYLIST,Y ; | ||
+ | BNE :CONT ; | ||
+ | JMP OBJDONE ; | ||
+ | :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 | ||
+ | | ||
+ | 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, | ||
+ | *`And`then`clear`the`EOR-buffer | ||
+ | |||
+ | EORFILL LDA FILL | ||
+ | BEQ OBJLOOP | ||
+ | |||
+ | >>> | ||
+ | LDA #<EORBUF | ||
+ | STA TEMP1 | ||
+ | LDA #>EORBUF | ||
+ | STA TEMP1+1 | ||
+ | |||
+ | LDA LOCXMIN ; | ||
+ | | ||
+ | BCC :EVEN ; | ||
+ | LDY #$80 | ||
+ | STY BUFFER | ||
+ | STY TEMP1 | ||
+ | CLC | ||
+ | :EVEN STA T2 | ||
+ | ADC BUFFER+1 | ||
+ | STA BUFFER+1 ; | ||
+ | LDA T2 | ||
+ | ADC TEMP1+1 ; | ||
+ | STA TEMP1+1 ;column | ||
+ | |||
+ | LDA LOCXMAX | ||
+ | SEC | ||
+ | SBC LOCXMIN | ||
+ | TAX ; | ||
+ | | ||
+ | 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 ; | ||
+ | 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 ; | ||
+ | STA VMCSB | ||
+ | LDA #$08 | ||
+ | EOR ZTEMP ; | ||
+ | STA ZTEMP ; | ||
+ | |||
+ | JMP MAIN ; | ||
+ | |||
+ | TXT ' | ||
+ | TXT ' | ||
+ | |||
+ | **`Rotate, | ||
+ | * | ||
+ | *`This`part`is`a`significant`change`since | ||
+ | *`v2.0.``Now`it`is`a`completely`general`polygon`plotter. | ||
+ | *`A`set`of`points`is`read`in, | ||
+ | *`plotted`into`the`drawing`buffer`(EOR`or`normal). | ||
+ | |||
+ | ROTPROJ | ||
+ | |||
+ | *`A`neat`macro | ||
+ | NEG MAC ; | ||
+ | CLC | ||
+ | LDA ]1 ;number. | ||
+ | EOR #$FF | ||
+ | ADC #$01 | ||
+ | <<< | ||
+ | |||
+ | *------------------------------- | ||
+ | *`These`macros`replace`the`previous`projection | ||
+ | *`subroutine. | ||
+ | |||
+ | SMULT`MAC`; | ||
+ | ; | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | SEC | ||
+ | | ||
+ | <<< | ||
+ | |||
+ | SMULTZ MAC ; | ||
+ | ; | ||
+ | STA Z1 | ||
+ | | ||
+ | EOR #$FF ; | ||
+ | ADC #$01 ; | ||
+ | STA Z2 | ||
+ | LDA (Z1),Y | ||
+ | SEC | ||
+ | SBC (Z2),Y | ||
+ | <<< | ||
+ | |||
+ | PROJECT MAC ; | ||
+ | |||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | |||
+ | LDY ]1 ; | ||
+ | LDA A11 | ||
+ | >>> | ||
+ | STA P1T | ||
+ | LDA D21 | ||
+ | >>> | ||
+ | STA P2T | ||
+ | LDA G31 | ||
+ | >>> | ||
+ | STA P3T | ||
+ | LDY ]2 ; | ||
+ | LDA B12 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P1T | ||
+ | STA P1T | ||
+ | LDA E22 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P2T | ||
+ | STA P2T | ||
+ | LDA H32 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P3T | ||
+ | STA P3T | ||
+ | LDY ]3 ; | ||
+ | LDA C13 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P1T | ||
+ | STA P1T | ||
+ | LDA F23 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P2T | ||
+ | STA P2T | ||
+ | LDA I33 | ||
+ | >>> | ||
+ | CLC | ||
+ | ADC P3T | ||
+ | STA ]3 ;Rotated`Z | ||
+ | TAX | ||
+ | LDY ZDIV,X ; | ||
+ | ; | ||
+ | |||
+ | LDA P1T | ||
+ | >>> | ||
+ | LDX ZOOM | ||
+ | CPX #64 | ||
+ | BEQ CONTX | ||
+ | STY TEMP1 | ||
+ | LDY ZOOM | ||
+ | >>> | ||
+ | LDY TEMP1 | ||
+ | CONTX CLC | ||
+ | ADC #64 ; | ||
+ | STA ]1 ; | ||
+ | CMP LOCXMIN ; | ||
+ | BCS NOTXMIN | ||
+ | STA LOCXMIN | ||
+ | NOTXMIN CMP LOCXMAX | ||
+ | BCC NOTXMAX | ||
+ | STA LOCXMAX | ||
+ | |||
+ | NOTXMAX LDA P2T | ||
+ | >>> | ||
+ | CPX #64 | ||
+ | BEQ CONTY | ||
+ | LDY ZOOM | ||
+ | >>> | ||
+ | CONTY CLC | ||
+ | ADC #64 | ||
+ | STA ]2 ; | ||
+ | CMP LOCYMIN | ||
+ | BCS NOTYMIN | ||
+ | STA LOCYMIN | ||
+ | NOTYMIN CMP LOCYMAX | ||
+ | BCC NOTYMAX | ||
+ | STA LOCYMAX | ||
+ | |||
+ | NOTYMAX <<< | ||
+ | |||
+ | *`LDA`#< | ||
+ | *`STA`BUFFER`; | ||
+ | *`LDA`#> | ||
+ | *`STA`BUFFER+1 | ||
+ | |||
+ | LDA #0 ; | ||
+ | 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 | ||
+ | >>> | ||
+ | >>> | ||
+ | >>> | ||
+ | |||
+ | LDA HIDE | ||
+ | BEQ :DOIT | ||
+ | LDA P2X ; | ||
+ | SEC | ||
+ | SBC P1X | ||
+ | | ||
+ | LDA P3Y | ||
+ | SEC | ||
+ | SBC P2Y ;A=(y3-y2) | ||
+ | >>> | ||
+ | STA TEMP1 | ||
+ | LDA P3X | ||
+ | SEC | ||
+ | SBC P2X | ||
+ | TAY | ||
+ | LDA P2Y | ||
+ | SEC | ||
+ | SBC P1Y | ||
+ | >>> | ||
+ | CMP TEMP1 ; | ||
+ | BMI :DOIT ;is`visible | ||
+ | DEC COUNTPTS ; | ||
+ | BEQ :ABORT ; | ||
+ | :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 ; | ||
+ | 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 | ||
+ | >>> | ||
+ | |||
+ | 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 ; | ||
+ | STA X2 | ||
+ | LDA P1Y | ||
+ | STA Y2 | ||
+ | LDA P3X | ||
+ | STA X1 | ||
+ | LDA P3Y | ||
+ | STA Y1 | ||
+ | JSR DRAW | ||
+ | RTS | ||
+ | |||
+ | TXT ' | ||
+ | TXT ' | ||
+ | |||
+ | |||
+ | *------------------------------- | ||
+ | *`General`questionable-value`error`procedure | ||
+ | |||
+ | *CHOKE`LDX`# | ||
+ | *: | ||
+ | *`BEQ`:DONE | ||
+ | *`JSR`CHROUT | ||
+ | *`INX | ||
+ | *`JMP`:LOOP | ||
+ | *:DONE`RTS | ||
+ | *: | ||
+ | *`TXT`' | ||
+ | *`HEX`0D00 | ||
+ | * | ||
+ | TXT ' | ||
+ | |||
+ | *------------------------------- | ||
+ | *`Drawin' | ||
+ | |||
+ | ***`Some`useful`macros | ||
+ | |||
+ | CINIT MAC ; | ||
+ | LDA ]1 ;dx`or`dy | ||
+ | LSR | ||
+ | <<< | ||
+ | |||
+ | *****`Macro`to`take`a`step`in`X | ||
+ | |||
+ | XSTEP MAC | ||
+ | LDX DX ; | ||
+ | >>> | ||
+ | XLOOP LSR CHUNK | ||
+ | BEQ FIXC ; | ||
+ | SBC DY | ||
+ | BCC FIXY ; | ||
+ | DEX | ||
+ | BNE XLOOP | ||
+ | DONE LDA OLDX ; | ||
+ | EOR CHUNK | ||
+ | ORA (BUFFER),Y | ||
+ | STA (BUFFER),Y | ||
+ | RTS | ||
+ | |||
+ | FIXC PHA | ||
+ | LDA OLDX | ||
+ | ORA (BUFFER),Y ;Plot | ||
+ | STA (BUFFER),Y | ||
+ | LDA #$FF ; | ||
+ | STA OLDX | ||
+ | STA CHUNK | ||
+ | LDA #$80 ; | ||
+ | EOR BUFFER | ||
+ | STA BUFFER | ||
+ | BNE C2 | ||
+ | INC BUFFER+1 | ||
+ | C2 | ||
+ | PLA | ||
+ | SBC DY | ||
+ | BCS CONT | ||
+ | ADC DX | ||
+ | IF I,]1 ; | ||
+ | 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 | ||
+ | <<< | ||
+ | |||
+ | *****`Take`a`step`in`Y | ||
+ | |||
+ | YSTEP MAC | ||
+ | LDX DY ; | ||
+ | BEQ DONE ; | ||
+ | >>> | ||
+ | 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 | ||
+ | | ||
+ | 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 | ||
+ | <<< | ||
+ | |||
+ | *`Take`an`x`step`in`the`EOR`buffer | ||
+ | *`The`sole`change`is`to`use`EOR`instead`of`ORA | ||
+ | |||
+ | EORXSTEP MAC | ||
+ | LDX DX ; | ||
+ | >>> | ||
+ | XLOOP LSR CHUNK | ||
+ | BEQ FIXC ; | ||
+ | SBC DY | ||
+ | BCC FIXY ; | ||
+ | DEX | ||
+ | BNE XLOOP | ||
+ | DONE LDA OLDX ; | ||
+ | EOR CHUNK | ||
+ | EOR (BUFFER),Y | ||
+ | STA (BUFFER),Y | ||
+ | RTS | ||
+ | |||
+ | FIXC PHA | ||
+ | LDA OLDX | ||
+ | EOR (BUFFER),Y ;Plot | ||
+ | STA (BUFFER),Y | ||
+ | LDA #$FF ; | ||
+ | STA OLDX | ||
+ | STA CHUNK | ||
+ | LDA #$80 ; | ||
+ | EOR BUFFER | ||
+ | STA BUFFER | ||
+ | BNE C2 | ||
+ | INC BUFFER+1 | ||
+ | C2 | ||
+ | PLA | ||
+ | SBC DY | ||
+ | BCS CONT | ||
+ | ADC DX | ||
+ | IF I,]1 ; | ||
+ | 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 | ||
+ | <<< | ||
+ | |||
+ | |||
+ | *`Take`a`y-step`in`the`EOR-buffer | ||
+ | *`Changes`from`above`are: | ||
+ | *`vertical`chunk, | ||
+ | |||
+ | EORYSTEP MAC | ||
+ | LDX DY ; | ||
+ | BEQ DONE ; | ||
+ | >>> | ||
+ | SEC | ||
+ | *YLOOP`PHA | ||
+ | *`LDA`OLDX | ||
+ | *`ORA`(BUFFER), | ||
+ | *`STA`(BUFFER), | ||
+ | *`PLA | ||
+ | YLOOP IF I,]1 | ||
+ | INY | ||
+ | ELSE | ||
+ | DEY | ||
+ | FIN | ||
+ | SBC DX | ||
+ | BCC FIXX | ||
+ | DEX | ||
+ | BNE YLOOP | ||
+ | *DONE`LDA`OLDX | ||
+ | *`ORA`(BUFFER), | ||
+ | *`STA`(BUFFER), | ||
+ | DONE RTS | ||
+ | |||
+ | FIXX ADC DY | ||
+ | | ||
+ | LDA OLDX | ||
+ | EOR (BUFFER),Y | ||
+ | STA (BUFFER),Y | ||
+ | PLA | ||
+ | LSR OLDX | ||
+ | | ||
+ | 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 | ||
+ | <<< | ||
+ | ****`Initial`line`setup | ||
+ | |||
+ | **`The`commented`lines`below`are`now`taken`care`of`by`the | ||
+ | **`calling`routine. | ||
+ | *DRAW`>>> | ||
+ | *`>>> | ||
+ | *`>>> | ||
+ | *`>>> | ||
+ | |||
+ | DRAW LDA FILL | ||
+ | BNE :SETEOR | ||
+ | >>> | ||
+ | JMP :SETUP | ||
+ | :SETEOR LDA #<EORBUF ; | ||
+ | STA BUFFER ; | ||
+ | LDA #>EORBUF | ||
+ | STA BUFFER+1 | ||
+ | |||
+ | :SETUP SEC ; | ||
+ | LDA X2 | ||
+ | SBC X1 | ||
+ | BCS :CONT | ||
+ | LDA Y2 ; | ||
+ | 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 ; | ||
+ | |||
+ | COLUMN TXA ; | ||
+ | LSR | ||
+ | | ||
+ | | ||
+ | LSR | ||
+ | BCC :EVEN ; | ||
+ | LDY #$80 ; | ||
+ | STY BUFFER | ||
+ | CLC | ||
+ | :EVEN ADC BUFFER+1 ; | ||
+ | STA BUFFER+1 | ||
+ | |||
+ | SEC | ||
+ | LDA Y2 ; | ||
+ | SBC Y1 | ||
+ | BCS :CONT2 ; | ||
+ | EOR #$FF ; | ||
+ | ADC #$01 | ||
+ | :CONT2 STA DY | ||
+ | CMP DX ; | ||
+ | BCC STEPINX ; | ||
+ | JMP STEPINY | ||
+ | |||
+ | STEPINX LDY Y1 | ||
+ | CPY Y2 | ||
+ | LDA BITP,X ; | ||
+ | STA OLDX | ||
+ | STA CHUNK | ||
+ | BCC XINCY ; | ||
+ | JMP XDECY | ||
+ | |||
+ | XINCY LDA FILL | ||
+ | BEQ NORMXINC | ||
+ | >>> | ||
+ | NORMXINC >>> | ||
+ | |||
+ | XDECY LDA FILL | ||
+ | BEQ NORMXDEC | ||
+ | >>> | ||
+ | NORMXDEC >>> | ||
+ | |||
+ | STEPINY LDY Y1 | ||
+ | LDA BITP,X ;X=x1 | ||
+ | STA OLDX | ||
+ | | ||
+ | EOR OLDX ; | ||
+ | STA OLDX | ||
+ | CPY Y2 | ||
+ | BCS YDECY | ||
+ | |||
+ | YINCY LDA FILL | ||
+ | BEQ NORMINC | ||
+ | >>> | ||
+ | NORMINC >>> | ||
+ | |||
+ | YDECY LDA FILL | ||
+ | BEQ NORMDEC | ||
+ | >>> | ||
+ | NORMDEC >>> | ||
+ | |||
+ | |||
+ | *------------------------------- | ||
+ | *`Clean`up | ||
+ | |||
+ | CLEANUP LDA VMCSB ; | ||
+ | AND #%11110101 ;default | ||
+ | STA VMCSB | ||
+ | |||
+ | | ||
+ | |||
+ | TXT ' | ||
+ | TXT ' | ||
+ | |||
+ | *------------------------------- | ||
+ | *`Set`up`bit`table | ||
+ | |||
+ | DS ^ ; | ||
+ | ; | ||
+ | BITP LUP 16 ; | ||
+ | DFB %11111111 | ||
+ | DFB %01111111 | ||
+ | DFB %00111111 | ||
+ | DFB %00011111 | ||
+ | DFB %00001111 | ||
+ | DFB %00000111 | ||
+ | DFB %00000011 | ||
+ | DFB %00000001 | ||
+ | --^ | ||
+ | |||
+ | SIN ; | ||
+ | COS EQU SIN+128 ; | ||
+ | ; | ||
+ | ; | ||
+ | ZDIV EQU COS+128 ; | ||
+ | TMATH1 EQU ZDIV+384 ; | ||
+ | TMATH2 EQU TMATH1+512 ; | ||
+ | POLYLIST EQU TMATH2+512 ; | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== Second SID Chip Installation ====== | ||
+ | < | ||
+ | Copyright 1988 Mark A. Dickenson | ||
+ | |||
+ | This information and software is COPYRIGHTED and made available on a | ||
+ | SHAREWARE basis. | ||
+ | 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. | ||
+ | the information is worth. | ||
+ | |||
+ | If you have any gripes, complaints, suggestions, | ||
+ | any sort please send them to: | ||
+ | |||
+ | Mark Dickenson | ||
+ | 600 South West Street | ||
+ | | ||
+ | |||
+ | 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. | ||
+ | respect to the use of the following information. | ||
+ | screw-up trying to install this modification, | ||
+ | |||
+ | 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. | ||
+ | do not like to do this modification. | ||
+ | you belong to a Users Group, tell them about the project and ask if there is | ||
+ | anyone there that could perform the operation. | ||
+ | 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). | ||
+ | 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. | ||
+ | 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, | ||
+ | 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' | ||
+ | 128D, so I cannot provide the information with any accuracy. | ||
+ | |||
+ | There are TWO different 64C circuit boards and both use DIFFERENT SID | ||
+ | chips. | ||
+ | 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 | ||
+ | 2 - 220pf capacitors | ||
+ | |||
+ | ----------------------------------- | ||
+ | |||
+ | Parts Commodore 64C Narrow Board | ||
+ | |||
+ | 1 - 6582 SID Chip From Jamco or Kassara Microsystems | ||
+ | 1 - 2222A transistor | ||
+ | 2 - .022uf capacitors | ||
+ | 2 - 1k ohm 1/4 watt resistors | ||
+ | |||
+ | ----------------------------------- | ||
+ | |||
+ | Parts 64, 64C (all) & 128 | ||
+ | |||
+ | 2 - 1k ohm 1/4 watt resistors | ||
+ | 1 - 1000 pf capacitor | ||
+ | the same as 1000pf | ||
+ | 1 - 10k ohm 1/4 watt resistor | ||
+ | 1 - 10 uf electrolitic capacitor | ||
+ | 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). | ||
+ | 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. | ||
+ | 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 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. | ||
+ | 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. | ||
+ | heat sink (silicon grease) between the two chips before soldering them | ||
+ | together. | ||
+ | |||
+ | 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. | ||
+ | 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). | ||
+ | the cartridge port to the circuit board. | ||
+ | the front of the computer. | ||
+ | counting to the right. | ||
+ | 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. | ||
+ | chip but starting at this address. | ||
+ | |||
+ | I am no longer describing how to connect for address $DF00. | ||
+ | address causes problems with the RAM Expansion Units and numerous other | ||
+ | cartridges. | ||
+ | |||
+ | 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 ' | ||
+ | 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. | ||
+ | for the audio output. | ||
+ | |||
+ | |||
+ | Pin 27 on | ||
+ | 9volts 64C (narrow) | ||
+ | SID chip resistor | ||
+ | !--. 10k ohm !collector | ||
+ | 27!----.--/ | ||
+ | --' | ||
+ | | ||
+ | < | ||
+ | > | ||
+ | |||
+ | =========================================================================== | ||
+ | </ | ||
+ | ====== SOLVING LARGE SYSTEMS OF LINEAR EQUATIONS ON A C64 WITHOUT MEMORY ====== | ||
+ | < | ||
+ | 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. | ||
+ | usefull numerical algorithm, " | ||
+ | look at the COMAL programming language and BLAS routines. | ||
+ | |||
+ | Linear systems of equations, A(, | ||
+ | and x and b are vectors (or arrays), must often be solved for x in the | ||
+ | solution of a variety of problems. | ||
+ | is n and just storing A requires 5*n*n bytes of memory, assuming C64/128 | ||
+ | 5 byte real variables. | ||
+ | Gaussian Elimination which requires 1/3 n*n*n multiplications. | ||
+ | ignore the additional n*n and n multiplies. | ||
+ | has two serious limitations, | ||
+ | arithmetic. | ||
+ | n=100 will require 100 times more memory and 1000 times more computing | ||
+ | time. The computing time is not a real problem. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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? | ||
+ | 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. | ||
+ | might be big enough. | ||
+ | flowfield around a body of revolution. | ||
+ | surface of the body (a meridian) and have a series of sort line segments | ||
+ | make up elements to approximate the shape. | ||
+ | the smooth shape is aproximated and the more accurate the computed | ||
+ | solution becomes. | ||
+ | could also use a " | ||
+ | a curved line element for the straight line segment. | ||
+ | matrix elements will be more difficult but n=40 curved elements might | ||
+ | give a more accurate solution than 100 flat elements. | ||
+ | consideration is the resolution of the solution. | ||
+ | the solution on the 200 x 320 pixel hi-res C64 screen. | ||
+ | be too coarse and 320 might be overkill. | ||
+ | calculate the slope or derivatives from the calculated solution which | ||
+ | will require more closely spaced solution points. | ||
+ | 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... | ||
+ | subproblem inside a large application program. | ||
+ | to solve reasonable sized problems using your prefered | ||
+ | environment without having to do a lot of chaining or loading of | ||
+ | separate programs. | ||
+ | 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. | ||
+ | which is a fast three pass interpreter. | ||
+ | fastest and provide the most useable memory. | ||
+ | or Pascal could also be a good choice. | ||
+ | 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). | ||
+ | Point OPerationS) where, c(i#, | ||
+ | operation. | ||
+ | overhead. | ||
+ | can far exceed the actual FP multiply time. With assembled code the FP | ||
+ | multiply time should dominate. | ||
+ | COMAL 2.0. For example: | ||
+ | |||
+ | c(i#, | ||
+ | FOR k#:=1 to n# do c(i#, | ||
+ | |||
+ | both calculate the same thing, a dot product with n# FLOPS. | ||
+ | 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. | ||
+ | MegaFLOPS/ | ||
+ | thousands of MFLOPS/ | ||
+ | counting the multiply and add as two FLOPS. | ||
+ | "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. | ||
+ | also use the CPU in the disk drives to do distributed processing. | ||
+ | this is beyond the scope of this article. | ||
+ | |||
+ | SOLUTION METHODS | ||
+ | |||
+ | Consider the following choices for numerical solution algorithms: | ||
+ | |||
+ | METHOD | ||
+ | Gaussian Elimination | ||
+ | Cholesky Decomposition 1/2 n*n 1/6 n*n*n | ||
+ | QR decomposition | ||
+ | QR updating | ||
+ | Gauss-Jordan | ||
+ | Quartersolve | ||
+ | |||
+ | |||
+ | Gaussian Elimination is the prefered method when enough memory is | ||
+ | available. | ||
+ | decomposed or factored into a lower triangular matrix and an upper | ||
+ | triangular matrix. | ||
+ | complication often required for a stable solution. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | Gaussian Elimination. | ||
+ | |||
+ | There is a variation of the QR solution known as QR updating. | ||
+ | problem is solved a row at a time. A Row of A can be read in from disk | ||
+ | storage or calculated as needed. | ||
+ | memory, n*(n+1)/2 memory locations. | ||
+ | and is updated as each row of A and its right hand side element are | ||
+ | processed. | ||
+ | sequentialy multiplied by Q transpose. | ||
+ | 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, | ||
+ | space. | ||
+ | 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. | ||
+ | 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. | ||
+ | Gaussian Elimination and most codes use n*n memory storage. | ||
+ | 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. | ||
+ | are available from the start. | ||
+ | |||
+ | Quartersolve is a clever implementation of Gauss-Jordan(? | ||
+ | the problem a row at a time like QR updating but only requires 1/4 n*n | ||
+ | memory storage. | ||
+ | problem twice as large as Gaussian Elimination but with a modest | ||
+ | performance penalty. | ||
+ | 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. | ||
+ | problems requiering external storage a blocked version of QR | ||
+ | decomposition might work best. Cholesky decomposition should be used | ||
+ | for symetric positive definite problems. | ||
+ | sparse, containing lots of zeros that need not be stored. | ||
+ | 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. | ||
+ | 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. | ||
+ | mentioning this algorithm. | ||
+ | C64 literature either. | ||
+ | saying that the code was too long or complex. | ||
+ | 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). | ||
+ | describing the algorithm since I have not seen Ref. 2 or analyzed the | ||
+ | algorithm. | ||
+ | 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." | ||
+ | sure from the description that he meant Gauss-Jordan which requires | ||
+ | about 50% more arithmetic than Gaussian Elimination. | ||
+ | the ith row only i(n-i) storage locations are required to store the | ||
+ | reduced matrix. | ||
+ | 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. | ||
+ | and release the memory for any other use as the procedure advances. | ||
+ | |||
+ | Now back to my initial memory free claim. | ||
+ | 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. | ||
+ | only n*n storage is needed, that for B. At the ith step the ith row of | ||
+ | A and B are calculated. | ||
+ | dimensioned array B filling it from the front. | ||
+ | B is stored in the same array B filling from from the end of array B. | ||
+ | As the process continues Quartersolve " | ||
+ | B never overwrite storage needed by Quartersolve. | ||
+ | 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#, | ||
+ | // 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#: | ||
+ | IF i#=1 THEN // | ||
+ | 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' | ||
+ | ENDFOR j# | ||
+ | FOR j#:=i# TO n# DO c(j# | ||
+ | p#: | ||
+ | r: | ||
+ | IF r=0 THEN RETURN | ||
+ | fail#: | ||
+ | IF p#<> | ||
+ | swap' | ||
+ | swap' | ||
+ | sswap(q#, | ||
+ | ENDIF | ||
+ | r:=1/c(i#) | ||
+ | IF mm1#<> | ||
+ | FOR j#:=1 TO nr# DO b(i#, | ||
+ | FOR k#:=1 TO nr# DO saxpy(q#, | ||
+ | IF mm1#>0 THEN | ||
+ | t#:=1 | ||
+ | FOR j#:=1 TO q# DO | ||
+ | r:=a(t#); t#:+1 | ||
+ | scopy(mm1#, | ||
+ | saxpy(mm1#, | ||
+ | t#:+mm1# | ||
+ | ENDFOR j# | ||
+ | scopy(mm1#, | ||
+ | ELSE // | ||
+ | FOR j#:=1 TO nr# DO | ||
+ | FOR k#:=1 TO n# DO c(sw# | ||
+ | scopy(n#, | ||
+ | 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#, | ||
+ | FOR i#:=1 TO n# DO | ||
+ | FOR j#:=1 TO n# DO b(j# | ||
+ | s:=0 | ||
+ | FOR j#:=n# TO 1 STEP -1 DO s:+b(j#) | ||
+ | rhs(i#, | ||
+ | slv(n#, | ||
+ | IF fail# THEN | ||
+ | PRINT " | ||
+ | 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 | ||
+ | " | ||
+ | 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. | ||
+ | personal matter of style. | ||
+ | arrays are passed by REFERENCE and do not allocate additional local | ||
+ | storage. | ||
+ | n times to solve the linear system. | ||
+ | Hilbert matrix which is ill conditioned. | ||
+ | error the solution should be a vector of ones. I usually dimension a() | ||
+ | to n# | ||
+ | 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. | ||
+ | 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 " | ||
+ | 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 " | ||
+ | is most restrictive in function and procedure lines where it limits the | ||
+ | number of parameters that can be passed. | ||
+ | characters. | ||
+ | processor you can enter 120 character lines. | ||
+ | execute tokenized lines up to 256 characters so the limitation is really | ||
+ | in the editor rather than COMAL. | ||
+ | quite long in Comal, but are kept short because of the line length | ||
+ | limitation. | ||
+ | |||
+ | a:+t is a shorter faster version a:=a+t, and a:-t is a shorter faster | ||
+ | version of a: | ||
+ | an integer. | ||
+ | |||
+ | Comal 2.0 supports ML packages. | ||
+ | or procedures that can be called and executed. | ||
+ | and stored in EPROM on the Comal 2.0 cartridge. | ||
+ | loaded from disk and will normally be stored in a RAM location that | ||
+ | COMAL does not use for normal programs. | ||
+ | link the ML package to a Comal program. | ||
+ | program when the program is saved and loaded, unless it is marked as | ||
+ | ROMMED. | ||
+ | and be placed in a package. | ||
+ | strings and blas. The command USE packagename makes all of the | ||
+ | functions and procedures of the package known. | ||
+ | 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' | ||
+ | package. | ||
+ | |||
+ | It does exactly what it says, e.g. swap' | ||
+ | t:=a; a:=b; b:=t. | ||
+ | |||
+ | Slv calls the sdot, isamax#, sswap, sscal, saxpy, and scopy routines | ||
+ | from the blas package. | ||
+ | it could, and should, be placed on EPROM. | ||
+ | |||
+ | Basic Linear Algebra Subroutines, | ||
+ | |||
+ | The BLAS were originally written for the Fortran language to speed | ||
+ | execution and streamline code used for solving linear algebra and other | ||
+ | matrix problems. | ||
+ | perhaps the best known. | ||
+ | highly optimized for a particular computer, coded in ML or a High Order | ||
+ | Language. | ||
+ | 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. | ||
+ | precision, and complex numbers. | ||
+ | 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. | ||
+ | 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 " | ||
+ | allow you pass an array, by reference, of single or multiple dimensions | ||
+ | and start from any position in the array. | ||
+ | as ordinary Comal routines you have to pass additional parameters and | ||
+ | have separate routines for single dimensioned arrays and two dimensional | ||
+ | arrays. | ||
+ | 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 | ||
+ | dimension c(n), a(ilda, | ||
+ | scopy(n, | ||
+ | scopy(n, | ||
+ | |||
+ | 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, | ||
+ | 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. | ||
+ | rather than as a package it would have to be different. | ||
+ | change. | ||
+ | |||
+ | scopy(n#, | ||
+ | scopy(n#, | ||
+ | |||
+ | PROC scopy(n#, REF x(), ix#, incx#, REF y(,), iy#, jy#, sdy#, incy#) CLOSED | ||
+ | iyinc#: | ||
+ | jyinc#: | ||
+ | FOR i#=1 TO n# DO | ||
+ | y(iy#, | ||
+ | ix#:+incx#; iy#: | ||
+ | ENDFOR | ||
+ | ENDPROC scopy | ||
+ | |||
+ | Note that more information has to be passed to the procedure and used | ||
+ | that the ML blas picks up automatically. | ||
+ | procedures to handle every combination of single and multi dimensional | ||
+ | arrays. | ||
+ | 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: | ||
+ | sum:=0 | ||
+ | FOR i#:=1 TO n# DO sum: | ||
+ | |||
+ | saxpy(n#, | ||
+ | FOR i#:=1 TO n# DO y(i# | ||
+ | |||
+ | prod: | ||
+ | prod:=0 | ||
+ | FOR i#:=1 TO n# DO prod: | ||
+ | |||
+ | sswap(n#, | ||
+ | FOR i#:=1 TO n# DO t:=x(i#); x(i# | ||
+ | |||
+ | scopy(n#, | ||
+ | For i#:=1 TO n# DO y(i# | ||
+ | |||
+ | max#: | ||
+ | | ||
+ | t:=0; max#:=1 | ||
+ | FOR i#:=1 TO n# | ||
+ | IF ABS(x(i# | ||
+ | ENDFOR i# | ||
+ | |||
+ | sscal(n#, | ||
+ | FOR i#:=1 TO n# DO x(i# | ||
+ | |||
+ | snrm2(n#, | ||
+ | norm2:=0 | ||
+ | FOR i#:=1 TO n# DO norm2: | ||
+ | norm2: | ||
+ | |||
+ | srot(n#, | ||
+ | FOR i#:=1 TO n# DO | ||
+ | t:=c*x(i#) + s*y(i#) | ||
+ | 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. | ||
+ | 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. | ||
+ | matrix with: | ||
+ | |||
+ | DIM a(n#,n#) | ||
+ | a(1,1):=1; a(1,2):=0 | ||
+ | scopy(n# | ||
+ | scopy(n# | ||
+ | |||
+ | References | ||
+ | |||
+ | 1. Zambardino, R. A., " | ||
+ | Partial Pivoting and Reduced Storage Requirements", | ||
+ | Vol. 17, No. 4, 1974, pp. 377-378. | ||
+ | |||
+ | 2. Orden A., " | ||
+ | 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, | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== The World of IRC - A New Life for the C64/128 ====== | ||
+ | < | ||
+ | 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. | ||
+ | can baffle the most erudite and experienced computer scientists in the | ||
+ | world. | ||
+ | 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! | ||
+ | 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)! | ||
+ | (and have not tried), then you need to look around. | ||
+ | be a college/ | ||
+ | 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, | ||
+ | country to country. | ||
+ | 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. | ||
+ | caution - make sure that it is ok with your employer to use his | ||
+ | facilities... and not on " | ||
+ | |||
+ | Another way that is becoming increasingly more common is to use | ||
+ | commercial " | ||
+ | purpose is to offer you an " | ||
+ | Internet. | ||
+ | greatly.. | ||
+ | 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. | ||
+ | to be the best way to locate the site possibilities. | ||
+ | provide a very good solution. | ||
+ | |||
+ | Another variation on commercial sites are national companies such as | ||
+ | Compuserve, Genie, America Online and Delphi. | ||
+ | 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... | ||
+ | now.. but the are all fascinating parts of the Internet. | ||
+ | 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 " | ||
+ | exchange files) in world-wide conversational " | ||
+ | lines", | ||
+ | readers of this magazine? | ||
+ | IRC called #c-64, a place where c64/128 users are able to meet and | ||
+ | exchange all sorts of information, | ||
+ | later. | ||
+ | |||
+ | 3) The IRC Client | ||
+ | |||
+ | First, to use IRC it is necessary to have access to an IRC client. | ||
+ | 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. | ||
+ | simply typing " | ||
+ | menu if you don't have a shell account. | ||
+ | notice is that your client is attempting to connect to 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. | ||
+ | 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 " | ||
+ | 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 | ||
+ | " | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | unique alphabetic (and an equivalent numeric) IP address. | ||
+ | |||
+ | Once an IRC session is in progress, Unix users can change servers by | ||
+ | typing:/ | ||
+ | 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). | ||
+ | are dozens more. Just ask someone on IRC...or do a few /whois nick | ||
+ | commands. | ||
+ | |||
+ | If your site does not have an irc client, it should be possible to | ||
+ | install one yourself. | ||
+ | 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. | ||
+ | source code in two forms: | ||
+ | 471k) or IRC2.2.9.tar.gz (Unix tar and GNU compress at 306k). | ||
+ | files are the same (except for the compression). | ||
+ | " | ||
+ | |||
+ | Move the file to its own subdirectory if you have not ftp'd it to one | ||
+ | already. | ||
+ | small subdirectory tree of files. | ||
+ | the top subdirectory. | ||
+ | |||
+ | Also in the main subdirectory, | ||
+ | editing to make the client work with you site. One is " | ||
+ | it there are at least two edits. | ||
+ | that u want the executable to reside in. This is most often your home | ||
+ | directory or the " | ||
+ | other is IRCII_LIBRARY. | ||
+ | IRCII code resides. | ||
+ | options and set them for the type of computer and Operating System that | ||
+ | your site uses. | ||
+ | |||
+ | The other file is " | ||
+ | to the alpha or numeric addy of your primary default server. | ||
+ | to enclose the server in quotes (" | ||
+ | |||
+ | For VMS users, there is a subdirectory in " | ||
+ | it. There are two versions - irc176 | ||
+ | a more native VMS version, the second is a Unix-like version. | ||
+ | both executables, | ||
+ | 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. | ||
+ | available for the PC, Amiga, MAC... and even the rumor of one to be | ||
+ | produced for the c64/ | ||
+ | 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 " | ||
+ | having an account at that site.. sorta like anonymous ftp for those of | ||
+ | you who know what that is. There are drawbacks, though. | ||
+ | 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. | ||
+ | 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, | ||
+ | not available from all sites, and usage is limited. | ||
+ | you need to. | ||
+ | |||
+ | Another variation of the " | ||
+ | account at nyx.cs.du.edu. | ||
+ | a little paperwork. | ||
+ | account with full IRC privileges, including DCC file exchange. | ||
+ | course, you need a " | ||
+ | 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 " | ||
+ | |||
+ | A " | ||
+ | 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 " | ||
+ | Before we go on, let me give you a VERY brief description of a bot. We | ||
+ | can say that a bot may be a " | ||
+ | statements understood by your IRC client; or it may be a separate | ||
+ | program (typically written in " | ||
+ | any help from its " | ||
+ | |||
+ | Instead, a bot is intended to respond to others on IRC who " | ||
+ | by "/ | ||
+ | "& | ||
+ | someone typing "load " | ||
+ | |||
+ | 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 < | ||
+ | point and see what happens. | ||
+ | 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 " | ||
+ | and talked to on IRC. Do this by typing: | ||
+ | |||
+ | /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 " | ||
+ | you off. Don't worry - just reconnect.. but try a different nick. Try | ||
+ | just changing the nick a little - like even putting a " | ||
+ | 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 < | ||
+ | |||
+ | Next, you will want to join a channel. | ||
+ | |||
+ | /join #< | ||
+ | |||
+ | A channel is a logical connection of all IRC users anywhere in the world | ||
+ | that have typed the same /join command. | ||
+ | by anyone on the channel are spread by IRC to all other people who are | ||
+ | on the channel. | ||
+ | " | ||
+ | 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' | ||
+ | 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 " | ||
+ | almost always there. | ||
+ | |||
+ | 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 < | ||
+ | |||
+ | As mentioned before, normal channel conversation is seen by everyone who | ||
+ | has joined the channel. | ||
+ | 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 < | ||
+ | |||
+ | Type it on a line of its own, and just < | ||
+ | Quite handy for the more " | ||
+ | Careful, though... use the wrong < | ||
+ | other than you intended will see your < | ||
+ | |||
+ | If you find you are doing a lot of /msg's to the same nick, try: | ||
+ | |||
+ | /query < | ||
+ | |||
+ | This will put you in a sort of ' | ||
+ | everything you type that would normally go to the channel will not act | ||
+ | like a "/msg < | ||
+ | just "/ | ||
+ | |||
+ | Let's jump, now, to /dcc, the command that allows most IRC users to | ||
+ | transfer files. | ||
+ | is allow two nicks to transfer files *directly* between their sites, not | ||
+ | going through either of their servers. | ||
+ | bot; IRC does not make a distinction. | ||
+ | |||
+ | When two nicks exchange files, the sender must always start by typing: | ||
+ | |||
+ | /dcc send < | ||
+ | |||
+ | The recipient will get a message telling what file is being offered and must | ||
+ | type: | ||
+ | |||
+ | /dcc get < | ||
+ | |||
+ | The [filename] is optional, but must be used if more than one file is to | ||
+ | be transferred simultaneously. | ||
+ | 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 "/ | ||
+ | to see what that directory is and you can type: | ||
+ | |||
+ | /cd < | ||
+ | |||
+ | to change that directory. | ||
+ | pathname of the file you want to send if it is not in your " | ||
+ | directory. | ||
+ | |||
+ | There are often a couple of bots on #c-64 that can give you c-64 files. | ||
+ | " | ||
+ | available for DCC. If coolhand is on IRC, type: | ||
+ | |||
+ | /msg coolhand xdcc list | ||
+ | |||
+ | to see a list of lists (of files). | ||
+ | type: | ||
+ | |||
+ | /msg coolhand xdcc list #n | ||
+ | |||
+ | To have coolhand' | ||
+ | |||
+ | /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.. | ||
+ | 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. | ||
+ | maybe to some. But interesting? | ||
+ | #c-64 channel, is a microcosm of the world, with all its variety of | ||
+ | people, personalities, | ||
+ | capability, a tool for communications, | ||
+ | and possibilities. | ||
+ | |||
+ | IRC is totally international, | ||
+ | U.S. and Canada, Europe is very well represented. | ||
+ | smaller but increasingly active contingent in Australia, as the net | ||
+ | becomes more accessible there. | ||
+ | S. America, Africa, and Asia. Russia and other former Soviet Union | ||
+ | countries also have a presence. | ||
+ | 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/ | ||
+ | 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. | ||
+ | current 64 " | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | offer or a willingness to help somehow, just make that known. | ||
+ | channel is full of people, some of whom probably need exactly what you | ||
+ | have to give. | ||
+ | |||
+ | A key thing: | ||
+ | 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. | ||
+ | the time. | ||
+ | |||
+ | Besides being patient yourself, be patient with other people on the | ||
+ | channel. | ||
+ | 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. | ||
+ | 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 # | ||
+ | files, and even new friends. | ||
+ | life and spirit. | ||
+ | |||
+ | ***************** | ||
+ | |||
+ | Some of the material in this article was previously published in " | ||
+ | and is used here by permission. | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== SwiftLink-232 Application Notes (version 1.0b) ====== | ||
+ | < | ||
+ | |||
+ | This information is made available from a paper document published by CMD, | ||
+ | with CMD's express written permission. | ||
+ | grammatical corrections and minor changes, plus, the source code has been | ||
+ | debugged and extended by Craig Bruce < | ||
+ | |||
+ | 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. | ||
+ | certain memory locations in the I/O block ($D000 - $DFFF) or through | ||
+ | interrupts. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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' | ||
+ | |||
+ | The 6551 ACIA was chosen for several reasons, including the low cost and | ||
+ | compatibility with other Commodore (MOS) integrated circuits. | ||
+ | the 6551 as a model for the Kernal software. | ||
+ | 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. | ||
+ | register locations do _not_ perform the same function as their software | ||
+ | counterparts. | ||
+ | accommodate the differences. | ||
+ | |||
+ | 2. BUFFERS | ||
+ | |||
+ | Bytes received are placed in " | ||
+ | routine below, and also by the first sample transmit routine. | ||
+ | similar to the Kernal RS-232 implementation, | ||
+ | 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 " | ||
+ | next byte to be removed from the buffer. | ||
+ | location in which to store a byte. If the head and tail both point to the | ||
+ | same location, the buffer is empty. | ||
+ | |||
+ | The interrupt handler described below will place received bytes at the tail of | ||
+ | the receive buffer. | ||
+ | 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). | ||
+ | the head pointer to the next character, and decrement the counter if you use | ||
+ | one. | ||
+ | |||
+ | You should send a " | ||
+ | capacity. | ||
+ | lowered. | ||
+ | probably do things more efficiently (we were using a _very_ rough | ||
+ | implementation) and set a higher maximum size. At some " | ||
+ | " | ||
+ | |||
+ | 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' | ||
+ | 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 | ||
+ | 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. | ||
+ | interrupts, you can't " | ||
+ | difficulties in your program. | ||
+ | |||
+ | One solution is to eliminate the transmit buffer completely. | ||
+ | decide when to send each byte and perform any other necessary tasks in between | ||
+ | bytes as needed. | ||
+ | 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' | ||
+ | a new byte will arrive. | ||
+ | using an interrupt-driven buffer just waste' | ||
+ | risks missing data. | ||
+ | |||
+ | For a thorough discussion of the use of buffers, the Kernal RS-232 routines, | ||
+ | and the Commodore NMI handler, see " | ||
+ | Kit: Kernal", | ||
+ | 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: | ||
+ | data bytes (i.e., it is a read/write register). | ||
+ | |||
+ | Data received by the ACIA is placed in this register. | ||
+ | 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). | ||
+ | register. | ||
+ | |||
+ | 3.2. STATUS REGISTER ($DE01) | ||
+ | |||
+ | This register has dual functionality: | ||
+ | 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. | ||
+ | and bits #3--#6 (described below) are set to reflect the cause of the | ||
+ | interrupt, as they should. | ||
+ | under 9600 bps, bit #7 seems to work as designed. | ||
+ | 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. | ||
+ | (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. | ||
+ | receiver active at all times. | ||
+ | DCD signal from the modem. | ||
+ | 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. | ||
+ | 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 preceding byte. If you wish to use these bits, store them for processing | ||
+ | by your program. | ||
+ | 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/ | ||
+ | interrupts. | ||
+ | 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. | ||
+ | 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, | ||
+ | interrupt handler disables transmit interrupts. | ||
+ | a RS-232 device that uses CTS/RTS handshaking, | ||
+ | temporarily by bringing RTS high (inactive): clear both bits #2 and #3. | ||
+ | |||
+ | Bit #1 should reflect whether or not you are using receive interrupts. | ||
+ | the sample code below, it is set to enable receive interrupts. | ||
+ | |||
+ | Bit #0 acts as a " | ||
+ | itself. | ||
+ | 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. | ||
+ | modems to hang up (by bringing DTR high). | ||
+ | session is over and the user exits the terminal program to insure that no | ||
+ | spurious interrupts are generated. | ||
+ | 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 " | ||
+ | indicate what values the bits of these registers contain after a hardware | ||
+ | reset (like toggling the computer' | ||
+ | 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" | ||
+ | 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 " | ||
+ | implemented. | ||
+ | are sold by us as well. | ||
+ | |||
+ | Eight of the nine lines from the AT serial port are implemented: | ||
+ | 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. | ||
+ | handles CTS automatically: | ||
+ | RS-232 device, the 6551 stops transmitting (clears the " | ||
+ | empty" bit [#4] in the status register). | ||
+ | |||
+ | The output signals are standard RS-232 level compatible. | ||
+ | with several commercial modems and with various computers using null-modem | ||
+ | cables up to 38,400 bps without difficulties. | ||
+ | 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. | ||
+ | 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. | ||
+ | your program at 38.4 Kbaud as well as lower baud rates. | ||
+ | helpful for local file transfers using the C-64/C-128 as a dumb terminal on | ||
+ | larger systems. | ||
+ | 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). | ||
+ | expansion port, the user would need to run a clip lead into the computer and | ||
+ | connect to pin 12 of U2 (a 74LS138). | ||
+ | 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. | ||
+ | value to the control register and then attempting to read it back or (2) | ||
+ | provide a user-configurable switch/ | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | lines to extract the code, and assemble it using the ACE assembler (ACE is a | ||
+ | public-domain program). | ||
+ | To use from BASIC: | ||
+ | |||
+ | LOAD" | ||
+ | SYS8192 | ||
+ | |||
+ | It is a very simple terminal program. | ||
+ | |||
+ | %%%---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 | ||
+ | command | ||
+ | control | ||
+ | |||
+ | ;Using the ACIA frees many addresses in zero page normally used by | ||
+ | ;Kernel RS-232 routines. | ||
+ | ;chosen arbitrarily. | ||
+ | ;the Kernal routines. | ||
+ | |||
+ | rhead | ||
+ | ; | ||
+ | rtail | ||
+ | rbuff | ||
+ | |||
+ | thead | ||
+ | ; | ||
+ | ttail | ||
+ | ;in transmit buffer | ||
+ | tbuff | ||
+ | |||
+ | xmitcount = | ||
+ | recvcount = | ||
+ | |||
+ | errors | ||
+ | |||
+ | xmiton | ||
+ | ; | ||
+ | xmitoff | ||
+ | ; | ||
+ | ; | ||
+ | |||
+ | NMINV | ||
+ | OLDVEC | ||
+ | |||
+ | ; ---=== INITIALIZATION ===--- | ||
+ | |||
+ | ;Call the following code as part of system initialization. | ||
+ | |||
+ | ;clear all buffer pointers, buffer counters, and errors location | ||
+ | |||
+ | org | ||
+ | lda #$00 | ||
+ | sta rhead | ||
+ | sta rtail | ||
+ | sta thead | ||
+ | sta ttail | ||
+ | |||
+ | sta | ||
+ | sta | ||
+ | sta | ||
+ | |||
+ | ;store the addresses of the buffers in the zero-page vectors | ||
+ | |||
+ | lda #< | ||
+ | sta tbuff | ||
+ | lda #> | ||
+ | sta tbuff + 1 | ||
+ | |||
+ | lda #< | ||
+ | sta rbuff | ||
+ | lda #> | ||
+ | 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 " | ||
+ | ;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. | ||
+ | ; | ||
+ | ; : | ||
+ | ; : | ||
+ | ; :: | ||
+ | ; ::: | ||
+ | ; ::: | ||
+ | ; :::: | ||
+ | ; :::: .----- baud | ||
+ | ; :::: :.---- rate | ||
+ | ; :::: ::.--- bits ;1010 == 4800 baud, change to what you want | ||
+ | ; :::: :::.-- 0-3 | ||
+ | lda # | ||
+ | sta | ||
+ | |||
+ | ;The ACIA " | ||
+ | ;receive interrupt enabling, hardware " | ||
+ | ;and " | ||
+ | ;no echo, disables transmit interrupts, and enables receive interrupts | ||
+ | ;(RTS and DTR low). | ||
+ | ; | ||
+ | ; : | ||
+ | ; :: | ||
+ | ; ::: | ||
+ | ; ::: | ||
+ | ; :::: | ||
+ | ; :::: .----------- transmit interrupt control, bits 2-3 | ||
+ | ; :::: : | ||
+ | ; :::: :: | ||
+ | ; :::: ::.------ receive interrupt control, 0 = enabled | ||
+ | ; :::: ::: | ||
+ | ; :::: :::.--- DTR control, 1=DTR low | ||
+ | lda # | ||
+ | sta | ||
+ | |||
+ | ;Besides initialization, | ||
+ | ;changes parity of echo mode. | ||
+ | ;It creates the " | ||
+ | ;handler and main-program transmit routine to control the ACIA | ||
+ | ;interrupt enabling. | ||
+ | ; | ||
+ | |||
+ | ;initialize with transmit interrupts off since | ||
+ | ;buffer will be empty | ||
+ | |||
+ | sta | ||
+ | and # | ||
+ | ora # | ||
+ | ;receive interrupts | ||
+ | sta | ||
+ | |||
+ | ;The standard NMI routine tests th < | ||
+ | ;for the presence of an autostart cartridge. | ||
+ | |||
+ | ;You can safely bypass the normal routine unless: | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ;If you need any of these functions, you can wedge the ACIA | ||
+ | ;interrupt handler in ahead of the Kernal routines. | ||
+ | ;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 " | ||
+ | ;A " | ||
+ | ;will probably cause a system crash if NEWNMI isn't there. | ||
+ | ;be best to initialize the ACIA to a "no interrupts" | ||
+ | ;new vector is stored. | ||
+ | ;ACIA interrupts, it pays to be sure. | ||
+ | |||
+ | ;If the user turns the modem off and on, an interrupt will probably be | ||
+ | ; | ||
+ | ;buffer, unless you don't have NEWNMI in place. | ||
+ | |||
+ | NEWVEC: | ||
+ | sei ;A stray IRQ shouldn' | ||
+ | ;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. | ||
+ | ;skip the next four lines. | ||
+ | ;the CPU registers to the stack. | ||
+ | ;you should pop the registers first (see EXITINT below). | ||
+ | |||
+ | lda | ||
+ | sta | ||
+ | lda | ||
+ | sta | ||
+ | |||
+ | ;come here from the SEI if you're not saving | ||
+ | ;the old vector | ||
+ | lda #< | ||
+ | sta | ||
+ | lda #> | ||
+ | sta | ||
+ | |||
+ | cli ;allow IRQs again | ||
+ | |||
+ | ;continue initializing your program | ||
+ | |||
+ | ; ::: | ||
+ | jmp | ||
+ | |||
+ | ;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 receive-data register full | ||
+ | ; :::: : | ||
+ | ; :::: : | ||
+ | ; :::: :: | ||
+ | ; :::: ::.------- high if framing error | ||
+ | ; :::: ::: | ||
+ | ; :::: :::.--- high if parity error | ||
+ | ; | ||
+ | |||
+ | NEWNMI: | ||
+ | ; | ||
+ | ; | ||
+ | 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. | ||
+ | ;caused by the receive register being full (bit #3) or the transmit | ||
+ | ;register being empty (bit #4) since these two activities should receive | ||
+ | ; | ||
+ | ;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. | ||
+ | ;it's safe and doesn' | ||
+ | |||
+ | ;The same technique should be used in parts of your program where timing | ||
+ | ;is critical. | ||
+ | ;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 ' | ||
+ | |||
+ | lda | ||
+ | |||
+ | ;Now prevent any more NMIs from the ACIA | ||
+ | |||
+ | ldx # | ||
+ | ;leave DTR active | ||
+ | stx | ||
+ | ;will re-enable interrupts | ||
+ | |||
+ | ;Store the status-register data only if needed for error checking. | ||
+ | ;The next received byte will clear the error flags. | ||
+ | |||
+ | ; | ||
+ | |||
+ | and # | ||
+ | ;receive interrupt indicators | ||
+ | |||
+ | ;If you don't use a transmit buffer you can use | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ;to test for receive interrupts only and skip the receive test shown | ||
+ | ;below. | ||
+ | |||
+ | beq | ||
+ | |||
+ | ;if 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: | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | |||
+ | ;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: | ||
+ | and # | ||
+ | beq | ||
+ | ;a transmit buffer, the interrupt must have been | ||
+ | ;caused by transmit. | ||
+ | lda | ||
+ | ldy | ||
+ | sta | ||
+ | inc | ||
+ | inc | ||
+ | ;(if used by your program) | ||
+ | |||
+ | ;Skip the " | ||
+ | ;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. | ||
+ | ;sample, receive interrupts take precedence. | ||
+ | ;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. | ||
+ | ;next character if there is one. Before sending a character to the ACIA to | ||
+ | ;be transmitted, | ||
+ | |||
+ | XMIT: | ||
+ | lda | ||
+ | ;fall through to process xmit buffer | ||
+ | beq | ||
+ | ;or | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ;if you don't check DCD or DSR in your program. | ||
+ | |||
+ | XMITBYTE: | ||
+ | lda | ||
+ | and # | ||
+ | beq | ||
+ | |||
+ | XMITCHAR: | ||
+ | ldy thead | ||
+ | lda | ||
+ | sta | ||
+ | |||
+ | ;point to next character in buffer | ||
+ | inc | ||
+ | dec | ||
+ | ;in xmit buffer | ||
+ | lda | ||
+ | beq | ||
+ | ;or | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ;if you don't check DCD or DSR in your program | ||
+ | |||
+ | ;If xmitcount decrements to zero, there are no more characters to | ||
+ | ; | ||
+ | |||
+ | ;If there are more bytes in the buffer, set up the ' | ||
+ | ;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 " | ||
+ | ;and " | ||
+ | |||
+ | lda | ||
+ | |||
+ | ;If you don't use DCD or DSR | ||
+ | |||
+ | bne | ||
+ | |||
+ | ;If your program uses DCD and/or DSR, you'll want to know when the state | ||
+ | ;of those lines changes. | ||
+ | ;by polling the ACIA status register, but if either of the lines changes, | ||
+ | ;the chip will generate an NMI anyway. | ||
+ | ;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: | ||
+ | |||
+ | ; | ||
+ | ;the proper mask to re-enable interrupts on | ||
+ | ;the ACIA | ||
+ | ; :: | ||
+ | ; :: | ||
+ | ; :: | ||
+ | ; :: | ||
+ | ; :: | ||
+ | ; :: | ||
+ | ; | ||
+ | ;DCD/DSR routine | ||
+ | ; | ||
+ | ;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 | ||
+ | |||
+ | ;and this line sets the interrupt status to whatever is in the ' | ||
+ | |||
+ | NMICOMMAND: | ||
+ | sta | ||
+ | |||
+ | ; | ||
+ | ;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. | ||
+ | ;to a custom handler. | ||
+ | |||
+ | EXITINT: | ||
+ | pla ; | ||
+ | tay | ||
+ | pla ; | ||
+ | tax | ||
+ | pla ; | ||
+ | |||
+ | ;If you want to continue processing the interrupt with the Kernal routines, | ||
+ | |||
+ | jmp | ||
+ | |||
+ | ;Or, if you add your own interrupt routine, | ||
+ | |||
+ | ; | ||
+ | |||
+ | ;If you use your own routine, or if you don't add anything, BE SURE to do | ||
+ | ;this last (C64 only): | ||
+ | |||
+ | ; | ||
+ | |||
+ | ;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 ' | ||
+ | ;push to stack if you need to preserve it. | ||
+ | |||
+ | SENDBYTE: | ||
+ | ;the ACIA to enable transmit interrupts (the | ||
+ | ;interrupt handler will disable them again | ||
+ | ;when the buffer is empty) | ||
+ | |||
+ | ldy | ||
+ | cpy # | ||
+ | beq | ||
+ | |||
+ | ldy | ||
+ | sta | ||
+ | inc | ||
+ | inc | ||
+ | |||
+ | lda | ||
+ | sta | ||
+ | |||
+ | rts ; | ||
+ | |||
+ | NOTHING: | ||
+ | lda # | ||
+ | ;byte was not transmitted | ||
+ | rts ;and return | ||
+ | |||
+ | ; | ||
+ | ;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 ' | ||
+ | ;Destroys ' | ||
+ | ; | ||
+ | ;SENDBYTE: | ||
+ | ; | ||
+ | ; | ||
+ | ;TESTACIA: | ||
+ | ; | ||
+ | ; ;the ACIA is ready to transmit another byte, | ||
+ | ; ;even if transmit interrupts are disabled. | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; 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 character was available. | ||
+ | ;the carry flag set. Destroys the ' | ||
+ | |||
+ | RECVBYTE: | ||
+ | ;there is no need to fiddle with any interrupts | ||
+ | |||
+ | lda | ||
+ | beq | ||
+ | |||
+ | ldy | ||
+ | lda | ||
+ | inc | ||
+ | dec | ||
+ | |||
+ | clc ; | ||
+ | rts ; | ||
+ | |||
+ | RECVEMPTY: | ||
+ | sec ;or whatever flag your program uses to tell that the | ||
+ | ;receive buffer was empty | ||
+ | rts ;and return | ||
+ | |||
+ | ; | ||
+ | ;Dumb -- very dumb -- terminal emulator. | ||
+ | ;the keyboard and puts received data to the screen and typed data to the send | ||
+ | ;buffer (thus, it assumes a full-duplex, | ||
+ | ; | ||
+ | ;STOP to exit. | ||
+ | ; | ||
+ | ;by Craig Bruce, 1995. | ||
+ | |||
+ | TERMINAL: | ||
+ | jsr | ||
+ | bcs | ||
+ | jsr | ||
+ | TERMTRYSEND: | ||
+ | jsr | ||
+ | cmp # | ||
+ | beq | ||
+ | cmp # | ||
+ | beq | ||
+ | jsr | ||
+ | jmp | ||
+ | TERMEXIT: | ||
+ | lda # | ||
+ | sta | ||
+ | sei | ||
+ | lda | ||
+ | sta NMINV | ||
+ | lda | ||
+ | sta | ||
+ | cli | ||
+ | rts ;exit | ||
+ | |||
+ | TRANSMIT_BUFFER = *+0 | ||
+ | RECEIVE_BUFFER | ||
+ | %%%---8< | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | </ | ||
+ | ====== APPENDIX: 6551 ACIA HARDWARE SPECS (DATA SHEET) ====== | ||
+ | < | ||
+ | C= Commodore Semiconductor Group | ||
+ | a division of Commodore Business Machines, Inc. | ||
+ | 950 Rittenhouse Road, Nornstown, PA 19400 * 215/ | ||
+ | (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. | ||
+ | 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: | ||
+ | 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 ___ | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | | ||
+ | | ||
+ | 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 | ||
+ | | ||
+ | | | ||
+ | v | ||
+ | | ||
+ | | TRANSMIT | | TRANSMIT | | ||
+ | / | ||
+ | | ||
+ | | ||
+ | +---------+ | ||
+ | o2 ---> | ||
+ | R-/W ---> | ||
+ | CS0 ---> | ||
+ | /CS1 --->| CONTROL | || +----------+ | ||
+ | RS0 ---> | ||
+ | RS1 ---> | ||
+ | /RES ---> | ||
+ | +---------+ | ||
+ | | ||
+ | || | ||
+ | +---------+ | ||
+ | DB0 < | ||
+ | ... | ||
+ | DB7 <-->| BUFFERS | || | REGISTER | | REGISTER | | | ||
+ | +---------+ | ||
+ | | ||
+ | | ||
+ | | ||
+ | | REGISTER | | CONTROL |<---+ | ||
+ | | ||
+ | | | | ||
+ | | ||
+ | +-------------------------------------> | ||
+ | |||
+ | 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/ | ||
+ | |||
+ | The R-/W is generated by the microprocessor and is used to control the | ||
+ | direction of data transfers. | ||
+ | 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. | ||
+ | an open drain output, permitting several devices to be connected to the common | ||
+ | /IRQ microprocessor input. | ||
+ | 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. | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 0 | ||
+ | 1 | ||
+ | 1 | ||
+ | |||
+ | * for programmed reset, data is " | ||
+ | |||
+ | 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 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. | ||
+ | generated clock may be used to drive the XTAL1 pin, in which case the XTAL2 | ||
+ | pin must float. | ||
+ | |||
+ | TxD (Transmit Data) | ||
+ | |||
+ | The TxD output line is used to transfer serial NRZ (non-return-to-zero) data | ||
+ | to the modem. | ||
+ | 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. | ||
+ | or the rate of an externally generated receiver clock. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | low indicates the " | ||
+ | impedance input and must not be a no-connect. | ||
+ | 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. | ||
+ | state of /DSR does not affect Transmitter operation [but must be low for the | ||
+ | Receiver to operate]. | ||
+ | |||
+ | /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. | ||
+ | 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. | ||
+ | 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. | ||
+ | |||
+ | * Bit 0 is the leading bit to be transmitted. | ||
+ | |||
+ | * Unused data bits are the high-order bits and are " | ||
+ | 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 " | ||
+ | |||
+ | * Parity bits are not contained in the Receive Data Register, but are stripped | ||
+ | off after being used for external parity checking. | ||
+ | high-order bits are " | ||
+ | |||
+ | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | | ||
+ | |||
+ | The following figure illustrates a single transmitted or received data | ||
+ | word, for the example of 8 data bits, parity, and 1 stop bit: | ||
+ | |||
+ | " | ||
+ | | ||
+ | | ||
+ | start parity | ||
+ | | ||
+ | |||
+ | |||
+ | 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: | ||
+ | |||
+ | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | irq | dcd | dsr | txr | rxr | ovr | fe | pe | | ||
+ | |||
+ | +---+ | ||
+ | | 7 | / | ||
+ | +---+ | ||
+ | 0 No interrupt | ||
+ | 1 | ||
+ | |||
+ | +---+ | ||
+ | | 6 | /DCD : non-resetable, | ||
+ | +---+ | ||
+ | 0 /DCD low | ||
+ | 1 /DCD high | ||
+ | |||
+ | +---+ | ||
+ | | 5 | /DSR : non-resetable, | ||
+ | +---+ | ||
+ | 0 /DSR low | ||
+ | 1 /DSR high | ||
+ | |||
+ | +---+ | ||
+ | | 4 | | ||
+ | +---+ | ||
+ | 0 Not empty | ||
+ | 1 Empty | ||
+ | |||
+ | +---+ | ||
+ | | 3 | | ||
+ | +---+ | ||
+ | 0 Not full | ||
+ | 1 Full | ||
+ | |||
+ | +---+ | ||
+ | | 2 | | ||
+ | +---+ | ||
+ | 0 No error | ||
+ | 1 Error | ||
+ | |||
+ | +---+ | ||
+ | | 1 | | ||
+ | +---+ | ||
+ | 0 No error | ||
+ | 1 Error | ||
+ | |||
+ | +---+ | ||
+ | | 0 | | ||
+ | +---+ | ||
+ | 0 No error | ||
+ | 1 Error | ||
+ | |||
+ | Notes: | ||
+ | ** 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 | ||
+ | | ||
+ | |||
+ | 7 | ||
+ | +---+---+---+---+---+---+---+---+ | ||
+ | | 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/ | ||
+ | and is shown here: | ||
+ | |||
+ | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | | ||
+ | |||
+ | +---+---+---+ | ||
+ | | 7 | 6 | 5 | | ||
+ | +---+---+---+ | ||
+ | x | ||
+ | 0 | ||
+ | 0 | ||
+ | 1 | ||
+ | 1 | ||
+ | |||
+ | +---+ | ||
+ | | 4 | | ||
+ | +---+ | ||
+ | 0 | ||
+ | 1 Echo (bits 2 and 3 must be " | ||
+ | |||
+ | +---+---+ | ||
+ | | 3 | 2 | Tx INTERRUPT | ||
+ | +---+---+ | ||
+ | 0 | ||
+ | 0 | ||
+ | 1 | ||
+ | 1 | ||
+ | |||
+ | +---+ | ||
+ | | 1 | | ||
+ | +---+ | ||
+ | 0 /IRQ interrupt Enabled from bit 3 of Status Register | ||
+ | 1 /IRQ interrupt Disabled | ||
+ | |||
+ | +---+ | ||
+ | | 0 | DATA TERMINAL READY | ||
+ | +---+ | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | 7 | ||
+ | +---+---+---+---+---+---+---+---+ | ||
+ | | 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: | ||
+ | |||
+ | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
+ | +-----+-----+-----+-----+-----+-----+-----+-----+ | ||
+ | |stops| | ||
+ | |||
+ | +---+ | ||
+ | | 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 | ||
+ | 0 | ||
+ | 1 | ||
+ | 1 | ||
+ | |||
+ | +---+ | ||
+ | | 4 | | ||
+ | +---+ | ||
+ | 0 | ||
+ | 1 baud rate generator | ||
+ | |||
+ | +---+---+---+---+ | ||
+ | | 3 | 2 | 1 | 0 | BAUD RATE GENERATOR | ||
+ | +---+---+---+---+ | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 0 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | 1 | ||
+ | |||
+ | 7 | ||
+ | +---+---+---+---+---+---+---+---+ | ||
+ | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | After Hardware reset | ||
+ | +---+---+---+---+---+---+---+---+ | ||
+ | | x | x | x | x | x | x | x | x | After Software reset | ||
+ | +---+---+---+---+---+---+---+---+ | ||
+ | |||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== Design and Implementation of a Simple/ | ||
+ | < | ||
+ | by Craig Bruce < | ||
+ | |||
+ | 1. INTRODUCTION | ||
+ | |||
+ | If you use your Commodore for telecommunications, | ||
+ | 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/ | ||
+ | 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, | ||
+ | Commodore computers, even though it uses a simple " | ||
+ | acknowledgement scheme. | ||
+ | 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 " | ||
+ | short. | ||
+ | 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 " | ||
+ | on your Commodore computer and a " | ||
+ | host. There is currently no server program for any other platform, but the | ||
+ | necessary changes to the C-language program wouldn' | ||
+ | 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. | ||
+ | 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 " | ||
+ | command is as follows: | ||
+ | |||
+ | fx [-dlvV7] [-m maximums] [-f argfile] [[-b] binfile ...] [-t textfile ...] | ||
+ | |||
+ | -d = debug mode | ||
+ | -l = write to log file (" | ||
+ | -v = verbose log/debug mode | ||
+ | -V = extremely verbose log/debug mode | ||
+ | -7 = use seven-bit encoding | ||
+ | -m = set maximum packet sizes; maximums = ulbin/ | ||
+ | -f = take arguments one-per-line from given argfile | ||
+ | -b = binary files prefix | ||
+ | -t = text files prefix | ||
+ | -help = help | ||
+ | |||
+ | well, for the server, anyway. | ||
+ | exotic options. | ||
+ | on the Server program, and are for debugging purposes only. | ||
+ | |||
+ | The " | ||
+ | to not use the 8th bit position in the data is transmitted. | ||
+ | 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. | ||
+ | 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, | ||
+ | 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. | ||
+ | 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. | ||
+ | program easily by changing some "# | ||
+ | |||
+ | The " | ||
+ | transfer. | ||
+ | slashes between them, which give the maximum ulbin/ | ||
+ | sizes, respectively. | ||
+ | user data encoded into packets and not the control or quoting information | ||
+ | (below). | ||
+ | |||
+ | The " | ||
+ | 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. | ||
+ | don't want other users to see the names of the files that you are | ||
+ | downloading. | ||
+ | " | ||
+ | in " | ||
+ | This option is not supported on the client program. | ||
+ | |||
+ | Finally come the " | ||
+ | FX that all of the following filenames (until the next " | ||
+ | files and the " | ||
+ | files. | ||
+ | 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. | ||
+ | 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. | ||
+ | session. | ||
+ | you don't really want to transfer any files after activating the server | ||
+ | program, you can type three Ctrl-X' | ||
+ | for the X-modem protocol. | ||
+ | |||
+ | 3. DESIGN DECISIONS | ||
+ | |||
+ | There are a number of design decisions to be made about our protocol. | ||
+ | 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 " | ||
+ | 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. | ||
+ | be aware of filenames, dates, permissions, | ||
+ | contents to get mangled like they do with X-modem (it pads them with Ctrl-Z' | ||
+ | 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 " | ||
+ | encoding the text data during transfer. | ||
+ | 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. | ||
+ | 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, | ||
+ | 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. | ||
+ | this allows us to change implementations to be able to transfer entire files | ||
+ | in one large packet. | ||
+ | flexible: be from one to N bytes. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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/ | ||
+ | with the stop-and-wait acknowledgement scheme to produce a Remote Procedure | ||
+ | Call (RPC) type of interaction between the machines. | ||
+ | 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. | ||
+ | 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. | ||
+ | packet out, if we don't receive the reply within a certain period of time, we | ||
+ | will timeout and send the request again. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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: | ||
+ | |||
+ | We also want to be able to both upload and download as conveniently as | ||
+ | possible. | ||
+ | (as described in the previous section). | ||
+ | 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. | ||
+ | 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. | ||
+ | or "byte stuffing", | ||
+ | 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. | ||
+ | corrupted, and the recovery will be easier since the packet is well | ||
+ | identified, and only it needs to be recovered. | ||
+ | a link can be shared between multiple (logical) communication streams fairly | ||
+ | and efficiently, | ||
+ | multiple physical links where facilities exist. | ||
+ | |||
+ | Packets also integrate well with many IPC schemes, including Remote Procedure | ||
+ | Calls. | ||
+ | using RPC over a stream-oriented transport system. | ||
+ | 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). | ||
+ | are very useful things indeed! | ||
+ | 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, | ||
+ | |||
+ | +------------------------+-----------+--------------+----------------------+ | ||
+ | | Start-of-packet Char | Payload | ||
+ | +------------------------+-----------+--------------+----------------------+ | ||
+ | |||
+ | 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. | ||
+ | implementation, | ||
+ | it is as fast as a simple add-up checksum, except much more reliable. | ||
+ | this error check, there will be approximately a one-in-4, | ||
+ | that a packet with an error in it will be accepted has being error-free. | ||
+ | These are pretty good odds for our purposes. | ||
+ | exclusively on the raw payload data. | ||
+ | |||
+ | The following special characters used by packets are defined: | ||
+ | |||
+ | NAME | ||
+ | --------- | ||
+ | CHR_START | ||
+ | CHR_END | ||
+ | CHR_ESC | ||
+ | CHR_ABORT | ||
+ | CHR_XON | ||
+ | CHR_XOFF | ||
+ | CHR_QUOTE8 | ||
+ | |||
+ | CHR_START is used to signify the start of a new packet. | ||
+ | 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. | ||
+ | after you toss away a garbled packet and you're back in business. | ||
+ | unaware of any reasonable alternatives to framing packets with a CHR_START | ||
+ | character. | ||
+ | |||
+ | CHR_ESC is used to " | ||
+ | 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 " | ||
+ | (0x40 and 0x5f) in the ASCII chart, or be the character "?" | ||
+ | character following the CHR_ESC is then " | ||
+ | off the " | ||
+ | 0x00 to 0x1f (the same range as the special control characters) and the | ||
+ | " | ||
+ | character following the CHR_ESC is a "?", | ||
+ | instead. | ||
+ | character being represented allows for greater resiliance of the protocol in | ||
+ | the presence of bits being garbled or bytes being dropped. | ||
+ | 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. | ||
+ | stream, the next character is extracted and is " | ||
+ | give it a " | ||
+ | followed by a CHR_ESC code, which is interpreted as above and then " | ||
+ | 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). | ||
+ | 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. | ||
+ | convenient error detection and recovery and works well with our interprocess | ||
+ | communication scheme. | ||
+ | |||
+ | One implementation note about the packetization has to do with buffering. | ||
+ | the Unix host, it is advantageous to encode a packet into a memory buffer and | ||
+ | then send out that buffer in a single " | ||
+ | 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. | ||
+ | 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. | ||
+ | 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' | ||
+ | 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 " | ||
+ | the system overhead, which is insignificant compared to the modem speed. | ||
+ | Unix program used the " | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | software is logically at a distinct layer that doesn' | ||
+ | higher layers. | ||
+ | file-transfer layer. | ||
+ | |||
+ | 5. CLIENT/ | ||
+ | |||
+ | As discussed previously, the client/ | ||
+ | Procedure Call paradigm. | ||
+ | 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. | ||
+ | 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. | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | |||
+ | This is what gets put into the the " | ||
+ | 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. | ||
+ | channel width is encoded into either a ' | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | 8 | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | The " | ||
+ | The " | ||
+ | command line that activated the server, and the " | ||
+ | size" is a ' | ||
+ | server is seven bits, or ' | ||
+ | all subsequent messages that are exchanged. | ||
+ | |||
+ | The server' | ||
+ | for uploading and downloading binary and text files. | ||
+ | the " | ||
+ | resulting maximum packet sizes for the rest of the file exchange session. | ||
+ | maximum packet sizes in the server' | ||
+ | that are stored from most-significant to least-significant bytes (big endian | ||
+ | order). | ||
+ | commonly in inter-machine protocols. | ||
+ | |||
+ | The reason that the client doesn' | ||
+ | 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 " | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | When the server receives this request, it replies with: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | And then exits like it should. | ||
+ | accept any more packets, since they would be sent to whatever command shell | ||
+ | you use on your Unix system, and wouldn' | ||
+ | sends the disconnect message but doesn' | ||
+ | and tell the user that it couldn' | ||
+ | should be a rare occurrence. | ||
+ | his terminal program and send Ctrl-X' | ||
+ | should have. | ||
+ | |||
+ | This arrangement allows us to avoid the famous(?) "two armies" | ||
+ | inherent in disconnecting two connected processes: there is no " | ||
+ | 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. | ||
+ | of 15 seconds or so after you finish transferring files. | ||
+ | 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' | ||
+ | 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. | ||
+ | uploading and downloading requests operate much like the regular file | ||
+ | operations of open, close, read, and write. | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 6 | ||
+ | 8 12 | ||
+ | | ||
+ | 20+n - SIZE | ||
+ | |||
+ | The "data type" field tells whether a text or binary file will be uploaded. | ||
+ | There is a provision for " | ||
+ | and downloading entire directory hierarchies), | ||
+ | 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). | ||
+ | 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. | ||
+ | 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. | ||
+ | is with the Unix operating system: " | ||
+ | and execute-as-owner, | ||
+ | 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 " | ||
+ | information is even harder to come across with Commodore-DOS, | ||
+ | it will have a 12-byte BCD format. | ||
+ | should be easy enough to figure out, and the " | ||
+ | of seconds. | ||
+ | Sunday to Saturday, and 7 for " | ||
+ | number of hours and minutes that your time zone is off from GMT. If the | ||
+ | number is negative (in the western hemisphere), | ||
+ | number of hours will be used, execept that the 0x80 bit of the hours byte will | ||
+ | be set. Finally, the " | ||
+ | timestamp. | ||
+ | to plus/minus 2^aa milliseconds. | ||
+ | within one second, then this field would be set to 10 in BCD (2^10 == | ||
+ | 1024ms). | ||
+ | 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" | ||
+ | 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, | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | |||
+ | 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' | ||
+ | be closed). | ||
+ | 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.). | ||
+ | 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, | ||
+ | data should be sent to the server one at a time, until all of the data is | ||
+ | uploaded. | ||
+ | a packet of data: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 6 | ||
+ | 6+n | ||
+ | |||
+ | The " | ||
+ | that retransmissions of packets are detected and handled properly, so that | ||
+ | each packet of data only appears in the file once. The "data length" | ||
+ | tells the number of user data bytes that follow in the packet, and then the | ||
+ | actual user data bytes appear. | ||
+ | but I figured that it would make programming a little easier, and allows | ||
+ | additional error checking. | ||
+ | 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.). | ||
+ | size values are also what will be used for the " | ||
+ | calls on the client and server, respectively. | ||
+ | efficient chunks. | ||
+ | |||
+ | Upon receiving each upload packet, the server replies with the following | ||
+ | acknowledgement message: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | |||
+ | I don't think that the " | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | This will close the upload channel and will finish writing the uploaded file | ||
+ | to the Unix file system. | ||
+ | message to acknowledge the request: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 5 | ||
+ | |||
+ | The " | ||
+ | error checking. | ||
+ | |||
+ | 4.3. FILE DOWNLOADING | ||
+ | |||
+ | Downloading files is analogous to uploading them: first we open the download | ||
+ | channel/ | ||
+ | channel. | ||
+ | |||
+ | To open the download channel, the client sends the following request to the | ||
+ | server: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | To which the server replies with: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 6 | ||
+ | 8 12 | ||
+ | | ||
+ | 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 " | ||
+ | |||
+ | If the server replies with a ' | ||
+ | server has no more files to offer for downloading. | ||
+ | are taken one at a time, from left to right, from the command line that was | ||
+ | used to start the server. | ||
+ | session is complete and the client disconnects (since the client uploads | ||
+ | its files first). | ||
+ | |||
+ | Alternatively, | ||
+ | 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. | ||
+ | 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. | ||
+ | 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 ' | ||
+ | not currently implemented) indicating that the file was correctly opened and | ||
+ | is either text or binary (as specified on the server' | ||
+ | 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. | ||
+ | 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. | ||
+ | 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' | ||
+ | file doesn' | ||
+ | |||
+ | 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. | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 6 | ||
+ | |||
+ | The " | ||
+ | requests for new packets, and the client tells the server the " | ||
+ | acceptable data length" | ||
+ | information is actually static during the connection, I included it here in | ||
+ | every " | ||
+ | particular bit of " | ||
+ | |||
+ | The server replies to the download-packet request with the following message: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 6 | ||
+ | 6+n | ||
+ | |||
+ | This is the only " | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | |||
+ | And the server will reply with: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 1 | ||
+ | 5 | ||
+ | |||
+ | The " | ||
+ | additional error checking. | ||
+ | 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. | ||
+ | detects an extended period of inactivity on the serial line for received data | ||
+ | (where " | ||
+ | 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. | ||
+ | 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' | ||
+ | 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. | ||
+ | IS currently open, then it must be the case that the current request is a | ||
+ | retransmission, | ||
+ | reply without performing any internal file operations. | ||
+ | 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. | ||
+ | from one another, and, in fact, that the entire upload and download file- | ||
+ | transfer channels are distinct and independent from each another. | ||
+ | allow for the future possibility of simultaneous file uploading and | ||
+ | downloading. | ||
+ | open/ | ||
+ | over-the-phone interactive file server. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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: | ||
+ | Download 151,267 bytes of tabular text: time= 45.9 sec, rate=3296 cps. | ||
+ | Download 141,299 bytes of JPEG image: | ||
+ | Upload | ||
+ | Upload | ||
+ | Upload | ||
+ | |||
+ | Using FX to/from my CMD Hard Drive: | ||
+ | |||
+ | Download 156,260 bytes of ~text: | ||
+ | Download 151,267 bytes of tabular text: time= 75.4 sec, rate=2006 cps. | ||
+ | Download 141,299 bytes of JPEG image: | ||
+ | Upload | ||
+ | Upload | ||
+ | Upload | ||
+ | |||
+ | Using DesTerm-128 v2.00 to/from my CMD Hard Drive, Y-Modem: | ||
+ | |||
+ | Download 156,260 bytes of ~text: | ||
+ | Download 151,267 bytes of tabular text: time=180.4 sec, rate= 839 cps. | ||
+ | Download 141,299 bytes of JPEG image: | ||
+ | Upload | ||
+ | Upload | ||
+ | Upload | ||
+ | |||
+ | Using NovaTerm-64 v9.5 to my CMD Hard Drive, Z-Modem, C64 mode: | ||
+ | |||
+ | Download 156,260 bytes of ~text: | ||
+ | Download 151,267 bytes of tabular text: time=230.0 sec, rate= 658 cps. | ||
+ | Download 141,299 bytes of JPEG image: | ||
+ | |||
+ | (There is no Z-Modem uploading support) | ||
+ | |||
+ | So there you have it: my simple protocol blows the others away. QED. | ||
+ | ======================================================================== | ||
+ | </ | ||
+ | ====== DESIGN AND IMPLEMENTATION OF A ' | ||
+ | < | ||
+ | |||
+ | by Craig S. Bruce < | ||
+ | |||
+ | 0. PREFACE | ||
+ | |||
+ | There has been a slight change in plans. | ||
+ | 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. | ||
+ | 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. | ||
+ | details of implementing such a beast. | ||
+ | provides system calls to create and " | ||
+ | information, | ||
+ | and to perform message-passing interprocess communication. | ||
+ | |||
+ | Currently, there is no real memory management, no real device drivers, and no | ||
+ | process-resource reclamation. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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). | ||
+ | especially for a microkernel environment. | ||
+ | loaded to dynamic addresses. | ||
+ | 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. | ||
+ | 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, | ||
+ | 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. | ||
+ | processes, two " | ||
+ | SID-banging process, and the Null process. | ||
+ | " | ||
+ | " | ||
+ | |||
+ | 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. | ||
+ | call the Commodore-Kernal routines. | ||
+ | from a client process at a time, calls to the Commodore Kernal are effectively | ||
+ | " | ||
+ | things would blow up pretty badly if two accesses the Commodore Kernal were to | ||
+ | happen concurrently. | ||
+ | |||
+ | The " | ||
+ | seconds and then request the Kernal Server to print a " | ||
+ | the screen. | ||
+ | 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. | ||
+ | use any CPU time, so the CPU time is allocated to other processes. | ||
+ | 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 " | ||
+ | send print messages to the Commodore-Server process. | ||
+ | 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. | ||
+ | the 16-bit frequency of voice #1 from $0000 to $ffff and then suspends its | ||
+ | execution for two seconds and then repeats. | ||
+ | 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. | ||
+ | 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' | ||
+ | |||
+ | 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. | ||
+ | $0400-$0403 (on the 40-column screen) whenever it is active. | ||
+ | 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. | ||
+ | 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, | ||
+ | |||
+ | 3. PROCESS CONTROL | ||
+ | |||
+ | A process is a user program that is in an active state of execution. | ||
+ | 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. | ||
+ | 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 " | ||
+ | |||
+ | In our system, the process that the CPU is currently executing is changed | ||
+ | every 1/60 of a second. | ||
+ | reasons, including the fact that, thanks to the MMU of the C128, " | ||
+ | switching" | ||
+ | |||
+ | 3.1. PROCESS-CONTROL CALLS | ||
+ | |||
+ | There are six kernel calls that deal with process control: | ||
+ | |||
+ | CALL NAME INPUT ARGUMENTS | ||
+ | ----------- | ||
+ | Create | ||
+ | Exit ( .A=code, .X=$00 ) < | ||
+ | MyPid ( ) .AY=pid | ||
+ | MyParentPid | ||
+ | Suspend | ||
+ | Delay ( .AY=jiffies ) < | ||
+ | |||
+ | 3.1.1. CREATE | ||
+ | |||
+ | The Create() kernel call is used to create a new process. | ||
+ | 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). | ||
+ | memory and be ready to be executed, since there is no facility for loading | ||
+ | external programs. | ||
+ | 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" | ||
+ | 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. | ||
+ | determines the number of cycles that the active-process pointer has to take | ||
+ | through the list before the process is activated. | ||
+ | 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. | ||
+ | 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. | ||
+ | that when the process is activated, it will not be deactivated again until it | ||
+ | blocks for some reason. | ||
+ | computations that block often, since it has the potential to starve out the | ||
+ | rest of the system. | ||
+ | (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. | ||
+ | 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' | ||
+ | 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. | ||
+ | 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), | ||
+ | currently be $00. I haven' | ||
+ | work yet and it currently only has a minimal implementation. | ||
+ | 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. | ||
+ | executing, or they can achieve the same result by executing an RTS instruction | ||
+ | at the end of their main routine. | ||
+ | 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. | ||
+ | very quickly. | ||
+ | is returned in the .AY register. | ||
+ | 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). | ||
+ | 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. | ||
+ | exist either before or after the current process makes this call; it may have | ||
+ | Exit()ed. | ||
+ | |||
+ | 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 " | ||
+ | another process can call in order to wake up the process that suspended | ||
+ | itself. | ||
+ | what it does is required by other kernel operations, and the cost of making | ||
+ | this call user-accessible was three 6502 instructions. | ||
+ | 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). | ||
+ | passed in in the .AY registers, giving a maximum possible delay period of | ||
+ | about 18 minutes. | ||
+ | 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. | ||
+ | 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 " | ||
+ | waits may actually be any period between a couple of microseconds to almost a | ||
+ | full jiffy, with a statistical average of half a jiffy. | ||
+ | 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. | ||
+ | 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' | ||
+ | skew caused by the processing that your process does. A DelayUntil() call | ||
+ | easily could be implemented, | ||
+ | |||
+ | 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. | ||
+ | awakened process a temporarily high priority, since it is probably likely that | ||
+ | the process will do some small think and then block again. | ||
+ | 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, | ||
+ | process control block are shown here, organized into classes: | ||
+ | |||
+ | OFF | ||
+ | --- | ||
+ | 0 | ||
+ | 2 | ||
+ | 4 | ||
+ | 5 | ||
+ | 6 | ||
+ | 7 | ||
+ | 8 | ||
+ | 9 | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | 3.2.1. QUEUE-CLASS FIELDS | ||
+ | |||
+ | The first four fields, of the class " | ||
+ | control block in queues with other PCBs. Some general-purpose queue-handling | ||
+ | routines have been written to make queue management easier: | ||
+ | QueueInsert(), | ||
+ | in a doubly linked circular order. | ||
+ | a forward (" | ||
+ | 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 " | ||
+ | " | ||
+ | the queue, except that its " | ||
+ | " | ||
+ | time. The " | ||
+ | bumped back into the head node again, indicating the end of the list. The | ||
+ | " | ||
+ | empty or not. | ||
+ | |||
+ | All of the processes that are ready to execute in the system are kept in the | ||
+ | ready queue. | ||
+ | is also an active node in the queue (a small but harmless kluge). | ||
+ | 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 " | ||
+ | that is not stored on the process' | ||
+ | fields include space for the stack pointer, the stack page, the zeropage, and | ||
+ | the contents of the MMU register at location $d506. | ||
+ | what was in the SP register of the CPU when the process last paused. | ||
+ | stack page and the zeropage values are the values in MMU registers $d505 and | ||
+ | $d507, respectively; | ||
+ | that are allocated to a process. | ||
+ | common memory is disabled, for hardware reasons. | ||
+ | be in either RAM0 or RAM1 if there is a need later. | ||
+ | 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' | ||
+ | process' | ||
+ | |||
+ | ADDR | ||
+ | ---- | ||
+ | $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 " | ||
+ | a process if it executes an RTS from its main routine. | ||
+ | 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 " | ||
+ | address of the next instruction to be executed by the process when it is | ||
+ | activated. | ||
+ | first instruction. | ||
+ | because this is what a hardware interrupt pushes onto the stack, and this is | ||
+ | what the RTI instruction expects to find. | ||
+ | |||
+ | The " | ||
+ | loaded into the corresponding registers inside of the CPU when the process is | ||
+ | activated. | ||
+ | |||
+ | The "$ff00 save" is the value to be loaded into the $ff00 " | ||
+ | the MMU when the process is activated. | ||
+ | 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 " | ||
+ | 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 " | ||
+ | context. | ||
+ | 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 " | ||
+ | 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, | ||
+ | purposes. | ||
+ | |||
+ | 3.2.3. SCHEDULE-CLASS FIELDS | ||
+ | |||
+ | The next three fields, of class " | ||
+ | The " | ||
+ | to the scheme already discussed. | ||
+ | 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. | ||
+ | time quantum, the " | ||
+ | process. | ||
+ | for activation. | ||
+ | |||
+ | The " | ||
+ | absolute system time when the process should be activated again. | ||
+ | time in the system is kept in a 16-bit kernel variable, and wraps around every | ||
+ | 18.2 minutes. | ||
+ | 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 " | ||
+ | communication (message passing). | ||
+ | " | ||
+ | requests; the four " | ||
+ | queue of processes that are waiting to communicate with the current process, | ||
+ | and the " | ||
+ | to communicate with, if the current process is waiting. | ||
+ | actually overlap with each other and with the " | ||
+ | earlier. | ||
+ | information at the same time. The last field, " | ||
+ | this time, but will be used in the future for an primitive to receive a | ||
+ | message only from a specific process. | ||
+ | discussed in much greater detail later. | ||
+ | |||
+ | 3.2.5. PROC-CLASS FIELDS | ||
+ | |||
+ | The final two fields, of class " | ||
+ | the process. | ||
+ | the " | ||
+ | current process, and the return value for the MyParentPid() kernel call is | ||
+ | taken from this field. | ||
+ | |||
+ | The " | ||
+ | different possible process states: | ||
+ | |||
+ | STATE NAME CODE | ||
+ | --------------- | ||
+ | STATE_READY | ||
+ | STATE_SEND | ||
+ | STATE_RECEIVE | ||
+ | STATE_REPLY | ||
+ | STATE_DELAY | ||
+ | STATE_SUSPENDED | ||
+ | |||
+ | The STATE_READY state means that the process is in the ready queue. | ||
+ | STATE_SEND, STATE_RECEIVE, | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | and stack page must be allocated. | ||
+ | very simply, by keeping a page pointer and incrementing it every time a page | ||
+ | is allocated. | ||
+ | even though it actually requires much less than that. Thus, each new process | ||
+ | chews up 768 bytes of memory space (plus code). | ||
+ | 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' | ||
+ | identifier). | ||
+ | to conveniently locate the process control block. | ||
+ | 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. | ||
+ | follows: | ||
+ | |||
+ | FIELD SIZ | ||
+ | -------------- | ||
+ | pcbNext | ||
+ | pcbPrev | ||
+ | pcbIsHead | ||
+ | pcbQCount | ||
+ | pcbSP 1 | ||
+ | pcbStackPage | ||
+ | pcbZeroPage | ||
+ | pcbD506 | ||
+ | pcbPriority | ||
+ | pcbCountdown | ||
+ | pcbWakeupTime | ||
+ | pcbSendQHead | ||
+ | pcbSendQTail | ||
+ | pcbSendQFlag | ||
+ | pcbSendQCount | ||
+ | pcbBlockedOn | ||
+ | pcbReceiveFrom | ||
+ | pcbParent | ||
+ | pcbState | ||
+ | |||
+ | The values on the top of the stack page are also initialized for the new | ||
+ | process, as follows: | ||
+ | |||
+ | ADDR | ||
+ | ---- | ||
+ | $ff sp+09 exitaddr-1.h | ||
+ | $fe sp+08 exitaddr-1.l | ||
+ | $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 | ||
+ | $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. | ||
+ | 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. | ||
+ | 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. | ||
+ | system, but for a couple of design reasons, BOS actually has three " | ||
+ | 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, | ||
+ | |||
+ | 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' | ||
+ | IRQ (and NMI and BRK) handling. | ||
+ | process control block, save the zero-page, stack-page, stack-pointer, | ||
+ | $d506 registers into the PCB, and load a $00 into the zero-page MMU register | ||
+ | to switch to the kernel' | ||
+ | stored). | ||
+ | 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 execute the ROM stack-handling code for exiting from an interrupt. | ||
+ | that there' | ||
+ | process from the one that was active when the interrupt occurred. | ||
+ | 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. | ||
+ | EnterKernel(), | ||
+ | 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. | ||
+ | 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()). | ||
+ | possible to organize the kernel calls to be entered by executing a BRK | ||
+ | instruction, | ||
+ | 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. | ||
+ | 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(). | ||
+ | in and out very quickly; all we have to do is switch to the kernel' | ||
+ | and then switch back to the user's zeropage before exit. | ||
+ | |||
+ | There' | ||
+ | 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. | ||
+ | IRQ style of context switching (and there' | ||
+ | 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. | ||
+ | immediately to the calling process without rescheduling (without skipping to | ||
+ | the next ready process in line). | ||
+ | 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 " | ||
+ | field of the PCB for the current process. | ||
+ | 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. | ||
+ | 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) | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | this wraparound problem (although with 64-bit times, wraparound periods would | ||
+ | be expressed in millions of millennia rather than in minutes). | ||
+ | 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). | ||
+ | point. | ||
+ | done using 17 bits (well, really 24 bits). | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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. | ||
+ | bootstrapping, | ||
+ | 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 " | ||
+ | progress" | ||
+ | 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. | ||
+ | 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. | ||
+ | implementation, | ||
+ | application and then becomes the Commodore-Kernal Server, which is a | ||
+ | convenient organization, | ||
+ | of the Kernal Server merely by calling MyParentPid(). | ||
+ | |||
+ | 4. INTERPROCESS COMMUNICATION | ||
+ | |||
+ | In this system, processes are not strictly independent and competitive; | ||
+ | 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. | ||
+ | with the much-hyped Client/ | ||
+ | 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. | ||
+ | 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 | ||
+ | ----------- | ||
+ | Send ( .AY=msgHead ) .CS=err(.A=errcode) | ||
+ | Receive | ||
+ | Reply ( .AY=msgHead[msgRet, | ||
+ | |||
+ | 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' | ||
+ | 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 | ||
+ | --- | ||
+ | 0 | ||
+ | 2 | ||
+ | 4 | ||
+ | 8 | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | 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 " | ||
+ | involved in an RPC interaction. | ||
+ | that a message is to be/has been sent to, and the " | ||
+ | the process which a message has been received from. For security reasons, the | ||
+ | sender does not fill in the " | ||
+ | has been sent and the sender is blocked. | ||
+ | someone else). | ||
+ | message is being sent and must be filled in with a legitimate value on a send | ||
+ | operation, and the " | ||
+ | is being replied to, and must be filled in with a legitimate value on a reply | ||
+ | operation. | ||
+ | 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 " | ||
+ | memory and the sizes of each. The send buffer (" | ||
+ | to point to a region of near/far memory that contains valid data for a send | ||
+ | operation, and the reply buffer (" | ||
+ | to a valid area of memory for the server to fill in with any bulky result data | ||
+ | from an RPC request. | ||
+ | size to allow for future expansion when the kernel will support " | ||
+ | that will be accessed through 32-bit pointers. | ||
+ | access these " | ||
+ | memory. | ||
+ | from place to place. | ||
+ | |||
+ | There are two special notes to make about there " | ||
+ | don't actually have to be used how they' | ||
+ | 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. | ||
+ | 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 " | ||
+ | be a " | ||
+ | arbitrary number of pieces, scattered throughout the global memory of the | ||
+ | system. | ||
+ | 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. | ||
+ | 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. | ||
+ | copied only when necessary, and copied only once. | ||
+ | |||
+ | 4.1.1.3. DATA-CLASS FIELDS | ||
+ | |||
+ | The final four fields, of class " | ||
+ | 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 " | ||
+ | wishes a server to execute. | ||
+ | error code that is returned from the server to the client upon completion of | ||
+ | an operation. | ||
+ | indicate which of the server' | ||
+ | operation on. And the " | ||
+ | arbitrary user data that is passed in with an operation and is passed back | ||
+ | from the server to give return values. | ||
+ | data in all of the fields is send with a request, but only the data in the | ||
+ | " | ||
+ | the other fields are passed back in a reply operation (the field values will | ||
+ | remain how they were before the send, for the sender). | ||
+ | the " | ||
+ | than was asked for by an operation, you will have to encode the " | ||
+ | reply-buffer length into the " | ||
+ | |||
+ | 4.1.2. SEND | ||
+ | |||
+ | Send() is used to transmit a message to a remote process and get back a reply | ||
+ | message. | ||
+ | header, which must have its " | ||
+ | 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 " | ||
+ | allocated and indicated in the message header. | ||
+ | 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. | ||
+ | process is not valid, and that destination process died before receiving/ | ||
+ | replying to your message. | ||
+ | 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. | ||
+ | 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. | ||
+ | sending process will be returned in the .AY register as well as in the | ||
+ | " | ||
+ | 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. | ||
+ | received from other processes in FIFO order. | ||
+ | |||
+ | A similar ReceiveSpecific() primitive may be provided in the future. | ||
+ | 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. | ||
+ | return information in the reply-message-header buffer and the reply buffer | ||
+ | area according to the client' | ||
+ | The near address of the reply-message-header buffer is loaded into the .AY | ||
+ | register as an argument to the call. Only 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. | ||
+ | error code is loaded in the .A register. | ||
+ | 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 | ||
+ | --- | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | The " | ||
+ | instructed to send a reply message to is indeed waiting for a reply from the | ||
+ | task calling Reply(). | ||
+ | list of process control blocks that are waiting to send a message to the | ||
+ | current process. | ||
+ | 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. | ||
+ | the other process involved performs the corresponding operation, the first | ||
+ | process' | ||
+ | The " | ||
+ | |||
+ | The process states of STATE_SEND, STATE_REPLY, | ||
+ | message passing. | ||
+ | a message to a server process and is waiting for it to do a Receive(). | ||
+ | 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(). | ||
+ | state means that the current process has performed a Receive() and is waiting | ||
+ | for some other process to perform a corresponding Send(). | ||
+ | names/ | ||
+ | |||
+ | The implementation of the actual Send(), Receive(), and Reply() operations is | ||
+ | actually quite straight-forward. | ||
+ | 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. | ||
+ | in STATE_RECEIVE, | ||
+ | directly to the receive-header buffer of the recipient. | ||
+ | receive-header buffer is taken from the " | ||
+ | receiver' | ||
+ | (the sending process' | ||
+ | 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(). | ||
+ | sender' | ||
+ | block, the sender' | ||
+ | process' | ||
+ | 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(). | ||
+ | of an error. | ||
+ | reply too long (in which case the reply is truncated). | ||
+ | |||
+ | The Receive() primitive first checks its " | ||
+ | have already tried to send a message to the receiver. | ||
+ | there, the sender' | ||
+ | queue then the sender process' | ||
+ | message-header contents (dereferenced by the sender' | ||
+ | are copied into the receiver' | ||
+ | then exits returning the pid of the sender. | ||
+ | |||
+ | If there is no process enqueued in the recipient process' | ||
+ | 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. | ||
+ | message-header fields and awakens 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()). | ||
+ | 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. | ||
+ | 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. | ||
+ | and save into a file named " | ||
+ | ACE assembler to generate the executable program (which is also included below | ||
+ | for your convenience). | ||
+ | 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 | ||
+ | pcbPrev | ||
+ | pcbIsHead | ||
+ | pcbQCount | ||
+ | pcbSP = 06 ;(1) ctxt | ||
+ | pcbStackPage | ||
+ | pcbZeroPage | ||
+ | pcbD506 | ||
+ | pcbPriority | ||
+ | pcbCountdown | ||
+ | pcbWakeupTime | ||
+ | pcbWaitEvent | ||
+ | pcbSendMsgPtr | ||
+ | pcbRecvMsgPtr | ||
+ | pcbSendQHead | ||
+ | pcbSendQTail | ||
+ | pcbSendQFlag | ||
+ | pcbSendQCount | ||
+ | pcbBlockedOn | ||
+ | pcbReceiveFrom | ||
+ | pcbParent | ||
+ | pcbState | ||
+ | pcbSize | ||
+ | |||
+ | STATE_READY | ||
+ | STATE_SEND | ||
+ | STATE_RECEIVE | ||
+ | STATE_REPLY | ||
+ | STATE_DELAY | ||
+ | STATE_SUSPENDED = $c5 | ||
+ | STATE_EVENT | ||
+ | |||
+ | KERN_ERR_OK | ||
+ | KERN_ERR_PID_NOT_REPLY = $e1 | ||
+ | |||
+ | msgTo | ||
+ | msgFrom | ||
+ | msgBuf | ||
+ | msgLen | ||
+ | msgRepBuf | ||
+ | msgRepLen | ||
+ | msgOp = 16 ;(1) | ||
+ | msgRet | ||
+ | msgObj | ||
+ | msgData | ||
+ | msgSize | ||
+ | |||
+ | queueHeadSize | ||
+ | |||
+ | nullPcb | ||
+ | delayQueue : buf queueHeadSize | ||
+ | jiffyTime | ||
+ | |||
+ | activePid | ||
+ | p = 04 ;(2) | ||
+ | q = 06 ;(2) | ||
+ | pcbPtr | ||
+ | msgPtr | ||
+ | pageAlloc | ||
+ | |||
+ | ;Stack: ($ff) : exitaddr-1.h | ||
+ | ; | ||
+ | ; ($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 | ||
+ | bkSelect = $ff00 | ||
+ | vic = $d000 | ||
+ | sid = $d400 | ||
+ | mmuZeroPage | ||
+ | mmuStackPage = $d509 | ||
+ | IrqExit | ||
+ | |||
+ | ; Create | ||
+ | ; 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, | ||
+ | |||
+ | ;======== kernel code ======== | ||
+ | |||
+ | main = * | ||
+ | sei | ||
+ | ;** entry | ||
+ | lda #bkBOS | ||
+ | sta bkSelect | ||
+ | ;** set interrupt vectors | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta $0314 | ||
+ | sty $0315 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta $0316 | ||
+ | sty $0317 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta $0318 | ||
+ | sty $0319 | ||
+ | ;** initialize delay queue | ||
+ | lda #0 | ||
+ | sta jiffyTime+0 | ||
+ | sta jiffyTime+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta q+0 | ||
+ | sty q+1 | ||
+ | jsr QueueInit | ||
+ | ;** initialize null/boot process | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | 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 # | ||
+ | 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 | ||
+ | createPriority : buf 1 | ||
+ | createZeropage : buf 1 | ||
+ | createStack | ||
+ | createPcb | ||
+ | |||
+ | Create = * ;( .AY=address, | ||
+ | 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 + | ||
+ | | ||
+ | + | ||
+ | ;** initialize pcb | ||
+ | ;** pcbNext | ||
+ | ;** pcbPrev | ||
+ | ;** pcbIsHead | ||
+ | ;** pcbQCount | ||
+ | ;** pcbSP ;(1) ctxt := $f6 | ||
+ | ;** pcbStackPage | ||
+ | ;** pcbZeroPage | ||
+ | ;** pcbD506 | ||
+ | ;** pcbPriority | ||
+ | ;** pcbCountdown | ||
+ | ;** pcbWakeupTime | ||
+ | ;** pcbSendQHead | ||
+ | ;** pcbSendQTail | ||
+ | ;** pcbSendQFlag | ||
+ | ;** pcbSendQCount | ||
+ | ;** pcbBlockedOn | ||
+ | ;** pcbReceiveFrom | ||
+ | ;** pcbParent | ||
+ | ;** pcbState | ||
+ | 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 # | ||
+ | sta createPcb+pcbState | ||
+ | ldy #pcbSize-1 | ||
+ | - lda createPcb,y | ||
+ | sta (pcbPtr),y | ||
+ | dey | ||
+ | bpl - | ||
+ | lda pcbPtr+0 | ||
+ | clc | ||
+ | adc # | ||
+ | sta q+0 | ||
+ | lda pcbPtr+1 | ||
+ | adc #0 | ||
+ | sta q+1 | ||
+ | jsr QueueInit | ||
+ | |||
+ | ;** initialize new stack | ||
+ | ;** Stack: ($ff) : exitaddr-1.h | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | 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 #< | ||
+ | sta (p),y | ||
+ | iny | ||
+ | lda #> | ||
+ | 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 # | ||
+ | sta (pcbPtr),y | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | 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 # | ||
+ | - lda queueInitVals, | ||
+ | sta (q),y | ||
+ | dey | ||
+ | bpl - | ||
+ | rts | ||
+ | | ||
+ | |||
+ | QueueInsert = * ;( (q)=queueHead, | ||
+ | ;** q->count +:= 1 | ||
+ | clc | ||
+ | ldy #pcbQCount | ||
+ | lda (q),y | ||
+ | adc #1 | ||
+ | sta (q),y | ||
+ | |||
+ | ;** pcbPtr-> | ||
+ | ldy #pcbNext | ||
+ | lda (p),y | ||
+ | sta (pcbPtr),y | ||
+ | iny | ||
+ | lda (p),y | ||
+ | sta (pcbPtr),y | ||
+ | |||
+ | ;** pcbPtr-> | ||
+ | iny | ||
+ | lda p+0 | ||
+ | sta (pcbPtr),y | ||
+ | iny | ||
+ | lda p+1 | ||
+ | sta (pcbPtr),y | ||
+ | |||
+ | ;** p-> | ||
+ | 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-> | ||
+ | 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-> | ||
+ | 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 # | ||
+ | sta (activePid), | ||
+ | ldy #pcbSP | ||
+ | tsx | ||
+ | txa | ||
+ | sta (activePid), | ||
+ | ldy # | ||
+ | lda mmuStackPage | ||
+ | sta (activePid), | ||
+ | ldy #pcbD506 | ||
+ | lda $d506 | ||
+ | sta (activePid), | ||
+ | |||
+ | ;** process interrupt | ||
+ | inc jiffyTime+0 | ||
+ | bne + | ||
+ | inc jiffyTime+1 | ||
+ | + lda delayQueue+pcbQCount | ||
+ | beq + | ||
+ | jsr DelayIrqAwake | ||
+ | + nop | ||
+ | |||
+ | ;** select new process | ||
+ | - ldy # | ||
+ | lda (activePid), | ||
+ | iny | ||
+ | sta (activePid), | ||
+ | beq ++ | ||
+ | - ldy # | ||
+ | lda (activePid), | ||
+ | tax | ||
+ | iny | ||
+ | lda (activePid), | ||
+ | stx activePid+0 | ||
+ | sta activePid+1 | ||
+ | ExitKernel = * | ||
+ | ldy # | ||
+ | lda (activePid), | ||
+ | beq ++ | ||
+ | sec | ||
+ | sbc #1 | ||
+ | sta (activePid), | ||
+ | beq + | ||
+ | jmp - | ||
+ | + ;check if null process | ||
+ | ldy #pcbIsHead | ||
+ | lda (activePid), | ||
+ | bpl + | ||
+ | iny | ||
+ | lda (activePid), | ||
+ | bne -- | ||
+ | + ;we've got a winner | ||
+ | |||
+ | ;** restore full context and exit | ||
+ | ldy #pcbD506 | ||
+ | lda (activePid), | ||
+ | sta $d506 | ||
+ | ldy # | ||
+ | lda (activePid), | ||
+ | sta mmuStackPage | ||
+ | ldy #pcbSP | ||
+ | lda (activePid), | ||
+ | tax | ||
+ | txs | ||
+ | ldy # | ||
+ | lda (activePid), | ||
+ | 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), | ||
+ | pha | ||
+ | iny | ||
+ | lda (activePid), | ||
+ | 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 # | ||
+ | sta (activePid), | ||
+ | dey | ||
+ | lda mmuStackPage | ||
+ | sta (activePid), | ||
+ | dey | ||
+ | tsx | ||
+ | txa | ||
+ | sta (activePid), | ||
+ | ldy #pcbD506 | ||
+ | lda $d506 | ||
+ | sta (activePid), | ||
+ | ;** 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 " | ||
+ | lda activePid+0 | ||
+ | sta pcbPtr+0 | ||
+ | lda activePid+1 | ||
+ | sta pcbPtr+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | 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 # | ||
+ | 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 # | ||
+ | ldy # | ||
+ | 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 #< | ||
+ | ldy #> | ||
+ | sta q+0 | ||
+ | sty q+1 | ||
+ | sta p+0 | ||
+ | sty p+1 | ||
+ | jsr DelayFindSpot | ||
+ | jsr QueueInsert | ||
+ | jmp ExitKernel | ||
+ | | ||
+ | | ||
+ | |||
+ | DelayFindSpot = * ;( (q)=queue, (p)=queueHead, | ||
+ | jsr IncPtrP | ||
+ | ldy #pcbIsHead | ||
+ | lda (p),y | ||
+ | bne DelayFindSpotExit | ||
+ | ldy # | ||
+ | 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 | ||
+ | |||
+ | | ||
+ | ;xx fall through | ||
+ | |||
+ | DecPtrP = * ;( (p) ) : (p): | ||
+ | ldy #pcbPrev | ||
+ | lda (p),y | ||
+ | tax | ||
+ | iny | ||
+ | lda (p),y | ||
+ | stx p+0 | ||
+ | sta p+1 | ||
+ | rts | ||
+ | |||
+ | IncPtrP = * ;( (p) ) : (p): | ||
+ | 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 # | ||
+ | lda (pcbPtr),y | ||
+ | cmp jiffyTime+0 | ||
+ | beq + | ||
+ | rts | ||
+ | + iny | ||
+ | lda (pcbPtr),y | ||
+ | cmp jiffyTime+1 | ||
+ | beq + | ||
+ | rts | ||
+ | + lda #< | ||
+ | ldy #> | ||
+ | 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 # | ||
+ | lda msgPtr+0 | ||
+ | sta (pcbPtr),y | ||
+ | iny | ||
+ | lda msgPtr+1 | ||
+ | sta (pcbPtr),y | ||
+ | ldy # | ||
+ | lda q+0 | ||
+ | sta (pcbPtr),y | ||
+ | iny | ||
+ | lda q+1 | ||
+ | sta (pcbPtr),y | ||
+ | ldy #pcbState | ||
+ | lda (q),y | ||
+ | cmp # | ||
+ | beq SendToReceiverBlocked | ||
+ | lda #STATE_SEND | ||
+ | sta (pcbPtr),y | ||
+ | clc | ||
+ | lda q+0 | ||
+ | adc # | ||
+ | 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 # | ||
+ | sta (pcbPtr),y | ||
+ | ldy # | ||
+ | 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, | ||
+ | sta setretSave+2 | ||
+ | stx setretSave+1 | ||
+ | sty setretSave+0 | ||
+ | php | ||
+ | pla | ||
+ | and #$01 | ||
+ | sta setretSave+3 | ||
+ | ldy # | ||
+ | lda (pcbPtr),y | ||
+ | sta p+1 | ||
+ | ldy #pcbSP | ||
+ | lda (pcbPtr),y | ||
+ | clc | ||
+ | adc #2 | ||
+ | sta p+0 | ||
+ | ldy #3 | ||
+ | - lda setretSave, | ||
+ | sta (p),y | ||
+ | dey | ||
+ | bpl - | ||
+ | rts | ||
+ | |||
+ | CopyMessage = * ;( (pcbPtr)=sender, | ||
+ | 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 # | ||
+ | lda (activePid), | ||
+ | bne ReceiveFromSender | ||
+ | pla | ||
+ | sta mmuZeroPage | ||
+ | jsr EnterKernel | ||
+ | jsr SuspendSub | ||
+ | lda # | ||
+ | sta (pcbPtr),y | ||
+ | ldy # | ||
+ | lda msgPtrSave+0 | ||
+ | sta (pcbPtr),y | ||
+ | iny | ||
+ | lda msgPtrSave+1 | ||
+ | sta (pcbPtr),y | ||
+ | jmp ExitKernel | ||
+ | |||
+ | ReceiveFromSender = * ;( (activePid), | ||
+ | lda activePid+0 | ||
+ | ldy activePid+1 | ||
+ | clc | ||
+ | adc # | ||
+ | bcc + | ||
+ | iny | ||
+ | + sta q+0 | ||
+ | sty q+1 | ||
+ | ldy # | ||
+ | lda (activePid), | ||
+ | sta pcbPtr+0 | ||
+ | iny | ||
+ | lda (activePid), | ||
+ | sta pcbPtr+1 | ||
+ | jsr QueueUnlink | ||
+ | ldy # | ||
+ | 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 | ||
+ | ldy #pcbState | ||
+ | lda # | ||
+ | sta (pcbPtr),y | ||
+ | ldx pcbPtr+0 | ||
+ | ldy pcbPtr+1 | ||
+ | pla | ||
+ | sta mmuZeroPage | ||
+ | txa | ||
+ | cli | ||
+ | clc | ||
+ | rts | ||
+ | |||
+ | zpPtrSave : buf 1 | ||
+ | |||
+ | Reply = * ;( .AY=msgBuf[msgRet, | ||
+ | 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 # | ||
+ | beq + | ||
+ | - lda # | ||
+ | ldx zpPtrSave | ||
+ | stx mmuZeroPage | ||
+ | sec | ||
+ | cli | ||
+ | rts | ||
+ | + ldy # | ||
+ | lda (pcbPtr),y | ||
+ | cmp activePid+0 | ||
+ | bne - | ||
+ | iny | ||
+ | lda (pcbPtr),y | ||
+ | cmp activePid+1 | ||
+ | bne - | ||
+ | ;** copy the reply contents | ||
+ | ldy # | ||
+ | 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 #< | ||
+ | ldy #> | ||
+ | ldx #2 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | ldx #1 | ||
+ | jsr Create | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | 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 #< | ||
+ | ldy #> | ||
+ | sta testDelay1Msg+msgBuf+0 | ||
+ | sty testDelay1Msg+msgBuf+1 | ||
+ | - lda #<60 | ||
+ | ldy #>60 | ||
+ | jsr Delay | ||
+ | inc $581 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | TestDelay2 = * | ||
+ | jsr MyParentPid | ||
+ | sta testDelay2Msg+msgTo+0 | ||
+ | sty testDelay2Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta testDelay2Msg+msgBuf+0 | ||
+ | sty testDelay2Msg+msgBuf+1 | ||
+ | - lda #<120 | ||
+ | ldy #>120 | ||
+ | jsr Delay | ||
+ | inc $582 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | TestDelay3 = * | ||
+ | jsr MyParentPid | ||
+ | sta testDelay3Msg+msgTo+0 | ||
+ | sty testDelay3Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta testDelay3Msg+msgBuf+0 | ||
+ | sty testDelay3Msg+msgBuf+1 | ||
+ | - lda #<180 | ||
+ | ldy #>180 | ||
+ | jsr Delay | ||
+ | inc $583 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | TestDelay4 = * | ||
+ | jsr MyParentPid | ||
+ | sta testDelay4Msg+msgTo+0 | ||
+ | sty testDelay4Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta testDelay4Msg+msgBuf+0 | ||
+ | sty testDelay4Msg+msgBuf+1 | ||
+ | - lda #<240 | ||
+ | ldy #>240 | ||
+ | jsr Delay | ||
+ | inc $584 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | TestDelay5 = * | ||
+ | jsr MyParentPid | ||
+ | sta testDelay5Msg+msgTo+0 | ||
+ | sty testDelay5Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta testDelay5Msg+msgBuf+0 | ||
+ | sty testDelay5Msg+msgBuf+1 | ||
+ | - lda #<300 | ||
+ | ldy #>300 | ||
+ | jsr Delay | ||
+ | inc $585 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | Blabber1 = * | ||
+ | jsr MyParentPid | ||
+ | sta blabber1Msg+msgTo+0 | ||
+ | sty blabber1Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta blabber1Msg+msgBuf+0 | ||
+ | sty blabber1Msg+msgBuf+1 | ||
+ | - inc $580 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | Spinner1 = * | ||
+ | jsr MyParentPid | ||
+ | sta spinner1Msg+msgTo+0 | ||
+ | sty spinner1Msg+msgTo+1 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | sta spinner1Msg+msgBuf+0 | ||
+ | sty spinner1Msg+msgBuf+1 | ||
+ | - inc $580 | ||
+ | lda #< | ||
+ | ldy #> | ||
+ | jsr Send | ||
+ | jmp - | ||
+ | | ||
+ | |||
+ | 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 | ||
+ | spinner1Msg | ||
+ | ksMsg = spinner1Msg+msgSize | ||
+ | -----=----- | ||
+ | |||
+ | APPENDIX B. UUENCODED DEMO PROGRAM | ||
+ | |||
+ | The uuencoded demo system follows. | ||
+ | with version 2.00 or higher of " | ||
+ | |||
+ | -nucode-begin 1 bos | ||
+ | begin 640 bos | ||
+ | M`!-, | ||
+ | M_ZEVH!6-%`., | ||
+ | MA`< | ||
+ | M`HT-$UA, | ||
+ | M`XP5`ZE`H/ | ||
+ | M````````````````````````````````````````> | ||
+ | M$ZD`C0? | ||
+ | MK> | ||
+ | M$Y$(B!# | ||
+ | MRM# | ||
+ | M" | ||
+ | M_Q2@!; | ||
+ | MI061" | ||
+ | ML0B%!: | ||
+ | M!F# | ||
+ | MU9$" | ||
+ | M`H4# | ||
+ | M!K$" | ||
+ | MC0? | ||
+ | M/ | ||
+ | MU9$" | ||
+ | MA0+(L0B%`Z`: | ||
+ | M# | ||
+ | M%4S1%0`````@4!> | ||
+ | MK0X7\02M# | ||
+ | MA0B$": | ||
+ | M%XR, | ||
+ | M!I$(R*4' | ||
+ | M3-$5J< | ||
+ | M`````(T9& | ||
+ | M8*`" | ||
+ | MC0? | ||
+ | ML0*%", | ||
+ | M" | ||
+ | MJ>& | ||
+ | ML0J1!, | ||
+ | MJ2.@& | ||
+ | MJ6B@& | ||
+ | MC034J0" | ||
+ | MNDRY& | ||
+ | M+" | ||
+ | MC!T, | ||
+ | M15-3(# | ||
+ | M& | ||
+ | M# | ||
+ | M0T534R`T# | ||
+ | M`AO(22P@5$A)4R!)4R!$14Q!62!04D]# | ||
+ | M? | ||
+ | MC)$, | ||
+ | M(" | ||
+ | (H`P@U1A, | ||
+ | ` | ||
+ | end | ||
+ | -nucode-end 1 2258 1430bdc2 | ||
+ | |||
+ | ========================================================================END=== | ||
+ | </ |
magazines/chacking10.txt · Last modified: 2015-04-17 04:34 by 127.0.0.1