10717 lines
411 KiB
Plaintext
10717 lines
411 KiB
Plaintext
########
|
|
##################
|
|
###### ######
|
|
#####
|
|
##### #### #### ## ##### #### #### #### #### #### #####
|
|
##### ## ## #### ## ## ## ### ## #### ## ## ##
|
|
##### ######## ## ## ## ##### ## ## ## ## ##
|
|
##### ## ## ######## ## ## ## ### ## ## #### ## ##
|
|
##### #### #### #### #### ##### #### #### #### #### #### ######
|
|
##### ##
|
|
###### ###### Issue #10
|
|
################## June 30, 1995
|
|
########
|
|
|
|
------------------------------------------------------------------------------
|
|
Editor's Notes
|
|
by Craig Taylor
|
|
|
|
This is my last issue of Commodore Hacking (having finally gotten out the
|
|
door, but I couldn't break tradition and get it out on time :-) ). I'm having
|
|
to give it up because I've gradually lost interest in Commodore computers over
|
|
the years and with the search for a job (anyone wanna hire a csc graduate?)
|
|
and as I get older I seem to have less and less time.
|
|
|
|
I'm gonna be handing the reigns of Commodore Hacking over to Jim Brain, who is
|
|
a very active member of the Commodore Internet community. He will also be
|
|
running a mailserver that will take the place of mine (Mine will become
|
|
unavailable after July 1st and will send pointers to Jim Brain's mailserver).
|
|
|
|
It's been interesting to watch the Commodore computers evolve, take off like a
|
|
rocket and then have Commodore go into liquidation. Commodore computers have
|
|
been and still are, (with some exceptions - 1541 head-banging comes to mind),
|
|
technologically sound. For a "hacking" machine they're wonderful.
|
|
|
|
My email address has changed to duck@nando.net. I periodically still check
|
|
mail at duck@pembvax1.pembroke.edu but only every 2 weeks or so. I am still
|
|
going to try to be in the Commodore community but time will govern my ability
|
|
to do that. I'm going to miss editing this rag....
|
|
|
|
And here is Jim Brain:
|
|
|
|
Mail Server Changes:
|
|
|
|
With Issue 10, the address for the Commodore Hacking mail server has changed.
|
|
The new address is brain@mail.msen.com The commands are the same as before.
|
|
Not all of the files have been moved yet, so please email the administrator
|
|
(Jim Brain, brain@mail.msen.com) if a file you need is not on the new site.
|
|
|
|
Howdy:
|
|
Howdy, my name is Jim Brain, and I will be taking over the position of
|
|
editor for Commodore Hacking starting with Issue 11. Some of you may know
|
|
me as the Commodore Trivia Contest administrator, the USENET newsgroup
|
|
comp.sys.cbm FAQ Manitainer, or the keeper of a Commodore Information
|
|
WWW Site at http://www.msen.com/~brain/cbmhome.html. Wherever you have
|
|
heard of me from, or even if you haven't, I will state that I plan on
|
|
handling Commodore Hacking in the following way. The next issue will
|
|
possibly look different cosmetically, as I edit somewhat differently than
|
|
Craig, but the content and basic layout will remain the same. The types
|
|
of material will not change, and the structure for submitting articles will
|
|
change only in the address to mail them to: brain@mail.msen.com. However,
|
|
I do have a few changes in mind:
|
|
|
|
1) Try to stabilize the issue generation so that Commodore Hacking will
|
|
become a quarterly publication.
|
|
|
|
2) Attempt a fully "HTML"ized version of the magazine, while still providing a
|
|
text version.
|
|
|
|
3) Pursue the possibility of providing a printed version of these issues
|
|
for those who have no online access to them.
|
|
|
|
4) Encourage User Groups and other CBM related organizations to carry this
|
|
magazine for their members.
|
|
|
|
So, again I say howdy. As always, Commodore Hacking will accept your
|
|
articles at any time, so please help us keep this quality magazine running.
|
|
If you have any questions or comments about the change in editorship, the
|
|
possible changes, or other matters, please feel free to drop me a note.
|
|
I look forward to hearing from you and publishing your articles.
|
|
|
|
Jim Brain
|
|
brain@mail.msen.com
|
|
===========================================================================
|
|
Legal Mumbo-Jumbo
|
|
|
|
Permission is granted to re-distribute this "net-magazine", in whole,
|
|
freely for non-profit use. However, please contact individual authors for
|
|
permission to publish or re-distribute articles separately. A charge of no
|
|
greater than 5 US dollars or equivlent may be charged for library service /
|
|
diskettte costs for this "net-magazine".
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
Please note that this issue and prior ones are available via anonymous FTP
|
|
from ccnga.uwaterloo.ca (among others) under /pub/cbm/hacking.mag and via a
|
|
mailserver which documentation can be obtained by sending mail to
|
|
"brain@mail.msen.com" with a subject line of "mailserver" and the
|
|
lines of "help" and "catalog" in the body of the message.
|
|
|
|
-------------------------------------------------------------------------------
|
|
In This Issue:
|
|
|
|
Commodore Trivia
|
|
|
|
Trivia Edition #13-18 are in this article. As you may know, these questions
|
|
form part of a contest in which the monthly winner gets a prize (Thanks to my
|
|
various prize donators). The whole thing is mainly just for fun, so please
|
|
enjoy. Try your hand at Commodore trivia!!
|
|
|
|
BFLI - New graphics modes 2
|
|
|
|
FLI gave us more color to the screen, AFLI increased the horizontal
|
|
resolution and color selection by using the hires mode. BFLI stands
|
|
for 'Big FLI' and gives us 400 lines instead of the usual two
|
|
hundred. AFLI and BFLI can be combined, but we are not going into
|
|
that.
|
|
|
|
Making stable raster routines (C64 and VIC-20)
|
|
|
|
In this article, I document two methods of creating stable raster
|
|
routines on Commodore computers. The principles apply for most 8-bit
|
|
computers, not only Commodores, but raster effects are very rarely
|
|
seen on other computers.
|
|
|
|
A Differant Perspective - Part III.
|
|
|
|
Yes!!! It's yet another article on 3D graphics! Even if you
|
|
haven't been following this series, you can use this program. This
|
|
time around we will write a completely general polygon plotter --
|
|
if you can type basic data statements, you can create a three-dimensional
|
|
object out of polygons and rotate and project it to your heart's content.
|
|
For the more technically inclined we will look at optimizations to the
|
|
line routine, EOR-buffer filling, and more! Yow!
|
|
|
|
Second SID Chip Installation
|
|
|
|
This article describes how to add a second sid chip for use in SidPlayer and
|
|
other programs. As always, be extra careful when making modifications to your
|
|
computer.
|
|
|
|
Solving Large Systems of Linear Equations on a C64 Without Memory
|
|
|
|
OK, now that I have your attention, I lied. You can't solve dense
|
|
linear systems of equations by direct methods without using memory to
|
|
store the problem data. However, I'll come back to this memory free
|
|
assertion later. The main purpose of this article is to rescue a
|
|
usefull numerical algorithm, "Quartersolve", and also to provide a brief
|
|
look at the COMAL programming language and BLAS routines.
|
|
|
|
The World of IRC - A New Life for the C64/128
|
|
|
|
I've heard people talking about IRC. What is it? Why is it useful to me as a
|
|
Commodore user? Bill "Coolhand" Lueck explains the hows and whys in this
|
|
article.
|
|
|
|
SwiftLink-232 Application Notes (version 1.0b)
|
|
|
|
This information is made available from a paper document published by CMD,
|
|
with CMD's permission.
|
|
|
|
Design and Implementation of a Simple/Efficient Upload/Download Protocol
|
|
|
|
This article details how to implement a custom upload/download protocol that
|
|
is faster than most of the ones common to the C64/128 computers.
|
|
|
|
Design and Implementation of a 'Real' Operating System for the 128: Part II
|
|
|
|
There has been a slight change in plans. I originally intended this
|
|
article to give the design of a theoretical distributed multitasking
|
|
microkernel operating systemfor the C128. I have decided to go a
|
|
different route: to take out the distributed component for now and implement
|
|
a real multitasking microkernel OS for a single machine and extend the system
|
|
to be distributed later. The implementation so far is, of course, only in
|
|
the prototype stage and the application for it is only a demo. Part III of
|
|
this series will extend this demo system into, perhaps, a usable distributed
|
|
operating system.
|
|
========================================================================
|
|
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. However, the trivia still keeps coming. I appreciate
|
|
all the people who contribute to the trivia and all the people who take
|
|
part in the monthly contest. I have collected Trivia Edition #13-18 in this
|
|
article. As you may know, these questions form part of a contest in which
|
|
the monthly winner gets a prize (Thanks to my various prize donators).
|
|
The whole thing is mainly just for fun, so please enjoy.
|
|
|
|
As the summer months start up, news on the trivia includes:
|
|
|
|
1) I now have access to some more orphan machines (C65, C116), so expect
|
|
some trivia questions on those models.
|
|
|
|
2) The new home now has a number of machines set up, so testing answers to
|
|
the trivia is even easier. I am still trying to get the old PET
|
|
machines in house, but the others are here.
|
|
|
|
3) The Commodore World Wide Web Pages (http://www.msen.com/~brain/cbmhome.html)
|
|
that I maintain and place the trivia on caught the eye of USA Today and
|
|
the Pheonix Gazette. I was interviewed for both articles. Look in the
|
|
June 20th edition of USA Today for the segment, and possibly a picture of
|
|
Jim Brain and the machines he uses to create the trivia.
|
|
|
|
As always, I welcome any questions (with answers), and encourage people
|
|
to enter their responses to the trivia, now at #18.
|
|
|
|
|
|
Jim.
|
|
|
|
|
|
The following article contains the answers to the December edition of trivia
|
|
($0C0 - $0CF), the questions and answers for January ($0D0 - $0DF),
|
|
February ($0E0 - $0EF), March ($0F0 - $0FF), April ($100 - $10F), and the
|
|
questions for the May edition ($110 - $11F). Enjoy them!
|
|
|
|
|
|
Here are the answers to Commodore Trivia Edition #13 for December, 1994
|
|
|
|
Q $0C0) The early 1541 drives used a mechanism developed by ______. Name
|
|
the company.
|
|
|
|
A $0C0) Alps.
|
|
|
|
Q $0C1) On later models, Commodore subsequently changed manufacturers
|
|
for the 1541 drive mechanism. Name the new manufacturer.
|
|
|
|
A $0C1) Newtronics.
|
|
|
|
Q $0C2) What is the most obvious difference(s). (Only one difference is
|
|
necessary)
|
|
|
|
A $0C2) Alps: push-type latch, round LED.
|
|
Newtronics: lever-type latch, rectangular LED.
|
|
|
|
Q $0C3) On Commodore BASIC V2.0, what answer does the following give:
|
|
PRINT (SQR(9)=3)
|
|
|
|
A $0C3) 0. According to Commodore BASIC, the answer should bby -1, which
|
|
is the BASIC value of TRUE. However, the above equation is NOT
|
|
true. Doing PRINT SQR(9) yields 3, but doing PRINT (SQR(9)-3)
|
|
yields 9.31322575E-10 (C64). This anomaly can be attributed to
|
|
roundoff errors in the floating point math routines in Commodore BASIC.
|
|
|
|
Q $0C4) In Commodore BASIC (Any version) what does B equal after the following
|
|
runs: C=0:B=C=0
|
|
|
|
A $0C4) B = -1. The second statement is the one to look at. The second
|
|
equals sign is treated as a comparison, while the first is treated
|
|
as a assignment. B gets set to the outcome of the comparison, which
|
|
is TRUE (-1).
|
|
|
|
Q $0C5) The first PET cassette decks were actually _______ brand cassette
|
|
players, modified for the PET computers. Name the comapny.
|
|
|
|
A $0C5) Sanyo. Specifically, Model M1540A. What a model number!
|
|
|
|
Q $0C6) In Commodore BASIC (Any version), what happens if the following
|
|
program is run:
|
|
|
|
10 J=0
|
|
20 IF J=0 GO TO 40
|
|
30 PRINT "J<>0"
|
|
40 PRINT "J=0"
|
|
|
|
A $0C6) On BASIC 2.0 or greater:
|
|
|
|
?SYNTAX ERROR IN 20
|
|
READY.
|
|
|
|
On BASIC 1.0: (found on the PET 2001 series)
|
|
|
|
J=0
|
|
READY.
|
|
|
|
BASIC 1.0 totally ignored spaces, so line 20 became "IFJ=0GOTO40".
|
|
That statement would be correctly parsed, sicne it contains the "GOTO"
|
|
keyword.
|
|
|
|
However, on BASIC 2.0 or greater, spaces weren't ignored so
|
|
completely, and the "TO" in "GO TO" would be tokenized separately, so
|
|
some code was added to BASIC to check to "GO". As the code that
|
|
accepts GOTO as a special case for THEN after an IF statement wasn't
|
|
patched this way, the above fails, because GO is not a valid keyword
|
|
after IF. The statement SHOULD work correctly, but does not because
|
|
of this failure to fix the IF command parsing.
|
|
|
|
On BASIC 2.0 or greater, substituting the following line for line 20
|
|
will cause the program to work:
|
|
|
|
20 IF J=0 THEN GO TO 40
|
|
|
|
Q $0C7) In question $068, we learned how Jack Tramiel first happened upon the
|
|
name "COMMODORE". According to the story, though, in what country
|
|
was he in when he first saw it?
|
|
|
|
A $0C7) Germany.
|
|
|
|
Q $0C8) On the Commodore user port connector, how many edge contacts are
|
|
there?
|
|
|
|
A $0C8) 24. Two rows of 12 contacts each.
|
|
|
|
Q $0C9) On most Commodore computers, a logical BASIC screen line can contain
|
|
up to 80 characters. On what Commodore computer(s) is this not true?
|
|
|
|
A $0C9) According to Commodore documentation, a _physical_ screen line is
|
|
defined as one screen line of characters. A _logical_ screen line is
|
|
defined as how many _physical_ lines can be chained together to
|
|
create a valid BASIC program line.
|
|
|
|
With that in mind, most Commodore computers chose a _logical_
|
|
screen line that was a multiple of the screen width. This works fine
|
|
for 40 and 80 column screens, but what do we do with the VIC-20, with
|
|
its 22 column screen. Solution: make the _logical_ line length equal
|
|
to 4 _physical_ lines, or 88 columns.
|
|
|
|
When the Commdore 128 was introduced, the number rose to 160
|
|
characters, which is 4 _physical_ lines in 40 column mode, or
|
|
2 _physical_ lines in 80 column mode. However, you can only take
|
|
advantage of this in 128 mode. 64 mode is limited to 80 characters.
|
|
|
|
To add to all this confusion, a valid BASIC program line (in memory)
|
|
can actually be 255 (tokenized) characters long, but creating such
|
|
a long line cannot be done from the built-in editor in direct mode.
|
|
|
|
The AmigaBASIC, available on the Amiga, also does not have the 80
|
|
column line limit. However, that BASIC is SOOO much different that
|
|
I am not surprised. The older CBM BASICs, on the other hand, were
|
|
all derivatives of the original Level 1 BASIC for the PET.
|
|
|
|
Q $0CA) If a file is saved to a Commodore Disk Drive with the following
|
|
characters: chr$(65);chr$(160);chr$(66), what will the directory
|
|
entry look like?
|
|
|
|
A $0CA) The filename will show up as "A"B, with the 'B' showing up to the
|
|
right of the '"' mark. This could be used to make program loading
|
|
easier. A file that showed up as "filename",8,1 could be loaded
|
|
by simply hitting shift-run/stop on that line.
|
|
|
|
Q $0CB) What is the maximum length (in characters) of a CBM datasette
|
|
filename?
|
|
|
|
A $0CB) References I have on hand say 128 characters. However, the actual
|
|
code on the 8032 and the C64 acts as though 187 characters can
|
|
actually be sent (tape buffer-5 control bytes = 192-5=187). The
|
|
references that claim 128 characters are Nick Hampshire's
|
|
_The VIC Revealed_ and _The PET Revealed_. ANyone care to lay
|
|
this one to rest?
|
|
|
|
Q $0CC) How many keys are on a stock Commodore 64 keyboard?
|
|
|
|
A $0CC) 66 keys. This is the same number as found on the VIC-20 and the
|
|
Commodore 16.
|
|
|
|
Q $0CD) Commodore BASIC uses keyword "tokens" to save program space. Token
|
|
129 becomes "FOR". What two tokens expand to include a left
|
|
parenthesis as well as a BASIC keyword?
|
|
|
|
A $0CD) TAB( (163) and SPC( (166).
|
|
|
|
Q $0CE) There are 6 wires in the Commodore serial bus. Name the 6 wires.
|
|
|
|
A $0CE) 1) Serial /SRQIN
|
|
2) GND
|
|
3) Serial ATN IN/OUT
|
|
4) Serial CLK IN/OUT
|
|
5) Serial DATA IN/OUT
|
|
6) /RESET
|
|
|
|
Q $0CF) On the Commodore datasette connector, how many logical connections are
|
|
there?
|
|
|
|
A $0CF) 6. Opposing pins on the connector are hooked together electrically.
|
|
|
|
|
|
Here are the answers to Commodore Trivia Edition #14 for January, 1995
|
|
|
|
Q $0D0) How many keys were there on the "original" PET and what was special
|
|
about them?
|
|
|
|
A $0D0) the original PET had 73 calculator-style keys that were laid out
|
|
in a rectangular matrix, not typewriter-style.
|
|
|
|
Q $0D1) How do you produce the "hidden" message(s) on the Commodore 128?
|
|
|
|
A $0D1) SYS 32800,123,45,6. The screen will clear, and the software
|
|
and hardware developers on the 128 project will be named.
|
|
|
|
The exact text is as follows:
|
|
|
|
[RVS] Brought to you by...
|
|
|
|
Software:
|
|
Fred Bowen
|
|
Terry Ryan
|
|
Von Ertwine
|
|
|
|
Herdware:
|
|
Bil Herd
|
|
Dave Haynie
|
|
Frank Palaia
|
|
|
|
[RVS]Link arms,don't make them.
|
|
|
|
Q $0D2) How much memory did the "original" PET show on bootup?
|
|
|
|
A $0D2) The "original" PET came in two configurations, 4K and 8K, so:
|
|
|
|
The PET 2001-4 had 3071 bytes.
|
|
The PET 2001-8 had 7167 bytes.
|
|
|
|
Q $0D3) We all know the "reboot" sys for the 64 is sys 64738, but who knows
|
|
the same sys location to reboot the CBM 8032?
|
|
|
|
A $0D3) sys 64790
|
|
|
|
Q $0D4) Which computer(s) beeped at bootup? (May be more than one, but only
|
|
one required)
|
|
|
|
A $0D4) I know some of these are corect, but the sheer size of the
|
|
list prevents me from checking them ALL out.
|
|
|
|
FAT 40XX series
|
|
80XX series
|
|
PC-10 (I suspect a number of IBM clones did, and these things have
|
|
no consistent naming convention across country boundaries.)
|
|
PC-20
|
|
Amiga 1000
|
|
SP9000 (SuperPET)
|
|
|
|
Q $0D5) How much memory did the CBM 8032 show on bootup?
|
|
|
|
A $0D5) 31743 bytes.
|
|
|
|
Q $0D6) Certain Commodore computers provided emtpy EPROM sockets on the
|
|
motherboard. Give me the number of empty sockets on the following
|
|
machines:
|
|
|
|
a) CBM 30XX.
|
|
b) CBM 8XXX.
|
|
c) CBM C128.
|
|
d) Plus/4.
|
|
|
|
A $0D6) a) 3 sockets.
|
|
b) 2 sockets.
|
|
c) 1 socket.
|
|
d) 1 socket.
|
|
|
|
Q $0D7) In Germany, the CBM 8032 came with a 4kB EPROM for the EXXX area,
|
|
while the US version only had a 2kB EPROM. Why?
|
|
|
|
A $0D7) The German version had additional keybaord drivers for umlaut
|
|
characters and dead keys.
|
|
|
|
Q $0D8) Who published the first PET memory map in the "PET Gazette"?
|
|
|
|
A $0D8) None other than the infamous Jim Butterfield.
|
|
|
|
Q $0D9) Which is faster to move the sursor on a PET/CBM or C64: SYS or
|
|
PRINT?
|
|
|
|
A $0D9) PRINT is faster, since the sys approach must process the pokes
|
|
before the sys, which are very slow.
|
|
|
|
Q $0DA) On the Amiga 1000, where are the signatures of the first Amiga
|
|
developers located?
|
|
|
|
A $0DA) Inside the top case of the Amiga (1000).
|
|
|
|
There is an interesting footnote to this question. It seems
|
|
that at least some original Amiga machines were labeled as
|
|
Amiga (with nu number). Then, at some later point, the number was
|
|
added. In addition, Commodore produced some Amiga 1000 machines
|
|
without the signatures, but most had the telltale handwriting on
|
|
the inside of the case.
|
|
|
|
Q $0DB) On the 6502, what does the accumulator contain after the following
|
|
is executed:
|
|
|
|
lda #$aa
|
|
sed
|
|
adc #01
|
|
|
|
A $0DB) Assume carry was clear. If so, then $11 is the correct answer.
|
|
|
|
Q $0DC) What is the model number of the US NTSC VIC-II chip?
|
|
|
|
A $0DC) Its first number was 6567, and that is the number most people know
|
|
it by, but Commodore produced a VIC-II using a new manufacturing
|
|
process that was numbered the 8562.
|
|
|
|
Q $0DD) What is the European PAL VIC-II chip's model number?
|
|
(Not sure if that's its rightful term, but I hope you understand).
|
|
|
|
A $0DD) Same here. The part number 6569 is the most remembered number, but
|
|
an 8565 will work as well.
|
|
|
|
Q $0DE) Assume you have two computers, one with each of the above chips inside.
|
|
Which chip draws more pixels on the screen per second?
|
|
|
|
A $0DE) Note, for the purposes of the calculation I am performing, "pixels"
|
|
refers to picture elements that can be adddress and modified using
|
|
normal VIC modes, so there are 320*200 "pixels" on both the PAL
|
|
and NTSC screens. (I probably should have stated this, but it is
|
|
too late now.) Also, the screen refresh rates used in the
|
|
calculations are those defined by the respective television
|
|
standards (60Hz U.S., 50Hz European), even though the actual
|
|
frequencies are off by a small percentage. (for example, the actual
|
|
50Hz refresh rate on European VIC-II chips was calculates as
|
|
50.124567Hz by Andreas Boose)
|
|
|
|
So, the PAL draws 320*200*50 pixels per second = 3200000 pixels/s
|
|
NTSC draws 320*200*60 pixels per second = 3840000 pixles/s
|
|
|
|
Now, some people thought I meant the whole screen, not just the
|
|
display area provided by the VIC-II chip. Well, I am not sure
|
|
exactly you calculate pixels on a screen, since the numbers could
|
|
vary from display to display, but if we measure in scanlines:
|
|
|
|
PAL = 312 scanlines * 50 = 15600 scanlines/s
|
|
NTSC = 262 scanlines * 60 = 15720 scanlines/s
|
|
|
|
The NTSC machines wins both ways.
|
|
|
|
Q $0DF) In Commodore BASIC, which statement executes faster:
|
|
|
|
a = 2--2
|
|
|
|
or
|
|
|
|
a = 2+2
|
|
|
|
A $0DF) b is the correct answer, and there are a couple of reasons why:
|
|
|
|
1) 2--2 takes longer to parse in the BASIC interpreter.
|
|
2) Commodore BASIC subtracts by complementing the sign of the
|
|
second number and adding. This incurs extra time.
|
|
|
|
There are even more subtle ones, but I leave them as an
|
|
exercise for the reader. Send me your reason why.
|
|
|
|
|
|
Here are the answers to Commodore Trivia Edition #15 for February, 1995
|
|
|
|
Q $0E0) What is the difference(s) between the Newtronics 1541 and the 1541C?
|
|
(only one difference is needed)
|
|
|
|
A $0E0) (George Page, a noted authority on CBM Drives, indicated that Commodore
|
|
made this a tough question to answer.) By the time the 1541C was
|
|
introduced, Commodore threw a number of drives together and called
|
|
them 1541Cs. The theoretical 1541C exhibited the following
|
|
features:
|
|
|
|
No head banging, and other problems fixed by modified ROMs.
|
|
Case color matches C64C and C128 computers.
|
|
|
|
Q $0E1) What happens when you type 35072121 in direct mode on the C64 and
|
|
hit return?
|
|
|
|
A $0E1) Simple answer: Most likely, the screen clears and the word READY.
|
|
is printed at screen top. This is the behavior seen when pressing
|
|
RUN-STOP/RESTORE. Alternately, nothing could happen, or the computer
|
|
could lock up.
|
|
|
|
Involved answer: There is a bug in BASIC 2.0. Easily fixed, but
|
|
destined to live life immortal. (long)
|
|
|
|
The bug is in the PETSCII number to binary conversion routine at
|
|
$a69b (LINGET). The routine basically reads in a character from the
|
|
line, multiplies a partial result by 10 and adds the new character
|
|
to the partial result. Here is a code snippet:
|
|
|
|
a96a rts
|
|
a96b ldx #$00 ; zero out partial result
|
|
a96d stx $14
|
|
a96f stx $15
|
|
a971 bcs $a96a ; not a number, return
|
|
a973 sbc #$2f ; PETSCII to binary
|
|
a975 sta $07
|
|
a977 lda $15 ; get hi byte or partial result
|
|
a979 sta $22
|
|
a97b cmp #$19 ; partial > 6399
|
|
a97d bcs $a953 ; yes, goto error
|
|
a97f lda $14 ; load lo byte of result
|
|
a981 asl ; lo*2
|
|
a982 rol $22 ; hi*2 + c
|
|
a984 asl ; lo*2
|
|
a985 rol $22 ; hi*2 + c
|
|
a987 adc $14 ; complete lo*5
|
|
a989 sta $14
|
|
a98b lda $22
|
|
a98d adc $15 ; complete hi*5
|
|
a98f sta $15
|
|
a991 asl $14 ; lo*2 complete lo*10
|
|
a993 rol $15 ; hi*2 complete hi*10
|
|
a995 lda $14
|
|
a997 adc $07 ; add new char
|
|
a999 sta $14
|
|
a99b bcc $a99f ; did lo overflow?
|
|
a99d inc $15 ; yes, inc hi
|
|
a99f jsr $0073 ; get next char
|
|
a9a2 jmp $a971 ; go through it again.
|
|
|
|
The problem is at $a97d. when the partial result is greater than 6399,
|
|
(if partial > 6399, then new partial result will be over 63999)
|
|
the routine needs to get to $af08 to print an error, but can't due to
|
|
branch restrictions. However, a branch that will get there is in the
|
|
preceding function, which handles the ON GOTO/GOSUB keywords ($a94b,
|
|
ONGOTO).
|
|
|
|
So, the BASIC writers just branched to the code in ONGOTO; specifically
|
|
$a953:
|
|
|
|
a94b jsr $b79e
|
|
a94e pha
|
|
a94f cmp #$8d ; is the keyword GOSUB ($8d)
|
|
a951 beq $a957 ; yes
|
|
a953 cmp #$89 ; is the keyword GOTO ($89)
|
|
a955 bne $a8e8 ; no, print SYNTAX ERROR.
|
|
a957 ... ; handle ON GOTO/GOSUB
|
|
|
|
This code is checking to make sure the ON (var) is followed with a
|
|
GOTO or GOSUB keyword.
|
|
|
|
The LINGET error handler branches to $a953, which compares
|
|
.A (which holds hi byte of partial result) to $89. Normally, this
|
|
fails, and the normal SYNTAX ERROR code is reached through the branch
|
|
to $a8e8. However, for partial results of the form $89XX, the check
|
|
succeeds, and BASIC tries to execute an ON GOTO/GOSUB call.
|
|
|
|
By the way, it is no coincidence that this error occurs on 35072121,
|
|
since one of the partial results is $8900 (hi byte is $89). In fact,
|
|
350721 will achieve the same result.
|
|
|
|
If the check succeeds, the code limps along until $a96a:
|
|
|
|
a969 pla ; complement to $a94e
|
|
a96a rts ; return
|
|
|
|
But we never executed $a94e, the push, so the stack is now
|
|
messed up. Since the stack held $9e, $79, $a5 before the PLA,
|
|
(The stack could hold other values, but I always saw these)
|
|
the RTS gets address $a579 to return to, which usually holds a BRK
|
|
opcode. The break handler is invoked, and the screen clears with the
|
|
READY. at the top.
|
|
|
|
Now, the BASIC 2.0 authors were justified in reusing the error
|
|
handler code in ONGOTO for LINGET, but they calculated the branch
|
|
offset wrong, according to my tests. If you have the LINGET error
|
|
handler branch to $a955, all these troubles disappear. You can
|
|
verify this procedure with the following BASIC program on a 64:
|
|
|
|
10 for t=57344 to 65535:poke t,peek(t):next
|
|
20 for t=40960 to 49151:poke t,peek(t):next
|
|
30 poke 43390, 214
|
|
40 poke 1, peek(1) and 254
|
|
|
|
Just to be complete, this error occurs when a 6 digit or greater line
|
|
number is entered and the first 6 digits indicate a number in the
|
|
range 35072-35327 ($8900-$89ff). Also, it appears the error occurs
|
|
on the VIC-20, but I didn't completely verify it. It would be
|
|
interesting to note if the error is found on all version of CBM BASIC.
|
|
|
|
Whew, what a mouthful.
|
|
|
|
Q $0E2) If a SID chip is producing a "sawtooth waveform", does the waveform look
|
|
like:
|
|
|
|
a) "/|/|/|/|" or
|
|
b) "|\|\|\|\" ?
|
|
|
|
A $0E2) a is the correct answer.
|
|
|
|
Q $0E3) On BASIC 2.0, what special precaution(s) must one take when working with
|
|
relative files? (only one is needed)
|
|
|
|
A $0E3) Because BASIC 2.0 doesn't handle positioning in relative files quite
|
|
right, one must position the relative file pointer before AND AFTER
|
|
a read or write to a relative file.
|
|
|
|
Q $0E4) What incompatibility existed between C128 Rev. 0 ROMS and the REU?
|
|
|
|
A $0E4) OK, I admit it. I placed this answer and its discussion somewhere
|
|
in my store of information, and it must have fallen behind the
|
|
cabinet, because I cannot find it. I will post an answer to this
|
|
as soon as I can find it, but the answers really must go out, as
|
|
they have been held up long enough.
|
|
|
|
Q $0E5) What can trigger an NMI interrupt? (count all sources on one chip as
|
|
one)
|
|
|
|
A $0E5) The following sources can trigger an NMI interrupt:
|
|
|
|
1) The expansion port.
|
|
2) CIA #2.
|
|
3) The RESTORE key.
|
|
|
|
Q $0E6) What can trigger an IRQ interrupt? (count all sources on one chip as
|
|
one)
|
|
|
|
A $0E6) The following sources can trigger an IRQ interrupt:
|
|
|
|
1) The VIC-II chip.
|
|
2) CIA #1.
|
|
3) The expansion port.
|
|
|
|
Q $0E7) Where is the ROM in a 1541 located in the 64K memory map?
|
|
|
|
A $0E7) The ROM is located from $C000 to $FFFF, yet the ROM code does not
|
|
begin until $C100.
|
|
|
|
Q $0E8) Which VIA on the 1541 is hooked to the read/write head?
|
|
|
|
A $0E8) VIA #2, located in memory from $1C00 to $1C0E.
|
|
|
|
Q $0E9) In the Commodore DOS, what bit in the file type byte denotes a "locked"
|
|
file?
|
|
|
|
A $0E9) bit 6.
|
|
|
|
Q $0EA) If files are "locked" under Commodore DOS, under what condition(s) may
|
|
the file be changed?
|
|
|
|
A $0EA) Depending on the file, the following operations can be done on a
|
|
locked file:
|
|
|
|
1) Rename will change file name, although not contents of file.
|
|
2) Random access can be used to alter file.
|
|
3) Formatting the disk will alter the file. (duh!)
|
|
4) Save-with-replace (@0:) will replace file and unlock it.
|
|
5) Opening file in append mode will allow it to be changed, and
|
|
unlock it.
|
|
6) Opening a relative file and adding or changing a record will
|
|
succeed and unlock file.
|
|
|
|
Q $0EB) How big can a program file be on a 1541 or similar?
|
|
|
|
A $0EB) The file can be as large as a sequential file, since both are stored
|
|
in the same way: 168656 bytes. However, since a program contains its
|
|
load address as bytes 0 and 1, the largest program size is 168654
|
|
bytes.
|
|
|
|
Q $0EC) Under BASIC 2.0, how does one open a random access file on a disk
|
|
drive?
|
|
|
|
A $0EC) Random access (or direct access) files are a misnomer. What you
|
|
really doing is opening the disk for reading and writing. You need
|
|
two open command to access a random file: (assume drive 8)
|
|
|
|
open 15,8,15 and
|
|
|
|
open 1,8,4,"#1" will open a random access file using buffer 1.
|
|
open 1,8,4,"#" will open a random access file using the first
|
|
available buffer
|
|
|
|
Now, by using B-R, B-W, B-A or their replacements, you can write
|
|
data to sectors on the disk.
|
|
|
|
Note that Random access files are different from relative files.
|
|
|
|
Q $0ED) A file that has a '*' immediately before the filetype is called
|
|
a _________ file.
|
|
|
|
A $0ED) a splat file. This is its correct term, believe it or not.
|
|
|
|
Q $0EE) We know the 1541 and similar drives have 5 internal buffer areas, but
|
|
how many does an 8050 drive have?
|
|
|
|
A $0EE) Since the 8050 has twice the on-board RAM (4kB), it has 16 buffers, but
|
|
only 13 are available. (All CBM drives use one buffer for zero-page
|
|
memory, one for stack memory, and one for temporary variables.)
|
|
|
|
Q $0EF) On a "save-with-replace", where is the location of the first track and
|
|
sector of the new copy of the program saved in the directory entry for
|
|
the old copy?
|
|
|
|
A $0EF) The new first track is stored at location 26, and the new first sector
|
|
is stored at location 27. These values are copied to their
|
|
correct locations after the save is completed.
|
|
|
|
|
|
Here are the answers to Commodore Trivia Edition #16 for March, 1995
|
|
|
|
Q $0F0) What size matrix of pixels comprises a character on a PET 2001
|
|
computer?
|
|
|
|
A $0F0) The matrix was 8 by 8.
|
|
|
|
Q $0F1) How many bytes did the opening screen on a CBM 4016 show as
|
|
available for use by BASIC?
|
|
|
|
A $0F1) 15359 bytes free.
|
|
|
|
Q $0F2) The character set that produces uppercase letters on unshifted keys
|
|
is the ________________ character set.
|
|
|
|
A $0F2) "standard mode".
|
|
|
|
Q $0F3) The character set that produces lowercase letters on unshifted keys
|
|
is the ________________ character set.
|
|
|
|
A $0F3) "alternate mode"
|
|
|
|
Q $0F4) To get to the set mentioned in $F2, what character code would be
|
|
printed to the screen?
|
|
|
|
A $0F4) chr$(142)
|
|
|
|
Q $0F5) What character code would one print to the screen to invoke the
|
|
chararacter set in $F3?
|
|
|
|
A $0F5) chr$(14)
|
|
|
|
Q $0F6) If one does LIST 60-100, will line 100 get "listed"?
|
|
|
|
A $0F6) Yes. The above translates as: LIST 60 through to and including 100.
|
|
|
|
Q $0F7) The abbreviation for the BASIC 4.0 command "COLLECT" is ________.
|
|
|
|
A $0F7) coL. "C" "O" "SHIFT-L". For those who are interested, the
|
|
COLLECT command is analogous to the VALIDATE operation.
|
|
|
|
Q $0F8) When you use a subscripted variable in BASIC, how many elements
|
|
are created by default if no DIM statement is issued?
|
|
|
|
A $0F8) 11 elements. A(0) - A(10). Almost everyone who has ever programmed
|
|
in Commodore BASIC has seen the "BAD SUBSCRIPT" error when they try
|
|
to use the 12th element in a un-DIMensioned array.
|
|
|
|
Q $0F9) How large is the keyboard buffer in CBM computers?
|
|
|
|
A $0F9) 10 bytes. Since this area could be POKEd to, many boot programs
|
|
would poke characters into this buffer to simulate keypresses.
|
|
|
|
Q $0FA) On the Commodore 1581, how large is a physical sector in bytes?
|
|
|
|
A $0FA) A physical sector is 512 bytes in length. Internally, the 1581
|
|
creates 2 256 "logical" sectors in a physical sector, to maintain
|
|
compatibility with older Commodore drives.
|
|
|
|
Q $0FB) You'll find BASIC 3.5 on the _____________ line of CBM computers.
|
|
|
|
A $0FB) The X64 series. That includes the Commodore 16, the Commodore 116,
|
|
and the Commodore Plus/4.
|
|
|
|
Q $0FC) On the Commodore 1351 mouse, what registers in the Commodore
|
|
computer would the X and Y proportional information be read
|
|
from?
|
|
|
|
A $0FC) Even though you are looking for digital information (how far the
|
|
mouse has traveled since the last movement in a particular axis),
|
|
the information is read from the "paddle" or potentiometer (POT)
|
|
registers. On the C64, the POT registers are part of the SID
|
|
chip, and are at 54297 ($D419) for POTX, and 54298 ($D41A) for
|
|
POTY.
|
|
|
|
Q $0FD) What is the maximum size of a sequential file on a 1581 drive?
|
|
|
|
A $0FD) 802640 bytes.
|
|
|
|
Q $0FE) What flaw exists in the early Commodore 1670 modems?
|
|
|
|
A $0FE) When the 1670 modem was first introduced, it powered up in auto-
|
|
answer mode, which means it would answer incoming calls after
|
|
the phong rang. You could turn this feature off through software
|
|
control, but if the power was reset, the modem would answer the
|
|
phone. So many people complained to Commodore that CBM revised
|
|
the 1670 to include an extra DIP switch that turned this feature
|
|
off.
|
|
|
|
Q $0FF) What is the model number of the first modem for the VIC and C64?
|
|
|
|
A $0FF) The 1600 manual dial/manual answer 0-300 bps modem. The author
|
|
owns one, and used it for many years. To operate, you must use
|
|
a phone with a detachable handset cord. You dialed the number
|
|
on the phone, waited for the answer, unplugged the handset, and
|
|
plugged the cord into the 1600. A switch toggled between using
|
|
originate or answer frequencies. The 1600 was manufactured by
|
|
Anchor Automation for Commodore. (As an aside, this unit claimed
|
|
300 bps, but I never could get 300 to work well. Most of my
|
|
telecommunications happened at 150 bps.)
|
|
|
|
|
|
-------Commodore Trivia Edition #17 Questions and Answers (BEGIN)--------
|
|
|
|
Q $100) On the MOS Technology's KIM-1, how many keys were on the keypad?
|
|
|
|
A $100) 23 keys. The keypad has room for 24, but one spot is taken by
|
|
a switch that puts the system into single-step mode. Interestingly,
|
|
some pictures have the switch on the upper left, some on the upper
|
|
right.
|
|
|
|
Q $101) The KIM-1 keypad had the common 0-9A-F keys on the keypad, but
|
|
also had some special keys. Name them.
|
|
|
|
A $101) GO (Go) Executes an instruction and displays the address of next,
|
|
ST (Stop) Stops execution of program and return control to monitor,
|
|
RS (Reset),
|
|
AD (Address) Address entry mode,
|
|
DA (Data) Data entry mode,
|
|
PC (Program Counter) Displays and restores program counter to values
|
|
in PCL and PCH,
|
|
+ (Increment) Increments the address without changing the entry mode.
|
|
|
|
Q $102) The KIM-1 was a set of modules that could be plugged together to
|
|
expand the system. Each module had a model number. What was the
|
|
model number of the KIM-1 motherboard?
|
|
|
|
A $102) The KIM-4.
|
|
|
|
Q $103) On the 1525 line of printers, if you wanted to create the following
|
|
graphic, what bytes would you send to the printer after turning on
|
|
graphics mode?
|
|
|
|
****
|
|
* *
|
|
* *
|
|
* *
|
|
* *
|
|
* *
|
|
****
|
|
|
|
A $103) I guess I should have stipulated that this is a bitmap. ASCII just
|
|
has a few limitations. Anyway, the correct bytes to send are:
|
|
255, 193, 193, 255. You got these by assigning each bit in a column
|
|
a value, and adding 128 to the result for each column.
|
|
|
|
Q $104) What is the horizontal resolution of the 1525 line of printers?
|
|
|
|
A $104) Character resolution: 80 chars, or 10 chars/inch (cpi).
|
|
Graphics resolution: 480 dots, or 60 dots/inch (dpi).
|
|
|
|
Q $105) On Commodore drives, explain the difference between the B-R command
|
|
and the U1 command.
|
|
|
|
A $105) The two commands read in data from a disk sector. However, the
|
|
U1 command always reads a full sector (255 bytes). The B-R
|
|
command reads the number of bytes specified in the first byte of
|
|
the sector. If the first byte is a 15, B-R will read 15 bytes
|
|
from the sector. (From the 1581 manual)
|
|
|
|
Q $106) On the Commodore 1541 drive, what does the U: command do?
|
|
|
|
A $106) This command has been traditionally used to reset Commodore drives,
|
|
including the CBM 1541. However, some early versions of the Drive
|
|
DOS did not correctly handle this command. In these versions, the
|
|
drive and computer failed to complete the command transaction
|
|
successfully, and what looked like a hung machine resulted.
|
|
Commodore later fixed this problem. If U: seems to not work on
|
|
your drive, try U; instead.
|
|
|
|
Q $107) What does the first routine in the 1541 drive ROM actually do?
|
|
|
|
A $107) The function, called SETLDA and residing at $C100, turns on the
|
|
drive active LED for the current drive. The routine loads the
|
|
current drive from $7F and sets bit 3 of DSKCNT ($1C00).
|
|
|
|
Q $108) How many files will a 1581 disk drive hold?
|
|
|
|
A $108) 296 files. Note that it is not a multiple of 144.
|
|
|
|
Q $109) Commodore 1581 drives have a special "autoboot" feature that enables
|
|
the drive to load and run a program off a disk upon drive bootup.
|
|
What is the required name of the file?
|
|
|
|
A $109) COPYRIGHT CBM 86
|
|
|
|
Q $10A) What filetype must the file mentioned in $109 be?
|
|
|
|
A $10A) USR.
|
|
|
|
Q $10B) To power up a 1351 mouse in "joystick mode", what must the user do?
|
|
|
|
A $10B) If one depresses the right mouse button during power-up, the 1351
|
|
will behave just like a joystick.
|
|
|
|
Q $10C) Describe the contents of the POTX or POTY registers when using a
|
|
1351 mouse.
|
|
|
|
A $10C) Each register holds the same type of information, just for a
|
|
separate axis, so we will describe just one register:
|
|
|
|
Bit: Function
|
|
|
|
7 Don't care
|
|
6-1 Mouse axis position mod 64.
|
|
0 Noise Bit. (check this bit to see whether mouse has moved)
|
|
|
|
Q $10D) Commodore computers typically use most of zero page for temporary
|
|
variables and other items. However, both the VIC-20 and the 64
|
|
reserve 4 bytes for user programs that need zero page memory. Where
|
|
are these locations?
|
|
|
|
A $10D) $FB-$FE (251-254). I am not sure these were "reserved" for
|
|
programmers as much as they were just not utilized by the
|
|
CBM programmers.
|
|
|
|
Q $10E) Name the 16 colors available on the 64.
|
|
|
|
A $10E) Black
|
|
White
|
|
Red
|
|
Cyan (Light Blue-Green)
|
|
Purple
|
|
Green
|
|
Blue
|
|
Yellow
|
|
Orange
|
|
Brown
|
|
Light Red
|
|
Dark Gray (Gray 1)
|
|
Medium Grey (Gray 2)
|
|
Light Green
|
|
Light Blue
|
|
Light Gray (Gray 3)
|
|
|
|
Q $10F) Both the VIC-20 and the C64 emulate the operation of the 6551 UART.
|
|
How many "mock 6551" registers are mapped into the memory map?
|
|
|
|
A $10F) 5, from $293-$297 (659-663). The register contents:
|
|
|
|
$293 6551 Control Register
|
|
$294 6551 Command Register
|
|
$295-6 6551 User Defined Baud Rate value.
|
|
$297 6551 Status Register
|
|
|
|
|
|
------------Commodore Trivia Edition #18 Questions (BEGIN)--------------
|
|
|
|
Q $110) What is the name of the company that recently purchased the
|
|
liquidated Commodore assets?
|
|
|
|
Q $111) At one time, Commodore attempted to manufacture a dual drive
|
|
version of the 1571 called the 1572. For what technical reason
|
|
did it utimately fail?
|
|
|
|
Q $112) Over what computer system did a User Group sue Commodore and win?
|
|
|
|
Q $113) In $103, the question asked how to create a graphic of a small box
|
|
on the 1525. In this quesrtion, we have made a different design.
|
|
If you wanted to create the following graphic using individual
|
|
dots on the printer, what bytes would you send to the printer after
|
|
turning on graphics mode?
|
|
|
|
** * *
|
|
* ***
|
|
* ** ***
|
|
* * * * *
|
|
** ** * *
|
|
* *
|
|
**
|
|
|
|
Q $114) (Some C65 questions) How many SID chips does the the development
|
|
Commodore 65 machine contain?
|
|
|
|
Q $115) What CPU does the Commodore 65 use?
|
|
|
|
Q $116) What is the alternate name for the Commodore 65?
|
|
|
|
Q $117) How many processors does the internal 1581-compatible drive
|
|
on the C65 contain?
|
|
|
|
Q $118) In the tradition of naming certian ICs after famous cartoon
|
|
characters, one of the ICs in the C65 is named after a Warner
|
|
Brothers cartoon character. Which one?
|
|
|
|
Q $119) What version of BASIC is included on the Commodore 65 in C65 mode?
|
|
|
|
Q $11A) How many I/O ports does a Commodore 65 contain?
|
|
|
|
Q $11B) What common Commodore 64 I/O port does the C65 NOT have?
|
|
|
|
Q $11C) How many function keys are on a Commodore 65?
|
|
|
|
Q $11D) What CBM disk drive DOS was used as the template for the internal
|
|
C65 drive DOS?
|
|
|
|
Q $11E) What resolution of text screen does the C65 power up in? (Please
|
|
give answers in characters).
|
|
|
|
Q $11F) What distinguishing non-textual characteristic in the C65 is not
|
|
present in othe Commodore 8-bit computers?
|
|
|
|
The information in this between the lines marked by (BEGIN) and (END)
|
|
is copyright 1995 by Jim Brain. Provided that the information
|
|
between the (BEGIN) and (END) lines is not changed except to correct
|
|
typographical errors, the so marked copyrighted information may be
|
|
reproduced in its entirety on other networks or in other mediums. For
|
|
more information about using this file, please contact the address
|
|
shown below.
|
|
|
|
Jim Brain
|
|
brain@mail.msen.com
|
|
602 North Lemen
|
|
Fenton, MI 48430
|
|
(810) 737-7300 x8528
|
|
|
|
Some are easy, some are hard, try your hand at:
|
|
Commodore Trivia #18!
|
|
========================================================================
|
|
BFLI - New graphics modes 2
|
|
by Pasi 'Albert' Ojala <albert@cs.tut.fi>
|
|
|
|
One day I was watching some demos that used linecrunch routines for
|
|
whole-screen multicolor-graphics upscrollers. I already had my
|
|
theories about how and why linecrunch worked, but because I had not
|
|
used it anywhere, the details were a bit vague. In fact, I have
|
|
many times accidentally created linecrunch effects when trying to do
|
|
something else with $D011. Probably every demo coder has.
|
|
|
|
But you learn by doing. I had the idea of using linecrunch for FLI
|
|
instead of a simple multicolor picture as it always seemed to be
|
|
used. However, this has probably been done before and because I
|
|
don't like to do things that have been done before, I decided to use
|
|
linecrunch to show a two-screen-tall FLI picture.
|
|
|
|
|
|
_Linecrunch Basics_
|
|
|
|
For those not familiar with linecrunch routines: linecrunch is used
|
|
to scroll the screen UPWARDS by convincing VIC-II that it has
|
|
already showed more character rows than it in reality has shown.
|
|
Surprisingly (or then, maybe not :) this consists of fiddling with
|
|
$D011. The timing is critical as always.
|
|
|
|
Linecrunch works by setting $D011 equal the line before the current
|
|
line and VIC-II will happily think that it is time to move on to the
|
|
next character row - add 40 to the video matrix counter, 320 to the
|
|
graphics memory counter and be ready to start a bad line. Or, maybe
|
|
'NOT to go back to the current row' would be a more suitable
|
|
description. (Programming VIC-II is slowly becoming a science.)
|
|
|
|
The required timing also does not cause bad lines so that you can
|
|
skip another line immediately on the successive line. In addition,
|
|
lines can be skipped only after the first character row and half of
|
|
the second character row have been displayed. This has something to
|
|
do with the way VIC-II decides when there is a bad line.
|
|
|
|
Because linecrunch causes VIC-II to skip rows, it will run out of
|
|
video matrix and color memory (and graphics memory) before reaching
|
|
the end of the screen. However, VIC-II does not stop displaying the
|
|
graphics nor does it reset the internal counters. The counters keep
|
|
on running and wrap around instead.
|
|
|
|
Normally, when VIC-II is displaying the last character row, it is
|
|
showing the memory from offsets $3c0 to $3e7. If VIC-II has skipped
|
|
one character row, it is displaying from $3e8 to $40f instead. But,
|
|
there are only 10 bits for the video matrix counter (0..1023), so it
|
|
wraps around to zero after $3ff. This means that the beginning of
|
|
the video matrix is displayed at the bottom of the screen. The
|
|
character rows become shifted by 24 character positions to the right
|
|
because there were originally 24 unused memory locations at the end
|
|
of the memory (1000..1023). (To be honest, sprite image pointers
|
|
are not unused memory, but they are not used with normal FLI.)
|
|
|
|
____________________ ____________________
|
|
|abcdefghijklmnopqrst| |abcdefghijklmnopqrst|
|
|
| | |--------------------| <- Skipped row
|
|
: : : :
|
|
: : : :
|
|
: : : :
|
|
| | |normally last line |
|
|
|normally last line | |XXXXXXXXZZZZabcdefgh|
|
|
`--------------------' `--------------------'
|
|
X = unused mem (1000..1015)
|
|
Z = sprite pointers (1016..1023)
|
|
|
|
Figure 1: Linecrunch
|
|
|
|
|
|
The same thing happens for color memory because it uses the same
|
|
counter for addressing the memory (in fact, color memory access and
|
|
character data access are performed simultaneosly, 12 bits at a
|
|
time). The graphics memory behaves the same way, except that the
|
|
counter has three bits more and it counts at eight times the speed,
|
|
so that it wraps at the exact same time as the other counter.
|
|
|
|
The first character row can't be used for linecrunch and the second
|
|
one is also lost in the process. The first usable line to display
|
|
is the third character row. However, those two lost rows can still
|
|
be used as an extension at the end of the first screen. You must
|
|
notice, however, that the alignment has been changed. After these
|
|
two rows have been displayed, the video bank is switched to get new
|
|
fresh data on the screen.
|
|
|
|
|
|
_Back to BFLI_
|
|
|
|
Wrapped data is nothing difficult to work with. It is just the
|
|
matter of writing the right conversion program. Also, the normal
|
|
FLI routine can be used, we just have to make sure VIC always has
|
|
the right bank visible - simple LDA bank,x:sta $DD00 can accomplish
|
|
that. The more difficult aspect is to make the display freely
|
|
locatable. We have 32 kilobytes of graphics data, this is the main
|
|
reason we can't even think about using copying. Linecrunch combined
|
|
with the bad line delaying technique will do the job much more
|
|
nicely.
|
|
|
|
Figure 2 shows the principles. To make things simpler I have chosen
|
|
location 0 to mean that the top of the picture is visible, 1 means
|
|
that the picture is scrolled one line upwards and so on. We can see
|
|
that linecrunch is not used at all for the location 0. To make the
|
|
picture start at the same point whether linecrunch has crunched
|
|
lines or not we compensate the non-lost raster lines by delaying the
|
|
next bad line. When the location is n*8 (n=0,1,2..), the sum of the
|
|
linecrunched and delayed lines is constant - the graphics display
|
|
always starts at the same point.
|
|
|
|
Then how do we deal with the location values that are not evenly
|
|
dividable by eight ? Now, lets assume that the location is L, and
|
|
we have C, which is the location divided by eight (C = L/8), and R,
|
|
which is the remainder (R = L%8). To make the picture scroll to the
|
|
right position, we need to delay the bad line less than before - R
|
|
lines less for location L than for location C*8. E.g. for location
|
|
2 we delay the bad line two lines less than for location 0. This
|
|
also shows that we need 7 lines more than is needed for to
|
|
compensate for the linecrunch.
|
|
|
|
Determining the number of linecrunch lines is a recursive process,
|
|
because when you use more linecrunch lines, that decreases the
|
|
number of lines you have available for the display and you need
|
|
bigger range for the location value. The linecrunch can be started
|
|
after 12 lines, and we need at least 7 lines to use the soft
|
|
y-scroll. This makes 181 lines available for the display
|
|
originally.
|
|
|
|
Because we need to show 400 lines of graphics, we would need
|
|
(400-181)/8=28 linecrunch lines. However, this in turn reduces the
|
|
number of lines we have for graphics to 181-28=153 and we need
|
|
(400-153)/8=31 linecrunch lines. Again, 181-31 is 150. We get
|
|
(400-150)/8=32 and there it finally converges and we have 149 lines
|
|
for graphics, which makes location values 0..251 valid.
|
|
|
|
|
|
Location 0 1 2 .. 8 9 .. 251
|
|
|
|
___________________.. ___________.. ________
|
|
___________________.. ___________.. ________
|
|
Linecrunch -------------------.. ___________..
|
|
^ ^ ^
|
|
| | | ^ ^
|
|
| | | | |
|
|
Bad line delayed| | | | |
|
|
| | | | | ========
|
|
| | v | | 244
|
|
| v ___.. | v :
|
|
v ________0 v ___.. :
|
|
Gfx Enabled ________0_______1__.. ________8__.. 250_____
|
|
0 1 2 8 9 251
|
|
1 2 3 9 10 252
|
|
2 3 4 10 11 253
|
|
3 4 5 11 12 254
|
|
4 5 6 12 13 255
|
|
5 6 7 13 14 256
|
|
6 7 8 14 15 257
|
|
7 8 9 15 16 258
|
|
: : : : : :
|
|
: : : : : :
|
|
148 149 150.. 156 157.. 399
|
|
|
|
Figure 2: Linecrunch and DMA delay in BFLI
|
|
(Graphics lines not in scale)
|
|
|
|
|
|
_Clipping added_
|
|
|
|
Now we can scroll the picture to any location we want, but the top
|
|
of the picture is not clipped and it is very annoying to watch. We
|
|
need to enable the graphics at the same point regardless of the
|
|
y-scroll value. The answer is in the extended color mode (ECM).
|
|
|
|
When both ECM and multicolor mode (MCM) are selected, VIC-II will
|
|
turn the display to black. This is because there is a conflicting
|
|
situation and it just can't decide which color scheme to use. The
|
|
video accesses will continue to happen just like before, the data is
|
|
just not displayed. When the ECM bit is cleared again, the normal
|
|
multicolor graphics is shown.
|
|
|
|
So, we set the ECM bit and start to display the first eight lines of
|
|
the FLI. Because the FLI routine already writes to $D011, we just
|
|
make sure the ECM bit is set in the first R number of writes to
|
|
$D011 and zero in all other.
|
|
|
|
The viewer is now 'complete'. You can take a look at the code below
|
|
or you can get C64Gfx1_4.lha and see it in action yourself and not
|
|
just rely on my word. The package includes converter programs for
|
|
BFLI, FLI and Koala (ANSI-C), couple of example pictures and viewers
|
|
for PAL and NTSC machines.
|
|
|
|
-Pasi 'Albert' Ojala albert@cs.tut.fi
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
BFLI viewer program for PAL machines
|
|
|
|
UPOS = $C00 ; temporary area for tables
|
|
BANK = $D00 ; UPOS for linecrunch, BANK for FLI bank select
|
|
RASTER = 29 ; where to position the sprite -> IRQ 20 lines later
|
|
DUMMY = $FFF ; dummy location for timing purposes
|
|
FLISZ = 19-1 ; visible FLI size in character rows - 1
|
|
|
|
*= $810
|
|
SEI
|
|
LDA #$7F:STA $DC0D ; IRQ setup
|
|
LDA #1:STA $D01A
|
|
STA $D015:STA KEYW+1
|
|
LDA #<IRQ:STA $314
|
|
LDA #>IRQ:STA $315
|
|
LDA #RASTER:STA $D001:CLC:ADC #20:STA $D012
|
|
LDA #0:STA $D017
|
|
LDA #0:STA 2
|
|
JSR NEWPOS ; Init the FLI routines
|
|
LDA #$A:STA $D011 ; Blank screen
|
|
LDX #23 ; Init tables
|
|
BLOOP LDA #$94:STA BANK,X
|
|
LDA #$96:STA BANK+24,X
|
|
DEX:BPL BLOOP
|
|
LDX #15
|
|
LOOP0 LDA YINIT,X:AND #$77 ; Change to $37 to better see the
|
|
STA UPOS,X ; workings of the routines
|
|
STA UPOS+16,X
|
|
STA UPOS+32,X
|
|
DEX:BPL LOOP0
|
|
|
|
LDA #$34:STA 1 ; Copy to the last video bank
|
|
LDA #$80:STA SRC+2 ; from $8000-$BFFF to $C000-$FFFF
|
|
LDA #$C0:STA DST+2
|
|
LDX #0:LDY #$40
|
|
SRC LDA $8000,X
|
|
DST STA $C000,X
|
|
INX:BNE SRC
|
|
INC SRC+2:INC DST+2
|
|
DEY:BNE SRC
|
|
LDA #$37:STA 1
|
|
|
|
LDX #0 ; Init color memory
|
|
LP LDA $3C00,X:STA $D800,X ; All 1024 bytes are used
|
|
LDA $3D00,X:STA $D900,X ; - some even twice!
|
|
LDA $3E00,X:STA $DA00,X
|
|
LDA $3F00,X:STA $DB00,X
|
|
INX:BNE LP
|
|
LDA $DC0D:CLI
|
|
|
|
KEYW LDX #0:BNE KEYW ; Wait for space to be pressed
|
|
SEI ; System to normal
|
|
LDA #$37:STA 1
|
|
JSR $FDA3
|
|
LDA #$97:STA $DD00
|
|
JSR $E5A0
|
|
LDY #3
|
|
IRQL LDA $FD30,Y:STA $314,Y
|
|
DEY:BPL IRQL
|
|
|
|
LDX #0:LDA #1 ; Clear color memory
|
|
CLL STA $D800,X:STA $D900,X
|
|
STA $DA00,X:STA $DB00,X
|
|
INX:BNE CLL
|
|
CLI:RTS
|
|
|
|
YINIT BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F
|
|
BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F
|
|
|
|
*=*-<*+256
|
|
|
|
IRQ LDA #$18:STA $D016:LDX #0:LDA #$5A
|
|
INC DUMMY:DEC DUMMY ; Synchronization
|
|
STX $D020:STX $D021:STA $D011
|
|
|
|
LDA #$15:STA $D018
|
|
LDA #$97:STA $DD00
|
|
LDX #44 ; Wait for the 4th line
|
|
LL DEX:BPL LL:NOP
|
|
LDX #0
|
|
|
|
LOOP3 NOP ; Linecrunch-part routine
|
|
LDA UPOS+6,X:INC DUMMY:STA $D011
|
|
NOP:NOP:INC DUMMY
|
|
NOP:NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP:NOP
|
|
INX
|
|
E1 CPX #$10:BNE LOOP3 ; Skip that many character rows-4
|
|
BIT $EA
|
|
LOOP4 LDA UPOS,X:INC DUMMY:STA $D011
|
|
NOP:NOP:NOP:INC DUMMY
|
|
NOP:NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP:LDA #0
|
|
INX
|
|
E2 CPX #$1F:BNE LOOP4 ; Delay DMA until we are at the
|
|
; 'same place' each time
|
|
|
|
LDA #0:STA $D020 ; Now wait for the bad line and start FLI
|
|
BIT $EA:NOP
|
|
NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP
|
|
NOP:NOP:NOP:NOP
|
|
B0 LDA #$92:STA $DD00:NOP ; The right video bank
|
|
|
|
; Wait for 0-7 lines to set the ECM mode off
|
|
; (makes the graphics visible)
|
|
|
|
F0 LDA #0:STA $D011:LDA #$08:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F1 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F2 LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F3 LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F4 LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F5 LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F6 LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
F7 LDA #0:STA $D011:LDA #$78:STA $D018
|
|
LDX #FLISZ:NOP:NOP:NOP:BIT $EA
|
|
|
|
; Do FLI 18 more character rows
|
|
|
|
F8 LDA #0:STA $D011:LDA #$08:STA $D018
|
|
B1 LDA BANK,X:STA $DD00:BIT $EA
|
|
F9 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FA LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FB LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FC LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FD LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FE LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA
|
|
FF LDA #0:STA $D011:LDA #$78:STA $D018:NOP:NOP:DEX:BMI EFLI:JMP F8
|
|
EFLI NOP
|
|
LDA #$FC
|
|
WL CMP $D012:BNE WL
|
|
INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY
|
|
INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY
|
|
INC DUMMY:INC DUMMY:STA $D020
|
|
|
|
JSR NEWPOS ; Update the location
|
|
JSR CHPOS ; Change to a new location
|
|
LDA $DC01:AND #$10:BNE OV3 ; Check for the space bar
|
|
LDA #0:STA KEYW+1
|
|
OV3 LDX #$53:STX $D011:INC $D019:JMP $EA81
|
|
|
|
NEWPOS LDA #0 ; Init the IRQ routine for this position
|
|
LSR:LSR:LSR:CLC:ADC #4:STA E1+1
|
|
LDA #7:SEC:SBC NEWPOS+1:AND #7:TAX:TAY:CLC:ADC #35:STA E2+1
|
|
LDA UPOS+3+7,Y:DEX:BMI J0:AND #$3F
|
|
J0 STA F7+1:AND #$3F:STA FF+1
|
|
LDA UPOS+3+6,Y:DEX:BMI J1:AND #$3F
|
|
J1 STA F6+1:AND #$3F:STA FE+1
|
|
LDA UPOS+3+5,Y:DEX:BMI J2:AND #$3F
|
|
J2 STA F5+1:AND #$3F:STA FD+1
|
|
LDA UPOS+3+4,Y:DEX:BMI J3:AND #$3F
|
|
J3 STA F4+1:AND #$3F:STA FC+1
|
|
LDA UPOS+3+3,Y:DEX:BMI J4:AND #$3F
|
|
J4 STA F3+1:AND #$3F:STA FB+1
|
|
LDA UPOS+3+2,Y:DEX:BMI J5:AND #$3F
|
|
J5 STA F2+1:AND #$3F:STA FA+1
|
|
LDA UPOS+3+1,Y:DEX:BMI J6:AND #$3F
|
|
J6 STA F1+1:AND #$3F:STA F9+1
|
|
LDA UPOS+3+0,Y:DEX:BMI J7:AND #$3F
|
|
J7 STA F0+1:AND #$3F:STA F8+1
|
|
LDA #$96:STA B0+1:LDA #199:SEC:SBC NEWPOS+1:BCC OV2
|
|
LSR:LSR:LSR:CLC:ADC #5:STA B1+1
|
|
RTS
|
|
OV2 LDA #0:STA B1+1:LDX #$94:STX B0+1:RTS
|
|
|
|
CHPOS LDX NEWPOS+1
|
|
LDA $DC00:TAY ; Get joystick
|
|
AND #$10:BNE DIR ; If no button pressed
|
|
TYA:AND #1:BEQ UP ; If joy up
|
|
TYA:AND #2:BEQ DOWN ; If joy down
|
|
RTS
|
|
DIR LDA #0:BEQ UP
|
|
DOWN DEX:CPX #$FF:BNE DOK
|
|
LDX #0:STX DIR+1 ; Change direction
|
|
DOK STX NEWPOS+1:RTS
|
|
UP INX:CPX #$FD:BCC UOK ; 251(locations)+149(visible)=400
|
|
LDX #$FC:STX DIR+1 ; Change direction
|
|
UOK STX NEWPOS+1:RTS
|
|
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
The BFLI file format:
|
|
|
|
File BFLI Display
|
|
Lines Offset Offset Lines Size
|
|
Colors 0-1.3 0..55 944..999 22.7-24 56
|
|
I 1.3-2 56..79 - 24
|
|
2-24 80..999 0..919 0-22 920
|
|
24-24.7 1000..1023 920..943 22-22.7 24
|
|
|
|
II 0-1.3 0..55 1968..2024 49.3-50.6 56
|
|
1.3-24.7 56..1023 1000..1967 24-49.3 968
|
|
|
|
|
|
Gfx 0-1.3 0..447 7552..7999 22.7-24 448
|
|
I 1.3-2 448..639 - 192
|
|
2-24 640..7999 0..7359 0-22 7360
|
|
24-24.7 8000..8191 7360..7551 22-22.7 192
|
|
|
|
II 0-1.3 0..447 15744..16192 49.3-50.6 448
|
|
1.3-24.7 448..8191 8000..15743 24-49.3 7744
|
|
|
|
========================================================================
|
|
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. And also, it is far too easy to make
|
|
virtually any demo crash by hitting the Restore key, or at least cause
|
|
visual distortions on the screen.
|
|
|
|
As late as a year ago I still hadn't coded a stable raster interrupt
|
|
routine myself. But then I had to do it, since I was researching the
|
|
video chip timing details together with my German friend Andreas
|
|
Boose. It was ashaming that we had the same level of knowledge when
|
|
it came to the hardware, but he was the only of us who had written a
|
|
stable raster routine. Well, finally I made me to start coding. I
|
|
used the same double-interrupt idea as Andreas used in his routine.
|
|
|
|
After a couple of errors my routine worked, and I understood how it
|
|
works exactly. (This is something that separates us normal coders
|
|
from demo people: They often code by instinct; by patching the routine
|
|
until it works, without knowing exactly what is happening. That's why
|
|
demos often rely on weird things, like crash if the memory is not
|
|
initialized properly.)
|
|
|
|
In this article, I document two methods of creating stable raster
|
|
routines on Commodore computers. The principles apply for most 8-bit
|
|
computers, not only Commodores, but raster effects are very rarely
|
|
seen on other computers.
|
|
|
|
|
|
Background
|
|
|
|
What are raster effects? They are effects, where you change the
|
|
screen appearance while it is being drawn. For instance, you can set
|
|
the screen color to white in the top of the screen, and to black in
|
|
the middle of the screen. In that way, you will get a picture whose
|
|
top half is white and bottom half black. Normally such effects are
|
|
implemented with interrupt routines that are executed synchronized
|
|
with the screen refresh.
|
|
|
|
The video chip on the Commodore 64 and many other videochips have a
|
|
special interrupt feature called the Raster interrupt. It will
|
|
generate an IRQ in the beginning of a specified raster line. On other
|
|
computers, like the VIC-20, there is no Raster interrupt, but you can
|
|
generate the interrupts with a timer, provided that the timer and the
|
|
videochip are clocked from the same source.
|
|
|
|
Even if the processor gets an interrupt signal at the same position on
|
|
each video frame, it won't always be executing the first instruction
|
|
of the interrupt routine at the same screen position. The NMOS 6502
|
|
machine instructions can take 2 to 9 machine cycles to execute, and if
|
|
the main program contains instructions of very varying lengths, the
|
|
beginning position of the interrupt can jump between 7 different
|
|
positions. This is why you need to synchronize the raster routine
|
|
when doing serious effects.
|
|
|
|
Also, executing the interrupt sequence will take 7 additional cycles,
|
|
and the interrupt sequence will only start after the current
|
|
instruction if the interrupt arrived at least two cycles before the
|
|
end of the current instruction. It is even possible that an interrupt
|
|
arrives while interrupts are disabled and the processor is just
|
|
starting to execute a CLI instruction. Alas, the processor will not
|
|
jump to the interrupt right after the CLI, but it will execute the
|
|
next instruction before jumping to it. This is natural, since the CLI
|
|
takes only two cycles. But anyway, this is only a constant in our
|
|
equation, and actually out of the scope of this article.
|
|
|
|
How to synchronize a raster interrupt routine? The only way is to
|
|
check the current screen position and delay appropriately many cycles.
|
|
There are several ways of doing this, some of which are very awful and
|
|
inefficient. The ugliest ways of doing this on the Commodore 64 I
|
|
know are busy-waiting several raster lines and polling the raster line
|
|
value, or using the Light pen feature, which will fail if the user
|
|
presses the fire button on Joystick port 1. Here I will present two
|
|
ways, both very elegant in my opinion.
|
|
|
|
|
|
Using an auxiliary timer
|
|
|
|
On the VIC-20, there is no Raster interrupt feature in the video chip.
|
|
All you can do is to use a timer for generating raster interrupts.
|
|
And if you use two timers running at a constant phase difference, you
|
|
can get full synchronization. The first timer generates the raster
|
|
interrupt, and the second timer, the auxiliary timer, tells the raster
|
|
routine where it is running. Actually you could even use the first
|
|
timer also for the checking, but the code will look nicer in the way I
|
|
will be presenting now. Besides, you can use the auxiliary timer idea
|
|
even when real raster interrupts are available.
|
|
|
|
The major drawback of using an auxiliary timer is initializing it.
|
|
The initialization routine must synchronize with the screen, that is,
|
|
wait for the beginning of the wanted raster line. To accomplish this,
|
|
the routine must first wait for a raster line that occurs a bit
|
|
earlier. About the only way to do this is with a loop like
|
|
|
|
LDA #value
|
|
loop CMP raster
|
|
BNE loop
|
|
|
|
One round of this loop will take 4+3=7 cycles to execute, assuming
|
|
that absolute addressing is being used. The loop will be finished if
|
|
the raster register contains the wanted value while the processor
|
|
reads it on the last cycle of the CMP instruction. The raster
|
|
register can actually have changed already on the first cycle of the
|
|
BNE instruction on the previous run of the loop, that is 7 cycles
|
|
earlier!
|
|
|
|
Because of this, the routine must poll the raster register for several
|
|
raster lines, always consuming one cycle more if the raster register
|
|
changed too early. As the synchronization can be off at most by 7
|
|
cycles, a loop of 7 raster register value changes would do, but I made
|
|
the loop a bit longer in my VIC-20 routine. (Well, I have to admit it,
|
|
I was too lazy to make it work only with 7 rounds.)
|
|
|
|
After the initialization routine is fully synchronized the screen, it
|
|
can set up the timer(s) and interrupts and exit. The auxiliary timer
|
|
in my VIC-20 demo routine is several dozens of cycles after the
|
|
primary timer, see the source code for comments. It is arranged so
|
|
that the auxiliary timer will be at least 0 when it is being read in
|
|
the raster routine. The raster routine will wait as many extra cycles
|
|
as the auxiliary timer reads, however at most 15 cycles.
|
|
|
|
|
|
Using double raster interrupt
|
|
|
|
On the Commodore 64, I have never seen the auxiliary timer scheme
|
|
being used. Actually I haven't seen it being used anywhere, I was
|
|
probably the first one who made a stable raster interrupt routine on
|
|
the VIC-20. Instead, the double interrupt method is becoming the
|
|
standard on the C64 side.
|
|
|
|
The double interrupt method is based entirely on the Raster interrupt
|
|
feature of the video chip. In the first raster interrupt routine, the
|
|
program sets up another raster interrupt on a further line, changes
|
|
the interrupt vector and enables interrupts.
|
|
|
|
In the place where the second raster interrupt will occur, there will
|
|
be 2-byte instructions in the first interrupt routine. In this way,
|
|
the beginning of the next raster interrupt will be off at most by one
|
|
cycle. Some coders might not care about this one cycle, but if you
|
|
can do it right, why wouldn't you do it right until the end?
|
|
|
|
At the beginning of the second raster interrupt routine, you will read
|
|
the raster line counter register at the point where it is about to
|
|
change. When the raster routine is being executed, there are two
|
|
possibilities: Either the raster counter has just changed, or it will
|
|
change on the next cycle. So, you just need to compare if the
|
|
register changed one cycle too early or not, and delay a cycle when
|
|
needed. This is easily accomplished with a branch to the next address.
|
|
|
|
Of course, somewhere in your second raster interrupt routine you must
|
|
restore the original raster interrupt position and set the interrupt
|
|
vector to point to the first interrupt routine.
|
|
|
|
|
|
Applying in practice
|
|
|
|
I almost forgot my complaints about demos crashing when you actively
|
|
hit the Restore key. On the VIC-20, you can disable NMI interrupts
|
|
generated by the Restore key, and on the C64, you can generate an NMI
|
|
interrupt with the CIA2 timer and leave the NMI-line low, so that no
|
|
further high-to-low transitions will be recognized on the line. The
|
|
example programs demonstrate how to do this.
|
|
|
|
So far, this article has been pretty theoretical. To apply these
|
|
results in practice, you must definitely know how many CPU clock
|
|
cycles the video chip consumes while drawing a scan line. This is
|
|
fairly easy to measure with a timer interrupt, if you patch the
|
|
interrupt handler so that it changes the screen color on each run.
|
|
Set the timer interval to LINES*COLUMNS cycles, where LINES is the
|
|
amount of raster lines and COLUMNS is your guess for the amount of
|
|
clock cycles spent in a raster line.
|
|
|
|
If your guess is right, the color will always be changed in the same
|
|
screen position (neglecting the 7-cycle jitter). When adjusting the
|
|
timer, remember that the timers on the 6522 VIA require 2 cycles for
|
|
re-loading, and the ones on the 6526 CIA need one extra cycle. Keep
|
|
trying different timer values until you the screen color changes at
|
|
one fixed position.
|
|
|
|
Commodore used several different values for LINES and COLUMNS on its
|
|
videochips. They never managed to make the screen refresh rate
|
|
exactly 50 or 60 Hertz, but they didn't hesitate to claim that their
|
|
computers comply with the PAL-B or NTSC-M standards. In the following
|
|
tables I have gathered some information of some Commodore video chips.
|
|
|
|
|
|
NTSC-M systems:
|
|
|
|
Chip Crystal Dot Processor Cycles/ Lines/
|
|
Host ID freq/Hz clock/Hz clock/Hz line frame
|
|
------ -------- -------- -------- --------- ------- ------
|
|
VIC-20 6560-101 14318181 4090909 1022727 65 261
|
|
C64 6567R56A 14318181 8181818 1022727 64 262
|
|
C64 6567R8 14318181 8181818 1022727 65 263
|
|
|
|
Later NTSC-M video chips were most probably like the 6567R8. Note
|
|
that the processor clock is a 14th of the crystal frequency on all
|
|
NTSC-M systems.
|
|
|
|
PAL-B systems:
|
|
|
|
Chip Crystal Dot Processor Cycles/ Lines/
|
|
Host ID freq/Hz clock/Hz clock/Hz line frame
|
|
------ -------- -------- -------- --------- ------- ------
|
|
VIC-20 6561-101 4433618 4433618 1108405 71 312
|
|
C64 6569 17734472 7881988 985248 63 312
|
|
|
|
On the PAL-B VIC-20, the crystal frequency is simultaneously the dot
|
|
clock, which is BTW a 4th of the crystal frequency used on the C64.
|
|
On the C64, the crystal frequency is divided by 18 to generate the
|
|
processor clock, which in turn is multiplied by 8 to generate the
|
|
dot clock.
|
|
|
|
The basic timings are the same on all 6569 revisions, and also on
|
|
any later C64 and C128 video chips. If I remember correctly, these
|
|
values were the same on the C16 videochip TED as well.
|
|
|
|
Note that the dot clock is 4 times the processor clock on the VIC-20,
|
|
and 8 times that on the C64. That is, one processor cycle is half a
|
|
character wide on the VIC-20, and a full character on a C64. I don't
|
|
have exact measurements of the VIC-20 timing, but it seems that while
|
|
the VIC-20 videochips draw the characters on the screen, it first
|
|
reads the character code, and then, on the following video cycle, the
|
|
appearance on the current character line. There are no bad lines,
|
|
like on the C64, where the character codes (and colors) are fetched on
|
|
every 8th raster line.
|
|
|
|
Those ones who got upset when I said that Commodore has never managed
|
|
to make a fully PAL-B or NTSC-M compliant 8-bit computer should take a
|
|
closer look at the "Lines/frame" columns. If that does not convince
|
|
you, calculate the raster line rate and the screen refresh rate from
|
|
the values in the table and see that they don't comply with the
|
|
standards. To calculate the line rate, divide the processor clock
|
|
frequency by the amount of cycles per line. To get the screen refresh
|
|
rate, divide that frequency by the amount of raster lines.
|
|
|
|
|
|
The Code
|
|
|
|
OK, enough theory and background. Here are the two example programs,
|
|
one for the VIC-20 and one for the C64. In order to fully understand
|
|
them, you need to know the exact execution times of NMOS 6502
|
|
instructions. (All 8-bit Commodore computers use the NMOS 6502
|
|
processor core, except the C65 prototype, which used a inferior CMOS
|
|
version with all nice poorly-documented features removed.) You should
|
|
check the 64doc document, available on my WWW pages at
|
|
http://www.hut.fi/~msmakela/cbm/emul/x64/64doc.html, or via FTP at
|
|
ftp.funet.fi:/pub/cbm/documents/64doc. I can also e-mail it to you on
|
|
request.
|
|
|
|
Also, I have written a complete description of the video timing on the
|
|
6567R56A, 6567R8 and 6569 video chips, which could maybe be turned
|
|
into another C=Hacking article. The document is currently partially
|
|
in English and partially in German. The English part is available
|
|
from ftp.funet.fi as /pub/cbm/documents/pal.timing, and I can send
|
|
copies of the German part (screen resolution, sprite disturbance
|
|
measurements, and more precise timing information) via e-mail.
|
|
|
|
The code is written for the DASM assembler, or more precisely for a
|
|
extended ANSI C port of it made by Olaf Seibert. This excellent
|
|
cross-assembler is available at ftp.funet.fi in /pub/cbm/programming.
|
|
|
|
First the raster demo for the VIC-20. Note that on the VIC-20, the
|
|
$9004 register contains the upper 8 bits of the raster counter. So,
|
|
this register changes only on every second line. I have tested the
|
|
program on my 6561-101-based VIC-20, but not on an NTSC-M system.
|
|
|
|
It was hard to get in contact with NTSC-M VIC-20 owners. Daniel
|
|
Dallmann, who has a NTSC-M VIC-20, although he lives in Germany, ran
|
|
my test to determine the amount of cycles per line and lines per frame
|
|
on the 6560-101. Unfortunately, the second VIA of his VIC-20 is
|
|
partially broken, and because of this, this program did not work on
|
|
his computer. Craig Bruce ran the program once, and he reported that
|
|
it almost worked. I corrected a little bug in the code, so that now
|
|
the display should be stable on an NTSC-M system, too. But the actual
|
|
raster effect, six 16*16-pixel boxes centered at the top border, are
|
|
very likely to be off their position.
|
|
|
|
|
|
processor 6502
|
|
|
|
NTSC = 1
|
|
PAL = 2
|
|
|
|
;SYSTEM = NTSC ; 6560-101: 65 cycles per raster line, 261 lines
|
|
SYSTEM = PAL ; 6561-101: 71 cycles per raster line, 312 lines
|
|
|
|
#if SYSTEM & PAL
|
|
LINES = 312
|
|
CYCLES_PER_LINE = 71
|
|
#endif
|
|
#if SYSTEM & NTSC
|
|
LINES = 261
|
|
CYCLES_PER_LINE = 65
|
|
#endif
|
|
TIMER_VALUE = LINES * CYCLES_PER_LINE - 2
|
|
|
|
.org $1001 ; for the unexpanded Vic-20
|
|
|
|
; The BASIC line
|
|
|
|
basic:
|
|
.word 0$ ; link to next line
|
|
.word 1995 ; line number
|
|
.byte $9E ; SYS token
|
|
|
|
; SYS digits
|
|
|
|
.if (* + 8) / 10000
|
|
.byte $30 + (* + 8) / 10000
|
|
.endif
|
|
.if (* + 7) / 1000
|
|
.byte $30 + (* + 7) % 10000 / 1000
|
|
.endif
|
|
.if (* + 6) / 100
|
|
.byte $30 + (* + 6) % 1000 / 100
|
|
.endif
|
|
.if (* + 5) / 10
|
|
.byte $30 + (* + 5) % 100 / 10
|
|
.endif
|
|
.byte $30 + (* + 4) % 10
|
|
0$:
|
|
.byte 0,0,0 ; end of BASIC program
|
|
|
|
start:
|
|
lda #$7f
|
|
sta $912e ; disable and acknowledge interrupts
|
|
sta $912d
|
|
sta $911e ; disable NMIs (Restore key)
|
|
|
|
;synchronize with the screen
|
|
sync:
|
|
ldx #28 ; wait for this raster line (times 2)
|
|
0$:
|
|
cpx $9004
|
|
bne 0$ ; at this stage, the inaccuracy is 7 clock cycles
|
|
; the processor is in this place 2 to 9 cycles
|
|
; after $9004 has changed
|
|
ldy #9
|
|
bit $24
|
|
1$:
|
|
ldx $9004
|
|
txa
|
|
bit $24
|
|
#if SYSTEM & PAL
|
|
ldx #24
|
|
#endif
|
|
#if SYSTEM & NTSC
|
|
bit $24
|
|
ldx #21
|
|
#endif
|
|
dex
|
|
bne *-1 ; first spend some time (so that the whole
|
|
cmp $9004 ; loop will be 2 raster lines)
|
|
bcs *+2 ; save one cycle if $9004 changed too late
|
|
dey
|
|
bne 1$
|
|
; now it is fully synchronized
|
|
; 6 cycles have passed since last $9004 change
|
|
; and we are on line 2(28+9)=74
|
|
|
|
;initialize the timers
|
|
timers:
|
|
lda #$40 ; enable Timer A free run of both VIAs
|
|
sta $911b
|
|
sta $912b
|
|
|
|
lda #<TIMER_VALUE
|
|
ldx #>TIMER_VALUE
|
|
sta $9116 ; load the timer low byte latches
|
|
sta $9126
|
|
|
|
#if SYSTEM & PAL
|
|
ldy #7 ; make a little delay to get the raster effect to the
|
|
dey ; right place
|
|
bne *-1
|
|
nop
|
|
nop
|
|
#endif
|
|
#if SYSTEM & NTSC
|
|
ldy #6
|
|
dey
|
|
bne *-1
|
|
bit $24
|
|
#endif
|
|
|
|
stx $9125 ; start the IRQ timer A
|
|
; 6560-101: 65 cycles from $9004 change
|
|
; 6561-101: 77 cycles from $9004 change
|
|
ldy #10 ; spend some time (1+5*9+4=55 cycles)
|
|
dey ; before starting the reference timer
|
|
bne *-1
|
|
stx $9115 ; start the reference timer
|
|
|
|
pointers:
|
|
lda #<irq ; set the raster IRQ routine pointer
|
|
sta $314
|
|
lda #>irq
|
|
sta $315
|
|
lda #$c0
|
|
sta $912e ; enable Timer A underflow interrupts
|
|
rts ; return
|
|
|
|
irq:
|
|
; irq (event) ; > 7 + at least 2 cycles of last instruction (9 to 16 total)
|
|
; pha ; 3
|
|
; txa ; 2
|
|
; pha ; 3
|
|
; tya ; 2
|
|
; pha ; 3
|
|
; tsx ; 2
|
|
; lda $0104,x ; 4
|
|
; and #xx ; 2
|
|
; beq ; 3
|
|
; jmp ($314) ; 5
|
|
; ---
|
|
; 38 to 45 cycles delay at this stage
|
|
|
|
lda $9114 ; get the NMI timer A value
|
|
; (42 to 49 cycles delay at this stage)
|
|
; sta $1e00 ; uncomment these if you want to monitor
|
|
; ldy $9115 ; the reference timer on the screen
|
|
; sty $1e01
|
|
cmp #8 ; are we more than 7 cycles ahead of time?
|
|
bcc 0$
|
|
pha ; yes, spend 8 extra cycles
|
|
pla
|
|
and #7 ; and reset the high bit
|
|
0$:
|
|
cmp #4
|
|
bcc 1$
|
|
bit $24 ; waste 4 cycles
|
|
and #3
|
|
1$:
|
|
cmp #2 ; spend the rest of the cycles
|
|
bcs *+2
|
|
bcs *+2
|
|
lsr
|
|
bcs *+2 ; now it has taken 82 cycles from the beginning of the IRQ
|
|
|
|
effect:
|
|
ldy #16 ; perform amazing video effect
|
|
lda $900f
|
|
tax
|
|
eor #$f7
|
|
0$:
|
|
sta $900f
|
|
stx $900f
|
|
sta $900f
|
|
stx $900f
|
|
sta $900f
|
|
stx $900f
|
|
sta $900f
|
|
stx $900f
|
|
sta $900f
|
|
stx $900f
|
|
sta $900f
|
|
stx $900f
|
|
pha
|
|
pla
|
|
#if SYSTEM & PAL
|
|
pha
|
|
pla
|
|
nop
|
|
#endif
|
|
#if SYSTEM & NTSC
|
|
bit $24
|
|
#endif
|
|
nop
|
|
dey
|
|
bne 0$ ; end of amazing video effect
|
|
|
|
jmp $eabf ; return to normal IRQ
|
|
|
|
|
|
And after you have recovered from the schock of seeing a VIC-20
|
|
program, here is an example for the C64. It does also something
|
|
noteworthy; it removes the side borders on a normal screen while
|
|
displaying all eight sprites. Well, it cannot remove the borders on
|
|
bad lines, and the bad lines look pretty bad. But I could use the
|
|
program for what I wanted: I measured the sprite distortions on all
|
|
videochip types I had at hand. (FYI: the sprites 0-2 get distorted at
|
|
the very right of the screen, and the sprites 6 and 7 are invisible at
|
|
the very left of the screen. You will need a monitor with horizontal
|
|
size controls to witness these effects.)
|
|
|
|
This program is really robust, it installs itself nicely to the
|
|
interrupt routine chain. It even has an entry point for deinstalling
|
|
itself. But in its robustness it uses self-modifying code to store
|
|
the original interrupt routine address. :-)
|
|
|
|
The code also relies on the page boundaries in being where they are.
|
|
The cycles are counted so that the branches "irqloop" must take 4
|
|
cycles. If the "irqloop" comes to the same CPU page with the branch
|
|
instructions, you must add one cycle to the loop in a way or another.
|
|
When coding the routine, I noticed again how stupid assembly coding
|
|
can be, especially conditional assembling. In a machine language
|
|
monitor you have far better control on page boundaries. BTW, you
|
|
might wonder why I disable the Restore key in a subroutine at the end
|
|
and not in the beginning of the program. Well, the routine was so
|
|
long that it would have affected the "irqloop" page boundaries. And I
|
|
didn't want to risk the modified programs working on all three
|
|
different videochip types on the first try.
|
|
|
|
|
|
In the code, there are some comments that document the video timing,
|
|
like this one:
|
|
|
|
;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
|
|
;ssssssssss ||ssssss Phi-2 VIC-II
|
|
;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
|
|
; ^ now we are here
|
|
|
|
The two vertical bars "|" denote optional cycles. On PAL-B systems
|
|
(63 cycles per line), they are not present. On 6567R56A, which has 64
|
|
cycles per line, there is one additional cycle on this position, and
|
|
the 6567R8 has two additional cycles there.
|
|
|
|
The numbers 0 through 7 are sprite pointer fetches (from the end of
|
|
the character matrix, e.g. the text screen), the "s" characters denote
|
|
sprite image fetches, the "r"s are memory refresh, and the "g" are
|
|
graphics fetches. The two idle video chip cycles are marked with "-".
|
|
On the processor timing line, the "=" signs show halted CPU, "x" means
|
|
free bus, and "X" means that the processor will be halted at once,
|
|
unless it is performing write cycles.
|
|
|
|
processor 6502
|
|
|
|
; Select the video timing (processor clock cycles per raster line)
|
|
CYCLES = 65 ; 6567R8 and above, NTSC-M
|
|
;CYCLES = 64 ; 6567R5 6A, NTSC-M
|
|
;CYCLES = 63 ; 6569 (all revisions), PAL-B
|
|
|
|
cinv = $314
|
|
cnmi = $318
|
|
raster = 52 ; start of raster interrupt
|
|
m = $fb ; zero page variable
|
|
|
|
.org $801
|
|
basic:
|
|
.word 0$ ; link to next line
|
|
.word 1995 ; line number
|
|
.byte $9E ; SYS token
|
|
|
|
; SYS digits
|
|
|
|
.if (* + 8) / 10000
|
|
.byte $30 + (* + 8) / 10000
|
|
.endif
|
|
.if (* + 7) / 1000
|
|
.byte $30 + (* + 7) % 10000 / 1000
|
|
.endif
|
|
.if (* + 6) / 100
|
|
.byte $30 + (* + 6) % 1000 / 100
|
|
.endif
|
|
.if (* + 5) / 10
|
|
.byte $30 + (* + 5) % 100 / 10
|
|
.endif
|
|
.byte $30 + (* + 4) % 10
|
|
|
|
0$:
|
|
.byte 0,0,0 ; end of BASIC program
|
|
|
|
start:
|
|
jmp install
|
|
jmp deinstall
|
|
|
|
install: ; install the raster routine
|
|
jsr restore ; Disable the Restore key (disable NMI interrupts)
|
|
checkirq:
|
|
lda cinv ; check the original IRQ vector
|
|
ldx cinv+1 ; (to avoid multiple installation)
|
|
cmp #<irq1
|
|
bne irqinit
|
|
cpx #>irq1
|
|
beq skipinit
|
|
irqinit:
|
|
sei
|
|
sta oldirq ; store the old IRQ vector
|
|
stx oldirq+1
|
|
lda #<irq1
|
|
ldx #>irq1
|
|
sta cinv ; set the new interrupt vector
|
|
stx cinv+1
|
|
skipinit:
|
|
lda #$1b
|
|
sta $d011 ; set the raster interrupt location
|
|
lda #raster
|
|
sta $d012
|
|
ldx #$e
|
|
clc
|
|
adc #3
|
|
tay
|
|
lda #0
|
|
sta m
|
|
0$:
|
|
lda m
|
|
sta $d000,x ; set the sprite X
|
|
adc #24
|
|
sta m
|
|
tya
|
|
sta $d001,x ; and Y coordinates
|
|
dex
|
|
dex
|
|
bpl 0$
|
|
lda #$7f
|
|
sta $dc0d ; disable timer interrupts
|
|
sta $dd0d
|
|
ldx #1
|
|
stx $d01a ; enable raster interrupt
|
|
lda $dc0d ; acknowledge CIA interrupts
|
|
lsr $d019 ; and video interrupts
|
|
ldy #$ff
|
|
sty $d015 ; turn on all sprites
|
|
cli
|
|
rts
|
|
|
|
deinstall:
|
|
sei ; disable interrupts
|
|
lda #$1b
|
|
sta $d011 ; restore text screen mode
|
|
lda #$81
|
|
sta $dc0d ; enable Timer A interrupts on CIA 1
|
|
lda #0
|
|
sta $d01a ; disable video interrupts
|
|
lda oldirq
|
|
sta cinv ; restore old IRQ vector
|
|
lda oldirq+1
|
|
sta cinv+1
|
|
bit $dd0d ; re-enable NMI interrupts
|
|
cli
|
|
rts
|
|
|
|
; Auxiliary raster interrupt (for syncronization)
|
|
irq1:
|
|
; irq (event) ; > 7 + at least 2 cycles of last instruction (9 to 16 total)
|
|
; pha ; 3
|
|
; txa ; 2
|
|
; pha ; 3
|
|
; tya ; 2
|
|
; pha ; 3
|
|
; tsx ; 2
|
|
; lda $0104,x ; 4
|
|
; and #xx ; 2
|
|
; beq ; 3
|
|
; jmp ($314) ; 5
|
|
; ---
|
|
; 38 to 45 cycles delay at this stage
|
|
lda #<irq2
|
|
sta cinv
|
|
lda #>irq2
|
|
sta cinv+1
|
|
nop ; waste at least 12 cycles
|
|
nop ; (up to 64 cycles delay allowed here)
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
inc $d012 ; At this stage, $d012 has already been incremented by one.
|
|
lda #1
|
|
sta $d019 ; acknowledge the first raster interrupt
|
|
cli ; enable interrupts (the second interrupt can now occur)
|
|
ldy #9
|
|
dey
|
|
bne *-1 ; delay
|
|
nop ; The second interrupt will occur while executing these
|
|
nop ; two-cycle instructions.
|
|
nop
|
|
nop
|
|
nop
|
|
oldirq = * + 1 ; Placeholder for self-modifying code
|
|
jmp * ; Return to the original interrupt
|
|
|
|
; Main raster interrupt
|
|
irq2:
|
|
; irq (event) ; 7 + 2 or 3 cycles of last instruction (9 or 10 total)
|
|
; pha ; 3
|
|
; txa ; 2
|
|
; pha ; 3
|
|
; tya ; 2
|
|
; pha ; 3
|
|
; tsx ; 2
|
|
; lda $0104,x ; 4
|
|
; and #xx ; 2
|
|
; beq ; 3
|
|
; jmp (cinv) ; 5
|
|
; ---
|
|
; 38 or 39 cycles delay at this stage
|
|
lda #<irq1
|
|
sta cinv
|
|
lda #>irq1
|
|
sta cinv+1
|
|
ldx $d012
|
|
nop
|
|
#if CYCLES - 63
|
|
#if CYCLES - 64
|
|
nop ; 6567R8, 65 cycles/line
|
|
bit $24
|
|
#else
|
|
nop ; 6567R56A, 64 cycles/line
|
|
nop
|
|
#endif
|
|
#else
|
|
bit $24 ; 6569, 63 cycles/line
|
|
#endif
|
|
cpx $d012 ; The comparison cycle is executed CYCLES or CYCLES+1 cycles
|
|
; after the interrupt has occurred.
|
|
beq *+2 ; Delay by one cycle if $d012 hadn't changed.
|
|
; Now exactly CYCLES+3 cycles have passed since the interrupt.
|
|
dex
|
|
dex
|
|
stx $d012 ; restore original raster interrupt position
|
|
ldx #1
|
|
stx $d019 ; acknowledge the raster interrupt
|
|
ldx #2
|
|
dex
|
|
bne *-1
|
|
nop
|
|
nop
|
|
lda #20 ; set the amount of raster lines-1 for the loop
|
|
sta m
|
|
ldx #$c8
|
|
irqloop:
|
|
ldy #2
|
|
dey
|
|
bne *-1 ; delay
|
|
dec $d016 ; narrow the screen (exact timing required)
|
|
;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
|
|
;ssssssssss ||ssssss Phi-2 VIC-II
|
|
;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
|
|
; ^ now we are here
|
|
stx $d016 ; expand the screen
|
|
#if CYCLES - 63
|
|
#if CYCLES - 64
|
|
bit $24 ; 6567R8
|
|
#else
|
|
nop ; 6567R56A
|
|
#endif
|
|
#else
|
|
nop ; 6569
|
|
#endif
|
|
dec m
|
|
bmi endirq
|
|
clc
|
|
lda $d011
|
|
sbc $d012
|
|
and #7
|
|
bne irqloop ; This instruction takes 4 cycles instead of 3,
|
|
; because the page boundary is crossed.
|
|
badline:
|
|
dec m
|
|
nop
|
|
nop
|
|
nop
|
|
nop
|
|
dec $d016
|
|
;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
|
|
;ssssssssss cccccccccccccccccccccccccccccccccccccccc ||ssssss Phi-2 VIC-II
|
|
;==========xXXX========================================||***====== Phi-2 6510
|
|
; ^ we are here
|
|
stx $d016
|
|
;3s4s5s6s7srrrrrgggggggggggggggggggggggggggggggggggggggg--||0s1s2s Phi-1 VIC-II
|
|
;ssssssssss ||ssssss Phi-2 VIC-II
|
|
;==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx||XXX====== Phi-2 6510
|
|
; ^ ^^- we are here (6569)
|
|
; | \- or here (6567R56A)
|
|
; \- or here (6567R8)
|
|
ldy #2
|
|
dey
|
|
bne *-1
|
|
nop
|
|
nop
|
|
#if CYCLES - 63
|
|
#if CYCLES - 64
|
|
nop ; 6567R8, 65 cycles/line
|
|
nop
|
|
nop
|
|
#else
|
|
bit $24 ; 6567R56A, 64 cycles/line
|
|
#endif
|
|
#else
|
|
nop ; 6569, 63 cycles/line
|
|
#endif
|
|
dec m
|
|
bpl irqloop ; This is a 4-cycle branch (page boundary crossed)
|
|
endirq:
|
|
jmp $ea81 ; return to the auxiliary raster interrupt
|
|
|
|
restore: ; disable the Restore key
|
|
lda cnmi
|
|
ldy cnmi+1
|
|
pha
|
|
lda #<nmi ; Set the NMI vector
|
|
sta cnmi
|
|
lda #>nmi
|
|
sta cnmi+1
|
|
ldx #$81
|
|
stx $dd0d ; Enable CIA 2 Timer A interrupt
|
|
ldx #0
|
|
stx $dd05
|
|
inx
|
|
stx $dd04 ; Prepare Timer A to count from 1 to 0.
|
|
ldx #$dd
|
|
stx $dd0e ; Cause an interrupt.
|
|
nmi = * + 1
|
|
lda #$40 ; RTI placeholder
|
|
pla
|
|
sta cnmi
|
|
sty cnmi+1 ; restore original NMI vector (although it won't be used)
|
|
rts
|
|
|
|
|
|
Binaries
|
|
|
|
Here are the programs in uuencoded format. First the VIC-20 programs:
|
|
|
|
Color boxes for the VIC-20, NTSC-M version (probably distorted display):
|
|
|
|
begin 644 copper.6560
|
|
M`1`*$,L'GC0Q,#D```"I?XTND8TMD8T>D:(<[`20T/N@"20DK@20BB0D)"2B
|
|
M%<K0_<T$D+``B-#KJ4"-&Y&-*Y&I0Z)"C1:1C2:1H`:(T/TD)(XED:`*B-#]
|
|
MCA61J6R-%`.I$(T5`ZG`C2Z18*T4D<D(D`1(:"D'R020!"0D*0/)`K``L`!*
|
|
ML`"@$*T/D*I)]XT/D(X/D(T/D(X/D(T/D(X/D(T/D(X/D(T/D(X/D(T/D(X/
|
|
,D$AH)"3JB-#43+_J
|
|
`
|
|
end
|
|
|
|
|
|
Color boxes for the VIC-20, PAL-B version:
|
|
|
|
begin 644 copper.6561
|
|
M`1`*$,L'GC0Q,#D```"I?XTND8TMD8T>D:(<[`20T/N@"20DK@20BB0DHAC*
|
|
MT/W-!)"P`(C0[:E`C1N1C2N1J8:B5HT6D8TFD:`'B-#]ZNJ.)9&@"HC0_8X5
|
|
MD:EJC10#J1"-%0.IP(TND6"M%)')")`$2&@I!\D$D`0D)"D#R0*P`+``2K``
|
|
MH!"M#Y"J2?>-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y"-#Y".#Y!(
|
|
+:$AHZNJ(T--,O^J.
|
|
`
|
|
end
|
|
|
|
|
|
Removed sideborders with 8 sprites and bad lines, PAL-B version:
|
|
|
|
begin 644 raster.63
|
|
M`0@*",L'GC(P-C$```!,$PA,=`@@'0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
|
|
ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
|
|
M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
|
|
M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
|
|
MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZB0D[!+0\`#*RHX2T*(!CAG0H@+*T/WJ
|
|
MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,",8K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6
|
|
MT*`"B-#]ZNKJQOL0S4R!ZJT8`ZP9`TBI0HT8`ZD)C1D#HH&.#=VB`(X%W>B.
|
|
1!-VBW8X.W:E`:(T8`XP9`V`8
|
|
`
|
|
end
|
|
|
|
|
|
Removed sideborders with 8 sprites and bad lines, 6567R56A version
|
|
(very old NTSC-M C64s):
|
|
|
|
begin 644 raster.64
|
|
M`0@*",L'GC(P-C$```!,$PA,=`@@'@FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
|
|
ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
|
|
M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
|
|
M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
|
|
MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNKJ[!+0\`#*RHX2T*(!CAG0H@+*T/WJ
|
|
MZJD4A?NBR*`"B-#]SA;0CA;0ZL;[,"08K1'0[1+0*0?0Y<;[ZNKJZLX6T(X6
|
|
MT*`"B-#]ZNHD),;[$,Q,@>JM&`.L&0-(J4.-&`.I"8T9`Z*!C@W=H@".!=WH
|
|
2C@3=HMV.#MVI0&B-&`.,&0-@
|
|
`
|
|
end
|
|
|
|
Removed sideborders with 8 sprites and bad lines, 6567R8 and above
|
|
(not too old NTSC-M C64s and all C128s)
|
|
|
|
begin 644 raster.65
|
|
M`0@*",L'GC(P-C$```!,$PA,=`@@(0FM%`.N%0/)E=`$X`CP$7B-N0B.N@BI
|
|
ME:((C10#CA4#J1N-$="I-(T2T*(.&&D#J*D`A?NE^YT`T&D8A?N8G0'0RLH0
|
|
M[ZE_C0W<C0W=H@&.&M"M#=Q.&="@_XP5T%A@>*D;C1'0J8&-#=RI`(T:T*VY
|
|
M"(T4`ZVZ"(T5`RP-W5A@J;N-%`.I"(T5`^KJZNKJZNX2T*D!C1G06*`)B-#]
|
|
MZNKJZNI,N`BIE8T4`ZD(C14#KA+0ZNHD).P2T/``RLJ.$M"B`8X9T*("RM#]
|
|
MZNJI%(7[HLB@`HC0_<X6T(X6T"0DQOLP)1BM$=#M$M`I!]#DQOOJZNKJSA;0
|
|
MCA;0H`*(T/WJZNKJZL;[$,I,@>JM&`.L&0-(J4:-&`.I"8T9`Z*!C@W=H@".
|
|
5!=WHC@3=HMV.#MVI0&B-&`.,&0-@
|
|
`
|
|
end
|
|
|
|
|
|
That was all, folks! I hope you learned something from this article.
|
|
Feel free to e-mail me at Marko.Makela@HUT.FI, should anything remain
|
|
unclear.
|
|
|
|
========================================================================
|
|
A Different Perspective, part III
|
|
by Stephen Judd --- sjudd@nwu.edu
|
|
George Taylor --- aa601@cfn.cs.dal.ca
|
|
|
|
Whew! What a busy time it's been -- research to get done,
|
|
conferences, classes... between getting things done and blowing other
|
|
things off, I one day reflected for a moment and realized that I had
|
|
three days left to get the next article together for C=Hacking! So
|
|
everything has been slapped together at the last minute, and I hope
|
|
you'll forgive any bugs or unclear concepts.
|
|
>>> ANECDOTE ALERT <<<
|
|
And that reminds me: I just got JiffyDOS and an FD-2000 drive --
|
|
what a wonderful device. I have a 1.6 megabyte disk formatted into
|
|
three partitions. The first contains my Merlin 128 assembler, the
|
|
second is some 4000 blocks large and I use it for all my various
|
|
versions of code while debugging, and the third is maybe 1000 blocks,
|
|
and contains only finished code -- no more swapping disks, no more
|
|
deleting old versions that I hope I don't need to make room on the
|
|
disk. Also, when I installed JiffyDOS I found a serious bug in my
|
|
128D -- a cricket, dead among the IC's.
|
|
|
|
This time we will cover a lot of ground which isn't so much
|
|
cutting-edge as it is very useful. Let's face it: cubes are getting
|
|
more than a little dull. A worthy end goal is to have a completely
|
|
general routine for plotting a series of polygons -- that is, you supply
|
|
a list of (x,y,z) coordinates from which the program can form a list of
|
|
polygons. These polygons may then be displayed in 2D, rotated, magnified,
|
|
filled, etc. And, much to my three-day astonishment, that is exactly
|
|
what we are going to do.
|
|
But first, a little excursion. One thing we are of course always
|
|
thinking about is optimization possibilities: in the shower, while
|
|
sleeping/dreaming, out on dates, etc. So, where to begin? The biggest
|
|
cycle hogs in the program are line drawing and face filling -- well,
|
|
filling faces is pretty straightforward. What about line drawing?
|
|
Well, one downer of the routine is that every single pixel is
|
|
plotted. But as we know, on a computer any given line is made up of
|
|
several smaller vertical and horizontal lines -- wouldn't it be neat
|
|
if we could think of a way to plot these line chunks all at once,
|
|
instead of a pixel at a time?
|
|
Heck yes it would! So here we go:
|
|
|
|
Neat-o Enhanced Chunky Line Drawing Routine
|
|
-------------------------------------------
|
|
|
|
First we need to be in the right mindframe. Let's say you're
|
|
drawing a line where you move three pixels in x before it's time to take
|
|
a step in y. Instead of plotting all three pixels it would of course
|
|
be much more efficient to just stick a number like %00011100 in the
|
|
drawing buffer. But somehow we need to keep track of a) how large the
|
|
chunk needs to be, and b) where exactly the chunk is.
|
|
In the above example, we started at a particular x-value:
|
|
|
|
%00010000
|
|
|
|
and we want to keep adding ones to the right of the starting point; three,
|
|
to be exact. Hmmm... we need to somehow rotate the starting bit in a way
|
|
that leaves a trail of ones behind it. Maybe rotate and ORA with the
|
|
original bit? But what happens when you take a step in Y?
|
|
No, we need something far sneakier. Let's say that instead of
|
|
%00010000 we start with
|
|
|
|
x = %00011111
|
|
|
|
Now, with each step in the x direction, we do an arithmetic shift on x. So
|
|
after one step we have
|
|
|
|
x = %00001111
|
|
|
|
and after two steps
|
|
|
|
x = %00000111
|
|
|
|
and at the third step of course
|
|
|
|
x = %00000011
|
|
|
|
Now it is time to take a step in Y. But now look: if we EOR x with its
|
|
|
|
original value xold = %00011111, we get
|
|
|
|
x EOR xold = %00011100
|
|
|
|
which is exactly the chunk we wanted. Moreover, x still remembers where it
|
|
is, so we don't have to do anything special each time a step is taken in
|
|
the y-direction.
|
|
|
|
So here is the algorithm for drawing a line in the x-direction:
|
|
|
|
initialize x, dx, etc.
|
|
xold = x
|
|
take a step in x: LSR X
|
|
have we hit the end of a column? If so, then plot and check on y
|
|
is it time to take a step in y?
|
|
if not, take another step in x
|
|
if it is, then let a=x EOR xold
|
|
plot a into the buffer
|
|
let xold=x
|
|
keep on going until we're finished
|
|
|
|
This simple modification gives us a substantial speed increase --
|
|
on the old filled hires cube3d program, I measured a gain of one frame per
|
|
second. Not earth-shattering, but not bad either! When faces are not
|
|
filled, the difference is of course much more noticable.
|
|
There are a few things to be careful of. There was a bug in the
|
|
old routine when the line was a single point. In that case dx=dy=0, and
|
|
the program would draw a vertical line on the screen. There are probably
|
|
some other things to be careful of, but since I wrote this part of the
|
|
code three months ago I really don't remember any of them!
|
|
This takes care of horizontal line chunks -- what about vertical
|
|
chunks? Well, because of the way points are plotted there is nothing
|
|
we can do about them. But, as we shall soon see, if we use an EOR-buffer
|
|
to fill faces we will be forced to take care of the vertical chunks!
|
|
|
|
General Polygon Routine
|
|
-----------------------
|
|
|
|
Now we can begin thinking about a general polygon routine. First
|
|
we need a list of sets of points, where each set corresponds to a
|
|
polygon. The first number in a set could be the number of (x,y,z) points
|
|
in that set, and the points could then follow. So a triangle could
|
|
be given by the data set:
|
|
|
|
3 -1 0 0 0 1 0 1 0 0
|
|
|
|
This would be a triangle with vertices at (-1,0,0), (0,1,0), and (1,0,0).
|
|
We can mash a bunch of these sets together, but somehow we have to know
|
|
when we've hit the end -- for this we can use a zero, since we don't
|
|
want to plot polygons with zero points in them.
|
|
For that matter, how many points should there be in a polygon?
|
|
There must be at least three, otherwise it makes no sense. Since we
|
|
want our polygons to be closed, the computer should be smart enough to
|
|
connect the last point to the first point -- in our triangle above,
|
|
the computer would join (-1,0,0) to (0,1,0), (0,1,0) to (1,0,0), and
|
|
(1,0,0) to (-1,0,0).
|
|
Now that we have a polygon, we want to rotate it. You will
|
|
recall that we have calculated a rotation matrix M, which acts on
|
|
points. So we need apply our rotation transform to each of the
|
|
points in the polygon, i.e. multiply M times each point of the
|
|
polygon. Furthermore, we need to project each of these points.
|
|
Uh-oh: matrix multiplication. In the past we have avoided this
|
|
issue by putting the vertices of our cube at 1 or -1. So we need to
|
|
use our multiplication routine from last time. But wait! As you recall,
|
|
the last program used a specially modified multiplication table. To get
|
|
a wider range of numbers to multiply we will need another set of
|
|
multiplication tables -- no big whoop.
|
|
Now, if you review the multiplication routine from last time,
|
|
it adds two numbers and subtracts two numbers. What kinds of numbers
|
|
will we be dealing with? The matrix elements vary between -64..64.
|
|
This then fixes our range of polygon coordinates from -64..64. Why?
|
|
If the matrix element is 64, and we multiply it by 64, the multiplication
|
|
routine will add 64 and 64 and get 128, which is right on the edge of
|
|
our multiplication table.
|
|
Can we improve this rotation process in any way? In fact, we can
|
|
cut down on the number of multiplications (i.e. do eight or even seven
|
|
instead of nine multiplications). However, there is a fair amount of
|
|
overhead involved in doing so, and our multiply routine is fast enough
|
|
that the extra overhead and complexity really gain us very little in all
|
|
but the most complicated of polygons. In other words, I didn't bother.
|
|
|
|
What about hidden faces? Again, from last time you may recall
|
|
that a method was described which used the cross-product of the projected
|
|
vectors. How do we implement this in the program? Well, if we take
|
|
the first three points of the polygon, we have two vectors. Let's say
|
|
these points are P1 P2 and P3. Then V1=P1-P2 and V2=P3-P2 are two
|
|
vectors in the plane of the polygon which are connected at the point P2
|
|
(this analysis will of course only work if the polygon lies in some plane).
|
|
Depending on how we take the cross product, the sign will be positive or
|
|
negative, and this will tell us if the polygon is visible.
|
|
Depending on how we take the cross product? Absolutely.
|
|
v1 x v2 = -v2 x v1. What it really boils down to is how you define the
|
|
points in your polygon. Specifically, what order they are in. Points
|
|
that are specified in a clockwise manner will give a face pointing in
|
|
the opposite direction of a polygon with the same points specified in
|
|
a counter-clockwise order. In my program, the polygons must be entered
|
|
in counter-clockwise order (with you facing the polygon) for hidden
|
|
faces to work the way you want them to ;-).
|
|
|
|
One other neat thing to have is the ability to zoom in and out.
|
|
We know from the very first article that zooming corresponds to multiplying
|
|
the projected points by a number, so that's what we'll do. The multiplication
|
|
routine returns A=A*Y/64, so a zoom factor of 64 would be like multiplying
|
|
the point by one. All the program does is multiply the projected points
|
|
by a number zoom, unless zoom=64, in which case the program skips the
|
|
zoom multiply. Be warned! No checks of any sort are made in the program,
|
|
so you can zoom at your own risk!
|
|
|
|
The important things to remember are: when entering polygons,
|
|
make sure the numbers range from -64 to 64, and that you enter points
|
|
in counterclockwise. Our triangle example above really should have been
|
|
entered as, say,
|
|
|
|
3 -64 0 0 64 0 0 0 64 0
|
|
|
|
Filled Faces -- Using an EOR buffer
|
|
-----------------------------------
|
|
|
|
Well we still have one thing left, which was alluded to in the
|
|
previous article: using EOR to make a filled face. Some possible
|
|
difficulties were raised, but when you plot a single polygon at a
|
|
time, the problem becomes vastly simplified.
|
|
First I should perhaps remind you what exclusive-or is: either
|
|
A or B, but not both. So 1 EOR 0 = 1, as does 0 EOR 1, but 0 EOR 0 = 0
|
|
and 1 EOR 1 = 0. As a simple introduction to using this for filling
|
|
faces, consider the following piece of the drawing buffer:
|
|
|
|
00001011 M1
|
|
00000000 M2
|
|
00000001 M3
|
|
00001010 M4
|
|
|
|
Lets say we move down memory, EORing as we go. Let M2 = M1 EOR M2. Then
|
|
let M3 = M2 EOR M3. Then let M4 = M3 EOR M4. Our little piece of memory
|
|
is now:
|
|
|
|
00001011 M1
|
|
00001011 M2
|
|
00001010 M3
|
|
00000000 M4
|
|
|
|
What just happened? Imagine that the original memory was a series of
|
|
pieces of line segments. We have just filled in the area between the
|
|
two line segments, like magic!
|
|
If you still aren't getting it, draw a large section of memory,
|
|
and then draw an object in it, like a triangle, or a trapazoid, and
|
|
EOR the memory by hand, starting from the top and moving downwards.
|
|
EOR flips bits. If you start with a zero, it stays zero until
|
|
it hits a one. It will then stay one until it hits another one. So
|
|
you can see that if you have an object bounded by ones, EORing
|
|
successive memory locations will automagically fill the object.
|
|
Right? Well, we have to be careful. One major problem is
|
|
a vertical line:
|
|
|
|
1 1
|
|
1 goes to 0
|
|
1 1
|
|
1 0
|
|
|
|
Not only is the resultant line dashed, but if there are an odd number of
|
|
points in the line segment, the last one will happily move downwards in
|
|
memory, and give you a much longer vertical line than you expected! Since
|
|
any line with slope greater than one is made up of a series of line
|
|
segments, this is a major consideration.
|
|
Another problem arises with single points: a one just sitting all
|
|
by itself will also generate a nice streak down your drawing area.
|
|
If you think about it, what we ideally want to have is an object
|
|
that at any given value of x there are exactly two points, one defining
|
|
the top of the object, and the other defining the bottom. This gives us
|
|
the insight to solve the above two problems.
|
|
First let's think about vertical lines. In principle we could
|
|
plot the first and last endpoints of each vertical line chunk, but that
|
|
is exactly what we don't want! Remember that these are closed polygons,
|
|
which means that there are _two_ lines we need to think about. If I
|
|
plot just a single point in each vertical line segment, there must
|
|
be another point somehwere, either above or below it, from another
|
|
line segment, which will close the point to EOR-filling. Remember, we
|
|
want exactly two points at each value of x: one will come from the
|
|
line, and the other will come from the other line which must lie above
|
|
or below the current one.
|
|
Furthermore, with any convex polygon there are exactly two
|
|
lines which come together at each vertex of the polygon. This means
|
|
that there are only certain cases which we need to worry about.
|
|
For instance, two lines might join in any of the following ways:
|
|
|
|
\ / \ /
|
|
\ / \ /
|
|
\_____ _____/ \/ etc.
|
|
|
|
If you draw out the different cases involving vertical lines, you can see
|
|
that you have to be careful about plotting the lines. One tricky one
|
|
is where two vertical lines with different slopes overlap at the point
|
|
of intersection.
|
|
So after staring at these pictures for a while, you can find
|
|
a consistent method which solves these difficulties. As long as you
|
|
follow the following rules, the problems all disappear; the line routine
|
|
needs to be modified slightly:
|
|
|
|
1) When plotting a vertical line (i.e. big steps in Y direction),
|
|
don't plot the endpoints (i.e. x1,y1 and x2,y2).
|
|
2) When plotting a vertical line, consistently plot either the
|
|
first part of each chunk or the last part of each chunk
|
|
(excluding the endpoints of course). In other words, only
|
|
plot a point when you take a step in x, and then plot one
|
|
and only one point.
|
|
|
|
Now I deduced these by staring at pictures for a few hours and trying
|
|
different things like top/bottom of chunk, left/right, first/last, etc.
|
|
You can see that in some cases this ensures that only one point appears
|
|
on a given line segment. But to me the only way to convince yourself
|
|
that this really does work is to draw a bunch of pictures, and try it
|
|
out! You have cases where two vertical lines intersect, and where
|
|
a vertical line intersects a horizontal line.
|
|
But there is still one thing which we have forgotten -- the
|
|
case of a single point. This can happen in, for instance, a pointy
|
|
triangle, pointing in the x-direction. How do we fix this? By
|
|
simply avoiding the point: in the line drawing routine, use EOR
|
|
to plot the points instead of ORA. Since vertical lines skip the
|
|
endpoints, vertical-horizontal intersections are OK. Horizontal-
|
|
horizontal intersections will force the point of intersection to
|
|
be zero.
|
|
Uh-oh, what about intersections like -----*------. Quite frankly
|
|
I just thought of it, and I think my program will fail on intersections
|
|
like these. Drat. Well, that just gives us something for next time!
|
|
One other thing needs to be mentioned: for EOR-filling to be useful
|
|
you need to draw the polygon in a special buffer, and then EOR this buffer
|
|
into the main display buffer. If you try to EOR the display buffer directly
|
|
you are going to have a whole heap of trouble, such as the concerns raised
|
|
last time.
|
|
Finally, this gives a simple way of filling with patterns instead
|
|
of boring monocolor. Instead of EOR (EORBUF),Y : ORA (DRAWBUF),Y you can
|
|
use EOR (EORBUF),Y : AND PATTERN,Y : ORA (DRAWBUF),Y (as long as you
|
|
preserve the original EOR (EORBUF),Y).
|
|
|
|
Well I am extremely tired and I hope Craig hasn't sent out C=Hacking
|
|
without me! I hope you have fun playing with the program, and I would be
|
|
very interested in seeing any neat geometric shapes you might design!
|
|
|
|
Program notes:
|
|
--------------
|
|
|
|
- Hidden faces defaults to "on". If you enter a shape and a black
|
|
screen comes up, hit 'h' to turn off hidden faces (you probably
|
|
entered the polygon clockwise).
|
|
- There is no pattern filling -- just simple EOR with a twist:
|
|
the EOR buffer is EOR'd into the drawing buffer.
|
|
- You might start hosing memory if you zoom too large.
|
|
|
|
SLJ 6/15/95
|
|
|
|
Addendum
|
|
--------
|
|
Stephen Judd sjudd@nwu.edu
|
|
|
|
Last time we put a circle into the 2D graphics toolbox. Chris
|
|
McBride has pointed something out to me about the algorithm, which makes
|
|
it complete. As you may recall, the algorithm gave a very squarish
|
|
circle for small radii. Chris told me that setting the initial counter
|
|
value to R/2, instead of R, gave a perfect circle. What is going on?
|
|
If you recall the algorithm, we are computing a fractional quantity,
|
|
and when that quantity becomes larger than one, we decrease X. Wouldn't
|
|
it be a whole lot smarter to round off that fraction instead of
|
|
truncate it? Of course it would, and that is what starting the counter
|
|
at R/2 does.
|
|
So, to update the previous algorithm, A should be initialized to
|
|
R/2 instead of R, which means that we change
|
|
|
|
LDA R
|
|
|
|
to
|
|
|
|
LDA R
|
|
LSR
|
|
|
|
for a perfect circle every time.
|
|
|
|
begin 666 cube3d3.2.s
|
|
M ' J*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*@TJH*"@H*"@H*"@
|
|
MH*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'-415!(14Z@:E5$1*"@H*"@H*"@
|
|
MH*"@H*"@H*"@*@TJH&=%3U)'1:!T05E,3U*@H*"@H*"@H*"@H*"@H*"@*@TJ
|
|
MH'-405)4140ZH#<O,3$O.32@H*"@H*"@H*"@H*"@*@TJH&9)3DE32$5$.J W
|
|
M+S$Y+SDTH*"@H*"@H*"@H*"@*@TJH%8R+C"@8T]-4$Q%5$5$.J Q,B\Q-R\Y
|
|
M-*"@H*"@*@TJH%8S+C"@8T]-4$Q%5$5$.J S+S(P+SDUH*"@H*"@*@TJH%8S
|
|
M+C&@8T]-4$Q%5$5$.J V+S$T+SDUH*"@H*"@*@TJH%8S+C*@8T]-4$Q%5$5$
|
|
M.J V+S$U+SDUH*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@
|
|
MH*"@*@TJH'=%3$PLH$E&H$%,3*!'3T53H%=%3$R@5$A)4Z"@*@TJH%!23T=2
|
|
M04V@5TE,3*!23U1!5$6@0:!#54)%+J"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@
|
|
MH*"@H*"@H*"@H*"@*@TJH%8R+C"@*Z!N15>@04Y$H&E-4%)/5D5$(:"@H*"@
|
|
M*@TJH&Y/5Z!7251(H$9!4U1%4J!23U5424Y%4RR@H*"@*@TJH$A)1$1%3J!3
|
|
M55)&04-%4RR@1DE,3$5$H*"@H*"@*@TJH$9!0T53+*!!3D2@15A44D&@5$]0
|
|
MH%-%0U)%5*"@*@TJH%1%6%2@34534T%'15,AH*"@H*"@H*"@H*"@H*"@*@TJ
|
|
MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C"@*Z!F05-4
|
|
MH$-(54Y+6:!,24Y%H*"@H*"@*@TJH%)/551)3D4NH*"@H*"@H*"@H*"@H*"@
|
|
MH*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S
|
|
M+C&@*Z!G14Y%4D%,H%!/3%E'3TZ@4$Q/5*"@*@TJH%=)5$B@2$E$1$5.H$9!
|
|
M0T53H"AX+5!23T150U0I*@TJH$%.1*!:3T]-H$9%05154D4NH*"@H*"@H*"@
|
|
MH*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH%8S+C*@
|
|
M*Z!E;W(M0E5&1D52H$9)3$Q)3D>@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@
|
|
MH*"@H*"@H*"@H*"@*@TJH'1(25.@4%)/1U)!3:!)4Z!)3E1%3D1%1*!43Z"@
|
|
M*@TJH$%#0T]-4$%.6:!42$6@05)424-,1:!)3J"@H*"@*@TJH&,]:$%#2TE.
|
|
M1RR@:E5.+J Y-:!)4U-512Z@H*"@*@TJH&9/4J!$151!24Q3H$].H%1(25.@
|
|
M4%)/1U)!32R@*@TJH%)%042@5$A%H$%25$E#3$4AH*"@H*"@H*"@H*"@*@TJ
|
|
MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'=2251%H%1/H%53
|
|
M(:"@H*"@H*"@H*"@H*"@H*"@*@TJH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@
|
|
MH*"@H*"@*@TJH&U94T5,1J!72$5.H%E/54Y'H$1)1*"@H*"@H*"@*@TJH$5!
|
|
M1T523%F@1E)%455%3E2@H*"@H*"@H*"@H*"@*@TJH&1/0U1/4J!!3D2@<T%)
|
|
M3E0LH$%.1*!(14%21*"@*@TJH$=214%4H&%21U5-14Y4H*"@H*"@H*"@H*"@
|
|
MH*"@*@TJH*!A0D]55*!)5*!!3D2@04)/550ZH$)55*"@H*"@*@TJH*!%5D52
|
|
M34]21:"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH&-!346@3U54H$)9H%1(1:!3
|
|
M04U%H&1/3U*@H*"@*@TJH$%3H$E.H&F@5T5.5"Z@H*"@H*"@H*"@H*"@H*"@
|
|
M*@TJH*"@H"V@<E5"04E9052@H*"@H*"@H*"@H*"@H*"@*@TJH*"@H*"@H*"@
|
|
MH*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH'1(3U5'2*!IH%-014%+H%=)5$B@
|
|
M5$A%H*"@H*"@*@TJH%1/3D=515.@3T:@345.H$%.1*!/1J!!3D=,15.@*@TJ
|
|
MH$%.1*!(059%H$Y/5*!,3U9%+*!IH$%-H*"@H*"@*@TJH$)%0T]-1:!!4Z!3
|
|
M3U5.1$E.1Z!"4D%34RR@3U*@*@TJH$&@5$E.2TQ)3D>@0UE-0D%,+J"@H*"@
|
|
MH*"@H*"@*@TJH*"@H"V@,:!C3U))3E1(24%.4Z Q,Z"@H*"@H*"@*@TJH*"@
|
|
MH*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@H*"@*@TJH' N<RZ@=$A)4Z!705.@
|
|
M5U))5%1%3J!54TE.1Z"@*@TJH*"@H*"@;4523$E.H#$R."Z@H*"@H*"@H*"@
|
|
MH*"@*@TJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*BHJ*@T-(&]R9R D
|
|
M.# P, T-*J!C3TY35$%.5%,-#6)U9F8Q(&5Q=2 D,S P," [9DE24U2@0TA!
|
|
M4D%#5$52H%-%5 UB=69F,B!E<74@)#,X,# @.W-%0T].1*!#2$%204-415*@
|
|
M4T54#65O<F)U9B!E<74@)#0P,# @.V5O<BU"549&15(-8G5F9F5R(&5Q=2 D
|
|
M83,@.W!215-534%"3%F@5$A%H%1!4$6@5T].)U2@0D6@4E5.3DE.1PUX,2!E
|
|
M<74@)&9B(#MP3TE.5%.@1D]2H$1205=)3D>@0:!,24Y%#7DQ(&5Q=2 D9F,@
|
|
M.W1(15-%H%I%4D^@4$%'1:!!1$1215-315,->#(@97%U("1F9" [1$].)U2@
|
|
M0T].1DQ)0U2@5TE42*!B87-I8PUY,B!E<74@)&9E#6]L9'@@97%U("1F9 UC
|
|
M:'5N:R!E<74@)&9E#61X(&5Q=2 D-C<@.W1(25.@25.@4TA!4D5$H%=)5$B@
|
|
M=#&@0D5,3U<-9'D@97%U("0V. UT96UP,2!E<74@)&9B(#MO1J!#3U524T4L
|
|
MH$-/54Q$H$-/3D9,24-4H%=)5$B@6#$-=&5M<#(@97%U("1F8R [=$5-4$]2
|
|
M05)9H%9!4DE!0DQ%4PUZ=&5M<"!E<74@)# R(#MU4T5$H$9/4J!"549&15*@
|
|
M4U=!4"Z@H&1/3B=4H%1/54-(+@UZ,2!E<74@)#(R(#MU4T5$H$)9H$U!5$B@
|
|
M4D]55$E.10UZ,B!E<74@)#(T(#MD3TXG5*!43U5#2*!42$531:!%251(15(A
|
|
M#7HS(&5Q=2 D,C8->C0@97%U("0R. UK(&5Q=2 D8C8@.V-/3E-404Y4H%53
|
|
M142@1D]2H$A)1$1%3@T@(" [4U521D%#1:!$151%0U1)3TZ@+:!$3TXG5*!4
|
|
M3U5#2 UH:61E(&5Q=2 D8C4@.V%21:!355)&04-%4Z!(241$14X_#69I;&P@
|
|
M97%U("0U," [85)%H%=%H%5324Y'H&5O<BU&24Q,/PUA;F=M87@@97%U(#$R
|
|
M," [=$A%4D6@05)%H#(J4$DO04Y'34%8H$%.1TQ%4PT-*J!V:6,-#79M8W-B
|
|
M(&5Q=2 D9# Q. UB:V=N9"!E<74@)&0P,C -8F]R9&5R(&5Q=2 D9# R,0US
|
|
M<W1A<G0@97%U(#$S-#0@.U)/5Z YH$E.H%-#4D5%3J!-14U/4EF@052@,3 R
|
|
M- T-#2J@:T523D%,#0UC:')O=70@97%U("1F9F0R#6=E=&EN(&5Q=2 D9F9E
|
|
M- T-*J!S3TU%H%9!4DE!0DQ%4PT-9VQO8GAM:6X@/2 D,V8@.W1(15-%H$%2
|
|
M1:!54T5$H$E.H$-,14%224Y'H%1(10UG;&]B>&UA>" ]("0T," [1%)!5TE.
|
|
M1Z H1TQ/0D%,*:!"549&15(-9VQO8GEM:6X@/2 D-#$-9VQO8GEM87@@/2 D
|
|
M-#(-;&]C>&UI;B ]("0U-R [=$A%4T6@05)%H%53142@24Z@0TQ%05))3D>@
|
|
M5$A%#6QO8WAM87@@/2 D-3@@.V5O<J H3$]#04PIH$)51D9%4@UL;V-Y;6EN
|
|
M(#T@)#4Y#6QO8WEM87@@/2 D-C -<#%X(#T@)#DR(#MT2$531:!!4D6@5$5-
|
|
M4$]205)9H%-43U)!1T4-<#%Y(#T@)#DS(#MU4T5$H$E.H%!,3U1424Y'H%1(
|
|
M1:!04D]*14-424].#7 Q>B ]("0Y- UP,G@@/2 D.34@.W1(15F@05)%H$A%
|
|
M4D6@4T^@5$A!5*!710UP,GD@/2 D.38@.T1/3B=4H$A!5D6@5$^@4D5#04Q#
|
|
M54Q!5$6@5$A%32X-<#)Z(#T@)&%E#7 S>" ]("1A9B [=$A%6:!-04M%H$Q)
|
|
M1D6@14%362X-<#-Y(#T@)&(P#7 S>B ]("1B,2 [=TA9H$%21:!93U6@3$]/
|
|
M2TE.1Z!!5*!-1:!,24M%H%1(050_#7 Q=" ]("1B,B [9$].)U2@64]5H%12
|
|
M55-4H$U%/PUP,G0@/2 D8C,-<#-T(#T@)&(T(#MH059)3D>@04Y/5$A%4J!#
|
|
M2$E,1*!705-.)U2@35F@241%02X-:6YD97@@/2 D-3$-8V]U;G1P=',@/2 D
|
|
M-3(->F]O;2 ]("0W,2 [>D]/3:!&04-43U(-9'-X(#T@)#8Q(#MD<WB@25.@
|
|
M5$A%H$E.0U)%345.5*!&3U(-(" @.U)/5$%424Y'H$%23U5.1*!8#61S>2 ]
|
|
M("0V,B [<TE-24Q!4J!&3U*@9'-Y+*!D<WH-9'-Z(#T@)#8S#7-X(#T@)#8T
|
|
M(#MT2$531:!!4D6@5$A%H$%#5%5!3*!!3D=,15.@24Z@6*!9H$%.1*!:#7-Y
|
|
M(#T@)#8U#7-Z(#T@)#8V#70Q(#T@)#8W(#MT2$531:!!4D6@55-%1*!)3J!4
|
|
M2$6@4D]4051)3TX-=#(@/2 D-C@-=#,@/2 D-CD@.W-%1:!42$6@05)424-,
|
|
M1:!&3U*@34]21:!$151!24Q3#70T(#T@)#9A#70U(#T@)#9B#70V(#T@)#9C
|
|
M#70W(#T@)#9D#70X(#T@)#9E#70Y(#T@)#9F#70Q," ]("0W, UA,3$@/2 D
|
|
M834@.W1(15-%H$%21:!42$6@14Q%345.5%.@3T:@5$A%H%)/5$%424].H$U!
|
|
M5%))6 UB,3(@/2 D838@.WAY>@UC,3,@/2 D83<-9#(Q(#T@)&$X(#MT2$6@
|
|
M3E5-0D52H$1%3D]415.@*%)/5RQ#3TQ534XI#64R,B ]("1A.0UF,C,@/2 D
|
|
M86$-9S,Q(#T@)&%B#6@S,B ]("1A8PUI,S,@/2 D860-#0TJ*BJ@;4%#4D]3
|
|
M#0UM;W9E(&UA8PT@;&1A(%TQ#2!S=&$@73(-(#P\/ T-9V5T:V5Y(&UA8R @
|
|
M.W=!252@1D]2H$&@2T594%)%4U,-=V%I="!J<W(@9V5T:6X-(&-M<" C,# -
|
|
M(&)E<2!W86ET#2 \/#P-#61E8G5G(&UA8R @.W!224Y4H$&@0TA!4D%#5$52
|
|
M#2!D;Z PH* [9$].)U2@05-314U"3$4-#2!L9&&@(UTQ#2!J<W*@8VAR;W5T
|
|
M#2!C;&D-(#X^/B!G971K97D@.V%.1*!704E4H%1/H$-/3E1)3E5%#2!C;7 @
|
|
M(R=3)R [;5F@4T5#4D5#5*!35TE40TB@2T59#2!B;F4@;#$-(&IS<B!C;&5A
|
|
M;G5P#2!J;7 @9&]N90UL,2!C;7 @(R=8)R [;5F@4T5#4D54H$%"3U)4H$M%
|
|
M60T@8FYE(&1O;F4-(&IM<"!C;&5A;G5P#2!F:6X-9&]N92 \/#P-#61E8G5G
|
|
M82!M86,-(&1OH# -(&QD82!=,0T@<W1A(#$P,C0-(&9I;@UD;VYE82 \/#P-
|
|
M#2HM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM#0T@;&1A(",D,# -
|
|
M('-T82!B:V=N9 T@<W1A(&)O<F1E<@T@;&1A('9M8W-B#2!A;F0@(R4P,# P
|
|
M,3$Q,2 [<T-2145.H$U%34]26:!43Z Q,#(T#2!O<F$@(R4P,# Q,# P, T@
|
|
M<W1A('9M8W-B#0T@;&1Y(",P, T@;&1A(",\='1E>'0-('-T82!T96UP,0T@
|
|
M;&1A(",^='1E>'0-('-T82!T96UP,@T@:FUP('1I=&QE#71T97AT(&AE>" Y
|
|
M,S U,3$Q,3$Q(#M#3$5!4J!30U)%14XLH%=(251%+*!#4E-2H$1.#2!T>'0@
|
|
M)Z"@H*"@H*"@H*"@H*!#54)%,T2@5C,N,B<L,$0L,$0-('1X=" GH*"@H*"@
|
|
MH*"@H*"@H*"@H*"@0EDG+#!$#2!H97@@.68@.T-904X-('1X=" GH*"@H%-4
|
|
M15!(14Z@2E5$1"<-(&AE>" Y.0T@='AT(">@H*"@1T5/4D=%H%1!64Q/4B<L
|
|
M,$0L,$0-(&AE>" Y8@T@='AT(">@H$-(14-+H$]55*!42$6@2D%.+J Y-:!)
|
|
M4U-51:!/1B<L,$0-(&AE>" Y-@T@='AT(">@H$,]2$%#2TE.1R<-(&AE>" Y
|
|
M8@T@='AT(">@1D]2H$U/4D6@1$5404E,4R$G+#!$#2!H97@@,&0Q9#%D.64Q
|
|
M,@T@='AT("=&,2]&,B<L.3(-('1X=" GH"V@24Y#+T1%0Z!8+5)/5$%424].
|
|
M)RPP1 T@:&5X(#%D,60Q,@T@='AT("=&,R]&-"<L.3(-('1X=" GH"V@24Y#
|
|
M+T1%0Z!9+5)/5$%424].)RPP1 T@:&5X(#%D,60Q,@T@='AT("=&-2]&-B<L
|
|
M.3(-('1X=" GH"V@24Y#+T1%0Z!:+5)/5$%424].)RPP1 T@:&5X(#%D,60Q
|
|
M,@T@='AT(">@1C>@H"<L.3(-('1X=" GH"V@4D53150G+#!$#2!H97@@,60Q
|
|
M9#$R#2!T>'0@)Z K+RV@)RPY,@T@='AT(">@+:!:3T]-H$E.+T]55"<L,$0-
|
|
M(&AE>" Q9#%D,3(-('1X=" GH*!(H* G+#DR#2!T>'0@)Z MH%1/1T=,1:!(
|
|
M241$14Z@4U521D%#15,G+#!$#2!H97@@,60Q9#$R#2!T>'0@)U-004-%)RPY
|
|
M,@T@='AT(">@+:!43T='3$6@4U521D%#1:!&24Q,24Y')RPP1"PP1 T@='AT
|
|
M(">@H%!215-3H%&@5$^@455)5"<L,$0-(&AE>" P9# U#2!T>'0@)Z"@H*"@
|
|
MH%!215-3H$%.6:!+15F@5$^@0D5'24XG+#!$#2!H97@@,# -=&ET;&4@;&1A
|
|
M("AT96UP,2DL>0T@8F5Q(#IC;VYT#2!J<W(@8VAR;W5T#2!I;GD-(&)N92!T
|
|
M:71L90T@:6YC('1E;7 R#2!J;7 @=&ET;&4-.F-O;G0@/CX^(&=E=&ME>0T-
|
|
M*BHJ*J!S152@55"@5$%"3$53*#\I#0TJH'1!0DQ%4Z!!4D6@0U524D5.5$Q9
|
|
MH%-%5*!54*!)3J!B87-I8PTJH$%.1*!"6:!42$6@05-314U"3$52+@T-=&%B
|
|
M;&5S(&QD82 C/G1M871H,0T@<W1A('HQ*S$-('-T82!Z,BLQ#2!L9&$@(SYT
|
|
M;6%T:#(-('-T82!Z,RLQ#2!S=&$@>C0K,0T-*BHJ*J!C3$5!4J!30U)%14Z@
|
|
M04Y$H%-%5*!54* B0DE434%0(@US971U<"!L9&$@(R0P,2 [=TA)5$4-('-T
|
|
M82 D9# R,2 [=$A)4Z!)4Z!$3TY%H%-/H%1(052@3TQ$15(-(&QD82 C,30W
|
|
M(#M-04-(24Y%4Z!724Q,H%-%5*!54 T@:G-R(&-H<F]U= T@;&1A(",D,# @
|
|
M.T-/4E)%0U1,60T@<W1A("1D,#(Q#2!L9&$@(SQS<W1A<G0-(&%D8R C,3(@
|
|
M.W1(1:!'3T%,H$E3H%1/H$-%3E1%4J!42$6@1U)!4$A)0U,-('-T82!T96UP
|
|
M,2 [8T],54U.H#$R#2!L9&$@(SYS<W1A<G0@.W)/5Z Y#2!S=&$@=&5M<#$K
|
|
M,2 [<W-T87)TH%!/24Y44Z!43Z!23U>@.0T@;&1A(",P, T@;&1Y(",P, T@
|
|
M;&1X(",P," [6*!724Q,H$-/54Y4H#$VH%)/5U.@1D]2H%53#2!C;&,-#3IL
|
|
M;V]P('-T82 H=&5M<#$I+'D-(&EN>0T@861C(",Q-@T@8F-C(#IL;V]P#2!C
|
|
M;&,-(&QD82!T96UP,0T@861C(",T," [;D5%1*!43Z!!1$2@-#"@5$^@5$A%
|
|
MH$)!4T6@4$])3E1%4@T@<W1A('1E;7 Q(#MT3Z!*54U0H%1/H%1(1:!.15A4
|
|
MH%)/5PT@;&1A('1E;7 Q*S$-(&%D8R C,# @.W1!2T6@0T%21:!/1J!#05)2
|
|
M2453#2!S=&$@=&5M<#$K,0T@;&1Y(",P, T@:6YX#2!T>&$@(#MXH$E3H$%,
|
|
M4T^@04Z@24Y$15B@24Y43Z!42$6@0TA!4D%#5$52H$Y534)%4@T@8W!X(",Q
|
|
M-@T@8FYE(#IL;V]P(#MN145$H%1/H$1/H$E4H#$VH%1)3453#0TJ*BHJH&-,
|
|
M14%2H$)51D9%4E,-#2!L9&$@(SQB=69F,0T@<W1A(&)U9F9E<@T@;&1A(",^
|
|
M8G5F9C$-('-T82!B=69F97(K,0T@;&1Y(",D,# -(&QD>" C,C0@.V%34U5-
|
|
M24Y'H$%,3*!42%)%1:!"549&15)3H$%210T@;&1A(",D,# @.T)!0TLM5$\M
|
|
M0D%#2PTZ8FQO;W @<W1A("AB=69F97(I+'D-(&EN>0T@8FYE(#IB;&]O< T@
|
|
M:6YC(&)U9F9E<BLQ#2!D97@-(&)N92 Z8FQO;W -#2HJ*BJ@<T54H%50H$)5
|
|
M1D9%4E,-#2!L9&$@(SQB=69F,0T@<W1A(&)U9F9E<@T@;&1A(",^8G5F9C$-
|
|
M('-T82!B=69F97(K,0T@<W1A('IT96UP(#M:5$5-4*!724Q,H$U!2T6@3$E&
|
|
M1:!324U03$6@1D]2H%53#2!L9&$@=FUC<V(-(&%N9" C)3$Q,3$P,# Q(#MS
|
|
M5$%25*!(15)%H%-/H%1(052@4U=!4*!"549&15)3H%=)3$R@5T]22Z!224=(
|
|
M5 T@;W)A(",E,# P,#$Q,3 -('-T82!V;6-S8@T-*BHJ*J!S152@55"@24Y)
|
|
M5$E!3*!604Q515,-#6EN:70@;&1A(",P, T@<W1A(&QO8WAM:6X-('-T82!L
|
|
M;V-X;6%X#2!S=&$@;&]C>6UI;@T@<W1A(&QO8WEM87@-('-T82!G;&]B>&UI
|
|
M;@T@<W1A(&=L;V)Y;6EN#2!S=&$@9VQO8GAM87@-('-T82!G;&]B>6UA> T@
|
|
M<W1A(&1S> T@<W1A(&1S>0T@<W1A(&1S>@T@<W1A('-X#2!S=&$@<WD-('-T
|
|
M82!S>@T@<W1A(&9I;&P-(&QD82 C,#$-('-T82!H:61E#2!L9&$@(S8T#2!S
|
|
M=&$@>F]O;0T-*BTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-*J!M
|
|
M04E.H$Q/3U -#2HJ*BJ@9T54H$M%65!215-3#0UM86EN#2!C;&D-:W!R97-S
|
|
M(&IS<B!G971I;@T@8VUP(",Q,S,@.V8Q/PT@8FYE(#IF,@T@;&1A(&1S> T@
|
|
M8VUP("-A;F=M87@O,B [;D^@34]21:!42$%.H%!)#2!B97$@.F-O;G0Q#2!I
|
|
M;F,@9'-X(#M/5$A%4E=)4T6@24Y#4D5!4T6@6"U23U1!5$E/3@T@:FUP(#IC
|
|
M;VYT#3IF,B!C;7 @(S$S-R [9C(_#2!B;F4@.F8S#2!L9&$@9'-X#2!B97$@
|
|
M.F-O;G0Q#2!D96,@9'-X#2!J;7 @.F-O;G0-.F8S(&-M<" C,3,T#2!B;F4@
|
|
M.F8T#2!L9&$@9'-Y#2!C;7 @(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@
|
|
M9'-Y(#MI3D-214%31:!9+5)/5$%424].#2!J;7 @.F-O;G0-.F8T(&-M<" C
|
|
M,3,X#2!B;F4@.F8U#2!L9&$@9'-Y#2!B97$@.F-O;G0Q#2!D96,@9'-Y#2!J
|
|
M;7 @.F-O;G0-.F8U(&-M<" C,3,U#2!B;F4@.F8V#2!L9&$@9'-Z#2!C;7 @
|
|
M(V%N9VUA>"\R#2!B97$@.F-O;G0Q#2!I;F,@9'-Z(#M:+5)/5$%424].#2!J
|
|
M;7 @.F-O;G0-.F8V(&-M<" C,3,Y#2!B;F4@.F8W#2!L9&$@9'-Z#2!B97$@
|
|
M.F-O;G0Q#2!D96,@9'-Z#2!J;7 @.F-O;G0-.F8W(&-M<" C,3,V#2!B;F4@
|
|
M.G!L=7,-(&IM<"!I;FET#3IC;VYT,2!J;7 @.F-O;G0-.G!L=7,@8VUP(",G
|
|
M*R<-(&)N92 Z;6EN=7,-(&EN8R!Z;V]M(#MB04@LH%=(3Z!.145$4Z!%4E)/
|
|
M4J!#2$5#2TE.1S\-(&EN8R!Z;V]M#2!J;7 @.F-O;G0-.FUI;G5S(&-M<" C
|
|
M)RTG#2!B;F4@.F@-(&1E8R!Z;V]M#2!D96,@>F]O;0T@8G!L(#IC;VYT#2!I
|
|
M;F,@>F]O;0T@:6YC('IO;VT-(&IM<" Z8V]N= TZ:"!C;7 @(R=()PT@8FYE
|
|
M(#IS<&%C90T@;&1A(&AI9&4-(&5O<B C)# Q#2!S=&$@:&ED90T@:FUP(#IC
|
|
M;VYT#3IS<&%C92!C;7 @(R>@)PT@8FYE(#IQ#2!L9&$@9FEL; T@96]R(",D
|
|
M,#$-('-T82!F:6QL#2!J;7 @.F-O;G0-.G$@8VUP(",G42<@.U&@455)5%,-
|
|
M(&)N92 Z8V]N= T@:FUP(&-L96%N=7 -#3IC;VYT('-E:2 @.W-0145$H%1(
|
|
M24Y'4Z!54*!!H$))5 T-*BHJ*J!U4$1!5$6@04Y'3$53#0UU<&1A=&4@8VQC
|
|
M#2!L9&$@<W@-(&%D8R!D<W@-(&-M<" C86YG;6%X(#MA4D6@5T6@/CV@34%8
|
|
M24U53:!!3D=,13\-(&)C8R Z8V]N=#$-('-B8R C86YG;6%X(#II1B!33RP@
|
|
M4D53150-.F-O;G0Q('-T82!S> T@8VQC#2!L9&$@<WD-(&%D8R!D<WD-(&-M
|
|
M<" C86YG;6%X#2!B8V,@.F-O;G0R#2!S8F,@(V%N9VUA>" [<T%-1:!$14%,
|
|
M#3IC;VYT,B!S=&$@<WD-(&-L8PT@;&1A('-Z#2!A9&,@9'-Z#2!C;7 @(V%N
|
|
M9VUA> T@8F-C(#IC;VYT,PT@<V)C("-A;F=M87@-.F-O;G0S('-T82!S>@T-
|
|
M*BHJ*J!R3U1!5$6@0T]/4D1)3D%415,-#7)O=&%T90T-*BHJH&9)4E-4+*!#
|
|
M04Q#54Q!5$6@5#$L5#(L+BXN+%0Q, T-*BJ@=%=/H$U!0U)/4Z!43Z!324U0
|
|
M3$E&6:!/55*@3$E&10UA9&1A(&UA8R @.V%$1*!45T^@04Y'3$53H%1/1T54
|
|
M2$52#2!C;&,-(&QD82!=,0T@861C(%TR#2!C;7 @(V%N9VUA>" [:5.@5$A%
|
|
MH%-53: ^H#(J4$D_#2!B8V,@9&]N90T@<V)C("-A;F=M87@@.VE&H%-/+*!3
|
|
M54)44D%#5* R*E!)#61O;F4@/#P\#0US=6)A(&UA8R @.W-50E1204-4H%17
|
|
M3Z!!3D=,15,-('-E8PT@;&1A(%TQ#2!S8F,@73(-(&)C<R!D;VYE#2!A9&,@
|
|
M(V%N9VUA>" [;T]04RR@5T6@3D5%1*!43Z!!1$2@,BI020UD;VYE(#P\/ T-
|
|
M*BJ@;D]7H$-!3$-53$%41:!4,2Q4,BQ%5$,N#0T@/CX^('-U8F$L<WD[<WH-
|
|
M('-T82!T,2 [5#$]4UDM4UH-(#X^/B!A9&1A+'-Y.W-Z#2!S=&$@=#(@.U0R
|
|
M/5-9*U-:#2 ^/CX@861D82QS>#MS>@T@<W1A('0S(#M4,SU36"M36@T@/CX^
|
|
M('-U8F$L<W@[<WH-('-T82!T-" [5#0]4U@M4UH-(#X^/B!A9&1A+'-X.W0R
|
|
M#2!S=&$@=#4@.U0U/5-8*U0R#2 ^/CX@<W5B82QS>#MT,0T@<W1A('0V(#M4
|
|
M-CU36"U4,0T@/CX^(&%D9&$L<W@[=#$-('-T82!T-R [5#<]4U@K5#$-(#X^
|
|
M/B!S=6)A+'0R.W-X#2!S=&$@=#@@.U0X/50R+5-8#2 ^/CX@<W5B82QS>3MS
|
|
M> T@<W1A('0Y(#M4.3U362U36 T@/CX^(&%D9&$L<W@[<WD-('-T82!T,3 @
|
|
M.U0Q,#U36"M360T-*J!E5*!63TE,02$-#2HJ*J!N15A4+*!#04Q#54Q!5$6@
|
|
M82QB+&,L+BXN+&D-#2HJH&%.3U1(15*@55-%1E5,H$Q)5%1,1:!-04-23PUD
|
|
M:78R(&UA8R @.V1)5DE$1:!!H%-)1TY%1*!.54U"15*@0EF@,@T[:52@25.@
|
|
M05-354U%1*!42$%4H%1(1:!.54U"15(-(&)P;"!P;W,@.TE3H$E.H%1(1:!!
|
|
M0T-5355,051/4@T@8VQC#2!E;W(@(R1F9B [=T6@3D5%1*!43Z!53BU.14=!
|
|
M5$E61:!42$6@3E5-0D52#2!A9&,@(S Q(#M"6:!404M)3D>@250G4Z!#3TU0
|
|
M3$5-14Y4#2!L<W(@(#M$259)1$6@0EF@5%=/#2!C;&,-(&5O<B C)&9F#2!A
|
|
M9&,@(S Q(#MM04M%H$E4H$Y%1T%4259%H$%'04E.#2!J;7 @9&]N961I=@UP
|
|
M;W,@;'-R(" [;E5-0D52H$E3H%!/4TE4259%#61O;F5D:78@/#P\#0UM=6PR
|
|
M(&UA8R @.VU53%1)4$Q9H$&@4TE'3D5$H$Y534)%4J!"6: R#2!B<&P@<&]S
|
|
M;0T@8VQC#2!E;W(@(R1F9@T@861C(",D,#$-(&%S; T@8VQC#2!E;W(@(R1F
|
|
M9@T@861C(",D,#$-(&IM<"!D;VYE;75L#7!O<VT@87-L#61O;F5M=6P@/#P\
|
|
M#0TJ*J!N3U1%H%1(052@5T6@05)%H$-54E)%3E1,6:!-04M)3D>@0:!-24Y/
|
|
M4J!,14%0#2HJH$]&H$9!251(H%1(052@3D^@3U9%4D9,3U=3H%=)3$R@3T-#
|
|
M55(N#0TZ8V%L8V$@8VQC#2!L9'@@=#$-(&QD82!C;W,L> T@;&1X('0R#2!A
|
|
M9&,@8V]S+'@-('-T82!A,3$@.V$]*$-/4RA4,2DK0T]3*%0R*2DO,@TZ8V%L
|
|
M8V(@;&1X('0Q#2!L9&$@<VEN+'@-('-E8PT@;&1X('0R#2!S8F,@<VEN+'@-
|
|
M('-T82!B,3(@.V(]*%-)3BA4,2DM4TE.*%0R*2DO,@TZ8V%L8V,@;&1X('-Y
|
|
M#2!L9&$@<VEN+'@-(#X^/B!M=6PR#2!S=&$@8S$S(#MC/5-)3BA362D-.F-A
|
|
M;&-D('-E8PT@;&1X('0X#2!L9&$@8V]S+'@-(&QD>"!T-PT@<V)C(&-O<RQX
|
|
M#2!S96,-(&QD>"!T-0T@<V)C(&-O<RQX#2!C;&,-(&QD>"!T-@T@861C(&-O
|
|
M<RQX(#MD23TH0T]3*%0X*2U#3U,H5#<I*T-/4RA4-BDM0T]3*%0U*2DO,@T@
|
|
M/CX^(&1I=C(-(&-L8PT@;&1X('0S#2!A9&,@<VEN+'@-('-E8PT@;&1X('0T
|
|
M#2!S8F,@<VEN+'@-('-T82!D,C$@.V0]*%-)3BA4,RDM4TE.*%0T*2MD22DO
|
|
M,@TZ8V%L8V4@<V5C#2!L9'@@=#4-(&QD82!S:6XL> T@;&1X('0V#2!S8F,@
|
|
M<VEN+'@-('-E8PT@;&1X('0W#2!S8F,@<VEN+'@-('-E8PT@;&1X('0X#2!S
|
|
M8F,@<VEN+'@@.V5)/2A324XH5#4I+5-)3BA4-BDM4TE.*%0W*2U324XH5#@I
|
|
M*2\R#2 ^/CX@9&EV,@T@8VQC#2!L9'@@=#,-(&%D8R!C;W,L> T@8VQC#2!L
|
|
M9'@@=#0-(&%D8R!C;W,L> T@<W1A(&4R,B [93TH0T]3*%0S*2M#3U,H5#0I
|
|
M*V5)*2\R#3IC86QC9B!L9'@@=#D-(&QD82!S:6XL> T@<V5C#2!L9'@@=#$P
|
|
M#2!S8F,@<VEN+'@-('-T82!F,C,@.V8]*%-)3BA4.2DM4TE.*%0Q,"DI+S(-
|
|
M.F-A;&-G(&QD>"!T-@T@;&1A('-I;BQX#2!S96,-(&QD>"!T. T@<V)C('-I
|
|
M;BQX#2!S96,-(&QD>"!T-PT@<V)C('-I;BQX#2!S96,-(&QD>"!T-0T@<V)C
|
|
M('-I;BQX(#MG23TH4TE.*%0V*2U324XH5#@I+5-)3BA4-RDM4TE.*%0U*2DO
|
|
M,@T@/CX^(&1I=C(-(&-L8PT@;&1X('0T#2!A9&,@8V]S+'@-('-E8PT@;&1X
|
|
M('0S#2!S8F,@8V]S+'@-('-T82!G,S$@.V<]*$-/4RA4-"DM0T]3*%0S*2MG
|
|
M22DO,@TZ8V%L8V@@8VQC#2!L9'@@=#8-(&QD82!C;W,L> T@;&1X('0W#2!A
|
|
M9&,@8V]S+'@-('-E8PT@;&1X('0U#2!S8F,@8V]S+'@-('-E8PT@;&1X('0X
|
|
M#2!S8F,@8V]S+'@@.VA)/2A#3U,H5#8I*T-/4RA4-RDM0T]3*%0U*2U#3U,H
|
|
M5#@I*2\R#2 ^/CX@9&EV,@T@8VQC#2!L9'@@=#,-(&%D8R!S:6XL> T@8VQC
|
|
M#2!L9'@@=#0-(&%D8R!S:6XL> T@<W1A(&@S,B [:#TH4TE.*%0S*2M324XH
|
|
M5#0I*VA)*2\R#3IW:&5W(&-L8PT@;&1X('0Y#2!L9&$@8V]S+'@-(&QD>"!T
|
|
M,3 -(&%D8R!C;W,L> T@<W1A(&DS,R [:3TH0T]3*%0Y*2M#3U,H5#$P*2DO
|
|
M,@T-*BJ@:50G4Z!!3$R@1$]73DA)3$R@1E)/3:!(15)%+@T-9&]W;FAI;&P-
|
|
M*BHJ*J!C3$5!4J!"549&15(-*J!AH$Q)5%1,1:!-04-23PT-<V5T8G5F(&UA
|
|
M8R @.W!55*!"549&15)3H%=(15)%H%1(15F@0T%.H$)%H$A54E0-(&QD82 C
|
|
M,# -('-T82!B=69F97(-(&QD82!Z=&5M<" [:$E'2*!"651%#7-T86)U9B!S
|
|
M=&$@8G5F9F5R*S$-(#P\/ T-(#X^/B!S971B=68-8VQR9')A=R!L9'@@(S X
|
|
M#2!L9&$@(S P#3IF;V]L(&QD>2 C,# -.F1O<&4@<W1A("AB=69F97(I+'D-
|
|
M(&EN>0T@8FYE(#ID;W!E#2!I;F,@8G5F9F5R*S$-(&1E> T@8FYE(#IF;V]L
|
|
M#0TJ*BHJH&U9H$=/3T1.15-3H$)55*!I)TV@0:!$3U!%#2IC;')D<F%WH&QD
|
|
M8:!G;&]B>&UI;@TJH&QS<J"@.VY%142@5$^@1T54H$E.5$^@5$A%H%))1TA4
|
|
MH$-/3%5-3@TJH&)C8Z Z979E;J [95A03$%)3D5$H$E.H$U/4D6@1$5404E,
|
|
MH$)%3$]7#2J@;&1YH",D.# -*J!S='F@8G5F9F5RH#MP4D5354U!0DQ9H%1(
|
|
M25.@5TE,3*!"1:!!H$Q)5%1,10TJH&-L8Z"@.TU/4D6@149&24-)14Y4+@TJ
|
|
M.F5V96Z@861CH&)U9F9E<BLQ#2J@<W1AH&)U9F9E<BLQ#2J@;&1AH&=L;V)X
|
|
M;6%X#2J@<V5C#2J@<V)CH&=L;V)X;6EN#2J@=&%X#2J@:6YX#2J@;&1YH&=L
|
|
M;V)Y;6%X#2J@8F5QH#IR97-E= TJ.GEA>:!L9&&@(R0P, TJH&QD>:!G;&]B
|
|
M>6UA> TJ.F)L86B@<W1AH"AB=69F97(I+'D-*J!D97D-*J!C<'F@9VQO8GEM
|
|
M:6X-*J!B8W.@.F)L86@-*J!L9&&@8G5F9F5R#2J@96]RH",D.# -*J!S=&&@
|
|
M8G5F9F5R#2J@8FYEH#IW:&]P964-*J!I;F.@8G5F9F5R*S$-*CIW:&]P966@
|
|
M9&5X#2J@8FYEH#IY87D-*CIR97-E=*!L9&&@(S"@.VY%142@5$^@4D53152@
|
|
M5$A%4T6@1U594PTJH'-T8:!G;&]B>&UA> TJH'-T8:!G;&]B>6UA> TJH&QD
|
|
M8: C)&9F#2J@<W1AH&=L;V)X;6EN#2J@<W1AH&=L;V)Y;6EN#0TJ*BHJH&Y%
|
|
M6%0LH%)%042@04Y$H$1205>@4$],64=/3E,-#7)E861D<F%W(&QD>2 C,# -
|
|
M('-T>2!I;F1E> UO8FIL;V]P(&QD>2!I;F1E> T@;&1A('!O;'EL:7-T+'D@
|
|
M.V9)4E-4+*!42$6@3E5-0D52H$]&H%!/24Y44PT@8FYE(#IC;VYT(#MB552@
|
|
M24:@3E5-4$])3E13H$E3H%I%4D^@5$A%3@T@:FUP(&]B:F1O;F4@.U=%H$%2
|
|
M1:!!5*!42$6@14Y$H$]&H%1(1:!,25-4#3IC;VYT('-T82!C;W5N='!T<PT@
|
|
M:6YC(&EN9&5X#0TJH')/5$%41:!04D]*14-4H$%.1*!$4D%7H%1(1:!03TQ9
|
|
M1T].#2J@;4%+1:!355)%H$)51D9%4J!"14E.1Z!$4D%73J!43Z!)4Z!#3$5!
|
|
M4B$-#3ID;VET(&IS<B!R;W1P<F]J#0TJH&-/3E9%4E2@6$U)3J!!3D2@6$U!
|
|
M6*!43Z!#3TQ534Y3#0T@;&1A(&QO8WAM:6X-(&QS<@T@;'-R#2!L<W(@(#M8
|
|
MH$U/1* X#2!S=&$@;&]C>&UI;@T@8VUP(&=L;V)X;6EN#2!B8W,@.FYA: T@
|
|
M<W1A(&=L;V)X;6EN#3IN86@@;&1A(&QO8WEM:6X-(&-M<"!G;&]B>6UI;@T@
|
|
M8F-S(#IU:'5H#2!S=&$@9VQO8GEM:6X-.G5H=6@@;&1A(&QO8WAM87@-(&QS
|
|
M<@T@;'-R#2!L<W(-('-T82!L;V-X;6%X#2!C;7 @9VQO8GAM87@-(&)C8R Z
|
|
M;F]W87D-('-T82!G;&]B>&UA> TZ;F]W87D@;&1A(&QO8WEM87@-(&-M<"!G
|
|
M;&]B>6UA> T@8F-C(&5O<F9I;&P-('-T82!G;&]B>6UA> T-*J!I1J!54TE.
|
|
M1Z!42$6@96]R+4)51D9%4BR@0T]06:!)3E1/H$1205=)3D>@0E5&1D52#2J@
|
|
M84Y$H%1(14Z@0TQ%05*@5$A%H&5O<BU"549&15(-#65O<F9I;&P@;&1A(&9I
|
|
M;&P-(&)E<2!O8FIL;V]P#0T@/CX^('-E=&)U9@T@;&1A(",\96]R8G5F#2!S
|
|
M=&$@=&5M<#$-(&QD82 C/F5O<F)U9@T@<W1A('1E;7 Q*S$-#2!L9&$@;&]C
|
|
M>&UI;B [;&]C>&UI;J!.3U>@0T].5$%)3E.@0T],54U.#2!L<W(@(#ME04-(
|
|
MH$-/3%5-3J!)4Z Q,CB@0EE415,-(&)C8R Z979E;B [<T^@5$A%4D6@34E'
|
|
M2%2@0D6@0:!#05)260T@;&1Y(",D.# -('-T>2!B=69F97(-('-T>2!T96UP
|
|
M,0T@8VQC#3IE=F5N('-T82!T,@T@861C(&)U9F9E<BLQ#2!S=&$@8G5F9F5R
|
|
M*S$@.V5!0TB@0T],54U.H$E3H#$R.*!"651%4PT@;&1A('0R#2!A9&,@=&5M
|
|
M<#$K,2 [;D]7H%=%H%=)3$R@4U1!4E2@052@5$A%#2!S=&$@=&5M<#$K,2 [
|
|
M0T],54U.#0T@;&1A(&QO8WAM87@-('-E8PT@<V)C(&QO8WAM:6X-('1A>" [
|
|
M=$]404R@3E5-0D52H$]&H$-/3%5-3E.@5$^@1$\-(&EN>" @.T4N1RZ@1DE,
|
|
M3*!#3TQ534Y3H#$N+C,-(&QD>2!L;V-Y;6%X#2!B;F4@.F9O;W -(&EN8R!L
|
|
M;V-Y;6%X#3IF;V]P(&QD>2!L;V-Y;6%X#2!L9&$@(S P#3IG;V]P(&5O<B H
|
|
M=&5M<#$I+'D@.V5O<BU"549&15(-('!H80TJH&U!64)%H%!55*!!3J!E;W*@
|
|
M0D5,3U<_#2!E;W(@*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-(&QD82 C
|
|
M,# @.VU)1TA4H$%3H%=%3$R@0TQ%05*@252@3D]7#2!S=&$@*'1E;7 Q*2QY
|
|
M#2!P;&$-(&1E>0T@8W!Y(&QO8WEM:6X-(&)C<R Z9V]O< T@;&1A(&)U9F9E
|
|
M<@T@96]R(",D.# -('-T82!B=69F97(-('-T82!T96UP,0T@8FYE(#IB;V]P
|
|
M#2!I;F,@8G5F9F5R*S$-(&EN8R!T96UP,2LQ#3IB;V]P(&1E> T@8FYE(#IF
|
|
M;V]P#2!J;7 @;V)J;&]O< T-;V)J9&]N90TJ*BHJH'-705"@0E5&1D524PT-
|
|
M<W=A<&)U9B!L9&$@=FUC<V(-(&5O<B C)# R(#MP4D545%F@5%))0TM9+*!%
|
|
M2#\-('-T82!V;6-S8@T@;&1A(",D,#@-(&5O<B!Z=&5M<" [6E1%35 ]2$E'
|
|
M2*!"651%H$I54U2@1DQ)4%,-('-T82!Z=&5M<" [0D545T5%3J D,S"@04Y$
|
|
MH"0S. T-(&IM<"!M86EN(#MA4D]53D2@04Y$H$%23U5.1*!71:!'3RXN+@T-
|
|
M('1X=" G9T5%H&)204E.+*!72$%4H$1/H%E/5:!704Y4H%1/H$1/H"<-('1X
|
|
M=" G5$].24=(5#\G#0TJ*J!R3U1!5$4LH%!23TI%0U0LH$%.1*!35$]21:!4
|
|
M2$6@4$])3E13#2H-*J!T2$E3H%!!4E2@25.@0:!324=.249)0T%.5*!#2$%.
|
|
M1T6@4TE.0T4-*J!6,BXP+J"@;D]7H$E4H$E3H$&@0T]-4$Q%5$5,6:!'14Y%
|
|
M4D%,H%!/3%E'3TZ@4$Q/5%1%4BX-*J!AH%-%5*!/1J!03TE.5%.@25.@4D5!
|
|
M1*!)3BR@4D]4051%1*!!3D2@4%)/2D5#5$5$+*!!3D0-*J!03$]45$5$H$E.
|
|
M5$^@5$A%H$1205=)3D>@0E5&1D52H"AE;W*@3U*@3D]234%,*2X-#7)O='!R
|
|
M;VH-#2J@8:!.14%4H$U!0U)/#6YE9R!M86,@(#MC2$%.1T6@5$A%H%-)1TZ@
|
|
M3T:@0:!45T\G4Z!#3TU03$5-14Y4#2!C;&,-(&QD82!=,2 [3E5-0D52+@T@
|
|
M96]R(",D9F8-(&%D8R C)# Q#2 \/#P-#2HM+2TM+2TM+2TM+2TM+2TM+2TM
|
|
M+2TM+2TM+2TM+2TM#2J@=$A%4T6@34%#4D]3H%)%4$Q!0T6@5$A%H%!2159)
|
|
M3U53H%!23TI%0U1)3TX-*J!354)23U5424Y%+@T-<VUU;'2@;6%CH#MM54Q4
|
|
M25!,6:!45T^@4TE'3D5$H#@M0DE4#2 [3E5-0D524SJ@82IY+S8TH"T^H&$-
|
|
M('-T8:!Z,PT@8VQCH* [=$A)4Z!-54Q425!,6:!)4Z!&3U*@3D]234%,#2!E
|
|
M;W*@(R1F9J [3E5-0D524RR@22Y%+J!8/2TV-"XN-C0-(&%D8Z C)# Q#2!S
|
|
M=&&@>C0-(&QD8: H>C,I+'D-('-E8PT@<V)CH"AZ-"DL>0T@/#P\H* [84Q,
|
|
MH$1/3D6@.BD-#7-M=6QT>B!M86,@.VU53%1)4$Q9H%173Z!324=.142@."U"
|
|
M250-(" @.TY534)%4E,ZH&$J>2\V-* M/J!A#2!S=&$@>C$-(&-L8R @.V%.
|
|
M1*!42$E3H$U53%1)4$Q9H$E3H%-014-)1DE#04Q,60T@96]R(",D9F8@.T9/
|
|
M4J!42$6@4%)/2D5#5$E/3J!005)4+*!72$5210T@861C(",D,#$@.TY534)%
|
|
M4E.@05)%H"TQ,3 N+C$Q,*!!3D2@,"XN-# -('-T82!Z,@T@;&1A("AZ,2DL
|
|
M>0T@<V5C#2!S8F,@*'HR*2QY#2 \/#P@(#MA3$R@1$].1: Z*0T-<')O:F5C
|
|
M="!M86,@(#MT2$6@04-454%,H%!23TI%0U1)3TZ@4D]55$E.10T[=$A%H%)/
|
|
M551)3D6@5$%+15.@5$A%H%!/24Y4#3M=,:!=,J!=,RR@4D]4051%4Z!!3D0-
|
|
M.U!23TI%0U13H$E4+*!!3D2@4U1/4D53H%1(10T[4D5354Q4H$E.H%TQH%TR
|
|
MH%TS+@T-(&QD>2!=,2 [;55,5$E03%F@1DE24U2@4D]4051)3TZ@0T],54U.
|
|
M#2!L9&$@83$Q#2 ^/CX@<VUU;'0-('-T82!P,70-(&QD82!D,C$-(#X^/B!S
|
|
M;75L= T@<W1A(' R= T@;&1A(&<S,0T@/CX^('-M=6QT#2!S=&$@<#-T#2!L
|
|
M9'D@73(@.W-%0T].1*!#3TQ534X-(&QD82!B,3(-(#X^/B!S;75L= T@8VQC
|
|
M#2!A9&,@<#%T#2!S=&$@<#%T#2!L9&$@93(R#2 ^/CX@<VUU;'0-(&-L8PT@
|
|
M861C(' R= T@<W1A(' R= T@;&1A(&@S,@T@/CX^('-M=6QT#2!C;&,-(&%D
|
|
M8R!P,W0-('-T82!P,W0-(&QD>2!=,R [=$A)4D2@0T],54U.#2!L9&$@8S$S
|
|
M#2 ^/CX@<VUU;'0-(&-L8PT@861C(' Q= T@<W1A(' Q= T@;&1A(&8R,PT@
|
|
M/CX^('-M=6QT#2!C;&,-(&%D8R!P,G0-('-T82!P,G0-(&QD82!I,S,-(#X^
|
|
M/B!S;75L= T@8VQC#2!A9&,@<#-T#2!S=&$@73,@.W)/5$%4142@>@T@=&%X
|
|
M#2!L9'D@>F1I=BQX(#MT04),1:!/1J!$+RA:*UHP*0T@(" [;D]7H'F@0T].
|
|
M5$%)3E.@4%)/2D5#5$E/3J!#3TY35 T-(&QD82!P,70-(#X^/B!S;75L='H-
|
|
M(&QD>"!Z;V]M#2!C<'@@(S8T#2!B97$@8V]N='@-('-T>2!T96UP,0T@;&1Y
|
|
M('IO;VT-(#X^/B!S;75L= T@;&1Y('1E;7 Q#6-O;G1X(&-L8PT@861C(",V
|
|
M-" [;T9&4T54H%1(1:!#3T]21$E.051%#2!S=&$@73$@.W)/5$%4142@04Y$
|
|
MH%!23TI%0U1%1 T@8VUP(&QO8WAM:6X@.W-%1:!)1J!)5*!)4Z!!H$Q/0T%,
|
|
MH$U)3DE-54T-(&)C<R!N;W1X;6EN#2!S=&$@;&]C>&UI;@UN;W1X;6EN(&-M
|
|
M<"!L;V-X;6%X#2!B8V,@;F]T>&UA> T@<W1A(&QO8WAM87@-#6YO='AM87@@
|
|
M;&1A(' R= T@/CX^('-M=6QT>@T@8W!X(",V- T@8F5Q(&-O;G1Y#2!L9'D@
|
|
M>F]O;0T@/CX^('-M=6QT#6-O;G1Y(&-L8PT@861C(",V- T@<W1A(%TR(#MR
|
|
M3U1!5$5$H$%.1*!04D]*14-4142@>0T@8VUP(&QO8WEM:6X-(&)C<R!N;W1Y
|
|
M;6EN#2!S=&$@;&]C>6UI;@UN;W1Y;6EN(&-M<"!L;V-Y;6%X#2!B8V,@;F]T
|
|
M>6UA> T@<W1A(&QO8WEM87@-#6YO='EM87@@/#P\(" [84Q,H$1/3D4-#2J@
|
|
M;&1AH",\96]R8G5FH#MF25)35*!71:!.145$H%1/H$-,14%2H%1(10TJH'-T
|
|
M8:!B=69F97*@.V5O<J!"549&15(-*J!L9&&@(SYE;W)B=68-*J!S=&&@8G5F
|
|
M9F5R*S$-#2!L9&$@(S @.W)%4T54H'E-24Z@04Y$H'E-05@-('-T82!L;V-Y
|
|
M;6%X#2!S=&$@;&]C>&UA> T@;&1A(",D9F8-('-T82!L;V-Y;6EN#2!S=&$@
|
|
M;&]C>&UI;@T-<F5A9'!T<R!L9'D@:6YD97@-(&QD82!P;VQY;&ES="QY#2!S
|
|
M=&$@<#%X#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#%Y#2!I;GD-(&QD
|
|
M82!P;VQY;&ES="QY#2!S=&$@<#%Z#2!I;GD-(&1E8R!C;W5N='!T<PT@;&1A
|
|
M('!O;'EL:7-T+'D-('-T82!P,G@-(&EN>0T@;&1A('!O;'EL:7-T+'D-('-T
|
|
M82!P,GD-(&EN>0T@;&1A('!O;'EL:7-T+'D-('-T82!P,GH-(&EN>0T@9&5C
|
|
M(&-O=6YT<'1S#2!L9&$@<&]L>6QI<W0L>0T@<W1A(' S> T@:6YY#2!L9&$@
|
|
M<&]L>6QI<W0L>0T@<W1A(' S>0T@:6YY#2!L9&$@<&]L>6QI<W0L>0T@<W1A
|
|
M(' S>@T@:6YY#2!S='D@:6YD97@-(#X^/B!P<F]J96-T+' Q>#MP,7D[<#%Z
|
|
M#2 ^/CX@<')O:F5C="QP,G@[<#)Y.W R>@T@/CX^('!R;VIE8W0L<#-X.W S
|
|
M>3MP,WH-#2!L9&$@:&ED90T@8F5Q(#ID;VET#2!L9&$@<#)X(#MH241$14Z@
|
|
M1D%#1:!#2$5#2PT@<V5C#2!S8F,@<#%X#2!T87D@(#MY/2A8,BU8,2D-(&QD
|
|
M82!P,WD-('-E8PT@<V)C(' R>2 [83TH63,M63(I#2 ^/CX@<VUU;'0-('-T
|
|
M82!T96UP,0T@;&1A(' S> T@<V5C#2!S8F,@<#)X#2!T87D-(&QD82!P,GD-
|
|
M('-E8PT@<V)C(' Q>0T@/CX^('-M=6QT#2!C;7 @=&5M<#$@.VE&H%@Q*EDR
|
|
M+5DQ*E@RH#Z@,*!42$5.H$9!0T4-(&)M:2 Z9&]I=" [25.@5DE324),10T@
|
|
M9&5C(&-O=6YT<'1S(#MO5$A%4E=)4T6@4D5!1*!)3J!214U!24Y)3D<-(&)E
|
|
M<2 Z86)O<G0@.U!/24Y44Z!!3D2@4D5455).#3IP;V]P(&EN8R!I;F1E> T@
|
|
M:6YC(&EN9&5X#2!I;F,@:6YD97@-(&1E8R!C;W5N='!T<PT@8FYE(#IP;V]P
|
|
M#3IA8F]R="!R=',-#3ID;VET(&QD82!P,7@-('-T82!X,0T@;&1A(' Q>0T@
|
|
M<W1A('DQ#2!L9&$@<#)X#2!S=&$@>#(-(&QD82!P,GD-('-T82!Y,@T@:G-R
|
|
M(&1R87<-(&QD82!P,G@-('-T82!X,0T@;&1A(' R>0T@<W1A('DQ#2!L9&$@
|
|
M<#-X#2!S=&$@>#(-(&QD82!P,WD-('-T82!Y,@T@:G-R(&1R87<-#2!D96,@
|
|
M8V]U;G1P=',-(&)N92!P;VQY;&]O<" [:5.@252@2E535*!!H%1224%.1TQ%
|
|
M/PT@:FUP('!O;'ED;VYE#0UP;VQY;&]O<"!L9'D@:6YD97@-(&QD82!P;VQY
|
|
M;&ES="QY#2!S=&$@<#)X#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#)Y
|
|
M#2!I;GD-(&QD82!P;VQY;&ES="QY#2!S=&$@<#)Z#2!I;GD-('-T>2!I;F1E
|
|
M> T@/CX^('!R;VIE8W0L<#)X.W R>3MP,GH-#2!L9&$@<#)X#2!S=&$@>#$-
|
|
M(&QD82!P,GD-('-T82!Y,0T@;&1A(' S> T@<W1A('@R#2!L9&$@<#-Y#2!S
|
|
M=&$@>3(-(&IS<B!D<F%W#0T@;&1A(' R> T@<W1A(' S> T@;&1A(' R>0T@
|
|
M<W1A(' S>0T@9&5C(&-O=6YT<'1S#2!B97$@<&]L>61O;F4-(&IM<"!P;VQY
|
|
M;&]O< UP;VQY9&]N92!L9&$@<#%X(#MC3$]31:!42$6@4$],64=/3@T@<W1A
|
|
M('@R#2!L9&$@<#%Y#2!S=&$@>3(-(&QD82!P,W@-('-T82!X,0T@;&1A(' S
|
|
M>0T@<W1A('DQ#2!J<W(@9')A=PT@<G1S#0T@='AT("=S04U%H%1(24Y'H%=%
|
|
MH$1/H$5615)9H$Y)1TA4+*!P24Y+63J@)PT@='AT("=44EF@5$^@5$%+1:!/
|
|
M5D52H%1(1:!73U),1"$G#0T-*BTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM
|
|
M+2TM+2T-*J!G14Y%4D%,H%%515-424].04),12U604Q51:!%4E)/4J!04D]#
|
|
M14154D4-#2IC:&]K9:!L9'B@(S P#2HZ;&]O<*!L9&&@.F-T97AT+'@-*J!B
|
|
M97&@.F1O;F4-*J!J<W*@8VAR;W5T#2J@:6YX#2J@:FUPH#IL;V]P#2HZ9&]N
|
|
M9:!R=',-*CIC=&5X=*!H97B@,&2@.V-R#2J@='ATH"=33TU%5$A)3D>@0TA/
|
|
M2T5$H#HH)PTJH&AE>* P9# P#2H-('1X=" G;D%21B$G#0TJ+2TM+2TM+2TM
|
|
M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH&1205=)3B>@0:!,24Y%+J"@8:!&
|
|
M04A.H$Q!2$XN#0TJ*BJ@<T]-1:!54T5&54R@34%#4D]3#0UC:6YI="!M86,@
|
|
M(#MM04-23Z!43Z!)3DE424%,25I%H%1(1:!#3U5.5$52#2!L9&$@73$@.T18
|
|
MH$]2H$19#2!L<W(-(#P\/" @.W1(1:!$6"\RH$U!2T53H$&@3DE#15*@3$]/
|
|
M2TE.1Z!,24Y%#0TJ*BHJ*J!M04-23Z!43Z!404M%H$&@4U1%4*!)3J!X#0UX
|
|
M<W1E<"!M86,-(&QD>"!D>" [;E5-0D52H$]&H$Q/3U"@251%4D%424].4PT@
|
|
M/CX^(&-I;FET+&1X#7AL;V]P(&QS<B!C:'5N:PT@8F5Q(&9I>&,@.W501$%4
|
|
M1:!#3TQ534X-('-B8R!D>0T@8F-C(&9I>'D@.W1)346@5$^@4U1%4*!)3J!Y
|
|
M#2!D97@-(&)N92!X;&]O< UD;VYE(&QD82!O;&1X(#MP3$]4H%1(1:!,05-4
|
|
MH$-(54Y+#2!E;W(@8VAU;FL-(&]R82 H8G5F9F5R*2QY#2!S=&$@*&)U9F9E
|
|
M<BDL>0T@<G1S#0UF:7AC('!H80T@;&1A(&]L9'@-(&]R82 H8G5F9F5R*2QY
|
|
M(#MP3$]4#2!S=&$@*&)U9F9E<BDL>0T@;&1A(",D9F8@.W501$%41:!#2%5.
|
|
M2PT@<W1A(&]L9'@-('-T82!C:'5N:PT@;&1A(",D.# @.VE.0U)%05-%H%1(
|
|
M1:!#3TQ534X-(&5O<B!B=69F97(-('-T82!B=69F97(-(&)N92!C,@T@:6YC
|
|
M(&)U9F9E<BLQ#6,R#2!P;&$-('-B8R!D>0T@8F-S(&-O;G0-(&%D8R!D> T@
|
|
M:68@:2Q=,2 [9$^@5T6@55-%H&EN>:!/4J!D97D_#2!I;GD-(&5L<V4-(&1E
|
|
M>0T@9FEN#6-O;G0@9&5X#2!B;F4@>&QO;W -(&IM<"!D;VYE#0UF:7AY(&%D
|
|
M8R!D> T@<&AA#2!L9&$@;VQD> T@96]R(&-H=6YK#2!O<F$@*&)U9F9E<BDL
|
|
M>0T@<W1A("AB=69F97(I+'D-(&QD82!C:'5N:PT@<W1A(&]L9'@-('!L80T@
|
|
M:68@:2Q=,2 [=5!$051%H'D-(&EN>0T@96QS90T@9&5Y#2!F:6X-(&1E> T@
|
|
M8FYE('AL;V]P#2!R=',-(#P\/" @.V5.1*!/1J!M04-23Z!84U1%4 T-*BHJ
|
|
M*BJ@=$%+1:!!H%-415"@24Z@>0T->7-T97 @;6%C#2!L9'@@9'D@.VY534)%
|
|
M4J!/1J!,3T]0H$E415)!5$E/3E,-(&)E<2!D;VYE(#MI1J!$63TPH$E4)U.@
|
|
M2E535*!!H%!/24Y4#2 ^/CX@8VEN:70L9'D-('-E8PUY;&]O<"!P:&$-(&QD
|
|
M82!O;&1X#2!O<F$@*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-('!L80T@
|
|
M:68@:2Q=,0T@:6YY#2!E;'-E#2!D97D-(&9I;@T@<V)C(&1X#2!B8V,@9FEX
|
|
M> T@9&5X#2!B;F4@>6QO;W -9&]N92!L9&$@;VQD> T@;W)A("AB=69F97(I
|
|
M+'D-('-T82 H8G5F9F5R*2QY#2!R=',-#69I>'@@861C(&1Y#2!L<W(@;VQD
|
|
M> T@<V5C(" [:4U03U)404Y4(0T@8F5Q(&9I>&,-(&1E> T@8FYE('EL;V]P
|
|
M#2!J;7 @9&]N90T-9FEX8R!P:&$-(&QD82 C)#@P#2!S=&$@;VQD> T@96]R
|
|
M(&)U9F9E<@T@<W1A(&)U9F9E<@T@8FYE(&,R#2!I;F,@8G5F9F5R*S$-8S(@
|
|
M<&QA#2!D97@-(&)N92!Y;&]O< T@:FUP(&1O;F4-(#P\/" @.V5.1*!/1J!M
|
|
M04-23Z!94U1%4 T-*J!T04M%H$%.H%B@4U1%4*!)3J!42$6@96]RH$)51D9%
|
|
M4@TJH'1(1:!33TQ%H$-(04Y'1:!)4Z!43Z!54T6@96]RH$E.4U1%042@3T:@
|
|
M;W)A#0UE;W)X<W1E<"!M86,-(&QD>"!D>" [;E5-0D52H$]&H$Q/3U"@251%
|
|
M4D%424].4PT@/CX^(&-I;FET+&1X#7AL;V]P(&QS<B!C:'5N:PT@8F5Q(&9I
|
|
M>&,@.W501$%41:!#3TQ534X-('-B8R!D>0T@8F-C(&9I>'D@.W1)346@5$^@
|
|
M4U1%4*!)3J!Y#2!D97@-(&)N92!X;&]O< UD;VYE(&QD82!O;&1X(#MP3$]4
|
|
MH%1(1:!,05-4H$-(54Y+#2!E;W(@8VAU;FL-(&5O<B H8G5F9F5R*2QY#2!S
|
|
M=&$@*&)U9F9E<BDL>0T@<G1S#0UF:7AC('!H80T@;&1A(&]L9'@-(&5O<B H
|
|
M8G5F9F5R*2QY(#MP3$]4#2!S=&$@*&)U9F9E<BDL>0T@;&1A(",D9F8@.W50
|
|
M1$%41:!#2%5.2PT@<W1A(&]L9'@-('-T82!C:'5N:PT@;&1A(",D.# @.VE.
|
|
M0U)%05-%H%1(1:!#3TQ534X-(&5O<B!B=69F97(-('-T82!B=69F97(-(&)N
|
|
M92!C,@T@:6YC(&)U9F9E<BLQ#6,R#2!P;&$-('-B8R!D>0T@8F-S(&-O;G0-
|
|
M(&%D8R!D> T@:68@:2Q=,2 [9$^@5T6@55-%H&EN>:!/4J!D97D_#2!I;GD-
|
|
M(&5L<V4-(&1E>0T@9FEN#6-O;G0@9&5X#2!B;F4@>&QO;W -(&IM<"!D;VYE
|
|
M#0UF:7AY(&%D8R!D> T@<&AA#2!L9&$@;VQD> T@96]R(&-H=6YK#2!E;W(@
|
|
M*&)U9F9E<BDL>0T@<W1A("AB=69F97(I+'D-(&QD82!C:'5N:PT@<W1A(&]L
|
|
M9'@-('!L80T@:68@:2Q=,2 [=5!$051%H'D-(&EN>0T@96QS90T@9&5Y#2!F
|
|
M:6X-(&1E> T@8FYE('AL;V]P#2!R=',-(#P\/" @.V5.1*!/1J!M04-23Z!8
|
|
M4U1%4 T-#2J@=$%+1:!!H%DM4U1%4*!)3J!42$6@96]R+4)51D9%4@TJH&-(
|
|
M04Y'15.@1E)/3:!!0D]61:!!4D4ZH$].3%F@4$Q/5*!,05-4H%!!4E2@3T:@
|
|
M14%#2 TJH%9%4E1)0T%,H$-(54Y++*!$3TXG5*!03$]4H$Q!4U2@4$])3E0L
|
|
MH%!,3U2@5TE42*!E;W(-#65O<GES=&5P(&UA8PT@;&1X(&1Y(#MN54U"15*@
|
|
M3T:@3$]/4*!)5$52051)3TY3#2!B97$@9&]N92 [:4:@1%D],*!)5"=3H$I5
|
|
M4U2@0:!03TE.5 T@/CX^(&-I;FET+&1Y#2!S96,-*GEL;V]PH'!H80TJH&QD
|
|
M8:!O;&1X#2J@;W)AH"AB=69F97(I+'D-*J!S=&&@*&)U9F9E<BDL>0TJH'!L
|
|
M80UY;&]O<"!I9B!I+%TQ#2!I;GD-(&5L<V4-(&1E>0T@9FEN#2!S8F,@9'@-
|
|
M(&)C8R!F:7AX#2!D97@-(&)N92!Y;&]O< TJ9&]N9:!L9&&@;VQD> TJH&]R
|
|
M8: H8G5F9F5R*2QY#2J@<W1AH"AB=69F97(I+'D-9&]N92!R=',-#69I>'@@
|
|
M861C(&1Y#2!P:&$@(#MW1:!/3DQ9H%!,3U2@5$A%H$Q!4U2@4$%25*!/1J!%
|
|
M04-(H$-(54Y+#2!L9&$@;VQD> T@96]R("AB=69F97(I+'D-('-T82 H8G5F
|
|
M9F5R*2QY#2!P;&$-(&QS<B!O;&1X#2!S96,@(#MI35!/4E1!3E0A#2!B97$@
|
|
M9FEX8PT@9&5X#2!B;F4@>6QO;W -(&IM<"!D;VYE#0UF:7AC('!H80T@;&1A
|
|
M(",D.# -('-T82!O;&1X#2!E;W(@8G5F9F5R#2!S=&$@8G5F9F5R#2!B;F4@
|
|
M8S(-(&EN8R!B=69F97(K,0UC,B!P;&$-(&1E> T@8FYE('EL;V]P#2!J;7 @
|
|
M9&]N90T@/#P\(" [94Y$H$]&H&U!0U)/H%E35$50#2HJ*BJ@:4Y)5$E!3*!,
|
|
M24Y%H%-%5%50#0TJ*J!T2$6@0T]-345.5$5$H$Q)3D53H$)%3$]7H$%21:!.
|
|
M3U>@5$%+14Z@0T%21:!/1J!"6:!42$4-*BJ@0T%,3$E.1Z!23U5424Y%+@TJ
|
|
M9')A=Z ^/CZ@;6]V92QT>#$[>#&@H#MM3U9%H%-4549&H$E.5$^@6D523Z!0
|
|
M04=%#2J@/CX^H&UO=F4L='@R.W@RH* [=TA%4D6@252@0T%.H$)%H$U/1$E&
|
|
M245$#2J@/CX^H&UO=F4L='DQ.WDQ#2J@/CX^H&UO=F4L='DR.WDR#0UD<F%W
|
|
M(&QD82!F:6QL#2!B;F4@.G-E=&5O<@T@/CX^('-E=&)U9@T@:FUP(#IS971U
|
|
M< TZ<V5T96]R(&QD82 C/&5O<F)U9B [=5-%H&5O<J!"549&15*@24Y35$5!
|
|
M1*!/1@T@<W1A(&)U9F9E<B [1$E34$Q!6:!"549&15*@1D]2H$1205=)3D<-
|
|
M(&QD82 C/F5O<F)U9@T@<W1A(&)U9F9E<BLQ#0TZ<V5T=7 @<V5C(" [;4%+
|
|
M1:!355)%H%@Q/%@R#2!L9&$@>#(-('-B8R!X,0T@8F-S(#IC;VYT#2!L9&$@
|
|
M>3(@.VE&H$Y/5"R@4U=!4*!P,:!!3D2@<#(-(&QD>2!Y,0T@<W1A('DQ#2!S
|
|
M='D@>3(-(&QD82!X,0T@;&1Y('@R#2!S='D@>#$-('-T82!X,@T-('-E8PT@
|
|
M<V)C('@Q(#MN3U>@83U$6 TZ8V]N="!S=&$@9'@-(&QD>"!X,2 [<%54H%@Q
|
|
MH$E.5$^@>"R@3D]7H%=%H$-!3J!44D%32*!X,0T-8V]L=6UN('1X82 [9DE.
|
|
M1*!42$6@1DE24U2@0T],54U.H$9/4J!X#2!L<W(-(&QS<B @.W1(15)%H$%2
|
|
M1:!8,2\XH#$R.*!"651%H$),3T-+4PT@;'-R(" [=TA)0TB@345!3E.@6#$O
|
|
M,3:@,C4VH$)95$6@0DQ/0TM3#2!L<W(-(&)C8R Z979E;B [=TE42*!!H%!/
|
|
M4U-)0DQ%H$585%)!H#$R.*!"651%H$),3T-+#2!L9'D@(R0X," [24:@4T\L
|
|
MH%-%5*!42$6@2$E'2*!"250-('-T>2!B=69F97(-(&-L8PTZ979E;B!A9&,@
|
|
M8G5F9F5R*S$@.V%$1*!)3J!42$6@3E5-0D52H$]&H#(U-J!"651%H$),3T-+
|
|
M4PT@<W1A(&)U9F9E<BLQ#0T@<V5C#2!L9&$@>3(@.V-!3$-53$%41:!$60T@
|
|
M<V)C('DQ#2!B8W,@.F-O;G0R(#MI4Z!9,CY9,3\-(&5O<B C)&9F(#MO5$A%
|
|
M4E=)4T6@1%D]63$M63(-(&%D8R C)# Q#3IC;VYT,B!S=&$@9'D-(&-M<"!D
|
|
M>" [=TA/)U.@0DE'1T52.J!$6:!/4J!$6#\-(&)C8R!S=&5P:6YX(#MI1J!$
|
|
M6"R@5$A%3BXN+@T@:FUP('-T97!I;GD-#7-T97!I;G@@;&1Y('DQ#2!C<'D@
|
|
M>3(-(&QD82!B:71P+'@@.WB@0U524D5.5$Q9H$-/3E1!24Y3H%@Q#2!S=&$@
|
|
M;VQD> T@<W1A(&-H=6YK#2!B8V,@>&EN8WD@.V1/H%=%H%-415"@1D]25T%2
|
|
M1%.@3U*@0D%#2U=!4D13H$E.H'D_#2!J;7 @>&1E8WD-#7AI;F-Y(&QD82!F
|
|
M:6QL#2!B97$@;F]R;7AI;F,-(#X^/B!E;W)X<W1E<"QI;GD-;F]R;7AI;F,@
|
|
M/CX^('AS=&5P+&EN>0T->&1E8WD@;&1A(&9I;&P-(&)E<2!N;W)M>&1E8PT@
|
|
M/CX^(&5O<GAS=&5P+&1E>0UN;W)M>&1E8R ^/CX@>'-T97 L9&5Y#0US=&5P
|
|
M:6YY(&QD>2!Y,0T@;&1A(&)I=' L>" [>#U8,0T@<W1A(&]L9'@-(&QS<B @
|
|
M.WF@1$]%4TXG5*!54T6@0TA53DM3#2!E;W(@;VQD>" [<T^@5T6@2E535*!7
|
|
M04Y4H%1(1:!"250-('-T82!O;&1X#2!C<'D@>3(-(&)C<R!Y9&5C>0T->6EN
|
|
M8WD@;&1A(&9I;&P-(&)E<2!N;W)M:6YC#2 ^/CX@96]R>7-T97 L:6YY#6YO
|
|
M<FUI;F,@/CX^('ES=&5P+&EN>0T->61E8WD@;&1A(&9I;&P-(&)E<2!N;W)M
|
|
M9&5C#2 ^/CX@96]R>7-T97 L9&5Y#6YO<FUD96,@/CX^('ES=&5P+&1E>0T-
|
|
M#2HM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM#2J@8TQ%04Z@55 -
|
|
M#6-L96%N=7 @;&1A('9M8W-B(#MS5TE40TB@0TA!4J!23TV@0D%#2Z!)3@T@
|
|
M86YD(",E,3$Q,3 Q,#$@.T1%1D%53%0-('-T82!V;6-S8@T-(')T<R @.T)9
|
|
M12$-#2!T>'0@)U-024Y!3*!#4D%#2T52H"<-('1X=" G4TQ*H#8O.34G#0TJ
|
|
M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0TJH'-%5*!54*!"252@
|
|
M5$%"3$4-#2!D<R!>(#MC3$5!4J!43Z!%3D2@3T:@4$%'10T@(" [<T^@5$A!
|
|
M5*!404),15.@4U1!4E2@3TZ@0:!004=%H$)/54Y$05)9#6)I=' @;'5P(#$V
|
|
M(#LQ,CB@94Y44DE%4Z!&3U*@> T@9&9B("4Q,3$Q,3$Q,0T@9&9B("4P,3$Q
|
|
M,3$Q,0T@9&9B("4P,#$Q,3$Q,0T@9&9B("4P,# Q,3$Q,0T@9&9B("4P,# P
|
|
M,3$Q,0T@9&9B("4P,# P,#$Q,0T@9&9B("4P,# P,# Q,0T@9&9B("4P,# P
|
|
M,# P,0T@+2U>#0US:6X@.W1!0DQ%H$]&H%-)3D53+* Q,C"@0EE415,-8V]S
|
|
M(&5Q=2!S:6XK,3(X(#MT04),1:!/1J!#3U-)3D53#2 @(#MB3U1(H$]&H%1(
|
|
M15-%H%1224>@5$%"3$53H$%210T@(" [0U524D5.5$Q9H%-%5*!54*!&4D]-
|
|
MH&)A<VEC#7ID:78@97%U(&-O<RLQ,C@@.V1)5DE324].H%1!0DQ%#71M871H
|
|
M,2!E<74@>F1I=BLS.#0@.VU!5$B@5$%"3$6@3T:@1BA8*3U8*E@O,C4V#71M
|
|
M871H,B!E<74@=&UA=&@Q*S4Q,B [<T5#3TY$H$U!5$B@5$%"3$4-<&]L>6QI
|
|
M<W0@97%U('1M871H,BLU,3(@.VQ)4U2@3T:@4$],64=/3E,-&AH:&AH:&AH:
|
|
8&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
|
|
end
|
|
|
|
begin 666 cube3d3.2.o
|
|
M ("I (T@T(TAT*T8T"D/"1"-&-"@ *D?A?NI@(7\3+R!DP41$1$@(" @(" @
|
|
M(" @(" @0U5"13-$(%8S+C(-#2 @(" @(" @(" @(" @(" @($)9#9\@(" @
|
|
M4U1%4$A%3B!*541$F2 @("!'14]21T4@5$%93$]2#0V;("!#2$5#2R!/550@
|
|
M5$A%($I!3BX@.34@25-3544@3T8-EB @0SU(04-+24Y'FR!&3U(@34]212!$
|
|
M151!24Q3(0T-'1V>$D8Q+T8RDB M($E.0R]$14,@6"U23U1!5$E/3@T='1)&
|
|
M,R]&-)(@+2!)3D,O1$5#(%DM4D]4051)3TX-'1T21C4O1C:2("T@24Y#+T1%
|
|
M0R!:+5)/5$%424].#1T=$B!&-R @DB M(%)%4T54#1T=$B K+RT@DB M(%I/
|
|
M3TT@24XO3U54#1T=$B @2" @DB M(%1/1T=,12!(241$14X@4U521D%#15,-
|
|
M'1T24U!!0T62("T@5$]'1TQ%(%-54D9!0T4@1DE,3$E.1PT-("!04D534R!1
|
|
M(%1/(%%5250-#04@(" @("!04D534R!!3ED@2T59(%1/($)%1TE.#0"Q^_ +
|
|
M(-+_R-#VYOQ,O($@Y/_) /#YJ9*%(X4EJ92%)X4IJ0&-(="IDR#2_ZD C2'0
|
|
MJ4!I#(7[J06%_*D H "B !B1^\AI$)#Y&*7[:2B%^Z7\:0"%_* Z(K@$-#D
|
|
MJ0"%HZDPA:2@ *(8J0"1H\C0^^:DRM#VJ0"%HZDPA:2% JT8T"GQ"0Z-&-"I
|
|
M (57A5B%685@A3^%085 A4*%885BA6.%9(5EA6:%4*D!A;6I0(5Q6"#D_\F%
|
|
MT NE8<D\\%'F84P-@\F)T FE8?!$QF%,#8/)AM +I6+)// UYF),#8/)BM )
|
|
MI6+P*,9B3 V#R8?0"Z5CR3SP&>9C3 V#R8O0":5C\ S&8TP-@\F(T 9,1H),
|
|
M#8/)*] 'YG'F<4P-@\DMT W&<<9Q$"CF<>9Q3 V#R4C0":6U20&%M4P-@\D@
|
|
MT FE4$D!A5!,#8/)4= #3)F.>!BE9&5AR7B0 NEXA608I65E8LEXD +I>(5E
|
|
M&*5F96/)>) "Z7B%9CBE9>5FL )I>(5G&*5E96;)>) "Z7B%:!BE9&5FR7B0
|
|
M NEXA6DXI63E9K ":7B%:ABE9&5HR7B0 NEXA6LXI63E9[ ":7B%;!BE9&5G
|
|
MR7B0 NEXA6TXI6CE9+ ":7B%;CBE9>5DL )I>(5O&*5D967)>) "Z7B%<!BF
|
|
M9[T D*9H?0"0A:6F9[V CSBF:/V CX6FIF6]@(\0#AA)_VD!"AA)_VD!3-V#
|
|
M"H6G.*9NO0"0IFW] ) XIFO] ) 8IFQ] ) 0#AA)_VD!2AA)_VD!3 >$2ABF
|
|
M:7V CSBF:OV CX6H.*9KO8"/IFS]@(\XIFW]@(\XIF[]@(\0#AA)_VD!2AA)
|
|
M_VD!3#V$2ABF:7T D!BF:GT D(6IIF^]@(\XIG#]@(^%JJ9LO8"/.*9N_8"/
|
|
M.*9M_8"/.*9K_8"/$ X82?]I 4H82?]I 4R A$H8IFI] ) XIFG] )"%JQBF
|
|
M;+T D*9M?0"0.*9K_0"0.*9N_0"0$ X82?]I 4H82?]I 4RVA$H8IFE]@(\8
|
|
MIFI]@(^%K!BF;[T D*9P?0"0A:VI (6CI0*%I*((J0"@ )&CR-#[YJ3*T/2@
|
|
M (11I%&Y );0 TR*A852YE$@Q86E5TI*2H57Q3^P H4_I5G%0; "A4&E6$I*
|
|
M2H58Q4"0 H5 I6#%0I "A4*E4/#!J0"%HZ4"A:2I (7[J4"%_*572I 'H("$
|
|
MHX3[&(5H9:2%I*5H9?R%_*58..57JNBD8- "YF"D8*D 4?M(4:.1HZD D?MH
|
|
MB,19L.^EHTF A:.%^] $YJ3F_,K0VDSMA*T8T$D"C1C0J0A% H4"3&Z"9T5%
|
|
M(&)204E.+"!72$%4($1/(%E/52!704Y4(%1/($1/(%1/3DE'2%0_J0"%8(58
|
|
MJ?^%6857I%&Y ):%DLBY ):%D\BY ):%E,C&4KD EH65R+D EH66R+D EH6N
|
|
MR,92N0"6A:_(N0"6A;#(N0"6A;'(A%&DDJ6EA2882?]I 84HL28X\2B%LJ6H
|
|
MA2882?]I 84HL28X\2B%LZ6KA2882?]I 84HL28X\2B%M*23I::%)AA)_VD!
|
|
MA2BQ)CCQ*!AELH6RI:F%)AA)_VD!A2BQ)CCQ*!AELX6SI:R%)AA)_VD!A2BQ
|
|
M)CCQ*!AEM(6TI)2EIX4F&$G_:0&%*+$F./$H&&6RA;*EJH4F&$G_:0&%*+$F
|
|
M./$H&&6SA;.EK84F&$G_:0&%*+$F./$H&&6TA92JO("0I;*%(AA)_VD!A22Q
|
|
M(CCQ)*9QX$#P%(3[I'&%)AA)_VD!A2BQ)CCQ**3[&&E A9+%5[ "A5?%6) "
|
|
MA5BELX4B&$G_:0&%)+$B./$DX$#P$*1QA2882?]I 84HL28X\2@8:4"%D\59
|
|
ML *%6<5@D *%8*25I:6%)AA)_VD!A2BQ)CCQ*(6RI:B%)AA)_VD!A2BQ)CCQ
|
|
M*(6SI:N%)AA)_VD!A2BQ)CCQ*(6TI):EIH4F&$G_:0&%*+$F./$H&&6RA;*E
|
|
MJ84F&$G_:0&%*+$F./$H&&6SA;.EK(4F&$G_:0&%*+$F./$H&&6TA;2DKJ6G
|
|
MA2882?]I 84HL28X\2@89;*%LJ6JA2882?]I 84HL28X\2@89;.%LZ6MA288
|
|
M2?]I 84HL28X\2@89;2%KJJ\@)"ELH4B&$G_:0&%)+$B./$DIG'@0/ 4A/ND
|
|
M<84F&$G_:0&%*+$F./$HI/L8:4"%E<57L *%5\58D *%6*6SA2(82?]I 84D
|
|
ML2(X\23@0/ 0I'&%)AA)_VD!A2BQ)CCQ*!AI0(66Q5FP H59Q6"0 H5@I*^E
|
|
MI84F&$G_:0&%*+$F./$HA;*EJ(4F&$G_:0&%*+$F./$HA;.EJX4F&$G_:0&%
|
|
M*+$F./$HA;2DL*6FA2882?]I 84HL28X\2@89;*%LJ6IA2882?]I 84HL28X
|
|
M\2@89;.%LZ6LA2882?]I 84HL28X\2@89;2%M*2QI:>%)AA)_VD!A2BQ)CCQ
|
|
M*!AELH6RI:J%)AA)_VD!A2BQ)CCQ*!AELX6SI:V%)AA)_VD!A2BQ)CCQ*!AE
|
|
MM(6QJKR D*6RA2(82?]I 84DL2(X\22F<>! \!2$^Z1QA2882?]I 84HL28X
|
|
M\2BD^QAI0(6OQ5>P H57Q5B0 H58I;.%(AA)_VD!A22Q(CCQ).! \!"D<84F
|
|
M&$G_:0&%*+$F./$H&&E A;#%6; "A5G%8) "A6"EM?!'I94XY9*HI; XY9:%
|
|
M)AA)_VD!A2BQ)CCQ*(7[I:\XY96HI98XY9.%)AA)_VD!A2BQ)CCQ*,7[, _&
|
|
M4O *YE'F4>91QE+0]F"EDH7[I9.%_*65A?VEEH7^(-"+I96%^Z66A?REKX7]
|
|
MI;"%_B#0B\92T -,=XND4;D EH65R+D EH66R+D EH6NR(11I)6EI84F&$G_
|
|
M:0&%*+$F./$HA;*EJ(4F&$G_:0&%*+$F./$HA;.EJX4F&$G_:0&%*+$F./$H
|
|
MA;2DEJ6FA2882?]I 84HL28X\2@89;*%LJ6IA2882?]I 84HL28X\2@89;.%
|
|
MLZ6LA2882?]I 84HL28X\2@89;2%M*2NI:>%)AA)_VD!A2BQ)CCQ*!AELH6R
|
|
MI:J%)AA)_VD!A2BQ)CCQ*!AELX6SI:V%)AA)_VD!A2BQ)CCQ*!AEM(6NJKR
|
|
MD*6RA2(82?]I 84DL2(X\22F<>! \!2$^Z1QA2882?]I 84HL28X\2BD^QAI
|
|
M0(65Q5>P H57Q5B0 H58I;.%(AA)_VD!A22Q(CCQ).! \!"D<84F&$G_:0&%
|
|
M*+$F./$H&&E A9;%6; "A5G%8) "A6"EE87[I9:%_*6OA?VEL(7^(-"+I96%
|
|
MKZ66A;#&4O #3!&*I9*%_:63A?ZEKX7[I;"%_"#0BV!S04U%(%1(24Y'(%=%
|
|
M($1/($5615)9($Y)1TA4+"!P24Y+63H@5%)9(%1/(%1!2T4@3U9%4B!42$4@
|
|
M5T]23$0A;D%21B&E4- +J0"%HZ4"A:1,YXNI (6CJ4"%I#BE_>7[L!.E_J3\
|
|
MA?R$_J7[I/V$^X7]..7[A6>F^XI*2DI*D 6@@(2C&&6DA:0XI?[E_+ $2?]I
|
|
M 85HQ6>0 TR-C:3\Q/Z] (^%_87^D -,XXRE4/!3IF>E9TI&_O 0Y6B0,<K0
|
|
M]:7]1?Y1HY&C8$BE_5&CD:.I_X7]A?ZI@$6CA:/0 N:D:.5HL -E9\C*T,I,
|
|
M38QE9TBE_47^4:.1HZ7^A?UHR,K0LV"F9Z5G2D;^\!#E:) QRM#UI?U%_A&C
|
|
MD:-@2*7]$:.1HZG_A?V%_JF 1:.%H] "YJ1HY6BP V5GR,K0RDR@C&5G2*7]
|
|
M1?X1HY&CI?Z%_6C(RM"S8*50\%.F9Z5G2D;^\!#E:) QRM#UI?U%_E&CD:-@
|
|
M2*7]4:.1HZG_A?V%_JF 1:.%H] "YJ1HY6BP V5GB,K0RDSWC&5G2*7]1?Y1
|
|
MHY&CI?Z%_6B(RM"S8*9GI6=*1O[P$.5HD#'*T/6E_47^$:.1HV!(I?T1HY&C
|
|
MJ?^%_87^J8!%HX6CT +FI&CE:+ #96>(RM#*3$J-96=(I?U%_A&CD:.E_H7]
|
|
M:(C*T+-@I/R] (^%_4I%_87]Q/ZP?J50\#JF:/ ,I6A*.,CE9Y $RM#X8&5H
|
|
M2*7]4:.1HVA&_3CP!LK0Y4RQC4BI@(7]1:.%H] "YJ1HRM#13+&-IFCP%*5H
|
|
M2CA(I?T1HY&C:,CE9Y *RM#PI?T1HY&C8&5H1OTX\ ;*T-],\XU(J8"%_46C
|
|
MA:/0 N:D:,K0RTSSC:50\#JF:/ ,I6A*.(CE9Y $RM#X8&5H2*7]4:.1HVA&
|
|
M_3CP!LK0Y4POCDBI@(7]1:.%H] "YJ1HRM#13"^.IFCP%*5H2CA(I?T1HY&C
|
|
M:(CE9Y *RM#PI?T1HY&C8&5H1OTX\ ;*T-],<8Y(J8"%_46CA:/0 N:D:,K0
|
|
MRTQQCJT8T"GUC1C08%-024Y!3"!#4D%#2T52(%-,2B V+SDU
|
|
M
|
|
M #_?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?
|
|
M#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_
|
|
M?S\?#P<# ?]_/Q\/!P,!_W\_'P\' P'_?S\?#P<# ?]_/Q\/!P,!_W\_'P\'
|
|
M P'_?S\?#P<# 1H:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
!&AH:
|
|
|
|
end
|
|
|
|
begin 666 shape3.2
|
|
M 1PA' CR!#54)%,T0@4TA!4$4@24Y)5"!04D]'4D%- $<<!0"7-3$L,C4U
|
|
M.I<U,BPQ,C<ZES4U+#(U-3J7-38L,3(W.IP ;AQB (\@34%)3B!04D]'4D%-
|
|
M(%-405)44R!!5"!,24Y%(#4P,# =!QC #H B!QD (\@4%)/1U)!32!.3U1%
|
|
M4P":'&X CR!33$H@-B\Q-B\Y-0"P''@ CR!$051!($9/4DU!5"!)4SH UAQ]
|
|
M (\@3E5-4$])3E13+%@Q+%DQ+%HQ+%@R+%DR+%HR+"XN+@#W'(( CR!42$4@
|
|
M4$],64=/3B!,25-4("I-55-4*B!"10 5'8< CR!415)-24Y!5$5$(%=)5$@@
|
|
M02!:15)/(0 ;'8D .@ ^'8P CR!/1B!#3U524T4L(%1(15)%($E3($$@3$]4
|
|
M($]& %X=D0"/($154$Q)0T%424].($1205=)3D<@3$E.15, @QV3 (\@5TA%
|
|
M3B!&04-%4R!!4D5.)U0@0D5)3D<@1DE,3$5$ *8=E@"/($$@1D%35"!04D]'
|
|
M4D%-(%=/54Q$3B=4($1205< RAV@ (\@5$A%(%-!344@3$E.12!45TE#12P@
|
|
M0E54(%1(25, Z!VE (\@25,@1T]/1"!%3D]51T@@1D]2($Y/5R$ "AZG (\@
|
|
M3T8@0T]54E-%+"!&3U(@1DE,3$5$($9!0T53 "L>J "/(%1(25,@25,@3D\@
|
|
M3$].1T52($%.($E34U5% #$>J@ Z %8>M "/($9%14P@1E)%12!43R!0550@
|
|
M64]54B!/5TX@1$%400!\'KD CR!)3BP@0E54($-(04Y'12!42$4@4$]+12!)
|
|
M3B!,24Y% )@>O@"/(#4P,#4@5$\@55-%($,Q($%.1"!#,@">'L@ .@##'M(
|
|
MCR!33U)262!!0D]55"!33TU%($]&(%1(12!,25143$4 WA[7 (\@0E5'4RP@
|
|
M15(N+BX@1D5!5%5215, _A[< (\@5$A)4R!705,@02!214%,(%)54T@@2D]"
|
|
M+@ $'^8 .@ G'_ CR!!3D0@248@64]5($9%14P@4T\@24Y#3$E.140L $0?
|
|
M^@"/(%=2251%(%1/(%-*541$0$Y752Y%1%4 2A\$ 3H :1\. 8\@04Y$($%"
|
|
M3U9%($%,3"P@2$%612!&54XA &\?& $Z ($?Y@./($9)4E-4(%-(05!% )<?
|
|
MYP-!,;+"*#8Q*3I!,K+"*#8R*0#''^@#@R T+"TR-BPM,C8L-C0L,C8L+3(V
|
|
M+#8T+#(V+#8T+#8T+"TR-BPV-"PV- #S'_(#@R T+"TR-BPM,C8L,"PR-BPM
|
|
M,C8L,"PR-BPV-"PP+"TR-BPV-"PP !<@_ .#(#,L,"PM,C8L,S(L,38L,S(L
|
|
M,S(L+3$V+#,R+#,R !\@!@2#(# ,B!*!(\@4T5#3TY$(%-(05!% $@@2P1"
|
|
M,;+"*#8Q*3I",K+"*#8R*0!\($P$@R T+"TQ-2PS,"PM,3(L+3$U+"TS,"PM
|
|
M,3(L+34W+"TS,"PM,RPM-3$L+38L+3, K"!6!(,@-"PQ-2PS,"PM,3(L-3$L
|
|
M+38L+3,L-3<L+3,P+"TS+#$U+"TS,"PM,3( X"!@!(,@-"PQ-2PS,"PM,3(L
|
|
M,34L+3,P+"TQ,BPM,34L+3,P+"TQ,BPM,34L,S L+3$R "0A:@2#(#<L,34L
|
|
M+3,P+"TQ,BPU-RPM,S L+3,L,S,L+3,P+#8L,"PM,S L,3(L+3,S+"TS,"PV
|
|
M+"TU-RPM,S L+3, -B%L!(,@+3$U+"TS,"PM,3( 6R%T!(,@,RPS,RPM,S L
|
|
M,"PS,RPM,S L+30L,SDL+3,P+"TR (,A?@2#(#,L+3,S+"TS,"PP+"TS.2PM
|
|
M,S L+3(L+3,S+"TS,"PM- "O(8@$@R T+#8L+3,P+#,L-BPM,S L+38L,3@L
|
|
M+3,P+"TT+#$X+"TS,"PQ -\AD@2#(#0L+38L+3,P+#,L+3$X+"TS,"PQ+"TQ
|
|
M."PM,S L+30L+38L+3,P+"TV <BG 2#(#,L+34W+"TS,"PM,RPM,S,L+3,P
|
|
M+#8L+34Q+"TQ,BPM,P O(J8$@R S+"TS,RPM,S L-BPM,34L,S L+3$R+"TU
|
|
M,2PM,3(L+3, 4R*P!(,@,RPM,34L,S L+3$R+"TS,RPM,S L-BPP+#8L,3(
|
|
M=2*Z!(,@,RPP+#8L,3(L+3,S+"TS,"PV+# L+3,P+#$R )DBQ 2#(#,L,34L
|
|
M,S L+3$R+"TQ-2PS,"PM,3(L,"PV+#$R +HBS@2#(#,L,"PM,S L,3(L,S,L
|
|
M+3,P+#8L,"PV+#$R -PBV 2#(#,L,"PV+#$R+#,S+"TS,"PV+#$U+#,P+"TQ
|
|
M,@ !(^($@R S+#$U+#,P+"TQ,BPS,RPM,S L-BPU,2PM,3(L+3, )B/L!(,@
|
|
M,RPS,RPM,S L-BPU-RPM,S L+3,L-3$L+3$R+"TS "XC]@2#(# 0",2!8\@
|
|
M5$A)4D0@4TA!4$4 5B,3!4,QLL(H-C$I.D,RLL(H-C(I ' C% 6/(%E/55(@
|
|
M1$%402!'3T53($A%4D4 >"/0!X,@, ",(X@30E"R,C4VK"@YK#$VJC8I + C
|
|
MC!./($-(04Y'12!"14Q/5R!43R!03TE.5"!43R!$051! ,(CC1.7-C4L0C$Z
|
|
MES8V+$(R .$CDA.'3CJ70E L3CI"4+)"4*HQ.HM.LC"G-3 U, #I(Y<3F2!.
|
|
M 8DG!.!2;(QI#.L3CJ'4#J+4+,PIU"R,C4VJE &"2F$Y="4"Q0.D)0LD)0
|
|
MJC$ (B2K$YDB+B([ "XDL!.".HDU,#$P #\DNA.>,S(W-C@ZG#J,.H :
|
|
M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
0&AH:&AH:&AH:&AH:&AH:&AH:
|
|
|
|
end
|
|
|
|
begin 666 tables$8f80-$95ff
|
|
M@(\ @,%!P@*"PT/$!$3%!47&!D:&QP='1X>'Q\@(" @(" @'Q\>'AT='!L:
|
|
M&1@7%103$1 /#0L*" <% P( _OW[^?CV]?/Q\._M[.OIZ.?FY>3CX^+BX>'@
|
|
MX.#@X.#@X>'BXN/CY.7FY^CIZ^SM[_#Q\_7V^/G[_?X _P #_\" @(" ?
|
|
M'QX>'1T<&QH9&!<5%!,1$ \-"PH(!P4# @#^_?OY^/;U\_'P[^WLZ^GHY^;E
|
|
MY./CXN+AX>#@X.#@X.#AX>+BX^/DY>;GZ.GK[.WO\/'S]?;X^?O]_@ " P4'
|
|
M" H+#0\0$1,4%1<8&1H;'!T='AX?'R @(" _____P (B(B(B(C(R,C(R,C
|
|
M(R,D)"0D)"0D)"4E)24E)24E)B8F)B8F)B8G)R<G)R<G*"@H*"@H*"DI*2DI
|
|
M*2HJ*BHJ*BLK*RLK*RPL+"PL+"TM+2TM+BXN+BXN+R\O+S P,# P,3$Q,3$R
|
|
M,C(R,S,S,S0T-#0U-34U-C8V-C<W-S@X.#@8&!@8&1D9&1D9&1D9&1D9&1D9
|
|
M&1D9&AH:&AH:&AH:&AH:&AH:&AL;&QL;&QL;&QL;&QL;&QP<'!P<'!P<'!P<
|
|
M'!P<'1T='1T='1T='1T='1X>'AX>'AX>'AX>'A\?'Q\?'Q\?'Q\?(" @(" @
|
|
M(" @(" A(2$A(2$A(2$A(B(B(O__ /____\ \ _P#__P #_____
|
|
M /____\ _____P #_ /__ /_P__\ _____P #_
|
|
M____ /____\ _____P #_____ /____\ _____P
|
|
M #_____ /_P 0$! 0$! 0$" @(" @(# P,#! 0$
|
|
M! 4%!04&!@8'!P<(" @)"0D*"@L+"PP,#0T.#@\/$! 1$1(2$Q,4%!45%A<7
|
|
M&!@9&AH;'!P='AX?(" A(B,C)"4F)B<H*2DJ*RPM+BXO,#$R,S0U-38W.#DZ
|
|
M.SP]/C] 04)#1$5&1TA)2DM-3D]045)35%976"LJ*2DH)R8F)20C(R(A(" ?
|
|
M'AX='!P;&AH9&!@7%Q85%104$Q,2$A$1$! /#PX.#0T,# L+"PH*"0D)" @(
|
|
M!P<'!@8&!04%!00$! 0# P,# @(" @(" 0$! 0$! 0$
|
|
M $! 0$! 0$! @(" @(" P,# P0$! 0%!04%!@8&!P<'" @(
|
|
M"0D)"@H+"PL,# T-#@X/#Q 0$1$2$A,3%!05%187%Q@8&1H:&QP<'1X>'R @
|
|
M(2(C(R0E)B8G*"DI*BLL+2XN+S Q,C,T-34V-S@Y.CL\/3X_0$%"0T1%1D=(
|
|
M24I+34Y/4%%24U165U@K*BDI*"<F)B4D(R,B(2 @'QX>'1P<&QH:&1@8%Q<6
|
|
M%144%!,3$A(1$1 0#P\.#@T-# P+"PL*"@D)"0@(" <'!P8&!@4%!04$! 0$
|
|
M P,# P(" @(" @$! 0$! 0$! ! 0$!
|
|
M 0$! 0(" @(" @,# P,$! 0$!04%!08&!@<'!P@(" D)"0H*"PL+# P-#0X.
|
|
M#P\0$!$1$A(3$Q04%146%Q<8&!D:&AL<'!T>'A\@("$B(R,D)28F)R@I*2HK
|
|
M+"TN+B\P,3(S-#4U-C<X.3H[/#T^/T _/CT\.SHY.#<V-34T,S(Q,"\N+BTL
|
|
M*RHI*2@G)B8E)",C(B$@(!\>'AT<'!L:&AD8&!<7%A45%!03$Q(2$1$0$ \/
|
|
M#@X-#0P,"PL+"@H)"0D(" @'!P<&!@8%!04%! 0$! ,# P," @(" @(! 0$!
|
|
M 0$! 0 0$! 0$! 0$" @(" @(# P,#
|
|
M! 0$! 4%!04&!@8'!P<(" @)"0D*"@L+"PP,#0T.#@\/$! 1$1(2$Q,4%!45
|
|
M%A<7&!@9&AH;'!P='AX?(" A(B,C)"4F)B<H*2DJ*RPM+BXO,#$R,S0U-38W
|
|
M.#DZ.SP]/C] /SX]/#LZ.3@W-C4U-#,R,3 O+BXM+"LJ*2DH)R8F)20C(R(A
|
|
M(" ?'AX='!P;&AH9&!@7%Q85%104$Q,2$A$1$! /#PX.#0T,# L+"PH*"0D)
|
|
M" @(!P<'!@8&!04%!00$! 0# P,# @(" @(" 0$! 0$! 0$
|
|
M !H:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
M&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
E&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:&AH:
|
|
|
|
end
|
|
|
|
|
|
********************************
|
|
*``````````````````````````````*
|
|
*`Stephen`Judd`````````````````*
|
|
*`George`Taylor````````````````*
|
|
*`Started:`7/11/94`````````````*
|
|
*`Finished:`7/19/94````````````*
|
|
*`v2.0`Completed:`12/17/94`````*
|
|
*`v3.0`Completed:`3/20/95``````*
|
|
*`v3.1`Completed:`6/14/95``````*
|
|
*`v3.2`Completed:`6/15/95``````*
|
|
*``````````````````````````````*
|
|
*`Well,`if`all`goes`well`this``*
|
|
*`program`will`rotate`a`cube.``*
|
|
*``````````````````````````````*
|
|
*`v2.0`+`New`and`Improved!`````*
|
|
*`Now`with`faster`routines,````*
|
|
*`hidden`surfaces,`filled``````*
|
|
*`faces,`and`extra`top`secret``*
|
|
*`text`messages!```````````````*
|
|
*``````````````````````````````*
|
|
*`v3.0`+`Fast`chunky`line``````*
|
|
*`routine.`````````````````````*
|
|
*``````````````````````````````*
|
|
*`v3.1`+`General`polygon`plot``*
|
|
*`with`hidden`faces`(X-product)*
|
|
*`and`zoom`feature.````````````*
|
|
*``````````````````````````````*
|
|
*`v3.2`+`EOR-buffer`filling````*
|
|
*``````````````````````````````*
|
|
*`This`program`is`intended`to``*
|
|
*`accompany`the`article`in`````*
|
|
*`C=Hacking,`Jun.`95`issue.````*
|
|
*`For`details`on`this`program,`*
|
|
*`read`the`article!````````````*
|
|
*``````````````````````````````*
|
|
*`Write`to`us!`````````````````*
|
|
*``````````````````````````````*
|
|
*`Myself`when`young`did````````*
|
|
*`eagerly`frequent`````````````*
|
|
*`Doctor`and`Saint,`and`heard``*
|
|
*`great`Argument```````````````*
|
|
*``About`it`and`about:`but`````*
|
|
*``evermore````````````````````*
|
|
*`Came`out`by`the`same`Door````*
|
|
*`as`in`I`went.````````````````*
|
|
*````-`Rubaiyat````````````````*
|
|
*``````````````````````````````*
|
|
*`Though`I`speak`with`the``````*
|
|
*`tongues`of`men`and`of`angles`*
|
|
*`and`have`not`love,`I`am``````*
|
|
*`become`as`sounding`brass,`or`*
|
|
*`a`tinkling`cymbal.```````````*
|
|
*````-`1`Corinthians`13````````*
|
|
*``````````````````````````````*
|
|
*`P.S.`This`was`written`using``*
|
|
*``````Merlin`128.`````````````*
|
|
********************************
|
|
|
|
ORG $8000
|
|
|
|
*`Constants
|
|
|
|
BUFF1 EQU $3000 ;First`character`set
|
|
BUFF2 EQU $3800 ;Second`character`set
|
|
EORBUF EQU $4000 ;EOR-buffer
|
|
BUFFER EQU $A3 ;Presumably`the`tape`won't`be`running
|
|
X1 EQU $FB ;Points`for`drawing`a`line
|
|
Y1 EQU $FC ;These`zero`page`addresses
|
|
X2 EQU $FD ;don't`conflict`with`BASIC
|
|
Y2 EQU $FE
|
|
OLDX EQU $FD
|
|
CHUNK EQU $FE
|
|
DX EQU $67 ;This`is`shared`with`T1`below
|
|
DY EQU $68
|
|
TEMP1 EQU $FB ;Of`course,`could`conflict`with`x1
|
|
TEMP2 EQU $FC ;Temporary`variables
|
|
ZTEMP EQU $02 ;Used`for`buffer`swap.``Don't`touch.
|
|
Z1 EQU $22 ;Used`by`math`routine
|
|
Z2 EQU $24 ;Don't`touch`these`either!
|
|
Z3 EQU $26
|
|
Z4 EQU $28
|
|
K EQU $B6 ;Constant`used`for`hidden
|
|
;surface`detection`-`don't`touch
|
|
HIDE EQU $B5 ;Are`surfaces`hidden?
|
|
FILL EQU $50 ;Are`we`using`EOR-fill?
|
|
ANGMAX EQU 120 ;There`are`2*pi/angmax`angles
|
|
|
|
*`VIC
|
|
|
|
VMCSB EQU $D018
|
|
BKGND EQU $D020
|
|
BORDER EQU $D021
|
|
SSTART EQU 1344 ;row`9`in`screen`memory`at`1024
|
|
|
|
|
|
*`Kernal
|
|
|
|
CHROUT EQU $FFD2
|
|
GETIN EQU $FFE4
|
|
|
|
*`Some`variables
|
|
|
|
GLOBXMIN = $3F ;These`are`used`in`clearing`the
|
|
GLOBXMAX = $40 ;drawing`(global)`buffer
|
|
GLOBYMIN = $41
|
|
GLOBYMAX = $42
|
|
LOCXMIN = $57 ;These`are`used`in`clearing`the
|
|
LOCXMAX = $58 ;EOR`(local)`buffer
|
|
LOCYMIN = $59
|
|
LOCYMAX = $60
|
|
P1X = $92 ;These`are`temporary`storage
|
|
P1Y = $93 ;Used`in`plotting`the`projection
|
|
P1Z = $94
|
|
P2X = $95 ;They`are`here`so`that`we
|
|
P2Y = $96 ;don't`have`to`recalculate`them.
|
|
P2Z = $AE
|
|
P3X = $AF ;They`make`life`easy.
|
|
P3Y = $B0
|
|
P3Z = $B1 ;Why`are`you`looking`at`me`like`that?
|
|
P1T = $B2 ;Don't`you`trust`me?
|
|
P2T = $B3
|
|
P3T = $B4 ;Having`another`child`wasn't`my`idea.
|
|
INDEX = $51
|
|
COUNTPTS = $52
|
|
ZOOM = $71 ;Zoom`factor
|
|
DSX = $61 ;DSX`is`the`increment`for
|
|
;rotating`around`x
|
|
DSY = $62 ;Similar`for`DSY,`DSZ
|
|
DSZ = $63
|
|
SX = $64 ;These`are`the`actual`angles`in`x`y`and`z
|
|
SY = $65
|
|
SZ = $66
|
|
T1 = $67 ;These`are`used`in`the`rotation
|
|
T2 = $68
|
|
T3 = $69 ;See`the`article`for`more`details
|
|
T4 = $6A
|
|
T5 = $6B
|
|
T6 = $6C
|
|
T7 = $6D
|
|
T8 = $6E
|
|
T9 = $6F
|
|
T10 = $70
|
|
A11 = $A5 ;These`are`the`elements`of`the`rotation`matrix
|
|
B12 = $A6 ;XYZ
|
|
C13 = $A7
|
|
D21 = $A8 ;The`number`denotes`(row,column)
|
|
E22 = $A9
|
|
F23 = $AA
|
|
G31 = $AB
|
|
H32 = $AC
|
|
I33 = $AD
|
|
|
|
|
|
***`Macros
|
|
|
|
MOVE MAC
|
|
LDA ]1
|
|
STA ]2
|
|
<<<
|
|
|
|
GETKEY MAC ;Wait`for`a`keypress
|
|
WAIT JSR GETIN
|
|
CMP #00
|
|
BEQ WAIT
|
|
<<<
|
|
|
|
DEBUG MAC ;Print`a`character
|
|
DO`0``;Don't`assemble
|
|
|
|
LDA`#]1
|
|
JSR`CHROUT
|
|
CLI
|
|
>>> GETKEY ;And`wait`to`continue
|
|
CMP #'s' ;My`secrect`switch`key
|
|
BNE L1
|
|
JSR CLEANUP
|
|
JMP DONE
|
|
L1 CMP #'x' ;My`secret`abort`key
|
|
BNE DONE
|
|
JMP CLEANUP
|
|
FIN
|
|
DONE <<<
|
|
|
|
DEBUGA MAC
|
|
DO`0
|
|
LDA ]1
|
|
STA 1024
|
|
FIN
|
|
DONEA <<<
|
|
|
|
*-------------------------------
|
|
|
|
LDA #$00
|
|
STA BKGND
|
|
STA BORDER
|
|
LDA VMCSB
|
|
AND #%00001111 ;Screen`memory`to`1024
|
|
ORA #%00010000
|
|
STA VMCSB
|
|
|
|
LDY #00
|
|
LDA #<TTEXT
|
|
STA TEMP1
|
|
LDA #>TTEXT
|
|
STA TEMP2
|
|
JMP TITLE
|
|
TTEXT HEX 9305111111 ;clear`screen,`white,`crsr`dn
|
|
TXT '`````````````cube3d`v3.2',0d,0d
|
|
TXT '``````````````````by',0d
|
|
HEX 9F ;cyan
|
|
TXT '````stephen`judd'
|
|
HEX 99
|
|
TXT '````george`taylor',0d,0d
|
|
HEX 9B
|
|
TXT '``check`out`the`jan.`95`issue`of',0d
|
|
HEX 96
|
|
TXT '``c=hacking'
|
|
HEX 9B
|
|
TXT '`for`more`details!',0d
|
|
HEX 0D1D1D9E12
|
|
TXT 'f1/f2',92
|
|
TXT '`-`inc/dec`x-rotation',0d
|
|
HEX 1D1D12
|
|
TXT 'f3/f4',92
|
|
TXT '`-`inc/dec`y-rotation',0d
|
|
HEX 1D1D12
|
|
TXT 'f5/f6',92
|
|
TXT '`-`inc/dec`z-rotation',0d
|
|
HEX 1D1D12
|
|
TXT '`f7``',92
|
|
TXT '`-`reset',0d
|
|
HEX 1D1D12
|
|
TXT '`+/-`',92
|
|
TXT '`-`zoom`in/out',0d
|
|
HEX 1D1D12
|
|
TXT '``h``',92
|
|
TXT '`-`toggle`hidden`surfaces',0d
|
|
HEX 1D1D12
|
|
TXT 'space',92
|
|
TXT '`-`toggle`surface`filling',0d,0d
|
|
TXT '``press`q`to`quit',0d
|
|
HEX 0D05
|
|
TXT '``````press`any`key`to`begin',0d
|
|
HEX 00
|
|
TITLE LDA (TEMP1),Y
|
|
BEQ :CONT
|
|
JSR CHROUT
|
|
INY
|
|
BNE TITLE
|
|
INC TEMP2
|
|
JMP TITLE
|
|
:CONT >>> GETKEY
|
|
|
|
****`Set`up`tables(?)
|
|
|
|
*`Tables`are`currently`set`up`in`BASIC
|
|
*`and`by`the`assembler.
|
|
|
|
TABLES LDA #>TMATH1
|
|
STA Z1+1
|
|
STA Z2+1
|
|
LDA #>TMATH2
|
|
STA Z3+1
|
|
STA Z4+1
|
|
|
|
****`Clear`screen`and`set`up`"bitmap"
|
|
SETUP LDA #$01 ;White
|
|
STA $D021 ;This`is`done`so`that`older
|
|
LDA #147 ;machines`will`set`up
|
|
JSR CHROUT
|
|
LDA #$00 ;correctly
|
|
STA $D021
|
|
LDA #<SSTART
|
|
ADC #12 ;The`goal`is`to`center`the`graphics
|
|
STA TEMP1 ;Column`12
|
|
LDA #>SSTART ;Row`9
|
|
STA TEMP1+1 ;SSTART`points`to`row`9
|
|
LDA #00
|
|
LDY #00
|
|
LDX #00 ;x`will`count`16`rows`for`us
|
|
CLC
|
|
|
|
:LOOP STA (TEMP1),Y
|
|
INY
|
|
ADC #16
|
|
BCC :LOOP
|
|
CLC
|
|
LDA TEMP1
|
|
ADC #40 ;Need`to`add`40`to`the`base`pointer
|
|
STA TEMP1 ;To`jump`to`the`next`row
|
|
LDA TEMP1+1
|
|
ADC #00 ;Take`care`of`carries
|
|
STA TEMP1+1
|
|
LDY #00
|
|
INX
|
|
TXA ;X`is`also`an`index`into`the`character`number
|
|
CPX #16
|
|
BNE :LOOP ;Need`to`do`it`16`times
|
|
|
|
****`Clear`buffers
|
|
|
|
LDA #<BUFF1
|
|
STA BUFFER
|
|
LDA #>BUFF1
|
|
STA BUFFER+1
|
|
LDY #$00
|
|
LDX #24 ;Assuming`all`three`buffers`are
|
|
LDA #$00 ;back-to-back
|
|
:BLOOP STA (BUFFER),Y
|
|
INY
|
|
BNE :BLOOP
|
|
INC BUFFER+1
|
|
DEX
|
|
BNE :BLOOP
|
|
|
|
****`Set`up`buffers
|
|
|
|
LDA #<BUFF1
|
|
STA BUFFER
|
|
LDA #>BUFF1
|
|
STA BUFFER+1
|
|
STA ZTEMP ;ztemp`will`make`life`simple`for`us
|
|
LDA VMCSB
|
|
AND #%11110001 ;Start`here`so`that`swap`buffers`will`work`right
|
|
ORA #%00001110
|
|
STA VMCSB
|
|
|
|
****`Set`up`initial`values
|
|
|
|
INIT LDA #00
|
|
STA LOCXMIN
|
|
STA LOCXMAX
|
|
STA LOCYMIN
|
|
STA LOCYMAX
|
|
STA GLOBXMIN
|
|
STA GLOBYMIN
|
|
STA GLOBXMAX
|
|
STA GLOBYMAX
|
|
STA DSX
|
|
STA DSY
|
|
STA DSZ
|
|
STA SX
|
|
STA SY
|
|
STA SZ
|
|
STA FILL
|
|
LDA #01
|
|
STA HIDE
|
|
LDA #64
|
|
STA ZOOM
|
|
|
|
*-------------------------------
|
|
*`Main`loop
|
|
|
|
****`Get`keypress
|
|
|
|
MAIN
|
|
CLI
|
|
KPRESS JSR GETIN
|
|
CMP #133 ;F1?
|
|
BNE :F2
|
|
LDA DSX
|
|
CMP #ANGMAX/2 ;No`more`than`pi
|
|
BEQ :CONT1
|
|
INC DSX ;otherwise`increase`x-rotation
|
|
JMP :CONT
|
|
:F2 CMP #137 ;F2?
|
|
BNE :F3
|
|
LDA DSX
|
|
BEQ :CONT1
|
|
DEC DSX
|
|
JMP :CONT
|
|
:F3 CMP #134
|
|
BNE :F4
|
|
LDA DSY
|
|
CMP #ANGMAX/2
|
|
BEQ :CONT1
|
|
INC DSY ;Increase`y-rotation
|
|
JMP :CONT
|
|
:F4 CMP #138
|
|
BNE :F5
|
|
LDA DSY
|
|
BEQ :CONT1
|
|
DEC DSY
|
|
JMP :CONT
|
|
:F5 CMP #135
|
|
BNE :F6
|
|
LDA DSZ
|
|
CMP #ANGMAX/2
|
|
BEQ :CONT1
|
|
INC DSZ ;z-rotation
|
|
JMP :CONT
|
|
:F6 CMP #139
|
|
BNE :F7
|
|
LDA DSZ
|
|
BEQ :CONT1
|
|
DEC DSZ
|
|
JMP :CONT
|
|
:F7 CMP #136
|
|
BNE :PLUS
|
|
JMP INIT
|
|
:CONT1 JMP :CONT
|
|
:PLUS CMP #'+'
|
|
BNE :MINUS
|
|
INC ZOOM ;Bah,`who`needs`error`checking?
|
|
INC ZOOM
|
|
JMP :CONT
|
|
:MINUS CMP #'-'
|
|
BNE :H
|
|
DEC ZOOM
|
|
DEC ZOOM
|
|
BPL :CONT
|
|
INC ZOOM
|
|
INC ZOOM
|
|
JMP :CONT
|
|
:H CMP #'h'
|
|
BNE :SPACE
|
|
LDA HIDE
|
|
EOR #$01
|
|
STA HIDE
|
|
JMP :CONT
|
|
:SPACE CMP #'`'
|
|
BNE :Q
|
|
LDA FILL
|
|
EOR #$01
|
|
STA FILL
|
|
JMP :CONT
|
|
:Q CMP #'q' ;q`quits
|
|
BNE :CONT
|
|
JMP CLEANUP
|
|
|
|
:CONT SEI ;Speed`things`up`a`bit
|
|
|
|
****`Update`angles
|
|
|
|
UPDATE CLC
|
|
LDA SX
|
|
ADC DSX
|
|
CMP #ANGMAX ;Are`we`>=`maximum`angle?
|
|
BCC :CONT1
|
|
SBC #ANGMAX :If so, reset
|
|
:CONT1 STA SX
|
|
CLC
|
|
LDA SY
|
|
ADC DSY
|
|
CMP #ANGMAX
|
|
BCC :CONT2
|
|
SBC #ANGMAX ;Same`deal
|
|
:CONT2 STA SY
|
|
CLC
|
|
LDA SZ
|
|
ADC DSZ
|
|
CMP #ANGMAX
|
|
BCC :CONT3
|
|
SBC #ANGMAX
|
|
:CONT3 STA SZ
|
|
|
|
****`Rotate`coordinates
|
|
|
|
ROTATE
|
|
|
|
***`First,`calculate`t1,t2,...,t10
|
|
|
|
**`Two`macros`to`simplify`our`life
|
|
ADDA MAC ;Add`two`angles`together
|
|
CLC
|
|
LDA ]1
|
|
ADC ]2
|
|
CMP #ANGMAX ;Is`the`sum`>`2*pi?
|
|
BCC DONE
|
|
SBC #ANGMAX ;If`so,`subtract`2*pi
|
|
DONE <<<
|
|
|
|
SUBA MAC ;Subtract`two`angles
|
|
SEC
|
|
LDA ]1
|
|
SBC ]2
|
|
BCS DONE
|
|
ADC #ANGMAX ;Oops,`we`need`to`add`2*pi
|
|
DONE <<<
|
|
|
|
**`Now`calculate`t1,t2,etc.
|
|
|
|
>>> SUBA,SY;SZ
|
|
STA T1 ;t1=sy-sz
|
|
>>> ADDA,SY;SZ
|
|
STA T2 ;t2=sy+sz
|
|
>>> ADDA,SX;SZ
|
|
STA T3 ;t3=sx+sz
|
|
>>> SUBA,SX;SZ
|
|
STA T4 ;t4=sx-sz
|
|
>>> ADDA,SX;T2
|
|
STA T5 ;t5=sx+t2
|
|
>>> SUBA,SX;T1
|
|
STA T6 ;t6=sx-t1
|
|
>>> ADDA,SX;T1
|
|
STA T7 ;t7=sx+t1
|
|
>>> SUBA,T2;SX
|
|
STA T8 ;t8=t2-sx
|
|
>>> SUBA,SY;SX
|
|
STA T9 ;t9=sy-sx
|
|
>>> ADDA,SX;SY
|
|
STA T10 ;t10=sx+sy
|
|
|
|
*`Et`voila!
|
|
|
|
***`Next,`calculate`A,B,C,...,I
|
|
|
|
**`Another`useful`little`macro
|
|
DIV2 MAC ;Divide`a`signed`number`by`2
|
|
;It`is`assumed`that`the`number
|
|
BPL POS ;is`in`the`accumulator
|
|
CLC
|
|
EOR #$FF ;We`need`to`un-negative`the`number
|
|
ADC #01 ;by`taking`it's`complement
|
|
LSR ;divide`by`two
|
|
CLC
|
|
EOR #$FF
|
|
ADC #01 ;Make`it`negative`again
|
|
JMP DONEDIV
|
|
POS LSR ;Number`is`positive
|
|
DONEDIV <<<
|
|
|
|
MUL2 MAC ;Multiply`a`signed`number`by`2
|
|
BPL POSM
|
|
CLC
|
|
EOR #$FF
|
|
ADC #$01
|
|
ASL
|
|
CLC
|
|
EOR #$FF
|
|
ADC #$01
|
|
JMP DONEMUL
|
|
POSM ASL
|
|
DONEMUL <<<
|
|
|
|
**`Note`that`we`are`currently`making`a`minor`leap
|
|
**`of`faith`that`no`overflows`will`occur.
|
|
|
|
:CALCA CLC
|
|
LDX T1
|
|
LDA COS,X
|
|
LDX T2
|
|
ADC COS,X
|
|
STA A11 ;A=(cos(t1)+cos(t2))/2
|
|
:CALCB LDX T1
|
|
LDA SIN,X
|
|
SEC
|
|
LDX T2
|
|
SBC SIN,X
|
|
STA B12 ;B=(sin(t1)-sin(t2))/2
|
|
:CALCC LDX SY
|
|
LDA SIN,X
|
|
>>> MUL2
|
|
STA C13 ;C=sin(sy)
|
|
:CALCD SEC
|
|
LDX T8
|
|
LDA COS,X
|
|
LDX T7
|
|
SBC COS,X
|
|
SEC
|
|
LDX T5
|
|
SBC COS,X
|
|
CLC
|
|
LDX T6
|
|
ADC COS,X ;Di=(cos(t8)-cos(t7)+cos(t6)-cos(t5))/2
|
|
>>> DIV2
|
|
CLC
|
|
LDX T3
|
|
ADC SIN,X
|
|
SEC
|
|
LDX T4
|
|
SBC SIN,X
|
|
STA D21 ;D=(sin(t3)-sin(t4)+Di)/2
|
|
:CALCE SEC
|
|
LDX T5
|
|
LDA SIN,X
|
|
LDX T6
|
|
SBC SIN,X
|
|
SEC
|
|
LDX T7
|
|
SBC SIN,X
|
|
SEC
|
|
LDX T8
|
|
SBC SIN,X ;Ei=(sin(t5)-sin(t6)-sin(t7)-sin(t8))/2
|
|
>>> DIV2
|
|
CLC
|
|
LDX T3
|
|
ADC COS,X
|
|
CLC
|
|
LDX T4
|
|
ADC COS,X
|
|
STA E22 ;E=(cos(t3)+cos(t4)+Ei)/2
|
|
:CALCF LDX T9
|
|
LDA SIN,X
|
|
SEC
|
|
LDX T10
|
|
SBC SIN,X
|
|
STA F23 ;F=(sin(t9)-sin(t10))/2
|
|
:CALCG LDX T6
|
|
LDA SIN,X
|
|
SEC
|
|
LDX T8
|
|
SBC SIN,X
|
|
SEC
|
|
LDX T7
|
|
SBC SIN,X
|
|
SEC
|
|
LDX T5
|
|
SBC SIN,X ;Gi=(sin(t6)-sin(t8)-sin(t7)-sin(t5))/2
|
|
>>> DIV2
|
|
|
|
CLC
|
|
LDX T4
|
|
ADC COS,X
|
|
SEC
|
|
LDX T3
|
|
SBC COS,X
|
|
STA G31 ;G=(cos(t4)-cos(t3)+Gi)/2
|
|
:CALCH CLC
|
|
LDX T6
|
|
LDA COS,X
|
|
LDX T7
|
|
ADC COS,X
|
|
SEC
|
|
LDX T5
|
|
SBC COS,X
|
|
SEC
|
|
LDX T8
|
|
SBC COS,X ;Hi=(cos(t6)+cos(t7)-cos(t5)-cos(t8))/2
|
|
>>> DIV2
|
|
CLC
|
|
LDX T3
|
|
ADC SIN,X
|
|
CLC
|
|
LDX T4
|
|
ADC SIN,X
|
|
STA H32 ;H=(sin(t3)+sin(t4)+Hi)/2
|
|
:WHEW CLC
|
|
LDX T9
|
|
LDA COS,X
|
|
LDX T10
|
|
ADC COS,X
|
|
STA I33 ;I=(cos(t9)+cos(t10))/2
|
|
|
|
**`It's`all`downhill`from`here.
|
|
|
|
DOWNHILL
|
|
****`Clear`buffer
|
|
*`A`little`macro
|
|
|
|
SETBUF MAC ;Put`buffers`where`they`can`be`hurt
|
|
LDA #00
|
|
STA BUFFER
|
|
LDA ZTEMP ;High`byte
|
|
STABUF STA BUFFER+1
|
|
<<<
|
|
|
|
>>> SETBUF
|
|
CLRDRAW LDX #08
|
|
LDA #00
|
|
:FOOL LDY #00
|
|
:DOPE STA (BUFFER),Y
|
|
INY
|
|
BNE :DOPE
|
|
INC BUFFER+1
|
|
DEX
|
|
BNE :FOOL
|
|
|
|
****`My`goodness`but`I'm`a`dope
|
|
*CLRDRAW`LDA`GLOBXMIN
|
|
*`LSR``;Need`to`get`into`the`right`column
|
|
*`BCC`:EVEN`;Explained`in`more`detail`below
|
|
*`LDY`#$80
|
|
*`STY`BUFFER`;Presumably`this`will`be`a`little
|
|
*`CLC``;more`efficient.
|
|
*:EVEN`ADC`BUFFER+1
|
|
*`STA`BUFFER+1
|
|
*`LDA`GLOBXMAX
|
|
*`SEC
|
|
*`SBC`GLOBXMIN
|
|
*`TAX
|
|
*`INX
|
|
*`LDY`GLOBYMAX
|
|
*`BEQ`:RESET
|
|
*:YAY`LDA`#$00
|
|
*`LDY`GLOBYMAX
|
|
*:BLAH`STA`(BUFFER),Y
|
|
*`DEY
|
|
*`CPY`GLOBYMIN
|
|
*`BCS`:BLAH
|
|
*`LDA`BUFFER
|
|
*`EOR`#$80
|
|
*`STA`BUFFER
|
|
*`BNE`:WHOPEE
|
|
*`INC`BUFFER+1
|
|
*:WHOPEE`DEX
|
|
*`BNE`:YAY
|
|
*:RESET`LDA`#0`;Need`to`reset`these`guys
|
|
*`STA`GLOBXMAX
|
|
*`STA`GLOBYMAX
|
|
*`LDA`#$FF
|
|
*`STA`GLOBXMIN
|
|
*`STA`GLOBYMIN
|
|
|
|
****`Next,`read`and`draw`polygons
|
|
|
|
READDRAW LDY #00
|
|
STY INDEX
|
|
OBJLOOP LDY INDEX
|
|
LDA POLYLIST,Y ;First,`the`number`of`points
|
|
BNE :CONT ;But`if`numpoints`is`zero`then
|
|
JMP OBJDONE ;we`are`at`the`end`of`the`list
|
|
:CONT STA COUNTPTS
|
|
INC INDEX
|
|
|
|
*`Rotate`project`and`draw`the`polygon
|
|
*`Make`sure`buffer`being`drawn`to`is`clear!
|
|
|
|
:DOIT JSR ROTPROJ
|
|
|
|
*`Convert`xmin`and`xmax`to`columns
|
|
|
|
LDA LOCXMIN
|
|
LSR
|
|
LSR
|
|
LSR ;x`mod`8
|
|
STA LOCXMIN
|
|
CMP GLOBXMIN
|
|
BCS :NAH
|
|
STA GLOBXMIN
|
|
:NAH LDA LOCYMIN
|
|
CMP GLOBYMIN
|
|
BCS :UHUH
|
|
STA GLOBYMIN
|
|
:UHUH LDA LOCXMAX
|
|
LSR
|
|
LSR
|
|
LSR
|
|
STA LOCXMAX
|
|
CMP GLOBXMAX
|
|
BCC :NOWAY
|
|
STA GLOBXMAX
|
|
:NOWAY LDA LOCYMAX
|
|
CMP GLOBYMAX
|
|
BCC EORFILL
|
|
STA GLOBYMAX
|
|
|
|
*`If`using`the`EOR-buffer,`copy`into`drawing`buffer
|
|
*`And`then`clear`the`EOR-buffer
|
|
|
|
EORFILL LDA FILL
|
|
BEQ OBJLOOP
|
|
|
|
>>> SETBUF
|
|
LDA #<EORBUF
|
|
STA TEMP1
|
|
LDA #>EORBUF
|
|
STA TEMP1+1
|
|
|
|
LDA LOCXMIN ;LOCXMIN`now`contains`column
|
|
LSR ;Each`column`is`128`bytes
|
|
BCC :EVEN ;So`there`might`be`a`carry
|
|
LDY #$80
|
|
STY BUFFER
|
|
STY TEMP1
|
|
CLC
|
|
:EVEN STA T2
|
|
ADC BUFFER+1
|
|
STA BUFFER+1 ;Each`column`is`128`bytes
|
|
LDA T2
|
|
ADC TEMP1+1 ;Now`we`will`start`at`the
|
|
STA TEMP1+1 ;column
|
|
|
|
LDA LOCXMAX
|
|
SEC
|
|
SBC LOCXMIN
|
|
TAX ;Total`number`of`columns`to`do
|
|
INX ;e.g.`fill`columns`1..3
|
|
LDY LOCYMAX
|
|
BNE :FOOP
|
|
INC LOCYMAX
|
|
:FOOP LDY LOCYMAX
|
|
LDA #00
|
|
:GOOP EOR (TEMP1),Y ;EOR-buffer
|
|
PHA
|
|
*`Maybe`put`an`EOR`below?
|
|
EOR (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
LDA #00 ;Might`as`well`clear`it`now
|
|
STA (TEMP1),Y
|
|
PLA
|
|
DEY
|
|
CPY LOCYMIN
|
|
BCS :GOOP
|
|
LDA BUFFER
|
|
EOR #$80
|
|
STA BUFFER
|
|
STA TEMP1
|
|
BNE :BOOP
|
|
INC BUFFER+1
|
|
INC TEMP1+1
|
|
:BOOP DEX
|
|
BNE :FOOP
|
|
JMP OBJLOOP
|
|
|
|
OBJDONE
|
|
****`Swap`buffers
|
|
|
|
SWAPBUF LDA VMCSB
|
|
EOR #$02 ;Pretty`tricky,`eh?
|
|
STA VMCSB
|
|
LDA #$08
|
|
EOR ZTEMP ;ztemp=high`byte`just`flips
|
|
STA ZTEMP ;between`$30`and`$38
|
|
|
|
JMP MAIN ;Around`and`around`we`go...
|
|
|
|
TXT 'Gee`Brain,`what`do`you`want`to`do`'
|
|
TXT 'tonight?'
|
|
|
|
**`Rotate,`project,`and`store`the`points
|
|
*
|
|
*`This`part`is`a`significant`change`since
|
|
*`v2.0.``Now`it`is`a`completely`general`polygon`plotter.
|
|
*`A`set`of`points`is`read`in,`rotated`and`projected,`and
|
|
*`plotted`into`the`drawing`buffer`(EOR`or`normal).
|
|
|
|
ROTPROJ
|
|
|
|
*`A`neat`macro
|
|
NEG MAC ;Change`the`sign`of`a`two's`complement
|
|
CLC
|
|
LDA ]1 ;number.
|
|
EOR #$FF
|
|
ADC #$01
|
|
<<<
|
|
|
|
*-------------------------------
|
|
*`These`macros`replace`the`previous`projection
|
|
*`subroutine.
|
|
|
|
SMULT`MAC`;Multiply`two`signed`8-bit
|
|
;numbers:`A*Y/64`->`A
|
|
STA`Z3
|
|
CLC``;This`multiply`is`for`normal
|
|
EOR`#$FF`;numbers,`i.e.`x=-64..64
|
|
ADC`#$01
|
|
STA`Z4
|
|
LDA`(Z3),Y
|
|
SEC
|
|
SBC`(Z4),Y
|
|
<<<``;All`done`:)
|
|
|
|
SMULTZ MAC ;Multiply`two`signed`8-bit
|
|
;numbers:`A*Y/64`->`A
|
|
STA Z1
|
|
CLC ;And`this`multiply`is`specifically
|
|
EOR #$FF ;for`the`projection`part,`where
|
|
ADC #$01 ;numbers`are`-110..110`and`0..40
|
|
STA Z2
|
|
LDA (Z1),Y
|
|
SEC
|
|
SBC (Z2),Y
|
|
<<< ;All`done`:)
|
|
|
|
PROJECT MAC ;The`actual`projection`routine
|
|
;The`routine`takes`the`point
|
|
;]1`]2`]3,`rotates`and
|
|
;projects`it,`and`stores`the
|
|
;result`in`]1`]2`]3.
|
|
|
|
LDY ]1 ;Multiply`first`rotation`column
|
|
LDA A11
|
|
>>> SMULT
|
|
STA P1T
|
|
LDA D21
|
|
>>> SMULT
|
|
STA P2T
|
|
LDA G31
|
|
>>> SMULT
|
|
STA P3T
|
|
LDY ]2 ;Second`column
|
|
LDA B12
|
|
>>> SMULT
|
|
CLC
|
|
ADC P1T
|
|
STA P1T
|
|
LDA E22
|
|
>>> SMULT
|
|
CLC
|
|
ADC P2T
|
|
STA P2T
|
|
LDA H32
|
|
>>> SMULT
|
|
CLC
|
|
ADC P3T
|
|
STA P3T
|
|
LDY ]3 ;Third`column
|
|
LDA C13
|
|
>>> SMULT
|
|
CLC
|
|
ADC P1T
|
|
STA P1T
|
|
LDA F23
|
|
>>> SMULT
|
|
CLC
|
|
ADC P2T
|
|
STA P2T
|
|
LDA I33
|
|
>>> SMULT
|
|
CLC
|
|
ADC P3T
|
|
STA ]3 ;Rotated`Z
|
|
TAX
|
|
LDY ZDIV,X ;Table`of`d/(z+z0)
|
|
;Now`Y`contains`projection`const
|
|
|
|
LDA P1T
|
|
>>> SMULTZ
|
|
LDX ZOOM
|
|
CPX #64
|
|
BEQ CONTX
|
|
STY TEMP1
|
|
LDY ZOOM
|
|
>>> SMULT
|
|
LDY TEMP1
|
|
CONTX CLC
|
|
ADC #64 ;Offset`the`coordinate
|
|
STA ]1 ;Rotated`and`projected
|
|
CMP LOCXMIN ;See`if`it`is`a`local`minimum
|
|
BCS NOTXMIN
|
|
STA LOCXMIN
|
|
NOTXMIN CMP LOCXMAX
|
|
BCC NOTXMAX
|
|
STA LOCXMAX
|
|
|
|
NOTXMAX LDA P2T
|
|
>>> SMULTZ
|
|
CPX #64
|
|
BEQ CONTY
|
|
LDY ZOOM
|
|
>>> SMULT
|
|
CONTY CLC
|
|
ADC #64
|
|
STA ]2 ;Rotated`and`projected`Y
|
|
CMP LOCYMIN
|
|
BCS NOTYMIN
|
|
STA LOCYMIN
|
|
NOTYMIN CMP LOCYMAX
|
|
BCC NOTYMAX
|
|
STA LOCYMAX
|
|
|
|
NOTYMAX <<< ;All`done
|
|
|
|
*`LDA`#<EORBUF`;First`we`need`to`clear`the
|
|
*`STA`BUFFER`;EOR`buffer
|
|
*`LDA`#>EORBUF
|
|
*`STA`BUFFER+1
|
|
|
|
LDA #0 ;Reset`Ymin`and`Ymax
|
|
STA LOCYMAX
|
|
STA LOCXMAX
|
|
LDA #$FF
|
|
STA LOCYMIN
|
|
STA LOCXMIN
|
|
|
|
READPTS LDY INDEX
|
|
LDA POLYLIST,Y
|
|
STA P1X
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P1Y
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P1Z
|
|
INY
|
|
DEC COUNTPTS
|
|
LDA POLYLIST,Y
|
|
STA P2X
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P2Y
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P2Z
|
|
INY
|
|
DEC COUNTPTS
|
|
LDA POLYLIST,Y
|
|
STA P3X
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P3Y
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P3Z
|
|
INY
|
|
STY INDEX
|
|
>>> PROJECT,P1X;P1Y;P1Z
|
|
>>> PROJECT,P2X;P2Y;P2Z
|
|
>>> PROJECT,P3X;P3Y;P3Z
|
|
|
|
LDA HIDE
|
|
BEQ :DOIT
|
|
LDA P2X ;Hidden`face`check
|
|
SEC
|
|
SBC P1X
|
|
TAY ;Y=(x2-x1)
|
|
LDA P3Y
|
|
SEC
|
|
SBC P2Y ;A=(y3-y2)
|
|
>>> SMULT
|
|
STA TEMP1
|
|
LDA P3X
|
|
SEC
|
|
SBC P2X
|
|
TAY
|
|
LDA P2Y
|
|
SEC
|
|
SBC P1Y
|
|
>>> SMULT
|
|
CMP TEMP1 ;If`x1*y2-y1*x2`>`0`then`face
|
|
BMI :DOIT ;is`visible
|
|
DEC COUNTPTS ;Otherwise`read`in`remaining
|
|
BEQ :ABORT ;points`and`return
|
|
:POOP INC INDEX
|
|
INC INDEX
|
|
INC INDEX
|
|
DEC COUNTPTS
|
|
BNE :POOP
|
|
:ABORT RTS
|
|
|
|
:DOIT LDA P1X
|
|
STA X1
|
|
LDA P1Y
|
|
STA Y1
|
|
LDA P2X
|
|
STA X2
|
|
LDA P2Y
|
|
STA Y2
|
|
JSR DRAW
|
|
LDA P2X
|
|
STA X1
|
|
LDA P2Y
|
|
STA Y1
|
|
LDA P3X
|
|
STA X2
|
|
LDA P3Y
|
|
STA Y2
|
|
JSR DRAW
|
|
|
|
DEC COUNTPTS
|
|
BNE POLYLOOP ;Is`it`just`a`triangle?
|
|
JMP POLYDONE
|
|
|
|
POLYLOOP LDY INDEX
|
|
LDA POLYLIST,Y
|
|
STA P2X
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P2Y
|
|
INY
|
|
LDA POLYLIST,Y
|
|
STA P2Z
|
|
INY
|
|
STY INDEX
|
|
>>> PROJECT,P2X;P2Y;P2Z
|
|
|
|
LDA P2X
|
|
STA X1
|
|
LDA P2Y
|
|
STA Y1
|
|
LDA P3X
|
|
STA X2
|
|
LDA P3Y
|
|
STA Y2
|
|
JSR DRAW
|
|
|
|
LDA P2X
|
|
STA P3X
|
|
LDA P2Y
|
|
STA P3Y
|
|
DEC COUNTPTS
|
|
BEQ POLYDONE
|
|
JMP POLYLOOP
|
|
POLYDONE LDA P1X ;Close`the`polygon
|
|
STA X2
|
|
LDA P1Y
|
|
STA Y2
|
|
LDA P3X
|
|
STA X1
|
|
LDA P3Y
|
|
STA Y1
|
|
JSR DRAW
|
|
RTS
|
|
|
|
TXT 'Same`thing`we`do`every`night,`Pinky:`'
|
|
TXT 'try`to`take`over`the`world!'
|
|
|
|
|
|
*-------------------------------
|
|
*`General`questionable-value`error`procedure
|
|
|
|
*CHOKE`LDX`#00
|
|
*:LOOP`LDA`:CTEXT,X
|
|
*`BEQ`:DONE
|
|
*`JSR`CHROUT
|
|
*`INX
|
|
*`JMP`:LOOP
|
|
*:DONE`RTS
|
|
*:CTEXT`HEX`0D`;CR
|
|
*`TXT`'something`choked`:('
|
|
*`HEX`0D00
|
|
*
|
|
TXT 'Narf!'
|
|
|
|
*-------------------------------
|
|
*`Drawin'`a`line.``A`fahn`lahn.
|
|
|
|
***`Some`useful`macros
|
|
|
|
CINIT MAC ;Macro`to`initialize`the`counter
|
|
LDA ]1 ;dx`or`dy
|
|
LSR
|
|
<<< ;The`dx/2`makes`a`nicer`looking`line
|
|
|
|
*****`Macro`to`take`a`step`in`X
|
|
|
|
XSTEP MAC
|
|
LDX DX ;Number`of`loop`iterations
|
|
>>> CINIT,DX
|
|
XLOOP LSR CHUNK
|
|
BEQ FIXC ;Update`column
|
|
SBC DY
|
|
BCC FIXY ;Time`to`step`in`Y
|
|
DEX
|
|
BNE XLOOP
|
|
DONE LDA OLDX ;Plot`the`last`chunk
|
|
EOR CHUNK
|
|
ORA (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
RTS
|
|
|
|
FIXC PHA
|
|
LDA OLDX
|
|
ORA (BUFFER),Y ;Plot
|
|
STA (BUFFER),Y
|
|
LDA #$FF ;Update`chunk
|
|
STA OLDX
|
|
STA CHUNK
|
|
LDA #$80 ;Increase`the`column
|
|
EOR BUFFER
|
|
STA BUFFER
|
|
BNE C2
|
|
INC BUFFER+1
|
|
C2
|
|
PLA
|
|
SBC DY
|
|
BCS CONT
|
|
ADC DX
|
|
IF I,]1 ;Do`we`use`INY`or`DEY?
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
CONT DEX
|
|
BNE XLOOP
|
|
JMP DONE
|
|
|
|
FIXY ADC DX
|
|
PHA
|
|
LDA OLDX
|
|
EOR CHUNK
|
|
ORA (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
LDA CHUNK
|
|
STA OLDX
|
|
PLA
|
|
IF I,]1 ;Update`Y
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
DEX
|
|
BNE XLOOP
|
|
RTS
|
|
<<< ;End`of`Macro`xstep
|
|
|
|
*****`Take`a`step`in`Y
|
|
|
|
YSTEP MAC
|
|
LDX DY ;Number`of`loop`iterations
|
|
BEQ DONE ;If`dy=0`it's`just`a`point
|
|
>>> CINIT,DY
|
|
SEC
|
|
YLOOP PHA
|
|
LDA OLDX
|
|
ORA (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
PLA
|
|
IF I,]1
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
SBC DX
|
|
BCC FIXX
|
|
DEX
|
|
BNE YLOOP
|
|
DONE LDA OLDX
|
|
ORA (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
RTS
|
|
|
|
FIXX ADC DY
|
|
LSR OLDX
|
|
SEC ;Important!
|
|
BEQ FIXC
|
|
DEX
|
|
BNE YLOOP
|
|
JMP DONE
|
|
|
|
FIXC PHA
|
|
LDA #$80
|
|
STA OLDX
|
|
EOR BUFFER
|
|
STA BUFFER
|
|
BNE C2
|
|
INC BUFFER+1
|
|
C2 PLA
|
|
DEX
|
|
BNE YLOOP
|
|
JMP DONE
|
|
<<< ;End`of`Macro`ystep
|
|
|
|
*`Take`an`x`step`in`the`EOR`buffer
|
|
*`The`sole`change`is`to`use`EOR`instead`of`ORA
|
|
|
|
EORXSTEP MAC
|
|
LDX DX ;Number`of`loop`iterations
|
|
>>> CINIT,DX
|
|
XLOOP LSR CHUNK
|
|
BEQ FIXC ;Update`column
|
|
SBC DY
|
|
BCC FIXY ;Time`to`step`in`Y
|
|
DEX
|
|
BNE XLOOP
|
|
DONE LDA OLDX ;Plot`the`last`chunk
|
|
EOR CHUNK
|
|
EOR (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
RTS
|
|
|
|
FIXC PHA
|
|
LDA OLDX
|
|
EOR (BUFFER),Y ;Plot
|
|
STA (BUFFER),Y
|
|
LDA #$FF ;Update`chunk
|
|
STA OLDX
|
|
STA CHUNK
|
|
LDA #$80 ;Increase`the`column
|
|
EOR BUFFER
|
|
STA BUFFER
|
|
BNE C2
|
|
INC BUFFER+1
|
|
C2
|
|
PLA
|
|
SBC DY
|
|
BCS CONT
|
|
ADC DX
|
|
IF I,]1 ;Do`we`use`INY`or`DEY?
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
CONT DEX
|
|
BNE XLOOP
|
|
JMP DONE
|
|
|
|
FIXY ADC DX
|
|
PHA
|
|
LDA OLDX
|
|
EOR CHUNK
|
|
EOR (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
LDA CHUNK
|
|
STA OLDX
|
|
PLA
|
|
IF I,]1 ;Update`Y
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
DEX
|
|
BNE XLOOP
|
|
RTS
|
|
<<< ;End`of`Macro`xstep
|
|
|
|
|
|
*`Take`a`y-step`in`the`EOR-buffer
|
|
*`Changes`from`above`are:`only`plot`last`part`of`each
|
|
*`vertical`chunk,`don't`plot`last`point,`plot`with`EOR
|
|
|
|
EORYSTEP MAC
|
|
LDX DY ;Number`of`loop`iterations
|
|
BEQ DONE ;If`dy=0`it's`just`a`point
|
|
>>> CINIT,DY
|
|
SEC
|
|
*YLOOP`PHA
|
|
*`LDA`OLDX
|
|
*`ORA`(BUFFER),Y
|
|
*`STA`(BUFFER),Y
|
|
*`PLA
|
|
YLOOP IF I,]1
|
|
INY
|
|
ELSE
|
|
DEY
|
|
FIN
|
|
SBC DX
|
|
BCC FIXX
|
|
DEX
|
|
BNE YLOOP
|
|
*DONE`LDA`OLDX
|
|
*`ORA`(BUFFER),Y
|
|
*`STA`(BUFFER),Y
|
|
DONE RTS
|
|
|
|
FIXX ADC DY
|
|
PHA ;We`only`plot`the`last`part`of`each`chunk
|
|
LDA OLDX
|
|
EOR (BUFFER),Y
|
|
STA (BUFFER),Y
|
|
PLA
|
|
LSR OLDX
|
|
SEC ;Important!
|
|
BEQ FIXC
|
|
DEX
|
|
BNE YLOOP
|
|
JMP DONE
|
|
|
|
FIXC PHA
|
|
LDA #$80
|
|
STA OLDX
|
|
EOR BUFFER
|
|
STA BUFFER
|
|
BNE C2
|
|
INC BUFFER+1
|
|
C2 PLA
|
|
DEX
|
|
BNE YLOOP
|
|
JMP DONE
|
|
<<< ;End`of`Macro`ystep
|
|
****`Initial`line`setup
|
|
|
|
**`The`commented`lines`below`are`now`taken`care`of`by`the
|
|
**`calling`routine.
|
|
*DRAW`>>>`MOVE,TX1;X1``;Move`stuff`into`zero`page
|
|
*`>>>`MOVE,TX2;X2``;Where`it`can`be`modified
|
|
*`>>>`MOVE,TY1;Y1
|
|
*`>>>`MOVE,TY2;Y2
|
|
|
|
DRAW LDA FILL
|
|
BNE :SETEOR
|
|
>>> SETBUF
|
|
JMP :SETUP
|
|
:SETEOR LDA #<EORBUF ;Use`EOR`buffer`instead`of
|
|
STA BUFFER ;display`buffer`for`drawing
|
|
LDA #>EORBUF
|
|
STA BUFFER+1
|
|
|
|
:SETUP SEC ;Make`sure`x1<x2
|
|
LDA X2
|
|
SBC X1
|
|
BCS :CONT
|
|
LDA Y2 ;If`not,`swap`P1`and`P2
|
|
LDY Y1
|
|
STA Y1
|
|
STY Y2
|
|
LDA X1
|
|
LDY X2
|
|
STY X1
|
|
STA X2
|
|
|
|
SEC
|
|
SBC X1 ;Now`A=dx
|
|
:CONT STA DX
|
|
LDX X1 ;Put`x1`into`X,`now`we`can`trash`X1
|
|
|
|
COLUMN TXA ;Find`the`first`column`for`X
|
|
LSR
|
|
LSR ;There`are`x1/8`128`byte`blocks
|
|
LSR ;Which`means`x1/16`256`byte`blocks
|
|
LSR
|
|
BCC :EVEN ;With`a`possible`extra`128`byte`block
|
|
LDY #$80 ;if`so,`set`the`high`bit
|
|
STY BUFFER
|
|
CLC
|
|
:EVEN ADC BUFFER+1 ;Add`in`the`number`of`256`byte`blocks
|
|
STA BUFFER+1
|
|
|
|
SEC
|
|
LDA Y2 ;Calculate`dy
|
|
SBC Y1
|
|
BCS :CONT2 ;Is`y2>y1?
|
|
EOR #$FF ;Otherwise`dy=y1-y2
|
|
ADC #$01
|
|
:CONT2 STA DY
|
|
CMP DX ;Who's`bigger:`dy`or`dx?
|
|
BCC STEPINX ;If`dx,`then...
|
|
JMP STEPINY
|
|
|
|
STEPINX LDY Y1
|
|
CPY Y2
|
|
LDA BITP,X ;X`currently`contains`x1
|
|
STA OLDX
|
|
STA CHUNK
|
|
BCC XINCY ;Do`we`step`forwards`or`backwards`in`Y?
|
|
JMP XDECY
|
|
|
|
XINCY LDA FILL
|
|
BEQ NORMXINC
|
|
>>> EORXSTEP,INY
|
|
NORMXINC >>> XSTEP,INY
|
|
|
|
XDECY LDA FILL
|
|
BEQ NORMXDEC
|
|
>>> EORXSTEP,DEY
|
|
NORMXDEC >>> XSTEP,DEY
|
|
|
|
STEPINY LDY Y1
|
|
LDA BITP,X ;X=x1
|
|
STA OLDX
|
|
LSR ;Y`doesn't`use`chunks
|
|
EOR OLDX ;So`we`just`want`the`bit
|
|
STA OLDX
|
|
CPY Y2
|
|
BCS YDECY
|
|
|
|
YINCY LDA FILL
|
|
BEQ NORMINC
|
|
>>> EORYSTEP,INY
|
|
NORMINC >>> YSTEP,INY
|
|
|
|
YDECY LDA FILL
|
|
BEQ NORMDEC
|
|
>>> EORYSTEP,DEY
|
|
NORMDEC >>> YSTEP,DEY
|
|
|
|
|
|
*-------------------------------
|
|
*`Clean`up
|
|
|
|
CLEANUP LDA VMCSB ;Switch`char`rom`back`in
|
|
AND #%11110101 ;default
|
|
STA VMCSB
|
|
|
|
RTS ;bye!
|
|
|
|
TXT 'spinal`cracker`'
|
|
TXT 'slj`6/95'
|
|
|
|
*-------------------------------
|
|
*`Set`up`bit`table
|
|
|
|
DS ^ ;Clear`to`end`of`page
|
|
;So`that`tables`start`on`a`page`boundary
|
|
BITP LUP 16 ;128`Entries`for`X
|
|
DFB %11111111
|
|
DFB %01111111
|
|
DFB %00111111
|
|
DFB %00011111
|
|
DFB %00001111
|
|
DFB %00000111
|
|
DFB %00000011
|
|
DFB %00000001
|
|
--^
|
|
|
|
SIN ;Table`of`sines,`120`bytes
|
|
COS EQU SIN+128 ;Table`of`cosines
|
|
;Both`of`these`trig`tables`are
|
|
;currently`set`up`from`BASIC
|
|
ZDIV EQU COS+128 ;Division`table
|
|
TMATH1 EQU ZDIV+384 ;Math`table`of`f(x)=x*x/256
|
|
TMATH2 EQU TMATH1+512 ;Second`math`table
|
|
POLYLIST EQU TMATH2+512 ;List`of`polygons
|
|
|
|
========================================================================
|
|
Second SID Chip Installation
|
|
Copyright 1988 Mark A. Dickenson
|
|
|
|
This information and software is COPYRIGHTED and made available on a
|
|
SHAREWARE basis. This file can be freely copied and distributed as long
|
|
as it is not SOLD. This information cannot be used to construct and sell a
|
|
hardware device without receiving prior permission from the author. There is
|
|
not a set fee for the use of this information. Just send in whatever you feel
|
|
the information is worth.
|
|
|
|
If you have any gripes, complaints, suggestions, COMPLIMENTS or DONATIONS of
|
|
any sort please send them to:
|
|
|
|
Mark Dickenson
|
|
600 South West Street
|
|
Nevada, Missouri 64772
|
|
|
|
Adding an extra SID 6581/6582 chip
|
|
|
|
This is not a project to be tackled by the sqeamish or people who are deathly
|
|
afraid of opening their computer just to take a peek inside.
|
|
|
|
Now let's get rid of the nasty stuff first. No liability is assumed with
|
|
respect to the use of the following information. In other words if you
|
|
screw-up trying to install this modification, then it's your responsability.
|
|
|
|
YOU DO THIS AT YOUR OWN RISK!!!!
|
|
|
|
If you do not feel up to it PLEASE take it to a Commodore repair center
|
|
or a repair service that can work on computers and let them do the
|
|
installation. I will warn you that most Commodore Repair Centers will not or
|
|
do not like to do this modification. When they do, it can be expensive. If
|
|
you belong to a Users Group, tell them about the project and ask if there is
|
|
anyone there that could perform the operation. This modification will NOT
|
|
hurt the computer in any way, unless it is installed WRONG.
|
|
|
|
You can make your own piggy back board or you can do what I am going to
|
|
describe (since it is a little hard to put a schematic in a text file).
|
|
|
|
You should ground yourself with a static guard wristband (such as what
|
|
Radio Shack sells). Even though the chip is quite durable, just the right
|
|
static discharge can ruin all or part of the SID chip.
|
|
|
|
For those of you that are not familier with the way pins are numbered on an
|
|
IC chip here is a short explanation. On one end of the IC you should find a
|
|
little notch, looking at the chip with the notch at the top the numbering goes
|
|
this way. The upper left corner of the chip is pin 1 and they are numbered
|
|
consecutively, counter-clockwise around the chip. Some chips do not have a
|
|
notch in one end, but instead dot is placed in one of the chip corners to
|
|
designate that pin 1 starts in that location.
|
|
|
|
notch
|
|
----,,----
|
|
1-!. !-8
|
|
2-! dot !-7
|
|
3-! !-6
|
|
4-! !-5
|
|
----------
|
|
|
|
|
|
I have included the information that is needed to install this modification
|
|
on the Commodore 64, 64C and 128. I haven't been able to look inside the
|
|
128D, so I cannot provide the information with any accuracy.
|
|
|
|
There are TWO different 64C circuit boards and both use DIFFERENT SID
|
|
chips. You can tell the difference by opening the 64C. If you see a 64-pin
|
|
chip on the board and the board is only 5.5-6 inches wide then you have the
|
|
narrow board 64C and must use the 9 volt 6582 SID chip. The number of the
|
|
chip in the 64C narrow is an 8520 and is the same as the 6582.
|
|
|
|
----------------------------------
|
|
|
|
Parts Commodore 64, 64C (wide) & 128
|
|
|
|
1 - 6581 SID chip from Jamco or Kassara Microsystems
|
|
1 - 2N2222 transistor Radio Shack 276-1617
|
|
2 - 220pf capacitors Radio Shack 272-124
|
|
|
|
-----------------------------------
|
|
|
|
Parts Commodore 64C Narrow Board
|
|
|
|
1 - 6582 SID Chip From Jamco or Kassara Microsystems
|
|
1 - 2222A transistor Radio Shack 276-2009
|
|
2 - .022uf capacitors Radio Shack 272-1066
|
|
2 - 1k ohm 1/4 watt resistors Radio Shack 271-1321
|
|
|
|
-----------------------------------
|
|
|
|
Parts 64, 64C (all) & 128
|
|
|
|
2 - 1k ohm 1/4 watt resistors Radio Shack 271-1321
|
|
1 - 1000 pf capacitor Radio Shack 272-126 listed as .001 mf this is
|
|
the same as 1000pf
|
|
1 - 10k ohm 1/4 watt resistor Radio Shack 271-1335
|
|
1 - 10 uf electrolitic capacitor Radio Shack 272-1025
|
|
1 - 5 inch length of wire
|
|
1 - 5 inch length of shielded cable
|
|
1 - surface mount female RCA plug (this is what you normally find on the back
|
|
of your stereo.
|
|
|
|
|
|
On the C-64 and 64C (wide) the SID is IC U18 (the IC number will be marked in
|
|
white on the circuit board). It is usually located in the middle of the
|
|
circuit board, next to the metal video chip case or up between and just
|
|
below the serial and monitor jacks.
|
|
|
|
On the C-64C (narrow board) the SID chip is IC U9. It is located in the
|
|
middle of the board, just a little to the right of center) and called 520.
|
|
|
|
On the C-128 the SID is IC U5. It is located at the back of the circuit
|
|
board just to the right of the metal housing for the 40 and 80 column video
|
|
chips.
|
|
|
|
First bend out pins 23, 24 and 26 and cut them off of the 6581/6582 SID
|
|
chip. These are for the two analog and one audio input lines. They will
|
|
cause problems if connected and since they will not be used it is best to
|
|
remove them.
|
|
|
|
Now bend out pins 1, 2, 3, 4, 8, and 27.
|
|
|
|
Solder one of the 220pf capacitors (64C narrow uses .022 uf) to pins 1
|
|
and 2 then solder the other 220pf (64C narrow - .022uf) capacitor to pins 3
|
|
and 4. The capacitors control the upper and lower frequency range and
|
|
filters of the SID chip.
|
|
|
|
The reason I am using 220pf capacitors is because of problems with the
|
|
filters in the SID chip. The C-64 first came out with 2200pf capacitors, but
|
|
they were changed to 470pf. The reason for this was because the filters of
|
|
the SID vary from chip to chip and using 2200pf caused a lot of them to sound
|
|
muffeled when the filters were on. I have found that by lowering the
|
|
capacitor value to 220 pf helps even more. If you wish, you can use 470s if
|
|
you feel it would be better, but DO NOT use 2200pf.
|
|
|
|
The 6582 SID chip for the 64C narrow must use the .022uf capacitors, as the
|
|
filter range is much different.
|
|
|
|
Solder one end of your wire to pin 8 of the SID chip. This is for the chip
|
|
select line. We will connect this to the cartridge port. This tells the
|
|
computer where in memory the chip resides (described later).
|
|
|
|
Now solder the remaining pins (excluding the ones we have bent out
|
|
and/or removed 1, 2, 3, 4, 8, 23, 24, 26 and 27) to the sid chip currently in
|
|
your computer. You may have to bend those pins inward just a little for them
|
|
to get a good grip on the SID chip. Be very careful not leave the soldering
|
|
iron on the chip TOO long as you could ruin BOTH SID chips. I would put some
|
|
heat sink (silicon grease) between the two chips before soldering them
|
|
together. This will provide better heat dispersal on the bottom chip.
|
|
|
|
Now that you have the chips soldered together (place the SID chips back in
|
|
the socket if you removed them), solder the wire from pin 8 (on the SID chip)
|
|
to pin 7 of the cartridge port on the back of the computer. Set the computer
|
|
infront of you like to are getting ready to type, with the back of the
|
|
computer away from you. Look at the cartridge port (located in the upper
|
|
right corner of the circuit board). You will see two rows of pins connecting
|
|
the cartridge port to the circuit board. You want the row of pins closest to
|
|
the front of the computer. Now, count the pins starting at the LEFT side and
|
|
counting to the right. You want to solder the wire from pin 8 of the extra
|
|
SID chip to pin number 7 of the cartridge port. This is the same place on all
|
|
of the models C-64, 64C and 128.
|
|
|
|
This will tell the computer that the extra SID chip is at address $DE00 hex
|
|
or 56832 decimal. You will access it just like you would the regular sid
|
|
chip but starting at this address.
|
|
|
|
I am no longer describing how to connect for address $DF00. This
|
|
address causes problems with the RAM Expansion Units and numerous other
|
|
cartridges. From now on address $DE00 is the ONLY address for the SID chip.
|
|
|
|
Now partially reassemble your computer (be careful that nothing shorts out
|
|
the pins still sticking out). Turn the computer on and load the player
|
|
program provided and tell it to load in 'TEST'. If you get sound then so far
|
|
so good. Turn off the computer and disassemble the case.
|
|
|
|
Drill a hole in the back end of the computer just large enough to anchor
|
|
the RCA plug. Then solder the center wire of the shielded cable to the
|
|
center post of the RCA plug. Insert the wire through the hole you have
|
|
just drilled and anchor the plug to the case. Now solder the ground wire to
|
|
the ground tab on the RCA plug.
|
|
|
|
Here comes the difficult part to explain. This is the coupling circuit
|
|
for the audio output. Here is a rough schematic.
|
|
|
|
|
|
Pin 27 on 12volts dc,
|
|
9volts 64C (narrow)
|
|
SID chip resistor
|
|
!--. 10k ohm !collector
|
|
27!----.--/!/!/--.-----O 2n2222 or 2222A
|
|
--' ! ! !emitter
|
|
! ! !
|
|
<resistor ! !
|
|
>1k ! ! +
|
|
|
|
===========================================================================
|
|
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. The main purpose of this article is to rescue a
|
|
usefull numerical algorithm, "Quartersolve", and also to provide a brief
|
|
look at the COMAL programming language and BLAS routines.
|
|
|
|
Linear systems of equations, A(,)*x()=b(), where A is a square matrix
|
|
and x and b are vectors (or arrays), must often be solved for x in the
|
|
solution of a variety of problems. The size or dimension of the problem
|
|
is n and just storing A requires 5*n*n bytes of memory, assuming C64/128
|
|
5 byte real variables. The prefered solution method is a form of
|
|
Gaussian Elimination which requires 1/3 n*n*n multiplications. I'll
|
|
ignore the additional n*n and n multiplies. For large problems our C64
|
|
has two serious limitations, small memory size and slow floating point
|
|
arithmetic. Problems with n=10 can be computed easily. Problems with
|
|
n=100 will require 100 times more memory and 1000 times more computing
|
|
time. The computing time is not a real problem. I don't mind letting
|
|
my computer run while I watch a movie, sleep, or go on a trip.
|
|
Calculating or setting up the problem may take much longer than its
|
|
solution anyway. Available memory is the practical limiting factor.
|
|
After we use up available RAM we have to resort to other algorithms that
|
|
will use the disk drive to move data in and out of the computer. The
|
|
1541 drive is particularly slow and I would not want to subject it to
|
|
undue wear and tear.
|
|
|
|
How big a problem do we need to be able to solve? In many cases the
|
|
problem itself will fix n and there is no way to reduce it. In other
|
|
cases you might be modeling a real continuous problem with a discrete
|
|
number of elements. N should be infinity but for problem solution n=50
|
|
might be big enough. Consider calculating the aerodynamic potential
|
|
flowfield around a body of revolution. You could fix points on the
|
|
surface of the body (a meridian) and have a series of sort line segments
|
|
make up elements to approximate the shape. The lager n is the closer
|
|
the smooth shape is aproximated and the more accurate the computed
|
|
solution becomes. n=100 might be a good choice for a simple shape. We
|
|
could also use a "higher order" menthod. In this case we can substitute
|
|
a curved line element for the straight line segment. Calculating the
|
|
matrix elements will be more difficult but n=40 curved elements might
|
|
give a more accurate solution than 100 flat elements. Another
|
|
consideration is the resolution of the solution. You might want to plot
|
|
the solution on the 200 x 320 pixel hi-res C64 screen. 40 points might
|
|
be too coarse and 320 might be overkill. We might also need to
|
|
calculate the slope or derivatives from the calculated solution which
|
|
will require more closely spaced solution points. There are often
|
|
choices that you can make in modeling a system and selecting a solution
|
|
algorithm so that a problem can be solved within the limits of a C64.
|
|
There are often interesting tradeoffs in memory requirements and
|
|
execution speed.
|
|
|
|
How big a problem can we solve with a C64? Using Quartersolve with
|
|
assembly language we can probably do n=200 or more. If we are going to
|
|
store the problem data on a single 1541 diskette and read it in a row at
|
|
time we can only do n=182 or so. Actually I think n should be well
|
|
under 100. Different operating systems and languages limit the amount
|
|
of useable RAM; BASIC 40K, COMAL 2.0 30K, GEOS 23K, the initial disk
|
|
loaded COMAL 0.14 10K... Solving a linear system may only be a small
|
|
subproblem inside a large application program. The idea is to be able
|
|
to solve reasonable sized problems using your prefered computing
|
|
environment without having to do a lot of chaining or loading of
|
|
separate programs. Quartersolve can free up a lot of memory for other
|
|
routines or allow n to be doubled.
|
|
|
|
SPEED
|
|
|
|
There are a few things that we can do to speed up the calculations.
|
|
First we can select a fast programming language. I prefere COMAL 2.0
|
|
which is a fast three pass interpreter. Using an assembler could be the
|
|
fastest and provide the most useable memory. A true compiler such as C
|
|
or Pascal could also be a good choice. BASIC is a poor choice except
|
|
that it is built in and free. In most cases execution can be sped up
|
|
with some machine language routines like the BLAS (Basic Linear Algebra
|
|
Subroutines). Calculation speed is measured in FLOPS/sec (Floating
|
|
Point OPerationS) where, c(i#,j#):=c(i#,j#) + a(i#,k#)*b(k#,j#) is the
|
|
operation. It is one FP multiply, one FP add, and some indexing
|
|
overhead. With some interpreters the indexing and interpreting overhead
|
|
can far exceed the actual FP multiply time. With assembled code the FP
|
|
multiply time should dominate. I use a ML level 1 BLAS package with
|
|
COMAL 2.0. For example:
|
|
|
|
c(i#,J#):+sdot(n#,a(i#,1),1,b(1,j#),sdb#)
|
|
FOR k#:=1 to n# do c(i#,j#):+a(i#,k#)*b(k#,j#)
|
|
|
|
both calculate the same thing, a dot product with n# FLOPS. For large
|
|
n# on a C64 the BLAS approach about 320 FLOPS/sec., The overhead of
|
|
calling the procedure from the interpreter is about the equivalent of 4
|
|
FLOPS. Of course modern computer performance is measured in
|
|
MegaFLOPS/sec. with 8 byte reals (super computers run hundreds or
|
|
thousands of MFLOPS/sec.). They also inflate the performance by
|
|
counting the multiply and add as two FLOPS. In his article I use the
|
|
"old flops" or number of multiplies.
|
|
|
|
It may also be possible to code 6502 FP arithmetic routines using lookup
|
|
tables that may perform faster than the built in routines. We could
|
|
also use the CPU in the disk drives to do distributed processing. But
|
|
this is beyond the scope of this article.
|
|
|
|
SOLUTION METHODS
|
|
|
|
Consider the following choices for numerical solution algorithms:
|
|
|
|
METHOD MEMORY FLOPS
|
|
Gaussian Elimination n*n 1/3 n*n*n
|
|
Cholesky Decomposition 1/2 n*n 1/6 n*n*n
|
|
QR decomposition n*n 2/3 n*n*n
|
|
QR updating 1/2 n*n 2 n*n*n
|
|
Gauss-Jordan n*n 1/2 n*n*n
|
|
Quartersolve 1/4 n*n 1/2 n*n*n
|
|
|
|
|
|
Gaussian Elimination is the prefered method when enough memory is
|
|
available. In modern terminology this is LU decomposition where A is
|
|
decomposed or factored into a lower triangular matrix and an upper
|
|
triangular matrix. Partial pivoting of rows or columns is an additional
|
|
complication often required for a stable solution. After the LU
|
|
decompostion you can readily solve for any number of right hand side
|
|
vectors in n*n flops each. In addition you can calculate matrix
|
|
condition number estimates and use iterative improvement techniques.
|
|
The LU decomposition is done in place overwriting the problem matrix A.
|
|
|
|
Cholesky Decomposition is a specialized version of Gaussian Elimination
|
|
for symetric positive definite matrices only. Since A is symetric we
|
|
only need n*(n+1)/2 memory storage locations. The L and U triangular
|
|
matrices are simply transposes of the other so only one needs to be
|
|
stored and is computed in place overwriting the original storage used
|
|
for A. No pivoting is required. This algorithm cannot solve general
|
|
nonsymetric problems and is included only for comparison.
|
|
|
|
QR decomposition factors A into an orthogonal matrix Q and a triangular
|
|
matrix R. QR decomposition is very stable and can be performed without
|
|
pivoting. Since Q is orthogonal its inverse is just Q transpose. To
|
|
solve the linear system we multiply the right hand side vector by Q
|
|
transpose then solve the triangular system R. Q is computed in a
|
|
special compact form and stored in the space below R. The decomposition
|
|
is done in place in the storage used for A, plus an additional n storage
|
|
locations. QR decomposition requires about twice as many flops as
|
|
Gaussian Elimination.
|
|
|
|
There is a variation of the QR solution known as QR updating. The
|
|
problem is solved a row at a time. A Row of A can be read in from disk
|
|
storage or calculated as needed. Only R needs to be stored in main
|
|
memory, n*(n+1)/2 memory locations. R is initialy the identity matrix
|
|
and is updated as each row of A and its right hand side element are
|
|
processed. Q is not stored, but the right hand side vector is
|
|
sequentialy multiplied by Q transpose. After all n rows have been
|
|
processed the solution is found by simply solving the triangular system
|
|
R. Since this method only needs half as much memory storage as LU
|
|
decomposition, we can solve problems 40% larger in a limited memory
|
|
space. However, the cost in flops is high. Actually QR updating is
|
|
best used for solving large overdetermined least squares problems.
|
|
|
|
Gauss-Jordan is a variation of Gaussian Elimination that reduces A to
|
|
the Identity matrix instead of to LU factors. By applying the same
|
|
transformations to to the right hand side that reduce A to the identity
|
|
matrix, the right hand side becomes the solution at completion.
|
|
Pivoting is requiered. Gauss-Jordan requires about 50% more flops than
|
|
Gaussian Elimination and most codes use n*n memory storage. Since the
|
|
LU factors are not computed we can't solve additional right hand side
|
|
vectors later, or estimate the matrix condition number, or use iterative
|
|
improvement techniques. It will solve multiple right hand sides that
|
|
are available from the start.
|
|
|
|
Quartersolve is a clever implementation of Gauss-Jordan(?) that solves
|
|
the problem a row at a time like QR updating but only requires 1/4 n*n
|
|
memory storage. With fixed available memory Quartersolve can solve a
|
|
problem twice as large as Gaussian Elimination but with a modest
|
|
performance penalty. Solving a 2n problem with Quartersolve would take
|
|
12 times longer (instead of 8) than Gaussian Elimination on a size n
|
|
problem.
|
|
|
|
My recommendation is to use Gaussian elimination for solving dense
|
|
general systems of linear equations when enough main memory is available
|
|
and switch to Quartersolve for larger problems. For solving huge
|
|
problems requiering external storage a blocked version of QR
|
|
decomposition might work best. Cholesky decomposition should be used
|
|
for symetric positive definite problems. Large problems are often
|
|
sparse, containing lots of zeros that need not be stored. Specialized
|
|
code exists for solving many sparce problems, particularly banded
|
|
matrices, and many of these methods can be used on a C64. Codes for
|
|
solving unstructured sparce problems are not very suitable for the C64
|
|
since they are complex and reduce the amount of memory available for
|
|
solving the problem. However, large sparce problems can also be solved
|
|
on the C64 by iterative methods such as Gauss-Siedel and Conjugate
|
|
Gradient algorithms.
|
|
|
|
QUARTERSOLVE
|
|
|
|
Quartersolve is a useful method for solving general dense systems of
|
|
linear equations that I discovered almost by accident while doing random
|
|
research in the library. I have not seen any recent texts or papers
|
|
mentioning this algorithm. I have not seen any reference to it in the
|
|
C64 literature either. At least one older text mentioned it in passing
|
|
saying that the code was too long or complex. This is a valid point
|
|
since usualy the code size directly subtracts from the problem storage.
|
|
The code is longer than the Gaussian Elimination code but in my
|
|
implementation it only takes about 2K of main memory storage and it is a
|
|
real advantage on the C64. With a C64 we can also put the entire code
|
|
in an EPROM on a cartridge so the code size is of little concern.
|
|
|
|
I found Quartersolve in Ref. 1 (R. A. Zamberdino, 1974), which credited
|
|
the algorithm to Ref. 2 (A. Orden, 1960). I am a little uneasy
|
|
describing the algorithm since I have not seen Ref. 2 or analyzed the
|
|
algorithm. I have coded the algorithm, tested it, and used it to solve
|
|
some large problems on a C64, up to n=90. Zambardino makes two
|
|
interesting statements in Ref 1. "The number of arithmetic operations
|
|
is the same as for the Gaussian Elimination method." I am reasonably
|
|
sure from the description that he meant Gauss-Jordan which requires
|
|
about 50% more arithmetic than Gaussian Elimination. After processing
|
|
the ith row only i(n-i) storage locations are required to store the
|
|
reduced matrix. Max[i(n-i)] = n*n/4. This maximum memory requirement
|
|
occurs at i = n/2. As i increases further memory required is reduced.
|
|
Although n*n/4 memory locations must be allocated and dimensioned in an
|
|
array at the start, Quartersolve always uses the first portion of the
|
|
array continuously and does not free up memory in holes scattered
|
|
throughout the array. The C language could possibly use "heap storage"
|
|
and release the memory for any other use as the procedure advances.
|
|
|
|
Now back to my initial memory free claim. The large problem that I
|
|
actually wanted to solve was: A*x=b, B*x=r, for r given b and the square
|
|
matrices A and B. Elements of A and B are most efficiently calculated
|
|
at the same time. I could write B to the drive and read it back in
|
|
after x is computed to calculate r, but I actually wanted to solve this
|
|
repeatedly inside another loop and I did not want to read and write to a
|
|
lousy 1541 that much. Using Gaussian elimination would require 2n*n
|
|
storage. Using Quartersolve could require 1.25n*n storage. However,
|
|
only n*n storage is needed, that for B. At the ith step the ith row of
|
|
A and B are calculated. The row of A is processed into the the n*n
|
|
dimensioned array B filling it from the front. The corresponding row of
|
|
B is stored in the same array B filling from from the end of array B.
|
|
As the process continues Quartersolve "dissuses" array B so that rows of
|
|
B never overwrite storage needed by Quartersolve. At the end we have
|
|
computed x and all of B is stored in the array B. Simple
|
|
multiplication produces r. So I can say with pride, at the expense of
|
|
honesty, that I have solved A*x=b without any additional memory storage
|
|
for A.
|
|
|
|
PROC slv(n#,nr#,i#,REF a(),REF c(),REF b(,),sdb#,REF sw#(),REF fail#) CLOSED
|
|
// This routine solves a system of equations using the quartersolve
|
|
// algorithm with partial pivoting.
|
|
// It is called a "line at a time" and uses only
|
|
// 0.25*nn memory locations which enables larger problems to be solved.
|
|
// The LU factorization is not available, nor a condition estimate.
|
|
// n# is the dimension of the problem
|
|
// nr# is the number of right hand vectors to be solved for.
|
|
// b(,) is the right hand side columns
|
|
// sdb# is the second dimension of the array b(,)
|
|
USE blas
|
|
USE strings
|
|
q#:=i#-1; m#:=n#-q#; mm1#:=m#-1; fail#:=TRUE; ip1#:=i#+1
|
|
IF i#=1 THEN //initialize pivot array
|
|
FOR j#:=1 TO n# DO sw#(j#):=j#
|
|
ENDIF
|
|
FOR j#:=1 TO q# DO //adjust for previous pivoting
|
|
k#:=sw#(j#)
|
|
WHILE k#<j# DO k#:=sw#(k#)
|
|
IF k#>j# THEN swap'real(c(j#),c(k#))
|
|
ENDFOR j#
|
|
FOR j#:=i# TO n# DO c(j#):-sdot(q#,c(1),1,a(j#-q#),m#)
|
|
p#:=q#+isamax#(m#,c(i#),1)
|
|
r:=ABS(c(p#))
|
|
IF r=0 THEN RETURN
|
|
fail#:=FALSE
|
|
IF p#<>i# THEN
|
|
swap'real(c(i#),c(p#))
|
|
swap'integer(sw#(i#),sw#(p#))
|
|
sswap(q#,a(1),m#,a(p#-q#),m#)
|
|
ENDIF
|
|
r:=1/c(i#)
|
|
IF mm1#<>0 THEN sscal(mm1#,r,c(ip1#),1)
|
|
FOR j#:=1 TO nr# DO b(i#,j#):=r*(b(i#,j#)-sdot(q#,c(1),1,b(1,j#),sdb#))
|
|
FOR k#:=1 TO nr# DO saxpy(q#,-b(i#,k#),a(1),m#,b(1,k#),sdb#)
|
|
IF mm1#>0 THEN
|
|
t#:=1
|
|
FOR j#:=1 TO q# DO
|
|
r:=a(t#); t#:+1
|
|
scopy(mm1#,a(t#),1,a(t#-j#),1)
|
|
saxpy(mm1#,-r,c(ip1#),1,a(t#-j#),1)
|
|
t#:+mm1#
|
|
ENDFOR j#
|
|
scopy(mm1#,c(ip1#),1,a(mm1#*q#+1),1)
|
|
ELSE //unscramble solution from pivoting
|
|
FOR j#:=1 TO nr# DO
|
|
FOR k#:=1 TO n# DO c(sw#(k#)):=b(k#,j#)
|
|
scopy(n#,c(1),1,b(1,j#),sdb#)
|
|
ENDFOR j#
|
|
ENDIF
|
|
ENDPROC slv
|
|
//
|
|
n#:=8; sdrh#:=1; nrh#:=1; nr#:=1
|
|
// a is of dimension n*n/4
|
|
DIM a(16), b(n#), rhs(n#,nrh#), sw#(n#)
|
|
FOR i#:=1 TO n# DO
|
|
FOR j#:=1 TO n# DO b(j#):=2297295/(i#+j#-1)
|
|
s:=0
|
|
FOR j#:=n# TO 1 STEP -1 DO s:+b(j#)
|
|
rhs(i#,1):=s
|
|
slv(n#,nr#,i#,a(),b(),rhs(,),sdrh#,sw#(),fail#)
|
|
IF fail# THEN
|
|
PRINT "singularity detected at i=";i#
|
|
STOP
|
|
ENDIF
|
|
ENDFOR i#
|
|
FOR j#:=1 TO n# DO PRINT rhs(j#,1);
|
|
END
|
|
|
|
The Quartersolve algorithm is presented here as a COMAL 2.0 procedure
|
|
"slv". COMAL is pretty much a dead language and I don't expect anyone
|
|
to run this code. However, COMAL is a structured algorithmic language
|
|
that is easy to read. You can readily translate it into the programming
|
|
language of your choice. Slv is coded as a CLOSED procedure as a
|
|
personal matter of style. An open procedure would execute faster. The
|
|
arrays are passed by REFERENCE and do not allocate additional local
|
|
storage. The main program is just an example for testing. It calls slv
|
|
n times to solve the linear system. The test problem solves a scaled
|
|
Hilbert matrix which is ill conditioned. In the absence of roundoff
|
|
error the solution should be a vector of ones. I usually dimension a()
|
|
to n#*(n#+1)/4. Slv is presented in its full generality, but you may
|
|
want to make some simplifications.
|
|
|
|
Slv can handle multiple right hand side vectors with the two dimensional
|
|
array b(,) in most applications you will only use a single vector,
|
|
nr#=1, and you can make some simplifications by just using a one
|
|
dimensional array.
|
|
|
|
Pivoting also complicates the code. Problems which are positive
|
|
definite, or diagonally dominant, or sometimes just well conditioned
|
|
can be safely solved without pivoting. Stripping out the pivoting code
|
|
is straight forward and will shorten the code and speed execution.
|
|
|
|
Anything following // is a comment and can be deleted from your running
|
|
code.
|
|
|
|
In COMAL 2.0 you can also "protect" the code which will strip out
|
|
comments and other information to make a shorter running version.
|
|
|
|
The remaining discussion will concern COMAL 2.0 and the BLAS.
|
|
|
|
COMAL 2.0
|
|
|
|
COMAL 2.0 is an excellent programming language for the C64/128 and I
|
|
can't describe all of its virtues here. It has one serious limitation.
|
|
It does not use "continuation" lines, so line length is limited. This
|
|
is most restrictive in function and procedure lines where it limits the
|
|
number of parameters that can be passed. Line length is limited to 80
|
|
characters. However, if you use a separate text editor or word
|
|
processor you can enter 120 character lines. Comal will actually
|
|
execute tokenized lines up to 256 characters so the limitation is really
|
|
in the editor rather than COMAL. Procedure and variable names can be
|
|
quite long in Comal, but are kept short because of the line length
|
|
limitation. "Quartersolve" was shortened to "slv" for this reason.
|
|
|
|
a:+t is a shorter faster version a:=a+t, and a:-t is a shorter faster
|
|
version of a:=a-t. This is most usefull when "a" is an array element or
|
|
an integer.
|
|
|
|
Comal 2.0 supports ML packages. A package is a collection of functions
|
|
or procedures that can be called and executed. A packaged can be ROMMED
|
|
and stored in EPROM on the Comal 2.0 cartridge. A package can also be
|
|
loaded from disk and will normally be stored in a RAM location that
|
|
COMAL does not use for normal programs. LINK "filename" will load and
|
|
link the ML package to a Comal program. It will stay attached to the
|
|
program when the program is saved and loaded, unless it is marked as
|
|
ROMMED. The entire slv procedure could be coded in assembly language
|
|
and be placed in a package. The slv procedure uses two packages,
|
|
strings and blas. The command USE packagename makes all of the
|
|
functions and procedures of the package known. Alternatively, you could
|
|
place the USE packagename command in the main program and put IMPORT
|
|
procedurename inside all of the closed procedures that call
|
|
procedurename.
|
|
|
|
Slv calls the swap'real and swap'integer proocedures from the strings
|
|
package. The strings package is a ROMMED package on the Super Chip ROM.
|
|
|
|
It does exactly what it says, e.g. swap'real(a,b) is the same as:
|
|
t:=a; a:=b; b:=t.
|
|
|
|
Slv calls the sdot, isamax#, sswap, sscal, saxpy, and scopy routines
|
|
from the blas package. The blas package is LINKed to the program, but
|
|
it could, and should, be placed on EPROM.
|
|
|
|
Basic Linear Algebra Subroutines, BLAS
|
|
|
|
The BLAS were originally written for the Fortran language to speed
|
|
execution and streamline code used for solving linear algebra and other
|
|
matrix problems. The LINPACK routines, Ref. 3, use the BLAS and are
|
|
perhaps the best known. The idea is that the BLAS routines will be
|
|
highly optimized for a particular computer, coded in ML or a High Order
|
|
Language. Some operating systems even include BLAS like routines.
|
|
Writing fast efficient programs is then a simple matter of selecting the
|
|
best solution algorithm and coding it in a manner that makes best use of
|
|
the blas routines. There are blas routines for single precision, double
|
|
precision, and complex numbers. The level 1 BLAS perform operations on
|
|
rows or columns of an array and typicaly do n scalar operations
|
|
replacing the inner most loop of code. There are also level 2 BLAS that
|
|
perform n*n operations and Level 3 BLAS that perform n*n*n operations.
|
|
Nicholas Higham has coded most of the single precision level 1 blas
|
|
routines and put them in a Comal 2.0 package. The Comal blas package is
|
|
included on the Packages Library Volume 2 disk. I am not aware of ML
|
|
blas routines coded for any other C64/128 languages although this is
|
|
certainly possible and recommended.
|
|
|
|
The Comal blas routines behave exactly the same way that the Fortran
|
|
blas routines do except that Fortran can pass the starting address of an
|
|
array with just "a", while Comal requires "a(1)". The Comal blas will
|
|
allow you pass an array, by reference, of single or multiple dimensions
|
|
and start from any position in the array. If you code the blas routines
|
|
as ordinary Comal routines you have to pass additional parameters and
|
|
have separate routines for single dimensioned arrays and two dimensional
|
|
arrays. Note also that Fortran stores two dimensional arrays by
|
|
columns, and Comal (like many other languages) stores two dimensional
|
|
arrays by rows. If you translate code between Fortran and Comal using
|
|
blas routines you will have to change the increment variables.
|
|
|
|
Fortran Comal
|
|
dimension c(n), a(ilda,isda) DIM c(n#), a(lda#,sda#)
|
|
scopy(n,c,1,a(i,1),ilda) scopy(n#,c(1),1,a(i#,1),1)
|
|
scopy(n,c,1,a(1,j),1) scopy(n#,c(1),1,a(1,j#),sda#)
|
|
|
|
The first scopy copies array c into the ith row of array a. The second
|
|
scopy copies array c into the jth column of array a.
|
|
|
|
This is what scopy does in Fortran:
|
|
|
|
subroutine scopy(n,sx,incx,sy,incy)
|
|
real sx(1),sy(1)
|
|
ix=1
|
|
iy=1
|
|
do 10 i = 1,n
|
|
sy(iy) = sx(ix)
|
|
ix = ix + incx
|
|
iy = iy + incy
|
|
10 continue
|
|
return
|
|
end
|
|
|
|
The Comal BLAS does exactly the same thing. If coded entirely in COMAL
|
|
rather than as a package it would have to be different. The call would
|
|
change.
|
|
|
|
scopy(n#,c(1),1,a(1,j#),sda#) would have to become,
|
|
scopy(n#,c(),1,1,a(,),1,j#,sda#,sda#) and the Comal procedure might be:
|
|
|
|
PROC scopy(n#, REF x(), ix#, incx#, REF y(,), iy#, jy#, sdy#, incy#) CLOSED
|
|
iyinc#:=incy# DIV sdy# //assuming y is dimensioned y(?,sdy#)
|
|
jyinc#:=incy# MOD sdy#
|
|
FOR i#=1 TO n# DO
|
|
y(iy#,jy#):=x(ix#)
|
|
ix#:+incx#; iy#:+iyinc#; jy#:+jyinc#
|
|
ENDFOR
|
|
ENDPROC scopy
|
|
|
|
Note that more information has to be passed to the procedure and used
|
|
that the ML blas picks up automatically. Also we would need separate
|
|
procedures to handle every combination of single and multi dimensional
|
|
arrays. The Comal ML blas are indeed wonderful. For speed
|
|
considerations this should also be left as an open procedure or better
|
|
yet just use in line code.
|
|
|
|
Here is a very simplified description of what each of the routines in
|
|
the Comal BLAS package does.
|
|
|
|
sum:=sasum(n#,x(1),1) Returns sum of absolute values in x().
|
|
sum:=0
|
|
FOR i#:=1 TO n# DO sum:+ABS(x(i#))
|
|
|
|
saxpy(n#,sa,x(1),1,y(1),1) Add a multiple of x() to y().
|
|
FOR i#:=1 TO n# DO y(i#):+sa*x(i#)
|
|
|
|
prod:=sdot(n#,x(1),1,y(1),1) Returns dot product of x() and y().
|
|
prod:=0
|
|
FOR i#:=1 TO n# DO prod:+x(i#)*y(i#)
|
|
|
|
sswap(n#,x(1),1,y(1),1) Swaps x() and y().
|
|
FOR i#:=1 TO n# DO t:=x(i#); x(i#):=y(i#); y(i#):=t
|
|
|
|
scopy(n#,x(1),1,y(1),1) Copy x() to y().
|
|
For i#:=1 TO n# DO y(i#):=x(i#)
|
|
|
|
max#:=isamax#(n,x(1),1) Returns index of the element of x() with the
|
|
largest absolute value.
|
|
t:=0; max#:=1
|
|
FOR i#:=1 TO n#
|
|
IF ABS(x(i#))>t THEN t:=ABS(x(i#)); max#:=i#
|
|
ENDFOR i#
|
|
|
|
sscal(n#,sa,x(1),1) Scale x() by a constant sa.
|
|
FOR i#:=1 TO n# DO x(i#):=sa*x(i#)
|
|
|
|
snrm2(n#,x(1),1) Returns the 2 norm of x().
|
|
norm2:=0
|
|
FOR i#:=1 TO n# DO norm2:+x(i#)*x(i#)
|
|
norm2:=SQR(norm2)
|
|
|
|
srot(n#,x(1),1,y(1),1,c,s) Apply Givens rotation.
|
|
FOR i#:=1 TO n# DO
|
|
t:=c*x(i#) + s*y(i#)
|
|
y(i#):=s*x(i#) + c*y(i#)
|
|
x(i#):=t
|
|
ENDFOR i#
|
|
|
|
|
|
Bear in mind that each of these simple examples can be more complex as
|
|
was given for scopy. You now have enough information to write your own
|
|
BLAS routines in ML or the programming language of your choice, or to
|
|
expand the BLAS routine calls in slv to ordinary in line code.
|
|
|
|
You can also apply the BLAS routines in creative ways besides just
|
|
operating on rows or columns. For example you could create the identity
|
|
matrix with:
|
|
|
|
DIM a(n#,n#)
|
|
a(1,1):=1; a(1,2):=0
|
|
scopy(n#*n#-2,a(1,2),0,a(1,3),1) // zero the rest of the matrix
|
|
scopy(n#-1,a(1,1),0,a(2,2),n#+1) // copy ones to the diagonal.
|
|
|
|
References
|
|
|
|
1. Zambardino, R. A., "Solutions of Systems of Linear Equations with
|
|
Partial Pivoting and Reduced Storage Requirements", The Computer Journal
|
|
Vol. 17, No. 4, 1974, pp. 377-378.
|
|
|
|
2. Orden A., "Matrix Inversion and Related Topics by Direct Methods",
|
|
in Mathematical Methods for Digital Computers, Vol. 1, Edited by A.
|
|
Ralston and H. Wilf, John Wiley and Sons Inc., 1960.
|
|
|
|
3. Dongarra, J. J., Moeler, C. B., Bunch, J. R., Stewart, G. W.,
|
|
Linpack Users' Guide, SIAM Press, Philadelphia, 1979.
|
|
|
|
========================================================================
|
|
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. It has complexities and intricacies that
|
|
can baffle the most erudite and experienced computer scientists in the
|
|
world. But, for the purposes of this article, maybe you can just accept
|
|
that the Internet is a worldwide connection of data lines that let
|
|
computers all over the world talk to each other.. and more importantly,
|
|
that allow the PEOPLE using the computers all over the world to talk to
|
|
other computers.. and to talk to other PEOPLE! Here, then, lies the
|
|
foundation for IRC: it is the mechanism on the Internet that allows
|
|
PEOPLE to talk to other PEOPLE.
|
|
|
|
2) Getting on the Net
|
|
|
|
If you obtained this magazine via the Internet, then you have passed
|
|
Step 1 (finding a site)! If you do not have access to the Internet
|
|
(and have not tried), then you need to look around. Possible sites may
|
|
be a college/university, your employer (use with care), or a commercial
|
|
provider.
|
|
|
|
If you are enrolled in college, then you probably have an account, or
|
|
you may be entitled to one, with no or little cost. The policies on
|
|
student accounts vary a lot from institution to institution, and from
|
|
country to country. But check into it.. it is one of the most common
|
|
methods of Internet access.
|
|
|
|
If you are employed, and your company has access to the Internet, it
|
|
may be possible for you to use their facilities. Just a word of
|
|
caution - make sure that it is ok with your employer to use his
|
|
facilities... and not on "company time".
|
|
|
|
Another way that is becoming increasingly more common is to use
|
|
commercial "Internet providers". These are companies whose sole
|
|
purpose is to offer you an "account" and give you access to the
|
|
Internet. The cost, time on line, storage, access, etc., can vary
|
|
greatly.. you must shop around a bit.. if you have this choice at
|
|
all.. for the best deal.
|
|
|
|
These commercial sites are not always easy to find. There may be
|
|
several commercial providers in an area, but, strangely, they tend not
|
|
to advertise. Word-of-mouth through friends, BBSs, or User Groups seem
|
|
to be the best way to locate the site possibilities. But they CAN
|
|
provide a very good solution.
|
|
|
|
Another variation on commercial sites are national companies such as
|
|
Compuserve, Genie, America Online and Delphi. They provide varying degrees of
|
|
access.. and possibly at somewhat higher costs than local providers.
|
|
But, again, it is another option.
|
|
|
|
There is MUCH to do on the Internet, once you have access to it: telnet,
|
|
ftp, usenet, archie, gopher, www... These may be just names to you
|
|
now.. but the are all fascinating parts of the Internet. But this
|
|
article is intended as an introduction to IRC - a fabulous Internet
|
|
resource which allows users who have access to a client program called
|
|
IRCII (most often invoked as "irc") to talk to each other (and often to
|
|
exchange files) in world-wide conversational "channels" (like "party
|
|
lines", often called "rooms" on some BBSs). Why is this important to
|
|
readers of this magazine? Well, there is a channel for c64/128 users on
|
|
IRC called #c-64, a place where c64/128 users are able to meet and
|
|
exchange all sorts of information, opinions, and files. More on this
|
|
later.
|
|
|
|
3) The IRC Client
|
|
|
|
First, to use IRC it is necessary to have access to an IRC client. A
|
|
client is a program, usually available on your local site, which
|
|
actually interprets and responds to your commands, accepts your typing,
|
|
and shows you the conversation on the channel(s) you have joined.
|
|
|
|
The most common way to access IRC from a site is to use the IRCII
|
|
client that your site makes available. This is most often done by
|
|
simply typing "irc" at your prompt or invoking the irc option from your
|
|
menu if you don't have a shell account. The first thing you will
|
|
notice is that your client is attempting to connect to a "server". A
|
|
server is a special program, run only on certain sites, that actually
|
|
provides the backbone of the IRC network.
|
|
|
|
Most sites have several servers pre-defined. You should see the client
|
|
trying one or more servers until it connects with one.
|
|
|
|
With Unix irc clients you can define your own unique set of servers by
|
|
starting IRC with:
|
|
|
|
irc nick server1 server2 ... serverN
|
|
|
|
where "serverX" is the alpha or numeric IP address of the server. This
|
|
will automatically set your irc nick (handle) and will establish a
|
|
series of servers that your client will switch to if your connection to
|
|
IRC gets broken (or if a server is not available when you invoke
|
|
"irc").
|
|
|
|
What is an IP address, you ask? Well, a basic premise of the Internet
|
|
is that each computer on the net (at all sites) has a UNIQUE address -
|
|
a computer code - that allows other computers to send specific data
|
|
just to that computer. In that way, computers can make sure that the
|
|
messages and data files that they want (and YOU want) to send to
|
|
certain places get to their proper destinations.
|
|
|
|
IP addresses may be used in an alphabetic or numeric form. In most
|
|
cases they can be used interchangeably. So, all irc servers have a
|
|
unique alphabetic (and an equivalent numeric) IP address.
|
|
|
|
Once an IRC session is in progress, Unix users can change servers by
|
|
typing:/server newserver where "newserver" is as above, the alpha or
|
|
numeric IP address of the server you want to switch to. More on servers
|
|
later; but just to mention few now: irc.indiana.edu (midwest);
|
|
irc.virginia.edu (east); irc.ctr.columbia.edu (east); irc.math.byu.edu
|
|
(west); irc.colorado.edu (midwest); irc.texas.net (southwest). There
|
|
are dozens more. Just ask someone on IRC...or do a few /whois nick
|
|
commands. You will spot many more.
|
|
|
|
If your site does not have an irc client, it should be possible to
|
|
install one yourself. This means that you need to ftp the source code
|
|
for an irc client to your account on your site, make some usually minor
|
|
edits, then compile the code in your home directory or a subdirectory
|
|
below it.
|
|
|
|
One good site for obtaining the necessary irc client code is
|
|
cs.bu.edu. cd to /irc/clients. Unix users will find the IRCII client
|
|
source code in two forms: IRC2.2.9.tar.Z (Unix tar and compress at
|
|
471k) or IRC2.2.9.tar.gz (Unix tar and GNU compress at 306k). Both
|
|
files are the same (except for the compression). Be sure to use
|
|
"BINARY" mode for the ftp transfer.
|
|
|
|
Move the file to its own subdirectory if you have not ftp'd it to one
|
|
already. Then uncompress and untar the file. You should now find a
|
|
small subdirectory tree of files. Be sure and read the INSTALL file in
|
|
the top subdirectory.
|
|
|
|
Also in the main subdirectory, there should be two files that need
|
|
editing to make the client work with you site. One is "Makefile". In
|
|
it there are at least two edits. Make INSTALL_EXECUTABLE the path name
|
|
that u want the executable to reside in. This is most often your home
|
|
directory or the "bin" subdirectory under your home directory. The
|
|
other is IRCII_LIBRARY. Set this to the top subdirectory where the
|
|
IRCII code resides. You also must read through the computer system
|
|
options and set them for the type of computer and Operating System that
|
|
your site uses.
|
|
|
|
The other file is "config.h". Change the #define DEFAULT_SERVER line
|
|
to the alpha or numeric addy of your primary default server. Be sure
|
|
to enclose the server in quotes ("server").
|
|
|
|
For VMS users, there is a subdirectory in "clients" named vms. cd to
|
|
it. There are two versions - irc176 and ircII-for-vms. The first is
|
|
a more native VMS version, the second is a Unix-like version. They are
|
|
both executables, and should run on VMS systems. Try both.. see which
|
|
you like best.
|
|
|
|
Another fairly new area of IRC clients is the personal client, running
|
|
on your own computer which would be connected to the Internet through a
|
|
version of SLIP or PPP, protocols that move much of the overhead of a
|
|
normal Internet provider down to your own machine. There are IRC clients
|
|
available for the PC, Amiga, MAC... and even the rumor of one to be
|
|
produced for the c64/128. This type of client is expanding very
|
|
rapidly and will be a significant option for an ever increasing number
|
|
of Internet users.
|
|
|
|
If you have Telnet only access from your site, there are some sites
|
|
which offer a "public" irc client, ones which you can use without
|
|
having an account at that site.. sorta like anonymous ftp for those of
|
|
you who know what that is. There are drawbacks, though. There are not
|
|
many of these public clients, they are often slow in response time, you
|
|
cannot exchange files with other users (DCC), and many of the sites are
|
|
not always up. Still, it is one possibility that might work for
|
|
certain situations. Actually, it is the way that I started on IRC and
|
|
used it for several months (my site did not have a local client, and I
|
|
did not know how to install one myself).
|
|
|
|
The public IRC sites I know about now are tiger.itc.univie.ac.at 6668,
|
|
sci.dixie.edu 6667, irc.nsysu.edu.tw, and irc.demon.co.uk. They are
|
|
not available from all sites, and usage is limited. But try them if
|
|
you need to.
|
|
|
|
Another variation of the "public" options is to apply for a free Unix
|
|
account at nyx.cs.du.edu. You will have to be validated, which involves
|
|
a little paperwork. But once completed, you will have a FREE Unix
|
|
account with full IRC privileges, including DCC file exchange. Of
|
|
course, you need a "local" account somewhere with telnet and ftp
|
|
privileges, but this is often easier to obtain than an account with all
|
|
options locally.
|
|
|
|
4) Basics of IRC
|
|
|
|
Well, hopefully, you will now have an Internet site with a method of
|
|
accessing IRC. Next, we want to give some tips on using and enjoying
|
|
IRC and introduce DCC, the command for transferring files between people
|
|
on IRC... and between "bots" and people.
|
|
|
|
A "bot", you say? Some of you may laugh; sure of course, a bot. What
|
|
else is new? But... I remember that it was ages before I finally
|
|
figured out.. or someone gave me a clue.. as to what a "bot" really was.
|
|
Before we go on, let me give you a VERY brief description of a bot. We
|
|
can say that a bot may be a "script", a series of IRC language
|
|
statements understood by your IRC client; or it may be a separate
|
|
program (typically written in "C"); which, in either case, runs without
|
|
any help from its "owner" - YOU.
|
|
|
|
Instead, a bot is intended to respond to others on IRC who "talk" to it
|
|
by "/msg", "/dcc chat", or even "on-channel" commands like "!list" or
|
|
"&help". One bot even lists the c64 files it has on-line in response to
|
|
someone typing "load "$",8".
|
|
|
|
What a bot does and how you command it varies a LOT. There really is no
|
|
standard way to talk to a bot. Try "/msg <botname>" help as a starting
|
|
point and see what happens. Most often there will be instructions that
|
|
tell you what to do next. Experiment a little - you will get the hang
|
|
of it.
|
|
|
|
Back to the main plot. The first thing to do after you get connected to
|
|
IRC is to choose a "nick". This is the handle that you will be known by
|
|
and talked to on IRC. Do this by typing:
|
|
|
|
/nick <nick>
|
|
|
|
It's your choice.. unless someone else is already using it. IRC does
|
|
not let two people use the same nick at the same time. It will tell you
|
|
about this if you try - sometimes in a rather active way - like "kick"
|
|
you off. Don't worry - just reconnect.. but try a different nick. Try
|
|
just changing the nick a little - like even putting a "1" or "2" behind
|
|
it.
|
|
|
|
Any number of people, however, can use the same nick at different times.
|
|
This CAN cause a little confusion.. make sure you know you are talking
|
|
to who you think you are.. check a nick's whole address with:
|
|
|
|
/whois <nick>
|
|
|
|
Next, you will want to join a channel. Do this by typing:
|
|
|
|
/join #<channel>
|
|
|
|
A channel is a logical connection of all IRC users anywhere in the world
|
|
that have typed the same /join command. All lines typed to the channel
|
|
by anyone on the channel are spread by IRC to all other people who are
|
|
on the channel. This is the real power of IRC... a world-wide
|
|
"conference" or "party line", where people with the same interests can
|
|
communicate with each other.
|
|
|
|
Because of different delays in different parts of the Internet, all the
|
|
lines typed by everyone will not always appear at the same time or even
|
|
in the same order at everyone's terminal. This usually does not cause
|
|
much of a problem - just be aware that it happens.
|
|
|
|
If the channel name does not exist at the time you type /join, it will
|
|
be created for you! Yes, anyone can "create" a channel. But #c-64 is
|
|
almost always there. Give it a try!
|
|
|
|
After you get on a channel, you can type:
|
|
|
|
/who *
|
|
|
|
This will give you a list of who (which nicks) is on the channel and
|
|
what their home sites are. This address may or may not be the correct
|
|
email address for the nick - so check with the person first (perhaps a
|
|
"/msg <nick>" - see below) if you want to email him.
|
|
|
|
As mentioned before, normal channel conversation is seen by everyone who
|
|
has joined the channel. This is great most of the time. Occasionally,
|
|
though, you may want to tell just one person (or bot) something that the
|
|
entire channel would not want to hear. In this case, use the command:
|
|
|
|
/msg <nick> <message>
|
|
|
|
Type it on a line of its own, and just <nick> will see your <message>.
|
|
Quite handy for the more "personal" or "specialized" conversations.
|
|
Careful, though... use the wrong <nick> or leave out the "/" and people
|
|
other than you intended will see your <message>.
|
|
|
|
If you find you are doing a lot of /msg's to the same nick, try:
|
|
|
|
/query <nick>
|
|
|
|
This will put you in a sort of 'permanent' /msg <nick> mode, so that
|
|
everything you type that would normally go to the channel will not act
|
|
like a "/msg <nick>" preceded it, and it will go just to <nick>. Type
|
|
just "/query" to cancel this mode.
|
|
|
|
Let's jump, now, to /dcc, the command that allows most IRC users to
|
|
transfer files. DCC stands for "Direct Client to Client". What it does
|
|
is allow two nicks to transfer files *directly* between their sites, not
|
|
going through either of their servers. One of the nicks can even be a
|
|
bot; IRC does not make a distinction.
|
|
|
|
When two nicks exchange files, the sender must always start by typing:
|
|
|
|
/dcc send <nick> <filename>
|
|
|
|
The recipient will get a message telling what file is being offered and must
|
|
type:
|
|
|
|
/dcc get <nick> [filename]
|
|
|
|
The [filename] is optional, but must be used if more than one file is to
|
|
be transferred simultaneously. Yes, simultaneous transfer of multiple
|
|
files CAN be done. Many people do not realize this. Just use the
|
|
[filename] option with the "/dcc get" command.
|
|
|
|
The files that you send and the files that you receive with DCC are
|
|
always in the directory you are in when you start IRC. You can type "/cd"
|
|
to see what that directory is and you can type:
|
|
|
|
/cd <pathname>
|
|
|
|
to change that directory. Or, you can give the absolute or relative
|
|
pathname of the file you want to send if it is not in your "local"
|
|
directory.
|
|
|
|
There are often a couple of bots on #c-64 that can give you c-64 files.
|
|
"coolhand" is partly a script bot that currently has a lot of c-64 files
|
|
available for DCC. If coolhand is on IRC, type:
|
|
|
|
/msg coolhand xdcc list
|
|
|
|
to see a list of lists (of files). To see the individual files on list n,
|
|
type:
|
|
|
|
/msg coolhand xdcc list #n
|
|
|
|
To have coolhand's script dcc you file #n, type:
|
|
|
|
/msg coolhand xdcc send #n
|
|
|
|
followed by:
|
|
|
|
/dcc get coolhand
|
|
|
|
when you get the dcc offer message.
|
|
|
|
There are many scripts that you can use that will autoget a file that is
|
|
DCC'd to you. The xdcc script that coolhand uses is one such script.
|
|
(Yes, coolhand will also autoget a file that you send to it.)
|
|
|
|
5) What/Who is on IRC?
|
|
|
|
Ok, now you are on IRC. So what will you find? Who is on the #c-64?
|
|
The answers are quite varied.. and constantly changing. I personally
|
|
have been on IRC for over 2 years.. (or is it 3?) And I have yet to
|
|
ascertain an absolute pattern of people or topics. Frustrating? Well,
|
|
maybe to some. But interesting? Yes, most certainly. IRC, and the
|
|
#c-64 channel, is a microcosm of the world, with all its variety of
|
|
people, personalities, projects, propaganda, and priorities. It is a
|
|
capability, a tool for communications, that is unexcelled in its scope
|
|
and possibilities.
|
|
|
|
IRC is totally international, and so is the #c-64 channel. Besides the
|
|
U.S. and Canada, Europe is very well represented. There is also a
|
|
smaller but increasingly active contingent in Australia, as the net
|
|
becomes more accessible there. You will also find a few c-64 users in
|
|
S. America, Africa, and Asia. Russia and other former Soviet Union
|
|
countries also have a presence. English is the accepted language for
|
|
use on #c-64, although you will occasionally see a few other language
|
|
used for brief times.
|
|
|
|
What is the channel used for? Just about anything you can imagine that
|
|
normal conversation would be used for. With a special emphasis for the
|
|
special interest of most channel participants - the c64/128. For the
|
|
most part, almost everyone on the channel has had or still has a c64 or
|
|
c128. Some are active users on a real c64/128, while others use one of
|
|
the several emulators that exist for various platforms. Many former and
|
|
current 64 "scene" members are finding their way to the channel, but all
|
|
members of the c64 community are always welcome, and all are treated
|
|
equally.
|
|
|
|
Many people find IRC and #c-64 a very useful way to exchange information
|
|
quickly without having to wait for email to pass back and forth. As was
|
|
mentioned before, the DCC capability allows for immediate transfer of
|
|
files, another quick and effective way to pass information and things
|
|
like utilities and coding examples. Such capabilities have encouraged
|
|
many people to either return to the c64 or take up using and programming
|
|
it for the first time. Yes, the c64 community is actually growing
|
|
again, thanks in part to the growing presence of the Internet, IRC, and
|
|
#c-64!
|
|
|
|
So, when you first get on the channel for the first time, don't be
|
|
afraid to ask for help. You will probably find that the people on there
|
|
are either new themselves, or were once new at one time and had the same
|
|
uncertainties and questions that you do. Most everyone is very willing
|
|
to help new people. So ask. Also, if you have knowledge or a talent to
|
|
offer or a willingness to help somehow, just make that known. The
|
|
channel is full of people, some of whom probably need exactly what you
|
|
have to give.
|
|
|
|
A key thing: be patient! When you are new on the channel, you may not
|
|
be noticed right away, especially if there are several conversations
|
|
already going on. In other cases, you may find that there is really no
|
|
one on the channel, except maybe a few bots. So hang in there or come
|
|
back a bit later. Believe me, the IS a lot of action on #c-64 most of
|
|
the time.
|
|
|
|
Besides being patient yourself, be patient with other people on the
|
|
channel. Like in the non-cyber world, misunderstandings CAN occur, since
|
|
your total communication with other people is via the typed word. But
|
|
the same rules of courteousness that common society utilizes also apply
|
|
on IRC. Treat people with respect and kindness, and they will most
|
|
likely respond in a like manner. Sounds like the golden rule? I think
|
|
so, and I think you will find that its works pretty well on IRC as it
|
|
does in other life.
|
|
|
|
Hopefully, this article will help you get started enjoying IRC and
|
|
particularly #c-64. There's a lot to be gained there... information,
|
|
files, and even new friends. It's a way to give our c64 community new
|
|
life and spirit. Give it a try! See you there.
|
|
|
|
*****************
|
|
|
|
Some of the material in this article was previously published in "Driven"
|
|
and is used here by permission.
|
|
|
|
========================================================================
|
|
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. [This version includes a couple of
|
|
grammatical corrections and minor changes, plus, the source code has been
|
|
debugged and extended by Craig Bruce <csbruce@ccnga.uwaterloo.ca>.]
|
|
|
|
1. INTRODUCTION
|
|
|
|
The SwiftLink-232 ACIA cartridge replaces the Commodore Kernal RS-232 routines
|
|
with a hardware chip. The chip handles all the bit-level processing now done
|
|
in software by the Commodore Kernal. The ACIA may be accessed by polling
|
|
certain memory locations in the I/O block ($D000 - $DFFF) or through
|
|
interrupts. The ACIA may be programmed to generate interrupts in the
|
|
following situations:
|
|
|
|
1) when a byte of data is received
|
|
2) when a byte of data may be transmitted (i.e., the data register is empty)
|
|
3) both (1) and (2)
|
|
4) never
|
|
|
|
The sample code below sets up the ACIA to generate an interrupt each time a
|
|
byte of data is received. For transmitting, two techniques are shown. The
|
|
first technique consists of an interrupt handler which enables transmit
|
|
interrupts when there are bytes ready to be sent from a transmit buffer.
|
|
There is a separate routine given that manages the transmit buffer. In the
|
|
second technique, which can be found at the very end of the sample code,
|
|
neither a transmit buffer or transmit interrupts are used. Instead, bytes of
|
|
data are sent to the ACIA directly as they are generated by the terminal
|
|
program.
|
|
|
|
NOTE: The ACIA will _always_ generate an interrupt when a change of state
|
|
occurs on either the DCD or DSR line (unless the lines are not connected in
|
|
the device's cable).
|
|
|
|
The 6551 ACIA was chosen for several reasons, including the low cost and
|
|
compatibility with other Commodore (MOS) integrated circuits. Commodore used
|
|
the 6551 as a model for the Kernal software. Control, Command, and Status
|
|
registers in the Kernal routines partially mimic their hardware counterparts
|
|
in the ACIA.
|
|
|
|
NOTE: If you're using the Kernal software registers in your program, be sure
|
|
to review the enclosed 6551 data sheet carefully. Several of the hardware-
|
|
register locations do _not_ perform the same function as their software
|
|
counterparts. You may need to make a few changes in your program to
|
|
accommodate the differences.
|
|
|
|
2. BUFFERS
|
|
|
|
Bytes received are placed in "circular" or "ring" buffers by the sample
|
|
routine below, and also by the first sample transmit routine. To keep things
|
|
similar to the Kernal RS-232 implementation, we've shown 256-byte buffers.
|
|
You may want to use larger buffers for file transfers or to allow more
|
|
screen-processing time. Bypassing the Kernal routines free many zero-page
|
|
locations, which could improve performance of pointers to large buffers.
|
|
|
|
If your program already directly manipulates the Kernal RS-232 buffers, you'll
|
|
find it very easy to adapt to the ACIA. If you use calls to the Kernal RS-232
|
|
file routines instead, you'll need to implement lower-level code to get and
|
|
store buffer data.
|
|
|
|
Briefly, each buffer has a "head" and "tail" pointer. The head points to the
|
|
next byte to be removed from the buffer. The tail points to the next free
|
|
location in which to store a byte. If the head and tail both point to the
|
|
same location, the buffer is empty. If (tail+1)==head, the buffer is full.
|
|
|
|
The interrupt handler described below will place received bytes at the tail of
|
|
the receive buffer. Your program should monitor the buffer, either by
|
|
comparing the head and tail pointers (as the Commodore Kernal routines do), or
|
|
by maintaining a byte count through the interrupt handler (as the attached
|
|
sample does). When bytes are available, your program can process them, move
|
|
the head pointer to the next character, and decrement the counter if you use
|
|
one.
|
|
|
|
You should send a "Ctrl-S" (ASCII 19) to the host when the buffer is nearing
|
|
capacity. At higher baud rates, this "maximum size" point may need to be
|
|
lowered. We found 50 to 100 bytes worked fairly well at 9600 baud. You can
|
|
probably do things more efficiently (we were using a _very_ rough
|
|
implementation) and set a higher maximum size. At some "maximum size", a
|
|
"Ctrl-Q" (ASCII 17) can be sent to the host to resume transmission.
|
|
|
|
To transmit a byte using the logic of the first transmit routine below, first
|
|
make sure that the transmit buffer isn't full. Then store the byte at the
|
|
tail of the transmit buffer, point the tail to the next available location,
|
|
and increment the transmit buffer counter (if used).
|
|
|
|
The 6551 transmit interrupt occurs when the transmit register is empty and
|
|
available for transmitting another byte. Unless there are bytes to transmit,
|
|
this creates unnecessary interrupts and wastes a lot of time. So, when the
|
|
last byte is removed from the buffer, the interrupt handler in the first
|
|
transmit routine below disables transmit interrupts.
|
|
|
|
Your program's code that stuffs new bytes into the transmit buffer must
|
|
re-enable transmit interrupts, or your bytes may never be sent. A model for a
|
|
main code routine for placing bytes into the transmit buffer follows the
|
|
sample interrupt handler.
|
|
|
|
Using a transmit buffer allows your main program to perform other takes while
|
|
the NMI interrupt routine takes care of sending bytes to the ACIA. If the
|
|
buffer has more than a few characters, however, you may find that most of the
|
|
processor time is spent servicing the interrupt. Since the ACIA generates NMI
|
|
interrupts, you can't "mask" them from the processor, and you may have timing
|
|
difficulties in your program.
|
|
|
|
One solution is to eliminate the transmit buffer completely. Your program can
|
|
decide when to send each byte and perform any other necessary tasks in between
|
|
bytes as needed. A model for the main-code routine for transmitting bytes
|
|
without a transmit buffer is also shown following the sample interrupt-handler
|
|
code. Feedback from developers to date is that many of them have better luck
|
|
_not_ using transmit interrupts or a transmit buffer.
|
|
|
|
Although it's possible to eliminate the receive buffer also, we strongly
|
|
advise that you don't. The host computer, not your program, decides when
|
|
a new byte will arrive. Polling the ACIA for received bytes instead of
|
|
using an interrupt-driven buffer just waste's your program's time and
|
|
risks missing data.
|
|
|
|
For a thorough discussion of the use of buffers, the Kernal RS-232 routines,
|
|
and the Commodore NMI handler, see "COMPUTE!'s VIC-20 and Commodore 64 Tool
|
|
Kit: Kernal", by Dan Heeb (COMPUTE! Books) and "What's Really Inside the
|
|
Commodore 64", by Milton Bathurst (distributed in the US by Schnedler
|
|
Systems).
|
|
|
|
3. ACIA REGISTERS
|
|
|
|
The four ACIA registers are explained in detail in the enclosed data sheets.
|
|
The default location for them in our cartridge is address $DE00--$DE03
|
|
(56832--56836).
|
|
|
|
3.1. DATA REGISTER ($DE00)
|
|
|
|
This register has dual functionality: it is used to receive and transmit all
|
|
data bytes (i.e., it is a read/write register).
|
|
|
|
Data received by the ACIA is placed in this register. If receive interrupts
|
|
are enabled, an interrupt will be generated when all bits for a received
|
|
byte have been assembled and the byte is ready to read.
|
|
|
|
Transmit interrupts, if enabled, are generated when this register is empty
|
|
(available for transmitting). A byte to be transmitted can be placed in this
|
|
register.
|
|
|
|
3.2. STATUS REGISTER ($DE01)
|
|
|
|
This register has dual functionality: it shows the various ACIA settings when
|
|
read, but when written to (data = anything [i.e., don't care]), this register
|
|
triggers a reset of the chip.
|
|
|
|
As the enclosed data sheet shows, the ACIA uses bits in this register to
|
|
indicate data flow and errors.
|
|
|
|
If the ACIA generates an interrupt, bit #7 is set. There are four possible
|
|
sources of interrupts:
|
|
|
|
1) receive (if programmed)
|
|
2) transmit (if programmed)
|
|
3) if a connected device changes the state of the DCD line
|
|
4) if a connected device changes the state of the DSR line
|
|
|
|
Some programmers have reported problems with using bit #7 to verify ACIA
|
|
interrupts. At 9600 bps and higher, the ACIA generates interrupts properly,
|
|
and bits #3--#6 (described below) are set to reflect the cause of the
|
|
interrupt, as they should. But, bit #7 is not consistently set. At speeds
|
|
under 9600 bps, bit #7 seems to work as designed. To avoid any difficulties,
|
|
the sample code below ignores bit #7 and tests the four interrupt source bits
|
|
directly.
|
|
|
|
Bit #5 indicates the status of the DSR line connected to the RS-232 device
|
|
(modem, printer, etc.), while bit #6 indicates the status of the DCD line.
|
|
NOTE: The function of these two bits is _reversed_ from the standard
|
|
implementation. Unlike many ACIAs, the 6551 was designed to use the DCD
|
|
(Data Carrier Detect) signal from the modem to activate the receiver section
|
|
of the chip. If DCD is inactive (no carrier detected), the modem messages
|
|
and echos of commands would not appear on the user's screen. We wanted the
|
|
receiver active at all times. We also wanted to give the you access to the
|
|
DCD signal from the modem. So, we exchanged the DCD and DSR signals at the
|
|
ACIA. Both lines are pulled active internally by the cartridge if left
|
|
unconnected by the user (i.e., in an null-modem cable possibility).
|
|
|
|
Bit #4 is set if the transmit register is empty. Your program must monitor
|
|
this bit and not write to the data register until the bit i sset (see the
|
|
sample XMIT code below).
|
|
|
|
Bit #3 is set if the receive register is full.
|
|
|
|
Bits #2, #1, and #0, when set, indicate overrun, framing, and parity errors in
|
|
received bytes. The next data byte received erases the error information for
|
|
the preceding byte. If you wish to use these bits, store them for processing
|
|
by your program. The sample code below does not implement any error checking,
|
|
but the Kernal software routines do, so adding features to your code might be
|
|
a good idea.
|
|
|
|
3.3. COMMAND REGISTER ($DE02)
|
|
|
|
The Command Register control parity checking, echo mode, and transmit/receive
|
|
interrupts. It is a read/write register, but reading the register simply
|
|
tells you what the settings of the various parameters are.
|
|
|
|
You use bits #7, #6, and #5 to choose the parity checking desired.
|
|
|
|
Bit #4 should normally be cleared (i.e., no echo)
|
|
|
|
Bits #3 and #2 should reflect whether or not you are using transmit
|
|
interrupts, and if so, what kind. In the first sample transmit routine below,
|
|
bit #3 is set and bit #2 is cleared to disable transmit interrupts (with RTS
|
|
low [active]) on startup. However, when a byte is placed in the transmit
|
|
buffer, bit #3 is cleared and bit #2 is set to enable transmit interrupts
|
|
(with RTS low). When all bytes in the buffer have been transmitted, the
|
|
interrupt handler disables transmit interrupts. NOTE: If you are connected to
|
|
a RS-232 device that uses CTS/RTS handshaking, you can tell the device to stop
|
|
temporarily by bringing RTS high (inactive): clear both bits #2 and #3.
|
|
|
|
Bit #1 should reflect whether or not you are using receive interrupts. In
|
|
the sample code below, it is set to enable receive interrupts.
|
|
|
|
Bit #0 acts as a "master control switch" for all interrupts on the chip
|
|
itself. It _must_ be set to enable any interrupts -- if it is cleared, all
|
|
interrupts are turned off and the receiver section of the chip is disabled.
|
|
This bit also pulls the DTR line low to enable communication with the
|
|
connected RS-232 device. Clearing this bit causes most Hayes-compatible
|
|
modems to hang up (by bringing DTR high). This bit should be cleared when a
|
|
session is over and the user exits the terminal program to insure that no
|
|
spurious interrupts are generated. One fairly elegant way to do this is to
|
|
perform a software reset of the chip (writing any value to the Status
|
|
register).
|
|
|
|
NOTE: In the figures on the 6551 data sheet, there are small charts at the
|
|
bottom of each of the labelled "Hardware Reset/Program Reset". These charts
|
|
indicate what values the bits of these registers contain after a hardware
|
|
reset (like toggling the computer's power) and a program reset (a write to the
|
|
Status register).
|
|
|
|
3.4. CONTROL REGISTER ($DE03)
|
|
|
|
You use this register to control the number of stop bits, the word length,
|
|
switch on the internal baud-rate generator, and set the baud rate. It is a
|
|
read/write register, but reading the register simply tells you what the
|
|
various parameters are. See the figure in the data sheet for a complete list
|
|
of parameters.
|
|
|
|
Be sure that bit #4, the "clock source" bit, is always set to use the on-chip
|
|
crystal-controlled baud-rate generator.
|
|
|
|
You use the other bits to choose the baud rate, word length, and number of
|
|
stop bits. Note that our cartridge uses a double-speed crystal, so values
|
|
given on the data sheet are doubled [this is how they are shown below] (the
|
|
minimum speed is 100 bps and the maximum speed is 38,400 bps).
|
|
|
|
4. ACIA HARDWARE INTERFACING
|
|
|
|
The ACIA is mounted on a circuit board designed to plug into the expansion
|
|
(cartridge) port. The board is housed in a cartridge shell with a male DB-9
|
|
connector at the rear. The "IBM(R) PC/AT(TM) standard" DB-9 RS-232 pinout is
|
|
implemented. Commercial DB-9 to DB-25 patch cords are readily available, and
|
|
are sold by us as well.
|
|
|
|
Eight of the nine lines from the AT serial port are implemented: TxD, RxD,
|
|
DTR, DSR, RTS, CTS, DCD, & GND. RI (Ring Indicator) is not implemented
|
|
because the 6551 does not have a pin to handle it. CTS and RTS are not
|
|
normally used by 2400 bps or slower Hayes-compatible modems, but these lines
|
|
are being used by several newer, faster modems (MNP modems in particular).
|
|
Note that although CTS is connected to the 6551, there is no way to monitor
|
|
what state it is -- the value does not appear in any register. The 6551
|
|
handles CTS automatically: if it is pulled high (inactive) by the connected
|
|
RS-232 device, the 6551 stops transmitting (clears the "transmit data register
|
|
empty" bit [#4] in the status register).
|
|
|
|
The output signals are standard RS-232 level compatible. We've tested units
|
|
with several commercial modems and with various computers using null-modem
|
|
cables up to 38,400 bps without difficulties. In addition, there are pull-up
|
|
resistors on three of the four input lines (DCD, DSR, CTS) so that if these
|
|
pins are not connected in a cable, those three lines will pull to the active
|
|
state. For example, if you happen to use a cable that is missing the DCD
|
|
line, the pull-up resistor will pull the line active, so that bit #6 in the
|
|
status register would be cleared (DCD is active low).
|
|
|
|
An on-board crystal provides the baud rate clock signal, with a maximum of
|
|
38.4 Kbaud, because we are using a double-speed crystal. If possible, test
|
|
your program at 38.4 Kbaud as well as lower baud rates. Users may find this
|
|
helpful for local file transfers using the C-64/C-128 as a dumb terminal on
|
|
larger systems. And, after all, low-cost 28.8 Kb modems for the masses are
|
|
just around the corner.
|
|
|
|
Default decoding for the ACIA addresses is done by the I/O #1 line (pin 7) on
|
|
the cartridge port. This line is infrequently used on either the C-64 or
|
|
C-128 and should allow compatibility with most other cartridge products,
|
|
including the REU. The circuit board also has pads for users with special
|
|
needs to change the decoding to I/O #2 (pin 10). This change moves the ACIA
|
|
base address to $DF00, making it incompatible with the REU.
|
|
|
|
C-128 users may also elect to decode the ACIA at $D700 (this is a SID-chip
|
|
mirror on the C-64). Since a $D700 decoding line is not available at the
|
|
expansion port, the user would need to run a clip lead into the computer and
|
|
connect to pin 12 of U2 (a 74LS138). We have tried this and it works. $D700
|
|
is an especially attractive location for C-128 BBS authors, because putting
|
|
the SwiftLink there will free up the other two memory slots for devices that
|
|
many BBS sysops use: IEEE and hard-drive interfaces.
|
|
|
|
Although we anticipate relatively few people changing ACIA decoding, you
|
|
should allow your software to work with a SwiftLink at any of the three
|
|
locations. You could either (1) test for the ACIA automatically by writing a
|
|
value to the control register and then attempting to read it back or (2)
|
|
provide a user-configurable switch/poke/menu option.
|
|
|
|
The Z80 CPU used for CP/M mode in the C-128 is not connected to the NMI line,
|
|
which poses a problem since the cleanest software interface for C-64/C-128-
|
|
mode programming is with this interrupt. We have added a switch to allow the
|
|
ACIA interrupt to be connected to either NMI or IRQ, which the Z80 does use.
|
|
The user can move this switch without opening the cartridge.
|
|
|
|
5. SAMPLE CODE
|
|
|
|
This section has been translated into ACE-assembler format. Cut on the dotted
|
|
lines to extract the code, and assemble it using the ACE assembler (ACE is a
|
|
public-domain program). This program will work on both the C64 and C128.
|
|
To use from BASIC:
|
|
|
|
LOAD"SAMPLE",8,1
|
|
SYS8192
|
|
|
|
It is a very simple terminal program. Press the STOP key to exit from it.
|
|
|
|
%%%---8<---cut-here---8<---%%%
|
|
;Sample NMI interrupt handler for 6551 ACIA on Commodore 64/128
|
|
|
|
;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
|
|
;Geoduck Development Systems, and Dr. Evil Labs.
|
|
|
|
; ---=== EQUATES ===---
|
|
|
|
base = $DE00 ;base ACIA address
|
|
data = base
|
|
status = base+1
|
|
command = base+2
|
|
control = base+3
|
|
|
|
;Using the ACIA frees many addresses in zero page normally used by
|
|
;Kernel RS-232 routines. The addresses for the buffer pointers were
|
|
;chosen arbitrarily. The buffer vector addresses are those used by
|
|
;the Kernal routines.
|
|
|
|
rhead = $A7 ;pointer to next byte to be removed from
|
|
;receive buffer
|
|
rtail = $A8 ;pointer to location to store next byte received
|
|
rbuff = $F7 ;receive-buffer vector
|
|
|
|
thead = $A9 ;pointer to next byte to be removed from
|
|
;transmit buffer
|
|
ttail = $AA ;pointer to location to store next byte
|
|
;in transmit buffer
|
|
tbuff = $F9 ;transmit buffer
|
|
|
|
xmitcount = $AB ;count of bytes remaining in transmit (xmit) buffer
|
|
recvcount = $B4 ;count of bytes remaining in receive buffer
|
|
|
|
errors = $B5 ;DSR, DCD, and received data errors information
|
|
|
|
xmiton = $B6 ;storage location for model of command register
|
|
;which turn both receive and transmit interrupts on
|
|
xmitoff = $BD ;storage location for model of command register
|
|
;which turns the receive interrupt on and the
|
|
;transmit interrupts off
|
|
|
|
NMINV = $0318 ;Commodore Non-Maskable Interrupt vector
|
|
OLDVEC = $03fe ;innocuous location to store old NMI vector (two bytes)
|
|
|
|
; ---=== INITIALIZATION ===---
|
|
|
|
;Call the following code as part of system initialization.
|
|
|
|
;clear all buffer pointers, buffer counters, and errors location
|
|
|
|
org $2000 ;change to suit your needs
|
|
lda #$00
|
|
sta rhead
|
|
sta rtail
|
|
sta thead
|
|
sta ttail
|
|
|
|
sta xmitcount
|
|
sta recvcount
|
|
sta errors
|
|
|
|
;store the addresses of the buffers in the zero-page vectors
|
|
|
|
lda #<TRANSMIT_BUFFER
|
|
sta tbuff
|
|
lda #>TRANSMIT_BUFFER
|
|
sta tbuff + 1
|
|
|
|
lda #<RECEIVE_BUFFER
|
|
sta rbuff
|
|
lda #>RECEIVE_BUFFER
|
|
sta rbuff + 1
|
|
|
|
;the next four instructions initialize the ACIA to arbitrary values.
|
|
;These could be program defaults, or replaced by code that picks up
|
|
;the user's requirements for baud rate, parity, etc.
|
|
|
|
;The ACIA "control" register controls stop bits, word length, the
|
|
;choice of internal or external baud-rate generator, and the baud
|
|
;rate when the internal generator is used. The value below sets the
|
|
;ACIA for one stop bit, eight-bit word length, and 4800 baud using the
|
|
;internal generator.
|
|
; .------------------------- 0 = one stop bit
|
|
; :
|
|
; :.-------------------- word length, bits 6-7
|
|
; ::.------------------- 00 = eight-bit word
|
|
; :::
|
|
; :::.------------- clock source, 1 = internal generator
|
|
; ::::
|
|
; :::: .----- baud
|
|
; :::: :.---- rate
|
|
; :::: ::.--- bits ;1010 == 4800 baud, change to what you want
|
|
; :::: :::.-- 0-3
|
|
lda #%0001_1010
|
|
sta control
|
|
|
|
;The ACIA "command" register controls the parity, echo mode, transmit and
|
|
;receive interrupt enabling, hardware "BRK", and (indirectly) the "RTS"
|
|
;and "DTR" lines. The value below sets the ACIA for no parity check,
|
|
;no echo, disables transmit interrupts, and enables receive interrupts
|
|
;(RTS and DTR low).
|
|
; .------------------------- parity control,
|
|
; :.------------------------ bits 5-7
|
|
; ::.----------------------- 000 = no parity
|
|
; :::
|
|
; :::.------------------- echo mode, 0 = normal (no echo)
|
|
; ::::
|
|
; :::: .----------- transmit interrupt control, bits 2-3
|
|
; :::: :.---------- 10 = xmit interrupt off, RTS low
|
|
; :::: ::
|
|
; :::: ::.------ receive interrupt control, 0 = enabled
|
|
; :::: :::
|
|
; :::: :::.--- DTR control, 1=DTR low
|
|
lda #%0000_1001
|
|
sta command
|
|
|
|
;Besides initialization, also call the following code whenever the user
|
|
;changes parity of echo mode.
|
|
;It creates the "xmitoff" and "xmiton" models used by the interrupt
|
|
;handler and main-program transmit routine to control the ACIA
|
|
;interrupt enabling. If you don't change the models' parity bits,
|
|
;you'll revert to "default" parity on the next NMI.
|
|
|
|
;initialize with transmit interrupts off since
|
|
;buffer will be empty
|
|
|
|
sta xmitoff ;store as a model for future use
|
|
and #%1111_0000 ;mask off interrupt bits, keep parity/echo bits
|
|
ora #%0000_0101 ;and set bits to enable both transmit and
|
|
;receive interrupts
|
|
sta xmiton ;store also for future use
|
|
|
|
;The standard NMI routine tests th <RESTORE> key, CIA #2, and checks
|
|
;for the presence of an autostart cartridge.
|
|
|
|
;You can safely bypass the normal routine unless:
|
|
; * you want to keep the user port active
|
|
; * you want to use the TOD clock in CIA #2
|
|
; * you want to detect an autostart cartridge
|
|
; * you want to detect the RESTOR key
|
|
;
|
|
;If you need any of these functions, you can wedge the ACIA
|
|
;interrupt handler in ahead of the Kernal routines. It's probably
|
|
;safer to replicate in your own program only the Kernal NMI functions
|
|
;that you need. We'll illustrate bypassing all Kernal tests.
|
|
|
|
;BE SURE THE "NEWNMI" ROUTINE IS IN PLACE BEFORE EXITING THIS CODE!
|
|
;A "stray" NMI that occurs after the vector is changed to NEWNMI's address
|
|
;will probably cause a system crash if NEWNMI isn't there. Also, it would
|
|
;be best to initialize the ACIA to a "no interrupts" state until the
|
|
;new vector is stored. Although a power-on reset should disable all
|
|
;ACIA interrupts, it pays to be sure.
|
|
|
|
;If the user turns the modem off and on, an interrupt will probably be
|
|
;generated. At worst, this may leave a stray character in teh receive
|
|
;buffer, unless you don't have NEWNMI in place.
|
|
|
|
NEWVEC:
|
|
sei ;A stray IRQ shouldn't cause any problems
|
|
;while we're changing the NMI vector, but
|
|
;why take chances?
|
|
|
|
;If you want all the normal NMI tests to occur after the ACIA check,
|
|
;save the old vector. If you don't need the regular stuff, you can
|
|
;skip the next four lines. Note that the Kernal NMI routine pushes
|
|
;the CPU registers to the stack. If you call it at the normal address,
|
|
;you should pop the registers first (see EXITINT below).
|
|
|
|
lda NMINV ;get low byte of present vector
|
|
sta OLDVEC ;and store it for future use
|
|
lda NMINV+1 ;do the same
|
|
sta OLDVEC+1 ;with the high byte
|
|
|
|
;come here from the SEI if you're not saving
|
|
;the old vector
|
|
lda #<NEWNMI ;get low byte of new NMI routine
|
|
sta NMINV ;store in vector
|
|
lda #>NEWNMI ;and do the same with
|
|
sta NMINV+1 ;the high byte
|
|
|
|
cli ;allow IRQs again
|
|
|
|
;continue initializing your program
|
|
|
|
; ::: :::::: ;program initialization continues
|
|
jmp TERMINAL ;go to the example dumb-terminal subroutine
|
|
|
|
;Save two bytes to store the old vector only if you need it
|
|
|
|
|
|
; ---=== New NMI Routine Starts Here ===---
|
|
|
|
;The code below is a simple interrupt patch to control the ACIA. When
|
|
;the ACIA generates an interrupt, this routine examines the status
|
|
;register which contains the following data.
|
|
|
|
; .---------------------------- high if ACIA caused interrupt;
|
|
; : not used in code below
|
|
; :
|
|
; :.------------------------- reflects state of DCD line
|
|
; ::
|
|
; ::.---------------------- reflects state of DSR line
|
|
; :::
|
|
; :::.------------------ high if xmit-data register is empty
|
|
; ::::
|
|
; :::: .--------------- high if receive-data register full
|
|
; :::: :
|
|
; :::: :.----------- high if overrun error
|
|
; :::: ::
|
|
; :::: ::.------- high if framing error
|
|
; :::: :::
|
|
; :::: :::.--- high if parity error
|
|
; status xxxx_xxxx
|
|
|
|
NEWNMI:
|
|
; sei ;the Kernal routine already does this before jumping
|
|
;through the NMINV vector
|
|
pha ;save A register
|
|
txa
|
|
pha ;save X register
|
|
tya
|
|
pha ;save Y register
|
|
|
|
;As discussed above, the ACIA can generate an interrupt from one of four
|
|
;different sources. We'll first check to see if the interrupt was
|
|
;caused by the receive register being full (bit #3) or the transmit
|
|
;register being empty (bit #4) since these two activities should receive
|
|
;priority. A BEQ (Branch if EQual) tests the status register and branches
|
|
;if the interrupt was not caused by the data register.
|
|
|
|
;Before testing for the source of the interrupt, we'll prevent more
|
|
;interrupts from the ACIA by disabling them at the chip. This prevents
|
|
;another NMI from interrupting this one. (SEI won't work because the
|
|
;CPU can't disable non-maskable interrupts).
|
|
|
|
;At lower baud rates (2400 baud and lower) this may not be necessary. But,
|
|
;it's safe and doesn't take much time, either.
|
|
|
|
;The same technique should be used in parts of your program where timing
|
|
;is critical. Disk access, for example, uses SEI to mask IRQ interrupts.
|
|
;You should turn off the ACIA interrupts during disk access also to prevent
|
|
;disk errors and system crashes.
|
|
|
|
;First, we'll load the status register which contains all the interrupt
|
|
;and any received-data error information in the 'A' register.
|
|
|
|
lda status
|
|
|
|
;Now prevent any more NMIs from the ACIA
|
|
|
|
ldx #%0000_0011 ;disable all interrupts, bring RTS inactive, and
|
|
;leave DTR active
|
|
stx command ;send to ACIA-- code at end of interrupt handler
|
|
;will re-enable interrupts
|
|
|
|
;Store the status-register data only if needed for error checking.
|
|
;The next received byte will clear the error flags.
|
|
|
|
; sta errors ;only if error checking implemented
|
|
|
|
and #%0001_1000 ;mask out all but transmit and
|
|
;receive interrupt indicators
|
|
|
|
;If you don't use a transmit buffer you can use
|
|
;
|
|
; and #%0000_1000
|
|
;
|
|
;to test for receive interrupts only and skip the receive test shown
|
|
;below.
|
|
|
|
beq TEST_DCD_DSR
|
|
|
|
;if the 'A' register=0, either the interrupt was not caused by the
|
|
;ACIA or the ACIA interrupt was caused by a change in the DCD or
|
|
;DSR lines, so we'll branch to check those sources.
|
|
|
|
;If your program ignores DCD and DSR, you can branch to
|
|
;the end of the interrupt handler instead:
|
|
;
|
|
; beq NMIEXIT
|
|
;
|
|
|
|
;Test the status register information to see if a received byte is ready
|
|
;If you don't use a transmit buffer, skip the next two instructions.
|
|
|
|
RECEIVE: ;process received byte
|
|
and #%0000_1000 ;mask all but bit #3
|
|
beq XMITCHAR ;if not set, no received byte - if you're using
|
|
;a transmit buffer, the interrupt must have been
|
|
;caused by transmit. So, branch to handle.
|
|
lda data ;get received byte
|
|
ldy rtail ;index to buffer
|
|
sta (rbuff),y ;and store it
|
|
inc rtail ;move index to next slot
|
|
inc recvcount ;increment count of bytes in receive buffer
|
|
;(if used by your program)
|
|
|
|
;Skip the "XMIT" routines below if you decide not to use a transmit buffer.
|
|
;In that case, the next code executed starts at TEST_DCD_DSR or NMIEXIT.
|
|
|
|
;After processing a received byte, this sample code tests for bytes
|
|
;in the transmit buffer and sends on if present. Note that, in this
|
|
;sample, receive interrupts take precedence. You may want to reverse the
|
|
;order in your program.
|
|
|
|
;If the ACIA generated a transmit interrupt and no received byte was
|
|
;ready, status bit #4 is already set. The ACIA is ready to accept
|
|
;the byte to be transmitted and we've branched directly to XMITCHAR below.
|
|
|
|
;If only bit #3 was set on entry to the interrupt handler, the ACIA may have
|
|
;been in the process of transmitting the last byte, and there may still be
|
|
;characters in the transmit buffer. We'll check for that now, and send the
|
|
;next character if there is one. Before sending a character to the ACIA to
|
|
;be transmitted, we must wait until bit #4 of the status register is set.
|
|
|
|
XMIT:
|
|
lda xmitcount ;if not zero, characters still in buffer
|
|
;fall through to process xmit buffer
|
|
beq TEST_DCD_DSR ;no characters in buffer-- go to next check
|
|
;or
|
|
;
|
|
; beq NMIEXIT
|
|
;
|
|
;if you don't check DCD or DSR in your program.
|
|
|
|
XMITBYTE:
|
|
lda status ;test bit #4
|
|
and #%00010000
|
|
beq TEST_DCD_DSR ;skip if transmitter still busy
|
|
|
|
XMITCHAR: ;transmit a character
|
|
ldy thead
|
|
lda (tbuff),y ;get character at head of buffer
|
|
sta data ;place in ACIA for transmit
|
|
|
|
;point to next character in buffer
|
|
inc thead ;and store new index
|
|
dec xmitcount ;subtract one from count of bytes
|
|
;in xmit buffer
|
|
lda xmitcount
|
|
beq TEST_DCD_DSR
|
|
;or
|
|
;
|
|
; beq NMIEXIT
|
|
;
|
|
;if you don't check DCD or DSR in your program
|
|
|
|
;If xmitcount decrements to zero, there are no more characters to
|
|
;transmit. The code at NMIEXIT turns ACIA transmit interrupts off.
|
|
|
|
;If there are more bytes in the buffer, set up the 'A' register with
|
|
;the model that turns both transmit and receive interrupts on. We felt
|
|
;that was safer, and not much slower, than EORing bits #3 and #4. Note
|
|
;that the status of the parity/echo bits is preserved in the way "xmiton"
|
|
;and "xmitoff" were initialized earlier.
|
|
|
|
lda xmiton ;model to leave both interrupts enabled
|
|
|
|
;If you don't use DCD or DSR
|
|
|
|
bne NMICOMMAND ;branch always to store model in command register
|
|
|
|
;If your program uses DCD and/or DSR, you'll want to know when the state
|
|
;of those lines changes. You can do that outside the interrupt handler
|
|
;by polling the ACIA status register, but if either of the lines changes,
|
|
;the chip will generate an NMI anyway. So, you can let the interrupt
|
|
;handler do teh work for you. The cost is the added time required to
|
|
;execute the DCD_DSR code on each NMI.
|
|
|
|
TEST_DCD_DSR:
|
|
|
|
; pha ;only if you use a transmit buffer, 'A' holds
|
|
;the proper mask to re-enable interrupts on
|
|
;the ACIA
|
|
; ::
|
|
; :: ;appropriate code here to compare bit #6 (DCD)
|
|
; :: ;and/or bit #5 (DSR) with their previous states
|
|
; :: ;which you've already stored in memory and take
|
|
; :: ;appropriate action
|
|
; ::
|
|
; pla ;only if you pushed it at the start of the
|
|
;DCD/DSR routine
|
|
; bne NMICOMMAND ;'A' holds the xmiton mask if it's not zero,
|
|
;implying that we arrived here from xmit routine
|
|
;not used if you're not using a transmit buffer.
|
|
|
|
;If the test for ACIA interrupt failed on entry to the handler, we branch
|
|
;directly to here. If you don't use additional handlers, the RESTORE key
|
|
;(for example) will fall through here and have no effect on your program
|
|
;or the machine, except for some wasted cycles.
|
|
|
|
NMIEXIT:
|
|
lda xmitoff ;load model to turn transmit interrupts off
|
|
|
|
;and this line sets the interrupt status to whatever is in the 'A' register.
|
|
|
|
NMICOMMAND:
|
|
sta command
|
|
|
|
;That's all we need for the ACIA interrupt handler. Since we've pushed the
|
|
;CPU registers to the stack, we need to pop them off. Note that you must
|
|
;do this EVEN IF YOU JUMP TO THE KERNAL HANDLER NEXT, since it will push
|
|
;them again immediately. You can skip this step only if you're proceeding
|
|
;to a custom handler.
|
|
|
|
EXITINT: ;restore things and exit
|
|
pla ;restore 'Y' register
|
|
tay
|
|
pla ;restore 'X' register
|
|
tax
|
|
pla ;restore 'A' register
|
|
|
|
;If you want to continue processing the interrupt with the Kernal routines,
|
|
|
|
jmp (OLDVEC) ;continue processing interrupt with Kernal handler
|
|
|
|
;Or, if you add your own interrupt routine,
|
|
|
|
; jmp YOURCODE ;continue with your own handler
|
|
|
|
;If you use your own routine, or if you don't add anything, BE SURE to do
|
|
;this last (C64 only):
|
|
|
|
; rti ;return from interrupt instruction
|
|
|
|
;to restore the flags register the CPU pushes to the stack before jumping
|
|
;to the Kernal code. It also returns you to the interrupted part of
|
|
;your program
|
|
|
|
;-----------------------------------------------------------------------------
|
|
;Sample routine to store a character in the buffer to be transmitted
|
|
;by the ACIA.
|
|
|
|
;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
|
|
;Geoduck Developmental Systems, and Dr. Evil Labs.
|
|
|
|
;Assumes the character is in the 'A' register on entry. Destroys 'Y'--
|
|
;push to stack if you need to preserve it.
|
|
|
|
SENDBYTE: ;adds a byte to the xmit buffer and sets
|
|
;the ACIA to enable transmit interrupts (the
|
|
;interrupt handler will disable them again
|
|
;when the buffer is empty)
|
|
|
|
ldy xmitcount ;count of bytes in transmit buffer
|
|
cpy #255 ;max buffer size
|
|
beq NOTHING ;buffer is full, don't add byte
|
|
|
|
ldy ttail ;pointer to end of buffer
|
|
sta (tbuff),y ;store byte in 'A' at end of buffer
|
|
inc ttail ;point to next slot in buffer
|
|
inc xmitcount ;and add one to count of bytes in buffer
|
|
|
|
lda xmiton ;get model for turning on transmit interrupts
|
|
sta command ;tell ACIA to do it
|
|
|
|
rts ;return to your program
|
|
|
|
NOTHING:
|
|
lda #$00 ;or whatever flag your program uses to tell that the
|
|
;byte was not transmitted
|
|
rts ;and return
|
|
|
|
;Alternative routine to transmit a character from main program when not using
|
|
;a transmit buffer.
|
|
;
|
|
;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh,
|
|
;Geoduck Developmental Systems, and Dr. Evil Labs.
|
|
;
|
|
;Assumes the character to be transmitted is in the 'A' register on entry.
|
|
;Destroys 'Y'; push to stack if you need to preserve it.
|
|
;
|
|
;SENDBYTE:
|
|
; tay ;remember byte to be transmitted
|
|
;
|
|
;TESTACIA:
|
|
; lda status ;bit #4 of the status register is set if
|
|
; ;the ACIA is ready to transmit another byte,
|
|
; ;even if transmit interrupts are disabled.
|
|
; and #%0001_0000
|
|
; beq TESTACIA ;wait for bit #4 to be set
|
|
; sty data ;give byte to ACIA
|
|
; rts
|
|
|
|
;Sample routine to fetch a character that has been received, from the
|
|
;receive buffer.
|
|
|
|
;by Craig Bruce, 1995, adapted from above
|
|
|
|
;Will return the character in the 'A' register and the carry flag cleared if
|
|
;a character was available. If no character was available, will return with
|
|
;the carry flag set. Destroys the 'Y' register.
|
|
|
|
RECVBYTE: ;fetches a byte from the receive buffer.
|
|
;there is no need to fiddle with any interrupts
|
|
|
|
lda recvcount ;count of bytes in receive buffer
|
|
beq RECVEMPTY ;buffer is empty, indicate to caller
|
|
|
|
ldy rhead ;pointer to start of buffer
|
|
lda (rbuff),y ;fetch byte out of buffer into 'A' register
|
|
inc rhead ;point to next slot in buffer
|
|
dec recvcount ;and add one to count of bytes in buffer
|
|
|
|
clc ;indicate that we have a character
|
|
rts ;return to your program
|
|
|
|
RECVEMPTY:
|
|
sec ;or whatever flag your program uses to tell that the
|
|
;receive buffer was empty
|
|
rts ;and return
|
|
|
|
;-----------------------------------------------------------------------------
|
|
;Dumb -- very dumb -- terminal emulator. Simply polls the receive buffer and
|
|
;the keyboard and puts received data to the screen and typed data to the send
|
|
;buffer (thus, it assumes a full-duplex, echoing link). There is no
|
|
;PETSCII->ASCII conversion, no cursor, nor any other fancy features. Press
|
|
;STOP to exit.
|
|
;
|
|
;by Craig Bruce, 1995.
|
|
|
|
TERMINAL:
|
|
jsr RECVBYTE ;see if there is a received byte in the recv buffer
|
|
bcs TERMTRYSEND ;if not, continue
|
|
jsr $FFD2 ;if received byte, print it to the screen (CHROUT)
|
|
TERMTRYSEND:
|
|
jsr $FFE4 ;try to get a character from the keyboard (GETIN)
|
|
cmp #$00 ;was there a keystroke available?
|
|
beq TERMINAL ;no--go back to top of polling loop
|
|
cmp #$03 ;check for STOP key
|
|
beq TERMEXIT ; exit if pressed
|
|
jsr SENDBYTE ;have char--put it into the transmit buffer and then
|
|
jmp TERMINAL ; go back to top of polling loop
|
|
TERMEXIT:
|
|
lda #%0000_0010 ;disable transmitter and receiver and all interrupts
|
|
sta command
|
|
sei
|
|
lda OLDVEC ;restore the NMI vector to its original value
|
|
sta NMINV
|
|
lda OLDVEC+1
|
|
sta NMINV+1
|
|
cli
|
|
rts ;exit
|
|
|
|
TRANSMIT_BUFFER = *+0
|
|
RECEIVE_BUFFER = *+256
|
|
%%%---8<---cut-here---8<---%%%
|
|
|
|
------------------------------------------------------------------------------
|
|
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/666-7950 * TWX 510-660-4168
|
|
(July 1987)
|
|
|
|
6551 ASYNCHRONOUS COMMUNICATION INTERFACE ADAPTER
|
|
|
|
CONCEPT:
|
|
|
|
The 6551 is an Asynchronous Communication Adapter (ACIA) intended to provide
|
|
for interfacing the 6500/6800 microprocessor families to serial communication
|
|
data sets and modems. A unique feature is the inclusion of an on-chip
|
|
programmable baud-rate generator, with a crystal being the only external
|
|
component required.
|
|
|
|
FEATURES:
|
|
|
|
* On-chip baud-rate generator: 15 programmable baud rates derived from a
|
|
standard standard 1.8432 MHz external crystal (50 to 19,200 baud) [these
|
|
rates are doubled in the SwiftLink].
|
|
|
|
* Programmable interrupt and status register to simplify software design.
|
|
|
|
* Single +5 volt power supply.
|
|
|
|
* Serial echo mode.
|
|
|
|
* False start bit detection.
|
|
|
|
* 8-bit bi-directional data bus for direct communication with the
|
|
microprocessor.
|
|
|
|
* External 16x clock input for non-standard baud rates (up to 125 Kbaud).
|
|
|
|
* Programmable: word lengths; number of stop bits; and parity-bit generation
|
|
and detection.
|
|
|
|
* Data set and modem control signals provided.
|
|
|
|
* Parity: (odd, even, none, mark, space).
|
|
|
|
* Full-duplex or half-duplex operation.
|
|
|
|
* 5,6,7 and 8-bit transmission.
|
|
|
|
* 1-MHz, 2-MHz, and 3-MHz operation.
|
|
|
|
ORDER NUMBER
|
|
|
|
MXS 6551 ___
|
|
- |
|
|
| +---- Frequency range
|
|
| Plain = 1 MHz
|
|
| A = 2 MHz
|
|
| B = 3 MHz
|
|
|
|
|
+----------- Package Designator
|
|
C = Ceramic
|
|
P = Plastic
|
|
|
|
6551 PIN CONFIGURATION
|
|
|
|
+---------------+
|
|
GND --| 1 28 |-- R-/W
|
|
CS0 --| 2 27 |-- o2
|
|
/CS1 --| 3 26 |-- /IRQ
|
|
/RES --| 4 25 |-- DB7
|
|
RxC --| 5 24 |-- DB6
|
|
XTAL1 --| 6 23 |-- DB5
|
|
XTAL2 --| 7 22 |-- DB4
|
|
/RTS --| 8 21 |-- DB3
|
|
/CTS --| 9 20 |-- DB2
|
|
TxD --| 10 19 |-- DB1
|
|
/DTR --| 11 18 |-- DB0
|
|
RxD --| 12 17 |-- /DSR
|
|
RS0 --| 13 16 |-- /DCD
|
|
RS1 --| 14 15 |-- Vcc
|
|
+---------------+
|
|
|
|
BLOCK DIAGRAM +----------+
|
|
| TRANSMIT |
|
|
| CONTROL |<------- CTS
|
|
+----------+
|
|
|
|
|
v
|
|
+----------+ +----------+
|
|
| TRANSMIT | | TRANSMIT |
|
|
/|===>| DATA |=========>| SHIFT |-------> TxD
|
|
|| | REGISTER | | REGISTER |
|
|
|| +----------+ +----------+
|
|
+---------+ ||
|
|
o2 --->| | || +----------+ +----------+
|
|
R-/W --->| SELECT | ||====| STATUS | | INTERRUPT|-------> /IRQ
|
|
CS0 --->| AND | || | REGISTER |<-------->| LOGIC |<------- /DCD
|
|
/CS1 --->| CONTROL | || +----------+ +----------+<------- /DSR
|
|
RS0 --->| LOGIC | ||
|
|
RS1 --->| | || +----------+ +----------+
|
|
/RES --->| | ||===>| CONTROL | | BAUD-RATE|<------> RxC
|
|
+---------+ || | REGISTER | | GENERATOR|<------- XTAL1
|
|
|| +----------+ +----------+<------- XTAL2
|
|
||
|
|
+---------+ || +----------+ +----------+
|
|
DB0 <-->| DATA- | || | RECEIVE | | RECEIVE |
|
|
... | BUS |<===||====| DATA |<=========| SHIFT |<---+--- RxD
|
|
DB7 <-->| BUFFERS | || | REGISTER | | REGISTER | |
|
|
+---------+ || +----------+ +-----.----+ |
|
|
|| | |
|
|
|| +----------+ +----------+ |
|
|
LEGEND: \|===>| COMMAND | | RECEIVE | |
|
|
| REGISTER | | CONTROL |<---+
|
|
===> : 8-bit line +----------+ +----------+
|
|
| |
|
|
---> : 1-bit line | +--------------------------------> /DTR
|
|
+-------------------------------------> /RTS
|
|
|
|
MAXIMUM RATINGS
|
|
|
|
<not included here>
|
|
|
|
ELECTRICAL CHARACTERISTICS
|
|
|
|
<not included here>
|
|
|
|
POWER DISSIPATION vs TEMPERATURE
|
|
|
|
<not included here>
|
|
|
|
TIMING CHARACTERISTICS
|
|
|
|
<not included here>
|
|
|
|
INTERFACE SIGNAL DESCRIPTION
|
|
|
|
/RES (Reset)
|
|
|
|
During system initialization a low on the /RES input will cause internal
|
|
registers to be cleared.
|
|
|
|
o2 (Input Clock)
|
|
|
|
The input clock is the system o2 clock and is used to trigger all data
|
|
transfers between the system microprocessor and the 6551.
|
|
|
|
R-/W (Read/Write)
|
|
|
|
The R-/W is generated by the microprocessor and is used to control the
|
|
direction of data transfers. A high on the R-/W pin allows the processor
|
|
to read the data supplied by the 6551. A low on the R-/W pin allows a write
|
|
to the 6551.
|
|
|
|
/IRQ (Interrupt Request)
|
|
|
|
The /IRQ pin is an interrupt signal from the interrupt-control logic. It is
|
|
an open drain output, permitting several devices to be connected to the common
|
|
/IRQ microprocessor input. Normally a high level, /IRQ goes low when an
|
|
interrupt occurs.
|
|
|
|
DB0--DB7 (Data Bus)
|
|
|
|
The DB0--DB7 pins are the eight data lines used for transfer of data between
|
|
the processor and the 6551. These lines are bi-directional and are normally
|
|
high-impedance except during Read cycles when selected.
|
|
|
|
CS0, /CS1 (Chip Selects)
|
|
|
|
The two chip-select inputs are normally connected to the processor-address
|
|
lines either directly or through decoders. The 6551 is selected when CS0 is
|
|
high and /CS1 is low.
|
|
|
|
RS0, RS1 (Register Selects)
|
|
|
|
The two register-select lines are normally connected to the processor-address
|
|
lines to allow the processor to select the various 6551 internal registers.
|
|
The following table indicates the internal register-select coding:
|
|
|
|
RS1 RS0 WRITE READ SL-Addr
|
|
--- --- ---------------------- --------------------- -------
|
|
0 0 Transmit Data Register Receive Data Register $DE00
|
|
0 1 Programmed Reset* Status Register $DE01
|
|
1 0 Command Register Command Register $DE02
|
|
1 1 Control Register Control Register $DE03
|
|
|
|
* for programmed reset, data is "don't care".
|
|
|
|
The table shows that only the Command and Control registers are read/write.
|
|
The Programmed Reset operation does not cause any data transfer, but is used
|
|
to clear the 6551 registers. The Programmed Reset is slightly different from
|
|
the Hardware Reset (/RES) and these differences are described in the
|
|
individual register definitions.
|
|
|
|
ACIA/MODEM INTERFACE SIGNAL DESCRIPTION
|
|
|
|
XTAL1, XTAL2 (Crystal Pins)
|
|
|
|
These pins are normally directly connected to the external crystal (1.8432
|
|
MHz) used to derive the various baud rates. Alternatively, an externally
|
|
generated clock may be used to drive the XTAL1 pin, in which case the XTAL2
|
|
pin must float. XTAL1 is the input pin for the transmit clock.
|
|
|
|
TxD (Transmit Data)
|
|
|
|
The TxD output line is used to transfer serial NRZ (non-return-to-zero) data
|
|
to the modem. The LSB (least-significant bit) of the Transmit Data Register
|
|
is the first data bit transmitted and the reate of data transmission is
|
|
determined by the baud rate selected.
|
|
|
|
RxD (Receive Data)
|
|
|
|
The RxD input line is used to transfer serial NRZ data into the ACIA from the
|
|
modem, LSB first. The receiver data rate is either the programmed baud rate
|
|
or the rate of an externally generated receiver clock. This selection is made
|
|
by programming the Control Register.
|
|
|
|
RxC (Receive Clock)
|
|
|
|
The RxC is a bi-directional pin which serves as either the receiver 16x clock
|
|
input or the receiver 16x clock output. The latter mode results if the
|
|
internal baud rate generator is selected for receiver data clocking.
|
|
|
|
/RTS (Request to Send)
|
|
|
|
The /RTS output pin is used to control the modem from the processor. The
|
|
state of the /RTS pin is determined by the contents of the Command Register.
|
|
|
|
/CTS (Clear to Send)
|
|
|
|
The /CTS input pin is used to control the transmitter operation. The enable
|
|
state is with /CTS low. The transmitter is automatically disabled if /CTS is
|
|
high.
|
|
|
|
/DTR (Data Terminal Ready)
|
|
|
|
The output pin is used to indicate the status of the 6551 to the modem. A low
|
|
of /DTR indicates the 6551 is enabled and a high indicates it is disabled.
|
|
The processor controls this pin via bit 0 of the Command Register.
|
|
|
|
/DSR (Data Set Ready)
|
|
|
|
The /DSR input pin is used to indicate to the 6551 the status of the modem. A
|
|
low indicates the "ready" state and a high, "not-ready". /DSR is a high-
|
|
impedance input and must not be a no-connect. If unused, it should be driven
|
|
high or low, but not switched.
|
|
|
|
Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs,
|
|
/IRQ will be set and Status Register Bit #[5] will reflect the new level. The
|
|
state of /DSR does not affect Transmitter operation [but must be low for the
|
|
Receiver to operate]. [This statement reflects the SwiftLink implementation].
|
|
|
|
/DCD (Data Carrier Detect)
|
|
|
|
The /DCD input pin is used to indicate to the 6551 the status of the carrier-
|
|
detect output of the modem. A low indicates that the modem carrier signal is
|
|
present and a high, that it is not. /DCD, like /DSR, is a high-impedance
|
|
input and must not be a no-connect.
|
|
|
|
Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs,
|
|
/IRQ will be set and Status Register Bit #[6] will reflect the new level. The
|
|
state of /DCD does not affect either Transmitter or Receiver operation.
|
|
|
|
INTERNAL ORGANIZATION
|
|
|
|
<not included here>
|
|
|
|
TRANSMIT AND RECEIVE DATA REGISTERS (SL-Addr: $DE00 / 56832)
|
|
|
|
These registers are used as temporary data storage for the 6551 Transmit and
|
|
Receive circuits. The Transmit Data Register is characterized as follows:
|
|
|
|
* Bit 0 is the leading bit to be transmitted.
|
|
|
|
* Unused data bits are the high-order bits and are "don't care" for
|
|
transmission.
|
|
|
|
The Receive Data Register is characterized in a similar fashion:
|
|
|
|
* Bit 0 is the leading bit received.
|
|
|
|
* Unused data bits are the high-order bits and are "0" for the receiver.
|
|
|
|
* Parity bits are not contained in the Receive Data Register, but are stripped
|
|
off after being used for external parity checking. Parity and all unused
|
|
high-order bits are "0".
|
|
|
|
Transmit / Receive Data Register
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| data |
|
|
|
|
The following figure illustrates a single transmitted or received data
|
|
word, for the example of 8 data bits, parity, and 1 stop bit:
|
|
|
|
"MARK"____ ___________________________________________________"MARK"
|
|
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | P | S .
|
|
|____|____|____|____|____|____|____|____|____|____|
|
|
start parity stop
|
|
bit ...data bits... bit bit
|
|
|
|
|
|
STATUS REGISTER (SL-Addr: $DE01 / 56833)
|
|
|
|
The Status Register is used to indicate to the processor the status of various
|
|
6551 functions and is outlined here:
|
|
|
|
Command Register
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| irq | dcd | dsr | txr | rxr | ovr | fe | pe |
|
|
|
|
+---+
|
|
| 7 | /IRQ*** : cleared by reading status register
|
|
+---+ --------------------------------------------
|
|
0 No interrupt
|
|
1 Interrupt
|
|
|
|
+---+
|
|
| 6 | /DCD : non-resetable, indicates /DCD status
|
|
+---+ --------------------------------------------
|
|
0 /DCD low
|
|
1 /DCD high
|
|
|
|
+---+
|
|
| 5 | /DSR : non-resetable, indicates /DSR status
|
|
+---+ --------------------------------------------
|
|
0 /DSR low
|
|
1 /DSR high
|
|
|
|
+---+
|
|
| 4 | Transmit Data Register Empty: Cleared by write to Tx Data reg
|
|
+---+ -------------------------------------------------------------
|
|
0 Not empty
|
|
1 Empty
|
|
|
|
+---+
|
|
| 3 | Receive Data Register Full: Cleared by read from Rx Data reg
|
|
+---+ -------------------------------------------------------------
|
|
0 Not full
|
|
1 Full
|
|
|
|
+---+
|
|
| 2 | Overrun*: Self-clearing**
|
|
+---+ -------------------------
|
|
0 No error
|
|
1 Error
|
|
|
|
+---+
|
|
| 1 | Framing Error*: Self-clearing**
|
|
+---+ -------------------------------
|
|
0 No error
|
|
1 Error
|
|
|
|
+---+
|
|
| 0 | Parity Error*: Self-clearing**
|
|
+---+ ------------------------------
|
|
0 No error
|
|
1 Error
|
|
|
|
Notes: * No interrupt generated for these conditions
|
|
** Cleared automatically after a read of RDR and the next error-
|
|
free receipt of data
|
|
*** Reading status reg. will clear the /IRQ bit except when
|
|
transmit intr. enabled
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+
|
|
| 0 | x | x | 1 | 0 | 0 | 0 | 0 | After Hardware reset
|
|
+---+---+---+---+---+---+---+---+
|
|
| x | x | x | x | x | 0 | x | x | After Software reset
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
COMMAND REGISTER (SL-Addr: $DE02 / 56834)
|
|
|
|
The Command Register is used to control specific Transmit/Receive functions
|
|
and is shown here:
|
|
|
|
Command Register
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| parity | echo| tx ctrl | rxi | dtr |
|
|
|
|
+---+---+---+
|
|
| 7 | 6 | 5 | PARITY CHECK CONTROLS
|
|
+---+---+---+ ----------------------
|
|
x x 0 parity disabled--no parity bit generated or received
|
|
0 0 1 odd parity receiver and transmitter
|
|
0 1 1 even parity receiver and transmitter
|
|
1 0 1 mark parity transmitted, parity check disabled
|
|
1 1 1 space parity transmitted, parity check disabled
|
|
|
|
+---+
|
|
| 4 | NORMAL/ECHO MODE FOR RECEIVER
|
|
+---+ ------------------------------
|
|
0 Normal
|
|
1 Echo (bits 2 and 3 must be "0")
|
|
|
|
+---+---+
|
|
| 3 | 2 | Tx INTERRUPT RTS LEVEL TRANSMITTER
|
|
+---+---+ ------------ --------- ------------
|
|
0 0 Disabled High Off
|
|
0 1 Enabled Low On
|
|
1 0 Disabled Low On
|
|
1 1 Disabled Low Transmit BRK
|
|
|
|
+---+
|
|
| 1 | RECEIVE INTERRUPT ENABLE
|
|
+---+ -------------------------
|
|
0 /IRQ interrupt Enabled from bit 3 of Status Register
|
|
1 /IRQ interrupt Disabled
|
|
|
|
+---+
|
|
| 0 | DATA TERMINAL READY
|
|
+---+ --------------------
|
|
0 Disable Receiver and all interrupts (/DTR high)
|
|
1 Enable Receiver and all interrupts (/DTR low)
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+
|
|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | After Hardware reset
|
|
+---+---+---+---+---+---+---+---+
|
|
| x | x | x | 0 | 0 | 0 | 0 | 0 | After Software reset
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
CONTROL REGISTER (SL-Addr: $DE03 / 56835 / cpm: 0001xxxx)
|
|
|
|
The Control Register is used to select the desired mode for the 6551. The
|
|
word length, number of stop bits, and clock controls are all determined
|
|
by the Control Register, which is shown here:
|
|
|
|
Control Register
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
|stops| word len | src | baud rate |
|
|
|
|
+---+
|
|
| 7 | STOP BITS
|
|
+---+ ----------
|
|
0 1 stop bit
|
|
1 2 stop bits
|
|
1 1 stop bit if word length== 8 bits and parity
|
|
this allows for 9-bit transmission (8 data bits plus parity)
|
|
1 1.5 stop bits if word length== 5 bits and no parity
|
|
|
|
+---+---+
|
|
| 6 | 5 | WORD LENGTH
|
|
+---+---+ ------------
|
|
0 0 8 bits
|
|
0 1 7 bits
|
|
1 0 6 bits
|
|
1 1 5 bits
|
|
|
|
+---+
|
|
| 4 | RECEIVER CLOCK SOURCE
|
|
+---+ ----------
|
|
0 external receiver clock
|
|
1 baud rate generator
|
|
|
|
+---+---+---+---+
|
|
| 3 | 2 | 1 | 0 | BAUD RATE GENERATOR
|
|
+---+---+---+---+ --------------------
|
|
0 0 0 0 16x external clock
|
|
0 0 0 1 100 baud
|
|
0 0 1 0 150 baud
|
|
0 0 1 1 219.84 baud
|
|
0 1 0 0 269.16 baud
|
|
0 1 0 1 300 baud
|
|
0 1 1 0 600 baud
|
|
0 1 1 1 1200 baud
|
|
1 0 0 0 2400 baud
|
|
1 0 0 1 3600 baud
|
|
1 0 1 0 4800 baud
|
|
1 0 1 1 7200 baud
|
|
1 1 0 0 9600 baud
|
|
1 1 0 1 14400 baud
|
|
1 1 1 0 19200 baud
|
|
1 1 1 1 38400 baud
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+
|
|
| 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/Efficient Upload/Download Protocol
|
|
by Craig Bruce <csbruce@ccnga.uwaterloo.ca>
|
|
|
|
1. INTRODUCTION
|
|
|
|
If you use your Commodore for telecommunications, then you are basically
|
|
interested in two things: using your C= to emulate a terminal for interactive
|
|
stuff, and using modem-file-transfer protocols to upload and download files
|
|
from and to your Commodore.
|
|
|
|
This document describes a custom upload/download protocol that was designed
|
|
for use with the ACE-128/64 system and is freely available to anyone who wants
|
|
it (well, when I finish with the Release #14 of ACE). While this protocol
|
|
non-standard, it blows the doors off of all other protocols available for
|
|
Commodore computers, even though it uses a simple "stop-and-wait"
|
|
acknowledgement scheme. There are two reasons for its speed: the fast device
|
|
drivers available with ACE, and its large packet size, up to about 18K
|
|
(although this could be significantly larger is ACE's memory usage were
|
|
reorganized).
|
|
|
|
The name of the protocol is "Craig's File eXchange Protocol", or just "FX" for
|
|
short. It is "file exchange" rather than "upload" or "download" because you
|
|
will use the same activation of the program to both upload and download all of
|
|
the files you name.
|
|
|
|
2. USAGE
|
|
|
|
The current implementation of FX consists of a "client" program for you to run
|
|
on your Commodore computer and a "server" program that you run on your Unix
|
|
host. There is currently no server program for any other platform, but the
|
|
necessary changes to the C-language program wouldn't be too hard. The client
|
|
program is written in 6502 assembler, of course (for the ACE-assembler to be
|
|
specific).
|
|
|
|
FX is an external program from the terminal program, so (for now) to activate
|
|
FX, you have to exit from the terminal program and enter the FX command line,
|
|
exchange the files, and then re-enter the terminal program from the command
|
|
line.
|
|
|
|
When you run FX, you will activate the Server program first on your Unix host
|
|
and then exit the terminal program and run the Client program on your
|
|
Commodore. You run the command "fx" on both the client and server machines,
|
|
which may be a little confusing (but I think you'll get used to it), and name
|
|
the files that you want to have transferred as arguments to the command on the
|
|
machine that you want to transfer the files FROM. The usage of the "fx"
|
|
command is as follows:
|
|
|
|
fx [-dlvV7] [-m maximums] [-f argfile] [[-b] binfile ...] [-t textfile ...]
|
|
|
|
-d = debug mode
|
|
-l = write to log file ("fx.log")
|
|
-v = verbose log/debug mode
|
|
-V = extremely verbose log/debug mode
|
|
-7 = use seven-bit encoding
|
|
-m = set maximum packet sizes; maximums = ulbin/ultxt/dlbin/dltxt (bytes)
|
|
-f = take arguments one-per-line from given argfile
|
|
-b = binary files prefix
|
|
-t = text files prefix
|
|
-help = help
|
|
|
|
well, for the server, anyway. The client program doesn't have the more
|
|
exotic options. The "-d", "-l", "-v", and "-V" options are available only
|
|
on the Server program, and are for debugging purposes only.
|
|
|
|
The "-7" option tells the protocol to use only 7-bit data. I.e., it tells it
|
|
to not use the 8th bit position in the data is transmitted. This is useful if
|
|
you are forced into the humiliation of only being able to use a 7-bit channel
|
|
to your Unix host. You need only need to give this option on either the
|
|
client or the host command line and the other side will be informed. It may
|
|
be useful to create an alias for this command with all of your options set to
|
|
what you want them to be.
|
|
|
|
The protocol has the capacity to use different packet sizes for four types of
|
|
file-transfer situations: uploading binary data, uploading text, downloading
|
|
binary data, and downloading text. These are useful distinctions, since your
|
|
host may or may not be able to handle the larger packet sizes without losing
|
|
bytes (your Commodore, of course, can handle the larger packet sizes with no
|
|
problems).
|
|
|
|
In determining which packet size to use for a file transfer (where the type of
|
|
transfer is known), the protocol finds that largest packet size that both the
|
|
client and the server can handle and then take the minimum of these two
|
|
values. The defaults for the client are all the same: the maximum amount of
|
|
program-area memory that it can use, about 18K. For the server program, I
|
|
have programmed in default maximum uploading packet sizes of 1K and maximum
|
|
downloading packet sizes of 64K-1. You can change these defaults in the C
|
|
program easily by changing some "#define"s.
|
|
|
|
The "-m" option allows you to manually set the default packet sizes for a
|
|
transfer. The argument following the "-m" flag should have four numbers with
|
|
slashes between them, which give the maximum ulbin/ultxt/dlbin/dltxt packet
|
|
sizes, respectively. Note that the packet sizes only include the size of the
|
|
user data encoded into packets and not the control or quoting information
|
|
(below).
|
|
|
|
The "-f" option on the server allows you to read arguments from a file rather
|
|
than the command line. This is useful if want to generate and edit the list
|
|
of files to download before you run the FX command. It's also useful if you
|
|
don't want other users to see the names of the files that you are
|
|
downloading. The name of the file comes in the first argument following the
|
|
"-f" flag and the arguments are put into this file one-per-line. You can put
|
|
in "-" options in addition to filenames if you wish (like "-t" and "-b").
|
|
This option is not supported on the client program.
|
|
|
|
Finally come the "-b", "-t", and filename arguments. The "-b" argument tells
|
|
FX that all of the following filenames (until the next "-t" option) are binary
|
|
files and the "-t" argument says that the following filenames are all of text
|
|
files. You can use as many "-b" and "-t" arguments as you want. If you don't
|
|
use any, then all of the files you name will be assumed to be binary files.
|
|
|
|
For each filename you give on a command line, that file will be transferred
|
|
from that machine to the other machine. On both Unix and ACE, you can use
|
|
wildcards in your filenames, of course, to transfer groups of files.
|
|
|
|
The client program controls the file exchange, and it uploads all of its files
|
|
first and then asks the server if the server has any files to be downloaded.
|
|
When the exchange is completed, both the client and server FX programs will
|
|
exit and you will find yourself back on the command lines in both
|
|
environments. Re-enter the terminal program to continue with your online
|
|
session. If something goes very wrong during a transfer or if you decide that
|
|
you don't really want to transfer any files after activating the server
|
|
program, you can type three Ctrl-X's to abort the server. This is the same as
|
|
for the X-modem protocol.
|
|
|
|
3. DESIGN DECISIONS
|
|
|
|
There are a number of design decisions to be made about our protocol. But
|
|
first, we want to recognize and appreciate that since we have a license to
|
|
design a completely new protocol, we are not bound, shackled, gagged, and
|
|
tortured by the "hysterical raisins" and bad design decisions of existing
|
|
compromised and bloated standard protocols... such as Z-modem.
|
|
|
|
We want the protocol to understand whether a file is text or binary data and
|
|
to translate them appropriately during downloading. We want the protocol to
|
|
be aware of filenames, dates, permissions, and we do not want our file
|
|
contents to get mangled like they do with X-modem (it pads them with Ctrl-Z's,
|
|
since it was designed for CP/M), and we want it to translate to/from PETSCII
|
|
if the file is text. We will require that the user tell us whether the file
|
|
is binary or text (although we may be able to statistically determine this
|
|
from snooping through the file), and we will use a "canonical form" for
|
|
encoding the text data during transfer. A convenient canonical form to use is
|
|
Unix-ASCII (ASCII-LF).
|
|
|
|
We want our protocol to be simultaneously simple and fast. To make it simple,
|
|
we will use a stop-and-wait acknowledgement scheme. This means that after
|
|
each packet is uploaded or downloaded, the transfer will pause and wait for
|
|
the receiving host to acknowledge that the packet has been transferred
|
|
correctly, and only then will the protocol continue to transfer more data.
|
|
|
|
In fact, this scheme fits well with the Commodore hardware, since it is not
|
|
possible to send or receive serial data while doing disk I/O (in the general
|
|
case), so we would have to stop listening anyway; the protocol makes it so
|
|
that there will be no bytes that we end up ignoring while doing I/O.
|
|
|
|
To make the protocol be fast even though we are using a stop-and-wait
|
|
acknowledgement scheme, we will use the largest data-packet sizes that we
|
|
possibly can. In the (current) ACE environment, this means about 18K. This
|
|
will maximize the amount of time of transferring data over the modem between
|
|
pauses to do I/O. If the I/O is to the ACE ramdisk, then the length of this
|
|
pause will be very short and we will achieve a very high link utilization.
|
|
(The ACE ramdisk can process an 18K read/write request in about 20
|
|
milliseconds on a Fast-mode C128 using an REU --- RAMDOS in the same
|
|
environment would require about 9 _seconds_ (450x slower)).
|
|
|
|
To allow for future use with other platforms, we will make the protocol define
|
|
the packet sizes using 32-bit fields. There isn't much data overhead, and
|
|
this allows us to change implementations to be able to transfer entire files
|
|
in one large packet. Also, the size of an individual packet should be
|
|
flexible: be from one to N bytes. This eliminates the X-modem padding problem
|
|
and the Y-modem crufty hack of using the small packet size when less than 1K
|
|
of user data remains to be transferred.
|
|
|
|
We also want our data to be well protected against corruption. Detecting
|
|
transmission errors efficiently on Commodore computers is already a well
|
|
solved problem: we will use a table-driven CRC-32 algorithm, the same one that
|
|
ZMODEM, PKZIP, and CRC32 use. To hide the computation costs of the CRC even
|
|
more (the cost is very low anyway), we will compute it WHILE sending or
|
|
receiving packets. Oh, actually, I guess that I forgot to mention an a-priori
|
|
design decision: we will be using a packet-oriented approach for transferring
|
|
data (described below); packetization offers so many advantages that this
|
|
decision is really a no-brainer.
|
|
|
|
Also, to make the process interaction as straightforward as possible, we want
|
|
to use the Client/Server programming paradigm. This paradigm combines well
|
|
with the stop-and-wait acknowledgement scheme to produce a Remote Procedure
|
|
Call (RPC) type of interaction between the machines. For those not familiar
|
|
with this Interprocess Communication (IPC) scheme, you can read a couple
|
|
issues of C= hacking ago where I talked about it for use with a multitasking
|
|
operation system. RPC is a very useful, powerful, simple, and widely
|
|
applicable IPC scheme.
|
|
|
|
To recover from packet corruption, we will be using a timeout+retransmission
|
|
scheme, and to be consistent with the RPC scheme, the client will do all
|
|
timeouts and retransmissions. This means that after sending a request RPC
|
|
packet out, if we don't receive the reply within a certain period of time, we
|
|
will timeout and send the request again. Or, to be more precise, since we
|
|
will be working with large packet sizes, we will timeout if we don't receive
|
|
any bytes from the server for a certain period of time, say 5 seconds, while
|
|
we are expecting more bytes from him.
|
|
|
|
The way that corrupted packets are dealt with is very simple: they are
|
|
ignored. The server could possibly send back a negative acknowledgement,
|
|
but we won't try that for now.
|
|
|
|
In order to make retransmissions work out correctly, we will be using sequence
|
|
numbers and internal-state variables inside of the server to insure that
|
|
requests aren't carried out more than once. We need these mechanisms because
|
|
when an RPC fails, we won't know if we got no response because the original
|
|
request was lost and the operations wasn't carried out, or whether the request
|
|
was received and carried out but the reply message was lost.
|
|
|
|
For example, if we request that packet #123 be downloaded and the server
|
|
carries out that request but the reply message is lost, then the client will
|
|
time out and retransmit the request. The server remembers the last request
|
|
number that the client sent it (123 here), so if the client asks for packet
|
|
#123 again, the server will simply retransmit the reply that it gave last
|
|
time. If, on the other hand, the client were to request packet #124 (or
|
|
simply "not 123"), then the server reads the next chunk of data from the file
|
|
and sends it as the reply. Our protocol will use an 8-bit sequence number
|
|
even though it only needs a 1-bit sequence number (since eight bits will allow
|
|
for the future expansion of having multiple requests being processed
|
|
concurrently: asynchronous RPC).
|
|
|
|
We also want to be able to both upload and download as conveniently as
|
|
possible. To me, this means doing both operations by calling only one command
|
|
(as described in the previous section). This arrangement also allows for the
|
|
future expansion of uploading and downloading files _simultaneously_ (the
|
|
protocol as designed places no restrictions on this possibility).
|
|
|
|
We also want to make use of an eight-bit clean link between the Unix host and
|
|
your Commodore, but this may not always be possible. Sometimes you may have
|
|
only a 7-bit connection, and even if you do have an 8-bit connection, there
|
|
may still be some software-flow-control problems with intermediate devices
|
|
between your Commodore and your Unix host. So, we want our protocol to not
|
|
make use of the X-on and X-off characters, and to use only 7-bit characters if
|
|
it cannot use eight. The way to achieve this is called "escaping", "quoting"
|
|
or "byte stuffing", and will be discussed in the next section. It turns out
|
|
that supporting 7-bit characters is pretty simple and the mechanism is
|
|
required by other aspects of the packetization.
|
|
|
|
There, that should take care of most of the major design decisions.
|
|
|
|
4. PACKETIZATION
|
|
|
|
Packetization refers to the process of taking a stream of data and breaking it
|
|
up into discrete chunks of data. Each packet is easily identified and is
|
|
processed as a single unit. There are many general advantages to using
|
|
packets. If there is a transmission error, then only a single packet is
|
|
corrupted, and the recovery will be easier since the packet is well
|
|
identified, and only it needs to be recovered. Packetization also means that
|
|
a link can be shared between multiple (logical) communication streams fairly
|
|
and efficiently, and means that a single communication stream can utilize
|
|
multiple physical links where facilities exist.
|
|
|
|
Packets also integrate well with many IPC schemes, including Remote Procedure
|
|
Calls. In fact, you end up emulating a packet-oriented scheme even if you are
|
|
using RPC over a stream-oriented transport system. Packets also take into
|
|
account the limited buffering capacity of both end systems and intermediate
|
|
systems, and allow for the convenient implementation of flow control (even if
|
|
said flow control consists of simply dropping packets on the floor). Packets
|
|
are very useful things indeed! And just think that back in the early 1970s
|
|
packets were dismissed as being infeasible and unusable.
|
|
|
|
Each packet used in the FX system has four parts to it: the start character,
|
|
the user data (payload), the error-check characters, and the end character.
|
|
Graphically, a packet has the following format:
|
|
|
|
+------------------------+-----------+--------------+----------------------+
|
|
| Start-of-packet Char | Payload | ErrorCheck | End-of-packet Char |
|
|
+------------------------+-----------+--------------+----------------------+
|
|
|
|
The payload can be arbitrarily long, up to whatever limit the two computers
|
|
involved in the transfer can handle.
|
|
|
|
The error check is a 32-bit (4-byte) Cyclic-Redundancy-Check value that
|
|
occupies the last four bytes before the End-of-packet character. The
|
|
implementation, which is based on a table-lookup method, is so efficient that
|
|
it is as fast as a simple add-up checksum, except much more reliable. Using
|
|
this error check, there will be approximately a one-in-4,000,000,000 chance
|
|
that a packet with an error in it will be accepted has being error-free.
|
|
These are pretty good odds for our purposes. The CRC is calculated
|
|
exclusively on the raw payload data.
|
|
|
|
The following special characters used by packets are defined:
|
|
|
|
NAME HEX DEC Control Meaning
|
|
--------- ---- --- ------- --------
|
|
CHR_START 0x01 1 Ctrl-A Packet-start indicator
|
|
CHR_END 0x19 25 Ctrl-Y Packet-end indicator
|
|
CHR_ESC 0x05 5 Ctrl-E Escape character for next code
|
|
CHR_ABORT 0x18 24 Ctrl-X Abort transfer if repeated three times
|
|
CHR_XON 0x11 17 Ctrl-Q Software flow-start: avoided
|
|
CHR_XOFF 0x13 19 Ctrl-S Software flow-stop: avoided
|
|
CHR_QUOTE8 0x14 20 Ctrl-T Quote-8 the next 7-bit sequence
|
|
|
|
CHR_START is used to signify the start of a new packet. This character is
|
|
not allowed to be used anywhere else for any other purpose.
|
|
|
|
CHR_END is used to signify the end of the current packet, and cannot be used
|
|
anywhere else. The reason for using special characters to mark the beginning
|
|
and the ending of a packet is to allow for easy error recovery after a
|
|
communication failure. All you do is search for the next CHR_START character
|
|
after you toss away a garbled packet and you're back in business. I am
|
|
unaware of any reasonable alternatives to framing packets with a CHR_START
|
|
character. Using a CHR_END special character is a convenience.
|
|
|
|
CHR_ESC is used to "escape" the next character. Since there are special
|
|
character codes that cannot be used in any other way than their intended
|
|
function (including CHR_START and CHR_ESC itself), this character is needed.
|
|
The character following the CHR_ESC character must be between "@" and "_"
|
|
(0x40 and 0x5f) in the ASCII chart, or be the character "?" (0x3f). The
|
|
character following the CHR_ESC is then "and"ed with the value 0x1f to mask
|
|
off the "letter" bits and turn it into a control character in the range of
|
|
0x00 to 0x1f (the same range as the special control characters) and the
|
|
"escape sequence" is treated as a single character of user data. If the
|
|
character following the CHR_ESC is a "?", then a code of 0x7f is interpreted
|
|
instead. Using a character following the escape that is different from the
|
|
character being represented allows for greater resiliance of the protocol in
|
|
the presence of bits being garbled or bytes being dropped. All special
|
|
characters in a packet except for the starting and ending characters are
|
|
escaped as described above.
|
|
|
|
CHR_ABORT can be typed by the user into a terminal program at any time to shut
|
|
down the server.
|
|
|
|
CHR_XON and CHR_XOFF can cause problems with intermediate devices on some
|
|
systems, so the FX protocol does not use these character codes at all; it
|
|
purposely avoids them and uses escape sequences (CHR_ESC) for them instead.
|
|
|
|
CHR_QUOTE8 is used to re-generate 8-bit data over a 7-bit link. Kermit uses
|
|
this same technique. When this character is encountered in the receive
|
|
stream, the next character is extracted and is "or"ed with a value of 0x80 to
|
|
give it a "1" in the high-bit position. The CHR_QUOTE8 character can also be
|
|
followed by a CHR_ESC code, which is interpreted as above and then "or"ed with
|
|
the 0x80 value.
|
|
|
|
One of the disadvantages of using this scheme is that each byte in the range
|
|
of 0x80 and 0xff takes at least two bytes to transmit and some of them three.
|
|
If fact, for many binary files it may be faster to uuencode the file and
|
|
transfer the resulting text, since uucode has a static encoding overhead of
|
|
33% whereas this quoting scheme has an expected overhead of 50% (plus the
|
|
CHR_ESC overhead). Of course, this feature is intended to be used as a last
|
|
resort if you cannot get an 8-bit connection.
|
|
|
|
So there you have it. Every message sent between the client and the server
|
|
is encapsulated in a packet as specified above. Packetization allows for
|
|
convenient error detection and recovery and works well with our interprocess
|
|
communication scheme.
|
|
|
|
One implementation note about the packetization has to do with buffering. On
|
|
the Unix host, it is advantageous to encode a packet into a memory buffer and
|
|
then send out that buffer in a single "write" operation. This less operating-
|
|
system overhead (which may or may not be significant) but more importantly,
|
|
it means that the packet will be sent between intermediate communication
|
|
devices as efficiently as possible. On my local Unix system, I connect to
|
|
a terminal server and to my Unix host through that. Performing single-byte
|
|
writes on the Unix host means that the bytes are sent in individual Ethernet
|
|
packets between the Unix host and the terminal server, and encounter more
|
|
overhead and communication delays. When I changed the program to send the
|
|
FX packet in a single operation, a significant performance gain was realized.
|
|
|
|
For receiving data on the Unix host, there isn't much you can do other than
|
|
reading one byte at a time, since the receiver doesn't know when a packet is
|
|
going to end. However, the same problem is not encountered here that was
|
|
encountered with sending data because data that is received by the Unix host
|
|
but not "read" by the user program are buffered and collected, smoothing out
|
|
the system overhead, which is insignificant compared to the modem speed. The
|
|
Unix program used the "stdin" and "stdout" file streams for receiving and
|
|
transmitting data, and sets the tty driver to turn off all line-editing
|
|
features to get at the raw bytes.
|
|
|
|
On the Commodore end, it is advantageous to read data from the modem driver in
|
|
chunks, since the system overhead is significant compared to the modem speed.
|
|
These are small computers that we are driving to the max, you know. Data is
|
|
read from the modem in chunks of up to 255 bytes (whatever is available at the
|
|
time) and processed a byte at a time from the read buffer. The CRC is
|
|
calculated during processing, to avoid doing this on the critical path. The
|
|
CRC calculation is performed as an operation by itself since the overhead is
|
|
very small on fast processors. The character-set translation for text files
|
|
will be performed on the critical path (on the Commodore) since it is more
|
|
convenient to do it at a higher layer in the IPC scheme. The packet- handling
|
|
software is logically at a distinct layer that doesn't have to worry about
|
|
higher layers. The next layer up is logically the RPC layer and then the
|
|
file-transfer layer.
|
|
|
|
5. CLIENT/SERVER OPERATION
|
|
|
|
As discussed previously, the client/server interaction is based on a Remote
|
|
Procedure Call paradigm. Thus, for each operation, the client sends a request
|
|
packet (message) to the server, and the server performs the requested
|
|
operation and sends back a reply (acknowledgement) message to the client.
|
|
|
|
There are eight request/ack interactions that are defined for the protocol:
|
|
two for connection management, three for uploading files, and three for
|
|
downloading files. The client is in charge of the file-exchange session
|
|
and of the error handling.
|
|
|
|
4.1. CONNECTION MANAGEMENT
|
|
|
|
When the client starts up, the first thing that it does is connect to the
|
|
server. The format of the message that it sends is as follows:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_CONNECT ('C')
|
|
1 1 protocol version := 0x01
|
|
2 1 transmit byte size: '7' or '8' bits
|
|
3 - SIZE
|
|
|
|
This is what gets put into the the "payload" portion of the packet. All of
|
|
the messages used in the protocol have an ASCII letter in the first byte that
|
|
identifies what the message type is. Each request has an uppercase letter and
|
|
each acknowledgement has the corresponding lowercase letter.
|
|
|
|
The connection-request message is fairly simple: it includes the protocol
|
|
version number and the number of bits wide that the client thinks that the
|
|
communication channel is. The version number is currently always 0x01 and is
|
|
included for cross-compatibility with future versions of the protocol. The
|
|
channel width is encoded into either a '7' or an '8' ASCII character. The
|
|
client will think that the channel width is seven bits only if you tell it
|
|
this on the command line.
|
|
|
|
When the server receives the connection request, it replies with the following
|
|
message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_CONNECT ('c')
|
|
1 1 protocol version := 0x01
|
|
2 1 transmit byte size: '7' or '8' bits
|
|
3 1 recommended request byte size: '7' or '8' bits
|
|
4 4 server maximum text-upload data size: H/M/M/L word
|
|
8 4 server maximum binary-upload data size: H/M/M/L word
|
|
12 4 server maximum text-download data size: H/M/M/L word
|
|
16 4 server maximum binary-download data size: H/M/M/L word
|
|
20 - SIZE
|
|
|
|
The "protocol version" is what the server is using, currently always 0x01.
|
|
The "transmit byte size" is the size that the user has specified on the
|
|
command line that activated the server, and the "recommended request byte
|
|
size" is a '7' if either the "transmit byte size" of the either the client or
|
|
server is seven bits, or '8' otherwise. This is what should be used for the
|
|
all subsequent messages that are exchanged.
|
|
|
|
The server's reply also includes the maximum packet sizes that it can handle
|
|
for uploading and downloading binary and text files. The client then takes
|
|
the "min" of the server's maximum packet sizes and its own, and uses the
|
|
resulting maximum packet sizes for the rest of the file exchange session. The
|
|
maximum packet sizes in the server's reply are all 32-bit unsigned integers
|
|
that are stored from most-significant to least-significant bytes (big endian
|
|
order). I picked big-endian order because that is the order used most
|
|
commonly in inter-machine protocols.
|
|
|
|
The reason that the client doesn't have to inform the server of the client's
|
|
maximum packet sizes in its connection message is that the maximum packet
|
|
size to use is included with each request to get the next packet of a download
|
|
file. It is sufficient that the client knows the full max-packet information.
|
|
Really, the "transmit byte size" field isn't needed in the server reply
|
|
message either, but I wanted the packet-size fields to be size-aligned.
|
|
|
|
After all of the file exchanging is completed, the client sends the following
|
|
message to terminate the connection and return the server back to its command-
|
|
line mode:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_DISCONNECT ('Q')
|
|
1 - SIZE
|
|
|
|
When the server receives this request, it replies with:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_DISCONNECT ('q')
|
|
1 - SIZE
|
|
|
|
And then exits like it should. Note that once the server exits, it cannot
|
|
accept any more packets, since they would be sent to whatever command shell
|
|
you use on your Unix system, and wouldn't do anything useful, so if the client
|
|
sends the disconnect message but doesn't receive any reply, it will time out
|
|
and tell the user that it couldn't disconnect cleanly from the server. This
|
|
should be a rare occurrence. Anyway, what the user would do then is re-enter
|
|
his terminal program and send Ctrl-X's at the server until it exits like it
|
|
should have.
|
|
|
|
This arrangement allows us to avoid the famous(?) "two armies" problem that is
|
|
inherent in disconnecting two connected processes: there is no "clean" way to
|
|
do it. What systems like Z-Modem and Berkeley Sockets do is to have the
|
|
server wait for a period of time that is longer than N times the timeout
|
|
period of the client so that if there is a retransmission of the disconnection
|
|
request, it likely that it will be received and processed correctly by the
|
|
server. This is the reason (presumably) that Z-Modem does an annoying pause
|
|
of 15 seconds or so after you finish transferring files. I think that my
|
|
solution is much nicer, since the server can exit immediately (even though my
|
|
server delays for 1 second, just so that your shell prompt will be cleanly in
|
|
your modem's ARQ buffer when you re-enter your terminal program, if you have a
|
|
hardware-flow-control modem).
|
|
|
|
4.2. FILE UPLOADING
|
|
|
|
Okay, so between connecting to and disconnecting from the server, actual
|
|
|
|
useful stuff happens, including uploading and downloading files. The
|
|
uploading and downloading requests operate much like the regular file
|
|
operations of open, close, read, and write. Really, the FX protocol makes the
|
|
server program a special kind of file server.
|
|
|
|
When the client decides that it wants to upload a file, it first informs the
|
|
server about this by sending the following message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_UPLOAD_OPEN ('U')
|
|
1 1 data type: 't'=text file, 'b'=binary file: 'd'=directory
|
|
2 4 estimated file size: H/M/M/L word
|
|
6 2 permissions ("-----sgr:wxrwxrwx"), like Unix, H:L
|
|
8 12 modified date: BCD format: <YY:YY:MM:DD:hh:mm:ss:tt:tw:GG:gg:aa>
|
|
20 n filename, null-terminated
|
|
20+n - SIZE
|
|
|
|
The "data type" field tells whether a text or binary file will be uploaded.
|
|
There is a provision for "uploading" a directory entry (as part of uploading
|
|
and downloading entire directory hierarchies), but support for this is not
|
|
implemented yet. Also, it makes no difference to a Unix system whether a file
|
|
contains text or binary data, but it may make a difference to other operating
|
|
systems (like Mess-DOS). The "estimated file size" field isn't really used
|
|
either, but it allows the server to make intelligent decisions about
|
|
pre-allocating space, buffering, etc., if it needed to. However, it is
|
|
currently not filled in by the client, since file-size information is
|
|
difficult to extract from Commodore-DOS. The file size is an unsigned 32-bit
|
|
quantity.
|
|
|
|
The permissions field is currently not supported by the server, but it is
|
|
intended to allow file permissions to be preserved when passing files from one
|
|
system to another. The interpretation of the 16 bits of this field is like it
|
|
is with the Unix operating system: "rwx" bits for the owner, group, and other,
|
|
and execute-as-owner, execute-as-group bits. The owner-id and group-id fields
|
|
aren't included since they are generally not portable across systems, and even
|
|
if they were, we usually want to receive files as our own owner-id and our own
|
|
group-id.
|
|
|
|
The "modification date" field is not currently filled in either, since this
|
|
information is even harder to come across with Commodore-DOS, but when it is,
|
|
it will have a 12-byte BCD format. The "YY:YY:MM:DD:hh:mm:ss" sub-fields
|
|
should be easy enough to figure out, and the "tt:t" fields contain thousandths
|
|
of seconds. The "w" field contains the day of the week, coded as 0-6 for
|
|
Sunday to Saturday, and 7 for "unknown". The "GG:gg" fields contain the
|
|
number of hours and minutes that your time zone is off from GMT. If the
|
|
number is negative (in the western hemisphere), then the regular positive
|
|
number of hours will be used, execept that the 0x80 bit of the hours byte will
|
|
be set. Finally, the "aa" sub-field is used to encode the accuracy of the
|
|
timestamp. The way that it is interpreted is that the time value is accurate
|
|
to plus/minus 2^aa milliseconds. For example, if my clock were accurate to
|
|
within one second, then this field would be set to 10 in BCD (2^10 ==
|
|
1024ms). A value of 99 means "unknown" (or that the clock could be off by
|
|
many billions of billions of years).
|
|
|
|
I decided to go all out in defining the date field so that it will be useful
|
|
in the future when "world consciousness" will be much more important than
|
|
it is today.
|
|
|
|
And last but certainly not least, the filename is encoded in ASCII with a
|
|
trailing zero byte.
|
|
|
|
Upon receiving this request, the server will attempt to create a file
|
|
according to your specifications, and will send back a reply of the form:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_UPLOAD_OPEN ('u')
|
|
1 1 error code: 'y'=successful, 'n'=open unsuccessful
|
|
2 - SIZE
|
|
|
|
The "error code" field tells whether the open operation was successful or
|
|
not. If it was, then the client can continue with uploading its file; if not,
|
|
then that file cannot be uploaded (and that the upload channel doesn't need to
|
|
be closed). It's up to the client whether to go on to the next file, abort,
|
|
or ask the user for help. The client will currently report an error to the
|
|
user and then go onto the next file. Of course, it's likely that whatever
|
|
caused the error in creating the current file will also cause an error in
|
|
creating subsequent files (insufficient access permissions on the current
|
|
directory, disk full, etc.). The server will overwrite any existing file
|
|
with the same name (since asking permission, etc., would require extra
|
|
mechanism, and would probably be a nuisance anyway).
|
|
|
|
If the upload channel is opened successfully, then the packets of upload
|
|
data should be sent to the server one at a time, until all of the data is
|
|
uploaded. The client sends the following message to the server to upload
|
|
a packet of data:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code; REQ_UPLOAD_PACKET ('R')
|
|
1 1 upload sequence number
|
|
2 4 data length: H/M/M/L word
|
|
6 n data
|
|
6+n - SIZE
|
|
|
|
The "upload sequence number", which was described before, is used to make sure
|
|
that retransmissions of packets are detected and handled properly, so that
|
|
each packet of data only appears in the file once. The "data length" field
|
|
tells the number of user data bytes that follow in the packet, and then the
|
|
actual user data bytes appear. The "data length" field is actually redundant,
|
|
but I figured that it would make programming a little easier, and allows
|
|
additional error checking. Normally, each upload-data packet will contain
|
|
the maximum-packet-size number of bytes of user data (according to whether
|
|
text or binary data is being uploaded), except for the last packet, which
|
|
will contain the number of data bytes that are left in the file. However,
|
|
each packet is allowed to contain anywhere from 1 to the maximum-packet-
|
|
size number of bytes: whatever the client wishes to use. Variable-sized
|
|
packets are a Good Thing (TM, Pat. Pend.). You will note that the data-
|
|
size values are also what will be used for the "read" and "write" system
|
|
calls on the client and server, respectively. I/O will be done in big,
|
|
efficient chunks.
|
|
|
|
Upon receiving each upload packet, the server replies with the following
|
|
acknowledgement message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_UPLOAD_PACKET ('r')
|
|
1 1 upload sequence number
|
|
2 - SIZE
|
|
|
|
I don't think that the "sequence number" field is actually necessary here, but
|
|
it is included to allow for future expansion and to provide redundancy for
|
|
protocol-error checking.
|
|
|
|
When the client has uploaded all of the packets of the file currently being
|
|
uploaded, it then sends the following message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_UPLOAD_CLOSE ('V')
|
|
1 - SIZE
|
|
|
|
This will close the upload channel and will finish writing the uploaded file
|
|
to the Unix file system. The server will then respond with the following
|
|
message to acknowledge the request:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_UPLOAD_CLOSE ('v')
|
|
1 4 number of bytes uploaded: H/M/M/L word
|
|
5 - SIZE
|
|
|
|
The "number of bytes" field is actually redundant, but is used for additional
|
|
error checking.
|
|
|
|
4.3. FILE DOWNLOADING
|
|
|
|
Downloading files is analogous to uploading them: first we open the download
|
|
channel/file, then we download the packets, and then we close the download
|
|
channel.
|
|
|
|
To open the download channel, the client sends the following request to the
|
|
server:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_DOWNLOAD_OPEN ('D')
|
|
1 - SIZE
|
|
|
|
To which the server replies with:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_DOWNLOAD_OPEN ('d')
|
|
1 1 data type: '0'=no more files (eom),'t'=text,'b'=bin,'e'=err,'d'=dir
|
|
2 4 estimated file size: H/M/M/L word
|
|
6 2 permissions ("-----sgr:wxrwxrwx"), like Unix, H:L
|
|
8 12 modified date: BCD format: <YY:YY:MM:DD:hh:mm:ss:tt:tw:GG:gg:aa>
|
|
20 n filename, null-terminated
|
|
20+n - SIZE
|
|
|
|
The file information is the same as for opening an upload file, except that
|
|
there are more possible return conditions, and all of the "meta data" fields
|
|
are actually filled in by the Unix host (since this information is actually
|
|
conveniently available via the "stat" system call).
|
|
|
|
If the server replies with a '0' "data type" code, then this means that the
|
|
server has no more files to offer for downloading. The filenames to download
|
|
are taken one at a time, from left to right, from the command line that was
|
|
used to start the server. When the server runs out, then the downloading
|
|
session is complete and the client disconnects (since the client uploads
|
|
its files first).
|
|
|
|
Alternatively, the server could reply with a 'e' code, which means that
|
|
it could not open the next filename given on its command line. An error
|
|
return is generated so that the client can inform the user that the file
|
|
could not be downloaded. This will normally result from the user giving
|
|
a bad filename on the command line. The client will continue the downloading
|
|
process by closing the download channel (below) asking for the next file by
|
|
re-opening the download channel. The download channel needs to be closed
|
|
on this condition since otherwise there would be no way of distinguishing
|
|
retransmissions from new requests at the server.
|
|
|
|
Finally, the server can reply with a 't' or 'b' code ('d' for directories is
|
|
not currently implemented) indicating that the file was correctly opened and
|
|
is either text or binary (as specified on the server's command line). Of the
|
|
meta information about the file, only the filename and file size are currently
|
|
used: the file is named according to the given name, translated to PETSCII and
|
|
truncated to 16 characters, and the file size is reported to the user so that
|
|
he can monitor downloading progress. I am not sure what to do yet about name
|
|
collisions on the Commodore end: either ask the user whether to overwrite the
|
|
file, automatically overwrite the file anyway, or automatically give the file
|
|
a slightly different name and download normally. I think that for the time
|
|
being, I will just overwrite the existing file. This will mean that you'll
|
|
want to be extra careful in putting the filenames onto the correct command
|
|
line (the client's or the server's), although there won't be a problem if the
|
|
file doesn't exist on the machine whose command line you put the name on.
|
|
|
|
When the file handling is all squared away and the download channel is opened,
|
|
the client then sucks packets out of the file until the end of the file is
|
|
reached. The packets are sucked out with the following request:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_DOWNLOAD_PACKET ('S')
|
|
1 1 download sequence number
|
|
2 4 maximum acceptable data length: H/M/M/L word
|
|
6 - SIZE
|
|
|
|
The "download sequence number" is used to distinguish retransmissions from
|
|
requests for new packets, and the client tells the server the "maximum
|
|
acceptable data length" for the reply packet. Although the max-packet
|
|
information is actually static during the connection, I included it here in
|
|
every "read" request since I didn't really want the server to keep that
|
|
particular bit of "state" internally.
|
|
|
|
The server replies to the download-packet request with the following message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_DOWNLOAD_PACKET ('s')
|
|
1 1 download sequence number
|
|
2 4 data length: H/M/M/L word, 0==EOF
|
|
6 n data
|
|
6+n - SIZE
|
|
|
|
This is the only "large" message that the server can produce. It includes the
|
|
sequence number, the number of bytes that are actually included, and the user
|
|
data. The number of data bytes in the packet is allowed to be smaller than
|
|
the number of bytes requested, but this is normally only the case for the last
|
|
packet of the file.
|
|
|
|
To indicate that the end of file has been reached and that no more user data
|
|
is available, the server will return a download packet with zero bytes of user
|
|
data in it. Upon receiving this, the client will close the download channel
|
|
with the following message:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: REQ_DOWNLOAD_CLOSE ('E')
|
|
1 - SIZE
|
|
|
|
And the server will reply with:
|
|
|
|
OFF SIZ DESC
|
|
--- --- -----
|
|
0 1 code: ACK_DOWNLOAD_CLOSE ('e')
|
|
1 4 number of file bytes downloaded: H/M/M/L word
|
|
5 - SIZE
|
|
|
|
The "number of file bytes downloaded" field is redundant but included for
|
|
additional error checking. After closing a file, the client will then ask
|
|
for the next file, or will disconnect if the last file to download was just
|
|
closed.
|
|
|
|
4.4. ERROR HANDLING
|
|
|
|
With all of the server calls except for disconnecting (discussed earlier), the
|
|
is the possibility that either the request message from the client or the
|
|
reply message from the server will become garbled and be dropped by the
|
|
packet-delivery layer of the software. To recover from this, if the client
|
|
detects an extended period of inactivity on the serial line for received data
|
|
(where "extended period" is defined as being "about five seconds"), then the
|
|
client will assume that something went wrong and it will retransmit the
|
|
request.
|
|
|
|
As pointed out way above, there are two possible reasons for a retransmission
|
|
being needed: either the request packet was corrupted and dropped, or the
|
|
reply packet was corrupted and dropped. In the format case, the request
|
|
wasn't processed by the server, but in the latter case, it was. Since we
|
|
don't want the server to perform an file operation twice (this is really
|
|
what the six file-transfer client operations really boil down to from the
|
|
server's perspective), the server must keep four pieces of internal state:
|
|
the last upload sequence number, the last download sequence number, whether
|
|
the upload file is open, and whether the download file is open.
|
|
|
|
If an upload-open request is received and the file to be uploaded is not open,
|
|
the the request must be a new one and the server processes it and sends back a
|
|
reply like normal. If an upload-open request is receive and the upload file
|
|
IS currently open, then it must be the case that the current request is a
|
|
retransmission, so all theat the server needs to do is to give a positive
|
|
reply without performing any internal file operations. The same holds true
|
|
for the download-open call and for both of the close calls (except that the
|
|
operation has already been processed if the file is CLOSED).
|
|
|
|
For the packet-upload and packet-download requests, sequence numbers are used
|
|
to detect duplicates. You will note that these sequence numbers are distinct
|
|
from one another, and, in fact, that the entire upload and download file-
|
|
transfer channels are distinct and independent from each another. This is to
|
|
allow for the future possibility of simultaneous file uploading and
|
|
downloading. In fact, if stream numbers (file descriptors) were added to the
|
|
open/read/write/close requests, then we could have us a full-blown remote-host
|
|
over-the-phone interactive file server. But anywho, sequence numbers start
|
|
from 0x00 for the first packet transferred and increment modulo 256 from
|
|
there.
|
|
|
|
Note that for high-speed data-compression modems (like I have) that already
|
|
include error detection and recovery at a level hidden from the user, the FX
|
|
protocol will work particularly well: there will never be an error, never be a
|
|
timeout delay, and never be a retransmission. And, really, the CRC-32 error
|
|
computation and checking is pretty much a zero cost. But, if something does
|
|
go wrong, outside of the modem-to-modem connection, the FX protocol is right
|
|
there to pick up the pieces and carry on.
|
|
|
|
6. CONCLUSION
|
|
|
|
You'll have to wait to get your hands on the program. The Unix Server
|
|
program is almost 100% (except for a few design changes that I made while
|
|
writing this document), and the ACE program is implemented except for
|
|
the error handling and text conversion. Both programs will be released
|
|
with the next release of ACE, which will be Real Soon Now (TM).
|
|
|
|
Here is my performance testing so far, using my USR Sportster modem over a
|
|
14.4-kbps phone connection, with a 38.4-kbps link to my modem from my C128, to
|
|
my usual Unix host:
|
|
|
|
Using FX to/from the ACE ramdisk, REU:
|
|
|
|
Download 156,260 bytes of ~text: time= 54.1 sec, rate=2888 cps.
|
|
Download 151,267 bytes of tabular text: time= 45.9 sec, rate=3296 cps.
|
|
Download 141,299 bytes of JPEG image: time= 92.5 sec, rate=1528 cps.
|
|
Upload 156,260 bytes of ~text: time= 57.4 sec, rate=2722 cps.
|
|
Upload 151,267 bytes of tabular text: time= 45.3 sec, rate=3339 cps.
|
|
Upload 141,299 bytes of JPEG image: time= 95.0 sec, rate=1487 cps.
|
|
|
|
Using FX to/from my CMD Hard Drive:
|
|
|
|
Download 156,260 bytes of ~text: time= 83.4 sec, rate=1874 cps.
|
|
Download 151,267 bytes of tabular text: time= 75.4 sec, rate=2006 cps.
|
|
Download 141,299 bytes of JPEG image: time=118.2 sec, rate=1195 cps.
|
|
Upload 156,260 bytes of ~text: time= 77.9 sec, rate=2006 cps.
|
|
Upload 151,267 bytes of tabular text: time= 66.2 sec, rate=2285 cps.
|
|
Upload 141,299 bytes of JPEG image: time=114.2 sec, rate=1237 cps.
|
|
|
|
Using DesTerm-128 v2.00 to/from my CMD Hard Drive, Y-Modem:
|
|
|
|
Download 156,260 bytes of ~text: time=189.5 sec, rate= 824 cps.
|
|
Download 151,267 bytes of tabular text: time=180.4 sec, rate= 839 cps.
|
|
Download 141,299 bytes of JPEG image: time=199.9 sec, rate= 707 cps.
|
|
Upload 156,260 bytes of ~text: time=255.1 sec, rate= 611 cps.
|
|
Upload 151,267 bytes of tabular text: time=238.6 sec, rate= 634 cps.
|
|
Upload 141,299 bytes of JPEG image: time=233.0 sec, rate= 606 cps.
|
|
|
|
Using NovaTerm-64 v9.5 to my CMD Hard Drive, Z-Modem, C64 mode:
|
|
|
|
Download 156,260 bytes of ~text: time=245.8 sec, rate= 636 cps.
|
|
Download 151,267 bytes of tabular text: time=230.0 sec, rate= 658 cps.
|
|
Download 141,299 bytes of JPEG image: time=262.6 sec, rate= 538 cps.
|
|
|
|
(There is no Z-Modem uploading support)
|
|
|
|
So there you have it: my simple protocol blows the others away. QED.
|
|
========================================================================
|
|
DESIGN AND IMPLEMENTATION OF A 'REAL' OPERATING SYSTEM FOR THE 128: PART II
|
|
|
|
by Craig S. Bruce <csbruce@ccnga.uwaterloo.ca>
|
|
|
|
0. PREFACE
|
|
|
|
There has been a slight change in plans. I originally intended this article
|
|
to give the design of a theoretical distributed multitasking microkernel
|
|
operating system for the C128. I have decided to go a different route: to
|
|
take out the distributed component for now and implement a real multitasking
|
|
microkernel OS for a single machine and extend the system to be distributed
|
|
later. The implementation so far is, of course, only in the prototype stage
|
|
and the application for it is only a demo. Part III of this series will
|
|
extend this demo system into, perhaps, a usable distributed operating system.
|
|
|
|
1. INTRODUCTION
|
|
|
|
The previous article talked about the general approach to building a
|
|
multitasking microkernel OS for the C128. It is assumed here that you have
|
|
read and understood the previous article. This article goes into the grungy
|
|
details of implementing such a beast. The prototype kernel implementation
|
|
provides system calls to create and "exit" user processes, obtain status
|
|
information, delay execution of a process for a specified period of time,
|
|
and to perform message-passing interprocess communication.
|
|
|
|
Currently, there is no real memory management, no real device drivers, and no
|
|
process-resource reclamation. More "infrastructure" features need to be added
|
|
before a command-shell environment or any such thing could be supported,
|
|
though not toooo many more; the Commodore-Kernal Server in the demo system
|
|
makes the $FFD2 (CHROUT) routine of the Commodore Kernal available to all
|
|
other processes in the demo system. It could easily be modified to provide
|
|
all of the Commodore-Kernal features to the other processes, thereby giving
|
|
us a basic I/O sub-system.
|
|
|
|
There is also no way to dynamically load external programs, so the test
|
|
programs have to be assembled with the kernel code. Loading external programs
|
|
in this type of environment has the requirement that the program will have to
|
|
be relocated upon being loaded to an address that would only be known at load
|
|
time. There are two ways to go on this: load all programs to a fixed address
|
|
or load them to dynamic addresses. If you load them to a fixed address, then
|
|
you can only have one processes loaded and concurrently running on each bank
|
|
of internal memory of the C128 and then demand-swap all of the other processes
|
|
into and out of these (two) slots, presumably from REU memory (any other type
|
|
would be much too slow). IHMO, even with REU memory, this would be too slow,
|
|
especially for a microkernel environment. So, programs will need to be
|
|
loaded to dynamic addresses. Fortunately, I have a program-relocation
|
|
mechanism in the works.
|
|
|
|
The entire kernel and the demo program fits in C128 memory in the slot between
|
|
$1300 and $1BFF of RAM0. The kernel uses storage from $C00-$CFF and
|
|
$2000-$BFFF. The latter section of memory is used up in 768-byte chunks by
|
|
each new process, so there can be a total of 53 concurrently executing user
|
|
processes in the system.
|
|
|
|
2. TEST PROGRAM
|
|
|
|
The test program includes no provisions for user interaction, so it may not
|
|
be something that you can impress your friends with, but I can assure you
|
|
that all kinds multitasking stuff is going on behind the scenes to make
|
|
everything happen.
|
|
|
|
The demo test program creates ten processes. There are five "delay"
|
|
processes, two "blabbering" processes, a Commodore-Kernal-Server process, one
|
|
SID-banging process, and the Null process. (Note that when I use the word
|
|
"kernel" I am referring to the OS that I have written, and when I use the word
|
|
"Kernal", I am referring to the Commodore Kernal in ROM).
|
|
|
|
The purpose of the Commodore-Kernal Server is to receive requests from the
|
|
worker processes to call the CHROUT routine to print a given line of text out
|
|
to the screen. The Kernal server is the only processes that is allowed to
|
|
call the Commodore-Kernal routines. Since it can only process one request
|
|
from a client process at a time, calls to the Commodore Kernal are effectively
|
|
"serialized" (made to happen one after another in time), which is good, since
|
|
things would blow up pretty badly if two accesses the Commodore Kernal were to
|
|
happen concurrently.
|
|
|
|
The "delay" processes, numbered from 1 to 5, each delay for a period of N
|
|
seconds and then request the Kernal Server to print a "I'm alive" message to
|
|
the screen. The number N of seconds to delay is the number of the process.
|
|
You should be able to observe from watching the execution that each delay
|
|
process prints a message to the screen with approximately the correct period
|
|
between messages. Note that while these processes are delaying, they don't
|
|
use any CPU time, so the CPU time is allocated to other processes. If you
|
|
try holding down the C= key to slow the scrolling or run the system in Slow
|
|
mode, you will still notice that the delay processes generate their output
|
|
at approximately the right time.
|
|
|
|
The two "blabbering" processes, named "blabber" and "spinner", continually
|
|
send print messages to the Commodore-Server process. You will observe that
|
|
the messages from each comes pretty much prefectly interleaved with each
|
|
other, and with the output of the delay process, because of the interprocess
|
|
communication scheduling policy: FIFO (first-in, first-out).
|
|
|
|
The SID-banging process runs continuously in the background. It increments
|
|
the 16-bit frequency of voice #1 from $0000 to $ffff and then suspends its
|
|
execution for two seconds and then repeats. A square wave with even pulse
|
|
widths is used. When the SID process delays for two seconds, you will notice
|
|
that the printing operation of the other processes speeds up a bit. This is
|
|
because the SID process is a heavy CPU user while it is active. The amount of
|
|
slow-down of the rest of the system is limited a bit because the SID process
|
|
runs with a slightly lower priority than the other processes in the system
|
|
(which makes the sound increment slightly slower than it otherwise would --
|
|
there's only so much CPU to go around).
|
|
|
|
The Null process is not actually needed in this exercise, but it would
|
|
normally be used to insure that the system always had some process to
|
|
schedule. All that it does is increment the binary value in locations
|
|
$0400-$0403 (on the 40-column screen) whenever it is active. It has the
|
|
lowest priority in the system and never gets to execute unless all of the
|
|
other processes are blocked (i.e., are suspended for some reason).
|
|
|
|
When you get tired of watching the demo, you can just hit the RESTORE key.
|
|
This will cause an NMI which will make the system exit back to BASIC. The
|
|
system does not have the ability to handle external events (like key strokes)
|
|
at this time. A couple of locations on the 40-column screen are used for
|
|
status information, so you will want to run the demo on the 80-column screen.
|
|
|
|
3. PROCESS CONTROL
|
|
|
|
A process is a user program that is in an active state of execution. A
|
|
process is periodically given a certain amount of CPU time to execute its
|
|
code, and then CPU attention is taken away from it to execute other
|
|
processes. This may sound like you're simply making N processes run N times
|
|
slower, and this is true in the worst case, but the normal case is that many
|
|
processes in the system will be blocked (for whatever reason) and will not
|
|
require any more CPU time until they wake up again (for whatever reason).
|
|
Therefore, multitasking is a "winnable" proposition.
|
|
|
|
In our system, the process that the CPU is currently executing is changed
|
|
every 1/60 of a second. This is a convenient "quantum" period for a number of
|
|
reasons, including the fact that, thanks to the MMU of the C128, "context
|
|
switching" can be efficiently performed this quickly.
|
|
|
|
3.1. PROCESS-CONTROL CALLS
|
|
|
|
There are six kernel calls that deal with process control:
|
|
|
|
CALL NAME INPUT ARGUMENTS RETURN VALUES
|
|
----------- ------------------------------ -------------------------------
|
|
Create ( .AY=address, .X=priority ) .AY=newPid, .CS=err(.A=errcode)
|
|
Exit ( .A=code, .X=$00 ) <none>
|
|
MyPid ( ) .AY=pid
|
|
MyParentPid ( ) .AY=parentPid
|
|
Suspend ( ) <none>
|
|
Delay ( .AY=jiffies ) <none>
|
|
|
|
3.1.1. CREATE
|
|
|
|
The Create() kernel call is used to create a new process. The first input
|
|
argument is the code address, and it is passed in the .AY register (.A is
|
|
loaded with the low byte of the address, and the .Y register is loaded with
|
|
the high byte of the address--.AY for short). The code must be present in
|
|
memory and be ready to be executed, since there is no facility for loading
|
|
external programs. Also, if the code is not re-entrant, then there must be no
|
|
other process already executing it or things will likely blow up. Re-entrant
|
|
code is code that can be executed by multiple processes simultaneously without
|
|
conflicts, essentially because there are no global variables that could be
|
|
banged on by more than one process at a time.
|
|
|
|
The priority argument is the priority to execute the new process at. Valid
|
|
values for this argument are on the range 0 to 127. The system keeps a list
|
|
at all times of all the processes that are ready to execute, called the "ready
|
|
list". The way that the scheduling works is that a pointer to the "active"
|
|
process is kept (the one that is currently executing) and this pointer cycles
|
|
through the ready list, trying to activate each process in turn, every 1/60 of
|
|
a second. This is roundrobin scheduling. The priority of a process
|
|
determines the number of cycles that the active-process pointer has to take
|
|
through the list before the process is activated. So, if a process has
|
|
priority 1, then it will be activated on every round; if it has a priority of
|
|
2, then it will be activated on every second round; and if it has a priority
|
|
of 86, then it will be activated only on every 86th round through the ready
|
|
list. The higher the priority value, the slower the process executes. This
|
|
policy gives a fair allocation of the CPU to the various processes in the
|
|
system.
|
|
|
|
Normally, foreground processes (ones that perform actions right in front of
|
|
the user's face) should have a priority of 1, and background processes should
|
|
have a relatively lower priority. A priority value of 0 for a process means
|
|
that when the process is activated, it will not be deactivated again until it
|
|
blocks for some reason. This priority level should be reserved for urgent
|
|
computations that block often, since it has the potential to starve out the
|
|
rest of the system. The Null process executes at a special priority level
|
|
(255) that makes it so that it will only be activated if there are no other
|
|
processes in the ready list.
|
|
|
|
The Create() call returns with the carry flag clear and the process id of the
|
|
newly created process in the .AY register upon success, or returns with the
|
|
carry flag set and an error code in the .A register upon failure. I do not
|
|
have the complete list of error conditions figured out a this time, but errors
|
|
will usually happen on a call like this because of a lack of resources
|
|
(memory) for the kernel's internal data structures. Upon successful return,
|
|
the child process is created, made ready, and may be activated by the system
|
|
at any time. The first instruction to be executed by the child will be at the
|
|
value given for the code address.
|
|
|
|
The newly created process will have a clear individual stack, except for a
|
|
couple of bytes on the very bottom of it (high addresses), and a clear
|
|
individual zeropage. Processes are allowed to make full use of every location
|
|
in their zeropage, except for the I/O registers at locations 0 and 1, and full
|
|
use of their stack, except that they must make sure that about a dozen bytes
|
|
are available on the stack at all times in case an interrupt happens.
|
|
|
|
3.1.2. EXIT
|
|
|
|
The Exit() kernel call is used to remove the current process from the system.
|
|
There are two input arguments: the .A register contains the return code that
|
|
will be made available to the parent process if it is interested (though not
|
|
in the current implementation), and a value in the .X register, which must
|
|
currently be $00. I haven't figured out exactly how the exit mechanism should
|
|
work yet and it currently only has a minimal implementation. The call does
|
|
make the kernel reclaim the resources that were allocated to the process yet,
|
|
although this functionality will be needed in any real operating system.
|
|
|
|
There is no return value from the Exit() call, because the call never returns.
|
|
The semantics of the call is the the process calling Exit() will never be made
|
|
active again. All processes should call Exit() when they are finished
|
|
executing, or they can achieve the same result by executing an RTS instruction
|
|
at the end of their main routine. The kernel pushes the address of an Exit()
|
|
stub routine onto the top of the stack of a user process when it is created,
|
|
and the user process will exit with a return code of $00 in this case.
|
|
|
|
3.1.3. MY_PID
|
|
|
|
The MyPid() kernel call is used to return the process identifier of the
|
|
current process. This call is very simple, takes no arguments, and executes
|
|
very quickly. The return value is the process id of the calling process and
|
|
is returned in the .AY register. This call cannot fail, because the current
|
|
process must exist in order to make the call in the first place, so there is
|
|
no error-return condition.
|
|
|
|
3.1.4. MY_PARENT_PID
|
|
|
|
The MyParentPid() kernel call is used to return the process identifier of the
|
|
parent process of the current process (i.e., the process that created the
|
|
current process). This call is simple, takes no arguments, and executes
|
|
quickly, very much like the MyPid() call. No error returns are possible, and
|
|
the process id of the parent to the current process is returned in the .AY
|
|
register. But note: it is not guaranteed that the parent process will still
|
|
exist either before or after the current process makes this call; it may have
|
|
Exit()ed. I may re-think this semantic.
|
|
|
|
This call is useful for setting up interprocess communication between a child
|
|
process and its parent.
|
|
|
|
3.1.5. SUSPEND
|
|
|
|
The Suspend() kernel call is used to suspend the execution of the currently
|
|
executing process for an indefinite period of time. Currently, this period of
|
|
time is forever, since there is no corresponding "Resume" system call that
|
|
another process can call in order to wake up the process that suspended
|
|
itself. The reason that this call is made available is because the guts of
|
|
what it does is required by other kernel operations, and the cost of making
|
|
this call user-accessible was three 6502 instructions. This call may be
|
|
retracted in the future, since it may cause programmers to do bad things.
|
|
|
|
The call takes no arguments, returns no values, and currently, will never
|
|
return at all, much like Exit().
|
|
|
|
3.1.6. DELAY
|
|
|
|
The Delay() kernel call is used to suspend the execution of the current
|
|
process for a user-specified period of time. The delay period is given in
|
|
units of jiffies (1/60ths of a second). The unsigned 16-bit delay period is
|
|
passed in in the .AY registers, giving a maximum possible delay period of
|
|
about 18 minutes. If a user process requests to delay for a period of zero
|
|
jiffies, its execution will not be suspended at all and the Delay() primitive
|
|
will return immediately.
|
|
|
|
Since there may be other processes in the system doing things when the current
|
|
processes wakes up after doing a delay, you can think of the process delaying
|
|
for "at least" the period of time that you specify. Actually, to muddy things
|
|
even more, your process will always go to sleep at a moment in time that is
|
|
inbetween two ticks of the jiffy clock, so the first "jiffy" that your process
|
|
waits may actually be any period between a couple of microseconds to almost a
|
|
full jiffy, with a statistical average of half a jiffy. This is an artifact
|
|
of any coarse-tick-based mechanism.
|
|
|
|
To muddy things again, the jiffy ticks, which are currently based on VIC
|
|
raster interrupts (one per screen update), may not be processed immediately
|
|
when they occur, since the IRQ may be delayed by a small period of time if
|
|
interrupts are disabled in the processor status register when the jiffy tick
|
|
happens. And finally, you should note that you will have a difficult time
|
|
using this call for true "real time" periodic operations, like performing some
|
|
specific task precisely every tenth of a second, since the call specifies a
|
|
period to delay for, rather than a time to wake up at. The actual period of
|
|
your process' activations will be determined by the waiting time plus the time
|
|
skew caused by the processing that your process does. A DelayUntil() call
|
|
easily could be implemented, if I figure that it will be needed for anything.
|
|
|
|
Currently, the scheduling policy is to make processes active immediately after
|
|
they are awakened, so this makes the activities of other processes less of a
|
|
worry to accurate timing. Unix does a similar thing by giving a freshly
|
|
awakened process a temporarily high priority, since it is probably likely that
|
|
the process will do some small think and then block again. This policy
|
|
statistically improves concurrency.
|
|
|
|
3.2. PROCESS CONTROL BLOCKS
|
|
|
|
A Process Control Block (PCB) is the data structure that the kernel keeps the
|
|
information that it needs to know about a process in. A Process identifier
|
|
(pid) is actually the RAM0 address of the process control block of a process,
|
|
for convenience, though this will have to change later. The fields of the
|
|
process control block are shown here, organized into classes:
|
|
|
|
OFF SIZ CLASS LABEL
|
|
--- --- ----- ------
|
|
0 2 queue pcbNext
|
|
2 2 queue pcbPrev
|
|
4 1 queue pcbIsHead
|
|
5 1 queue pcbQCount
|
|
6 1 ctxt pcbSP
|
|
7 1 ctxt pcbStackPage
|
|
8 1 ctxt pcbZeroPage
|
|
9 1 ctxt pcbD506
|
|
10 1 sched pcbPriority
|
|
11 1 sched pcbCountdown
|
|
12 2 sched pcbWakeupTime
|
|
12 2 ipc pcbSendMsgPtr (overlap)
|
|
12 2 ipc pcbRecvMsgPtr (overlap)
|
|
14 2 ipc/q pcbSendQHead
|
|
16 2 ipc/q pcbSendQTail
|
|
18 1 ipc/q pcbSendQFlag
|
|
19 1 ipc/q pcbSendQCount
|
|
20 2 ipc pcbBlockedOn
|
|
22 2 ipc pcbReceiveFrom
|
|
24 2 proc pcbParent
|
|
26 1 proc pcbState
|
|
27 - - SIZE
|
|
|
|
3.2.1. QUEUE-CLASS FIELDS
|
|
|
|
The first four fields, of the class "queue" are used for maintaining a process
|
|
control block in queues with other PCBs. Some general-purpose queue-handling
|
|
routines have been written to make queue management easier: QueueInit(),
|
|
QueueInsert(), and QueueUnlink(). Each queue has a head node, and the nodes
|
|
in a doubly linked circular order. This means that each node in the queue has
|
|
a forward ("pcbNext") and a backward ("pcbPrev") pointer and that the first
|
|
node points back to the head and the last node in a list points forward to the
|
|
head. This organization removes all of the quirks of handling null pointers
|
|
from the code. Using a doubly linked organization makes it easy to remove an
|
|
arbitrary node from the middle of a queue.
|
|
|
|
Each node also has a "pcbIsHead" field which is always False (zero) and a
|
|
"pcbQCount" field which is always zero. The head is the same as an entry in
|
|
the queue, except that its "pcbIsHead" field is set to True ($ff) and its
|
|
"pcbQCount" field records the number of nodes that are in the queue at any
|
|
time. The "pcbIsHead" field is checked when scanning a list to tell if you've
|
|
bumped back into the head node again, indicating the end of the list. The
|
|
"pcbQCount" field is very convenient to check to see whether the queue is
|
|
empty or not.
|
|
|
|
All of the processes that are ready to execute in the system are kept in the
|
|
ready queue. The PCB of the Null process acts as the head for this queue, and
|
|
is also an active node in the queue (a small but harmless kluge). The pointer
|
|
to the active process is kept in a kernel-zero-page variable, and sweeps
|
|
through the circularly linked ready-process list to activate new processes.
|
|
The active PCB is not removed from the ready list while it is active.
|
|
|
|
3.2.2. CONTEXT-CLASS FIELDS
|
|
|
|
The next four fields, of the class "ctxt", store the "context" of a process
|
|
that is not stored on the process' stack when it is not executing. These
|
|
fields include space for the stack pointer, the stack page, the zeropage, and
|
|
the contents of the MMU register at location $d506. The stack pointer is
|
|
what was in the SP register of the CPU when the process last paused. The
|
|
stack page and the zeropage values are the values in MMU registers $d505 and
|
|
$d507, respectively; these are the page numbers of the pages in RAM0 memory
|
|
that are allocated to a process. These pages can only be in RAM0 unless
|
|
common memory is disabled, for hardware reasons. I may allow these pages to
|
|
be in either RAM0 or RAM1 if there is a need later. The $d506 register of
|
|
the MMU stores the most-significant bits of the RAM bank that is selected if
|
|
you have expanded internal memory on your 128 (a la TwinCities-128) and the
|
|
bank selection for REU (DMA) operations.
|
|
|
|
The rest of a process' context is stored on its stack. Here is what a
|
|
process' stack looks like just after it has been created:
|
|
|
|
ADDR SP-REL DESCRIPTION
|
|
---- ------ ------------
|
|
$ff sp+09 exitaddr-1.h
|
|
$fe sp+08 exitaddr-1.l
|
|
$fd sp+07 pc.h
|
|
$fc sp+06 pc.l
|
|
$fb sp+05 status register
|
|
$fa sp+04 .A
|
|
$f9 sp+03 .X
|
|
$f8 sp+02 .Y
|
|
$f7 sp+01 $ff00 save
|
|
$f6 sp+00 -empty-
|
|
|
|
The "exitaddr" is the address of the routine in the kernel that will terminate
|
|
a process if it executes an RTS from its main routine. The address is in the
|
|
regular low-high order (although it is pushed on high-low since the stack
|
|
grows downward in memory) but the value pushed is actually one less than the
|
|
address of the routine, because this is what JSR pushes onto the stack and
|
|
this is what RTS expects to find. The "pc" low and high fields give the
|
|
address of the next instruction to be executed by the process when it is
|
|
activated. When the process is first created, this will be the address of the
|
|
first instruction. The "pc" value is the actual address, not one before,
|
|
because this is what a hardware interrupt pushes onto the stack, and this is
|
|
what the RTI instruction expects to find.
|
|
|
|
The "status register", ".A", ".X", and ".Y" fields contain the values to be
|
|
loaded into the corresponding registers inside of the CPU when the process is
|
|
activated. For a new process, these values are all zero.
|
|
|
|
The "$ff00 save" is the value to be loaded into the $ff00 "shadow" register of
|
|
the MMU when the process is activated. This gives the memory context that the
|
|
process is to execute in. As the kernel currently only works with one bank
|
|
configuration (RAM0, NO BASIC ROM, I/O enabled, KERNAL ROM enabled), this is
|
|
the value put here when a process is created.
|
|
|
|
The final field is "-empty-" because the stack pointer in the 6502 points to
|
|
the next location in stack memory that will be used. All other values in the
|
|
stack are relative to this. Upon startup, the stack-pointer field of the
|
|
process control block will be set to $f6, which is what the table above
|
|
shows.
|
|
|
|
The stack contents look exactly the same after a process has been interrupted
|
|
by a hardware interrupt, except that the stack pointer will likely be lower in
|
|
the stack memory, so the absolute addresses in stack memory in the above table
|
|
no longer apply and the "exitaddr" bytes are not part of the interrupt
|
|
context. That things look the same is no coincidence; on startup, we set up
|
|
the stack to make things look as if an interrupt had just occurred, and to
|
|
start a process executing, we execute the code that returns from an interrupt,
|
|
which loads the "context" that is on the stack into the process registers, and
|
|
we are ready to rock.
|
|
|
|
The above stack organization is exactly the same as it is for processing
|
|
interrupts normally using the Commodore-Kernal environment on the 128, and
|
|
this too is no coincidence because the code that sets up the stack like this
|
|
upon an interrupt is burned into the Kernal ROM and there is very little that
|
|
I can do about it. Fortunately, the organization is just fine for our
|
|
purposes.
|
|
|
|
3.2.3. SCHEDULE-CLASS FIELDS
|
|
|
|
The next three fields, of class "sched", are used to schedule the process.
|
|
The "pcbPriority" field gives the relative priority of the process according
|
|
to the scheme already discussed. The "pcbCountdown" field is used to keep
|
|
count of the number of remaining times that the process will have to be
|
|
bypassed in cycling through the ready queue before the process will be
|
|
activated again. When a process gives up the CPU upon the expiration of its
|
|
time quantum, the "pcbCountdown" field is loaded with the pcbPriority of the
|
|
process. When the "pcbCountdown" value reaches zero, the process is selected
|
|
for activation.
|
|
|
|
The "pcbWakeupTime" field is used with the Delay() kernel call to indicate the
|
|
absolute system time when the process should be activated again. The current
|
|
time in the system is kept in a 16-bit kernel variable, and wraps around every
|
|
18.2 minutes. If the process is not currently time-delayed, then the
|
|
WakeupTime field is not used (in fact, the memory may be used to record other
|
|
status information).
|
|
|
|
3.2.4. IPC-CLASS FIELDS
|
|
|
|
The eight eight fields, of classes "ipc" and "ipc/q" are used for interprocess
|
|
communication (message passing). The first two fields, "pcbSendMsgPtr" and
|
|
"pcbRecvMsgPtr", are used for storing temporary values for handling message
|
|
requests; the four "ipc/q"-class fields are used to implement the head of a
|
|
queue of processes that are waiting to communicate with the current process,
|
|
and the "pcbBlockedOn" field indicates which process this process is waiting
|
|
to communicate with, if the current process is waiting. The first two fields
|
|
actually overlap with each other and with the "pcbWakeupTime" field discussed
|
|
earlier. This is okay since none of the fields will store active status
|
|
information at the same time. The last field, "pcbReceiveFrom" is not used at
|
|
this time, but will be used in the future for an primitive to receive a
|
|
message only from a specific process. The interprocess communication is
|
|
discussed in much greater detail later.
|
|
|
|
3.2.5. PROC-CLASS FIELDS
|
|
|
|
The final two fields, of class "proc", store information about the status of
|
|
the process. I guess the same can be said of all the other fields. Anyway,
|
|
the "pcbParent" field indicates which process is the process that created the
|
|
current process, and the return value for the MyParentPid() kernel call is
|
|
taken from this field.
|
|
|
|
The "pcbState" field gives the current state of the process. Here are the
|
|
different possible process states:
|
|
|
|
STATE NAME CODE
|
|
--------------- ----
|
|
STATE_READY $c0
|
|
STATE_SEND $c1
|
|
STATE_RECEIVE $c2
|
|
STATE_REPLY $c3
|
|
STATE_DELAY $c4
|
|
STATE_SUSPENDED $c5
|
|
|
|
The STATE_READY state means that the process is in the ready queue. The
|
|
STATE_SEND, STATE_RECEIVE, and STATE_REPLY states mean that a process is
|
|
waiting for some interprocess communication primitive to be called by the
|
|
process that it is communicating with. The STATE_DELAY state means that the
|
|
process has called the Delay() primitive and is waiting for some period of
|
|
real time to pass before it can be activated again. The STATE_SUSPENDED state
|
|
means that a process has called the Suspend() or Exit() primitive and will
|
|
never be made active again (currently, Suspend() and Exit() mean the same
|
|
thing).
|
|
|
|
The state information is needed for some operations, and will definitely be
|
|
needed by a Kill()-process operation, to find out what state a process is in
|
|
so that it can be removed from any queue or whatever it is in, in order to
|
|
obliterate all information about the process from the system. Currently,
|
|
there is no Kill() call.
|
|
|
|
3.3. TASK CREATION
|
|
|
|
When the user calls for the creation of a new process in the system, lots of
|
|
stuff has to happen. First, memory for the process control block, zero page,
|
|
and stack page must be allocated. Currently, this allocation is performed
|
|
very simply, by keeping a page pointer and incrementing it every time a page
|
|
is allocated. The process control block ends up getting allocated 256 bytes
|
|
even though it actually requires much less than that. Thus, each new process
|
|
chews up 768 bytes of memory space (plus code). Also, there is currently no
|
|
mechanism for recovering the memory allocated to a process, which is okay
|
|
since the mechanism for Exit()ing a process is incomplete too.
|
|
|
|
The address that a PCB gets allocated at is used for the process' PID (process
|
|
identifier). This is particularly useful since the real purpose of a PID is
|
|
to conveniently locate the process control block. This will have to change
|
|
in the future, however, since the PIDs will also have to locate the machine
|
|
that a process is on.
|
|
|
|
Then the process control block must be initialized. It is initialized as
|
|
follows:
|
|
|
|
FIELD SIZ CLASS INITIAL VALUE
|
|
-------------- --- ----- --------------
|
|
pcbNext 2 queue 0
|
|
pcbPrev 2 queue 0
|
|
pcbIsHead 1 queue 0
|
|
pcbQCount 1 queue 0
|
|
pcbSP 1 ctxt $f6
|
|
pcbStackPage 1 ctxt set to newly allocated space
|
|
pcbZeroPage 1 ctxt set to newly allocated space
|
|
pcbD506 1 ctxt $04
|
|
pcbPriority 1 sched set to the given argument
|
|
pcbCountdown 1 sched set to the same value as "pcbPriority"
|
|
pcbWakeupTime 2 sched 0 (overloaded field)
|
|
pcbSendQHead 2 ipc/q set by the QueueInit() function for empty queue
|
|
pcbSendQTail 2 ipc/q set by the QueueInit() function for empty queue
|
|
pcbSendQFlag 1 ipc/q set by the QueueInit() function for empty queue
|
|
pcbSendQCount 1 ipc/q set by the QueueInit() function for empty queue
|
|
pcbBlockedOn 2 ipc 0
|
|
pcbReceiveFrom 2 ipc 0
|
|
pcbParent 2 proc set to the id of the creator process
|
|
pcbState 1 proc STATE_SUSPENDED
|
|
|
|
The values on the top of the stack page are also initialized for the new
|
|
process, as follows:
|
|
|
|
ADDR SP-REL DESCRIPTION INITIAL VALUE
|
|
---- ------ --------------- --------------
|
|
$ff sp+09 exitaddr-1.h high byte of ExitAddr-1: the exit routine
|
|
$fe sp+08 exitaddr-1.l low byte of ExitAddr-1: the exit routine
|
|
$fd sp+07 pc.h high byte of the code-execution address
|
|
$fc sp+06 pc.l high byte of the code-execution address
|
|
$fb sp+05 status register $00
|
|
$fa sp+04 .A $00
|
|
$f9 sp+03 .X $00
|
|
$f8 sp+02 .Y $00
|
|
$f7 sp+01 $ff00 save $0e (Kernal ROM, I/O, rest RAM0)
|
|
$f6 sp+00 -empty- --
|
|
|
|
And now, the new process is ready for action. We insert it into the ready
|
|
queue in the next position after the current process, set its state to
|
|
STATE_READY (actually, both of these operations are performed by the
|
|
MakeReady() function, which is generally useful and is called from a number of
|
|
places) and then we exit back to the calling process, returning the process id
|
|
of the calling process. I should change this a little bit in the future, to
|
|
make it exit to the newly created child process if the priority of the child
|
|
process is greater than the priority of the parent.
|
|
|
|
3.4. CONTEXT SWITCHING
|
|
|
|
Context switching describes the procedure of switching control of the
|
|
processor from a user process to the kernel and then switching control back to
|
|
a user process. Normally, there is only one "style" of context switching in a
|
|
system, but for a couple of design reasons, BOS actually has three "styles" of
|
|
context switching: IRQ switching, JSR switching, and quick JSR switching.
|
|
IRQ-style switching is the one type normally implemented in operating systems
|
|
for other architectures, so it will be the one that we cover first.
|
|
|
|
IRQ-style context switching involves saving the full context of a process onto
|
|
its stack and into its process control block, switching into the kernel, doing
|
|
work, switching back out of the kernel, and reloading the full context of a
|
|
user process and activating to it. All of the work of saving and restoring
|
|
the the stack portion of a process' context is handled by the ROM routines for
|
|
IRQ (and NMI and BRK) handling. All we have to do is locate the current
|
|
process control block, save the zero-page, stack-page, stack-pointer, and
|
|
$d506 registers into the PCB, and load a $00 into the zero-page MMU register
|
|
to switch to the kernel's zeropage (where some of the kernel's variables are
|
|
stored). Note that the interrupt will be executed using the user process'
|
|
stack; therefore, enough space should always be available on user stacks to
|
|
handle this system overhead.
|
|
|
|
When we are done processing the interrupt, we execute the priority-management
|
|
algorithm that was described earlier to select the next process to activate,
|
|
and then restore the zero-page, stack-page, stack-pointer, and $d506 registers
|
|
and execute the ROM stack-handling code for exiting from an interrupt. Note
|
|
that there's a chance that we might well be exiting to a different user
|
|
process from the one that was active when the interrupt occurred. There
|
|
aren't many registers to save and restore, so context switching has a fairly
|
|
low overhead, so there is no problem in doing it (at least) sixty times a
|
|
second.
|
|
|
|
JSR-style context switching is pretty much the same as IRQ-style context
|
|
switching, except that the stack will not have most of the processor registers
|
|
already saved on it; it will only have the return address that performed the
|
|
JSR. Immediately upon entering the kernel, interrupts are disabled to prevent
|
|
all sorts of bad things from happening. Then a function is called,
|
|
EnterKernel(), which will pull the return address of the process that called
|
|
the JSR off the stack and increment it by one (since we will be exiting by
|
|
using an RTI instruction rather than an RTS) and saves the other processor
|
|
registers onto the stack in the same way that the interrupt-handling code in
|
|
ROM would. Then we save the four additional registers into the PCB as before,
|
|
activate the kernel zeropage, and we are switched in.
|
|
|
|
This style of context switching is used for kernel calls that will cause the
|
|
calling process to block (like a non-zero Delay()). It would have been
|
|
possible to organize the kernel calls to be entered by executing a BRK
|
|
instruction, which would have caused the stack to be already set up in the
|
|
same way as with IRQ interrupts, but I decided against this for two reasons:
|
|
efficiency, it would have been slower to do this, and debugging (security?),
|
|
since I only want the BRK condition to signal a bug in the code. The exit
|
|
from this type of context switch is the same as for the IRQ style of context
|
|
switch, since things are rigged to end up looking the same on the stack. This
|
|
is a good thing, since the action that will cause a Delay()ed process to be
|
|
re-activated will, in fact, be an IRQ interrupt.
|
|
|
|
Quick JSR-style context switching is used for kernel calls that will not block
|
|
or cause a new process to be activated when they finish, such as MyPid() or
|
|
(currently) Create(). No context has to be saved since the function will get
|
|
in and out very quickly; all we have to do is switch to the kernel's zeropage
|
|
and then switch back to the user's zeropage before exit.
|
|
|
|
There's one more note to make about return values. For the quick JSR-style
|
|
context switch, there is no problem with return values, since we just have to
|
|
load them into the processor registers and exit. With the full JSR-style
|
|
context switch, the return values have to be put onto the user stack into the
|
|
positions in the stack memory the hold the processor register contents, since
|
|
these values will be what are restored into the processor immediately upon the
|
|
return to the user process. There are no return values associated with the
|
|
IRQ style of context switching (and there'd better not be), since an interrupt
|
|
can happen at any point in the execution of a user process.
|
|
|
|
3.5. DELAY PRIMITIVE
|
|
|
|
There are two complementary halves to the implemention of the Delay()
|
|
primitive: the half that is called by the user and causes a process to go to
|
|
sleep, and the half that wakes up a sleeping process at the correct time.
|
|
This latter half is executed by the 60-Hz system interrupt.
|
|
|
|
3.5.1. USER HALF OF THE DELAY PRIMITIVE
|
|
|
|
The first thing that the user half of the Delay() primitive does is check to
|
|
see if the delay period is zero jiffies. If it is, then the primitive returns
|
|
immediately to the calling process without rescheduling (without skipping to
|
|
the next ready process in line). I may change this semantic, because it is
|
|
often useful to have a primitive that yeilds process execution to the next
|
|
ready process without actually blocking the current process.
|
|
|
|
If the delay period is longer than zero jiffies, then the current process is
|
|
suspended and removed from the ready queue, and the absolute time that the
|
|
process is to be reawakened is calculated and put into the "pcbWakeupTime"
|
|
field of the PCB for the current process. The absolute wakeup time is
|
|
calculated, of course, by adding the number of jiffies to delay to the current
|
|
absolute time, which is maintained by the system and incremented on every
|
|
(60 Hz) system interrupt.
|
|
|
|
Then the current process control block is inserted into the delay queue at the
|
|
correct position. The delay queue is a queue (implemented in the standard
|
|
way) of process control blocks for processes which are asleep, ordered by the
|
|
absolute wakeup time of each process such that the process that will be
|
|
awakened at the nearest time in the future is at the head of the list and that
|
|
the process which will be awakened at the farthest point in the future is at
|
|
the tail. The following diagram gives an example:
|
|
|
|
CurrentTime = 2016
|
|
|
|
+---------+ +---------+ +---------+ +---------+
|
|
--->| Proc A |----->| Proc B |----->| Proc C |----->| Proc D |
|
|
| wakeup: | | wakeup: | | wakeup: | | wakeup: |
|
|
<---| @ 2345 |<-----| @ 2765 |<-----| @ 54999 |<-----| @ 441 |
|
|
+---------+ +---------+ +---------+ +---------+
|
|
(ct+5.5sec) (ct+12.5sec) (ct+14.7min) (ct+17.8min)
|
|
|
|
There is a rub here: only 16 bits are used for storing times, which equals
|
|
about 18.2 minutes, so we have to worry about time quantities overflowing and
|
|
wrapping around. For example, if the current time is 48232 and a process
|
|
wants to sleep for 18000 jiffies (5 minutes), then its wakeup time would be at
|
|
696 jiffies, accounting for the 16-bit wraparound, which is a lower numerical
|
|
value than the current time, or than the wakeup time of any other process that
|
|
will wake up before the current-time wraparound. In fact, all timers have
|
|
this wraparound problem (although with 64-bit times, wraparound periods would
|
|
be expressed in millions of millennia rather than in minutes). Sixteen bits
|
|
is a good number of bits to use, however, because that is the maximum delay
|
|
period (2^16-1).
|
|
|
|
When we insert a new process into the delay queue, we scan the delay queue
|
|
from the head and continue until we find a record that has a time that is
|
|
higher than or equal to the wakeup time of the new process (or we hit the end
|
|
of the queue). Then, we insert the new process immediately before this
|
|
point. To handle the wraparound problem, all comparisons of wakeup times are
|
|
done using 17 bits (well, really 24 bits). For each value in the comparison,
|
|
we add 65536 to it (set its 17th bit) if the value is less than the current
|
|
time. We don't have to worry about the current time changing while we are
|
|
doing this, because interrupts will be disabled for the entire time that
|
|
we are executing the system call, as per usual. Things could go horribly
|
|
wrong anyway if interrupts were not disabled.
|
|
|
|
Okay, so now our delaying process is removed from the ready queue, its
|
|
complete context is saved, and it is put into the delay queue at the right
|
|
spot. So, set the active process pointer to the next ready process in the
|
|
system and finish by activating the next ready process.
|
|
|
|
3.5.2. SYSTEM HALF OF THE DELAY PRIMITIVE
|
|
|
|
During each 60-Hz system interrupt, the current time (jiffy counter) is
|
|
incremented by one. Note that since this timer is only 16 bits wide, it is
|
|
not suitable for keeping track of the current time of day; for this purpose,
|
|
the TOD clocks in the CIA chips should (and will) be used. The jiffy counter
|
|
may also be inaccurate if interrupts are disabled for a long period of time,
|
|
such as they are during some Commodore-Kernal I/O operations.
|
|
|
|
After incrementing the time, the kernel checks to see if any Delay()ed
|
|
processes need to be woken up. If there are no processes in the delay queue,
|
|
then this is a quick check. If there are any processes in the queue, then if
|
|
the wakeup time of the head process is equal to the current time, then that
|
|
process is woken up and this check is performed repeatedly until the condition
|
|
fails, since there may be multiple processes that want to be woken up at the
|
|
same jiffy of absolute time. Note that because of the scheduling for a
|
|
freshly unblocked process, the process that Delay()ed first will be the
|
|
first one activated after it is woken up, if there are multiple processes
|
|
woken up at the start of the same jiffy.
|
|
|
|
3.6. SYSTEM BOOTSTRAPPING
|
|
|
|
Operating systems always have a bootstrapping problem, because you always need
|
|
to use the services of the operating system in order to start it up, but, of
|
|
course, it's not started up yet, chicken and egg, catch-22. So, what usually
|
|
ends up happening is that you just "fake it", start from somewhere, get the
|
|
ball rolling, and snowball up to a fully running system.
|
|
|
|
The first thing that the kernel does is change all of the interrupt vectors
|
|
(IRQ, NMI, and BRK) to my custom routines. I need to cover all of the
|
|
interrupts, since I chave the zero page during the execution of the system,
|
|
and if a BRK or NMI were to happen and be serviced by the Commodore-Kernal ROM
|
|
routines, all hell would break loose. Currently, the NMI and BRK routines
|
|
just clean things up and return to BASIC.
|
|
|
|
Then we initialize the kernel variables, including the delay queue and the
|
|
jiffy counter.
|
|
|
|
And then we fake the creation of the Null process. For the purposes of
|
|
bootstrapping, the Null process doubles as the "Boot" process. Its process
|
|
control block is not allocated in the normal way, either; it is at a fixed
|
|
location, and its PCB doubles as the head of the process list. A kluge here
|
|
and a hack there and the Null process is initialized and "joined in
|
|
progress". Then, the Null process creates the Init process, using a standard
|
|
call to the Create() primitive, and then the Null process goes into an endless
|
|
loop of incrementing the 32-bit value at addresses $400-$403, the first four
|
|
locations of the 40-column screen memory. It doesn't matter whether you run
|
|
BOS with the clock in Fast or Slow mode, except in terms of performance.
|
|
|
|
It is the responsibility of the Init process to start up all of the user
|
|
processes in the user application after Init starts running. In the current
|
|
implementation, Init starts up all of the other processes in the test
|
|
application and then becomes the Commodore-Kernal Server, which is a
|
|
convenient organization, since all of the other processes can find out the pid
|
|
of the Kernal Server merely by calling MyParentPid().
|
|
|
|
4. INTERPROCESS COMMUNICATION
|
|
|
|
In this system, processes are not strictly independent and competitive; many
|
|
must cooperate and comunicate to get work done. To facilitiate this
|
|
interprocess communication (IPC), a particular paradigm was chosen: the Remote
|
|
Procedure Call (RPC) paradigm. RPC is a message-passing scheme that is used
|
|
with the much-hyped Client/Server system-architecture model. Its operation
|
|
parallels the implicit operations that take place when you call a local
|
|
procedure (a subroutine).
|
|
|
|
The RPC message-passing paradigm is also coupled with a shared-memory paradigm
|
|
to offer greater performance for passing around massive amounts of data. All
|
|
processes in the system (and in the entire distributed system when this OS is
|
|
extended) have global access to all of the memory in the system. The coupling
|
|
of the two paradigms is such that you get the best of both worlds: the
|
|
convenence and natural interprocess *coordination* (synchronization) semantics
|
|
of RPC and the convenience and raw performance of shared storage.
|
|
|
|
4.1. MESSAGE-PASSING CALLS
|
|
|
|
The kernel provides three primitives for message passing:
|
|
|
|
CALL NAME INPUT ARGUMENTS RETURN VALUES
|
|
----------- ------------------------------ -------------------------------
|
|
Send ( .AY=msgHead ) .CS=err(.A=errcode)
|
|
Receive ( .AY=msgHead ) .AY=senderPid
|
|
Reply ( .AY=msgHead[msgRet,msgData] ) .CS=err(.A=errcode)
|
|
|
|
These calls will send a message from one process (the client) to another
|
|
process (the server) and wait for a reply, receive a message from another
|
|
process (a client), and reply to a message sent from another process (a
|
|
client) that has been received, respectively.
|
|
|
|
4.1.1. MESSAGE-HEADER DATA STRUCTURE
|
|
|
|
Each of the message-passing primitives requires a pointer to a message-header
|
|
data structure that is stored in the user program's data space. The message
|
|
header must be initialized with appropriate values before a message can be
|
|
sent. Note that this scheme of passing a pointer to a message header allows
|
|
you to have multiple message headers lying around, initialized and ready for
|
|
action, and you can easily pick between them. Here is what a message header
|
|
looks like:
|
|
|
|
OFF SIZ CLASS LABEL
|
|
--- --- ----- ------
|
|
0 2 pid msgTo
|
|
2 2 pid msgFrom
|
|
4 4 buf msgBuf
|
|
8 2 buf msgLen
|
|
10 4 buf msgRepBuf
|
|
14 2 buf msgRepLen
|
|
16 1 data msgOp
|
|
17 1 data msgRet
|
|
18 2 data msgObj
|
|
20 4 data msgData
|
|
24 - - SIZE
|
|
|
|
You should not put too much faith in the offsets of the fields in the data
|
|
structure remaining static; you should always use the label to access the
|
|
fields of the structure, as in:
|
|
|
|
sta myMessageHeader+msgTo+0
|
|
sty myMessageHeader+msgTo+1
|
|
|
|
4.1.1.1. PID-CLASS FIELDS
|
|
|
|
The first two fields, of class "pid", are used to identify the processes
|
|
involved in an RPC interaction. The "msgTo" field is the pid of the process
|
|
that a message is to be/has been sent to, and the "pcbFrom" field is the id of
|
|
the process which a message has been received from. For security reasons, the
|
|
sender does not fill in the "pcbFrom" field; the kernel does after the message
|
|
has been sent and the sender is blocked. (Or else the sender could fake being
|
|
someone else). The "pcbTo" field is used as the destination for when a
|
|
message is being sent and must be filled in with a legitimate value on a send
|
|
operation, and the "pcbFrom" field is used as the destination when a message
|
|
is being replied to, and must be filled in with a legitimate value on a reply
|
|
operation. The "pcbTo" field is the only field of the message header that
|
|
actually needs to have a legitimate value before a message can be sent.
|
|
|
|
4.1.1.1. BUF-CLASS FIELDS
|
|
|
|
The next four fields, of class "buf", point out the send and reply buffers in
|
|
memory and the sizes of each. The send buffer ("msgBuf"/"msgLen") is expected
|
|
to point to a region of near/far memory that contains valid data for a send
|
|
operation, and the reply buffer ("msgRepBuf"/"msgRepLen") is expected to point
|
|
to a valid area of memory for the server to fill in with any bulky result data
|
|
from an RPC request. Each of the message-buffer pointers is four bytes in
|
|
size to allow for future expansion when the kernel will support "far" memory
|
|
that will be accessed through 32-bit pointers. User processes are expected to
|
|
access these "far" buffers directly themselves, through the global shared
|
|
memory. This eliminates the system overhead of uselessly copying bulky data
|
|
from place to place.
|
|
|
|
There are two special notes to make about there "buf" fields. First, they
|
|
don't actually have to be used how they're intended to be used. as long as
|
|
both the client and the server agree on what the contents of these fields are
|
|
supposed to mean. In this respect, the fields can be used to quickly pass
|
|
twelve bytes of completely arbitrary information. This is useful because many
|
|
RPCs only require that a small amount of information be transferred from one
|
|
process to another, or at least that bulky data be passed in only one
|
|
direction (like read or write), so that one of the buffer pointers is free to
|
|
be used quick, tiny data.
|
|
|
|
Second, on the sending side, the "buffer" that is pointed to does not have to
|
|
be a "buffer" at all; it can be an arbitrary data structure that has an
|
|
arbitrary number of pieces, scattered throughout the global memory of the
|
|
system. The only responsibility of the sender is to insure that no one else
|
|
will be attempting to modify the shared data simultaneously while the server
|
|
is accessing it. This scheme is quite ingenious, I think (thank you, thank
|
|
you). (The scheme may appear to have a security leak in the design, but our
|
|
system has no real hardware security anyway).
|
|
|
|
The expected usage of buffers will be for the sender to use near memory for
|
|
the request and reply buffers and access them as regular near memory to
|
|
construct and interpret request and reply messages. The receiver will (in the
|
|
future) access the buffers as far memory (which they may very well be since
|
|
processes will be allowed to execute on different banks of internal memory and
|
|
even on different machines), and may wish to fetch parts of messages into near
|
|
memory for processing. The use of far pointers makes it so that data is
|
|
copied only when necessary, and copied only once.
|
|
|
|
4.1.1.3. DATA-CLASS FIELDS
|
|
|
|
The final four fields, of class "data", are intended to be used to
|
|
conveniently pass small amounts of arbitrary data. This data can be
|
|
arbitrary, but the fields do have a convention that should usually be
|
|
followed, unless both parties agree to an alternative usage.
|
|
|
|
The "msgOp" field is intended to be the "operation code" that a client process
|
|
wishes a server to execute. The "msgRet" field is intended to be the return/
|
|
error code that is returned from the server to the client upon completion of
|
|
an operation. The "msgObj" field is intended to be used by the client to
|
|
indicate which of the server's "objects" the client wishes to perform the
|
|
operation on. And the "msgData" field is intended to contain four bytes of
|
|
arbitrary user data that is passed in with an operation and is passed back
|
|
from the server to give return values. In the spirit of these semantics, the
|
|
data in all of the fields is send with a request, but only the data in the
|
|
"msgRet" and "msgData" fields is passed back in a reply operation. None of
|
|
the other fields are passed back in a reply operation (the field values will
|
|
remain how they were before the send, for the sender). Take special note that
|
|
the "msgRepLen" field will not be passed back; if there is less data returned
|
|
than was asked for by an operation, you will have to encode the "actual"
|
|
reply-buffer length into the "msgData" field.
|
|
|
|
4.1.2. SEND
|
|
|
|
Send() is used to transmit a message to a remote process and get back a reply
|
|
message. The .AY register contains the near-memory address of the message
|
|
header, which must have its "msgTo" field filled in to be the pid of the
|
|
process that the message is being sent to. The sending process will suspend
|
|
its execution while it is waiting for remote process to process its request.
|
|
If there is to be bulky reply data for the request (such as there would be for
|
|
a "read" request to a file server), then space for the reply buffer must be
|
|
allocated and indicated in the message header. The reply-buffer space should
|
|
normally be owned by the sender.
|
|
|
|
If there is an error in passing the message, the the error return will be
|
|
indicated by the carry flag being set and the error code will be returned in
|
|
the .A register. Some possible errors will be, in the future: destination
|
|
process is not valid, and that destination process died before receiving/
|
|
replying to your message. (These conditions are not currently checked). Also
|
|
in the future, this call will work completely transparently for passing
|
|
messages between machines in a network.
|
|
|
|
4.1.3. RECEIVE
|
|
|
|
Receive() is used to receive a message transmitted by a remote process to the
|
|
current process. The receiver will block until another process does a
|
|
corresponding Send() operation, and then the message header sent by the sender
|
|
will be retrieved into the message-header buffer pointed to by the .AY
|
|
register, for this call. No error returns are possible. The pid of the
|
|
sending process will be returned in the .AY register as well as in the
|
|
"msgFrom" field of the receive-message-header buffer. The receiver is then
|
|
expected to eventually call the Reply() primitive to re-awaken the sender.
|
|
The receiver is free to do anything it wants to after receiving a message from
|
|
a process, including receiving messages from other processes. Messages are
|
|
received from other processes in FIFO order.
|
|
|
|
A similar ReceiveSpecific() primitive may be provided in the future. It would
|
|
only accept a message from a specifically named process and would enqueue all
|
|
other messages that are received before the specific message, to be received
|
|
later.
|
|
|
|
4.1.4. REPLY
|
|
|
|
Reply() is used to re-awaken a process that sent a message that was Receive()d
|
|
by the current process. The current process is expected to have set up the
|
|
return information in the reply-message-header buffer and the reply buffer
|
|
area according to the client's wishes before calling the Reply() primitive.
|
|
The near address of the reply-message-header buffer is loaded into the .AY
|
|
register as an argument to the call. Only the "msgFrom", "msgRet", and
|
|
"msgData" fields need to have values. The "msgFrom" field identifies the
|
|
process to send the reply message to, and that process must be in the state of
|
|
waiting for a reply from the Reply()ing process, or an error will be
|
|
returned. An error is indicated by the carry flag being set on return and the
|
|
error code is loaded in the .A register. In the case of an error, no action
|
|
will have been performed by the system.
|
|
|
|
4.2. IMPLEMENTATION
|
|
|
|
The fields of the process control block that are used for message passing
|
|
are restated here:
|
|
|
|
OFF SIZ CLASS LABEL
|
|
--- --- ----- ------
|
|
12 2 ipc pcbSendMsgPtr (overlap)
|
|
12 2 ipc pcbRecvMsgPtr (overlap)
|
|
14 2 ipc/q pcbSendQHead
|
|
16 2 ipc/q pcbSendQTail
|
|
18 1 ipc/q pcbSendQFlag
|
|
19 1 ipc/q pcbSendQCount
|
|
20 2 ipc pcbBlockedOn
|
|
22 2 ipc pcbReceiveFrom
|
|
|
|
The "pcbBlockedOn" field is used to allow Reply() to verify that the pid it is
|
|
instructed to send a reply message to is indeed waiting for a reply from the
|
|
task calling Reply(). The "pcbSendQ*" fields constitute a queue head for a
|
|
list of process control blocks that are waiting to send a message to the
|
|
current process. The "pcbSendMsgPtr" and "pcbRecvMsgPtr" fields are used to
|
|
save the message data parameters of a Send() or Receive() call, respectively,
|
|
when it has to be suspended without a transfer of the message header. When
|
|
the other process involved performs the corresponding operation, the first
|
|
process' header buffer pointer is recovered from its process control block.
|
|
The "pcbReceiveFrom" field is unused at this time.
|
|
|
|
The process states of STATE_SEND, STATE_REPLY, and STATE_RECEIVE are used with
|
|
message passing. The STATE_SEND state means that the current process has sent
|
|
a message to a server process and is waiting for it to do a Receive(). The
|
|
STATE_REPLY state means that the current process has sent a message to a
|
|
server process, the message has been Receive()d, and that the current process
|
|
is waiting for the server process to perform a Reply(). The STATE_RECEIVE
|
|
state means that the current process has performed a Receive() and is waiting
|
|
for some other process to perform a corresponding Send(). These state
|
|
names/meanings may be a bit inconsistent; deal with it.
|
|
|
|
The implementation of the actual Send(), Receive(), and Reply() operations is
|
|
actually quite straight-forward. Both Send() and Receive() have to handle two
|
|
possibile situations: either the other process involved has already performed
|
|
its corresponding operation and is waiting, or it has not. Reply() is
|
|
simplified in that it knows that the sender is already waiting for its reply
|
|
so it can proceed to copy the reply-message-header contents directly.
|
|
|
|
The Send() primitive (will) checks the given destination pid for validity and
|
|
then checks the state of the recipient process. If the recipient process is
|
|
in STATE_RECEIVE, the Send() function copies the message-header contents
|
|
directly to the receive-header buffer of the recipient. The address of the
|
|
receive-header buffer is taken from the "pcbRecvMsgPtr" field of the
|
|
receiver's process control block in this case. The receiver's return value
|
|
(the sending process' pid) is set up (on the receiving process' stack) and the
|
|
receiver is awakened while the sender is put to sleep, in STATE_REPLY state
|
|
(since the receive has already happened, it is waiting for the corresponding
|
|
Reply()).
|
|
|
|
If the recipient process is not in the STATE_RECEIVE state, then the sending
|
|
process will have to wait for the recipient to perform a Receive(). The
|
|
sender's message-header buffer address is stored into its process control
|
|
block, the sender's process control block is linked into the recipient
|
|
process' "pcbSendQ*", and the sender is put to sleep, in the STATE_SEND
|
|
state.
|
|
|
|
The Send() function does not set up the return value for the user's
|
|
system call since that will not be known until another process performs the
|
|
corresponding Reply(). A return value is set up immediately only in the case
|
|
of an error. The possible error returns from Send() are: invalid pid and
|
|
reply too long (in which case the reply is truncated).
|
|
|
|
The Receive() primitive first checks its "pcbSendQ*" to see if any processes
|
|
have already tried to send a message to the receiver. If there is a process
|
|
there, the sender's process control block is removed from the head of the send
|
|
queue then the sender process' state is changed to STATE_REPLY and the sent
|
|
message-header contents (dereferenced by the sender's "pcbSendMsgPtr" pointer)
|
|
are copied into the receiver's message-header buffer. The Receive() primitive
|
|
then exits returning the pid of the sender. No error returns are possible.
|
|
|
|
If there is no process enqueued in the recipient process' "pcbSendQ*", then
|
|
the receiving process is put to sleep in the STATE_RECEIVE state and its
|
|
message-header buffer pointer is copied into its process control block.
|
|
|
|
The Reply() primitive verifies that the destination process is valid (but not
|
|
in the current implementation) and is actually awaiting a reply from the
|
|
replying process. If not, it craps out. Otherwise, it copies the two
|
|
message-header fields and awakens the sender. The return value of the sender
|
|
is (already) set up to be carry-clear (no error) and the Reply() primitive
|
|
returns error-free too.
|
|
|
|
The Exit() kernel call does not currently recover from a process performing a
|
|
Receive() and then Exit()ing before performing the corresponding Reply().
|
|
Some care will have to be taken to insure that all process involved in IPC can
|
|
consistently recover if one of the processes gets blown away, for whatever
|
|
reason (including Exit()). Such consistent recovery has to be carefully
|
|
thought out for any kind of operating system; however, since there are only a
|
|
small number of kernel concepts in this one, consistent recovery is that much
|
|
easier to insure.
|
|
|
|
5. CONCLUSION
|
|
|
|
So there ya have it; the start of a real operating system for the Commodore
|
|
128. What the operating system needs in terms of features is to be extended
|
|
to execute processes on any bank of internal memory, to access far memory, and
|
|
to be distributed so that it will work across multiple hosts. What it needs
|
|
in terms of software is: device drivers, a command shell, utility programs,
|
|
and an assembler that can produce relocatable code. Oh where, oh where shall
|
|
I ever find such software??? ;-)
|
|
------------------------------------------------------------------------------
|
|
APPENDIX A. SOURCE-CODE LISTING
|
|
|
|
The source code follows. Extract everything between the "-----=-----" lines
|
|
and save into a file named "bos.s" (or whatever) and then run it through the
|
|
ACE assembler to generate the executable program (which is also included below
|
|
for your convenience). The ACE assembler is available for free with the
|
|
ACE-128/64 system.
|
|
|
|
I have not gone through and fully documented the source code, since I have
|
|
been sitting on this program for quite a while and am in a rush to get it out
|
|
the door. Besides, the functionality of each important component has already
|
|
been discussed.
|
|
|
|
-----=-----
|
|
;simple multitasking kernel by Craig Bruce, started 25-Oct-1994.
|
|
|
|
;This program is written in the ACE-Assembler format.
|
|
|
|
org $1300
|
|
jmp main
|
|
|
|
;======== declarations ========
|
|
|
|
pcbNext = 00 ;(2) mgmt
|
|
pcbPrev = 02 ;(2) mgmt
|
|
pcbIsHead = 04 ;(1) mgmt
|
|
pcbQCount = 05 ;(1) mgmt
|
|
pcbSP = 06 ;(1) ctxt
|
|
pcbStackPage = 07 ;(1) ctxt
|
|
pcbZeroPage = 08 ;(1) ctxt
|
|
pcbD506 = 09 ;(1) ctxt
|
|
pcbPriority = 10 ;(1) sche
|
|
pcbCountdown = 11 ;(1) sche
|
|
pcbWakeupTime = 12 ;(2) sche (overlap)
|
|
pcbWaitEvent = 12 ;(1) sche (overlap)
|
|
pcbSendMsgPtr = 12 ;(2) sche (overlap)
|
|
pcbRecvMsgPtr = 12 ;(2) sche (overlap)
|
|
pcbSendQHead = 14 ;(2) ipc
|
|
pcbSendQTail = 16 ;(2) ipc
|
|
pcbSendQFlag = 18 ;(1) ipc
|
|
pcbSendQCount = 19 ;(1) ipc
|
|
pcbBlockedOn = 20 ;(2) ipc
|
|
pcbReceiveFrom = 22 ;(2) ipc
|
|
pcbParent = 24 ;(2) proc
|
|
pcbState = 26 ;(1) proc
|
|
pcbSize = 27
|
|
|
|
STATE_READY = $c0
|
|
STATE_SEND = $c1
|
|
STATE_RECEIVE = $c2
|
|
STATE_REPLY = $c3
|
|
STATE_DELAY = $c4
|
|
STATE_SUSPENDED = $c5
|
|
STATE_EVENT = $c6
|
|
|
|
KERN_ERR_OK = $e0
|
|
KERN_ERR_PID_NOT_REPLY = $e1
|
|
|
|
msgTo = 0 ;(2)
|
|
msgFrom = 2 ;(2)
|
|
msgBuf = 4 ;(4)
|
|
msgLen = 8 ;(2)
|
|
msgRepBuf = 10 ;(4)
|
|
msgRepLen = 14 ;(2)
|
|
msgOp = 16 ;(1)
|
|
msgRet = 17 ;(1)
|
|
msgObj = 18 ;(2)
|
|
msgData = 20 ;(4)
|
|
msgSize = 24
|
|
|
|
queueHeadSize = 6
|
|
|
|
nullPcb : buf pcbSize
|
|
delayQueue : buf queueHeadSize
|
|
jiffyTime : buf 2
|
|
|
|
activePid = 02 ;(2)
|
|
p = 04 ;(2)
|
|
q = 06 ;(2)
|
|
pcbPtr = 08 ;(2)
|
|
msgPtr = 10 ;(2)
|
|
pageAlloc = 12 ;(1)
|
|
|
|
;Stack: ($ff) : exitaddr-1.h
|
|
; ($fe) : exitaddr-1.l
|
|
; ($fd) sp+07: pc.h
|
|
; ($fc) sp+06: pc.l
|
|
; ($fb) sp+05: status register
|
|
; ($fa) sp+04: .A
|
|
; ($f9) sp+03: .X
|
|
; ($f8) sp+02: .Y
|
|
; ($f7) sp+01: $ff00 save
|
|
; ($f6) sp+00: -empty-
|
|
|
|
bkBOS = $0e
|
|
bkUser = $0e
|
|
bkSelect = $ff00
|
|
vic = $d000
|
|
sid = $d400
|
|
mmuZeroPage = $d507
|
|
mmuStackPage = $d509
|
|
IrqExit = $ff33
|
|
|
|
; Create ( .AY=address, .X=priority ) : .AY=pid
|
|
; Exit ( .A=code, .X=$00 )
|
|
; MyPid ( ) : .AY=pid
|
|
; MyParentPid ( ) : .AY=parentPid
|
|
; Suspend ( )
|
|
; Delay ( .AY=jiffies ) : .CS=err
|
|
; Send ( .AY=msgBuf ) : .CS:.A=err
|
|
; Receive ( .AY=msgBuf ) : .AY=senderPid
|
|
; Reply ( .AY=msgBuf[msgRet,msgData] ) : .CS:.A=err
|
|
|
|
;======== kernel code ========
|
|
|
|
main = *
|
|
sei
|
|
;** entry
|
|
lda #bkBOS
|
|
sta bkSelect
|
|
;** set interrupt vectors
|
|
lda #<IrqHandler
|
|
ldy #>IrqHandler
|
|
sta $0314
|
|
sty $0315
|
|
lda #<BrkHandler
|
|
ldy #>BrkHandler
|
|
sta $0316
|
|
sty $0317
|
|
lda #<NmiHandler
|
|
ldy #>NmiHandler
|
|
sta $0318
|
|
sty $0319
|
|
;** initialize delay queue
|
|
lda #0
|
|
sta jiffyTime+0
|
|
sta jiffyTime+1
|
|
lda #<delayQueue
|
|
ldy #>delayQueue
|
|
sta q+0
|
|
sty q+1
|
|
jsr QueueInit
|
|
;** initialize null/boot process
|
|
lda #<nullPcb
|
|
ldy #>nullPcb
|
|
sta nullPcb+pcbNext+0
|
|
sty nullPcb+pcbNext+1
|
|
sta nullPcb+pcbPrev+0
|
|
sty nullPcb+pcbPrev+1
|
|
sta activePid+0
|
|
sty activePid+1
|
|
lda #$ff
|
|
sta nullPcb+pcbIsHead
|
|
lda #0
|
|
sta nullPcb+pcbQCount
|
|
lda #>$2000
|
|
sta pageAlloc
|
|
lda #STATE_READY
|
|
sta nullPcb+pcbState
|
|
lda #2
|
|
sta nullPcb+pcbPriority
|
|
cli
|
|
jmp Null
|
|
|
|
Null = *
|
|
;** create init process
|
|
lda #<Init
|
|
ldy #>Init
|
|
ldx #1
|
|
jsr Create
|
|
;** go into endless loop
|
|
- inc $0400
|
|
bne +
|
|
inc $0401
|
|
bne +
|
|
inc $0402
|
|
bne +
|
|
inc $0403
|
|
+ jmp -
|
|
|
|
NmiHandler = *
|
|
BrkHandler = *
|
|
Shutdown = *
|
|
;** restore interrupt vectors
|
|
sei
|
|
lda #<$fa65
|
|
ldy #>$fa65
|
|
sta $0314
|
|
sty $0315
|
|
lda #<$fa40
|
|
ldy #>$fa40
|
|
sta $0318
|
|
sty $0319
|
|
ldx #250
|
|
txs
|
|
lda #$00
|
|
sta mmuZeroPage
|
|
sta mmuZeroPage+1
|
|
ldx #$01
|
|
stx mmuStackPage
|
|
sta mmuStackPage+1
|
|
lda #%00000100
|
|
sta $d506
|
|
cli
|
|
jmp $4db7
|
|
|
|
zpSave : buf 1
|
|
|
|
createAddr : buf 2
|
|
createPriority : buf 1
|
|
createZeropage : buf 1
|
|
createStack : buf 1
|
|
createPcb : buf pcbSize
|
|
|
|
Create = * ;( .AY=address, .X=priority ) : .AY=pid
|
|
sei
|
|
;** switch in
|
|
sta createAddr+0
|
|
sty createAddr+1
|
|
stx createPriority
|
|
lda mmuZeroPage
|
|
sta zpSave
|
|
lda #$00
|
|
sta mmuZeroPage
|
|
;** allocate resources
|
|
lda #$00
|
|
ldy pageAlloc
|
|
sta pcbPtr+0
|
|
sty pcbPtr+1
|
|
iny
|
|
sty createZeropage
|
|
iny
|
|
sty createStack
|
|
iny
|
|
sty pageAlloc
|
|
cpy #>$c000
|
|
bcc +
|
|
brk ; recover gracefully from the condition of running out of memory
|
|
+
|
|
;** initialize pcb
|
|
;** pcbNext ;(2) mgmt := 0
|
|
;** pcbPrev ;(2) mgmt := 0
|
|
;** pcbIsHead ;(1) mgmt := 0
|
|
;** pcbQCount ;(1) mgmt := 0
|
|
;** pcbSP ;(1) ctxt := $f6
|
|
;** pcbStackPage ;(1) ctxt := new
|
|
;** pcbZeroPage ;(1) ctxt := new
|
|
;** pcbD506 ;(1) ctxt := $04
|
|
;** pcbPriority ;(1) sche := given
|
|
;** pcbCountdown ;(1) sche := priority
|
|
;** pcbWakeupTime ;(2) sche := 0
|
|
;** pcbSendQHead ;(2) ipc := QueueInit
|
|
;** pcbSendQTail ;(2) ipc := QueueInit
|
|
;** pcbSendQFlag ;(1) ipc := QueueInit
|
|
;** pcbSendQCount ;(1) ipc := QueueInit
|
|
;** pcbBlockedOn ;(2) ipc := 0
|
|
;** pcbReceiveFrom ;(2) ipc := 0
|
|
;** pcbParent ;(2) proc := creator
|
|
;** pcbState ;(1) proc := STATE_SUSPENDED
|
|
ldx #pcbSize-1
|
|
lda #$00
|
|
- sta createPcb,x
|
|
dex
|
|
bpl -
|
|
lda #$f6
|
|
sta createPcb+pcbSP
|
|
lda createStack
|
|
sta createPcb+pcbStackPage
|
|
lda createZeropage
|
|
sta createPcb+pcbZeroPage
|
|
lda #$04
|
|
sta createPcb+pcbD506
|
|
lda createPriority
|
|
sta createPcb+pcbPriority
|
|
sta createPcb+pcbCountdown
|
|
lda activePid+0
|
|
ldy activePid+1
|
|
sta createPcb+pcbParent+0
|
|
sty createPcb+pcbParent+1
|
|
lda #STATE_SUSPENDED
|
|
sta createPcb+pcbState
|
|
ldy #pcbSize-1
|
|
- lda createPcb,y
|
|
sta (pcbPtr),y
|
|
dey
|
|
bpl -
|
|
lda pcbPtr+0
|
|
clc
|
|
adc #pcbSendQHead
|
|
sta q+0
|
|
lda pcbPtr+1
|
|
adc #0
|
|
sta q+1
|
|
jsr QueueInit
|
|
|
|
;** initialize new stack
|
|
;** Stack: ($ff) : exitaddr-1.h := >ExitAddr
|
|
;** ($fe) : exitaddr-1.l := <ExitAddr
|
|
;** ($fd) sp+07: pc.h := >Addr
|
|
;** ($fc) sp+06: pc.l := <Addr
|
|
;** ($fb) sp+05: status register := $00
|
|
;** ($fa) sp+04: .A := $00
|
|
;** ($f9) sp+03: .X := $00
|
|
;** ($f8) sp+02: .Y := $00
|
|
;** ($f7) sp+01: $ff00 save := $0e
|
|
;** ($f6) sp+00: -empty-
|
|
lda #$00
|
|
ldy createStack
|
|
sta p+0
|
|
sty p+1
|
|
ldy #$f6+1
|
|
lda #bkUser
|
|
sta (p),y ;$ff00
|
|
iny
|
|
ldx #4
|
|
lda #$00
|
|
- sta (p),y
|
|
iny
|
|
dex
|
|
bne -
|
|
lda createAddr+0
|
|
sta (p),y
|
|
iny
|
|
lda createAddr+1
|
|
sta (p),y
|
|
iny
|
|
lda #<DefaultExit-1
|
|
sta (p),y
|
|
iny
|
|
lda #>DefaultExit-1
|
|
sta (p),y
|
|
|
|
;** make new process ready
|
|
jsr MakeReady
|
|
|
|
;** switch out
|
|
lda pcbPtr+0
|
|
ldy pcbPtr+1
|
|
ldx zpSave
|
|
stx mmuZeroPage
|
|
clc
|
|
cli
|
|
rts
|
|
|
|
MakeReady = * ;( (pcbPtr)=pcb ) ;after activePid
|
|
ldy #pcbState
|
|
lda #STATE_READY
|
|
sta (pcbPtr),y
|
|
lda #<nullPcb
|
|
ldy #>nullPcb
|
|
sta q+0
|
|
sty q+1
|
|
lda activePid+0
|
|
ldy activePid+1
|
|
sta p+0
|
|
sty p+1
|
|
jsr QueueInsert
|
|
rts
|
|
|
|
QueueInit = * ;( (q)=queueHead )
|
|
lda q+0
|
|
ldy q+1
|
|
sta queueInitVals+pcbNext+0
|
|
sty queueInitVals+pcbNext+1
|
|
sta queueInitVals+pcbPrev+0
|
|
sty queueInitVals+pcbPrev+1
|
|
lda #$ff
|
|
sta queueInitVals+pcbIsHead
|
|
lda #0
|
|
sta queueInitVals+pcbQCount
|
|
ldy #queueHeadSize-1
|
|
- lda queueInitVals,y
|
|
sta (q),y
|
|
dey
|
|
bpl -
|
|
rts
|
|
queueInitVals : buf queueHeadSize
|
|
|
|
QueueInsert = * ;( (q)=queueHead, (p)=nodeToInsertAfter, (pcbPtr)=newItem )
|
|
;** q->count +:= 1
|
|
clc
|
|
ldy #pcbQCount
|
|
lda (q),y
|
|
adc #1
|
|
sta (q),y
|
|
|
|
;** pcbPtr->next := p->next
|
|
ldy #pcbNext
|
|
lda (p),y
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda (p),y
|
|
sta (pcbPtr),y
|
|
|
|
;** pcbPtr->prev := p
|
|
iny
|
|
lda p+0
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda p+1
|
|
sta (pcbPtr),y
|
|
|
|
;** p->next->prev := pcbPtr
|
|
ldy #pcbNext
|
|
lda (p),y
|
|
sta q+0
|
|
iny
|
|
lda (p),y
|
|
sta q+1
|
|
ldy #pcbPrev
|
|
lda pcbPtr+0
|
|
sta (q),y
|
|
iny
|
|
lda pcbPtr+1
|
|
sta (q),y
|
|
|
|
;** p->next := pcbPtr
|
|
ldy #pcbNext
|
|
lda pcbPtr+0
|
|
sta (p),y
|
|
iny
|
|
lda pcbPtr+1
|
|
sta (p),y
|
|
rts
|
|
|
|
QueueUnlink = * ;( (q)=queueHead, (pcbPtr)=node ) ;uses p
|
|
;** pcbPtr->next->prev := pcbPtr->prev
|
|
ldy #pcbNext
|
|
lda (pcbPtr),y
|
|
sta p+0
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta p+1
|
|
ldy #pcbPrev
|
|
lda (pcbPtr),y
|
|
sta (p),y
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta (p),y
|
|
|
|
;** pcbPtr->prev->next := pcbPtr->next
|
|
ldy #pcbPrev
|
|
lda (pcbPtr),y
|
|
sta p+0
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta p+1
|
|
ldy #pcbNext
|
|
lda (pcbPtr),y
|
|
sta (p),y
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta (p),y
|
|
|
|
;** q->count -:= 1
|
|
ldy #pcbQCount
|
|
lda (q),y
|
|
sec
|
|
sbc #1
|
|
sta (q),y
|
|
rts
|
|
|
|
IrqHandler = *
|
|
cld
|
|
lda #bkBOS
|
|
sta bkSelect
|
|
lda vic+$19
|
|
bpl +
|
|
and #1
|
|
bne Sixty
|
|
+ lda $dc0d
|
|
|
|
Sixty = *
|
|
sta vic+$19
|
|
;** save full context
|
|
lda mmuZeroPage
|
|
ldx #$00
|
|
stx mmuZeroPage
|
|
ldy #pcbZeroPage
|
|
sta (activePid),y
|
|
ldy #pcbSP
|
|
tsx
|
|
txa
|
|
sta (activePid),y
|
|
ldy #pcbStackPage
|
|
lda mmuStackPage
|
|
sta (activePid),y
|
|
ldy #pcbD506
|
|
lda $d506
|
|
sta (activePid),y
|
|
|
|
;** process interrupt
|
|
inc jiffyTime+0
|
|
bne +
|
|
inc jiffyTime+1
|
|
+ lda delayQueue+pcbQCount
|
|
beq +
|
|
jsr DelayIrqAwake
|
|
+ nop
|
|
|
|
;** select new process
|
|
- ldy #pcbPriority ;give cur full count
|
|
lda (activePid),y
|
|
iny
|
|
sta (activePid),y
|
|
beq ++
|
|
- ldy #pcbNext ;find next proc
|
|
lda (activePid),y
|
|
tax
|
|
iny
|
|
lda (activePid),y
|
|
stx activePid+0
|
|
sta activePid+1
|
|
ExitKernel = *
|
|
ldy #pcbCountdown
|
|
lda (activePid),y
|
|
beq ++
|
|
sec
|
|
sbc #1
|
|
sta (activePid),y
|
|
beq +
|
|
jmp -
|
|
+ ;check if null process
|
|
ldy #pcbIsHead
|
|
lda (activePid),y
|
|
bpl +
|
|
iny
|
|
lda (activePid),y ;only run null if only proc
|
|
bne --
|
|
+ ;we've got a winner
|
|
|
|
;** restore full context and exit
|
|
ldy #pcbD506
|
|
lda (activePid),y
|
|
sta $d506
|
|
ldy #pcbStackPage
|
|
lda (activePid),y
|
|
sta mmuStackPage
|
|
ldy #pcbSP
|
|
lda (activePid),y
|
|
tax
|
|
txs
|
|
ldy #pcbZeroPage
|
|
lda (activePid),y
|
|
sta mmuZeroPage
|
|
jmp IrqExit
|
|
|
|
DefaultExit = *
|
|
lda #$00
|
|
ldx #$00
|
|
Exit = * ;( .A=code, .X=$00 )
|
|
jmp Suspend
|
|
brk
|
|
|
|
MyPid = * ;( ) : .AY=pid
|
|
lda #$00
|
|
ldx mmuZeroPage
|
|
sta mmuZeroPage
|
|
lda activePid+0
|
|
ldy activePid+1
|
|
stx mmuZeroPage
|
|
clc
|
|
rts
|
|
|
|
MyParentPid = * ;( ) : .AY=parentPid
|
|
lda #$00
|
|
ldx mmuZeroPage
|
|
sta mmuZeroPage
|
|
ldy #pcbParent
|
|
lda (activePid),y
|
|
pha
|
|
iny
|
|
lda (activePid),y
|
|
tay
|
|
pla
|
|
stx mmuZeroPage
|
|
clc
|
|
rts
|
|
|
|
enterKernSave : buf 4
|
|
|
|
EnterKernel = *
|
|
;** set up process stack as if it had performed an interrupt
|
|
;** necessary if process will block
|
|
;** called as a one-level-deep subroutine of the system call
|
|
sta enterKernSave+2
|
|
;** save system-call return address
|
|
pla
|
|
sta enterKernSave+0
|
|
pla
|
|
sta enterKernSave+1
|
|
;** increment user-process return address (rts -> rti)
|
|
pla
|
|
clc
|
|
adc #1
|
|
sta enterKernSave+3
|
|
pla
|
|
adc #0
|
|
pha
|
|
lda enterKernSave+3
|
|
pha
|
|
;** set up processor registers as-is, status $00
|
|
lda #$00
|
|
pha
|
|
lda enterKernSave+2
|
|
pha
|
|
txa
|
|
pha
|
|
tya
|
|
pha
|
|
lda $ff00 ;xxx change for multi-banks
|
|
pha
|
|
;** save info into pcb
|
|
lda mmuZeroPage
|
|
ldx #$00
|
|
stx mmuZeroPage
|
|
ldy #pcbZeroPage
|
|
sta (activePid),y
|
|
dey
|
|
lda mmuStackPage
|
|
sta (activePid),y
|
|
dey
|
|
tsx
|
|
txa
|
|
sta (activePid),y
|
|
ldy #pcbD506
|
|
lda $d506
|
|
sta (activePid),y
|
|
;** restore system-call return address
|
|
;** (continue to use user-process stack)
|
|
lda enterKernSave+1
|
|
pha
|
|
lda enterKernSave+0
|
|
pha
|
|
rts
|
|
|
|
Suspend = * ;( ) ;suspend self
|
|
sei
|
|
jsr EnterKernel
|
|
jsr SuspendSub
|
|
jmp ExitKernel
|
|
|
|
SuspendSub = * ;( activePid ) : activePid, pcbPtr, q=nullPcb
|
|
;** Remove the active pid from the ready queue and set another pid to
|
|
;** active; set pcbPtr to point to the suspended process; and set the
|
|
;** process state to "suspended".
|
|
lda activePid+0
|
|
sta pcbPtr+0
|
|
lda activePid+1
|
|
sta pcbPtr+1
|
|
lda #<nullPcb
|
|
ldy #>nullPcb
|
|
sta q+0
|
|
sty q+1
|
|
jsr QueueUnlink
|
|
ldy #pcbNext
|
|
lda (pcbPtr),y
|
|
sta activePid+0
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta activePid+1
|
|
ldy #pcbState
|
|
lda #STATE_SUSPENDED
|
|
sta (pcbPtr),y
|
|
rts
|
|
|
|
Delay = * ;( .AY=jiffies ) : .CS=err
|
|
cmp #0
|
|
bne +
|
|
cpy #0
|
|
bne +
|
|
clc
|
|
rts
|
|
+ sei
|
|
sta delayTime+0
|
|
sty delayTime+1
|
|
jsr EnterKernel
|
|
jsr SuspendSub
|
|
ldy #pcbState
|
|
lda #STATE_DELAY
|
|
ldy #pcbWakeupTime
|
|
clc
|
|
lda delayTime+0
|
|
adc jiffyTime+0
|
|
sta delayTime+0
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda delayTime+1
|
|
adc jiffyTime+1
|
|
sta delayTime+1
|
|
sta (pcbPtr),y
|
|
lda #0
|
|
rol
|
|
sta delayTime+2
|
|
lda #<delayQueue
|
|
ldy #>delayQueue
|
|
sta q+0
|
|
sty q+1
|
|
sta p+0
|
|
sty p+1
|
|
jsr DelayFindSpot
|
|
jsr QueueInsert
|
|
jmp ExitKernel
|
|
delayTime : buf 3
|
|
pTimeHi : buf 1
|
|
|
|
DelayFindSpot = * ;( (q)=queue, (p)=queueHead, (pcbPtr) ) : p=prevNode
|
|
jsr IncPtrP
|
|
ldy #pcbIsHead
|
|
lda (p),y
|
|
bne DelayFindSpotExit
|
|
ldy #pcbWakeupTime
|
|
lda (p),y
|
|
cmp jiffyTime+0
|
|
iny
|
|
lda (p),y
|
|
sbc jiffyTime+1
|
|
ldx #0
|
|
bcs +
|
|
inx
|
|
+ stx pTimeHi
|
|
dey
|
|
lda delayTime+0
|
|
cmp (p),y
|
|
iny
|
|
lda delayTime+1
|
|
sbc (p),y
|
|
lda delayTime+2
|
|
sbc pTimeHi
|
|
bcs DelayFindSpot
|
|
|
|
DelayFindSpotExit = *
|
|
;xx fall through
|
|
|
|
DecPtrP = * ;( (p) ) : (p):=(p)->prev
|
|
ldy #pcbPrev
|
|
lda (p),y
|
|
tax
|
|
iny
|
|
lda (p),y
|
|
stx p+0
|
|
sta p+1
|
|
rts
|
|
|
|
IncPtrP = * ;( (p) ) : (p):=(p)->next
|
|
ldy #pcbNext
|
|
lda (p),y
|
|
tax
|
|
iny
|
|
lda (p),y
|
|
stx p+0
|
|
sta p+1
|
|
rts
|
|
|
|
DelayIrqAwake = *
|
|
lda delayQueue+pcbNext+0
|
|
ldy delayQueue+pcbNext+1
|
|
sta pcbPtr+0
|
|
sty pcbPtr+1
|
|
ldy #pcbWakeupTime
|
|
lda (pcbPtr),y
|
|
cmp jiffyTime+0
|
|
beq +
|
|
rts
|
|
+ iny
|
|
lda (pcbPtr),y
|
|
cmp jiffyTime+1
|
|
beq +
|
|
rts
|
|
+ lda #<delayQueue
|
|
ldy #>delayQueue
|
|
sta q+0
|
|
sty q+1
|
|
jsr QueueUnlink
|
|
jsr MakeReady
|
|
jmp DelayIrqAwake
|
|
|
|
msgPtrSave : buf 2
|
|
|
|
Send = * ;( .AY=msgBuf ) : .CS:.A=err
|
|
sei
|
|
sta msgPtrSave+0
|
|
sty msgPtrSave+1
|
|
jsr EnterKernel
|
|
jsr SuspendSub
|
|
lda msgPtrSave+0
|
|
ldy msgPtrSave+1
|
|
sta msgPtr+0
|
|
sty msgPtr+1
|
|
ldy #msgTo
|
|
lda (msgPtr),y
|
|
sta q+0
|
|
iny
|
|
lda (msgPtr),y
|
|
sta q+1
|
|
;xx should verify that receiver is a process
|
|
ldy #pcbSendMsgPtr
|
|
lda msgPtr+0
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda msgPtr+1
|
|
sta (pcbPtr),y
|
|
ldy #pcbBlockedOn
|
|
lda q+0
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda q+1
|
|
sta (pcbPtr),y
|
|
ldy #pcbState
|
|
lda (q),y
|
|
cmp #STATE_RECEIVE
|
|
beq SendToReceiverBlocked
|
|
lda #STATE_SEND
|
|
sta (pcbPtr),y
|
|
clc
|
|
lda q+0
|
|
adc #pcbSendQHead
|
|
sta q+0
|
|
bcc +
|
|
inc q+1
|
|
+ ldy #pcbPrev
|
|
lda (q),y
|
|
sta p+0
|
|
iny
|
|
lda (q),y
|
|
sta p+1
|
|
jsr QueueInsert
|
|
jmp ExitKernel
|
|
|
|
SendToReceiverBlocked = *
|
|
lda #STATE_REPLY
|
|
sta (pcbPtr),y
|
|
ldy #pcbRecvMsgPtr
|
|
lda (q),y
|
|
sta p+0
|
|
iny
|
|
lda (q),y
|
|
sta p+1
|
|
jsr CopyMessage
|
|
lda pcbPtr+0
|
|
ldy pcbPtr+1
|
|
ldx q+0
|
|
stx pcbPtr+0
|
|
ldx q+1
|
|
stx pcbPtr+1
|
|
ldx #$00
|
|
clc
|
|
jsr SetReturn
|
|
jsr MakeReady
|
|
jmp ExitKernel
|
|
|
|
setretSave : buf 4
|
|
|
|
SetReturn = * ;( (pcbPtr)=proc, .AXY=regvals, .C=cval ) : (p)=junk
|
|
sta setretSave+2
|
|
stx setretSave+1
|
|
sty setretSave+0
|
|
php
|
|
pla
|
|
and #$01
|
|
sta setretSave+3
|
|
ldy #pcbStackPage
|
|
lda (pcbPtr),y
|
|
sta p+1
|
|
ldy #pcbSP
|
|
lda (pcbPtr),y
|
|
clc
|
|
adc #2
|
|
sta p+0
|
|
ldy #3
|
|
- lda setretSave,y
|
|
sta (p),y
|
|
dey
|
|
bpl -
|
|
rts
|
|
|
|
CopyMessage = * ;( (pcbPtr)=sender, (msgPtr)=sendmsg, (p)=recvmsg )
|
|
ldy #msgFrom
|
|
lda pcbPtr+0
|
|
sta (msgPtr),y
|
|
iny
|
|
lda pcbPtr+1
|
|
sta (msgPtr),y
|
|
ldy #msgSize-1
|
|
- lda (msgPtr),y
|
|
sta (p),y
|
|
dey
|
|
bpl -
|
|
rts
|
|
|
|
Receive = * ;( .AY=msgBuf ) : .AY=senderPid
|
|
sei
|
|
sta msgPtrSave+0
|
|
sty msgPtrSave+1
|
|
lda mmuZeroPage
|
|
pha
|
|
lda #$00
|
|
sta mmuZeroPage
|
|
ldy #pcbSendQCount
|
|
lda (activePid),y
|
|
bne ReceiveFromSender
|
|
pla
|
|
sta mmuZeroPage
|
|
jsr EnterKernel
|
|
jsr SuspendSub
|
|
lda #STATE_RECEIVE
|
|
sta (pcbPtr),y
|
|
ldy #pcbRecvMsgPtr
|
|
lda msgPtrSave+0
|
|
sta (pcbPtr),y
|
|
iny
|
|
lda msgPtrSave+1
|
|
sta (pcbPtr),y
|
|
jmp ExitKernel
|
|
|
|
ReceiveFromSender = * ;( (activePid), (msgPtrSave) )
|
|
lda activePid+0
|
|
ldy activePid+1
|
|
clc
|
|
adc #pcbSendQHead
|
|
bcc +
|
|
iny
|
|
+ sta q+0
|
|
sty q+1
|
|
ldy #pcbSendQHead
|
|
lda (activePid),y
|
|
sta pcbPtr+0
|
|
iny
|
|
lda (activePid),y
|
|
sta pcbPtr+1
|
|
jsr QueueUnlink ;( (q)=queueHead, (pcbPtr)=node ) ;uses p
|
|
ldy #pcbSendMsgPtr
|
|
lda (pcbPtr),y
|
|
sta msgPtr+0
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta msgPtr+1
|
|
lda msgPtrSave+0
|
|
ldy msgPtrSave+1
|
|
sta p+0
|
|
sty p+1
|
|
jsr CopyMessage ;( (pcbPtr)=sender, (msgPtr)=sendmsg, (p)=recvmsg )
|
|
ldy #pcbState
|
|
lda #STATE_REPLY
|
|
sta (pcbPtr),y
|
|
ldx pcbPtr+0
|
|
ldy pcbPtr+1
|
|
pla
|
|
sta mmuZeroPage
|
|
txa
|
|
cli
|
|
clc
|
|
rts
|
|
|
|
zpPtrSave : buf 1
|
|
|
|
Reply = * ;( .AY=msgBuf[msgRet,msgData] ) : .CS:.A=err
|
|
sei
|
|
;** switch to kernel
|
|
ldx mmuZeroPage
|
|
stx zpPtrSave
|
|
ldx #$00
|
|
stx mmuZeroPage
|
|
sta msgPtr+0
|
|
sty msgPtr+1
|
|
;** find and check the sender
|
|
ldy #msgFrom
|
|
lda (msgPtr),y
|
|
sta pcbPtr+0
|
|
iny
|
|
lda (msgPtr),y
|
|
sta pcbPtr+1
|
|
;xx verify that receiver is a pcb here
|
|
ldy #pcbState
|
|
lda (pcbPtr),y
|
|
cmp #STATE_REPLY
|
|
beq +
|
|
- lda #KERN_ERR_PID_NOT_REPLY
|
|
ldx zpPtrSave
|
|
stx mmuZeroPage
|
|
sec
|
|
cli
|
|
rts
|
|
+ ldy #pcbBlockedOn
|
|
lda (pcbPtr),y
|
|
cmp activePid+0
|
|
bne -
|
|
iny
|
|
lda (pcbPtr),y
|
|
cmp activePid+1
|
|
bne -
|
|
;** copy the reply contents
|
|
ldy #pcbSendMsgPtr
|
|
lda (pcbPtr),y
|
|
sta p+0
|
|
iny
|
|
lda (pcbPtr),y
|
|
sta p+1
|
|
ldy #msgRet
|
|
lda (msgPtr),y
|
|
sta (p),y
|
|
ldy #msgData
|
|
- lda (msgPtr),y
|
|
sta (p),y
|
|
iny
|
|
cpy #msgData+4
|
|
bcc -
|
|
;** wake up the sender and exit
|
|
jsr MakeReady
|
|
ldx zpPtrSave
|
|
stx mmuZeroPage
|
|
clc
|
|
cli
|
|
rts
|
|
|
|
;======== test application ========
|
|
|
|
testNumber : buf 1
|
|
|
|
Init = *
|
|
lda #1
|
|
sta testNumber
|
|
lda #<TestSid1
|
|
ldy #>TestSid1
|
|
ldx #2
|
|
jsr Create
|
|
lda #<TestDelay1
|
|
ldy #>TestDelay1
|
|
ldx #1
|
|
jsr Create
|
|
lda #<TestDelay2
|
|
ldy #>TestDelay2
|
|
ldx #1
|
|
jsr Create
|
|
lda #<TestDelay3
|
|
ldy #>TestDelay3
|
|
ldx #1
|
|
jsr Create
|
|
lda #<TestDelay4
|
|
ldy #>TestDelay4
|
|
ldx #1
|
|
jsr Create
|
|
lda #<TestDelay5
|
|
ldy #>TestDelay5
|
|
ldx #1
|
|
jsr Create
|
|
lda #<Blabber1
|
|
ldy #>Blabber1
|
|
ldx #1
|
|
jsr Create
|
|
lda #<Spinner1
|
|
ldy #>Spinner1
|
|
ldx #1
|
|
jsr Create
|
|
jmp KernelServer
|
|
|
|
TestSid1 = *
|
|
ldx #$1c-1
|
|
lda #$00
|
|
- sta $d400,x
|
|
dex
|
|
bpl -
|
|
lda #$50
|
|
sta 2
|
|
sta 3
|
|
lda #$08
|
|
sta $d418
|
|
lda #$00
|
|
ldy #$08
|
|
sta $d402
|
|
sty $d403
|
|
lda #$41
|
|
sta $d404
|
|
lda #$00
|
|
sta $d405
|
|
lda #$f0
|
|
sta $d406
|
|
- lda 2
|
|
ldy 3
|
|
sta $d400
|
|
sty $d401
|
|
lda 2
|
|
ora 3
|
|
bne +
|
|
lda #120
|
|
ldy #0
|
|
jsr Delay
|
|
+ inc 2
|
|
bne +
|
|
inc 3
|
|
+ inc $d020
|
|
tsx
|
|
jmp -
|
|
|
|
TestDelay1 = *
|
|
jsr MyParentPid
|
|
sta testDelay1Msg+msgTo+0
|
|
sty testDelay1Msg+msgTo+1
|
|
lda #<testDelay1Txt
|
|
ldy #>testDelay1Txt
|
|
sta testDelay1Msg+msgBuf+0
|
|
sty testDelay1Msg+msgBuf+1
|
|
- lda #<60
|
|
ldy #>60
|
|
jsr Delay
|
|
inc $581
|
|
lda #<testDelay1Msg
|
|
ldy #>testDelay1Msg
|
|
jsr Send
|
|
jmp -
|
|
testDelay1Txt : db "Hi, this is delay process 1 *\n",0
|
|
|
|
TestDelay2 = *
|
|
jsr MyParentPid
|
|
sta testDelay2Msg+msgTo+0
|
|
sty testDelay2Msg+msgTo+1
|
|
lda #<testDelay2Txt
|
|
ldy #>testDelay2Txt
|
|
sta testDelay2Msg+msgBuf+0
|
|
sty testDelay2Msg+msgBuf+1
|
|
- lda #<120
|
|
ldy #>120
|
|
jsr Delay
|
|
inc $582
|
|
lda #<testDelay2Msg
|
|
ldy #>testDelay2Msg
|
|
jsr Send
|
|
jmp -
|
|
testDelay2Txt : db "Hi, this is delay process 2\n",0
|
|
|
|
TestDelay3 = *
|
|
jsr MyParentPid
|
|
sta testDelay3Msg+msgTo+0
|
|
sty testDelay3Msg+msgTo+1
|
|
lda #<testDelay3Txt
|
|
ldy #>testDelay3Txt
|
|
sta testDelay3Msg+msgBuf+0
|
|
sty testDelay3Msg+msgBuf+1
|
|
- lda #<180
|
|
ldy #>180
|
|
jsr Delay
|
|
inc $583
|
|
lda #<testDelay3Msg
|
|
ldy #>testDelay3Msg
|
|
jsr Send
|
|
jmp -
|
|
testDelay3Txt : db "Hi, this is delay process 3\n",0
|
|
|
|
TestDelay4 = *
|
|
jsr MyParentPid
|
|
sta testDelay4Msg+msgTo+0
|
|
sty testDelay4Msg+msgTo+1
|
|
lda #<testDelay4Txt
|
|
ldy #>testDelay4Txt
|
|
sta testDelay4Msg+msgBuf+0
|
|
sty testDelay4Msg+msgBuf+1
|
|
- lda #<240
|
|
ldy #>240
|
|
jsr Delay
|
|
inc $584
|
|
lda #<testDelay4Msg
|
|
ldy #>testDelay4Msg
|
|
jsr Send
|
|
jmp -
|
|
testDelay4Txt : db "Hi, this is delay process 4\n",0
|
|
|
|
TestDelay5 = *
|
|
jsr MyParentPid
|
|
sta testDelay5Msg+msgTo+0
|
|
sty testDelay5Msg+msgTo+1
|
|
lda #<testDelay5Txt
|
|
ldy #>testDelay5Txt
|
|
sta testDelay5Msg+msgBuf+0
|
|
sty testDelay5Msg+msgBuf+1
|
|
- lda #<300
|
|
ldy #>300
|
|
jsr Delay
|
|
inc $585
|
|
lda #<testDelay5Msg
|
|
ldy #>testDelay5Msg
|
|
jsr Send
|
|
jmp -
|
|
testDelay5Txt : db "Hi, this is delay process 5\n",0
|
|
|
|
Blabber1 = *
|
|
jsr MyParentPid
|
|
sta blabber1Msg+msgTo+0
|
|
sty blabber1Msg+msgTo+1
|
|
lda #<blabber1Txt
|
|
ldy #>blabber1Txt
|
|
sta blabber1Msg+msgBuf+0
|
|
sty blabber1Msg+msgBuf+1
|
|
- inc $580
|
|
lda #<blabber1Msg
|
|
ldy #>blabber1Msg
|
|
jsr Send
|
|
jmp -
|
|
blabber1Txt : db "Hi, this is blabber\n",0
|
|
|
|
Spinner1 = *
|
|
jsr MyParentPid
|
|
sta spinner1Msg+msgTo+0
|
|
sty spinner1Msg+msgTo+1
|
|
lda #<spinner1Txt
|
|
ldy #>spinner1Txt
|
|
sta spinner1Msg+msgBuf+0
|
|
sty spinner1Msg+msgBuf+1
|
|
- inc $580
|
|
lda #<spinner1Msg
|
|
ldy #>spinner1Msg
|
|
jsr Send
|
|
jmp -
|
|
spinner1Txt : db "Hi, this is spinner +\n",0
|
|
|
|
KernelServer = *
|
|
lda #$00
|
|
sta mmuZeroPage
|
|
lda #14
|
|
jsr $ffd2
|
|
- lda #<ksMsg
|
|
ldy #>ksMsg
|
|
jsr Receive
|
|
lda ksMsg+msgBuf+0
|
|
ldy ksMsg+msgBuf+1
|
|
sta $80
|
|
sty $81
|
|
ldy #0
|
|
- lda ($80),y
|
|
beq +
|
|
jsr $ffd2
|
|
iny
|
|
bne -
|
|
+ lda #<ksMsg
|
|
ldy #>ksMsg
|
|
jsr Reply
|
|
jmp --
|
|
|
|
bss = *
|
|
testDelay1Msg = $c00 ;** put these here to save pgm memory
|
|
testDelay2Msg = testDelay1Msg+msgSize
|
|
testDelay3Msg = testDelay2Msg+msgSize
|
|
testDelay4Msg = testDelay3Msg+msgSize
|
|
testDelay5Msg = testDelay4Msg+msgSize
|
|
blabber1Msg = testDelay5Msg+msgSize
|
|
spinner1Msg = blabber1Msg+msgSize
|
|
ksMsg = spinner1Msg+msgSize
|
|
-----=-----
|
|
|
|
APPENDIX B. UUENCODED DEMO PROGRAM
|
|
|
|
The uuencoded demo system follows. You can extract it with any uudecoder or
|
|
with version 2.00 or higher of "unbcode" (ACE has only version 1.00).
|
|
|
|
-nucode-begin 1 bos
|
|
begin 640 bos
|
|
M`!-,)A,``````````````````````````````````````````````'BI#HT`
|
|
M_ZEVH!6-%`.,%0.IJZ`3C18#C!<#J:N@$XT8`XP9`ZD`C203C243J1Z@$X4&
|
|
MA`<@U12I`Z`3C0,3C`03C043C`83A0*$`ZG_C0<3J0"-"!.I((4,J<"-'1.I
|
|
M`HT-$UA,C1.I.Z`9H@$@_1/N``30#>X!!-`([@($T`/N`P1,EA-XJ66@^HT4
|
|
M`XP5`ZE`H/J-&`.,&0.B^IJI`(T'U8T(U:(!C@G5C0K5J02-!M583+=-````
|
|
M````````````````````````````````````````>(W=$XS>$X[?$ZT'U8W<
|
|
M$ZD`C0?5J0"D#(4(A`G(C.`3R(SA$\B$#,#`D`$`HAJI`)WB$\H0^JGVC>@3
|
|
MK>$3C>D3K>`3C>H3J02-ZQ.MWQ.-[!.-[1.E`J0#C?H3C/L3J<6-_!.@&KGB
|
|
M$Y$(B!#XI0@8:0Z%!J4):0"%!R#5%*D`K.$3A02$!:#WJ0Z1!,BB!*D`D03(
|
|
MRM#ZK=T3D03(K=X3D03(J0F1!,BI%I$$(+L4I0BD":[<$XX'U1A88*`:J<"1
|
|
M"*D#H!.%!H0'I0*D`X4$A`4@`!5@I0:D!XWZ%(S[%(W\%(S]%*G_C?X4J0"-
|
|
M_Q2@!;GZ%)$&B!#X8````````!B@!;$&:0&1!J``L021",BQ!)$(R*4$D0C(
|
|
MI061"*``L02%!LBQ!(4'H`*E")$&R*4)D0:@`*4(D03(I0F1!&"@`+$(A03(
|
|
ML0B%!:`"L0B1!,BQ")$$H`*Q"(4$R+$(A06@`+$(D03(L0B1!*`%L08XZ0&1
|
|
M!F#8J0Z-`/^M&=`0!"D!T`.M#=R-&="M!]6B`(X'U:`(D0*@!KJ*D0*@!ZT)
|
|
MU9$"H`FM!M61`NXD$]`#[B43K2,3\`,@71?JH`JQ`LB1`O`GH`"Q`JK(L0*&
|
|
M`H4#H`NQ`O`5..D!D0+P`TS%%:`$L0(0!<BQ`M#0H`FQ`HT&U:`'L0*-"=6@
|
|
M!K$"JIJ@"+$"C0?53#/_J0"B`$R.%@"I`*X'U8T'U:4"I`..!]488*D`K@?5
|
|
MC0?5H!BQ`DC(L0*H:(X'U1A@`````(T\%FB-.A9HC3L6:!AI`8T]%FAI`$BM
|
|
M/19(J0!(K3P62(I(F$BM`/](K0?5H@".!]6@")$"B*T)U9$"B+J*D0*@":T&
|
|
MU9$"K3L62*TZ%DA@>"`^%B"8%DS1%:4"A0BE`X4)J0.@$X4&A`<@0!6@`+$(
|
|
MA0+(L0B%`Z`:J<61"&#)`-`&P`#0`AA@>(T-%XP.%R`^%B"8%J`:J<2@#!BM
|
|
M#1=M)!.-#1>1",BM#A=M)1.-#A>1"*D`*HT/%ZD>H!.%!H0'A02$!2`1%R``
|
|
M%4S1%0`````@4!>@!+$$T"F@#+$$S203R+$$[243H@"P`>B.$!>(K0T7T03(
|
|
MK0X7\02M#Q?M$!>PSJ`"L02JR+$$A@2%!6"@`+$$JLBQ!(8$A05@K1X3K!\3
|
|
MA0B$":`,L0C-)!/P`6#(L0C-)1/P`6"I'J`3A0:$!R!`%2"[%$Q=%P``>(V+
|
|
M%XR,%R`^%B"8%JV+%ZR,%X4*A`N@`+$*A0;(L0J%!Z`,I0J1",BE"Y$(H!2E
|
|
M!I$(R*4'D0B@&K$&R<+P(*G!D0@8I09I#H4&D`+F!Z`"L0:%!,BQ!H4%(``5
|
|
M3-$5J<.1"*`,L0:%!,BQ!H4%($48I0BD":8&A@BF!X8)H@`8(!L8(+L43-$5
|
|
M`````(T9&(X8&(P7&`AH*0&-&AB@![$(A06@!K$(&&D"A02@`[D7&)$$B!#X
|
|
M8*`"I0B1"LBE"9$*H!>Q"I$$B!#Y8'B-BQ>,C!>M!]5(J0"-!]6@$[$"T!YH
|
|
MC0?5(#X6()@6J<*1"*`,K8L7D0C(K8P7D0A,T16E`J0#&&D.D`'(A0:$!Z`.
|
|
ML0*%",BQ`H4)($`5H`RQ"(4*R+$(A0NMBQ>LC!>%!(0%($48H!JIPY$(I@BD
|
|
M"6B-!]6*6!A@`'BN!]6.U!BB`(X'U84*A`N@`K$*A0C(L0J%":`:L0C)P_`+
|
|
MJ>&NU!B.!]4X6&"@%+$(Q0+0[<BQ",4#T.:@#+$(A03(L0B%!:`1L0J1!*`4
|
|
ML0J1!,C`&)#W(+L4KM08C@?5&%A@`*D!C3H9J8N@&:("(/T3J=V@&:(!(/T3
|
|
MJ2.@&J(!(/T3J6>@&J(!(/T3J:N@&J(!(/T3J>^@&J(!(/T3J3.@&Z(!(/T3
|
|
MJ6B@&Z(!(/T33)\;HANI`)T`U,H0^JE0A0*%`ZD(C1C4J0"@"(T"U(P#U*E!
|
|
MC034J0"-!=2I\(T&U*4"I`.-`-2,`=2E`@4#T`>I>*``(+T6Y@+0`N8#[B#0
|
|
MNDRY&2`C%HT`#(P!#*D$H!J-!`R,!0RI/*``(+T6[H$%J0"@#""-%TSP&<A)
|
|
M+"!42$E3($E3($1%3$%9(%!23T-%4U,@,2`J#0`@(Q:-&`R,&0RI2J`:C1P,
|
|
MC!T,J7B@`""]%NZ"!:D8H`P@C1=,-AK(22P@5$A)4R!)4R!$14Q!62!04D]#
|
|
M15-3(#(-`"`C%HTP#(PQ#*F.H!J--`R,-0RIM*``(+T6[H,%J3"@#""-%TQZ
|
|
M&LA)+"!42$E3($E3($1%3$%9(%!23T-%4U,@,PT`(",6C4@,C$D,J=*@&HU,
|
|
M#(Q-#*GPH``@O1;NA`6I2*`,((T73+X:R$DL(%1(25,@25,@1$5,05D@4%)/
|
|
M0T534R`T#0`@(Q:-8`R,80RI%J`;C60,C&4,J2R@`2"]%NZ%!:E@H`P@C1=,
|
|
M`AO(22P@5$A)4R!)4R!$14Q!62!04D]#15-3(#4-`"`C%HUX#(QY#*E3H!N-
|
|
M?`R,?0SN@`6I>*`,((T73$8;R$DL(%1(25,@25,@0DQ!0D)%4@T`(",6C9`,
|
|
MC)$,J8B@&XV4#(R5#.Z`!:F0H`P@C1=,>QO(22P@5$A)4R!)4R!34$E.3D52
|
|
M("L-`*D`C0?5J0X@TO^IJ*`,(%H8K:P,K*T,A8"$@:``L8#P!B#2_\C0]JFH
|
|
(H`P@U1A,J1L`
|
|
`
|
|
end
|
|
-nucode-end 1 2258 1430bdc2
|
|
|
|
========================================================================END===
|