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