home *** CD-ROM | disk | FTP | other *** search
/ Brotikasten / BROTCD01.iso / texte / cbmhack2.txt < prev    next >
Text File  |  1995-08-20  |  145KB  |  3,525 lines

  1.      CCCCCC       HH    HH    AAAA    CCCCC KK  KK IIIIII NN   NN  GGGG
  2.     CC   ====     HH    HH  AA    AA CC     KK KK    II   NNN  NN GG
  3.     CC   ===      HH    HH  AA    AA CC     KKKK     II   NN NNNN GG
  4.     CC            HHHHHHHH  AA    AA CC     KKKK     II   NN NNNN GG GGG
  5.     CC   ===      HH    HH  AAAAAAAA CC     KK KK    II   NN  NNN GG   GG
  6.     CC   ====     HH    HH  AA    AA CC     KK KK    II   NN   NN GG   GG
  7.      CCCCCC       HH    HH  AA    AA  CCCCC KK  KK IIIIII NN   NN  GGGGG
  8.  
  9.                     Volume 1 - Issue 2 - April 22, 1992
  10. =====================================================================(v1.1)===
  11.  
  12. Editor's Notes:
  13. by Craig Taylor (duck@pembvax1.pembroke.edu)
  14.  
  15.    Eeegh! - When I first started this I never realized how much work it'd be.
  16. I'm glad of the reception it's gotten from the Commodore community at large. I'd
  17. like to thank each of the author's in this issue and last for their work they've
  18. put into it as well as their time. 
  19.  
  20.   Please note that all files, documentation etcetera associated with C= Hacking
  21. and whatnot contained within are also now available at tybalt.caltech.edu via
  22. anonymous ftp under the directory /pub/rknop/hacking.mag.  Any updates to files
  23. contained within or corrections will be posted there as well as mentioned
  24. here.  Currently it has the correct 1st issue and (soon to be) 2nd issue.  Also
  25. Robert Knop's file bmover.sfx is there for the Banking Geos article in this
  26. issue.
  27.  
  28.   It seems as if we're averaging about 2 months for each issue and hopefully 
  29. we'll keep that rate during the summer but due to an internship (I'll hopefully
  30. get) I may not have net access during the summer.  In that case it'll be delayed
  31. until after I get back to school in the fall.
  32.  
  33.   Also, if you've got any ideas for articles or have written a program that is
  34. unique that you'd be interested in documenting and p'haps letting other people
  35. see some of the tricks of the trade then please send any queries to 
  36. duck@pembvax1.pembroke.edu.  
  37.  
  38. ****************** WARNINGS, UPDATES, BUG REPORTS, ETC... *********************
  39.  
  40.   Please note that in the last issue when the undocumented opcodes were 
  41. discussed that they are _VERY NON-STANDARD_.  And any future accelerator boards
  42. for the C=128 or C=64, in all likelehood, _will not work_. Zip's board [when are
  43. they ever gonna finish it?] for the C=128 will be based on a similair processor
  44. to the 8502 and is practically guarenteed not to work with the undocumented 
  45. op-codes.  If you plan to release any ML programs for general use PLEASE be 
  46. aware that they may be in-compatible with future systems.
  47.  
  48. ============================================================================
  49.  
  50. Note: Permission is granted to re-distribute this "net-magazine", in whole,
  51.   freely for non-profit use.  However, please contact individual authors for 
  52.   permission to publish or re-distribute articles seperately. 
  53.  
  54.   *** AUTHORS LISTED BELOW RETAIN ALL RIGHTS TO THEIR ARTICLES ***
  55.  
  56. ============================================================================
  57.  
  58.   In this edition we've got the following articles:
  59.  
  60. Learning ML - Part 2
  61.  
  62.   Yes, the infamous learning machine langauge tutors by Craig Taylor 
  63. (duck@pembvax1.pembroke.edu).  In this session we examine indexed addressing
  64. and it's usefulness in printing strings of characters.
  65.  
  66. 8563 : An In-Depth Look
  67.  
  68.   This article documents and details the 8563 in-depth.  It covers all 
  69. available registers, documents their functions and suggested methods of getting
  70. certain effects.  Also covers how to read and write to 8563 registers as well
  71. as read the 16k or 64k of memory that contains the VDC char-set, screen memory
  72. etc.  Written by Craig Taylor (duck@pembvax1.pembroke.edu).
  73.  
  74. The Poor Man's Way to Getting Files from MS-Dos Diskettes
  75.  
  76.   Now there's a way to transfer files of any length from MS-Dos diskettes using
  77. a public domain program that will only read files of 43k or less and a IBM
  78. program to split the files up.  There are better ways, but if you don't want
  79. to pay for Big-Blue Reader this is one method to go.  Written by Mark Lawrence
  80. (9152427D@Levels.UniSa.Edu.Au).
  81.  
  82. Banking on Geos
  83.  
  84.   GEOS 128, being an extended and expanded version of GEOS 64, provides a 
  85. contiguous block of application space in a single RAM bank.  The "standard"
  86. programming documentation makes few references to the use of other banks in 
  87. GEOS.  This article describes accessing other RAM banks (including RAM banks 2
  88. and 3 for 256K expanded 128's) under GEOS128, using both the GEOS Kernal
  89. routines and more direct methods.  By Robert Knop (rknop@tybalt.caltech.edu).
  90.  
  91. Dynamic Memory Allocation
  92.  
  93.   Written by Craig Bruce (csbruce@ccnga.uwaterloo.ca) this article examines
  94. how to implement and use dynamically allocated memory that will allow your
  95. programs to better utilize all of the available memory on your C=128, including
  96. expansion memory.  These routines are extracted from the Zed-128 program which
  97. is a text editor that can edit 600KByte files on a 512K expanded 128.
  98.  
  99. =============================================================================
  100.  
  101. Beginning ML #2
  102. by Craig Taylor (duck@pembvax1.pembroke.edu)
  103.  
  104.   Last time we introduced the definition of what exactly Machine Language / 
  105. Assembly Language is along with an example of clearing the screen in Machine
  106. Language.
  107.  
  108.   Now, in this issue let's print my name (and later your name). Looking at the
  109. code from last time the following assembly source jumps to mind:
  110.  
  111. ------------ 
  112. print_1.asm:
  113.  
  114.           lda #147         ; clr/screen code
  115.           jsr $ffd2        ; print
  116.           lda #'C'         ; code for ascii "C"
  117.           jsr $ffd2        ; print
  118.           lda #'r'    
  119.           jsr $ffd2
  120.           lda #'a'
  121.           jsr $ffd2
  122.           lda #'i'
  123.           jsr $ffd2
  124.           lda #'g'
  125.           jsr $ffd2
  126.           lda #32          ; code for space 
  127.           jsr $ffd2
  128.           lda #'T'         ; print my last name....
  129.           jsr $ffd2
  130.              .
  131.                .            (ad naseum...)    
  132.                  .        
  133.           rts
  134. ----------
  135.  
  136.   Now, for short strings like "HI!" that might be fine but if your name is 
  137. something like "Seymour Johnson the third" it can get a little bit ridiculous
  138. in terms of the amount of memory and the amount of typing (eegh! - typing!) 
  139. involved. There's an easier way.
  140.   
  141.   It's called indexed addressing. What is this you say? Let's first take a 
  142. look at the above program using indexed addressing and then explain it.
  143.  
  144. ------------ 
  145. print_2.asm
  146.  
  147.           ldy #$00
  148.    loop   lda string,y
  149.           jsr $ffd2
  150.           iny
  151.           cpy #numchars
  152.           bne loop
  153.           rts
  154.  
  155.  string   .byte 147
  156.           .ascii "Craig Taylor"
  157.  
  158.  numchars = *-string
  159.  
  160. ------------
  161.  
  162.   Hmm, looks a little bit confusing 'eh?
  163.  
  164.   What we're doing is using the register y to act as a pointer to grab the 
  165. y'th character in what is at memory location STRING. If y is 0 then we'll get
  166. string+0 which happens to be a 147.
  167.  
  168.   The .byte and .ascii directives are not real instructions the computer
  169. understands. What happens is that your assembler sees that you want data
  170. put at those locations so it convert 147 and "Craig Taylor" to numbers
  171. and puts them at the proper locations, relieving you the burden of doing it.
  172.  
  173.    The numchars = *-string looks confusing at first... obviously, numchars
  174. stands for the number of characters we need to print out but how is it being
  175. figured?  Most assemblers keep the current location in memory it's assembling
  176. to in something called a program counter of PC.  Most assemblers also will let
  177. you have the value at assembly time by referencing it using the "*" symbol.
  178. "string" is already a symbol that has been set an address in memory and after
  179. assembling the .byte and .ascii instruction "*" will be equal to the next 
  180. address that the assembler would put any instructions at, had we had any.
  181. Now, *-string basically is saying to the compiler, look, take the current 
  182. program counter (where it's assembling to) and subtract it from where the 
  183. symbol string starts at (which it just assembled a while back). This should
  184. be then, our number of characters we have.
  185.  
  186. WALK-THROUGH:
  187.  
  188.   Register Y is initially set to zero in the first instruction, as we want to
  189. begin with the first character. The first character is at string+0, not 
  190. string+1. This may seem a little bit odd at first, but try thinking of it this
  191. way:
  192.  
  193.      Take, for example, 3 diskettes. Put them in a row and call the one on the
  194.      left "string" (or some other name). Then point at "string+1", "string+2"..
  195.      Notice there's no "string+3" even tho' there's 3 diskettes?  This may 
  196.      seem a little bit strange at first, but after thinking about it a while
  197.      you'll begin to understand. In machine / assembly language, you typically
  198.      count starting from zero, in the real world, typically from one.
  199.  
  200.   The lda string,y instruction is telling the computer to reference string as 
  201. if it was an array, get the byte at location string + y, or for you basic
  202. programmers thinking of string as an array of bytes (with no bounds checking)
  203. string[y]. Thus, the accumulator is equal to the yth byte starting from
  204. the location string is defined to be.
  205.  
  206.   We then call the routine Commodore so graciously provided for us that prints
  207. out the contents of the accumulator. Now, some routines, as we'll see in other
  208. editions, are not so nice and may change the value of the accumulator, the 
  209. x and y registers. However, Commodore was extra nice and the routine at $ffd2 is
  210. guaranteed not to change any of the registers.
  211.  
  212.   The routine then "iny".  What is this? It "INcrements the Y register". INX 
  213. will "INcrement the X register".  The X and Y register can not have any math
  214. performed on them other than increment and decrement operations (ie: adding
  215. one and subtracting one).   The only register that allows addition or
  216. subtraction is the accumulator. However, in this case we just want y to point
  217. to the next character, the next byte so "INY" serves us fine.
  218.   
  219.   We then "ComPare Y" register to the number of characters in the string. Notice
  220. the # sign. If we hadn't have had that there, it would've tried to look at 
  221. whatever memory location numchars was defined for. Numchars was set up to hold
  222. the number of characters in the string, not be a pointer for a memory location.
  223.  
  224.   Now that we've compared it, we "Branch if the last comparison was Not Equal"
  225. back to where loop is at (in this case, where we load a with character again).
  226.  
  227.   If it was equal we fall through to the RTS where we return from our little
  228. program.
  229.  
  230.   Basically, we've got something like the following flowchart:
  231.                   _______
  232.                  / START \
  233.                  \_______/
  234.                     |
  235.                    \|/
  236.                 +-----------------+
  237.                 | Set Index (Y)   |
  238.                 | first character | 
  239.                 +-----------------+
  240.                     |
  241.                    \|/
  242.                 +-------------------+
  243.                 | Get the character | /
  244.                 | pointed to by     |<------------------+
  245.                 | the index(Y)      | \                 |
  246.                 +-------------------+                   |
  247.                     |                                   |
  248.                    \|/                                  |
  249.                 +-------------+                         |
  250.                 | Print it    |                         |
  251.                 +-------------+                         |
  252.                     |                                   |
  253.                    \|/                                  |
  254.                 +------------------------+              |
  255.                 | Increment the Index(Y) |              |
  256.                 +------------------------+              |
  257.                     |                                   |
  258.                    \|/                                  |
  259.                     /\                                  |
  260.                    /= \                                 |
  261.                   /# of\                                |
  262.                  /chars?\                               |
  263.                 /to print\___no,not =_____------------->+
  264.                 \???     /
  265.                  \      /
  266.                   \    /
  267.                    \  /
  268.                     \/
  269.                      |
  270.                     \|/
  271.                     _____
  272.                    / END \
  273.                    \_____/
  274.  
  275.   Indexed addressing is used *very* often in assembly language.  Try playing 
  276. with the second program and experiment with it until you understand fully what
  277. is going on.  Next time we'll look at how to access some of the diskette 
  278. routines and to display a file on disk.
  279.  
  280. ===============================================================================
  281.  
  282. An In-Depth Look at the 8563 Video Chip on the C= 128
  283. -----------------------------------------------------
  284. by Craig Taylor (duck@pembvax1.pembroke.edu)
  285.  
  286.   Due to the article in the last issue by Craig Bruce (csbruce@ccnga.uwaterloo.
  287. ca) and some letters from people asking about how the 8563 Video Chip works and
  288. more technical information this article will attempt to present as much detail
  289. as possible on the 8563 chip & it's various capibilities.
  290.  
  291.   
  292.                             ---------------------  
  293.                             ! Hardware Aspects: !
  294.                             ---------------------
  295.  
  296.   The following is a physical layout of the 8563 and the available pin outs:
  297.  
  298.               +------------------+
  299.          42 o_|DD7  VDD    CS DA7|_o 33   DA0-DA7 - Address Bus for Ram
  300.          41 o_|DD6            DA6|_o 32   DD0-DD7 - Data Bus for Ram
  301.          40 o_|DD5            DA5|_o 31   D0 - D7 - Data Bus 8563 / Cpu
  302.          39 o_|DD4            DA4|_o 30   CS /CS  - Chip Selection Pin
  303.          38 o_|DD3            DA3|_o 29   /RS     - Register Select
  304.          36 o_|DD2            DA2|_o 28   R/W     - Data Direction for Data Bus
  305.          35 o_|DD1            DA1|_o 27   INIT    - Initialize internal latches
  306.          34 o_|DD0            DA0|_o 26   DISPEN  - (Unused) Display Enable
  307.               |                  |        RES     - (Unused) Reset all scan cnts
  308.               |                  |        TST     - (Unused) Test purposes only
  309.          10 o_|D7            /CAS|_o 48   DR/W    - Local Dram Read/Write
  310.          11 o_|D6            /RAS|_o 47   /RAS    - Row Address Strobe
  311.          13 o_|D5            DR/W|_o 21   /CAS    - Column Address Strobe
  312.          14 o_|D4                |        DCLK    - Video Dot Clock
  313.          15 o_|D3               R|_o 46   CCLK    - (Unused) Character Clock 
  314.          16 o_|D2               G|_o 45   LP2     - Input for Light Pen
  315.          17 o_|D1               B|_o 44   HSYNC   - Horizontal Sync
  316.          18 o_|D0               I|_o 43   R,G,B,I - Pixel Data Outputs
  317.               |                  |        
  318.               |                  |
  319.           8 o_|/RS               |
  320.           7 o_|/CS               |
  321.           9 o_|R/W           VSYN|_o 20
  322.          23 o_|/RES          HSYN|_o 3
  323.               |                  |
  324.               |              CCLK|_o 1
  325.          25 o_|/LP2        DISPEN|_o 19 
  326.               |                  |
  327.               |               TST|_o 24
  328.           2 o_|/DCLK  VS5    INIT|_o 22
  329.               +------------------+ 
  330.                        !12
  331.                        o
  332.  
  333.  
  334.    Taken from Pg. 596-8 C=128 Programmer's Reference Manual Publ. Feb 1986 
  335.    Bantem Books
  336.  
  337.  
  338.  
  339.                          +-----------------------------+
  340.                          | How Commodore Hooked It Up! |
  341.                          +-----------------------------+
  342.  
  343.   Now, the 8563 is hooked up to the computer via the following method:
  344.  
  345.      +---------------------+                          
  346.      |                     |             +--------+   +-------+
  347.      |Computer Memory      |             | 8563   |   |16k or |   
  348.      |    (RAM)            |             |        | % | 64k   |   
  349.      |                     |___$d600_____|da0-7   | % |VDC RAM|   
  350.      |                     |             |        | % |       |
  351.      |                     |___$d601_____|dr0-7   | % |(Screen|
  352.      |                     |   ( /rs)    |    d0-7|___| Mem)  |
  353.      +---------------------+             +--------+   +-------+  
  354.  
  355.   Confusing 'eh? (The %'s represent control signals that also are used).. What 
  356. basically happens is that every time the computer wants to access the 8563 to 
  357. program or change one of it's numerous registers it has to store the register
  358. number to $d600, then loop until the 7th bit of $d600 changes to a 1.  Once
  359. this is done, you can then read or write a value to/from $d601.
  360.  
  361.   Commodore also employed the MMU (Memory Management Unit) to manipulate pages 
  362. of memory and thus, the 8563 only shows up in the I/O page (usually referenced
  363. as Bank 15 or a value of $00 in the MMU Register at $ff00) or in pages that the 
  364. I/O section of memory is enabled.
  365.  
  366.   The register at $d600 in the I/O space of the C=128 is laid out as follows:
  367.  
  368.   Bit Position:
  369.       7       6        5       4       3       2        1      0
  370.       Status  LightPen VBlank  -----Unused---- ------Version #--------
  371.  
  372.   When a value is placed in $d600 instead of putting the value in Status,
  373. LightPen bits etc, the value reflects which register # is requested. Bit 7 of
  374. this register (Status) will then return a binary 1 when $d601 reflects the 
  375. actual value of the register just poked to $d600. (See the ML routines for
  376. storing and reading values to/from registers at the end of this article). When
  377. a value is first place in this register, $d600 bit 7 is equal to a zero.
  378.  
  379.   Bit 6, is used to indicate when new values have been latched into the 
  380. lightpen registers (16-17). Bit 5, VBlank refers to when the 8563 is in the
  381. period known as "Vertical Blanking Period".  Usually, however this bit is 
  382. seldom referred to as updating the 8563 is usally too slow to make use of this
  383. for any special effects.  
  384.  
  385.   Bits 0-2 return a version # of which %000 and %001 are the known versions out.
  386. Early 128's will contain the value of $0 while later 128's will contain the 
  387. value of $1.  Note that there are slight differences between the 8563's, in that
  388. register 25 (horizontal smooth scoll register) requires different settings. 
  389.  
  390.   The register at $d601 returns the value of register # that has been written
  391. into $d601 (when bit 7 of $d600 = %1). Note that storing a value here will also
  392. do a write into the register # selected. (Refer to the ML routines for storing
  393. and reading values to/from registers at the end of this article for an example).
  394.  
  395.  
  396.                               ------------------------
  397.                               | Register Definitions |
  398.                               ------------------------
  399.  
  400. Reg#     7    6    5    4    3    2    1    0     Description              Notes
  401. ------- ---- ---- ---- ---- ---- ---- ---- ----   ------------------------ -----
  402.   0     HzT7 HzT6 HzT5 HzT4 HzT3 HzT2 HzT1 HzT0   Horizontal Total          ^1  
  403.   1     HzD7 HzD6 HzD5 HzD4 HzD3 HzD2 HzD1 HzD0   Horizontal Displayed      ^1
  404.   2     HzS7 HzS6 HzS5 HzS4 HzS3 HzS2 HzS2 HzS0   Horizontal Sync Position  ^1
  405.   3     VSW3 VSW2 VSW1 VSW0 HSW3 HSW2 HSW1 HSW0   Vert/Horiz. Sync Width    ^2
  406.   4     VeT7 VeT6 VeT5 VeT4 VeT3 VeT2 VeT1 VeT0   Vertical Total            ^3
  407.   5     .... .... .... VeA4 VeA3 VeA2 VeA1 VeA0   Vertical Total Fine Adju  ^3
  408.   6     VeD7 VeD6 VeD5 VeD4 VeD3 VeD2 VeD1 VeD0   Vertical Displayed        ^3
  409.   7     VeS7 VeS6 VeS5 VeS4 VeS3 VeS2 VeS1 VeS0   Vertical Sync Position    ^2
  410.   8     .... .... .... .... .... .... Ilc1 Ilc0   Interlace Mode            ^4
  411.   9     .... .... .... CTV4 CTV3 CTV2 CTV1 CTV0   Character Total Vertical  ^5
  412.  10     .... CrM1 CrM0 Css4 Css3 Css2 Css1 Css0   Cursor Mode/ Start Scan   ^6
  413.  11     .... .... .... Ces4 Ces3 Ces2 Ces1 Ces0   Cursor End Scan           ^6
  414.  12     Ds15 Ds14 Ds13 Ds12 Ds11 Ds10 Ds09 Ds08   Display Start Adrs (Hi)   ^7
  415.  13     Ds07 Ds06 Ds05 Ds04 Ds03 Ds02 Ds01 Ds00   Display Start Adrs (Lo)   ^7
  416.  14     Cp15 Cp14 Cp13 Cp12 Cp11 Cp10 Cp09 Cp08   Cursor Position (Hi)      ^7
  417.  15     Cp07 Cp06 Cp05 Cp04 Cp03 Cp02 Cp01 Cp00   Cursor Position (Lo)      ^7
  418.  16     LpV7 LpV6 LpV5 LpV4 LpV3 LpV2 LpV1 LpV0   Light Pen Veritcal        ^8
  419.  17     LpH7 LpH6 LpH5 LpH4 LpH3 LpH2 LpH1 LpH0   Light Pen Horizontal      ^8
  420.  18     Ua15 Ua14 Ua13 Ua12 Ua11 Ua10 Ua09 Ua08   Update Address (Hi)       ^9
  421.  19     Ua07 Ua06 Ua05 Ua04 Ua03 Ua02 Ua01 Ua00   Update Address (Lo)       ^9
  422.  20     At15 At14 At13 At12 At11 At10 At09 At08   Attribute Start Adrs (Hi) ^7
  423.  21     At07 At06 At05 At04 At03 At02 At01 At00   Attribute Start Adrs (Lo) ^7
  424.  22     HcP3 HcP2 HcP1 HcP0 IcS3 IcS2 IcS1 IcS0   Hz Chr Pxl Ttl/IChar Spc  ^A
  425.  23     .... .... .... VcP4 VcP3 VcP2 VcP1 VcP0   Vert. Character Pxl Spc   ^5
  426.  24     BlkM RvsS Vss5 Vss4 Vss3 Vss2 Vss1 Vss0   Block/Rvs Scr/V. Scroll ^9^B^C
  427.  25     Text Atri Semi Dble Hss3 Hss2 Hss1 Hss0   Diff. Mode Sw/H. Scroll  ^D,^E
  428.  26     Fgd3 Fgd2 Fgd1 Fgd0 Bgd3 Bgd2 Bgd1 Bgd0   ForeGround/BackGround Col ^F
  429.  27     Rin7 Rin6 Rin5 Rin4 Rin3 Rin2 Rin1 Rin0   Row/Adrs. Increment       ^G
  430.  28     CSa2 CSa1 CSa0 RamT .... .... .... ....   Character Set Addrs/Ram  ^H,^I
  431.  29     .... .... .... UdL4 UdL3 UdL2 UdL1 UdL0   Underline Scan Line       ^6
  432.  30     WdC7 WdC6 WdC5 WdC4 WdC3 WdC2 WdC1 WdC0   Word Count (-1)           ^9
  433.  31     Dta7 Dta6 Dta5 Dta4 Dta3 Dta2 Dta1 Dta0   Data                      ^9  
  434.  32     BlkF BlkE BlkD BlkC BlkB BlkA Blk9 Blk8   Block Copy Source (hi)    ^9
  435.  33     Blk7 Blk6 Blk5 Blk4 Blk3 Blk2 Blk1 Blk0   Block Copy Source (lo)    ^9
  436.  34     DeB7 DeB6 DeB5 DeB4 DeB3 DeB2 DeB1 DeB0   Display Enable Begin      ^J
  437.  35     DeE7 DeE6 DeE5 DeE4 DeE3 DeE2 DeE1 DeE0   Display Enable End        ^J
  438.  36     .... .... .... .... Drm3 Drm2 Drm1 Drm0   DRAM Refresh Rate         ^K
  439.  
  440.                              +-----------------+
  441.                              | Register Usage: |
  442.                              +-----------------+
  443.  
  444. ^1 : Register #0:     Horizontal Total
  445. ---  Register #1:     Horizontal Displayed
  446.      Register #2:     Horizontal Sync Pulse
  447.   
  448.   These two register function to define the display width of the screen. 
  449. Register 0 will contain the number of characters minus 1 between sucessive
  450. horizontal sync pulses, the horizontal border and the interval between
  451. horizontal sync pulses. The normal value for this is usually set to 126.
  452. Register 1 specifies how many of the positions as specified in register 0 can
  453. actually be used to display characters.  The default value for this is 80. 
  454. The VDC can take values less than 80 and thus, will only display that many
  455. characters. A useful effect can be a sweep from the right by incrementing
  456. the value here from 1 to 80. Register #2 specifies the starting character 
  457. position at which the vertical sync pulse begins. Thus, it also determines
  458. where on the active screen characters appear. A default value of 102, 
  459. increasing the value moves the screen to the left, decreasing it moves it to
  460. the right.
  461.  
  462. ^2 : Register #3:     Vertical / Horizontal Sync Position.
  463. ---- Register #7:     Vertical Sync Position 
  464.  
  465.   In Register 3, Bits 0-3 of this register specifies the horizontal sync width
  466. and should be equal to 1 + the number of pixels per character. Thus, the value
  467. here is normally 1+8 or 9. Bits 4-7 of register 3 specify the vertical sync
  468. width and normally contains a value of 4. For interlace sync and video mode,
  469. use a value that is twice the number of scan lines desired. Register #7 allows
  470. for adjustment of where the vertical sync will be generated allowing shifting
  471. of the actual display up and down. Normally, a value of 4, decreasing the value
  472. will move the screen down, increasing it will appear to move it upwards.
  473.  
  474. ^3 : Register #4:     Vertical Total
  475. ---- Register #5:     Vertical Total Fine Adjust
  476.      Register #6:     Vertical Displayed
  477.  
  478.   Register #4 of this register determines the total number of screen rows, 
  479. including the rows for the active display, and the top and bottom borders in
  480. addition to that of the vertical sync width. The value held here is normally
  481. a value of 32 for NTSC systems (US Standard) or 39 for PAL(European) systems.
  482. Register #5 holds in bits 0-4 a "fine-adjust" where any extra scan lines that
  483. are necessary to make up the display can be specified here. The value here is 
  484. normally a 0 in both the NTSC and PAL initializations by the kernal and bits
  485. 5-7 are unused, always returning a binary 1111. Register #6 specifies the total
  486. number of the vertical character positions (as set in Register 4) that can be
  487. used for actual display of characters. Thus, this register usually holds a 
  488. value of 25 for a standard 25-row display.
  489.  
  490. ^4 : Register #8:     Interlace Mode Control
  491. ----
  492.   
  493.   Register 8 allows control of various display modes the 8563 can generate. 
  494. Bits 0 and 1 are the only bits used in this register, the rest always reading
  495. a binary 1. Bits 0 and 1 are configured as follows:
  496.  
  497.   Binary %00, %10 - NonInterlaced Mode
  498.               %01 - Interlaced Sync
  499.               %11 - Interlaced Sync and Video
  500.  
  501.   Note that the default value is $00 which is standard, non-interlaced.
  502. Interlaced sync draws each horizontal scan line twice but appears to suffer from
  503. an annoying jitter due to how it is drawn. Interlaced Sync and Video draws twice
  504. as many lines, thus doubling the resolution. However, it also suffers from
  505. jitter and that is why most monitors suffer horribly when using programs that 
  506. support more than 30 rows. Note that for interlaced sync and video, the
  507. following registers will need to be changed: #'s: 0,4,6,7,8.
  508.  
  509. ^5 : Register #9:     Total Scan Lines Per Character
  510. ---- 
  511.   
  512.   Bits 0-4 of this register are the only relevant ones, the rest returning a 
  513. binary 1. Bits 0-4 determine the character height in scan-lines of displayed
  514. characters and allow up to scan-line heights of 32 scan lines. The VDC normally
  515. sets aside 16 bytes for each character (normally, each byte is equivlent to
  516. 1 scan line) so the value here could be increased to 16-1 and a double-height
  517. character set could be loaded in. Note, however that values less than 16 will
  518. tell the VDC to use a 8,192 byte character set (normal) while specifying values
  519. greater than 16 will make it use 32 bytes per character even if some of the 
  520. bytes are not used.
  521.  
  522. ^6 : Register #10:    Cursor Mode / Start Scan Line     
  523. ---- Register #11:    Cursor End Scan Line.
  524.      Register #29:    UnderLine Scan Line Control.
  525.   
  526.   These registers allow the user to specify the cursor blink mode, as well as
  527. the starting and ending scan lines for the cursor (allowing a full solid, 
  528. an underline, Bits 0-4 of regiseter #10 determines the scan line within each
  529. position for the top of the cursor. Normally, this value holds a value
  530. of 0 for the block cursor, or a value of 7 for the underline cursor. Bits 5-6 of
  531. Register 10 specify the blink rate for the cursor. A value of %00 specifies no
  532. blink, ie: a solid cursor. A value of %01 specifies no cursor, a value of %10
  533. specifies a flash rate of 1/16 the screen refresh rate, while a value of %11
  534. specifies a flash rate of 1/32 the screen refresh rate. Note that bit 7 of
  535. Register 10 is unused and normally returns a binary 1. Register 11 specifies
  536. the bottom scan lines in bits 0-4, the other unused bits returning a binary 1.
  537. The value held in these bits usually is 7 for the block and underline cursor
  538. modes in the normal 128 editor. Register #29 is used to indicate where the scan
  539. line is "set" in the character. The "underline" is only 1 pixel tall and thus,
  540. this location just indicates the start and end location in pixels, similair to 
  541. registers #10 and #11 being the same value. Note that bits 5-7 of this register
  542. is unused and normally return a binary 1.
  543.  
  544. ^7 : Register #12:    Display Start Address (Hi)
  545. ---- Register #13:    Display Start Address (Lo)
  546.      Register #14:    Cursor Position       (Hi)
  547.      Register #15:    Cursor Position       (Lo)
  548.      Register #20:    Attribute Start Addrs (Hi)
  549.      Register #21:    Attribute Start Addrs (Lo)
  550.  
  551.   Note first, that all of these registers are grouped in Hi byte, Lo byte order
  552. which is usually different from the 6502 convention of low byte, hi byte (ie: 
  553. in normal 6502 ml, $c000 is stored as $00 $c0, however in the 8563 it would be
  554. stored as $c0 $00).  Registers 12 and 13 determine, where in VDC memory the
  555. 8563 is the start of the screen. Incrementing this value by 80 (the number of
  556. characters per line) and with a little additional work can provide a very 
  557. effecient way of having a screen that "seems" to be larger than just 80x25.
  558. The cursor position in registers 14 and 15 reflect the actual character in
  559. memory that the cursor currently lies over. If it's not on the display screen,
  560. then it is not displayed. Registers 20 and 21 reflect where in the 8563 memory
  561. attribute memory is held. Attribute memory refers to the character attributes
  562. such as flash, inverse video, color etc that can be set for each character.
  563.  
  564. ^8 : Register #16:    Light Pen Vertical
  565. ---- Register #17:    Light Pen Horizontal
  566.  
  567.   These registers return the light pen position and refer to the actual 
  568. character positions on screen (ie: values ranging from 1..25 for vertical).
  569. The horizontal reading will not corrospond exactly to character positions, but
  570. will range from values of 27-29 to 120 depending on the edge of the screen.
  571. It's recommended that the horizontal character position is given more tolerance
  572. than the vertical light pen position for this reason.
  573.  
  574. ^9 : Register #18:    Update Address (Hi)
  575. ---- Register #19:    Update Address (Lo)
  576.      Register #24:7   Copy / Fill Bit
  577.      Register #30:    Word Count(-1) 
  578.      Register #31:    Data
  579.      Register #32:    Block Copy Src (Hi)
  580.      Register #33:    Block Copy Src (Lo)
  581.  
  582.   These registers allow control and manipulation of the 16k or 64k block within
  583. the 8563 memory.  Registers 18 and 19 point to where in VDC memory the next
  584. read or write will take place from. Register 30 specifies the number of bytes
  585. - 1 to copy or fill depending on bit # 7 of register #24. Normally, the 8563 
  586. will automatically perform the designated operation (of what bit 7 of register
  587. #24 says) when register #31 (the data byte) is written to. Registers 18 and 19
  588. automatically update upon read or write, so that is why register #30 specifies
  589. a value 1 less than what is actually needed. Register #31, as already mentioned
  590. is the byte to write for register #30 times (or copy from Register#32 / #33).
  591. If register #24, bit 7 is specified as a binary 1 then the memory is copied from
  592. the address in VDC memory pointed to by registers #32 and #33.
  593.  
  594. ^A : Register #22:    Character Horizontal Size Control
  595. ----
  596.  
  597.   Bits 0-3 of this register determines how many horizontal pixels are used
  598. for each displayed character. Values greater than 8 here result in apparent
  599. gaps in the display. Inter-character spacing can be achieved by setting this
  600. value greater than that of bits 4-7. Bits 4-7 determine the width of each
  601. character position in pixels. Thus, while bits 0-3 allocate n-pixels, bits
  602. 4-7 specify how many of those pixels are used for character display.
  603.  
  604. ^B : Register #24:5   Reverse Screen Bit
  605. ---- Register #24:6   Blink Rate for Characters.
  606.  
  607.   Bit #6 specifies for the VDC for all pixels normally unset on the VDC screen
  608. to be set, and all set pixels to be unset.  Bit #5 specifies the blink rate
  609. for all characters with the blink attribute. Setting this to a binary 1
  610. specifies a blink rate of 1/32 the refresh rate, while a binary 0 is equivlant
  611. to a blink rate 1/16th of the refresh rate.
  612.  
  613. ^C : Register #24:0-4 Vertical Smooth Scroll
  614. ----
  615.    
  616.   The 8563 provides for a smooth scroll, allowing bits 0-4 to function as an 
  617. indicator of the number of bits to scroll the screen vertically upward.
  618.  
  619. ^D : Register #25:7   Text or Graphics Mode Indicator Bit
  620. ---- Register #25:6   Monochrome Mode Bit
  621.      Register #25:5   Semi-Graphics Mode
  622.      Register #25:4   Double-Pixel Mode
  623.  
  624.   The 8563 allows the implementation of a graphics mode, in where all of the 16k
  625. of the screen may be bit-mapped sequentially resulting in a resolution of
  626. 640x200 (see Craig Bruce's 8563 Line-Plotting routine in the first issue for a
  627. more detailed explanation of this feature). Setting this bit to 1 specifies
  628. graphics mode, binary 0 indicates text mode.  Bit 6 indicates to the 8563 where
  629. to obtain its color information etc, about the characters. Bit 6 when it is a
  630. binary 0 results in the 8563 taking it's color information from bits 4-7 of 
  631. register 26. When this bit is a binary 1, the attribute memory is used to 
  632. obtain color, flash, reverse information. Also note than when this bit is a
  633. binary 1 that only the first of the two character sets is available. Bit #5
  634. indicates a semi-graphics mode that allows the rightmost pixel of any characters
  635. to be repeated through-out the intercharacter spacing gap. Activating it on the
  636. normal display will result in what appears to be a "digital" character font. The
  637. 8563 with bit #4 allows a pixel-double feature which results in all displayed
  638. horizontal pixels having twice their usual size. Thus, a 40 column screen is
  639. easily obtainable although the values in registers #00-#02 must be halved.
  640.  
  641. ^E : Register #25:    Horizontal Smooth Control
  642. ---- 
  643.  
  644.   This register is analogous to register #24 Vertical Smooth Control and 
  645. functions similairly. Increasing this bits moves the screen one pixel to the
  646. right, while decreasing them moves the screen one pixel to the left.
  647.  
  648. ^F : Register #26:    ForeGround / BackGround Color Register
  649. ----
  650.  
  651.   This register, in bits 0-3 specifies the background color of the display while
  652. bits 4-7 specify the foreground character colors when attributes are disabled 
  653. (via bit 6 of register #25).  Note, these are not the usual C= colors but are
  654. instead organized as follows:
  655.  
  656.    Bit Value  Decimal Value   Color
  657.    ----------------------------------       +-----------------------------+
  658.     %0000       0 / $00       Black         |  Note: Bit 0 = Intensity    |
  659.     %0001       1 / $01       Dark Gray     |        Bit 1 = Blue         |
  660.     %0010       2 / $02       Dark Blue     | RGBI   Bit 2 = Green        |
  661.     %0011       3 / $03       Light Blue    |        Bit 3 = Red          | 
  662.     %0100       4 / $04       Dark Green    |                             |
  663.     %0101       5 / $05       Light Green   +-----------------------------+
  664.     %0110       6 / $06       Dark Cyan
  665.     %0111       7 / $07       Light Cyan
  666.     %1000       8 / $08       Dark Red
  667.     %1001       9 / $09       Light Red
  668.     %1010      10 / $0A       Dark Purple
  669.     %1011      11 / $0B       Light Purple
  670.     %1100      12 / $0C       Dark Yellow
  671.     %1101      13 / $0D       Light Yellow
  672.     %1110      14 / $0E       Light Gray (Dark White)
  673.     %1111      15 / $0F       White
  674.  
  675. ^G : Register #27:    Row Address Display Increment
  676. ----
  677.  
  678.   This register specifies the number of bytes to skip, when displaying
  679. characters on the 8563 screen. Normally, this byte holds a value of $00 
  680. indicating no bytes to skip; however typically programs that "scroll" the 
  681. screen do so by setting this to 80 or 160 allowing the program to then alter
  682. the Screen Start (Registers #12 and #13) and appear to "scroll". Note the 
  683. normal C= 128 Kernal Screen Editor does not support this function.
  684.  
  685. ^H : Register #28:7-5 Character Set Address
  686. ----
  687.  
  688.   These bits indicate the address of screen memory * 8k. Thus the values in 
  689. these bits may be multiplied by 8192 to obtain the starting character set
  690. position (normall these bits hold a value of $01 indicating the character
  691. set begins at 8192).  Note that the character set is not in ROM, but is usually
  692. copied to 8192 when the computer is first turned on and the 8563 is initialized.
  693. (Examine the INIT80 routine at $CE0C in bank 15).
  694.  
  695. ^I : Register #28:4   Ram Chip Type
  696. ----
  697.   
  698.   This bit specifies whether 16k or 64k of RAM has been installed. Note, however
  699. that this value may not reflect future upgrades from 16k to 64k.  It is best,
  700. if a program is dependant on 64k to write to an address > 16k and see if it
  701. is mirrored at any other location in another section of memory. This bit has a
  702. binary value of 0 if 16k or 1 if 64k RAM.
  703.  
  704. ^J : Register #34:    Display Enable Begin
  705. ---- Register #35:    Display Enable End
  706.  
  707.   The 8563 can extend it's horizontal blanking interval to blank a portion of
  708. the displayed screen.  The value in register #34 determines the rightmost 
  709. blanked column, and register #35 determines the leftmost blanked column.  Note
  710. that a value of 6 usually corresponds to the leftmost column of the screen, 
  711. while a value of 85 corresponds to the rightmost column.  This feature is useful
  712. for "inside-out" wraps in which both the right and left margin can close-in on
  713. text, the text can be cleared, these values reset etc... 
  714.  
  715. ^K : Register #36:    Refresh Cycles per Scan Line
  716. ----
  717.  
  718.   This register in bits 0-3 allows the user (if he had any reason) to specify
  719. the number of refresh cycles for memory for the ram.  Setting this value too
  720. low may cause the RAM to not remember all the information.  Changing this value
  721. gives some advantage, in terms of display speed increases but is not advised. 
  722. The value normally held here is $05, for five refresh cycles per scan line.
  723.  
  724.                            +--------------------------+
  725.                            | 8563 Memory Organization |
  726.                            +--------------------------+
  727.  
  728.   Normally, the extra memory of the C=128's equipped with 64k goes unused (48k
  729. worth) unless programs like Basic-8 etc, take advantage of it. There are various
  730. mod files describing the upgrade from 16k to 64k and it is _strongly_ advised
  731. (although the author has not yet done so) and be aware that ***OPENING YOUR
  732. COMPUTER JUST TO LOOK, YOU MAY MESS IT UP*** and it is _strongly_ advised that 
  733. you contact a person experienced with electronics to perform the upgrade for
  734. you.  Note also that some mail order companies are offering an "up-grade board"
  735. which plugs into the 8563 slot and does not involve desoldering the RAM chips.
  736.  
  737.   Now, the 8563 uses the 16k of memory (it ignores the extra 48k of memory when
  738. it's got 64k, thus the following applies also to the 8563's equipped with 64k
  739. of memory) and normally, has the following memory map:
  740.  
  741.   $0000 - $07ff - Screen Memory
  742.   $0800 - $0fff - Attribute Memory
  743.   $1000 - $1fff - Unused
  744.   $2000 - $2fff - UpperCase / Graphic Character Set   (Char Set #1)
  745.   $3000 - $3fff - LowerCase / UpperCase Character Set (Char Set #2)
  746.   
  747.                           +---------------------------+ 
  748.                           | Writing to 8563 Registers |
  749.                           +---------------------------+ 
  750.  
  751.   Now how do we write to these registers we've learned so much about? There's
  752. several ways depending on how lazy you are.  The pure-ml version:
  753.  
  754. WRITING TO A REGISTER:
  755.  
  756.    writereg = * ; this routine writes .a to register # .x, Asssumes I/O block in
  757.          stx $d600
  758.        - ldx $d600
  759.          bpl -
  760.          sta $d601
  761.          rts
  762.    
  763.   also, in bank 15 there is a similair routine at $cdcc. Calling it at $cdca
  764. loads .x with a value of 31 indicating the data register which is often useful.
  765.  
  766.   From basic, just use a SYS 52684, value, register#
  767.  
  768.  READING FROM A REGISTER:
  769.    
  770.    readreg = * ; this routine returns the contents of register # .x in .a
  771.                ; Assumes I/O block switched in
  772.         stx $d600
  773.       - ldx $d600
  774.         bpl -
  775.         lda $d601
  776.  
  777.    or use the routine in bank 15 at $cdda.  From basic, a SYS 52698,,register#
  778. and then a RREG A returns the value in variable A. 
  779.  
  780.                           +--------------------+
  781.                           | Further 8563 Notes |
  782.                           +--------------------+ 
  783.  
  784.   Many C=128 owners are still using their monitors they had when they had their
  785. C=64's and are able to use the 80 column screen through a "converter-cable"
  786. (basically taking pin 7 of the RGBI port and feeding it as raw video).  There
  787. is also a text file out explaining how to take the R,G,B,I pins on that port
  788. to display shades of gray on a monochrome monitor (basically tying resistors
  789. with diodes across each color pin and then joining them).  There is relief!! :-)
  790.  
  791.   The 8563 is a chip full of cabibilities waiting to be found and developed. I'd
  792. be interested in seeing any code / techniques that readers of this net-mag have
  793. found.  Given that enough are submitted, a possible listing of some of the 
  794. better tricks and techniques might be possible in the future.
  795.  
  796. ===========================================================================
  797. FILE SPLITTER  - Mark Lawrence      9152427d@levels.unisa.edu.au
  798. =============
  799.  
  800. This program stemmed from the inability of XLINK to transfer CS-DOS from my pc
  801. to my 128.  XLINK transfers about 43K (I think), whereas CS-DOS was about 48K.
  802.  
  803. Rather than do the whole thing at once, why not cut the job up into more
  804. sizeable pieces, transfer the program piece by piece, and then reassemble the
  805. pieces at the other end?
  806.  
  807. And so eventuated the birth of SPLIT :-)
  808.  
  809. SPLIT, written entirely in Turbo Pascal, allows you to split DOS files into
  810. smaller pieces - you can either tell it a size to split the files into, or
  811. tell it a number of files to create.  You then give SPLIT the base filename
  812. for the new files WITH NO EXTENSION - SPLIT will give the new files their own
  813. extensions, and SPLIT will then create these files to your liking.
  814.  
  815. Just transfer the following program to Turbo, compile it, and away you go!!!
  816.  
  817. Hopefully, the program is commented enough to give you a fair idea of what's
  818. going on - although it isn't at all complicated to understand.
  819.  
  820. At some points I have comments that seem the least important - END { CASE } -
  821. they are to help me when I program...  I find it easy to lose track of which
  822. END is for what, stuff up my indentation, lost bits and pieces, delete the
  823. wrong parts, etc, etc.
  824.  
  825. I found it helped me, so it may help others.
  826.  
  827. If you need any further explanation, just let me know :-)
  828.  
  829. Another interesting thing I discovered about XLINK.  It doesn't transfer the
  830. files to the correct size.  I think (haven't had time to sit down and check
  831. it out yet) it transfers to the nearest 256, 512 or 1024 byte boundary.  If
  832. your file doesn't reach the boundary, it will pad the rest out with zeroes
  833. I think.  So, when you go to reassemble the file, it's got all this garbage
  834. in places where it shouldn't be, and the thing won't work.
  835.  
  836. So, when SPLITting a file, specify the size to a multiple of one of these
  837. boundaries.
  838.  
  839. Then, using a m/c monitor, load all the parts in together.
  840.  
  841. I'll try to set aside a little time in the not too distant future to write a
  842. m/c program to join the parts for you, since it can get confusing reassembling
  843. the parts by hand, and the built in dos copy that commodore so kindly graced us
  844. with is so darned fast <cough> <cough> :-)
  845.  
  846. [Ed. Note: While the dos copy command is slow.... for those of you who are
  847.  impatient try using somethine like the following to join files togather making
  848.  sure that there's enough space on the disk:
  849.      open15,8,15,"c0:name=name1,name2...": close15]
  850.  
  851.  
  852. So, good luck and enjoy!
  853.  
  854. ---------------------------------------------------------------------------
  855. Program Split (input,output);
  856.  
  857. Uses Dos;
  858.   { uses specific file handling routines }
  859. Var
  860.    InFile,Outfile                : File of Byte;
  861.    Count,Number,Size,NewSize,
  862.    Last,Counter                  : Longint;
  863.    InFileName,Newfile,OutFileName: String;
  864.    S,P                           : PathStr;
  865.    D                             : DirStr;
  866.    N                             : NameStr;
  867.    E                             : ExtStr;
  868.    SplitType,Check               : Char;
  869.    Data                          : Byte;
  870.    Extension                     : String[3];
  871.  
  872. Begin
  873.    For count := 1 to 25 do
  874.       Writeln;
  875.       { Dumb way to clear the screen :-)  }
  876.  
  877.    Writeln ('*********************************************************');
  878.    Writeln ('*  FILE SPLITTING UTILITY V0.01 (C) 1992 MARK LAWRENCE  *');
  879.    Writeln ('*               (-: MADE IN AUSTRALIA :-)               *');
  880.    Writeln ('*********************************************************');
  881.    Writeln;
  882.    Write   ('Enter Filename (including drive and path)    ');
  883.    ReadLn  (InFileName);
  884.    Writeln;
  885.  
  886.    For count := 1 to length (InFileName) do
  887.       InFileName[count] := UpCase ( InFileName[count] );
  888.       { change filename to all uppercase characters }
  889.  
  890.    FSplit(InFileName,d,n,e);
  891.       { split filename into it's respective parts:
  892.               d - Directory
  893.               n - Name
  894.               e - Extension }
  895.  
  896.    S := FSearch(InFileName,GetEnv(D));
  897.       { search for file FILENAME in directory D }
  898.  
  899.    if S = '' then
  900.       writeln ('*ERROR*     File "',InFileName,'" not found.')
  901.       { S equals '' (nothing) if FILENAME doesn't exist }
  902.  
  903.    Else
  904.    Begin
  905.       Assign  (Infile,InFileName);
  906.       Reset   (Infile);
  907.       { Open the Input File }
  908.  
  909.       Size := FileSize (InFile);
  910.       { Get file size }
  911.  
  912.       Writeln ('FileName:     ',InFileName);
  913.       Writeln ('FileSize:     ',Size,' Bytes.');
  914.       Writeln;
  915.       { Show file info }
  916.  
  917.       Writeln ('In which way would you like the file split?');
  918.       Writeln ('     (a)  Number of Bytes.');
  919.       Writeln ('     (b)  Number of Files.');
  920.       Repeat
  921.          Write ('Enter your selection    ');
  922.          Readln (SplitType);
  923.          SplitType := UpCase(SplitType);
  924.       Until (SplitType >= 'A') and (SplitType <= 'B');
  925.       { let user choose which way to split file }
  926.  
  927.       Writeln;
  928.       Case SplitType of
  929.          'A': Begin
  930.             { split by number of bytes }
  931.             Write ('Enter byte size of new files    ');
  932.             Readln (NewSize);
  933.             Writeln;
  934.             If (NewSize > Size) then
  935.                Writeln ('Hey - Even I can''t do that!!!')
  936.             Else
  937.             begin
  938.                Number  := Size div NewSize;
  939.                Last    := Size - Number * NewSize;
  940.                Number  := Number + 1;
  941.                Write ('Enter Base Filename (including drive and path)    ');
  942.                Readln (NewFile);
  943.                Writeln;
  944.                Writeln ('Creating ',Number,' new files...');
  945.             End;
  946.          End; { A }
  947.  
  948.          'B': Begin
  949.             { Split by file size }
  950.             Write ('Enter number of new files: ');
  951.             Readln(Number);
  952.             Writeln;
  953.             NewSize := Size div Number + 1;
  954.             Last    := Size - (Number - 1) * Newsize;
  955.             Number  := Number;
  956.             Write ('Enter Base Filename (including drive and path)    ');
  957.             Readln (NewFile);
  958.             Writeln;
  959.             Writeln ('Creating ',Number,' new files...');
  960.          End; { B }
  961.       End;  { Case }
  962.  
  963.          Writeln;
  964.  
  965.          For Count := 1 to Number do
  966.          { NUMBER new files will be created }
  967.  
  968.          Begin
  969.             If Count = Number then
  970.                NewSize := Last;
  971.             { More often than not, the files won't divide evenly from the
  972.               original.  So, the last file will be smaller than the rest.
  973.               Because of this, I previously calculated the size of the final
  974.               file, and here check if we're up to the last part yet - and if
  975.               we are, I set the size of the last file accordingly }
  976.  
  977.             Str(Count,Extension);
  978.             { Make EXTENSION a string representation of COUNT, to be added to
  979.               the OutFileName to make things a tad easier }
  980.  
  981.             OutFileName := Concat(NewFile,'.',Copy('00',1,3-Length(Extension)),E
  982.             { Create filename based on which part we're up to }
  983.  
  984.             Assign  (OutFile,OutFileName);
  985.             Rewrite (Outfile);
  986.             { Open each Output File }
  987.  
  988.             Write   ('Creating ',OutFileName,'... ');
  989.  
  990.             For Counter := 1 to NewSize do
  991.             { Write to each Output File }
  992.  
  993.             Begin
  994.                Read  (Infile,Data);
  995.                Write (OutFile,Data);
  996.                { Transfer data from input file to output file }
  997.             End;
  998.  
  999.             Close   (Outfile);
  1000.             { Close each Output File }
  1001.             Writeln ('Done!');
  1002.  
  1003.          End;
  1004.  
  1005.       Writeln;
  1006.       Writeln ('Job Completed :-)');
  1007.  
  1008.    end;
  1009.  
  1010.    For Counter := 1 to 3 do
  1011.       Writeln;
  1012.    { Make a bit of space when finished :-) }
  1013. end.
  1014.  
  1015. ================================================================================
  1016. BANKING ON GEOS
  1017.  
  1018. by Robert A. Knop Jr.
  1019.  
  1020. I. Introduction
  1021.  
  1022. GEOS was originally written for the Commodore 64.  When Berkeley Softworks
  1023. came out with GEOS128 (and, for a time, it wasn't clear that they would; then,
  1024. it looked like they would release a GEOS128 that wouldn't support the 80
  1025. column screen; finally, the release of GEOS128 did turn out to be a full 128
  1026. program), it was largely compatible with GEOS64.  Applications could share
  1027. documents (a geoPaint file is a geoPaint file), and even many GEOS64
  1028. appliations run on the 128 in 40 columns.  This heritage is also evident to
  1029. the GEOS programmer.
  1030.  
  1031. As we all know, the C-128 has two 64K RAM banks; the C-64 only has one 64K RAM
  1032. "bank."  Thus, of course, all of GEOS64 goes into that one 64K RAM space.
  1033. This includes the Kernal as well as the space available to applications.  Once
  1034. the Kernal, graphics screens, and so forth, have claimed their RAM, the GEOS
  1035. programmer is left with 23.75K of memory from $0400 to $5fff.
  1036.  
  1037. To a cursory "glance," the GEOS128 programming environment looks very much
  1038. like the GEOS64 programming enviroment.  You still have $0400 to $5fff
  1039. available for applications; graphics screens and variables are in the same
  1040. place; the Kernal jump table is the same (with some 128 specific additions).
  1041. What happened to the other 64K that the 128 has available?
  1042.  
  1043. As it turns out, the core of GEOS128- including the application program space,
  1044. the 40 column foreground and background screen, and the Kernal jump table- are
  1045. all in the 128's RAM block 1, what GEOS calls FrontRAM.  To us 128 programmers
  1046. used to RAM block 0 being the "main" RAM block, this may sound odd.  However,
  1047. it actually makes sense.  First of all, since GEOS is an operating system in
  1048. and of itself, and applications almost never need to call the C128's Kernal
  1049. routines, the application no longer needs access to Bank 15.  Second, it
  1050. allows GEOS128 to keep much of its memory map the same as GEOS64; it can use
  1051. the memory range from $200-$3ff in RAM 1 without worrying about disturbing key
  1052. system routins like STAFAR which are in the same memory range in RAM 0.
  1053.  
  1054.  
  1055. II. Yeah, Yeah, But What Happened to RAM 0 Anyway?
  1056.  
  1057. It's still there.  Some of RAM 0 is used by GEOS128 to improve the system
  1058. performance and to take advantage of the 128's unique features.  (For
  1059. instance, the code for the "software sprites" seen on the 128's 80 column
  1060. screen is found beneath $2000 in RAM 0.)  Fortunately, some space does remain
  1061. available for an application to use.  In RAM 0, the 32K memory space between
  1062. $2000 and $9fff is not normally used by GEOS, and is ALMOST available for
  1063. application use [1].
  1064.  
  1065. Why do I say "almost"?  The problem is desk accessories.  When GEOS 64 loads a
  1066. desk accessory (DA), it must load it into the same application space as the
  1067. application loading the DA.  The memory that the DA will used is first saved
  1068. to disk in a swap file.  Under GEOS128, the routine LdDeskAcc, instead of
  1069. saving a swap file to disk, copies the memory to be overwritten by the DA to
  1070. RAM0 between $2000 and $9fff.  So, if your application uses DA's (and it is
  1071. highly recommended that major applications support DA's), you have to be
  1072. careful using the space between $2000 and $9fff.  You can use it as temporary
  1073. swap space within routines- but you cannot assume that it will remain intact
  1074. whenever your routine returns to the GEOS MainLoop with your application in a
  1075. state that will allow the loading of DA's.
  1076.  
  1077. Nowadays, RAM 0 is not the be-all and end-all.  GEOS128 was written for the
  1078. C=128, not the C=256.  Consequently, if you have expanded your 128 to 256K or
  1079. 512K as described in the articles by Richard Curcio in Twin Cities 128 Issues
  1080. #30 and #31 [2,3], you have free use of RAM 2-3 (256K) or RAM 2-7 (512K).
  1081. (Note that you should not touch RAM 4-7 on a 512K 128 if you want to be
  1082. compatible with task switching as described in TC128 #31.  Also, although GEOS
  1083. right now does not run in the 2nd 256K, applications should not assume they
  1084. are in the 1st 256K, and thus should be careful with the 512K mode bits (4-5)
  1085. in the MMU Ram Configuration Register (RCR), $d506.)  While the number of
  1086. people with 256K and 512K 128's is now small, you can be sure that it will
  1087. increase when the promised ZIP accelerator board for the 128 comes out; the
  1088. current specs for the ZIP board include provisions for memory expansion on the
  1089. board.
  1090.  
  1091. RAM 2-3 provide almost another complete 128K available for your application to
  1092. use.  So how do you go about accessing this?
  1093.  
  1094.  
  1095. III. Storing Data In Other RAM Blocks
  1096.  
  1097. The most obvious use for RAM blocks other than FrontRAM (which is the only
  1098. block where GEOS Kernal routines are available) is as data storage.  For
  1099. instance, one could visualize a geoPaint previewing utility which loads and
  1100. decompacts an entire geoPaint document at once to RAM 2.  (The full
  1101. decompacted geoPaint document would reqire 56.25K.)  One could then quickly
  1102. scroll through the document by just copying the relevant portions of the
  1103. bitmap from RAM 2 to the foreground screen.  Or, if one were really bold, one
  1104. could just redirect the VIC screen memory to the relevant range in RAM2 using
  1105. the proper MMU and VIC registers.  (This would actully require use of both RAM
  1106. 2 and 3, since VIC screen locations are quantized to 8K; you lose the use of
  1107. the highest 8K, since you don't want to overwrite the MMU registers at
  1108. $ff00-$ff05; additional practical considerations make use of the lowest 8K
  1109. difficult.)
  1110.  
  1111. GEOS128 provides a few routines for easily moving data between FrontRAM and
  1112. what it calls BackRAM (but we know it just means RAM 0).  Happily, these
  1113. routines work quite admirably with RAM 2 and 3.  (To access RAM 4-7, fiddle
  1114. bits 4 and 5 of the MMU RCR to make the desired RAM blocks appear to the
  1115. system as virtual RAM 2 and RAM 3, then call these routines.)  The core
  1116. routine is DoBOp, which is summarized below [4]:
  1117.  
  1118. ***********************************************************************
  1119. DoBOp=$c2ec:  Copy/verify memory between RAM blocks on the C-128.
  1120.  
  1121. Pass:
  1122.    r0   : ADDR1 - address of first ("source") memory range
  1123.    r1   : ADDR2 - address of second ("destination") memory range
  1124.    r2   : COUNT - number of bytes to operate on
  1125.    r3L  : A1BANK - bank of ADDR1 (e.g. 1=FrontRAM, 0=BackRAM)
  1126.    r3H  : A2BANK - bank of ADDR2
  1127.    y    : MODE - operation to perform
  1128.  
  1129.  
  1130. Returns: r0-r3 unchanged
  1131.  
  1132.   when verifying: x=$00 if two ranges match, x=$ff if they don't match
  1133.  
  1134.  
  1135. Destroys: a,x,y
  1136.  
  1137.  
  1138. The operation mode is passed in y as follows:
  1139.  
  1140.         bit0  bit1   Description
  1141.         ----  ----   -----------
  1142.          0     0      Move from memory at ADDR1 to memory at ADDR2
  1143.          0     1      Move from memory at ADDR2 to memory at ADDR1
  1144.          1     0      Swap memory at ADDR1 and ADDR2
  1145.          1     1      Verify (compare) memory at ADDR1 and ADDR2
  1146. ***********************************************************************
  1147.  
  1148. (r0, r1, etc. are all the standard BSW symbols defined in the Official GEOS
  1149. Programmer's Reference Guide [5], and that come in the file geosSym with
  1150. geoProgrammer.)
  1151.  
  1152. There are a number of additional routines which are also provided for
  1153. programmer convenience which automatically set the MODE in the y register for
  1154. you.  In all of these routines, r0-r3 have the same meaning as they do in
  1155. DoBOp.
  1156.  
  1157. Routine     Address  MODE   Description
  1158. -------     -------  ----   -----------
  1159. MoveBData    $c2e3    00    Copy data at ADDR1 to ADDR2
  1160. SwapBData    $c2e6    10    Swap data between ADDR1 and ADDR2
  1161. VerifyBData  $c2e9    11    Compare data at ADDR1 and ADDR2
  1162.  
  1163. I have written a short demonstration program which shows the use of MoveBData
  1164. and VerifyBData.  The full source to this program, BMover, is available
  1165. through anonymous ftp at tybalt.caltech.edu (in the /pub/rknop/hacking.mag
  1166. directory) as well as elsewhere.  If you can't find it, contact me (addresses
  1167. are below).  The source is geoProgrammer code, in geoWrite 2.1 format.  All of
  1168. the files you need (except geosSym and geosMac, which come with geoProgrammer)
  1169. are in the bmover.sfx archive.
  1170.  
  1171. The first function of BMover repeatedly copies a single block on RAM 1 to
  1172. successive parts in memory in any other specified bank.  The destination bank,
  1173. destination addresses, size of the block to move, and number of times to copy
  1174. it are all set in constants found at the beginning of the source file BMovAsm.
  1175. Once the moves (which use MoveBData) have all been performed, BMover uses
  1176. VerifyBData to make sure that all of the blocks were copied succesfully.
  1177.  
  1178. For informational purposes, BMover reports the amount of time (in tenths of
  1179. seconds) it took to perform all of the moves.  (For this, I use the CIA #1 TOD
  1180. clock, saving its value at the beginning and end of the move, and subtracting
  1181. to get the difference.)  I ran a trial where I copied an 8K block of memory to
  1182. RAM 2 7 times (thus filling 56K of RAM 2).  These moves together took 1 second
  1183. at 2 MHz, and 2.2 seconds at 1 Mhz.  56K/second may be no DMA, but it's faster
  1184. than a burst load!
  1185.  
  1186.  
  1187. IV. Executing Routines In Other Banks
  1188.  
  1189. So, you've written an object oriented drawing program that stores its list of
  1190. objects (32 byte records) in RAM 2.  Or, you have a database that has records
  1191. in RAM 0.  You want to delete one record at the beginning of the list, which
  1192. means moving all of the subsequent records down over the memory freed up by
  1193. the deletion.  There are a few things you can do.  One, you can use Craig
  1194. Bruce's dynamic memory allocation routines (highly recommended).  Two, you can
  1195. repeated do MoveBData to move memory from RAM 2 (or 0) to a buffer in FrontRAM
  1196. and back.  Or, you can write a short mover routine in the RAM bank where all
  1197. the moving is going to happen.
  1198.  
  1199. This is just an example.  One can visualize other reasons for calling routines
  1200. in other RAM banks (what I call "extrabankal routines").  There exist no GEOS
  1201. Kernal routines for calling extrabankal routines.  Additionally, since your
  1202. main application memory is in RAM 1, you are inable to use the 128 Kernal's
  1203. JSRFAR (which returns you to Bank 15).  So, we are left with implementing our
  1204. own JSRFAR.
  1205.  
  1206. GEOS128 normally operates with NO common memory enabled.  Thanks to one of the
  1207. less well-known features of the MMU, there is no need to enable common memory.
  1208. The MMU zero page registers ($d507 and $d508) allow you to locate the zero
  1209. page that the processor sees anywhere in RAM 0 or RAM 1.  What this means is,
  1210. no matter what your memory configuration is, the processor sees zero page in
  1211. the RAM block specified in $d508.  (Unless you have common memory enable, in
  1212. which case it is not a good idea to put ZP in RAM blocks other than RAM 0
  1213. [6,7].)  So, zero page is effectively common memory!
  1214.  
  1215. This provides for the possiblity of copying to zero page a short "switchboard"
  1216. routine, basically a reimplementation of JSRFAR, which configures the system
  1217. for the destination bank, jsr's to a routine, reconfigures the system for the
  1218. calling bank, and rts's.
  1219.  
  1220. I also demonstrate this technique in BMover.  The second function of BMover
  1221. first uses MoveBData to copy a routine to $2000 in DESTBANK (which is set
  1222. right now in the source code to RAM 0).  It then copies the routine ZPJSR to
  1223. $02, which stores DESTCFG in $ff00 and jsr's to $2000.  The routine at $2000
  1224. moves some data around in DESTBANK.  Once ZPJSR has returned the program flow
  1225. to FrontRAM, BMover calls VerifyBData to make sure everything worked.
  1226.  
  1227. While messing around in different banks, to be safe I dissable IRQ interrupts.
  1228. On a related note, geoDebugger 2.0 seems to have problems with programs
  1229. messing around with different banks.  It is not surprising that the BackRAM
  1230. debugger (which locates itself in RAM 0) would have trouble with programs that
  1231. tried to use RAM 0, but it also has trouble with programs that try to use RAM
  1232. 2 and 3.  This is true even when one uses the system routine MoveBData.  (I
  1233. found that I was sometimes able to make it past a call to MoveBData while in
  1234. the debugger, but that more often the system would hang.  This is all probably
  1235. an interrupt-related issue.)
  1236.  
  1237. If one is to be really classy, one doesn't actually have to copy the ZPJSR
  1238. routine to zero page.  One could assemble the application such that ZPJSR fell
  1239. to a known offset from a page boundry; then, use the MMU to point zero page to
  1240. the page containg ZPJSR.  Unfortunately, this technique did not work on my
  1241. 512K expanded 128.  The one incompatility I have found is that with the 512K
  1242. modification enabled (I do have a switch to disable it, don't worry), the MMU
  1243. fails to correctly see zero page in RAM 1 when requested to.  Richard Curcio
  1244. experimented with it, and it seems that when you try to relocate zero page to
  1245. a page in RAM 1, it is actually seen in RAM 3.  It is not yet clear whether
  1246. this is a problem with the 256K/512K modification, or if the MMU in a stock
  1247. 128 just relocates ZP to RAM 3 figuring that RAM 3 = RAM 1 (which is true on a
  1248. stock 128, but not on a 256K expanded 128!)
  1249.  
  1250.  
  1251.  
  1252. Anyone who wants to get ahold of the BMover source, or who has other
  1253. questions/comments/flames can contact me, Robert Knop, at the following
  1254. addresses:
  1255.  
  1256. InterNet: rknop@tybalt.caltech.edu
  1257. GEnie:    R.KNOP1
  1258. U.S. Mail:  Robert Knop
  1259.             123 S. Chester #3
  1260.             Pasadena, CA  91106
  1261.  
  1262.  
  1263. V. References
  1264.  
  1265. [1] William Coleman, 1989: "Inside GEOS 128"  _The_Transactor_ 9(4), p. 29.
  1266.  
  1267. [2] Richard Curcio, 1991: "Expanding the 128 Part One: 256K" _Twin_Cities_128_
  1268. #30, p. 7.
  1269.  
  1270. [3] Richard Curcio, 1992: "Expanding the 128 Part Two: 4 Mode 512K"
  1271. _Twin_Cities_128_ #31, p. 5.
  1272.  
  1273. [4] Berkeley Softworks, 1988: _The_Hitchhiker's_Guide_To_Geos_.
  1274.  
  1275. [5] Michael Farr, 1987: _The_Offical_GEOS_Programmer's_Reference_Guide_.
  1276. Bantam Books, New York/Toronto.
  1277.  
  1278. [6] Larry Greenly et. al, 1986: _Commodore_128_Programmer's_Reference_Guide_.
  1279. Bantam Books, New York/Toronto.
  1280.  
  1281. [7] Ottis R. Cowper, 1986: _Mapping_the_Commodore_128_.  Compute! Publications,
  1282. Greensboro, NC.
  1283.  
  1284. ==============================================================================
  1285. DYNAMIC MEMORY ALLOCATION FOR THE 128: Breaking the 64K Barrier
  1286.  
  1287. by Craig Bruce (csbruce@ccnga.uwaterloo.ca)
  1288.  
  1289. Although this article would be best described as extremely technical, I think
  1290. that it has something for everyone.  It could also be described as being
  1291. extremely long.
  1292.  
  1293. Below I have written a program that will read in the lines of a file, sort
  1294. them, and write then back out to another file.  Because of the nature of the
  1295. problem, the each line of the entire file must reside in the memory of the
  1296. computer.  I implement and use dynamic memory allocation such that the file to
  1297. be sorted can be larger than 64K, and I use a dynamic data structure such that
  1298. the memory is used very efficiently.  The memory routines were extracted from
  1299. a text editor called "Zed-128" which also breaks the 64K barrier and can edit
  1300. some humongous files (and very efficiently too).  Although implemented for the
  1301. C-128, the dynamic memory scheme could also be fairly easily (ie. in a single
  1302. lifetime) ported to the C-64.
  1303.  
  1304. ------------------------------------------------------------------------------
  1305.  
  1306. 1. INTRODUCTION
  1307.  
  1308. How many of us are sick and tired of the "64K limit" that a lot of programs for
  1309. the 128 and 64 seem to have?  Many terminal programs, text editors, and even
  1310. file copiers seem to be afflicted with this problem.  Another problem is that
  1311. programs often reserve large sections of memory for specific purposes (such as
  1312. the kill buffer of a text editor) and cannot reconfigure themselves (very
  1313. easily) for different demands.  Still another problem is that many programs do
  1314. not make use of a Ram Expansion Unit (if you are fortunate enough to have one)
  1315. to store your volumnous user data.
  1316.  
  1317. The way to overcome the limitations of the 64K architecture of the C128 and
  1318. C64 is to use dynamically allocated memory.  What this means is that
  1319. initially, all of the memory of the computer is free and when a user program
  1320. requires some memory to store user data, it calls a special subroutine that
  1321. allocates a given number of bytes of memory to the program to store the user
  1322. data.  And when the program is finished using that chunk of memory, it calls a
  1323. special subroutine to free the memory chunk and make it available for future
  1324. allocation requests.
  1325.  
  1326. One complication of this memory usage scheme is that a program has to keep
  1327. track of which chunks of memory it uses for what.  This is where dynamic data
  1328. structures come in.  The most important concept here is a pointer.  A pointer
  1329. is simply a variable that stores the address of some data structure (ie. some
  1330. chunk of memory).  If we know the address of a data structure, then we can
  1331. read it and modify it.
  1332.  
  1333. To overcome the problem of not knowing how many records will need to be
  1334. stored, records are often stored in lists, where every record contains a
  1335. pointer to the next record in the list, except for the last one, which
  1336. contains a special value that could not be mistaken for an ordinary pointer
  1337. (it is called the Null (or Nil for you Pascalers) pointer).  Thus, if we know
  1338. the address of the first record (by using a "head pointer"), then we have
  1339. sequential access to all of the records in the list.  If we want to add or
  1340. delete records from the list, then we must modify the other pointers such that
  1341. the consistency of the list is maintained.  Organizations other than simple
  1342. lists are also possible.
  1343.  
  1344. The implementation here is able to allocate RAM0 memory for storing user data
  1345. records, as well as RAM1 memory and even REU memory.  As long as the
  1346. application program keeps track of the pointers to its records, large volumes
  1347. of user data can be stored since it will be distributed among all of the
  1348. memory that is available from both the internal memory banks and the external
  1349. memory banks, thus breaking the 64K barrier.
  1350.  
  1351. ------------------------------------------------------------------------------
  1352.  
  1353. 2. FOR THE NOVICE HACKER
  1354.  
  1355. You get a sorting utility program.  This program implements the insertion sort
  1356. algorithm, so don't expect to break any speed records.  Also, the way that
  1357. dynamic memory is implemented here is more suited for large data structures
  1358. that will only be accessed slowly and infrequently (such as the current
  1359. document in a text editor); however, I wanted to come up with a useful utility
  1360. and I have never heard of a general file sorter for the 128 or 64.  The
  1361. insertion sort does, however, lend itself well to being used with dynamic data
  1362. structures in general, since you don't actually have to move anything; you
  1363. just change a couple of pointers in order to insert a line (record) between
  1364. two other lines.  Also, it turns out the the insertion sort is quite efficient
  1365. if your input file is already mostly or partially sorted.
  1366.  
  1367. The sort utility itself is completely machine language but assumes that the
  1368. input and output files are already opened, so a BASIC driver program is
  1369. required to set things up to and allow the user to easily change the sorting
  1370. parameters.  Such a program is listed here:
  1371.  
  1372.  1 i$="inputfile.txt" : id=8 : sf=1
  1373.  2 o$="outputfile.txt" : od=8
  1374.  3 :
  1375.  100 print"loading sort.bin..."
  1376.  110 bank 15
  1377.  120 bload"sort.bin",u(id)
  1378.  130 print"scratching old file..."
  1379.  140 scratch(o$),u(od)
  1380.  150 print"sorting..."
  1381.  160 open1,id,2,"0:"+i$
  1382.  170 open2,od,3,"0:"+o$+",s,w"
  1383.  180 sys dec("1300"),sf
  1384.  190 close2
  1385.  200 close1
  1386.  210 print"finished!"
  1387.  
  1388. Lines 1 and 2 set up the sorting parameters: the input and output filenames,
  1389. the input and output file device numbers, and the sorting field position.
  1390. Change the "sf" value to the position of the first character of the key
  1391. field.  The first position on the line is 1 (not 0).  (This corresponds to
  1392. what Zed uses for columns).  Starting from that position, the rest of the line
  1393. is used for the comparison that determines the order of the lines.  If a line
  1394. is encountered that is shorter than the position of the sorting field, the key
  1395. value is taken to be the Null String (which comes before any other string).
  1396.  
  1397. The program continues to load in the machine language (which fits into the
  1398. $1300 slot) and scratch the output file if it already exists.  Then the files
  1399. are opened, machine language is called, and the files are closed and the
  1400. program exits.  While reading the file, the program will split any lines that
  1401. are longer than 242 characters and treat them as multiple lines.
  1402.  
  1403. For testing the sort utility, I used a file that contains 1058 lines of the
  1404. following form:
  1405.  
  1406. ROXETTE                     MUST HAVE BEEN LOVE                       A01-1-01
  1407. ADAMS, BRYAN                SUMMER OF '69                             A05-1-10
  1408. JOEL, BILLY                 PRESSURE                                  M11-1-07
  1409. EAGLES                      NEW KID IN TOWN                           R06-2-04
  1410. ELECTRIC LIGHT ORCHESTRA    CALLING AMERICA                           R11-1-05
  1411. COCKBURN, BRUCE             WONDERING WHERE THE LIONS ARE             R14-1-03
  1412.  
  1413. As you may guess, it is a tape library.  The file is 83K in length.  I sorted
  1414. it on both my 1581 (with JiffyDOS) and my RamLink and then I sorted again the
  1415. file that I sorted in the first place.  The resulting execution times are as
  1416. follows:
  1417.  
  1418.     WITH EXPANSION MEMORY                WITHOUT EXPANSION MEMORY
  1419.  
  1420.  Ramlink regular = 110 seconds         Ramlink regular = 376 seconds
  1421.  Ramlink sorted  =  20 seconds         Ramlink sorted  =  24 seconds
  1422.  1581 regular    = 120 seconds         1581 regular    = 397 seconds
  1423.  1581 sorted     =  33 seconds         1581 sorted     =  55 seconds
  1424.  
  1425. You'll note that having expansion memory makes sort operate faster.  This is
  1426. because the REU Controller can transfer data around faster than the CPU can.
  1427. The effect is even more pronounced when using records longer than 78-character
  1428. lines of text.  This is why it is sensible to use expansion memory for general
  1429. data storage and accessing.  The reason why the execution times are so long is
  1430. that approximately 1058*529/2 = 280,000 "far memory" line-length fetches have
  1431. to take place, along with that number of "zpload"s and string comparisons, and
  1432. 1058 "malloc"s and "free"s.  Also, we all know that the Commodore file reading
  1433. and writing mechanisms are not severely swift.
  1434.  
  1435. ------------------------------------------------------------------------------
  1436.  
  1437. 3. FOR THE INTERMEDIATE HACKER
  1438.  
  1439. You get a dynamic memory allocation and usage package that can be incorporated
  1440. into your own programs.
  1441.  
  1442. 3.1. MEMORY PACKAGE CALLS
  1443.  
  1444. The package includes eight system calls:
  1445.  
  1446.  startup ()
  1447.  shutdown()
  1448.  zpload  ( [zp1]=FarPointer, .X=ZpAddr, .Y=Length )
  1449.  zpstore ( [zp1]=FarPointer, .X=ZpAddr, .Y=Length )
  1450.  fetch   ( [zp1]=FarPointer, (zw1)=Ram0pointer, .AY=Length )
  1451.  stash   ( [zp1]=FarPointer, (zw1)=Ram0pointer, .AY=Length )
  1452.  malloc  ( .AY=Length ) : [zp1]=FarPointer, .CS=error
  1453.  free    ( [zp1]=FarPointer, .AY=Length ) : .CS=error
  1454.  
  1455. The "(...)" means input parameters and ":" preceeds output parameters.  ".X"
  1456. and ".Y" refer to the processor registers and ".AY" means the 16-bit value
  1457. with the .A register holding the low byte and the .Y register holding the high
  1458. byte.  With "(zw1)" I am refering to the indirect pointer in the zero page
  1459. locations "zw1" (low byte) and "zw1+1" (high byte) ("zw" means zero page word
  1460. and it is assigned to locations $FE to $FF) and with "[zp1]" I am refering to
  1461. the three byte pointer value in locations "zp1" (address low byte), "zp1+1"
  1462. (address high byte), and "zp1+2" (bank number byte) ("zp" means zero page
  1463. pointer and it is assigned to addresses $FA to $FC).  This three-byte pointer
  1464. is refered to as a "Far Pointer".  The ".CS=error" means that if the routine
  1465. returns with the carry flag set, an error has occured.  The only possible
  1466. error return in this package is if malloc cannot find enough contiguous free
  1467. memory to satisfy your request.
  1468.  
  1469. You do not actually have to know what the bank numbers mean since they are
  1470. generated and used by the package as an opaque data type (parlez-vous
  1471. Modula-2?), but here is what they mean anyway.  A value of $3F means internal
  1472. bank RAM0 and a value of $7F means internal bank RAM1.  This works out
  1473. conveniently in the implementation, since these are the MMU configuration
  1474. register values for those two banks.  A value from $80 to $FE refers to an
  1475. expansion (REU) memory bank.  $80 means expansion bank0, $81 bank1, etc.  This
  1476. means that the package can support up to 8 Megs (minus 64K) of expansion
  1477. memory (and it does).  These values are convenient to use since after loading
  1478. the bank number into a register, the Negative flag of the processor will be
  1479. set (useful if handling expansion memory is a special case), and this value
  1480. can be put directly into the REU Controller's bank register.  I don't think
  1481. you have to worry about having the high bit be a "1" since it is done
  1482. consistently and I have never heard of an REU larger than 2 Megs.  A bank
  1483. value of $FF is used to represent the Null pointer.
  1484.  
  1485. The "startup" routine installs the common code, determines the size of your
  1486. REU, and initializes the dynamic memory allocation mechanism.  In order for
  1487. the package to access internal memory bank RAM1, it has to call a routine that
  1488. is in memory below address $0400.  Since the package starts at $1300, it has
  1489. to copy a few "common code" subroutines into low memory such that it can call
  1490. them later.  The common code is installed at address $0200, the BASIC input
  1491. buffer.  Don't overwrite this area while the package is in use.  The "sniff"
  1492. routine is called to determine the number of banks that your REU has.  Zero
  1493. banks means that you have no REU.  While sniffing, the package overwrites the
  1494. first four bytes of every existing expansion bank (unless you limit the number
  1495. of expansion banks that the package is allowed to use).  To initialize the
  1496. dynamic memory allocation, the "free" routine is called for RAM0, RAM1, and
  1497. each expansion bank.  RAM0 from $4000 to the top of BASIC memory ($FEFF) is
  1498. freed, RAM1 from $0400 to $FEFF is freed, and all expansion banks are freed
  1499. between addresses $0000 to $FFF7.  Thus, if you have no expansion memory, you
  1500. get about 110K free and if you have a 512K expander, you get about 620K free.
  1501.  
  1502. The "shutdown" routine doesn't actually have very much to do.  Basically, it
  1503. just zeros out the common code.  I did this so if you called the sort routine
  1504. from BASIC direct input mode, you would not get a "syntax error" from BASIC
  1505. trying to interpret the garbage left behind.  Now, when BASIC encounters a
  1506. zero, it stops interpreting.
  1507.  
  1508. The "zpload" routine will load the given number of bytes into zero page
  1509. starting at the given zero page address, from any far pointer address.  It
  1510. doesn't matter whether the far address is in internal or expansion memory; the
  1511. operation is the same.  This is the level of software that makes accessing the
  1512. different types of memory transparent to the user.  To load from RAM0, true
  1513. RAM0 is switched into context (did I mention that the package is meant to
  1514. execute with MMU configuration $0E in context - this configuration gives RAM0
  1515. from $0000 to $BFFF, the kernel ROM from $C000 to $FFFF and the I/O space on
  1516. top of the kernel ROM from $D000 to $DFFF - I call this the SYS or SYS0 bank)
  1517. and the transfer is done with a loop.  For a zpload from RAM1, a common code
  1518. routine is called that switches RAM1 into context, copies in a loop, and then
  1519. switches back to SYS0.  For an expansion memory pointer, the REU Controller
  1520. registers are set up and the transfer is performed.  The package will work
  1521. with whatever Zero Page is in context (with MMU register $D507), since it is
  1522. convenient to use your own zero page in your programs.  For transfers of less
  1523. than about 16 bytes, internal memory is faster, and for longer transfers,
  1524. expansion memory turns out to be faster.  For really long transfers (say, 80
  1525. bytes), using the expansion memory is MUCH faster (a marginal cost of one
  1526. microsecond per byte as opposed to nine).  The "[zp1]" parameter is unaltered
  1527. by this call, but the register values are quite changed.  The "(zw1)"
  1528. parameter area is also left untouched.
  1529.  
  1530. The "zpstore" routine works the same as "zpload" except it stores to the far
  1531. memory from zero page.
  1532.  
  1533. The "fetch" routine fetches the given number of bytes from a far address into
  1534. the RAM0 bank (not SYS0) at the given address.  Unlike the zero page load
  1535. routine, you can transfer up to 64K of memory with this routine.  Again, the
  1536. type of memory to be fetched is transparent to the user.  For an internal
  1537. memory fetch, the transfers are performed in 256 byte chunks.  This makes the
  1538. implementation easier.  For each byte transferred from RAM1, RAM1 is switched
  1539. in and then RAM0 is switched in, so the transfer is not extremely efficient.
  1540. For the expansion memory, the REU Controller is set up and then the entire
  1541. transfer (up to 64K) is performed at a rate of 1 Meg/second.  This is
  1542. considerably faster than internal memory fetching.  This routine handles a
  1543. transfer length of 0 bytes properly.  The "zp1" and "zw1" parameters are
  1544. returned unaltered, but again, the registers are smashed.
  1545.  
  1546. The "stash" routine operates the same as fetch, except that the data is
  1547. transferred from the near ("zw1") address to the far ("zp1") address.
  1548.  
  1549. The "malloc" routine attempts to find a chunk of contiguous memory of the
  1550. given length to allocate to you.  If it can find one, it returns the far
  1551. pointer to it in the "[zp1]" parameter.  If it cannot find one, it returns
  1552. with the carry flag set.  This routine clobbers the registers.
  1553.  
  1554. The "free" routine returns to the pool of free memory the chunk of memory
  1555. specified by the far pointer and length parameters.  This routine clobbers the
  1556. "[zp1]" parameter and the registers.  The carry flag is always cleared upon
  1557. return, since the routine does not (currently) check for any errors.
  1558.  
  1559. 3.2. MEMORY ALLOCATE AND FREE
  1560.  
  1561. The malloc and free routines maintain a linked list of free memory chunks.  A
  1562. free memory chunk is described by a five byte structure that is at the
  1563. beginning of the chunk.  The first three bytes are a far pointer to the next
  1564. free memory chunk and the following two bytes give the total length of the
  1565. chunk.  The structure is thus:
  1566.  
  1567.   +----------+----------+----------+----------+----------+---...
  1568.   | Next     | Next     | Next     | Chunk    | Chunk    |
  1569.   | chunk    | chunk    | chunk    | length   | length   |  garbage
  1570.   | low addr | high addr| bank num | low      | high     |
  1571.   +----------+----------+----------+----------+----------+---...
  1572.     chunk+0    chunk+1    chunk+2    chunk+3    chunk+4
  1573.  
  1574. All of the free (and allocated) memory chunks are always aligned on an eight
  1575. byte boundary.  This guarantees that no matter what happens, there will always
  1576. be at least eight bytes available in each free memory chunk to hold the free
  1577. chunk descriptor information.  Thus, if you were to make a request for three
  1578. bytes, the system would give you eight, and when you request to free those
  1579. three bytes, the system would automatically free eight.  This can lead to some
  1580. some wasted space when using small structures.
  1581.  
  1582. The memory chunks are kept in order of "increasing" address.  I say
  1583. "increasing" because while the chunks within a bank are in increasing address
  1584. order, the system considers bank number $87 (expansion bank 7) to be lower
  1585. than bank number $3F (RAM0).  This anomaly makes the system allocate its
  1586. external memory before allocating internal memory.  This is good since
  1587. external memory generally works faster than internal memory.
  1588.  
  1589. This memory is allocated first since the malloc routine uses a first-find
  1590. algorithm for searching for a sufficient free memory chunk.  It stops
  1591. searching when it finds a free memory chunk large enough to satisfy the user's
  1592. request.  If the free chunk is exactly the same size as the request, the free
  1593. chunk is unlinked from the free chunk list and the pointer is returned.  If
  1594. the free chunk is larger than the requested size, it is split up.  A pointer
  1595. to the top N bytes of the chunk is retured to the user and the size of the
  1596. free chunk is reduced by N.  The memory is allocated from the top of the chunk
  1597. to make it so no linking and unlinking has to take place in this case.
  1598.  
  1599. The free routine is more complicated than the allocate routine since free has
  1600. to deal with more cases.  Free has to search through the linked list of free
  1601. memory chunks to find the two chunks that straddle the chunk to be freed.
  1602. Free attempts to coalesce (merge) the new chunk with the previous chunk and
  1603. with the next chunk in order to end up with the largest free chunk that it
  1604. can under the circumstances.  Large free chunks are good since they can be
  1605. used for larger requests.  Two chunks can be coalesced if they are
  1606. side-by-side in memory (zero bytes apart) and on the same bank.  Note that
  1607. chunks on different banks cannot be coalesced together, so the largest
  1608. possible free chunk is 64K in length.  To coalesce them, the size of the first
  1609. one is increased by the size of the second one and the pointer to the second
  1610. one is forgotten.
  1611.  
  1612. Note that this scheme works differently from the dynamic allocation scheme
  1613. that BASIC uses for its strings.  BASIC does not attempt to coalesce together
  1614. (or even re-use) freed chunks; it relies upon garbage collecting to get rid of
  1615. the free chunks.  The scheme implemented here is more static (interesting word
  1616. to choose) in that once you are allocated a chunk, that chunk is pinned to
  1617. that address and will never move.  This static organization can lead to the
  1618. problem of memory fragmentation, where lots of memory can be free but is in
  1619. un-coalescable chunks that are too small to be useful.  Oh well.  I don't
  1620. think that it is really a problem for storing lines of text as individual
  1621. records, and it is no problem at all for a program that always uses fixed size
  1622. records.
  1623.  
  1624. 3.3. THE SORT UTILITY
  1625.  
  1626. The sort utility makes full use of the capabilites of the package.  First it
  1627. reads in the input file one line at a time and stores the lines in a linked
  1628. list as individual records of the form:
  1629.  
  1630.   +--------+--------+--------+--------+-------...-----+--------+
  1631.   | Next   | Next   | Next   | Total  |               |        |
  1632.   | line   | line   | line   | record |  characters   | .byte  |
  1633.   | ptr    | ptr    | ptr    | length |  of the line  |    $00 |
  1634.   | low    | high   | bank   |        |               |        |
  1635.   +--------+--------+--------+--------+-------...-----+--------+
  1636.     line+0   line+1   line+2   line+3     line+4        line+?
  1637.  
  1638. Note that these are variable length records; each record is only as long as it
  1639. has to be.  The total record length is stored at the front of the record.  In
  1640. order to read a line into a processing buffer, a "zpload" is done that reads
  1641. the first four bytes of the record in order to get the length of the record.
  1642. Then the entire record can be fetched since its length is known at that time.
  1643. Each record ends with a $00 byte to simplify the string comparison
  1644. subroutine.
  1645.  
  1646. The line list is maintained in alphabetical order (actually, reverse
  1647. alphabetical order; below).  When a new line is read in from the input file,
  1648. the line list is searched for the two other lines whose values straddle the
  1649. value of the new line.  The line is then linked in at that position in the
  1650. list.  No other lines have to be moved around since pointers are used to
  1651. maintain the order of the list.  In order for a line already in the list to be
  1652. compared with the new line, the old line has to be fetched from far memory
  1653. (using the zpload + fetch scheme above) into a work buffer in the SYS0 bank.
  1654. On average, half of the existing list will have to be searched in this way in
  1655. order to find the correct spot to insert the new line.
  1656.  
  1657. After the position for the new line is found, space for the line is allocated
  1658. by calling "malloc" and then the data is stored from the work buffer it was
  1659. read into to far memory.  The zpload and zpstore routines are used to modify
  1660. the pointers to link in the new line.  A number of pointer manipulations are
  1661. also required on the zero page varialbles.
  1662.  
  1663. If the line list was generated in forward alphabetic order, then the utility
  1664. would achieve its WORST performance when the input file was already mostly or
  1665. partially sorted.  This is because when each line is read, if it comes after
  1666. most or all of the other lines, the most or all of the line list would have to
  1667. be searched to find the final resting position for the new line.  This would
  1668. be unacceptable and extremely wasteful.  A better scheme is to generate the
  1669. line list in reverse alphabetic order.  Then, when a "higher valued" line is
  1670. read in, its correct position would be at or near the top of the list, so
  1671. it would only have to be compared against a few of the lines already on the
  1672. list.  In the case of an input file that is already in pretty much random
  1673. order, it makes no difference whether the list is in forward or reverse
  1674. order.
  1675.  
  1676. Since the list is generated in reverse order, it must be reveresed again
  1677. before writing it to the output file, since the user would want it to be in
  1678. forward order (and since this is the order that can be most easily sorted
  1679. again later).  A clever little subroutine is called that reverses the order of
  1680. the list.  It only has to make use of zpload and zpstore to read/change the
  1681. first few bytes of each record, since it is not concerned with the data
  1682. contents of each record.
  1683.  
  1684. Although this is not strictly necessary, all of the records in the line list
  1685. are freed before the sort utilitiy exits.  This is a good practice, and would
  1686. be necessary if the program were to continue to do useful work after writing
  1687. the sorted file to output.  A pointer is stepped through the list (starting
  1688. from the head pointer) and the space for each line is deallocated by calling
  1689. free, after determining the size of the record by reading the first few bytes
  1690. of it.  Since the list will be in (pretty much) random order (of addresses),
  1691. the deallocation mechanism does not achieve its best performance.
  1692.  
  1693. A convenient jump table is set up at the start of the code to make it easier
  1694. for you to link your own programs to the package.  Make sure that MMU
  1695. configuration value $0E is in effect before calling any of the routines.  You
  1696. may have to muck with the code a little bit to get it to work for you.
  1697.  
  1698. ------------------------------------------------------------------------------
  1699.  
  1700. 4. FOR THE EXPERT HACKER
  1701.  
  1702. You get to see the code that actually implements the memory package and the
  1703. sort utility.  I have it here in a special form; each code line is preceeded
  1704. by a few special characters and the line number.  The line number is there to
  1705. allow me to refer to specific lines, and the special characters are there to
  1706. allow you to easily extract the assembler code from the rest of this magazine
  1707. (and all of my ugly comments).  On a Unix system, all you have to do is
  1708. execute the following command line (substitute filenames as appropriate):
  1709.  
  1710. grep '^\.%....\!' Hack2 | sed 's/^.%....\!..//' | sed 's/.%....\!//' >sort.asm
  1711.  
  1712. Dontcha just love those Unix commands!  Here is the assembler code:
  1713.  
  1714. .%0001!  ;Sort utility using dynamic memory allocation with expanded memory
  1715. .%0002!  ;written 92/04/22 by Craig Bruce for C= Hacking Net Magazine
  1716. .%0003!  ;--------------------------------------------------------------------
  1717.  
  1718. This program is written for the Buddy assembler.  Like most assemblers, it
  1719. needs a few directives to start off, so here they are.  Note that my comments
  1720. come BEFORE the section of code that I am commenting on.
  1721.  
  1722. .%0004!  .mem
  1723. .%0005!  .bank 15
  1724. .%0006!  .org $1300
  1725. .%0007!
  1726. .%0008!  ;*** global declarations
  1727. .%0009!
  1728.  
  1729. Here are the zero page locations that the package uses for its own purposes.
  1730. I stuck the sysWork variable over the BASIC graphics command parameters since
  1731. it seems like a good place.  It requires 16 bytes and is used by most of the
  1732. routines for temporary storage.  "temp1" is used for "very" temporary
  1733. storage.
  1734.  
  1735. .%0010!  zp1 = $fa
  1736. .%0011!  temp1 = $fd
  1737. .%0012!  zw1 = $fe
  1738. .%0013!  sysWork = $80    ;16-byte block
  1739. .%0014!
  1740.  
  1741. These are the non-zero page storage locations.  The common code buffer pretty
  1742. much has to be at $200 since that is (about) the only free section of memory
  1743. below address $0400 (in the common memory range).
  1744.  
  1745. .%0015!  comCodeBuffer = $200
  1746. .%0016!  workBuffer = $b00
  1747. .%0017!
  1748.  
  1749. These are the MMU configuration register values and some important I/O
  1750. addresses.
  1751.  
  1752. .%0018!  bkSys = $0e
  1753. .%0019!  bkKernel = $00
  1754. .%0020!  bkSelect = $ff00
  1755. .%0021!  bkSelectRam0 = $ff01
  1756. .%0022!  bkSelectRam1 = $ff02
  1757. .%0023!  bkRam0 = $3f
  1758. .%0024!  bkRam1 = $7f
  1759. .%0025!  bkExp0 = $80
  1760. .%0026!  bkNull = $ff
  1761. .%0027!  zpSelect = $d507
  1762. .%0028!  reu = $df00
  1763. .%0029!  vic = $d000
  1764. .%0030!
  1765. .%0031!  errInsufficientMemory = 1
  1766. .%0032!
  1767. .%0033!  ;*** jump to main routine
  1768. .%0034!
  1769. .%0035!     jmp main
  1770. .%0036!
  1771. .%0037!  ;*** jump table
  1772. .%0038!
  1773.  
  1774. Here's that jump table.
  1775.  
  1776. .%0039!  startup   jmp internStartup
  1777. .%0040!  shutdown  jmp internShutdown
  1778. .%0041!  zpload    jmp internZpLoad
  1779. .%0042!  zpstore   jmp internZpStore
  1780. .%0043!  fetch     jmp internRam0Fetch
  1781. .%0044!  stash     jmp internRam0Stash
  1782. .%0045!  malloc    jmp internAlloc
  1783. .%0046!  free      jmp internFree
  1784. .%0047!
  1785. .%0048!  ;*** storage
  1786. .%0049!
  1787.  
  1788. Here are some useful storage locations.  "errno" contains the code for the
  1789. error encountered in a routine if the routine exits with the carry flag set
  1790. (and it is supposed to be cleared for OK).  "nExpBanks" gives the number of
  1791. expansion memory banks, and "freeMemory" gives the number of bytes currently
  1792. free in the system.  Both of these are useful status values and can be read
  1793. directly.
  1794.  
  1795. .%0050!  errno      .buf 1
  1796. .%0051!  nExpBanks  .buf 1
  1797. .%0052!  mallocHead .buf 3
  1798. .%0053!  freeMemory .buf 3
  1799. .%0054!
  1800. .%0055!  ;***startup
  1801. .%0056!
  1802.  
  1803. This routine gets the ball rolling.  It clears the status register in case you
  1804. start up the system with the decimal mode flag set or interrupts disabled.
  1805.  
  1806. .%0057!  internStartup = *
  1807. .%0058!     lda #0
  1808. .%0059!     pha
  1809. .%0060!     plp
  1810. .%0061!     lda #bkSys
  1811. .%0062!     sta bkSelect
  1812. .%0063!     jsr installCommonCode
  1813. .%0064!     jsr sniffREU
  1814. .%0065!     jsr initDynamicMemory
  1815. .%0066!     rts
  1816. .%0067!
  1817.  
  1818. And this routine stops the ball from rolling.  I fill the BASIC command line
  1819. buffer with zeros to stop that syntax error thing.
  1820.  
  1821. .%0068!  internShutdown = *
  1822. .%0069!     ldx #0
  1823. .%0070!     lda #0
  1824. .%0071!  -  sta $200,x
  1825. .%0072!     inx
  1826. .%0073!     cpx #comCodeEnd-comCodeStart
  1827. .%0074!     bne -
  1828. .%0075!     lda #bkKernel
  1829. .%0076!     sta bkSelect
  1830. .%0077!     rts
  1831. .%0078!
  1832. .%0079!  ;***install common code
  1833. .%0080!
  1834.  
  1835. This routine copies the common code subroutines into the common code buffer
  1836. (at $0200).
  1837.  
  1838. .%0081!  installCommonCode = *
  1839. .%0082!     ldx #0
  1840. .%0083!  -  lda comCodeStart,x
  1841. .%0084!     sta comCodeBuffer,x
  1842. .%0085!     inx
  1843. .%0086!     cpx #comCodeEnd-comCodeStart
  1844. .%0087!     bcc -
  1845. .%0088!     rts
  1846. .%0089!
  1847. .%0090!  ;--------------------------------------------------------------------
  1848. .%0091!  ;***common code
  1849. .%0092!
  1850.  
  1851. And this is the common code.  It contains four subroutines for accessing RAM1
  1852. (and the zero page routines are used for RAM0 as well).
  1853.  
  1854. .%0093!  comCodeStart = *
  1855. .%0094!
  1856.  
  1857. Selects the MMU configuration according to the bank number and copies the
  1858. number of bytes required for a zpload.  It exits by restoring the SYS bank.
  1859. This is used only for internal memory zploads.
  1860.  
  1861. .%0095!  comZpLoad = *
  1862. .%0096!     lda zp1+2
  1863. .%0097!     sta bkSelect
  1864. .%0098!     sty temp1
  1865. .%0099!     ldy #0
  1866. .%0100!  -  lda (zp1),y
  1867. .%0101!     sta 0,x
  1868. .%0102!     inx
  1869. .%0103!     iny
  1870. .%0104!     cpy temp1
  1871. .%0105!     bcc -
  1872. .%0106!     lda #bkSys
  1873. .%0107!     sta bkSelect
  1874. .%0108!     rts
  1875. .%0109!
  1876.  
  1877. Pretty much the same as zpload.
  1878.  
  1879. .%0110!  comZpStore = *
  1880. .%0111!     lda zp1+2
  1881. .%0112!     sta bkSelect
  1882. .%0113!     sty temp1
  1883. .%0114!     ldy #0
  1884. .%0115!  -  lda 0,x
  1885. .%0116!     sta (zp1),y
  1886. .%0117!     inx
  1887. .%0118!     iny
  1888. .%0119!     cpy temp1
  1889. .%0120!     bcc -
  1890. .%0121!     lda #bkSys
  1891. .%0122!     sta bkSelect
  1892. .%0123!     rts
  1893. .%0124!
  1894.  
  1895. As the name suggests, this copies from RAM1 to RAM0.  Only .Y number of bytes
  1896. are copied, and if .Y=0, 256 bytes are copied.  You'll notice that the MMU
  1897. configurations are switched between for every byte copied.  This is not the
  1898. most efficient scheme, but it suffices.  The MMU preconfiguration registers
  1899. are used and the value that BASIC put in them are assumed to still be there.
  1900.  
  1901. .%0125!  comCopyRam1ToRam0 = *
  1902. .%0126!     dey
  1903. .%0127!     beq +
  1904. .%0128!  -  sta bkSelectRam1
  1905. .%0129!     lda (zp1),y
  1906. .%0130!     sta bkSelectRam0
  1907. .%0131!     sta (zw1),y
  1908. .%0132!     dey
  1909. .%0133!     bne -
  1910. .%0134!  +  sta bkSelectRam1
  1911. .%0135!     lda (zp1),y
  1912. .%0136!     sta bkSelectRam0
  1913. .%0137!     sta (zw1),y
  1914. .%0138!     lda #bkSys
  1915. .%0139!     sta bkSelect
  1916. .%0140!     rts
  1917. .%0141!
  1918.  
  1919. The opposite direction.
  1920.  
  1921. .%0142!  comCopyRam0ToRam1 = *
  1922. .%0143!     dey
  1923. .%0144!     beq +
  1924. .%0145!  -  sta bkSelectRam0
  1925. .%0146!     lda (zw1),y
  1926. .%0147!     sta bkSelectRam1
  1927. .%0148!     sta (zp1),y
  1928. .%0149!     dey
  1929. .%0150!     bne -
  1930. .%0151!  +  sta bkSelectRam0
  1931. .%0152!     lda (zw1),y
  1932. .%0153!     sta bkSelectRam1
  1933. .%0154!     sta (zp1),y
  1934. .%0155!     lda #bkSys
  1935. .%0156!     sta bkSelect
  1936. .%0157!     rts
  1937. .%0158!
  1938.  
  1939. The end of the common code.  The length of the common code is determined by
  1940. subtracting the end address from the start address.
  1941.  
  1942. .%0159!  comCodeEnd = *
  1943. .%0160!
  1944. .%0161!  ;--------------------------------------------------------------------
  1945. .%0162!  ;*** zpload( [zp1]=Source, .X=ZpDest, .Y=Length )
  1946. .%0163!
  1947.  
  1948. The actual zpload routine.  It dispatches to the common code routine if
  1949. internal memory is specified by the far pointer, or falls through to REU code
  1950. if expansion memory is specified.
  1951.  
  1952. .%0164!  internZpLoad = *
  1953. .%0165!     lda zp1+2
  1954. .%0166!     bmi +
  1955. .%0167!     jmp comZpLoad-comCodeStart+comCodeBuffer
  1956. .%0168!  +  sty reu+7
  1957. .%0169!     ldy #$91
  1958. .%0170!
  1959.  
  1960. Sets up the REU Controller registers for the parameters of the transfer.  Note
  1961. that the value of the zero page address is not assumed to be absolute $0000
  1962. but is taken from the zero page selection register of the MMU.  The REU
  1963. Controller does not use the MMU for decoding zero page and stack page
  1964. addresses; it accesses the absolute memory directly.
  1965.  
  1966. .%0171!  zeroPageReuOp = *
  1967. .%0172!     sta reu+6
  1968. .%0173!     stx reu+2
  1969. .%0174!     lda zpSelect
  1970. .%0175!     sta reu+3
  1971. .%0176!     lda zp1
  1972. .%0177!     sta reu+4
  1973. .%0178!     lda zp1+1
  1974. .%0179!     sta reu+5
  1975. .%0180!     lda #0
  1976. .%0181!     sta reu+8
  1977.  
  1978. Here the system clock speed is put into Slow mode while the transfer occurs
  1979. and is then restored.  This is necessary.
  1980.  
  1981. .%0182!     lda vic+$30
  1982. .%0183!     ldx #$00
  1983. .%0184!     stx vic+$30
  1984. .%0185!     sty reu+1
  1985. .%0186!     sta vic+$30
  1986. .%0187!     rts
  1987. .%0188!
  1988. .%0189!  ;*** zpstore( .X=ZpSource, [zp1]=Dest, .Y=Length )
  1989. .%0190!
  1990.  
  1991. Pretty much the same as the zpload routine, except that a command code for the
  1992. REU Controller is different (specifying an internal to expansion memory
  1993. transfer).  The REU code in the zpload routine is called.
  1994.  
  1995. .%0191!  internZpStore = *
  1996. .%0192!     lda zp1+2
  1997. .%0193!     bmi +
  1998. .%0194!     jmp comZpStore-comCodeStart+comCodeBuffer
  1999. .%0195!  +  sty reu+7
  2000. .%0196!     ldy #$90
  2001. .%0197!     jmp zeroPageReuOp
  2002. .%0198!
  2003. .%0199!  ;--------------------------------------------------------------------
  2004. .%0200!  ;*** fetch( [zp1]=FarSource, (zw1)=Ram0Dest, .AY=Length )
  2005. .%0201!
  2006.  
  2007. Some working storage locations are necessary for this routine, since it is
  2008. designed to copy data a page at a time.  The source (zp1+1) and destination
  2009. (zw1+1) page addresses are saved and later restored because this routine
  2010. alters them while copying.  If the far address is in expansion memory, this
  2011. routine dispatches to the REU fetch/stash code.
  2012.  
  2013. .%0202!  fetchLength = sysWork
  2014. .%0203!  fetchSaveSource = sysWork+2
  2015. .%0204!  fetchSaveDest = sysWork+3
  2016. .%0205!
  2017. .%0206!  internRam0Fetch = *
  2018. .%0207!     ldx zp1+2
  2019. .%0208!     bpl +
  2020. .%0209!     ldx #$91
  2021. .%0210!     jmp doReu
  2022.  
  2023. If the transfer is less than one page long, it can be done by calling the
  2024. fetchPage code directly.  Otherwise, the long fetch code has to be called.
  2025.  
  2026. .%0211!  +  cpy #0
  2027. .%0212!     bne fetchLong
  2028. .%0213!     tay
  2029. .%0214!     bne fetchPage
  2030. .%0215!     rts
  2031. .%0216!
  2032.  
  2033. If the (internal) page to be fetched is on RAM1, the common code routine is
  2034. called; otherwise, the copy is done here by switching RAM0 into context.  We
  2035. can copy between RAM0 locations without switching contexts for every byte.
  2036.  
  2037. .%0217!     fetchPage = *
  2038. .%0218!     cpx #bkRam0
  2039. .%0219!     beq +
  2040. .%0220!     jmp comCopyRam1ToRam0-comCodeStart+comCodeBuffer
  2041. .%0221!  +  stx bkSelect
  2042. .%0222!     dey
  2043. .%0223!     beq +
  2044. .%0224!  -  lda (zp1),y
  2045. .%0225!     sta (zw1),y
  2046. .%0226!     dey
  2047. .%0227!     bne -
  2048. .%0228!  +  lda (zp1),y
  2049. .%0229!     sta (zw1),y
  2050. .%0230!     lda #bkSys
  2051. .%0231!     sta bkSelect
  2052. .%0232!     rts
  2053. .%0233!
  2054.  
  2055. This is called for long (>=256 byte) (internal) fetches.  It calls the
  2056. fetchPage code repeatedly, after incrementing the source and destination page
  2057. numbers.  The transfer length is decremented until it is less than 256 bytes.
  2058.  
  2059. .%0234!     fetchLong = *
  2060. .%0235!     sta fetchLength
  2061. .%0236!     sty fetchLength+1
  2062. .%0237!     lda zp1+1
  2063. .%0238!     sta fetchSaveSource
  2064. .%0239!     lda zw1+1
  2065. .%0240!     sta fetchSaveDest
  2066. .%0241!     lda fetchLength+1
  2067. .%0242!     beq fetchLongExit
  2068. .%0243!  -  ldx zp1+2
  2069. .%0244!     ldy #0
  2070. .%0245!     jsr fetchPage
  2071. .%0246!     inc zp1+1
  2072. .%0247!     inc zw1+1
  2073. .%0248!     dec fetchLength+1
  2074. .%0249!     bne -
  2075. .%0250!
  2076. .%0251!     fetchLongExit = *
  2077.  
  2078. This fetches the last chunk of less than 256 bytes and then restores the zp1
  2079. and zw1 parameters to what they were before this routine was called.
  2080.  
  2081. .%0252!     ldy fetchLength
  2082. .%0253!     beq +
  2083. .%0254!     ldx zp1+2
  2084. .%0255!     jsr fetchPage
  2085. .%0256!  +  lda fetchSaveSource
  2086. .%0257!     sta zp1+1
  2087. .%0258!     lda fetchSaveDest
  2088. .%0259!     sta zw1+1
  2089. .%0260!     rts
  2090. .%0261!
  2091. .%0262!  ;*** stash( (zw1)=Ram0Source, [zp1]=FarDest, .AY=length )
  2092. .%0263!
  2093.  
  2094. Stash has exactly the same structure as fetch.
  2095.  
  2096. .%0264!  stashLength = sysWork
  2097. .%0265!  stashSaveSource = sysWork+2
  2098. .%0266!  stashSaveDest = sysWork+3
  2099. .%0267!
  2100. .%0268!  internRam0Stash = *
  2101. .%0269!     ldx zp1+2
  2102. .%0270!     bpl +
  2103. .%0271!     ldx #$90
  2104. .%0272!     jmp doReu
  2105. .%0273!  +  cpy #0
  2106. .%0274!     bne stashLong
  2107. .%0275!     tay
  2108. .%0276!     bne stashPage
  2109. .%0277!     rts
  2110. .%0278!
  2111. .%0279!     stashPage = *
  2112. .%0280!     cpx #bkRam0
  2113. .%0281!     beq +
  2114. .%0282!     jmp comCopyRam0ToRam1-comCodeStart+comCodeBuffer
  2115. .%0283!  +  stx bkSelect
  2116. .%0284!     dey
  2117. .%0285!     beq +
  2118. .%0286!  -  lda (zw1),y
  2119. .%0287!     sta (zp1),y
  2120. .%0288!     dey
  2121. .%0289!     bne -
  2122. .%0290!  +  lda (zw1),y
  2123. .%0291!     sta (zp1),y
  2124. .%0292!     lda #bkSys
  2125. .%0293!     sta bkSelect
  2126. .%0294!     rts
  2127. .%0295!
  2128. .%0296!     stashLong = *
  2129. .%0297!     sta stashLength
  2130. .%0298!     sty stashLength+1
  2131. .%0299!     lda zw1+1
  2132. .%0300!     sta stashSaveSource
  2133. .%0301!     lda zp1+1
  2134. .%0302!     sta stashSaveDest
  2135. .%0303!     lda stashLength+1
  2136. .%0304!     beq stashLongExit
  2137. .%0305!  -  ldx zp1+2
  2138. .%0306!     ldy #0
  2139. .%0307!     jsr stashPage
  2140. .%0308!     inc zp1+1
  2141. .%0309!     inc zw1+1
  2142. .%0310!     dec stashLength+1
  2143. .%0311!     bne -
  2144. .%0312!
  2145. .%0313!     stashLongExit = *
  2146. .%0314!     ldy stashLength
  2147. .%0315!     beq +
  2148. .%0316!     ldx zp1+2
  2149. .%0317!     jsr stashPage
  2150. .%0318!  +  lda stashSaveSource
  2151. .%0319!     sta zw1+1
  2152. .%0320!     lda stashSaveDest
  2153. .%0321!     sta zp1+1
  2154. .%0322!     rts
  2155. .%0323!
  2156. .%0324!  ;*** ram0 load/store(.X) expn memory [zp1] <- -> (zw1) for .AY bytes
  2157. .%0325!
  2158.  
  2159. This is the code that does the fetching and stashing from/to expansion
  2160. memory.  The only difference between a fetch and a stash is the REU Controller
  2161. command code, so that is an input parameter.  The REU Controller registers are
  2162. set up, the clock is slowed, the transfer happens, and then the clock speed is
  2163. restored.  The bulk transfer is done entirely by the REU Controller.
  2164. Interestingly, it would have been faster to transfer the internal memory to
  2165. expansion memory and then fetch it back again in order to achieve an internal
  2166. memory transfer (if you have an REU), but I didn't bother with that.
  2167.  
  2168. .%0326!  doReu = *
  2169. .%0327!     sta reu+7
  2170. .%0328!     sty reu+8
  2171. .%0329!     lda zw1
  2172. .%0330!     ldy zw1+1
  2173. .%0331!     sta reu+2
  2174. .%0332!     sty reu+3
  2175. .%0333!     lda zp1
  2176. .%0334!     ldy zp1+1
  2177. .%0335!     sta reu+4
  2178. .%0336!     sty reu+5
  2179. .%0337!     lda zp1+2
  2180. .%0338!     sta reu+6
  2181. .%0339!     ldy vic+$30
  2182. .%0340!     lda #0
  2183. .%0341!     sta vic+$30
  2184. .%0342!     stx reu+1
  2185. .%0343!     sty vic+$30
  2186. .%0344!     rts
  2187. .%0345!
  2188. .%0346!  ;*** sniffREU - determine number of banks of expansion memory
  2189. .%0347!
  2190.  
  2191. The work locations are used to store a string to the first four addresses of
  2192. each expansion memory bank and then fetch them back again in order to
  2193. determine whether the bank exists or not.  Expansion bank #0 is also checked
  2194. after each bank to see if a bank number wrap-around occured.  The
  2195. "reuSizeLimit" will force this routine to stop searching after that number of
  2196. banks have been sniffed.  The maximum value is 127, since only bank numbers
  2197. $80 to $FE are available.  By changing this value, you can stop this package
  2198. from using expansion memory reserved by another program.  Note that this
  2199. program uses expansion banks 0 up to but not including "reuSizeLimit".
  2200.  
  2201. .%0348!  sniffWork1 = sysWork
  2202. .%0349!  sniffWork2 = sysWork+4
  2203. .%0350!  reuSizeLimit .byte 127
  2204. .%0351!
  2205. .%0352!  sniffREU = *
  2206.  
  2207. Here I save the data in the memory "beneath" the REU Controller registers.  If
  2208. there isn't a REU installed, this memory would otherwise be corrupted by I/O
  2209. addresses bleeding through to the underlying RAM.
  2210.  
  2211. .%0353!     lda #bkRam0
  2212. .%0354!     sta bkSelect
  2213. .%0355!     ldx #$a
  2214. .%0356!  -  lda reu,x
  2215. .%0357!     sta workBuffer,x
  2216. .%0358!     dex
  2217. .%0359!     bpl -
  2218. .%0360!     lda #bkSys
  2219. .%0361!     sta bkSelect
  2220.  
  2221. Here I initialize the configuration REU Controller registers.  They are set
  2222. only once by this package.
  2223.  
  2224. .%0362!     lda #$00
  2225. .%0363!     sta reu+$9
  2226. .%0364!     sta reu+$a
  2227. .%0365!     lda reu+$0
  2228.  
  2229. The three-byte identifier string is copied into the source tag.  The fourth
  2230. byte will be filled in by the bank number.
  2231.  
  2232. .%0366!     ldx #2
  2233. .%0367!  -  lda expRamId,x
  2234. .%0368!     sta sniffWork1,x
  2235. .%0369!     dex
  2236. .%0370!     bpl -
  2237.  
  2238. Initialization continues.
  2239.  
  2240. .%0371!     lda #0
  2241. .%0372!     sta nExpBanks
  2242. .%0373!     lda #$00
  2243. .%0374!     ldx #bkExp0
  2244. .%0375!     sta zp1
  2245. .%0376!     sta zp1+1
  2246. .%0377!     stx zp1+2
  2247. .%0378!
  2248.  
  2249. This is the main loop.  It tests the current expansion bank and then goes on
  2250. to the next one if ok.  Otherwise, it stops at the number of okay banks.
  2251.  
  2252. .%0379!  -  jsr testExpBank
  2253. .%0380!     bcs +
  2254. .%0381!     inc nExpBanks
  2255. .%0382!     inc zp1+2
  2256. .%0383!     bne -
  2257. .%0384!  +  lda nExpBanks
  2258. .%0385!     bne +
  2259.  
  2260. Restore the underlying RAM contents and exit.
  2261.  
  2262. .%0386!     lda #bkRam0
  2263. .%0387!     sta bkSelect
  2264. .%0388!     ldx #$a
  2265. .%0389!  -  lda workBuffer,x
  2266. .%0390!     sta reu,x
  2267. .%0391!     dex
  2268. .%0392!     bpl -
  2269. .%0393!     lda #bkSys
  2270. .%0394!     sta bkSelect
  2271. .%0395!  +  rts
  2272. .%0396!
  2273. .%0397!  ;*** test expansion bank( [zp1]=BankPtr ) : .CC=ok
  2274. .%0398!
  2275.  
  2276. First checks that the maximum number of allowed expansion banks has not been
  2277. exceeded.  Stores the test string through the bank pointer and then tests to
  2278. see that the string has been stored correctly and that the string on expansion
  2279. bank 0 is still ok (it wouldn't be ok if a wrap-around occured).
  2280.  
  2281. .%0399!  testExpBank = *
  2282. .%0400!     lda nExpBanks
  2283. .%0401!     cmp reuSizeLimit
  2284. .%0402!     bcc +
  2285. .%0403!     rts
  2286. .%0404!  +  lda zp1+2
  2287. .%0405!     sta sniffWork1+3
  2288. .%0406!     ldx #sniffWork1
  2289. .%0407!     ldy #4
  2290. .%0408!     jsr zpstore
  2291. .%0409!     jsr testExpBankInternal  ;test current bank
  2292. .%0410!     bcs +
  2293. .%0411!     lda zp1+2
  2294. .%0412!     pha
  2295. .%0413!     lda #bkExp0
  2296. .%0414!     sta zp1+2
  2297. .%0415!     sta sniffWork1+3
  2298. .%0416!     jsr testExpBankInternal  ;test expansion bank 0
  2299. .%0417!     pla
  2300. .%0418!     sta zp1+2
  2301. .%0419!  +  rts
  2302. .%0420!
  2303.  
  2304. This routine reads the bytes at address [zp1] and makes sure they are the same
  2305. as the previous routine put there.  On return, the carry flag is set if the
  2306. string found is not the same as what was previously put out.
  2307.  
  2308. .%0421!  testExpBankInternal = *
  2309. .%0422!     lda #$00
  2310. .%0423!     sta sniffWork2
  2311. .%0424!     sta sniffWork2+3
  2312. .%0425!     ldx #sniffWork2
  2313. .%0426!     ldy #4
  2314. .%0427!     jsr zpload
  2315. .%0428!     ldx #3
  2316. .%0429!  -  lda sniffWork2,x
  2317. .%0430!     cmp sniffWork1,x
  2318. .%0431!     bne +
  2319. .%0432!     dex
  2320. .%0433!     bpl -
  2321. .%0434!     clc
  2322. .%0435!     rts
  2323. .%0436!  +  sec
  2324. .%0437!     rts
  2325. .%0438!
  2326.  
  2327. This is the three-byte string put into the expansion banks.  The value means
  2328. "RAM identifier".
  2329.  
  2330. .%0439!  expRamId   .byte "r"
  2331. .%0440!             .byte "I"
  2332. .%0441!             .byte "d"
  2333. .%0442!
  2334. .%0443!  ;--------------------------------------------------------------------
  2335. .%0444!  ;*** initialize dynamically allocated memory() : nExpBanks
  2336. .%0445!
  2337.  
  2338. This routine calls "free" to initialize the free memory on each existing
  2339. bank.  RAM0 is set to be free from $4000 to the top of BASIC memory, so you'll
  2340. have to change the "ram0FreeStartPage" parameter if you want to have a program
  2341. that occupies memory higher than this address.  RAM1 is declared to be free
  2342. from $0400 to $FEFF
  2343.  
  2344. .%0446!  ram0FreeStartPage .byte $40
  2345. .%0447!  ram1FreeStartPage .byte $04
  2346. .%0448!  ram1FreeLength    .byte 256-1-$04
  2347. .%0449!
  2348. .%0450!  currentExpBank = sysWork+$f
  2349. .%0451!
  2350. .%0452!  initDynamicMemory = *
  2351.  
  2352. Set the memory allocation first free chunk pointer to Null and set the number
  2353. of bytes of free memory to 0.
  2354.  
  2355. .%0453!     ldx #2
  2356. .%0454!  -  lda #$00
  2357. .%0455!     sta freeMemory,x
  2358. .%0456!     lda #$ff
  2359. .%0457!     sta mallocHead,x
  2360. .%0458!     dex
  2361. .%0459!     bpl -
  2362.  
  2363. Determine the length of free memory on RAM0 and free the memory.
  2364.  
  2365. .%0460!     sec
  2366. .%0461!     lda $1212   ;top of BASIC program Low
  2367. .%0462!     beq +
  2368. .%0463!     clc
  2369. .%0464!  +  lda $1213   ;top of BASIC program High
  2370. .%0465!     sbc ram0FreeStartPage
  2371. .%0466!     tay
  2372. .%0467!     lda ram0FreeStartPage
  2373. .%0468!     ldx #bkRam0
  2374. .%0469!     jsr initInternalBankMalloc
  2375.  
  2376. Free the memory of RAM1
  2377.  
  2378. .%0470!     lda ram1FreeStartPage
  2379. .%0471!     ldy ram1FreeLength
  2380. .%0472!     ldx #bkRam1
  2381. .%0473!     jsr initInternalBankMalloc
  2382. .%0474!
  2383.  
  2384. For each existing expansion bank, free it from addresses $0000 to $FFF7.  You
  2385. cannot free all 65536 bytes since this would cause the length of the free
  2386. chunk to be set to $0000 which would cause problems later on.  $FFF8 bytes are
  2387. set to free since then length has to be a multiple of eight bytes.
  2388.  
  2389. .%0475!     lda #0
  2390. .%0476!     sta currentExpBank
  2391. .%0477!  -  lda currentExpBank
  2392. .%0478!     cmp nExpBanks
  2393. .%0479!     bcs +
  2394. .%0480!     ora #bkExp0
  2395. .%0481!     sta zp1+2
  2396. .%0482!     lda #$00
  2397. .%0483!     sta zp1
  2398. .%0484!     sta zp1+1
  2399. .%0485!     lda #$f8
  2400. .%0486!     ldy #$ff
  2401. .%0487!     jsr free
  2402. .%0488!     inc currentExpBank
  2403. .%0489!     bne -
  2404. .%0490!  +  rts
  2405. .%0491!
  2406.  
  2407. This routine is called for freeing banks RAM0 and RAM1.  It does nothing other
  2408. than set parameters and is put in for convenience.
  2409.  
  2410. .%0492!  initInternalBankMalloc = *
  2411. .%0493!     sta zp1+1
  2412. .%0494!     stx zp1+2
  2413. .%0495!     lda #0
  2414. .%0496!     sta zp1
  2415. .%0497!     jmp free
  2416. .%0498!
  2417. .%0499!  ;--------------------------------------------------------------------
  2418. .%0500!  ;*** malloc( .AY=Bytes ) : [zp1]=FarPointer
  2419. .%0501!
  2420.  
  2421. One of the biggies.  The "MemNextPtr" and "MemLength" variables are used to
  2422. store the information at the start of the current free memory chunk.  "Length"
  2423. is used to hold the length input parameter and "Q" is the pointer to the
  2424. previous free memory chunk whereas "zp1" is used to point to the current free
  2425. chunk.  I prefix these variables with "malloc" to avoid naming collisions with
  2426. other routines.  The concept of local variables might be a nice thing for
  2427. future assemblers to have.
  2428.  
  2429. .%0502!  mallocMemNextPtr = sysWork
  2430. .%0503!  mallocMemLength  = sysWork+3
  2431. .%0504!  mallocLength     = sysWork+5
  2432. .%0505!  mallocQ          = sysWork+7
  2433. .%0506!
  2434. .%0507!  internAlloc = *
  2435.  
  2436. Align the number of bytes requested to an even multiple of eight.
  2437.  
  2438. .%0508!     clc
  2439. .%0509!     adc #7
  2440. .%0510!     bcc +
  2441. .%0511!     iny
  2442. .%0512!  +  and #$f8
  2443. .%0513!     sta mallocLength
  2444. .%0514!     sty mallocLength+1
  2445.  
  2446. Set the current free chunk pointer to the first free chunk and set Q to Null.
  2447.  
  2448. .%0515!     ldx #2
  2449. .%0516!  -  lda mallocHead,x
  2450. .%0517!     sta zp1,x
  2451. .%0518!     lda #$ff
  2452. .%0519!     sta mallocQ,x
  2453. .%0520!     dex
  2454. .%0521!     bpl -
  2455. .%0522!
  2456.  
  2457. Search for a free chunk that is long enough to satisfy the request.
  2458.  
  2459. .%0523!     mallocLook = *
  2460.  
  2461. If the current free chunk pointer is Null, then we are S.O.L. (Out of Luck)
  2462. since that means we have exhausted the list of free chunks and have to report
  2463. that insufficient free memory could be found.
  2464.  
  2465. .%0524!     lda zp1+2
  2466. .%0525!     cmp #$ff
  2467. .%0526!     bne +
  2468. .%0527!
  2469. .%0528!     mallocErrorExit = *
  2470. .%0529!     lda #$ff   ;return a Null pointer
  2471. .%0530!     sta zp1
  2472. .%0531!     sta zp1+1
  2473. .%0532!     sta zp1+2
  2474. .%0533!     lda #errInsufficientMemory
  2475. .%0534!     sta errno
  2476. .%0535!     sec
  2477. .%0536!     rts
  2478. .%0537!
  2479.  
  2480. Fetch the header information of the current free chunk and check the length.
  2481. If the current free chunk is not large enough, then we set the Q pointer to
  2482. the current pointer, and take the new value for the current pointer from the
  2483. header of the current free chunk (mallocMemNextPtr) and then continue
  2484. searching.
  2485.  
  2486. .%0538!  +  ldx #mallocMemNextPtr
  2487. .%0539!     ldy #5
  2488. .%0540!     jsr zpload
  2489. .%0541!     lda mallocMemLength
  2490. .%0542!     cmp mallocLength
  2491. .%0543!     lda mallocMemLength+1
  2492. .%0544!     sbc mallocLength+1
  2493. .%0545!     bcs mallocGotBlock
  2494. .%0546!     ldx #2
  2495. .%0547!  -  lda zp1,x
  2496. .%0548!     sta mallocQ,x
  2497. .%0549!     lda mallocMemNextPtr,x
  2498. .%0550!     sta zp1,x
  2499. .%0551!     dex
  2500. .%0552!     bpl -
  2501. .%0553!     jmp mallocLook
  2502. .%0554!
  2503.  
  2504. Now, we've found a block that is large enough.
  2505.  
  2506. .%0555!     mallocGotBlock = *
  2507. .%0556!     sec
  2508.  
  2509. Subtract the number of bytes requested from the total number of bytes free.
  2510.  
  2511. .%0557!     lda freeMemory
  2512. .%0558!     sbc mallocLength
  2513. .%0559!     sta freeMemory
  2514. .%0560!     lda freeMemory+1
  2515. .%0561!     sbc mallocLength+1
  2516. .%0562!     sta freeMemory+1
  2517. .%0563!     bcs +
  2518. .%0564!     dec freeMemory+2
  2519.  
  2520. If the size of the current free chunk is exactly the same as the number of
  2521. bytes requested, then branch ahead.
  2522.  
  2523. .%0565!  +  lda mallocMemLength
  2524. .%0566!     cmp mallocLength
  2525. .%0567!     bne +
  2526. .%0568!     lda mallocMemLength+1
  2527. .%0569!     sbc mallocLength+1
  2528. .%0570!     beq mallocTakeWholeBlock
  2529.  
  2530. Subtract the number of bytes requested from the length of the current free
  2531. chunk and then write the updated header back to the current free chunk.
  2532.  
  2533. .%0571!  +  sec
  2534. .%0572!     lda mallocMemLength
  2535. .%0573!     sbc mallocLength
  2536. .%0574!     sta mallocMemLength
  2537. .%0575!     lda mallocMemLength+1
  2538. .%0576!     sbc mallocLength+1
  2539. .%0577!     sta mallocMemLength+1
  2540. .%0578!     ldx #mallocMemNextPtr
  2541. .%0579!     ldy #5
  2542. .%0580!     jsr zpstore
  2543.  
  2544. Add the length of the free chunk to the pointer to the start of the free chunk
  2545. to determine the address of the memory that has just been allocated.  Then
  2546. exit, returning this address.
  2547.  
  2548. .%0581!     clc
  2549. .%0582!     lda zp1
  2550. .%0583!     adc mallocMemLength
  2551. .%0584!     sta zp1
  2552. .%0585!     lda zp1+1
  2553. .%0586!     adc mallocMemLength+1
  2554. .%0587!     sta zp1+1
  2555. .%0588!     clc
  2556. .%0589!     rts
  2557. .%0590!
  2558.  
  2559. Here, the size of the free chunk is exactly the same size as the request, so
  2560. the entire block has to be allocated and thus removed from the free chunk
  2561. list.  This is why the Q pointer has been maintained.
  2562.  
  2563. .%0591!     mallocTakeWholeBlock = *
  2564.  
  2565. If there is no previous block (Q == Null) then set the free chunk list head
  2566. pointer to the next free chunk after the current one.  Then exit with the
  2567. current chunk as the return pointer.
  2568.  
  2569. .%0592!     lda mallocQ+2
  2570. .%0593!     cmp #bkNull
  2571. .%0594!     bne +
  2572. .%0595!     ldx #2
  2573. .%0596!  -  lda mallocMemNextPtr,x
  2574. .%0597!     sta mallocHead,x
  2575. .%0598!     dex
  2576. .%0599!     bpl -
  2577. .%0600!     clc
  2578. .%0601!     rts
  2579.  
  2580. If there is an actual previous chunk, then we have to set it to point to the
  2581. next chunk from the current chunk.  This will unlink the current free chunk
  2582. from the free chunk list, thereby allocating it.
  2583.  
  2584. First, we swap the Q and current pointers, since we can only access memory
  2585. through the "zp1" pointer.
  2586.  
  2587. .%0602!  +  ldx #2
  2588. .%0603!  -  lda zp1,x
  2589. .%0604!     ldy mallocQ,x
  2590. .%0605!     sta mallocQ,x
  2591. .%0606!     sty zp1,x
  2592. .%0607!     dex
  2593. .%0608!     bpl -
  2594.  
  2595. Then we set the the NextPointer of the previous free chunk to point to the
  2596. next free chunk after the current chunk.
  2597.  
  2598. .%0609!     ldx #mallocMemNextPtr
  2599. .%0610!     ldy #3
  2600. .%0611!     jsr zpstore
  2601.  
  2602. And then we restore the current chunk pointer and return it to the user.
  2603.  
  2604. .%0612!     ldx #2
  2605. .%0613!  -  lda mallocQ,x
  2606. .%0614!     sta zp1,x
  2607. .%0615!     dex
  2608. .%0616!     bpl -
  2609. .%0617!     clc
  2610. .%0618!     rts
  2611. .%0619!
  2612. .%0620!  ;*** free( [zp1]=FarPointer, .AY=Length )  {alters [zp1]}
  2613. .%0621!
  2614.  
  2615. And here is the real biggie, since Free is more complicated than Malloc.  The
  2616. variables are the same as for free, except that "NewPtr" is required to
  2617. remember the input parameter to new chunk to be freed.
  2618.  
  2619. .%0622!  freeMemNextPtr = sysWork
  2620. .%0623!  freeMemLength  = sysWork+3
  2621. .%0624!  freeLength     = sysWork+5
  2622. .%0625!  freeNewPtr     = sysWork+7
  2623. .%0626!  freeQ          = sysWork+10
  2624. .%0627!
  2625. .%0628!  internFree = *
  2626.  
  2627. Again, align the length of the chunk.  The pointer to the start of the new
  2628. chunk is assumed to be aligned (since malloc only returns aligned chunks).  If
  2629. the chunk pointer is not aligned, all hell can break loose.
  2630.  
  2631. .%0629!     clc
  2632. .%0630!     adc #7
  2633. .%0631!     bcc +
  2634. .%0632!     iny
  2635. .%0633!  +  and #$f8
  2636. .%0634!     sta freeLength
  2637. .%0635!     sty freeLength+1
  2638.  
  2639. Save the new chunk input parameter and set "zp1" for searching the free chunk
  2640. list.  Also set Q to Null since Q will be used to remember the previous block
  2641. to "zp1".
  2642.  
  2643. .%0636!     ldx #2
  2644. .%0637!  -  lda zp1,x
  2645. .%0638!     sta freeNewPtr,x
  2646. .%0639!     lda mallocHead,x
  2647. .%0640!     sta zp1,x
  2648. .%0641!     lda #$ff
  2649. .%0642!     sta freeQ,x
  2650. .%0643!     dex
  2651. .%0644!     bpl -
  2652. .%0645!
  2653.  
  2654. Search for the two free chunks whose addresses straddle the new free chunk.
  2655.  
  2656. .%0646!     freeSearchLoop = *
  2657.  
  2658. If the current free chunk pointer is Null or if the current free chunk's bank
  2659. number is less than the new chunk's bank number, then we can stop searching;
  2660. we have found a free chunk that is "higher" than the new chunk, so Q and zp1
  2661. must straddle the address of the new chunk.  Note that by using a "bcc" on
  2662. line 652, external memory free chunks will be allocated first.  If I had used
  2663. a "bcs" there, the internal memory starting from RAM0 would be allocated
  2664. first.
  2665.  
  2666. .%0647!     lda zp1+2
  2667. .%0648!     cmp #$ff
  2668. .%0649!     beq freeCoalesceQandNew
  2669. .%0650!     lda zp1+2
  2670. .%0651!     cmp freeNewPtr+2
  2671. .%0652!     bcc freeCoalesceQandNew  ;** determines bank order
  2672.  
  2673. Here we know that the bank number is not "higher", so if the bank numbers are
  2674. not equal, then we continue searching.  If the bank numbers are equal, we must
  2675. check the addresses within the bank to see if zp1 is higher than the new
  2676. chunk.  If so, we stop searching.
  2677.  
  2678. .%0653!     bne +
  2679. .%0654!     lda zp1
  2680. .%0655!     cmp freeNewPtr
  2681. .%0656!     lda zp1+1
  2682. .%0657!     sbc freeNewPtr+1
  2683. .%0658!     bcs freeCoalesceQandNew
  2684.  
  2685. Here we continue searching.  We stick the current free chunk pointer into Q
  2686. and get the next free chunk pointer from the current chunk in memory.  Then we
  2687. go back to the top of the search.
  2688.  
  2689. .%0659!  +  ldx #freeMemNextPtr
  2690. .%0660!     ldy #3
  2691. .%0661!     jsr zpload
  2692. .%0662!     ldx #2
  2693. .%0663!  -  lda zp1,x
  2694. .%0664!     sta freeQ,x
  2695. .%0665!     lda freeMemNextPtr,x
  2696. .%0666!     sta zp1,x
  2697. .%0667!     dex
  2698. .%0668!     bpl -
  2699. .%0669!     bmi freeSearchLoop
  2700. .%0670!
  2701.  
  2702. Here we know that Q and zp1 straddle the new chunk, and we try to coalesce the
  2703. new chunk to the Q chunk.
  2704.  
  2705. .%0671!     freeCoalesceQandNew = *
  2706. .%0672!     ldx #2
  2707. .%0673!  -  lda freeQ,x
  2708. .%0674!     sta zp1,x
  2709. .%0675!     dex
  2710. .%0676!     bpl -
  2711.  
  2712. If the Q pointer is Null, then there is no Q chunk to coalesce with, so the
  2713. free chunk head pointer is set to point to the new chunk and the new chunk
  2714. header is set to the size of the new chunk.  Then next pointer for the new
  2715. chunk is set to what was previously the head pointer.
  2716.  
  2717. .%0677!     lda zp1+2
  2718. .%0678!     cmp #$ff
  2719. .%0679!     bne +
  2720. .%0680!     ldx #2
  2721. .%0681!  -  lda mallocHead,x
  2722. .%0682!     sta freeMemNextPtr,x
  2723. .%0683!     lda freeNewPtr,x
  2724. .%0684!     sta mallocHead,x
  2725. .%0685!     dex
  2726. .%0686!     bpl -
  2727. .%0687!     lda freeLength
  2728. .%0688!     ldy freeLength+1
  2729. .%0689!     sta freeMemLength
  2730. .%0690!     sty freeMemLength+1
  2731. .%0691!     jmp freeCoalesceNewAndP
  2732. .%0692!
  2733.  
  2734. Here there actually is a previous (Q) chunk, so its header is fetched.  If it
  2735. is not on the same bank as the new chunk, then the new chunk cannot be
  2736. coalesced with it.  Also, if the address of the new chunk does not exactly
  2737. follow the Q chunk, then they cannot be coalesced.
  2738.  
  2739. .%0693!  +  ldx #freeMemNextPtr
  2740. .%0694!     ldy #5
  2741. .%0695!     jsr zpload
  2742. .%0696!     lda zp1+2
  2743. .%0697!     cmp freeNewPtr+2
  2744. .%0698!     bne +
  2745. .%0699!     clc
  2746. .%0700!     lda zp1
  2747. .%0701!     adc freeMemLength
  2748. .%0702!     tax
  2749. .%0703!     lda zp1+1
  2750. .%0704!     adc freeMemLength+1
  2751. .%0705!     cmp freeNewPtr+1
  2752. .%0706!     bne +
  2753. .%0707!     cpx freeNewPtr
  2754. .%0708!     bne +
  2755.  
  2756. Here, we know that the previous chunk and the new chunk can be coalesced.  We
  2757. add the length of the new chunk to the length of the previous chunk and change
  2758. the new chunk pointer to point to the previous chunk.
  2759.  
  2760. .%0709!     clc
  2761. .%0710!     lda freeMemLength
  2762. .%0711!     adc freeLength
  2763. .%0712!     sta freeMemLength
  2764. .%0713!     lda freeMemLength+1
  2765. .%0714!     adc freeLength+1
  2766. .%0715!     sta freeMemLength+1
  2767. .%0716!     ldx #2
  2768. .%0717!  -  lda freeQ,x
  2769. .%0718!     sta freeNewPtr,x
  2770. .%0719!     dex
  2771. .%0720!     bpl -
  2772. .%0721!     bmi freeCoalesceNewAndP
  2773. .%0722!
  2774.  
  2775. Here, we know that the previous and new chunks cannot be coalesced.  We change
  2776. the actual header of the pervious chunk to point to the new chunk and change
  2777. the new chunk header length to the free request length.  The pointer to the
  2778. next chunk is already in the new chunk header from before.  Note that now we
  2779. are using "memNextPtr" and "memLength" to construct the new free chunk
  2780. header.  Line 729 caused Mr. Bruce some problems because he forgot to stick
  2781. the "+1" there after extracting the code from Zed.
  2782.  
  2783. .%0723!  +  ldx #freeNewPtr
  2784. .%0724!     ldy #3
  2785. .%0725!     jsr zpstore
  2786. .%0726!     lda freeLength
  2787. .%0727!     ldy freeLength+1
  2788. .%0728!     sta freeMemLength
  2789. .%0729!     sty freeMemLength+1
  2790. .%0730!
  2791.  
  2792. At this point, we are finished trying to coalesce the new chunk with the
  2793. previous chunk, so we will attempt to coalesce the new chunk with the next
  2794. higher address free chunk.  The "memNextPtr" and "memLength" variables hold
  2795. the header information for the new chunk (the "memNextPtr" also points to the
  2796. next free chunk), and "NewPtr" points to the new chunk.  We check to see if
  2797. the new chunk immediately preceeds the next chunk in the same way as before.
  2798. Note that the case of a Null next chunk pointer is handled here implicitly,
  2799. since the bank numbers won't match.
  2800.  
  2801. .%0731!     freeCoalesceNewAndP = *
  2802. .%0732!     lda freeNewPtr+2
  2803. .%0733!     cmp freeMemNextPtr+2
  2804. .%0734!     bne +
  2805. .%0735!     clc
  2806. .%0736!     lda freeNewPtr
  2807. .%0737!     adc freeMemLength
  2808. .%0738!     tax
  2809. .%0739!     lda freeNewPtr+1
  2810. .%0740!     adc freeMemLength+1
  2811. .%0741!     cmp freeMemNextPtr+1
  2812. .%0742!     bne +
  2813. .%0743!     cpx freeMemNextPtr
  2814. .%0744!     bne +
  2815. .%0745!
  2816.  
  2817. Here, we know that the new chunk can be coalesced with the next chunk.  We
  2818. have to fetch the header of the next chunk to know the length and the pointer
  2819. to the free chunk after the next chunk.  We then add the length of the next
  2820. chunk to the length of the new chunk and keep the pointer to the chunk after
  2821. the next chunk for the new chunk header.  Effectively, the next free chunk is
  2822. unlinked (since nothing is left to point to it) and the new chunk grows to
  2823. swallow it up.
  2824.  
  2825. .%0746!     ldx #2
  2826. .%0747!  -  lda freeMemNextPtr,x
  2827. .%0748!     sta zp1,x
  2828. .%0749!     dex
  2829. .%0750!     bpl -
  2830. .%0751!     lda freeMemLength+1
  2831. .%0752!     pha
  2832. .%0753!     lda freeMemLength
  2833. .%0754!     pha
  2834. .%0755!     ldx #freeMemNextPtr
  2835. .%0756!     ldy #5
  2836. .%0757!     jsr zpload
  2837. .%0758!     clc
  2838. .%0759!     pla
  2839. .%0760!     adc freeMemLength
  2840. .%0761!     sta freeMemLength
  2841. .%0762!     pla
  2842. .%0763!     adc freeMemLength+1
  2843. .%0764!     sta freeMemLength+1
  2844. .%0765!
  2845.  
  2846. Here, we wrap things up.  We have the header for the new free chunk all
  2847. prepared and we have tried to coalesce the two neighboring chunks to the new
  2848. chunk.  All we do now is write the new chunk header out to main memory and
  2849. increase the number of bytes free variable by the length of the (original)
  2850. free request.
  2851.  
  2852. .%0766!  +  ldx #2
  2853. .%0767!  -  lda freeNewPtr,x
  2854. .%0768!     sta zp1,x
  2855. .%0769!     dex
  2856. .%0770!     bpl -
  2857. .%0771!     ldx #freeMemNextPtr
  2858. .%0772!     ldy #5
  2859. .%0773!     jsr zpstore
  2860. .%0774!     clc
  2861. .%0775!     lda freeMemory
  2862. .%0776!     adc freeLength
  2863. .%0777!     sta freeMemory
  2864. .%0778!     lda freeMemory+1
  2865. .%0779!     adc freeLength+1
  2866. .%0780!     sta freeMemory+1
  2867. .%0781!     bcc +
  2868. .%0782!     inc freeMemory+2
  2869.  
  2870. We always return with carry cleared, since we don't check for any errors.
  2871.  
  2872. .%0783!  +  clc
  2873. .%0784!     rts
  2874. .%0785!
  2875. .%0786!  ;--------------------------------------------------------------------
  2876. .%0787!  ;*** sort - the application: reads from file #1, writes to file #2
  2877. .%0788!
  2878.  
  2879. This is where the actual application code starts.  If you want to write your
  2880. own program that uses the dynamic memory allocation package, then you can
  2881. follow the structure of this application.
  2882.  
  2883. We start off by declaring the storage areas for the current line being
  2884. processed and for the line being compared to the current line.  The addresses
  2885. reflect the structure of the record for the input line that was discussed
  2886. earlier.  The sorting field starting column number parameter is can be put at
  2887. $8FF since the input line can only be 242 characters long.
  2888.  
  2889. .%0789!  sortbuf    = $b00
  2890. .%0790!  sortbuflen = $b03
  2891. .%0791!  sortline   = $b04
  2892. .%0792!  cmpbuf     = $800
  2893. .%0793!  cmpbuflen  = $803
  2894. .%0794!  cmpline    = $804
  2895. .%0795!  sortColumn = $8ff
  2896. .%0796!
  2897.  
  2898. These are the zero page locations that sort uses.
  2899.  
  2900. .%0797!  eofstat  = $02  ;deferred ST variable ($90)
  2901. .%0798!  sorthead = $03  ;pointer to first line in line list
  2902. .%0799!  sortP    = $06  ;current line for list searching
  2903. .%0800!  sortQ    = $09  ;previous line for list searching
  2904. .%0801!  header   = $0c  ;4 bytes - holds the current line record's header
  2905. .%0802!
  2906.  
  2907. And these are the kernel routines that are called.
  2908.  
  2909. .%0803!  kernelChkin  = $ffc6
  2910. .%0804!  kernelChkout = $ffc9
  2911. .%0805!  kernelClrchn = $ffcc
  2912. .%0806!  kernelChrin  = $ffcf
  2913. .%0807!  kernelChrout = $ffd2
  2914.  
  2915. "echoStatus" can be changed to point to an RTS if you do not want sort to
  2916. print status information out while it is working.
  2917.  
  2918. .%0808!  echoStatus   = kernelChrout
  2919. .%0809!
  2920. .%0810!  ;*** getline( sortline ) : .CS=eof
  2921. .%0811!
  2922.  
  2923. This routine reads a new line in from the current input channel and puts it
  2924. into the processing buffer.  It returns with carry set if there are no more
  2925. lines to read or if a read error occurs.
  2926.  
  2927. .%0812!  getline = *
  2928. .%0813!     ldy #0
  2929.  
  2930. The "eofstat" is checked first to see if the previous character read before
  2931. the new call was the last of the file.  This overcomes the kernel's awkward
  2932. way of setting EOI for the last character rather than when for when you go
  2933. beyond the last character.
  2934.  
  2935. .%0814!  -  bit eofstat
  2936. .%0815!     bvs getlineEof
  2937. .%0816!     jsr kernelChrin
  2938. .%0817!     bcs getlineEof
  2939. .%0818!     sta sortline,y
  2940. .%0819!     iny
  2941. .%0820!     ldx $90
  2942. .%0821!     stx eofstat
  2943.  
  2944. It exits when the maximum line length is exceeded or when a carriage return is
  2945. encountered.
  2946.  
  2947. .%0822!     cpy #242
  2948. .%0823!     bcs getlineExit
  2949. .%0824!     cmp #13
  2950. .%0825!     bne -
  2951. .%0826!     dey
  2952. .%0827!
  2953.  
  2954. A trailing '\0' is appended to the string for easier processing later, and the
  2955. length of the input line record is recorded.  The length of the entire record
  2956. rather than the length of just the text is more convenient to know when
  2957. working with the memory package.
  2958.  
  2959. .%0828!     getlineExit = *
  2960. .%0829!     lda #0
  2961. .%0830!     sta sortline,y
  2962. .%0831!     clc
  2963. .%0832!     tya
  2964. .%0833!     adc #5
  2965. .%0834!     sta sortbuflen
  2966. .%0835!     clc
  2967. .%0836!     rts
  2968. .%0837!
  2969.  
  2970. On end of file, we exit with carry set.  If, however, we have read characters
  2971. before the EOF was encountered, they are returned as belonging to the last
  2972. line of the file.  True EOF will be returned on the next call.
  2973.  
  2974. .%0838!     getlineEof = *
  2975. .%0839!     lda #$40
  2976. .%0840!     sta eofstat
  2977. .%0841!     cpy #0
  2978. .%0842!     bne getlineExit
  2979. .%0843!     sec
  2980. .%0844!     rts
  2981. .%0845!
  2982. .%0846!  ;*** putline( appline )
  2983. .%0847!
  2984.  
  2985. This routine simply writes out the current line ('\0' terminated) and writes
  2986. an additional carriage return, since the getline routine strips off the CR.
  2987.  
  2988. .%0848!  putline = *
  2989. .%0849!     ldy #0
  2990. .%0850!  -  lda sortline,y
  2991. .%0851!     beq +
  2992. .%0852!     jsr kernelChrout
  2993. .%0853!     iny
  2994. .%0854!     bne -
  2995. .%0855!  +  lda #13
  2996. .%0856!     jmp kernelChrout
  2997. .%0857!
  2998. .%0858!  ;*** fetchline( sortP=LinePtr, .AY=Ram0buf )
  2999. .%0859!
  3000.  
  3001. This routine fetches the line at the pointer sortP into RAM0 at the given
  3002. address.  It has to zpload the line header first to determine the record size
  3003. to fetch.
  3004.  
  3005. .%0860!  fetchline = *
  3006. .%0861!     sta zw1
  3007. .%0862!     sty zw1+1
  3008. .%0863!     ldx #2
  3009. .%0864!  -  lda sortP,x
  3010. .%0865!     sta zp1,x
  3011. .%0866!     dex
  3012. .%0867!     bpl -
  3013. .%0868!     ldx #header
  3014. .%0869!     ldy #4
  3015. .%0870!     jsr zpload
  3016. .%0871!     lda header+3
  3017. .%0872!     ldy #0
  3018. .%0873!     jmp fetch
  3019. .%0874!
  3020. .%0875!  ;*** sortGTcmp( sortline, cmpline ) : .CS={sortline >= cmpline}
  3021. .%0876!
  3022.  
  3023. This routine compares the lines stored in the "sortline" and "cmpline" buffers
  3024. and returns with carry set if the "sortline" is larger (alphabetically).  It
  3025. also takes into account the starting comparison positions and handles the case
  3026. of either or both lines not being as long as the start position of the string
  3027. comparison.
  3028.  
  3029. .%0877!  sortGTcmp = *
  3030.  
  3031. This section of code makes bit0 of .X a "1" if sortline is not long enough to
  3032. be compared, and makes bit1 a "1" if cmpline is too short.
  3033.  
  3034. .%0878!     ldx #0
  3035. .%0879!     clc
  3036. .%0880!     lda sortColumn
  3037. .%0881!     adc #5
  3038. .%0882!     cmp sortbuflen
  3039. .%0883!     bcc +
  3040. .%0884!     inx
  3041. .%0885!  +  cmp cmpbuflen
  3042. .%0886!     bcc +
  3043. .%0887!     inx
  3044. .%0888!     inx
  3045.  
  3046. And here is where it takes action depending whether the lines are large enough
  3047. or not.  The cases are:
  3048.  
  3049. . .X=%00000000 - strings are long enough to be compared, so continue
  3050. . .X=%00000001 - sortline is too short, cmpline ok, so return with carry clear
  3051. . .X=%00000010 - cmpline is too short, sortline ok, so return with carry set
  3052. . .X=%00000011 - both sortline and cmpline are too short; carry set
  3053.  
  3054. .%0889!  +  txa
  3055. .%0890!     beq doCompare
  3056. .%0891!     cmp #2
  3057. .%0892!     rts
  3058. .%0893!
  3059. .%0894!     doCompare = *
  3060.  
  3061. This section does the compare if both lines are long enough.
  3062.  
  3063. .%0895!     ldy sortColumn
  3064. .%0896!  -  lda sortline,y
  3065. .%0897!     cmp cmpline,y
  3066. .%0898!     bne +
  3067. .%0899!     cmp #0
  3068. .%0900!     beq +
  3069. .%0901!     iny
  3070. .%0902!     bne -
  3071. .%0903!  +  rts
  3072. .%0904!
  3073. .%0905!  ;*** positionLine( sortline ) : sortQ=prev, sortP=next
  3074. .%0906!
  3075.  
  3076. This routine searches for the correct position in the line list to insert the
  3077. new line, and returns sortQ and sortP to straddle the new line position.  Note
  3078. that this routine causes the list to be in reverse order as discussed
  3079. earlier.
  3080.  
  3081. .%0907!  positionLine = *
  3082.  
  3083. Set P to head and Q to Null.
  3084.  
  3085. .%0908!     ldx #2
  3086. .%0909!  -  lda #bkNull
  3087. .%0910!     sta sortQ,x
  3088. .%0911!     lda sorthead,x
  3089. .%0912!     sta sortP,x
  3090. .%0913!     dex
  3091. .%0914!     bpl -
  3092. .%0915!
  3093. .%0916!     positionSearch = *
  3094.  
  3095. This routine breaks out if the current line pointer is Null.  Otherwise, it
  3096. fetches the current line pointer (sortP) into the cmpline buffer and calls the
  3097. string compare routine.  If the new line read in from the file is greater than
  3098. or equal to the current line already in the list, the search kicks out.  The
  3099. "bcs" on line 924 controls the order of the sort.  Otherwise, the P and Q
  3100. pointers are updated in the usual way and the search continues.
  3101.  
  3102. .%0917!     lda sortP+2
  3103. .%0918!     cmp #bkNull
  3104. .%0919!     beq positionExit
  3105. .%0920!     lda #<cmpbuf
  3106. .%0921!     ldy #>cmpbuf
  3107. .%0922!     jsr fetchline
  3108. .%0923!     jsr sortGTcmp
  3109. .%0924!     bcs positionExit    ;** controls sort order
  3110. .%0925!     ldx #2
  3111. .%0926!  -  lda sortP,x
  3112. .%0927!     sta sortQ,x
  3113. .%0928!     lda cmpbuf,x
  3114. .%0929!     sta sortP,x
  3115. .%0930!     dex
  3116. .%0931!     bpl -
  3117. .%0932!     bmi positionSearch
  3118. .%0933!
  3119. .%0934!     positionExit = *
  3120.  
  3121. At this point, sortP and sortQ straddle the position to put the new line, so
  3122. we return.
  3123.  
  3124. .%0935!     rts
  3125. .%0936!
  3126. .%0937!  ;*** storeline( sortline )    {between sortQ and sortP}
  3127. .%0938!
  3128.  
  3129. This routine actually stores the new line read in between the sortQ and sortP
  3130. lines.
  3131.  
  3132. .%0939!  storeline = *
  3133.  
  3134. First, space for the new line is allocated.
  3135.  
  3136. .%0940!     lda sortbuflen
  3137. .%0941!     ldy #0
  3138. .%0942!     jsr malloc
  3139. .%0943!     bcc +
  3140. .%0944!     rts
  3141.  
  3142. And the new line's next pointer is set to point to sortP.
  3143.  
  3144. .%0945!  +  ldx #2
  3145. .%0946!  -  lda sortP,x
  3146. .%0947!     sta sortbuf,x
  3147. .%0948!     dex
  3148. .%0949!     bpl -
  3149.  
  3150. And the new line is stashed out to main memory.
  3151.  
  3152. .%0950!     lda #<sortbuf
  3153. .%0951!     ldy #>sortbuf
  3154. .%0952!     sta zw1
  3155. .%0953!     sty zw1+1
  3156. .%0954!     lda sortbuflen
  3157. .%0955!     ldy #0
  3158. .%0956!     jsr stash
  3159.  
  3160. Now all that is left to is make the previous line record (sortQ) point to the
  3161. new line record.
  3162.  
  3163. .%0957!     lda sortQ+2
  3164. .%0958!     cmp #bkNull
  3165. .%0959!     beq storelineFirst
  3166.  
  3167. If there is an actual previous line, the new line pointer is written out over
  3168. the next line pointer in its header.
  3169.  
  3170. .%0960!     ldx #2
  3171. .%0961!  -  lda zp1,x
  3172. .%0962!     ldy sortQ,x
  3173. .%0963!     sta sortQ,x
  3174. .%0964!     sty zp1,x
  3175. .%0965!     dex
  3176. .%0966!     bpl -
  3177. .%0967!     ldx #sortQ
  3178. .%0968!     ldy #3
  3179. .%0969!     jsr zpstore
  3180. .%0970!     clc
  3181. .%0971!     rts
  3182. .%0972!
  3183.  
  3184. If there is no actual previous line, then the line list head pointer is set to
  3185. point to the new line (which is now the first line on the list).
  3186.  
  3187. .%0973!     storelineFirst = *
  3188. .%0974!     ldx #2
  3189. .%0975!  -  lda zp1,x
  3190. .%0976!     sta sorthead,x
  3191. .%0977!     dex
  3192. .%0978!     bpl -
  3193. .%0979!     clc
  3194. .%0980!     rts
  3195. .%0981!
  3196. .%0982!  ;*** readfile()
  3197. .%0983!
  3198.  
  3199. This routine reads in the file and puts the lines into their correct sorted
  3200. positions as it is reading.
  3201.  
  3202. .%0984!  readfile = *
  3203.  
  3204. Clear the line list by setting the head pointer to Null.
  3205.  
  3206. .%0985!     ldx #2
  3207. .%0986!     lda #bkNull
  3208. .%0987!  -  sta sorthead,x
  3209. .%0988!     dex
  3210. .%0989!     bpl -
  3211.  
  3212. Set the EOF flag to 0 and set the current input channel to logical file #1
  3213. which is assumed to be opened before the sort utility is invoked.
  3214.  
  3215. .%0990!     lda #0
  3216. .%0991!     sta eofstat
  3217. .%0992!     ldx #1
  3218. .%0993!     jsr kernelChkin
  3219. .%0994!     bcs readExit
  3220.  
  3221. Until EOF, read the new line, find the position in the line list, store it,
  3222. print out a "." to indicate to the user that another line has been processed,
  3223. and repeat.  Exit on EOF.
  3224.  
  3225. .%0995!  -  jsr getline
  3226. .%0996!     bcs readExit
  3227. .%0997!     jsr positionLine
  3228. .%0998!     jsr storeline
  3229. .%0999!     bcs readExit
  3230. .%1000!     lda #"."
  3231. .%1001!     jsr echoStatus
  3232. .%1002!     jmp -
  3233. .%1003!
  3234. .%1004!     readExit = *
  3235. .%1005!     rts
  3236. .%1006!
  3237. .%1007!  ;*** writefile()
  3238. .%1008!
  3239.  
  3240. This routine writes the line list out to logical file number 2 which is
  3241. assumed to be opened before the sort utility is invoked.  This routine follows
  3242. the standard structure for processing a linked list.
  3243.  
  3244. .%1009!  writefile = *
  3245. .%1010!     ldx #2
  3246. .%1011!  -  lda sorthead,x
  3247. .%1012!     sta sortP,x
  3248. .%1013!     dex
  3249. .%1014!     bpl -
  3250. .%1015!     ldx #2
  3251. .%1016!     jsr kernelChkout
  3252. .%1017!
  3253. .%1018!     writeLine = *
  3254. .%1019!     lda sortP+2
  3255. .%1020!     cmp #bkNull
  3256. .%1021!     beq writeExit
  3257. .%1022!     lda #<sortbuf
  3258. .%1023!     ldy #>sortbuf
  3259. .%1024!     jsr fetchline
  3260. .%1025!     jsr putline
  3261. .%1026!     ldx #2
  3262. .%1027!  -  lda sortbuf,x
  3263. .%1028!     sta sortP,x
  3264. .%1029!     dex
  3265. .%1030!     bpl -
  3266. .%1031!     jmp writeLine
  3267. .%1032!
  3268. .%1033!     writeExit = *
  3269. .%1034!     jsr kernelClrchn
  3270. .%1035!     rts
  3271. .%1036!
  3272. .%1037!  ;*** reverseList()
  3273. .%1038!
  3274.  
  3275. This routine will reverse the order of the line list.  Starting from the head
  3276. line, each line is extracted and is made to point to the previous line
  3277. extracted.  No data actually has to be moved around; only the headers of the
  3278. line records have to be changed.
  3279.  
  3280. .%1039!  reverseFile = *
  3281. .%1040!     ldx #2
  3282. .%1041!  -  lda sorthead,x
  3283. .%1042!     sta zp1,x
  3284. .%1043!     lda #bkNull
  3285. .%1044!     sta sorthead,x
  3286. .%1045!     dex
  3287. .%1046!     bpl -
  3288. .%1047!
  3289. .%1048!     reverseLine = *
  3290. .%1049!     lda zp1+2
  3291. .%1050!     cmp #bkNull
  3292. .%1051!     beq reverseExit
  3293.  
  3294. Fetch the pointer from the current line into sortP and then replace it with
  3295. the value at sorthead (the previous line altered).
  3296.  
  3297. .%1052!     ldx #sortP
  3298. .%1053!     ldy #3
  3299. .%1054!     jsr zpload
  3300. .%1055!     ldx #sorthead
  3301. .%1056!     ldy #3
  3302. .%1057!     jsr zpstore
  3303.  
  3304. Make sorthead point to the current line, and then go to the next line whose
  3305. pointer was extracted from the current line (before the current line was
  3306. changed).
  3307.  
  3308. .%1058!     ldx #2
  3309. .%1059!  -  lda zp1,x
  3310. .%1060!     sta sorthead,x
  3311. .%1061!     lda sortP,x
  3312. .%1062!     sta zp1,x
  3313. .%1063!     dex
  3314. .%1064!     bpl -
  3315. .%1065!     bmi reverseLine
  3316. .%1066!
  3317. .%1067!     reverseExit = *
  3318. .%1068!     rts
  3319. .%1069!
  3320. .%1070!  ;*** freefile()
  3321. .%1071!
  3322.  
  3323. This routine scans through the lines in the line list and deallocates each
  3324. line record.
  3325.  
  3326. .%1072!  freefile = *
  3327. .%1073!     ldx #2
  3328. .%1074!  -  lda sorthead,x
  3329. .%1075!     sta zp1,x
  3330. .%1076!     dex
  3331. .%1077!     bpl -
  3332. .%1078!
  3333. .%1079!     freeLine = *
  3334. .%1080!     lda zp1+2
  3335. .%1081!     cmp #bkNull
  3336. .%1082!     bne +
  3337. .%1083!     rts
  3338. .%1084!  +  ldx #header
  3339. .%1085!     ldy #4
  3340. .%1086!     jsr zpload
  3341. .%1087!     lda header+3
  3342. .%1088!     ldy #0
  3343. .%1089!     jsr free
  3344. .%1090!     ldx #2
  3345. .%1091!  -  lda header,x
  3346. .%1092!     sta zp1,x
  3347. .%1093!     dex
  3348. .%1094!     bpl -
  3349. .%1095!     jmp freeLine
  3350. .%1096!
  3351. .%1097!  ;*** main()
  3352. .%1098!
  3353.  
  3354. Finally!  The main routine sets the sort key column and calls each of the
  3355. subroutines for the different phases of the sort and prints out a letter
  3356. indicating what the program is currently doing.
  3357.  
  3358. .%1099!  main = *
  3359. .%1100!     cmp #1
  3360. .%1101!     bcc +
  3361. .%1102!     sbc #1
  3362. .%1103!  +  sta sortColumn
  3363. .%1104!     lda #"s"
  3364. .%1105!     jsr echoStatus
  3365. .%1106!     jsr startup
  3366. .%1107!     lda #"r"
  3367. .%1108!     jsr echoStatus
  3368. .%1109!     jsr readfile
  3369. .%1110!     lda #"v"
  3370. .%1111!     jsr echoStatus
  3371. .%1112!     jsr reverseFile
  3372. .%1113!     lda #"w"
  3373. .%1114!     jsr echoStatus
  3374. .%1115!     jsr writefile
  3375. .%1116!     lda #"f"
  3376. .%1117!     jsr echoStatus
  3377. .%1118!     jsr freefile
  3378. .%1119!     lda #"x"
  3379. .%1120!     jsr echoStatus
  3380. .%1121!     jsr shutdown
  3381. .%1122!     lda #13
  3382. .%1123!     jsr echoStatus
  3383.  
  3384. It returns with .A set to zero in case the user calls sort again and forgets
  3385. to specify a value for the sorting column using the BASIC SYS statement.
  3386.  
  3387. .%1124!     lda #0
  3388. .%1125!     rts
  3389.  
  3390. ------------------------------------------------------------------------------
  3391.  
  3392. 5. FUTURE ENHANCEMENTS
  3393.  
  3394. This dynamic memory allocation package does not support expanded internal
  3395. memory (as specified in Twin Cities-128 Magazine) or RamLink memory.  I am
  3396. planning to modify the memory allocation in the Zed-128 program to support
  3397. both of these kinds of memory.  The extra internal memory banks would be
  3398. accessed in a similar manner as RAM1 is, except that I will need to have some
  3399. special bank numbers for them, since they cannot be handled in exactly the
  3400. same way as RAM0 and RAM1.  I will also have to modify that other MMU register
  3401. in order to select which real banks show up in the RAM2 and RAM3 positons.
  3402.  
  3403. The memory inside a RamLink can be accessed in a similar way to how memory is
  3404. accessed in an REU.  One big difference is that the layout of the storage in a
  3405. RamLink is actually organized.  A RamLink (and a RamDrive I assume) can have
  3406. up to 31 partitons of various types.  I am thinking that to sniff a RamLink,
  3407. the package will check to see if you have a RamLink and will then check to see
  3408. if you have partiton number 31 set up as a "foreign" mode partition with the
  3409. name "swap".  If so, the package will ask the RL-DOS for the start address and
  3410. length of the partition and will then use the RamLink memory instead of an
  3411. REU.  This makes sense since an REU can be made to be part of the RamLink
  3412. and since you can get a lot more memory in a RamLink than I have ever heard of
  3413. in an expanded REU.  I personally have an 8 Meg RamLink and I have set aside a
  3414. 1 Meg partition for the swap space.  Now I just have to write the software to
  3415. use it.
  3416.  
  3417. These additional types of memory can be seemlessly implemented into this
  3418. package and the usage will be compeletely transparent to the user and to the
  3419. higher level routines.
  3420.  
  3421. Also, although I have not attempted to do this, the code presented here could
  3422. be ported to the Commodore 64.  The common code routines would be removed
  3423. since the 64 has only one internal bank, and instead of using the MMU to
  3424. select RAM0, you would store into the processor I/O port to select the bare
  3425. internal RAM.  (You would also have to worry about interrupts happening while
  3426. you are accessing this memory).  All of the higher level code above the
  3427. zpload, zpstore, fetch and stash routines would (probably) stay (pretty much)
  3428. the same, since they call the lower level routines to do the actual
  3429. machine-specific grunt work.
  3430.  
  3431. If you have any questions or comments about this article, feel free to drop me
  3432. a line.
  3433.  
  3434. ------------------------------------------------------------------------------
  3435.  
  3436. 6. UUENCODED BINARIES
  3437.  
  3438. Here are the BASIC program and the machine language subroutines for the
  3439. sorting utility.  They are in uuencoded form and you will probably have to
  3440. extract them into separate files before you uudecode them.  Enjoy!
  3441.  
  3442. begin 640 sort
  3443. M`1PF'`$`222R(DE.4%541DE,12Y46%0B(#H@242R."`Z(%-&LC$`11P"`$\D
  3444. MLB)/5510551&24Q%+E185"(@.B!/1+(X`$L<`P`Z`&8<9`"9(DQ/041)3D<@
  3445. M4T]25"Y"24XN+BXB`'`<;@#^`B`Q-0"'''@`_A$B4T]25"Y"24XB+%4H240I
  3446. M`*4<@@"9(E-#4D%40TA)3D<@3TQ$($9)3$4N+BXB`+4<C`#R*$\D*2Q5*$]$
  3447. M*0#'')8`F2)33U)424Y'+BXN(@#;'*``GS$L240L,BPB,#HBJDDD`/8<J@"?
  3448. M,BQ/1"PS+"(P.B*J3R2J(BQ3+%<B``D=M`">(-$H(C$S,#`B*2Q31@`0';X`
  3449. =H#(`%QW(`*`Q`"@=T@"9(D9)3DE32$5$(2(````H
  3450. `
  3451. end
  3452.  
  3453. begin 640 sort.bin
  3454. M`!-,I!E,(Q-,-A-,R!-,_A-,#11,;11,`Q9,R18``````````*D`2"BI#HT`
  3455. M_R!($R#\%""G%6"B`*D`G0`"Z.!RT/BI`(T`_V"B`+U6$YT``NC@<I#U8*7\
  3456. MC0#_A/V@`+'ZE0#HR,3]D/:I#HT`_V"E_(T`_X3]H`"U`)'ZZ,C$_9#VJ0Z-
  3457. M`/]@B/`-C0+_L?J-`?^1_HC0\XT"_['ZC0'_D?ZI#HT`_V"(\`V-`?^Q_HT"
  3458. M_Y'ZB-#SC0'_L?Z-`O^1^JD.C0#_8*7\,`-,``*,!]^@D8T&WXX"WZT'U8T#
  3459. MWZ7ZC03?I?N-!=^I`(T(WZTPT*(`CC#0C`'?C3#08*7\,`-,&0*,!]^@D$S4
  3460. M$Z;\$`6BD4S-%,``T"*HT`%@X#_P`TPR`HX`_XCP!['ZD?Z(T/FQ^I'^J0Z-
  3461. M`/]@A8"$@:7[A8*E_X6#I8'P#Z;\H``@'A3F^^;_QH'0\:2`\`6F_"`>%*6"
  3462. MA?NE@X7_8*;\$`6BD$S-%,``T"*HT`%@X#_P`TQ2`HX`_XCP!['^D?J(T/FQ
  3463. M_I'ZJ0Z-`/]@A8"$@:7_A8*E^X6#I8'P#Z;\H``@?A3F^^;_QH'0\:2`\`6F
  3464. M_"!^%*6"A?^E@X7[8(T'WXP(WZ7^I/^-`M^,`]^E^J3[C03?C`7?I?R-!M^L
  3465. M,-"I`(TPT(X!WXPPT&!_J3^-`/^B"KT`WYT`"\H0]ZD.C0#_J0"-"=^-"M^M
  3466. M`-^B`KVA%96`RA#XJ0"-'!.I`**`A?J%^X;\(%P5L`?N'!/F_-#TK1P3T!6I
  3467. M/XT`_Z(*O0`+G0#?RA#WJ0Z-`/]@K1P3S?L4D`%@I?R%@Z*`H`0@#!,@A16P
  3468. M#Z7\2*F`A?R%@R"%%6B%_&"I`(6$A8>BA*`$(`D3H@.UA-6`T`7*$/<88#A@
  3469. M4LE$0`3[H@*I`)T@$ZG_G1T3RA#S.*T2$O`!&*T3$NVD%:BMI!6B/R#X%:VE
  3470. M%:RF%:)_(/@5J0"%CZ6/S1P3L!4)@(7\J0"%^H7[J?B@_R`8$^:/T.1@A?N&
  3471. M_*D`A?I,&!,8:0>0`<@I^(6%A(:B`KT=$Y7ZJ?^5A\H0]*7\R?_0#ZG_A?J%
  3472. M^X7\J0&-&Q,X8**`H`4@"1.E@\6%I83EAK`0H@*U^I6'M8"5^LH0]4P=%CBM
  3473. M(!/EA8T@$ZTA$^6&C2$3L`/.(A.E@\6%T`:EA.6&\",XI8/EA86#I83EAH6$
  3474. MHH"@!2`,$QBE^F6#A?JE^V6$A?L88*6)R?_0#*("M8"='1/*$/@88*("M?JT
  3475. MAY6'E/K*$/6B@*`#(`P3H@*UAY7ZRA#Y&&`8:0>0`<@I^(6%A(:B`K7ZE8>]
  3476. M'1.5^JG_E8K*$/"E_,G_\"BE_,6)D"+0"J7ZQ8>E^^6(L!:B@*`#(`D3H@*U
  3477. M^I6*M8"5^LH0]3#2H@*UBI7ZRA#YI?S)_]`:H@*]'1.5@+6'G1T3RA#SI86D
  3478. MAH6#A(1,A!>B@*`%(`D3I?S%B=`J&*7Z98.JI?MEA,6(T!SDA]`8&*6#986%
  3479. M@Z6$98:%A*("M8J5A\H0^3`/HH>@`R`,$Z6%I(:%@X2$I8G%@M`S&*6'98.J
  3480. MI8AEA,6!T"7D@-`AH@*U@)7ZRA#YI81(I8-(HH"@!2`)$QAH98.%@VAEA(6$
  3481. MH@*UAY7ZRA#YHH"@!2`,$QBM(!-EA8T@$ZTA$V6&C2$3D`/N(A,88*``)`)P
  3482. M)"#/_[`?F00+R*:0A@+`\+`%R0W0YXBI`)D$"QB8:06-`PL88*E`A0+``-#J
  3483. M.&"@`+D$"_`&(-+_R-#UJ0U,TO^%_H3_H@*U!I7ZRA#YH@R@!"`)$Z4/H`!,
  3484. M#Q.B`!BM_PAI!<T#"Y`!Z,T#")`"Z.B*\`/)`F"L_PBY!`O9!`C0!\D`\`/(
  3485. MT/%@H@*I_Y4)M0.5!LH0]:4(R?_P'*D`H`@@*Q@@1ABP$*("M0:5";T`")4&
  3486. MRA#T,-Y@K0,+H``@%1.0`6"B`K4&G0`+RA#XJ0"@"X7^A/^M`PN@`"`2$Z4+
  3487. MR?_P%J("M?JT"94)E/K*$/6B":`#(`P3&&"B`K7ZE0/*$/D88*("J?^5`\H0
  3488. M^ZD`A0*B`2#&_[`5(.47L!`@=!@@I!BP"*DN(-+_3`098*("M0.5!LH0^:("
  3489. M(,G_I0C)__`7J0"@"R`K&"`9&*("O0`+E0;*$/A,*!D@S/]@H@*U`Y7ZJ?^5
  3490. M`\H0]:7\R?_P':(&H`,@"1.B`Z`#(`P3H@*U^I4#M0:5^LH0]3#=8*("M0.5
  3491. M^LH0^:7\R?_0`6"B#*`$(`D3I0^@`"`8$Z("M0R5^LH0^4R#&<D!D`+I`8W_
  3492. M"*E3(-+_(`,3J5(@TO\@\!BI5B#2_R!)&:E7(-+_(!H9J48@TO\@>AFI6"#2
  3493. ,_R`&$ZD-(-+_J0!@
  3494. `
  3495. end
  3496.  
  3497. ============================================================================
  3498. Next Issue: (Hopefully!!! :-] )
  3499.  
  3500. The 1351 Mouse Demystified
  3501.  
  3502.   An indepth look at how the 1351 mouse operates and how to access it within 
  3503. your ML programs, in addition to a BASIC driver for the 80 column screen.
  3504.  
  3505. ML Tutor - Part 3
  3506.  
  3507.   In this edition we take a look at reading and writing commands to the disk
  3508. drive, including reading the disk directory.  This article will also parallel
  3509. the discussion of the C=128 and C=64 KERNAL jump tables of available routines.
  3510.  
  3511. KERNAL 64/128 
  3512.  
  3513.   The C=128 and C=64 jump table that points to many valuable system routines is
  3514. listed and discussed with example applications and how to use them.
  3515.  
  3516. Bursting your 128
  3517.  
  3518.   This article will examine the routines and mysteries about how to use Burst
  3519. commands on the 1571 and 1581, including the Fastload utility and the block
  3520. Read and Write calls.
  3521. ============================================================================
  3522. END of C= Hacking Issue 2.
  3523. ============================================================================
  3524.  
  3525.