magazines:chacking2
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | magazines:chacking2 [2015-04-17 04:34] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== C= Hacking #2 ====== | ||
+ | < | ||
+ | | ||
+ | CC | ||
+ | CC | ||
+ | CC HHHHHHHH | ||
+ | CC | ||
+ | CC | ||
+ | | ||
+ | |||
+ | Volume 1 - Issue 2 - April 22, 1992 | ||
+ | ============================================================================== | ||
+ | |||
+ | Editor' | ||
+ | by Craig Taylor (duck@pembvax1.pembroke.edu) | ||
+ | |||
+ | | ||
+ | I'm glad of the reception it's gotten from the Commodore community at large. I'd | ||
+ | like to thank each of the author' | ||
+ | put into it as well as their time. | ||
+ | |||
+ | Please note that all files, documentation etcetera associated with C= Hacking | ||
+ | and whatnot contained within are also now available at tybalt.caltech.edu via | ||
+ | anonymous ftp under the directory / | ||
+ | contained within or corrections will be posted there as well as mentioned | ||
+ | here. Currently it has the correct 1st issue and (soon to be) 2nd issue. | ||
+ | Robert Knop's file bmover.sfx is there for the Banking Geos article in this | ||
+ | issue. | ||
+ | |||
+ | It seems as if we're averaging about 2 months for each issue and hopefully | ||
+ | we'll keep that rate during the summer but due to an internship (I'll hopefully | ||
+ | get) I may not have net access during the summer. | ||
+ | until after I get back to school in the fall. | ||
+ | |||
+ | Also, if you've got any ideas for articles or have written a program that is | ||
+ | unique that you'd be interested in documenting and p'haps letting other people | ||
+ | see some of the tricks of the trade then please send any queries to | ||
+ | duck@pembvax1.pembroke.edu. | ||
+ | |||
+ | ****************** WARNINGS, UPDATES, BUG REPORTS, ETC... ********************* | ||
+ | |||
+ | Please note that in the last issue when the undocumented opcodes were | ||
+ | discussed that they are _VERY NON-STANDARD_. | ||
+ | for the C=128 or C=64, in all likelehood, _will not work_. Zip's board [when are | ||
+ | they ever gonna finish it?] for the C=128 will be based on a similair processor | ||
+ | to the 8502 and is practically guarenteed not to work with the undocumented | ||
+ | op-codes. | ||
+ | aware that they may be in-compatible with future systems. | ||
+ | |||
+ | ============================================================================ | ||
+ | |||
+ | Note: Permission is granted to re-distribute this " | ||
+ | freely for non-profit use. However, please contact individual authors for | ||
+ | permission to publish or re-distribute articles seperately. | ||
+ | |||
+ | *** AUTHORS LISTED BELOW RETAIN ALL RIGHTS TO THEIR ARTICLES *** | ||
+ | |||
+ | ============================================================================ | ||
+ | |||
+ | In this edition we've got the following articles: | ||
+ | |||
+ | Learning ML - Part 2 | ||
+ | |||
+ | Yes, the infamous learning machine langauge tutors by Craig Taylor | ||
+ | (duck@pembvax1.pembroke.edu). | ||
+ | and it's usefulness in printing strings of characters. | ||
+ | |||
+ | 8563 : An In-Depth Look | ||
+ | |||
+ | This article documents and details the 8563 in-depth. | ||
+ | available registers, documents their functions and suggested methods of getting | ||
+ | certain effects. | ||
+ | as read the 16k or 64k of memory that contains the VDC char-set, screen memory | ||
+ | etc. Written by Craig Taylor (duck@pembvax1.pembroke.edu). | ||
+ | |||
+ | The Poor Man's Way to Getting Files from MS-Dos Diskettes | ||
+ | |||
+ | Now there' | ||
+ | a public domain program that will only read files of 43k or less and a IBM | ||
+ | program to split the files up. There are better ways, but if you don't want | ||
+ | to pay for Big-Blue Reader this is one method to go. Written by Mark Lawrence | ||
+ | (9152427D@Levels.UniSa.Edu.Au). | ||
+ | |||
+ | Banking on Geos | ||
+ | |||
+ | GEOS 128, being an extended and expanded version of GEOS 64, provides a | ||
+ | contiguous block of application space in a single RAM bank. The " | ||
+ | programming documentation makes few references to the use of other banks in | ||
+ | GEOS. This article describes accessing other RAM banks (including RAM banks 2 | ||
+ | and 3 for 256K expanded 128's) under GEOS128, using both the GEOS Kernal | ||
+ | routines and more direct methods. | ||
+ | |||
+ | Dynamic Memory Allocation | ||
+ | |||
+ | Written by Craig Bruce (csbruce@ccnga.uwaterloo.ca) this article examines | ||
+ | how to implement and use dynamically allocated memory that will allow your | ||
+ | programs to better utilize all of the available memory on your C=128, including | ||
+ | expansion memory. | ||
+ | is a text editor that can edit 600KByte files on a 512K expanded 128. | ||
+ | |||
+ | ============================================================================= | ||
+ | </ | ||
+ | ====== Beginning ML #2 ====== | ||
+ | < | ||
+ | by Craig Taylor (duck@pembvax1.pembroke.edu) | ||
+ | |||
+ | Last time we introduced the definition of what exactly Machine Language / | ||
+ | Assembly Language is along with an example of clearing the screen in Machine | ||
+ | Language. | ||
+ | |||
+ | Now, in this issue let's print my name (and later your name). Looking at the | ||
+ | code from last time the following assembly source jumps to mind: | ||
+ | |||
+ | ------------ | ||
+ | print_1.asm: | ||
+ | |||
+ | lda #147 ; clr/screen code | ||
+ | jsr $ffd2 ; print | ||
+ | lda #' | ||
+ | jsr $ffd2 ; print | ||
+ | lda #' | ||
+ | jsr $ffd2 | ||
+ | lda #' | ||
+ | jsr $ffd2 | ||
+ | lda #' | ||
+ | jsr $ffd2 | ||
+ | lda #' | ||
+ | jsr $ffd2 | ||
+ | lda #32 ; code for space | ||
+ | jsr $ffd2 | ||
+ | lda #' | ||
+ | jsr $ffd2 | ||
+ | . | ||
+ | | ||
+ | | ||
+ | rts | ||
+ | ---------- | ||
+ | |||
+ | Now, for short strings like " | ||
+ | something like " | ||
+ | in terms of the amount of memory and the amount of typing (eegh! - typing!) | ||
+ | involved. There' | ||
+ | | ||
+ | It's called indexed addressing. What is this you say? Let's first take a | ||
+ | look at the above program using indexed addressing and then explain it. | ||
+ | |||
+ | ------------ | ||
+ | print_2.asm | ||
+ | |||
+ | ldy #$00 | ||
+ | | ||
+ | jsr $ffd2 | ||
+ | iny | ||
+ | cpy #numchars | ||
+ | bne loop | ||
+ | rts | ||
+ | |||
+ | | ||
+ | .ascii "Craig Taylor" | ||
+ | |||
+ | | ||
+ | |||
+ | ------------ | ||
+ | |||
+ | Hmm, looks a little bit confusing 'eh? | ||
+ | |||
+ | What we're doing is using the register y to act as a pointer to grab the | ||
+ | y'th character in what is at memory location STRING. If y is 0 then we'll get | ||
+ | string+0 which happens to be a 147. | ||
+ | |||
+ | The .byte and .ascii directives are not real instructions the computer | ||
+ | understands. What happens is that your assembler sees that you want data | ||
+ | put at those locations so it convert 147 and "Craig Taylor" | ||
+ | and puts them at the proper locations, relieving you the burden of doing it. | ||
+ | |||
+ | The numchars = *-string looks confusing at first... obviously, numchars | ||
+ | stands for the number of characters we need to print out but how is it being | ||
+ | figured? | ||
+ | to in something called a program counter of PC. Most assemblers also will let | ||
+ | you have the value at assembly time by referencing it using the " | ||
+ | " | ||
+ | assembling the .byte and .ascii instruction " | ||
+ | address that the assembler would put any instructions at, had we had any. | ||
+ | Now, *-string basically is saying to the compiler, look, take the current | ||
+ | program counter (where it's assembling to) and subtract it from where the | ||
+ | symbol string starts at (which it just assembled a while back). This should | ||
+ | be then, our number of characters we have. | ||
+ | |||
+ | WALK-THROUGH: | ||
+ | |||
+ | Register Y is initially set to zero in the first instruction, | ||
+ | begin with the first character. The first character is at string+0, not | ||
+ | string+1. This may seem a little bit odd at first, but try thinking of it this | ||
+ | way: | ||
+ | |||
+ | Take, for example, 3 diskettes. Put them in a row and call the one on the | ||
+ | left " | ||
+ | | ||
+ | seem a little bit strange at first, but after thinking about it a while | ||
+ | | ||
+ | count starting from zero, in the real world, typically from one. | ||
+ | |||
+ | The lda string,y instruction is telling the computer to reference string as | ||
+ | if it was an array, get the byte at location string + y, or for you basic | ||
+ | programmers thinking of string as an array of bytes (with no bounds checking) | ||
+ | string[y]. Thus, the accumulator is equal to the yth byte starting from | ||
+ | the location string is defined to be. | ||
+ | |||
+ | We then call the routine Commodore so graciously provided for us that prints | ||
+ | out the contents of the accumulator. Now, some routines, as we'll see in other | ||
+ | editions, are not so nice and may change the value of the accumulator, | ||
+ | x and y registers. However, Commodore was extra nice and the routine at $ffd2 is | ||
+ | guaranteed not to change any of the registers. | ||
+ | |||
+ | The routine then " | ||
+ | will " | ||
+ | performed on them other than increment and decrement operations (ie: adding | ||
+ | one and subtracting one). The only register that allows addition or | ||
+ | subtraction is the accumulator. However, in this case we just want y to point | ||
+ | to the next character, the next byte so " | ||
+ | | ||
+ | We then " | ||
+ | the # sign. If we hadn't have had that there, it would' | ||
+ | whatever memory location numchars was defined for. Numchars was set up to hold | ||
+ | the number of characters in the string, not be a pointer for a memory location. | ||
+ | |||
+ | Now that we've compared it, we " | ||
+ | back to where loop is at (in this case, where we load a with character again). | ||
+ | |||
+ | If it was equal we fall through to the RTS where we return from our little | ||
+ | program. | ||
+ | |||
+ | Basically, we've got something like the following flowchart: | ||
+ | _______ | ||
+ | / START \ | ||
+ | | ||
+ | | | ||
+ | \|/ | ||
+ | +-----------------+ | ||
+ | | Set Index (Y) | | ||
+ | | first character | | ||
+ | +-----------------+ | ||
+ | | | ||
+ | \|/ | ||
+ | +-------------------+ | ||
+ | | Get the character | / | ||
+ | | pointed to by | ||
+ | | the index(Y) | ||
+ | +-------------------+ | ||
+ | | | | ||
+ | | ||
+ | +-------------+ | ||
+ | | Print it | | | ||
+ | +-------------+ | ||
+ | | | | ||
+ | | ||
+ | +------------------------+ | ||
+ | | Increment the Index(Y) | | | ||
+ | +------------------------+ | ||
+ | | | | ||
+ | | ||
+ | /\ | | ||
+ | /= \ | | ||
+ | /# of\ | | ||
+ | / | ||
+ | /to print\___no, | ||
+ | \??? / | ||
+ | | ||
+ | \ / | ||
+ | | ||
+ | \/ | ||
+ | | | ||
+ | \|/ | ||
+ | _____ | ||
+ | / END \ | ||
+ | | ||
+ | |||
+ | Indexed addressing is used *very* often in assembly language. | ||
+ | with the second program and experiment with it until you understand fully what | ||
+ | is going on. Next time we'll look at how to access some of the diskette | ||
+ | routines and to display a file on disk. | ||
+ | |||
+ | =============================================================================== | ||
+ | </ | ||
+ | ====== An In-Depth Look at the 8563 Video Chip on the C= 128 ====== | ||
+ | < | ||
+ | by Craig Taylor (duck@pembvax1.pembroke.edu) | ||
+ | |||
+ | Due to the article in the last issue by Craig Bruce (csbruce@ccnga.uwaterloo. | ||
+ | ca) and some letters from people asking about how the 8563 Video Chip works and | ||
+ | more technical information this article will attempt to present as much detail | ||
+ | as possible on the 8563 chip & it's various capibilities. | ||
+ | |||
+ | | ||
+ | --------------------- | ||
+ | ! Hardware Aspects: ! | ||
+ | --------------------- | ||
+ | |||
+ | The following is a physical layout of the 8563 and the available pin outs: | ||
+ | |||
+ | +------------------+ | ||
+ | 42 o_|DD7 | ||
+ | 41 o_|DD6 | ||
+ | 40 o_|DD5 | ||
+ | 39 o_|DD4 | ||
+ | 38 o_|DD3 | ||
+ | 36 o_|DD2 | ||
+ | 35 o_|DD1 | ||
+ | 34 o_|DD0 | ||
+ | | | RES - (Unused) Reset all scan cnts | ||
+ | | | TST - (Unused) Test purposes only | ||
+ | 10 o_|D7 /CAS|_o 48 | ||
+ | 11 o_|D6 /RAS|_o 47 / | ||
+ | 13 o_|D5 DR/W|_o 21 / | ||
+ | 14 o_|D4 | DCLK - Video Dot Clock | ||
+ | 15 o_|D3 R|_o 46 | ||
+ | 16 o_|D2 G|_o 45 | ||
+ | 17 o_|D1 B|_o 44 | ||
+ | 18 o_|D0 I|_o 43 | ||
+ | | | | ||
+ | | | | ||
+ | 8 o_|/ | ||
+ | 7 o_|/ | ||
+ | 9 o_|R/ | ||
+ | 23 o_|/ | ||
+ | | | | ||
+ | | CCLK|_o 1 | ||
+ | 25 o_|/ | ||
+ | | | | ||
+ | | | ||
+ | 2 o_|/ | ||
+ | +------------------+ | ||
+ | !12 | ||
+ | o | ||
+ | |||
+ | |||
+ | Taken from Pg. 596-8 C=128 Programmer' | ||
+ | | ||
+ | |||
+ | |||
+ | |||
+ | | ||
+ | | How Commodore Hooked It Up! | | ||
+ | | ||
+ | |||
+ | Now, the 8563 is hooked up to the computer via the following method: | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | Confusing 'eh? (The %'s represent control signals that also are used).. What | ||
+ | basically happens is that every time the computer wants to access the 8563 to | ||
+ | program or change one of it's numerous registers it has to store the register | ||
+ | number to $d600, then loop until the 7th bit of $d600 changes to a 1. Once | ||
+ | this is done, you can then read or write a value to/from $d601. | ||
+ | |||
+ | Commodore also employed the MMU (Memory Management Unit) to manipulate pages | ||
+ | of memory and thus, the 8563 only shows up in the I/O page (usually referenced | ||
+ | as Bank 15 or a value of $00 in the MMU Register at $ff00) or in pages that the | ||
+ | I/O section of memory is enabled. | ||
+ | |||
+ | The register at $d600 in the I/O space of the C=128 is laid out as follows: | ||
+ | |||
+ | Bit Position: | ||
+ | 7 | ||
+ | Status | ||
+ | |||
+ | When a value is placed in $d600 instead of putting the value in Status, | ||
+ | LightPen bits etc, the value reflects which register # is requested. Bit 7 of | ||
+ | this register (Status) will then return a binary 1 when $d601 reflects the | ||
+ | actual value of the register just poked to $d600. (See the ML routines for | ||
+ | storing and reading values to/from registers at the end of this article). When | ||
+ | a value is first place in this register, $d600 bit 7 is equal to a zero. | ||
+ | |||
+ | Bit 6, is used to indicate when new values have been latched into the | ||
+ | lightpen registers (16-17). Bit 5, VBlank refers to when the 8563 is in the | ||
+ | period known as " | ||
+ | seldom referred to as updating the 8563 is usally too slow to make use of this | ||
+ | for any special effects. | ||
+ | |||
+ | Bits 0-2 return a version # of which %000 and %001 are the known versions out. | ||
+ | Early 128's will contain the value of $0 while later 128's will contain the | ||
+ | value of $1. Note that there are slight differences between the 8563' | ||
+ | register 25 (horizontal smooth scoll register) requires different settings. | ||
+ | |||
+ | The register at $d601 returns the value of register # that has been written | ||
+ | into $d601 (when bit 7 of $d600 = %1). Note that storing a value here will also | ||
+ | do a write into the register # selected. (Refer to the ML routines for storing | ||
+ | and reading values to/from registers at the end of this article for an example). | ||
+ | |||
+ | |||
+ | ------------------------ | ||
+ | | Register Definitions | | ||
+ | ------------------------ | ||
+ | |||
+ | Reg# | ||
+ | ------- ---- ---- ---- ---- ---- ---- ---- ---- | ||
+ | 0 HzT7 HzT6 HzT5 HzT4 HzT3 HzT2 HzT1 HzT0 | ||
+ | 1 HzD7 HzD6 HzD5 HzD4 HzD3 HzD2 HzD1 HzD0 | ||
+ | 2 HzS7 HzS6 HzS5 HzS4 HzS3 HzS2 HzS2 HzS0 | ||
+ | 3 VSW3 VSW2 VSW1 VSW0 HSW3 HSW2 HSW1 HSW0 | ||
+ | 4 VeT7 VeT6 VeT5 VeT4 VeT3 VeT2 VeT1 VeT0 | ||
+ | 5 .... .... .... VeA4 VeA3 VeA2 VeA1 VeA0 | ||
+ | 6 VeD7 VeD6 VeD5 VeD4 VeD3 VeD2 VeD1 VeD0 | ||
+ | 7 VeS7 VeS6 VeS5 VeS4 VeS3 VeS2 VeS1 VeS0 | ||
+ | 8 .... .... .... .... .... .... Ilc1 Ilc0 | ||
+ | 9 .... .... .... CTV4 CTV3 CTV2 CTV1 CTV0 | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | | Register Usage: | | ||
+ | | ||
+ | |||
+ | ^1 : Register #0: | ||
+ | --- Register #1: | ||
+ | | ||
+ | | ||
+ | These two register function to define the display width of the screen. | ||
+ | Register 0 will contain the number of characters minus 1 between sucessive | ||
+ | horizontal sync pulses, the horizontal border and the interval between | ||
+ | horizontal sync pulses. The normal value for this is usually set to 126. | ||
+ | Register 1 specifies how many of the positions as specified in register 0 can | ||
+ | actually be used to display characters. | ||
+ | The VDC can take values less than 80 and thus, will only display that many | ||
+ | characters. A useful effect can be a sweep from the right by incrementing | ||
+ | the value here from 1 to 80. Register #2 specifies the starting character | ||
+ | position at which the vertical sync pulse begins. Thus, it also determines | ||
+ | where on the active screen characters appear. A default value of 102, | ||
+ | increasing the value moves the screen to the left, decreasing it moves it to | ||
+ | the right. | ||
+ | |||
+ | ^2 : Register #3: | ||
+ | ---- Register #7: | ||
+ | |||
+ | In Register 3, Bits 0-3 of this register specifies the horizontal sync width | ||
+ | and should be equal to 1 + the number of pixels per character. Thus, the value | ||
+ | here is normally 1+8 or 9. Bits 4-7 of register 3 specify the vertical sync | ||
+ | width and normally contains a value of 4. For interlace sync and video mode, | ||
+ | use a value that is twice the number of scan lines desired. Register #7 allows | ||
+ | for adjustment of where the vertical sync will be generated allowing shifting | ||
+ | of the actual display up and down. Normally, a value of 4, decreasing the value | ||
+ | will move the screen down, increasing it will appear to move it upwards. | ||
+ | |||
+ | ^3 : Register #4: | ||
+ | ---- Register #5: | ||
+ | | ||
+ | |||
+ | Register #4 of this register determines the total number of screen rows, | ||
+ | including the rows for the active display, and the top and bottom borders in | ||
+ | addition to that of the vertical sync width. The value held here is normally | ||
+ | a value of 32 for NTSC systems (US Standard) or 39 for PAL(European) systems. | ||
+ | Register #5 holds in bits 0-4 a " | ||
+ | are necessary to make up the display can be specified here. The value here is | ||
+ | normally a 0 in both the NTSC and PAL initializations by the kernal and bits | ||
+ | 5-7 are unused, always returning a binary 1111. Register #6 specifies the total | ||
+ | number of the vertical character positions (as set in Register 4) that can be | ||
+ | used for actual display of characters. Thus, this register usually holds a | ||
+ | value of 25 for a standard 25-row display. | ||
+ | |||
+ | ^4 : Register #8: | ||
+ | ---- | ||
+ | | ||
+ | Register 8 allows control of various display modes the 8563 can generate. | ||
+ | Bits 0 and 1 are the only bits used in this register, the rest always reading | ||
+ | a binary 1. Bits 0 and 1 are configured as follows: | ||
+ | |||
+ | Binary %00, %10 - NonInterlaced Mode | ||
+ | %01 - Interlaced Sync | ||
+ | %11 - Interlaced Sync and Video | ||
+ | |||
+ | Note that the default value is $00 which is standard, non-interlaced. | ||
+ | Interlaced sync draws each horizontal scan line twice but appears to suffer from | ||
+ | an annoying jitter due to how it is drawn. Interlaced Sync and Video draws twice | ||
+ | as many lines, thus doubling the resolution. However, it also suffers from | ||
+ | jitter and that is why most monitors suffer horribly when using programs that | ||
+ | support more than 30 rows. Note that for interlaced sync and video, the | ||
+ | following registers will need to be changed: #'s: 0,4,6,7,8. | ||
+ | |||
+ | ^5 : Register #9: Total Scan Lines Per Character | ||
+ | ---- | ||
+ | | ||
+ | Bits 0-4 of this register are the only relevant ones, the rest returning a | ||
+ | binary 1. Bits 0-4 determine the character height in scan-lines of displayed | ||
+ | characters and allow up to scan-line heights of 32 scan lines. The VDC normally | ||
+ | sets aside 16 bytes for each character (normally, each byte is equivlent to | ||
+ | 1 scan line) so the value here could be increased to 16-1 and a double-height | ||
+ | character set could be loaded in. Note, however that values less than 16 will | ||
+ | tell the VDC to use a 8,192 byte character set (normal) while specifying values | ||
+ | greater than 16 will make it use 32 bytes per character even if some of the | ||
+ | bytes are not used. | ||
+ | |||
+ | ^6 : Register #10: Cursor Mode / Start Scan Line | ||
+ | ---- Register #11: Cursor End Scan Line. | ||
+ | | ||
+ | | ||
+ | These registers allow the user to specify the cursor blink mode, as well as | ||
+ | the starting and ending scan lines for the cursor (allowing a full solid, | ||
+ | an underline, Bits 0-4 of regiseter #10 determines the scan line within each | ||
+ | position for the top of the cursor. Normally, this value holds a value | ||
+ | of 0 for the block cursor, or a value of 7 for the underline cursor. Bits 5-6 of | ||
+ | Register 10 specify the blink rate for the cursor. A value of %00 specifies no | ||
+ | blink, ie: a solid cursor. A value of %01 specifies no cursor, a value of %10 | ||
+ | specifies a flash rate of 1/16 the screen refresh rate, while a value of %11 | ||
+ | specifies a flash rate of 1/32 the screen refresh rate. Note that bit 7 of | ||
+ | Register 10 is unused and normally returns a binary 1. Register 11 specifies | ||
+ | the bottom scan lines in bits 0-4, the other unused bits returning a binary 1. | ||
+ | The value held in these bits usually is 7 for the block and underline cursor | ||
+ | modes in the normal 128 editor. Register #29 is used to indicate where the scan | ||
+ | line is " | ||
+ | this location just indicates the start and end location in pixels, similair to | ||
+ | registers #10 and #11 being the same value. Note that bits 5-7 of this register | ||
+ | is unused and normally return a binary 1. | ||
+ | |||
+ | ^7 : Register #12: Display Start Address (Hi) | ||
+ | ---- Register #13: Display Start Address (Lo) | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | Note first, that all of these registers are grouped in Hi byte, Lo byte order | ||
+ | which is usually different from the 6502 convention of low byte, hi byte (ie: | ||
+ | in normal 6502 ml, $c000 is stored as $00 $c0, however in the 8563 it would be | ||
+ | stored as $c0 $00). Registers 12 and 13 determine, where in VDC memory the | ||
+ | 8563 is the start of the screen. Incrementing this value by 80 (the number of | ||
+ | characters per line) and with a little additional work can provide a very | ||
+ | effecient way of having a screen that " | ||
+ | The cursor position in registers 14 and 15 reflect the actual character in | ||
+ | memory that the cursor currently lies over. If it's not on the display screen, | ||
+ | then it is not displayed. Registers 20 and 21 reflect where in the 8563 memory | ||
+ | attribute memory is held. Attribute memory refers to the character attributes | ||
+ | such as flash, inverse video, color etc that can be set for each character. | ||
+ | |||
+ | ^8 : Register #16: Light Pen Vertical | ||
+ | ---- Register #17: Light Pen Horizontal | ||
+ | |||
+ | These registers return the light pen position and refer to the actual | ||
+ | character positions on screen (ie: values ranging from 1..25 for vertical). | ||
+ | The horizontal reading will not corrospond exactly to character positions, but | ||
+ | will range from values of 27-29 to 120 depending on the edge of the screen. | ||
+ | It's recommended that the horizontal character position is given more tolerance | ||
+ | than the vertical light pen position for this reason. | ||
+ | |||
+ | ^9 : Register #18: Update Address (Hi) | ||
+ | ---- Register #19: Update Address (Lo) | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | These registers allow control and manipulation of the 16k or 64k block within | ||
+ | the 8563 memory. | ||
+ | read or write will take place from. Register 30 specifies the number of bytes | ||
+ | - 1 to copy or fill depending on bit # 7 of register #24. Normally, the 8563 | ||
+ | will automatically perform the designated operation (of what bit 7 of register | ||
+ | #24 says) when register #31 (the data byte) is written to. Registers 18 and 19 | ||
+ | automatically update upon read or write, so that is why register #30 specifies | ||
+ | a value 1 less than what is actually needed. Register #31, as already mentioned | ||
+ | is the byte to write for register #30 times (or copy from Register#32 / #33). | ||
+ | If register #24, bit 7 is specified as a binary 1 then the memory is copied from | ||
+ | the address in VDC memory pointed to by registers #32 and #33. | ||
+ | |||
+ | ^A : Register #22: Character Horizontal Size Control | ||
+ | ---- | ||
+ | |||
+ | Bits 0-3 of this register determines how many horizontal pixels are used | ||
+ | for each displayed character. Values greater than 8 here result in apparent | ||
+ | gaps in the display. Inter-character spacing can be achieved by setting this | ||
+ | value greater than that of bits 4-7. Bits 4-7 determine the width of each | ||
+ | character position in pixels. Thus, while bits 0-3 allocate n-pixels, bits | ||
+ | 4-7 specify how many of those pixels are used for character display. | ||
+ | |||
+ | ^B : Register #24:5 | ||
+ | ---- Register #24:6 Blink Rate for Characters. | ||
+ | |||
+ | Bit #6 specifies for the VDC for all pixels normally unset on the VDC screen | ||
+ | to be set, and all set pixels to be unset. | ||
+ | for all characters with the blink attribute. Setting this to a binary 1 | ||
+ | specifies a blink rate of 1/32 the refresh rate, while a binary 0 is equivlant | ||
+ | to a blink rate 1/16th of the refresh rate. | ||
+ | |||
+ | ^C : Register #24:0-4 Vertical Smooth Scroll | ||
+ | ---- | ||
+ | |||
+ | The 8563 provides for a smooth scroll, allowing bits 0-4 to function as an | ||
+ | indicator of the number of bits to scroll the screen vertically upward. | ||
+ | |||
+ | ^D : Register #25:7 Text or Graphics Mode Indicator Bit | ||
+ | ---- Register #25:6 | ||
+ | | ||
+ | | ||
+ | |||
+ | The 8563 allows the implementation of a graphics mode, in where all of the 16k | ||
+ | of the screen may be bit-mapped sequentially resulting in a resolution of | ||
+ | 640x200 (see Craig Bruce' | ||
+ | more detailed explanation of this feature). Setting this bit to 1 specifies | ||
+ | graphics mode, binary 0 indicates text mode. Bit 6 indicates to the 8563 where | ||
+ | to obtain its color information etc, about the characters. Bit 6 when it is a | ||
+ | binary 0 results in the 8563 taking it's color information from bits 4-7 of | ||
+ | register 26. When this bit is a binary 1, the attribute memory is used to | ||
+ | obtain color, flash, reverse information. Also note than when this bit is a | ||
+ | binary 1 that only the first of the two character sets is available. Bit #5 | ||
+ | indicates a semi-graphics mode that allows the rightmost pixel of any characters | ||
+ | to be repeated through-out the intercharacter spacing gap. Activating it on the | ||
+ | normal display will result in what appears to be a " | ||
+ | 8563 with bit #4 allows a pixel-double feature which results in all displayed | ||
+ | horizontal pixels having twice their usual size. Thus, a 40 column screen is | ||
+ | easily obtainable although the values in registers #00-#02 must be halved. | ||
+ | |||
+ | ^E : Register #25: Horizontal Smooth Control | ||
+ | ---- | ||
+ | |||
+ | This register is analogous to register #24 Vertical Smooth Control and | ||
+ | functions similairly. Increasing this bits moves the screen one pixel to the | ||
+ | right, while decreasing them moves the screen one pixel to the left. | ||
+ | |||
+ | ^F : Register #26: ForeGround / BackGround Color Register | ||
+ | ---- | ||
+ | |||
+ | This register, in bits 0-3 specifies the background color of the display while | ||
+ | bits 4-7 specify the foreground character colors when attributes are disabled | ||
+ | (via bit 6 of register #25). Note, these are not the usual C= colors but are | ||
+ | instead organized as follows: | ||
+ | |||
+ | Bit Value Decimal Value Color | ||
+ | | ||
+ | %0000 0 / $00 | ||
+ | %0001 1 / $01 Dark Gray | ||
+ | %0010 2 / $02 Dark Blue | RGBI Bit 2 = Green | | ||
+ | %0011 3 / $03 Light Blue | Bit 3 = Red | | ||
+ | %0100 4 / $04 Dark Green | | | ||
+ | %0101 5 / $05 Light Green | ||
+ | %0110 6 / $06 Dark Cyan | ||
+ | %0111 7 / $07 Light Cyan | ||
+ | %1000 8 / $08 Dark Red | ||
+ | %1001 9 / $09 Light Red | ||
+ | %1010 10 / $0A Dark Purple | ||
+ | %1011 11 / $0B Light Purple | ||
+ | %1100 12 / $0C Dark Yellow | ||
+ | %1101 13 / $0D Light Yellow | ||
+ | %1110 14 / $0E Light Gray (Dark White) | ||
+ | %1111 15 / $0F White | ||
+ | |||
+ | ^G : Register #27: Row Address Display Increment | ||
+ | ---- | ||
+ | |||
+ | This register specifies the number of bytes to skip, when displaying | ||
+ | characters on the 8563 screen. Normally, this byte holds a value of $00 | ||
+ | indicating no bytes to skip; however typically programs that " | ||
+ | screen do so by setting this to 80 or 160 allowing the program to then alter | ||
+ | the Screen Start (Registers #12 and #13) and appear to " | ||
+ | normal C= 128 Kernal Screen Editor does not support this function. | ||
+ | |||
+ | ^H : Register #28:7-5 Character Set Address | ||
+ | ---- | ||
+ | |||
+ | These bits indicate the address of screen memory * 8k. Thus the values in | ||
+ | these bits may be multiplied by 8192 to obtain the starting character set | ||
+ | position (normall these bits hold a value of $01 indicating the character | ||
+ | set begins at 8192). | ||
+ | copied to 8192 when the computer is first turned on and the 8563 is initialized. | ||
+ | (Examine the INIT80 routine at $CE0C in bank 15). | ||
+ | |||
+ | ^I : Register #28:4 Ram Chip Type | ||
+ | ---- | ||
+ | | ||
+ | This bit specifies whether 16k or 64k of RAM has been installed. Note, however | ||
+ | that this value may not reflect future upgrades from 16k to 64k. It is best, | ||
+ | if a program is dependant on 64k to write to an address > 16k and see if it | ||
+ | is mirrored at any other location in another section of memory. This bit has a | ||
+ | binary value of 0 if 16k or 1 if 64k RAM. | ||
+ | |||
+ | ^J : Register #34: Display Enable Begin | ||
+ | ---- Register #35: Display Enable End | ||
+ | |||
+ | The 8563 can extend it's horizontal blanking interval to blank a portion of | ||
+ | the displayed screen. | ||
+ | blanked column, and register #35 determines the leftmost blanked column. | ||
+ | that a value of 6 usually corresponds to the leftmost column of the screen, | ||
+ | while a value of 85 corresponds to the rightmost column. | ||
+ | for " | ||
+ | text, the text can be cleared, these values reset etc... | ||
+ | |||
+ | ^K : Register #36: Refresh Cycles per Scan Line | ||
+ | ---- | ||
+ | |||
+ | This register in bits 0-3 allows the user (if he had any reason) to specify | ||
+ | the number of refresh cycles for memory for the ram. Setting this value too | ||
+ | low may cause the RAM to not remember all the information. | ||
+ | gives some advantage, in terms of display speed increases but is not advised. | ||
+ | The value normally held here is $05, for five refresh cycles per scan line. | ||
+ | |||
+ | | ||
+ | | 8563 Memory Organization | | ||
+ | | ||
+ | |||
+ | Normally, the extra memory of the C=128' | ||
+ | worth) unless programs like Basic-8 etc, take advantage of it. There are various | ||
+ | mod files describing the upgrade from 16k to 64k and it is _strongly_ advised | ||
+ | (although the author has not yet done so) and be aware that ***OPENING YOUR | ||
+ | COMPUTER JUST TO LOOK, YOU MAY MESS IT UP*** and it is _strongly_ advised that | ||
+ | you contact a person experienced with electronics to perform the upgrade for | ||
+ | you. Note also that some mail order companies are offering an " | ||
+ | which plugs into the 8563 slot and does not involve desoldering the RAM chips. | ||
+ | |||
+ | Now, the 8563 uses the 16k of memory (it ignores the extra 48k of memory when | ||
+ | it's got 64k, thus the following applies also to the 8563's equipped with 64k | ||
+ | of memory) and normally, has the following memory map: | ||
+ | |||
+ | $0000 - $07ff - Screen Memory | ||
+ | $0800 - $0fff - Attribute Memory | ||
+ | $1000 - $1fff - Unused | ||
+ | $2000 - $2fff - UpperCase / Graphic Character Set (Char Set #1) | ||
+ | $3000 - $3fff - LowerCase / UpperCase Character Set (Char Set #2) | ||
+ | | ||
+ | +---------------------------+ | ||
+ | | Writing to 8563 Registers | | ||
+ | +---------------------------+ | ||
+ | |||
+ | Now how do we write to these registers we've learned so much about? There' | ||
+ | several ways depending on how lazy you are. The pure-ml version: | ||
+ | |||
+ | WRITING TO A REGISTER: | ||
+ | |||
+ | | ||
+ | stx $d600 | ||
+ | - ldx $d600 | ||
+ | bpl - | ||
+ | sta $d601 | ||
+ | rts | ||
+ | |||
+ | also, in bank 15 there is a similair routine at $cdcc. Calling it at $cdca | ||
+ | loads .x with a value of 31 indicating the data register which is often useful. | ||
+ | |||
+ | From basic, just use a SYS 52684, value, register# | ||
+ | |||
+ | | ||
+ | |||
+ | | ||
+ | ; Assumes I/O block switched in | ||
+ | stx $d600 | ||
+ | - ldx $d600 | ||
+ | bpl - | ||
+ | lda $d601 | ||
+ | |||
+ | or use the routine in bank 15 at $cdda. | ||
+ | and then a RREG A returns the value in variable A. | ||
+ | |||
+ | +--------------------+ | ||
+ | | Further 8563 Notes | | ||
+ | +--------------------+ | ||
+ | |||
+ | Many C=128 owners are still using their monitors they had when they had their | ||
+ | C=64's and are able to use the 80 column screen through a " | ||
+ | (basically taking pin 7 of the RGBI port and feeding it as raw video). | ||
+ | is also a text file out explaining how to take the R,G,B,I pins on that port | ||
+ | to display shades of gray on a monochrome monitor (basically tying resistors | ||
+ | with diodes across each color pin and then joining them). | ||
+ | |||
+ | The 8563 is a chip full of cabibilities waiting to be found and developed. I'd | ||
+ | be interested in seeing any code / techniques that readers of this net-mag have | ||
+ | found. | ||
+ | better tricks and techniques might be possible in the future. | ||
+ | |||
+ | =========================================================================== | ||
+ | </ | ||
+ | ====== FILE SPLITTER | ||
+ | 9152427d@levels.unisa.edu.au | ||
+ | < | ||
+ | |||
+ | This program stemmed from the inability of XLINK to transfer CS-DOS from my pc | ||
+ | to my 128. XLINK transfers about 43K (I think), whereas CS-DOS was about 48K. | ||
+ | |||
+ | Rather than do the whole thing at once, why not cut the job up into more | ||
+ | sizeable pieces, transfer the program piece by piece, and then reassemble the | ||
+ | pieces at the other end? | ||
+ | |||
+ | And so eventuated the birth of SPLIT :-) | ||
+ | |||
+ | SPLIT, written entirely in Turbo Pascal, allows you to split DOS files into | ||
+ | smaller pieces - you can either tell it a size to split the files into, or | ||
+ | tell it a number of files to create. | ||
+ | for the new files WITH NO EXTENSION - SPLIT will give the new files their own | ||
+ | extensions, and SPLIT will then create these files to your liking. | ||
+ | |||
+ | Just transfer the following program to Turbo, compile it, and away you go!!! | ||
+ | |||
+ | Hopefully, the program is commented enough to give you a fair idea of what's | ||
+ | going on - although it isn't at all complicated to understand. | ||
+ | |||
+ | At some points I have comments that seem the least important - END { CASE } - | ||
+ | they are to help me when I program... | ||
+ | END is for what, stuff up my indentation, | ||
+ | wrong parts, etc, etc. | ||
+ | |||
+ | I found it helped me, so it may help others. | ||
+ | |||
+ | If you need any further explanation, | ||
+ | |||
+ | Another interesting thing I discovered about XLINK. | ||
+ | files to the correct size. I think (haven' | ||
+ | it out yet) it transfers to the nearest 256, 512 or 1024 byte boundary. | ||
+ | your file doesn' | ||
+ | I think. | ||
+ | in places where it shouldn' | ||
+ | |||
+ | So, when SPLITting a file, specify the size to a multiple of one of these | ||
+ | boundaries. | ||
+ | |||
+ | Then, using a m/c monitor, load all the parts in together. | ||
+ | |||
+ | I'll try to set aside a little time in the not too distant future to write a | ||
+ | m/c program to join the parts for you, since it can get confusing reassembling | ||
+ | the parts by hand, and the built in dos copy that commodore so kindly graced us | ||
+ | with is so darned fast < | ||
+ | |||
+ | [Ed. Note: While the dos copy command is slow.... for those of you who are | ||
+ | | ||
+ | sure that there' | ||
+ | | ||
+ | |||
+ | |||
+ | So, good luck and enjoy! | ||
+ | |||
+ | --------------------------------------------------------------------------- | ||
+ | Program Split (input, | ||
+ | |||
+ | Uses Dos; | ||
+ | { uses specific file handling routines } | ||
+ | Var | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | Begin | ||
+ | For count := 1 to 25 do | ||
+ | Writeln; | ||
+ | { Dumb way to clear the screen :-) } | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | For count := 1 to length (InFileName) do | ||
+ | InFileName[count] := UpCase ( InFileName[count] ); | ||
+ | { change filename to all uppercase characters } | ||
+ | |||
+ | | ||
+ | { split filename into it's respective parts: | ||
+ | d - Directory | ||
+ | n - Name | ||
+ | e - Extension } | ||
+ | |||
+ | S := FSearch(InFileName, | ||
+ | { search for file FILENAME in directory D } | ||
+ | |||
+ | if S = '' | ||
+ | writeln (' | ||
+ | { S equals '' | ||
+ | |||
+ | Else | ||
+ | Begin | ||
+ | Assign | ||
+ | Reset | ||
+ | { Open the Input File } | ||
+ | |||
+ | Size := FileSize (InFile); | ||
+ | { Get file size } | ||
+ | |||
+ | Writeln (' | ||
+ | Writeln (' | ||
+ | Writeln; | ||
+ | { Show file info } | ||
+ | |||
+ | Writeln ('In which way would you like the file split?' | ||
+ | Writeln (' | ||
+ | Writeln (' | ||
+ | Repeat | ||
+ | Write (' | ||
+ | | ||
+ | | ||
+ | Until (SplitType >= ' | ||
+ | { let user choose which way to split file } | ||
+ | |||
+ | Writeln; | ||
+ | Case SplitType of | ||
+ | ' | ||
+ | { split by number of bytes } | ||
+ | Write (' | ||
+ | Readln (NewSize); | ||
+ | Writeln; | ||
+ | If (NewSize > Size) then | ||
+ | | ||
+ | Else | ||
+ | begin | ||
+ | | ||
+ | | ||
+ | | ||
+ | Write (' | ||
+ | | ||
+ | | ||
+ | | ||
+ | End; | ||
+ | End; { A } | ||
+ | |||
+ | ' | ||
+ | { Split by file size } | ||
+ | Write (' | ||
+ | Readln(Number); | ||
+ | Writeln; | ||
+ | NewSize := Size div Number + 1; | ||
+ | Last := Size - (Number - 1) * Newsize; | ||
+ | Number | ||
+ | Write (' | ||
+ | Readln (NewFile); | ||
+ | Writeln; | ||
+ | Writeln (' | ||
+ | End; { B } | ||
+ | End; { Case } | ||
+ | |||
+ | | ||
+ | |||
+ | For Count := 1 to Number do | ||
+ | { NUMBER new files will be created } | ||
+ | |||
+ | Begin | ||
+ | If Count = Number then | ||
+ | | ||
+ | { More often than not, the files won't divide evenly from the | ||
+ | original. | ||
+ | Because of this, I previously calculated the size of the final | ||
+ | file, and here check if we're up to the last part yet - and if | ||
+ | we are, I set the size of the last file accordingly } | ||
+ | |||
+ | Str(Count, | ||
+ | { Make EXTENSION a string representation of COUNT, to be added to | ||
+ | the OutFileName to make things a tad easier } | ||
+ | |||
+ | OutFileName := Concat(NewFile,' | ||
+ | { Create filename based on which part we're up to } | ||
+ | |||
+ | Assign | ||
+ | Rewrite (Outfile); | ||
+ | { Open each Output File } | ||
+ | |||
+ | Write | ||
+ | |||
+ | For Counter := 1 to NewSize do | ||
+ | { Write to each Output File } | ||
+ | |||
+ | Begin | ||
+ | | ||
+ | Write (OutFile, | ||
+ | { Transfer data from input file to output file } | ||
+ | End; | ||
+ | |||
+ | Close | ||
+ | { Close each Output File } | ||
+ | Writeln (' | ||
+ | |||
+ | End; | ||
+ | |||
+ | Writeln; | ||
+ | Writeln ('Job Completed :-)'); | ||
+ | |||
+ | end; | ||
+ | |||
+ | For Counter := 1 to 3 do | ||
+ | Writeln; | ||
+ | { Make a bit of space when finished :-) } | ||
+ | end. | ||
+ | |||
+ | ================================================================================ | ||
+ | </ | ||
+ | ====== BANKING ON GEOS ====== | ||
+ | < | ||
+ | by Robert A. Knop Jr. | ||
+ | |||
+ | I. Introduction | ||
+ | |||
+ | GEOS was originally written for the Commodore 64. When Berkeley Softworks | ||
+ | came out with GEOS128 (and, for a time, it wasn't clear that they would; then, | ||
+ | it looked like they would release a GEOS128 that wouldn' | ||
+ | column screen; finally, the release of GEOS128 did turn out to be a full 128 | ||
+ | program), it was largely compatible with GEOS64. | ||
+ | documents (a geoPaint file is a geoPaint file), and even many GEOS64 | ||
+ | appliations run on the 128 in 40 columns. | ||
+ | the GEOS programmer. | ||
+ | |||
+ | As we all know, the C-128 has two 64K RAM banks; the C-64 only has one 64K RAM | ||
+ | " | ||
+ | This includes the Kernal as well as the space available to applications. | ||
+ | the Kernal, graphics screens, and so forth, have claimed their RAM, the GEOS | ||
+ | programmer is left with 23.75K of memory from $0400 to $5fff. | ||
+ | |||
+ | To a cursory " | ||
+ | like the GEOS64 programming enviroment. | ||
+ | available for applications; | ||
+ | place; the Kernal jump table is the same (with some 128 specific additions). | ||
+ | What happened to the other 64K that the 128 has available? | ||
+ | |||
+ | As it turns out, the core of GEOS128- including the application program space, | ||
+ | the 40 column foreground and background screen, and the Kernal jump table- are | ||
+ | all in the 128's RAM block 1, what GEOS calls FrontRAM. | ||
+ | used to RAM block 0 being the " | ||
+ | it actually makes sense. | ||
+ | and of itself, and applications almost never need to call the C128's Kernal | ||
+ | routines, the application no longer needs access to Bank 15. Second, it | ||
+ | allows GEOS128 to keep much of its memory map the same as GEOS64; it can use | ||
+ | the memory range from $200-$3ff in RAM 1 without worrying about disturbing key | ||
+ | system routins like STAFAR which are in the same memory range in RAM 0. | ||
+ | |||
+ | |||
+ | II. Yeah, Yeah, But What Happened to RAM 0 Anyway? | ||
+ | |||
+ | It's still there. | ||
+ | performance and to take advantage of the 128's unique features. | ||
+ | instance, the code for the " | ||
+ | screen is found beneath $2000 in RAM 0.) Fortunately, | ||
+ | available for an application to use. In RAM 0, the 32K memory space between | ||
+ | $2000 and $9fff is not normally used by GEOS, and is ALMOST available for | ||
+ | application use [1]. | ||
+ | |||
+ | Why do I say " | ||
+ | desk accessory (DA), it must load it into the same application space as the | ||
+ | application loading the DA. The memory that the DA will used is first saved | ||
+ | to disk in a swap file. Under GEOS128, the routine LdDeskAcc, instead of | ||
+ | saving a swap file to disk, copies the memory to be overwritten by the DA to | ||
+ | RAM0 between $2000 and $9fff. | ||
+ | highly recommended that major applications support DA's), you have to be | ||
+ | careful using the space between $2000 and $9fff. | ||
+ | swap space within routines- but you cannot assume that it will remain intact | ||
+ | whenever your routine returns to the GEOS MainLoop with your application in a | ||
+ | state that will allow the loading of DA's. | ||
+ | |||
+ | Nowadays, RAM 0 is not the be-all and end-all. | ||
+ | C=128, not the C=256. | ||
+ | 512K as described in the articles by Richard Curcio in Twin Cities 128 Issues | ||
+ | #30 and #31 [2,3], you have free use of RAM 2-3 (256K) or RAM 2-7 (512K). | ||
+ | (Note that you should not touch RAM 4-7 on a 512K 128 if you want to be | ||
+ | compatible with task switching as described in TC128 #31. Also, although GEOS | ||
+ | right now does not run in the 2nd 256K, applications should not assume they | ||
+ | are in the 1st 256K, and thus should be careful with the 512K mode bits (4-5) | ||
+ | in the MMU Ram Configuration Register (RCR), $d506.) | ||
+ | people with 256K and 512K 128's is now small, you can be sure that it will | ||
+ | increase when the promised ZIP accelerator board for the 128 comes out; the | ||
+ | current specs for the ZIP board include provisions for memory expansion on the | ||
+ | board. | ||
+ | |||
+ | RAM 2-3 provide almost another complete 128K available for your application to | ||
+ | use. So how do you go about accessing this? | ||
+ | |||
+ | |||
+ | III. Storing Data In Other RAM Blocks | ||
+ | |||
+ | The most obvious use for RAM blocks other than FrontRAM (which is the only | ||
+ | block where GEOS Kernal routines are available) is as data storage. | ||
+ | instance, one could visualize a geoPaint previewing utility which loads and | ||
+ | decompacts an entire geoPaint document at once to RAM 2. (The full | ||
+ | decompacted geoPaint document would reqire 56.25K.) | ||
+ | scroll through the document by just copying the relevant portions of the | ||
+ | bitmap from RAM 2 to the foreground screen. | ||
+ | could just redirect the VIC screen memory to the relevant range in RAM2 using | ||
+ | the proper MMU and VIC registers. | ||
+ | 2 and 3, since VIC screen locations are quantized to 8K; you lose the use of | ||
+ | the highest 8K, since you don't want to overwrite the MMU registers at | ||
+ | $ff00-$ff05; | ||
+ | difficult.) | ||
+ | |||
+ | GEOS128 provides a few routines for easily moving data between FrontRAM and | ||
+ | what it calls BackRAM (but we know it just means RAM 0). Happily, these | ||
+ | routines work quite admirably with RAM 2 and 3. (To access RAM 4-7, fiddle | ||
+ | bits 4 and 5 of the MMU RCR to make the desired RAM blocks appear to the | ||
+ | system as virtual RAM 2 and RAM 3, then call these routines.) | ||
+ | routine is DoBOp, which is summarized below [4]: | ||
+ | |||
+ | *********************************************************************** | ||
+ | DoBOp=$c2ec: | ||
+ | |||
+ | Pass: | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | Returns: r0-r3 unchanged | ||
+ | |||
+ | when verifying: x=$00 if two ranges match, x=$ff if they don't match | ||
+ | |||
+ | |||
+ | Destroys: a,x,y | ||
+ | |||
+ | |||
+ | The operation mode is passed in y as follows: | ||
+ | |||
+ | bit0 bit1 | ||
+ | ---- ---- | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | *********************************************************************** | ||
+ | |||
+ | (r0, r1, etc. are all the standard BSW symbols defined in the Official GEOS | ||
+ | Programmer' | ||
+ | geoProgrammer.) | ||
+ | |||
+ | There are a number of additional routines which are also provided for | ||
+ | programmer convenience which automatically set the MODE in the y register for | ||
+ | you. In all of these routines, r0-r3 have the same meaning as they do in | ||
+ | DoBOp. | ||
+ | |||
+ | Routine | ||
+ | ------- | ||
+ | MoveBData | ||
+ | SwapBData | ||
+ | VerifyBData | ||
+ | |||
+ | I have written a short demonstration program which shows the use of MoveBData | ||
+ | and VerifyBData. | ||
+ | through anonymous ftp at tybalt.caltech.edu (in the / | ||
+ | directory) as well as elsewhere. | ||
+ | are below). | ||
+ | the files you need (except geosSym and geosMac, which come with geoProgrammer) | ||
+ | are in the bmover.sfx archive. | ||
+ | |||
+ | The first function of BMover repeatedly copies a single block on RAM 1 to | ||
+ | successive parts in memory in any other specified bank. The destination bank, | ||
+ | destination addresses, size of the block to move, and number of times to copy | ||
+ | it are all set in constants found at the beginning of the source file BMovAsm. | ||
+ | Once the moves (which use MoveBData) have all been performed, BMover uses | ||
+ | VerifyBData to make sure that all of the blocks were copied succesfully. | ||
+ | |||
+ | For informational purposes, BMover reports the amount of time (in tenths of | ||
+ | seconds) it took to perform all of the moves. | ||
+ | clock, saving its value at the beginning and end of the move, and subtracting | ||
+ | to get the difference.) | ||
+ | RAM 2 7 times (thus filling 56K of RAM 2). These moves together took 1 second | ||
+ | at 2 MHz, and 2.2 seconds at 1 Mhz. 56K/second may be no DMA, but it's faster | ||
+ | than a burst load! | ||
+ | |||
+ | |||
+ | IV. Executing Routines In Other Banks | ||
+ | |||
+ | So, you've written an object oriented drawing program that stores its list of | ||
+ | objects (32 byte records) in RAM 2. Or, you have a database that has records | ||
+ | in RAM 0. You want to delete one record at the beginning of the list, which | ||
+ | means moving all of the subsequent records down over the memory freed up by | ||
+ | the deletion. | ||
+ | Bruce' | ||
+ | repeated do MoveBData to move memory from RAM 2 (or 0) to a buffer in FrontRAM | ||
+ | and back. Or, you can write a short mover routine in the RAM bank where all | ||
+ | the moving is going to happen. | ||
+ | |||
+ | This is just an example. | ||
+ | in other RAM banks (what I call " | ||
+ | Kernal routines for calling extrabankal routines. | ||
+ | main application memory is in RAM 1, you are inable to use the 128 Kernal' | ||
+ | JSRFAR (which returns you to Bank 15). So, we are left with implementing our | ||
+ | own JSRFAR. | ||
+ | |||
+ | GEOS128 normally operates with NO common memory enabled. | ||
+ | less well-known features of the MMU, there is no need to enable common memory. | ||
+ | The MMU zero page registers ($d507 and $d508) allow you to locate the zero | ||
+ | page that the processor sees anywhere in RAM 0 or RAM 1. What this means is, | ||
+ | no matter what your memory configuration is, the processor sees zero page in | ||
+ | the RAM block specified in $d508. | ||
+ | which case it is not a good idea to put ZP in RAM blocks other than RAM 0 | ||
+ | [6, | ||
+ | |||
+ | This provides for the possiblity of copying to zero page a short " | ||
+ | routine, basically a reimplementation of JSRFAR, which configures the system | ||
+ | for the destination bank, jsr's to a routine, reconfigures the system for the | ||
+ | calling bank, and rts's. | ||
+ | |||
+ | I also demonstrate this technique in BMover. | ||
+ | first uses MoveBData to copy a routine to $2000 in DESTBANK (which is set | ||
+ | right now in the source code to RAM 0). It then copies the routine ZPJSR to | ||
+ | $02, which stores DESTCFG in $ff00 and jsr's to $2000. | ||
+ | moves some data around in DESTBANK. | ||
+ | to FrontRAM, BMover calls VerifyBData to make sure everything worked. | ||
+ | |||
+ | While messing around in different banks, to be safe I dissable IRQ interrupts. | ||
+ | On a related note, geoDebugger 2.0 seems to have problems with programs | ||
+ | messing around with different banks. | ||
+ | debugger (which locates itself in RAM 0) would have trouble with programs that | ||
+ | tried to use RAM 0, but it also has trouble with programs that try to use RAM | ||
+ | 2 and 3. This is true even when one uses the system routine MoveBData. | ||
+ | found that I was sometimes able to make it past a call to MoveBData while in | ||
+ | the debugger, but that more often the system would hang. This is all probably | ||
+ | an interrupt-related issue.) | ||
+ | |||
+ | If one is to be really classy, one doesn' | ||
+ | routine to zero page. One could assemble the application such that ZPJSR fell | ||
+ | to a known offset from a page boundry; then, use the MMU to point zero page to | ||
+ | the page containg ZPJSR. | ||
+ | 512K expanded 128. The one incompatility I have found is that with the 512K | ||
+ | modification enabled (I do have a switch to disable it, don't worry), the MMU | ||
+ | fails to correctly see zero page in RAM 1 when requested to. Richard Curcio | ||
+ | experimented with it, and it seems that when you try to relocate zero page to | ||
+ | a page in RAM 1, it is actually seen in RAM 3. It is not yet clear whether | ||
+ | this is a problem with the 256K/512K modification, | ||
+ | 128 just relocates ZP to RAM 3 figuring that RAM 3 = RAM 1 (which is true on a | ||
+ | stock 128, but not on a 256K expanded 128!) | ||
+ | |||
+ | |||
+ | |||
+ | Anyone who wants to get ahold of the BMover source, or who has other | ||
+ | questions/ | ||
+ | addresses: | ||
+ | |||
+ | InterNet: rknop@tybalt.caltech.edu | ||
+ | GEnie: | ||
+ | U.S. Mail: Robert Knop | ||
+ | 123 S. Chester #3 | ||
+ | Pasadena, CA 91106 | ||
+ | |||
+ | |||
+ | V. References | ||
+ | |||
+ | [1] William Coleman, 1989: " | ||
+ | |||
+ | [2] Richard Curcio, 1991: " | ||
+ | #30, p. 7. | ||
+ | |||
+ | [3] Richard Curcio, 1992: " | ||
+ | _Twin_Cities_128_ #31, p. 5. | ||
+ | |||
+ | [4] Berkeley Softworks, 1988: _The_Hitchhiker' | ||
+ | |||
+ | [5] Michael Farr, 1987: _The_Offical_GEOS_Programmer' | ||
+ | Bantam Books, New York/ | ||
+ | |||
+ | [6] Larry Greenly et. al, 1986: _Commodore_128_Programmer' | ||
+ | Bantam Books, New York/ | ||
+ | |||
+ | [7] Ottis R. Cowper, 1986: _Mapping_the_Commodore_128_. | ||
+ | Greensboro, NC. | ||
+ | |||
+ | ============================================================================== | ||
+ | </ | ||
+ | ====== DYNAMIC MEMORY ALLOCATION FOR THE 128: Breaking the 64K Barrier ====== | ||
+ | < | ||
+ | by Craig Bruce (csbruce@ccnga.uwaterloo.ca) | ||
+ | |||
+ | Although this article would be best described as extremely technical, I think | ||
+ | that it has something for everyone. | ||
+ | extremely long. | ||
+ | |||
+ | Below I have written a program that will read in the lines of a file, sort | ||
+ | them, and write then back out to another file. Because of the nature of the | ||
+ | problem, the each line of the entire file must reside in the memory of the | ||
+ | computer. | ||
+ | be sorted can be larger than 64K, and I use a dynamic data structure such that | ||
+ | the memory is used very efficiently. | ||
+ | a text editor called " | ||
+ | some humongous files (and very efficiently too). Although implemented for the | ||
+ | C-128, the dynamic memory scheme could also be fairly easily (ie. in a single | ||
+ | lifetime) ported to the C-64. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 1. INTRODUCTION | ||
+ | |||
+ | How many of us are sick and tired of the "64K limit" that a lot of programs for | ||
+ | the 128 and 64 seem to have? Many terminal programs, text editors, and even | ||
+ | file copiers seem to be afflicted with this problem. | ||
+ | programs often reserve large sections of memory for specific purposes (such as | ||
+ | the kill buffer of a text editor) and cannot reconfigure themselves (very | ||
+ | easily) for different demands. | ||
+ | not make use of a Ram Expansion Unit (if you are fortunate enough to have one) | ||
+ | to store your volumnous user data. | ||
+ | |||
+ | The way to overcome the limitations of the 64K architecture of the C128 and | ||
+ | C64 is to use dynamically allocated memory. | ||
+ | initially, all of the memory of the computer is free and when a user program | ||
+ | requires some memory to store user data, it calls a special subroutine that | ||
+ | allocates a given number of bytes of memory to the program to store the user | ||
+ | data. And when the program is finished using that chunk of memory, it calls a | ||
+ | special subroutine to free the memory chunk and make it available for future | ||
+ | allocation requests. | ||
+ | |||
+ | One complication of this memory usage scheme is that a program has to keep | ||
+ | track of which chunks of memory it uses for what. This is where dynamic data | ||
+ | structures come in. The most important concept here is a pointer. | ||
+ | is simply a variable that stores the address of some data structure (ie. some | ||
+ | chunk of memory). | ||
+ | read it and modify it. | ||
+ | |||
+ | To overcome the problem of not knowing how many records will need to be | ||
+ | stored, records are often stored in lists, where every record contains a | ||
+ | pointer to the next record in the list, except for the last one, which | ||
+ | contains a special value that could not be mistaken for an ordinary pointer | ||
+ | (it is called the Null (or Nil for you Pascalers) pointer). | ||
+ | the address of the first record (by using a "head pointer" | ||
+ | sequential access to all of the records in the list. If we want to add or | ||
+ | delete records from the list, then we must modify the other pointers such that | ||
+ | the consistency of the list is maintained. | ||
+ | lists are also possible. | ||
+ | |||
+ | The implementation here is able to allocate RAM0 memory for storing user data | ||
+ | records, as well as RAM1 memory and even REU memory. | ||
+ | application program keeps track of the pointers to its records, large volumes | ||
+ | of user data can be stored since it will be distributed among all of the | ||
+ | memory that is available from both the internal memory banks and the external | ||
+ | memory banks, thus breaking the 64K barrier. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 2. FOR THE NOVICE HACKER | ||
+ | |||
+ | You get a sorting utility program. | ||
+ | algorithm, so don't expect to break any speed records. | ||
+ | dynamic memory is implemented here is more suited for large data structures | ||
+ | that will only be accessed slowly and infrequently (such as the current | ||
+ | document in a text editor); however, I wanted to come up with a useful utility | ||
+ | and I have never heard of a general file sorter for the 128 or 64. The | ||
+ | insertion sort does, however, lend itself well to being used with dynamic data | ||
+ | structures in general, since you don't actually have to move anything; you | ||
+ | just change a couple of pointers in order to insert a line (record) between | ||
+ | two other lines. | ||
+ | if your input file is already mostly or partially sorted. | ||
+ | |||
+ | The sort utility itself is completely machine language but assumes that the | ||
+ | input and output files are already opened, so a BASIC driver program is | ||
+ | required to set things up to and allow the user to easily change the sorting | ||
+ | parameters. | ||
+ | |||
+ | 1 i$=" | ||
+ | 2 o$=" | ||
+ | 3 : | ||
+ | 100 print" | ||
+ | 110 bank 15 | ||
+ | 120 bload" | ||
+ | 130 print" | ||
+ | 140 scratch(o$), | ||
+ | 150 print" | ||
+ | 160 open1, | ||
+ | 170 open2, | ||
+ | 180 sys dec(" | ||
+ | 190 close2 | ||
+ | 200 close1 | ||
+ | 210 print" | ||
+ | |||
+ | Lines 1 and 2 set up the sorting parameters: the input and output filenames, | ||
+ | the input and output file device numbers, and the sorting field position. | ||
+ | Change the " | ||
+ | field. | ||
+ | what Zed uses for columns). | ||
+ | is used for the comparison that determines the order of the lines. | ||
+ | is encountered that is shorter than the position of the sorting field, the key | ||
+ | value is taken to be the Null String (which comes before any other string). | ||
+ | |||
+ | The program continues to load in the machine language (which fits into the | ||
+ | $1300 slot) and scratch the output file if it already exists. | ||
+ | are opened, machine language is called, and the files are closed and the | ||
+ | program exits. | ||
+ | are longer than 242 characters and treat them as multiple lines. | ||
+ | |||
+ | For testing the sort utility, I used a file that contains 1058 lines of the | ||
+ | following form: | ||
+ | |||
+ | ROXETTE | ||
+ | ADAMS, BRYAN SUMMER OF ' | ||
+ | JOEL, BILLY | ||
+ | EAGLES | ||
+ | ELECTRIC LIGHT ORCHESTRA | ||
+ | COCKBURN, BRUCE | ||
+ | |||
+ | As you may guess, it is a tape library. | ||
+ | it on both my 1581 (with JiffyDOS) and my RamLink and then I sorted again the | ||
+ | file that I sorted in the first place. | ||
+ | follows: | ||
+ | |||
+ | WITH EXPANSION MEMORY | ||
+ | |||
+ | | ||
+ | | ||
+ | 1581 regular | ||
+ | 1581 sorted | ||
+ | |||
+ | You'll note that having expansion memory makes sort operate faster. | ||
+ | because the REU Controller can transfer data around faster than the CPU can. | ||
+ | The effect is even more pronounced when using records longer than 78-character | ||
+ | lines of text. This is why it is sensible to use expansion memory for general | ||
+ | data storage and accessing. | ||
+ | that approximately 1058*529/2 = 280,000 "far memory" | ||
+ | to take place, along with that number of " | ||
+ | 1058 " | ||
+ | and writing mechanisms are not severely swift. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 3. FOR THE INTERMEDIATE HACKER | ||
+ | |||
+ | You get a dynamic memory allocation and usage package that can be incorporated | ||
+ | into your own programs. | ||
+ | |||
+ | 3.1. MEMORY PACKAGE CALLS | ||
+ | |||
+ | The package includes eight system calls: | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | The " | ||
+ | and " | ||
+ | with the .A register holding the low byte and the .Y register holding the high | ||
+ | byte. With " | ||
+ | locations " | ||
+ | and it is assigned to locations $FE to $FF) and with " | ||
+ | the three byte pointer value in locations " | ||
+ | (address high byte), and " | ||
+ | pointer and it is assigned to addresses $FA to $FC). This three-byte pointer | ||
+ | is refered to as a "Far Pointer" | ||
+ | returns with the carry flag set, an error has occured. | ||
+ | error return in this package is if malloc cannot find enough contiguous free | ||
+ | memory to satisfy your request. | ||
+ | |||
+ | You do not actually have to know what the bank numbers mean since they are | ||
+ | generated and used by the package as an opaque data type (parlez-vous | ||
+ | Modula-2?), but here is what they mean anyway. | ||
+ | bank RAM0 and a value of $7F means internal bank RAM1. This works out | ||
+ | conveniently in the implementation, | ||
+ | register values for those two banks. | ||
+ | expansion (REU) memory bank. $80 means expansion bank0, $81 bank1, etc. This | ||
+ | means that the package can support up to 8 Megs (minus 64K) of expansion | ||
+ | memory (and it does). | ||
+ | the bank number into a register, the Negative flag of the processor will be | ||
+ | set (useful if handling expansion memory is a special case), and this value | ||
+ | can be put directly into the REU Controller' | ||
+ | you have to worry about having the high bit be a " | ||
+ | consistently and I have never heard of an REU larger than 2 Megs. A bank | ||
+ | value of $FF is used to represent the Null pointer. | ||
+ | |||
+ | The " | ||
+ | REU, and initializes the dynamic memory allocation mechanism. | ||
+ | the package to access internal memory bank RAM1, it has to call a routine that | ||
+ | is in memory below address $0400. | ||
+ | to copy a few " | ||
+ | them later. | ||
+ | buffer. | ||
+ | routine is called to determine the number of banks that your REU has. Zero | ||
+ | banks means that you have no REU. While sniffing, the package overwrites the | ||
+ | first four bytes of every existing expansion bank (unless you limit the number | ||
+ | of expansion banks that the package is allowed to use). To initialize the | ||
+ | dynamic memory allocation, the " | ||
+ | each expansion bank. RAM0 from $4000 to the top of BASIC memory ($FEFF) is | ||
+ | freed, RAM1 from $0400 to $FEFF is freed, and all expansion banks are freed | ||
+ | between addresses $0000 to $FFF7. | ||
+ | get about 110K free and if you have a 512K expander, you get about 620K free. | ||
+ | |||
+ | The " | ||
+ | just zeros out the common code. I did this so if you called the sort routine | ||
+ | from BASIC direct input mode, you would not get a " | ||
+ | trying to interpret the garbage left behind. | ||
+ | zero, it stops interpreting. | ||
+ | |||
+ | The " | ||
+ | starting at the given zero page address, from any far pointer address. | ||
+ | doesn' | ||
+ | operation is the same. This is the level of software that makes accessing the | ||
+ | different types of memory transparent to the user. To load from RAM0, true | ||
+ | RAM0 is switched into context (did I mention that the package is meant to | ||
+ | execute with MMU configuration $0E in context - this configuration gives RAM0 | ||
+ | from $0000 to $BFFF, the kernel ROM from $C000 to $FFFF and the I/O space on | ||
+ | top of the kernel ROM from $D000 to $DFFF - I call this the SYS or SYS0 bank) | ||
+ | and the transfer is done with a loop. For a zpload from RAM1, a common code | ||
+ | routine is called that switches RAM1 into context, copies in a loop, and then | ||
+ | switches back to SYS0. For an expansion memory pointer, the REU Controller | ||
+ | registers are set up and the transfer is performed. | ||
+ | with whatever Zero Page is in context (with MMU register $D507), since it is | ||
+ | convenient to use your own zero page in your programs. | ||
+ | than about 16 bytes, internal memory is faster, and for longer transfers, | ||
+ | expansion memory turns out to be faster. | ||
+ | bytes), using the expansion memory is MUCH faster (a marginal cost of one | ||
+ | microsecond per byte as opposed to nine). | ||
+ | by this call, but the register values are quite changed. | ||
+ | parameter area is also left untouched. | ||
+ | |||
+ | The " | ||
+ | memory from zero page. | ||
+ | |||
+ | The " | ||
+ | the RAM0 bank (not SYS0) at the given address. | ||
+ | routine, you can transfer up to 64K of memory with this routine. | ||
+ | type of memory to be fetched is transparent to the user. For an internal | ||
+ | memory fetch, the transfers are performed in 256 byte chunks. | ||
+ | implementation easier. | ||
+ | in and then RAM0 is switched in, so the transfer is not extremely efficient. | ||
+ | For the expansion memory, the REU Controller is set up and then the entire | ||
+ | transfer (up to 64K) is performed at a rate of 1 Meg/ | ||
+ | considerably faster than internal memory fetching. | ||
+ | transfer length of 0 bytes properly. | ||
+ | returned unaltered, but again, the registers are smashed. | ||
+ | |||
+ | The " | ||
+ | transferred from the near (" | ||
+ | |||
+ | The " | ||
+ | given length to allocate to you. If it can find one, it returns the far | ||
+ | pointer to it in the " | ||
+ | with the carry flag set. This routine clobbers the registers. | ||
+ | |||
+ | The " | ||
+ | specified by the far pointer and length parameters. | ||
+ | " | ||
+ | return, since the routine does not (currently) check for any errors. | ||
+ | |||
+ | 3.2. MEMORY ALLOCATE AND FREE | ||
+ | |||
+ | The malloc and free routines maintain a linked list of free memory chunks. | ||
+ | free memory chunk is described by a five byte structure that is at the | ||
+ | beginning of the chunk. | ||
+ | free memory chunk and the following two bytes give the total length of the | ||
+ | chunk. | ||
+ | |||
+ | +----------+----------+----------+----------+----------+---... | ||
+ | | Next | Next | Next | Chunk | Chunk | | ||
+ | | chunk | chunk | chunk | length | ||
+ | | low addr | high addr| bank num | low | high | | ||
+ | +----------+----------+----------+----------+----------+---... | ||
+ | chunk+0 | ||
+ | |||
+ | All of the free (and allocated) memory chunks are always aligned on an eight | ||
+ | byte boundary. | ||
+ | be at least eight bytes available in each free memory chunk to hold the free | ||
+ | chunk descriptor information. | ||
+ | bytes, the system would give you eight, and when you request to free those | ||
+ | three bytes, the system would automatically free eight. | ||
+ | some wasted space when using small structures. | ||
+ | |||
+ | The memory chunks are kept in order of " | ||
+ | " | ||
+ | order, the system considers bank number $87 (expansion bank 7) to be lower | ||
+ | than bank number $3F (RAM0). | ||
+ | external memory before allocating internal memory. | ||
+ | external memory generally works faster than internal memory. | ||
+ | |||
+ | This memory is allocated first since the malloc routine uses a first-find | ||
+ | algorithm for searching for a sufficient free memory chunk. | ||
+ | searching when it finds a free memory chunk large enough to satisfy the user's | ||
+ | request. | ||
+ | chunk is unlinked from the free chunk list and the pointer is returned. | ||
+ | the free chunk is larger than the requested size, it is split up. A pointer | ||
+ | to the top N bytes of the chunk is retured to the user and the size of the | ||
+ | free chunk is reduced by N. The memory is allocated from the top of the chunk | ||
+ | to make it so no linking and unlinking has to take place in this case. | ||
+ | |||
+ | The free routine is more complicated than the allocate routine since free has | ||
+ | to deal with more cases. | ||
+ | memory chunks to find the two chunks that straddle the chunk to be freed. | ||
+ | Free attempts to coalesce (merge) the new chunk with the previous chunk and | ||
+ | with the next chunk in order to end up with the largest free chunk that it | ||
+ | can under the circumstances. | ||
+ | used for larger requests. | ||
+ | side-by-side in memory (zero bytes apart) and on the same bank. Note that | ||
+ | chunks on different banks cannot be coalesced together, so the largest | ||
+ | possible free chunk is 64K in length. | ||
+ | one is increased by the size of the second one and the pointer to the second | ||
+ | one is forgotten. | ||
+ | |||
+ | Note that this scheme works differently from the dynamic allocation scheme | ||
+ | that BASIC uses for its strings. | ||
+ | (or even re-use) freed chunks; it relies upon garbage collecting to get rid of | ||
+ | the free chunks. | ||
+ | to choose) in that once you are allocated a chunk, that chunk is pinned to | ||
+ | that address and will never move. This static organization can lead to the | ||
+ | problem of memory fragmentation, | ||
+ | un-coalescable chunks that are too small to be useful. | ||
+ | think that it is really a problem for storing lines of text as individual | ||
+ | records, and it is no problem at all for a program that always uses fixed size | ||
+ | records. | ||
+ | |||
+ | 3.3. THE SORT UTILITY | ||
+ | |||
+ | The sort utility makes full use of the capabilites of the package. | ||
+ | reads in the input file one line at a time and stores the lines in a linked | ||
+ | list as individual records of the form: | ||
+ | |||
+ | +--------+--------+--------+--------+-------...-----+--------+ | ||
+ | | Next | Next | Next | Total | | ||
+ | | line | line | line | record | characters | ||
+ | | ptr | ptr | ptr | length | of the line | $00 | | ||
+ | | low | high | bank | ||
+ | +--------+--------+--------+--------+-------...-----+--------+ | ||
+ | line+0 | ||
+ | |||
+ | Note that these are variable length records; each record is only as long as it | ||
+ | has to be. The total record length is stored at the front of the record. | ||
+ | order to read a line into a processing buffer, a " | ||
+ | the first four bytes of the record in order to get the length of the record. | ||
+ | Then the entire record can be fetched since its length is known at that time. | ||
+ | Each record ends with a $00 byte to simplify the string comparison | ||
+ | subroutine. | ||
+ | |||
+ | The line list is maintained in alphabetical order (actually, reverse | ||
+ | alphabetical order; below). | ||
+ | the line list is searched for the two other lines whose values straddle the | ||
+ | value of the new line. The line is then linked in at that position in the | ||
+ | list. No other lines have to be moved around since pointers are used to | ||
+ | maintain the order of the list. In order for a line already in the list to be | ||
+ | compared with the new line, the old line has to be fetched from far memory | ||
+ | (using the zpload + fetch scheme above) into a work buffer in the SYS0 bank. | ||
+ | On average, half of the existing list will have to be searched in this way in | ||
+ | order to find the correct spot to insert the new line. | ||
+ | |||
+ | After the position for the new line is found, space for the line is allocated | ||
+ | by calling " | ||
+ | read into to far memory. | ||
+ | the pointers to link in the new line. A number of pointer manipulations are | ||
+ | also required on the zero page varialbles. | ||
+ | |||
+ | If the line list was generated in forward alphabetic order, then the utility | ||
+ | would achieve its WORST performance when the input file was already mostly or | ||
+ | partially sorted. | ||
+ | most or all of the other lines, the most or all of the line list would have to | ||
+ | be searched to find the final resting position for the new line. This would | ||
+ | be unacceptable and extremely wasteful. | ||
+ | line list in reverse alphabetic order. | ||
+ | read in, its correct position would be at or near the top of the list, so | ||
+ | it would only have to be compared against a few of the lines already on the | ||
+ | list. In the case of an input file that is already in pretty much random | ||
+ | order, it makes no difference whether the list is in forward or reverse | ||
+ | order. | ||
+ | |||
+ | Since the list is generated in reverse order, it must be reveresed again | ||
+ | before writing it to the output file, since the user would want it to be in | ||
+ | forward order (and since this is the order that can be most easily sorted | ||
+ | again later). | ||
+ | the list. It only has to make use of zpload and zpstore to read/change the | ||
+ | first few bytes of each record, since it is not concerned with the data | ||
+ | contents of each record. | ||
+ | |||
+ | Although this is not strictly necessary, all of the records in the line list | ||
+ | are freed before the sort utilitiy exits. | ||
+ | be necessary if the program were to continue to do useful work after writing | ||
+ | the sorted file to output. | ||
+ | from the head pointer) and the space for each line is deallocated by calling | ||
+ | free, after determining the size of the record by reading the first few bytes | ||
+ | of it. Since the list will be in (pretty much) random order (of addresses), | ||
+ | the deallocation mechanism does not achieve its best performance. | ||
+ | |||
+ | A convenient jump table is set up at the start of the code to make it easier | ||
+ | for you to link your own programs to the package. | ||
+ | configuration value $0E is in effect before calling any of the routines. | ||
+ | may have to muck with the code a little bit to get it to work for you. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 4. FOR THE EXPERT HACKER | ||
+ | |||
+ | You get to see the code that actually implements the memory package and the | ||
+ | sort utility. | ||
+ | by a few special characters and the line number. | ||
+ | allow me to refer to specific lines, and the special characters are there to | ||
+ | allow you to easily extract the assembler code from the rest of this magazine | ||
+ | (and all of my ugly comments). | ||
+ | execute the following command line (substitute filenames as appropriate): | ||
+ | |||
+ | grep ' | ||
+ | |||
+ | Dontcha just love those Unix commands! | ||
+ | |||
+ | .%0001! | ||
+ | .%0002! | ||
+ | .%0003! | ||
+ | |||
+ | This program is written for the Buddy assembler. | ||
+ | needs a few directives to start off, so here they are. Note that my comments | ||
+ | come BEFORE the section of code that I am commenting on. | ||
+ | |||
+ | .%0004! | ||
+ | .%0005! | ||
+ | .%0006! | ||
+ | .%0007! | ||
+ | .%0008! | ||
+ | .%0009! | ||
+ | |||
+ | Here are the zero page locations that the package uses for its own purposes. | ||
+ | I stuck the sysWork variable over the BASIC graphics command parameters since | ||
+ | it seems like a good place. | ||
+ | routines for temporary storage. | ||
+ | storage. | ||
+ | |||
+ | .%0010! | ||
+ | .%0011! | ||
+ | .%0012! | ||
+ | .%0013! | ||
+ | .%0014! | ||
+ | |||
+ | These are the non-zero page storage locations. | ||
+ | much has to be at $200 since that is (about) the only free section of memory | ||
+ | below address $0400 (in the common memory range). | ||
+ | |||
+ | .%0015! | ||
+ | .%0016! | ||
+ | .%0017! | ||
+ | |||
+ | These are the MMU configuration register values and some important I/O | ||
+ | addresses. | ||
+ | |||
+ | .%0018! | ||
+ | .%0019! | ||
+ | .%0020! | ||
+ | .%0021! | ||
+ | .%0022! | ||
+ | .%0023! | ||
+ | .%0024! | ||
+ | .%0025! | ||
+ | .%0026! | ||
+ | .%0027! | ||
+ | .%0028! | ||
+ | .%0029! | ||
+ | .%0030! | ||
+ | .%0031! | ||
+ | .%0032! | ||
+ | .%0033! | ||
+ | .%0034! | ||
+ | .%0035! | ||
+ | .%0036! | ||
+ | .%0037! | ||
+ | .%0038! | ||
+ | |||
+ | Here's that jump table. | ||
+ | |||
+ | .%0039! | ||
+ | .%0040! | ||
+ | .%0041! | ||
+ | .%0042! | ||
+ | .%0043! | ||
+ | .%0044! | ||
+ | .%0045! | ||
+ | .%0046! | ||
+ | .%0047! | ||
+ | .%0048! | ||
+ | .%0049! | ||
+ | |||
+ | Here are some useful storage locations. | ||
+ | error encountered in a routine if the routine exits with the carry flag set | ||
+ | (and it is supposed to be cleared for OK). " | ||
+ | expansion memory banks, and " | ||
+ | free in the system. | ||
+ | directly. | ||
+ | |||
+ | .%0050! | ||
+ | .%0051! | ||
+ | .%0052! | ||
+ | .%0053! | ||
+ | .%0054! | ||
+ | .%0055! | ||
+ | .%0056! | ||
+ | |||
+ | This routine gets the ball rolling. | ||
+ | start up the system with the decimal mode flag set or interrupts disabled. | ||
+ | |||
+ | .%0057! | ||
+ | .%0058! | ||
+ | .%0059! | ||
+ | .%0060! | ||
+ | .%0061! | ||
+ | .%0062! | ||
+ | .%0063! | ||
+ | .%0064! | ||
+ | .%0065! | ||
+ | .%0066! | ||
+ | .%0067! | ||
+ | |||
+ | And this routine stops the ball from rolling. | ||
+ | buffer with zeros to stop that syntax error thing. | ||
+ | |||
+ | .%0068! | ||
+ | .%0069! | ||
+ | .%0070! | ||
+ | .%0071! | ||
+ | .%0072! | ||
+ | .%0073! | ||
+ | .%0074! | ||
+ | .%0075! | ||
+ | .%0076! | ||
+ | .%0077! | ||
+ | .%0078! | ||
+ | .%0079! | ||
+ | .%0080! | ||
+ | |||
+ | This routine copies the common code subroutines into the common code buffer | ||
+ | (at $0200). | ||
+ | |||
+ | .%0081! | ||
+ | .%0082! | ||
+ | .%0083! | ||
+ | .%0084! | ||
+ | .%0085! | ||
+ | .%0086! | ||
+ | .%0087! | ||
+ | .%0088! | ||
+ | .%0089! | ||
+ | .%0090! | ||
+ | .%0091! | ||
+ | .%0092! | ||
+ | |||
+ | And this is the common code. It contains four subroutines for accessing RAM1 | ||
+ | (and the zero page routines are used for RAM0 as well). | ||
+ | |||
+ | .%0093! | ||
+ | .%0094! | ||
+ | |||
+ | Selects the MMU configuration according to the bank number and copies the | ||
+ | number of bytes required for a zpload. | ||
+ | This is used only for internal memory zploads. | ||
+ | |||
+ | .%0095! | ||
+ | .%0096! | ||
+ | .%0097! | ||
+ | .%0098! | ||
+ | .%0099! | ||
+ | .%0100! | ||
+ | .%0101! | ||
+ | .%0102! | ||
+ | .%0103! | ||
+ | .%0104! | ||
+ | .%0105! | ||
+ | .%0106! | ||
+ | .%0107! | ||
+ | .%0108! | ||
+ | .%0109! | ||
+ | |||
+ | Pretty much the same as zpload. | ||
+ | |||
+ | .%0110! | ||
+ | .%0111! | ||
+ | .%0112! | ||
+ | .%0113! | ||
+ | .%0114! | ||
+ | .%0115! | ||
+ | .%0116! | ||
+ | .%0117! | ||
+ | .%0118! | ||
+ | .%0119! | ||
+ | .%0120! | ||
+ | .%0121! | ||
+ | .%0122! | ||
+ | .%0123! | ||
+ | .%0124! | ||
+ | |||
+ | As the name suggests, this copies from RAM1 to RAM0. Only .Y number of bytes | ||
+ | are copied, and if .Y=0, 256 bytes are copied. | ||
+ | configurations are switched between for every byte copied. | ||
+ | most efficient scheme, but it suffices. | ||
+ | are used and the value that BASIC put in them are assumed to still be there. | ||
+ | |||
+ | .%0125! | ||
+ | .%0126! | ||
+ | .%0127! | ||
+ | .%0128! | ||
+ | .%0129! | ||
+ | .%0130! | ||
+ | .%0131! | ||
+ | .%0132! | ||
+ | .%0133! | ||
+ | .%0134! | ||
+ | .%0135! | ||
+ | .%0136! | ||
+ | .%0137! | ||
+ | .%0138! | ||
+ | .%0139! | ||
+ | .%0140! | ||
+ | .%0141! | ||
+ | |||
+ | The opposite direction. | ||
+ | |||
+ | .%0142! | ||
+ | .%0143! | ||
+ | .%0144! | ||
+ | .%0145! | ||
+ | .%0146! | ||
+ | .%0147! | ||
+ | .%0148! | ||
+ | .%0149! | ||
+ | .%0150! | ||
+ | .%0151! | ||
+ | .%0152! | ||
+ | .%0153! | ||
+ | .%0154! | ||
+ | .%0155! | ||
+ | .%0156! | ||
+ | .%0157! | ||
+ | .%0158! | ||
+ | |||
+ | The end of the common code. The length of the common code is determined by | ||
+ | subtracting the end address from the start address. | ||
+ | |||
+ | .%0159! | ||
+ | .%0160! | ||
+ | .%0161! | ||
+ | .%0162! | ||
+ | .%0163! | ||
+ | |||
+ | The actual zpload routine. | ||
+ | internal memory is specified by the far pointer, or falls through to REU code | ||
+ | if expansion memory is specified. | ||
+ | |||
+ | .%0164! | ||
+ | .%0165! | ||
+ | .%0166! | ||
+ | .%0167! | ||
+ | .%0168! | ||
+ | .%0169! | ||
+ | .%0170! | ||
+ | |||
+ | Sets up the REU Controller registers for the parameters of the transfer. | ||
+ | that the value of the zero page address is not assumed to be absolute $0000 | ||
+ | but is taken from the zero page selection register of the MMU. The REU | ||
+ | Controller does not use the MMU for decoding zero page and stack page | ||
+ | addresses; it accesses the absolute memory directly. | ||
+ | |||
+ | .%0171! | ||
+ | .%0172! | ||
+ | .%0173! | ||
+ | .%0174! | ||
+ | .%0175! | ||
+ | .%0176! | ||
+ | .%0177! | ||
+ | .%0178! | ||
+ | .%0179! | ||
+ | .%0180! | ||
+ | .%0181! | ||
+ | |||
+ | Here the system clock speed is put into Slow mode while the transfer occurs | ||
+ | and is then restored. | ||
+ | |||
+ | .%0182! | ||
+ | .%0183! | ||
+ | .%0184! | ||
+ | .%0185! | ||
+ | .%0186! | ||
+ | .%0187! | ||
+ | .%0188! | ||
+ | .%0189! | ||
+ | .%0190! | ||
+ | |||
+ | Pretty much the same as the zpload routine, except that a command code for the | ||
+ | REU Controller is different (specifying an internal to expansion memory | ||
+ | transfer). | ||
+ | |||
+ | .%0191! | ||
+ | .%0192! | ||
+ | .%0193! | ||
+ | .%0194! | ||
+ | .%0195! | ||
+ | .%0196! | ||
+ | .%0197! | ||
+ | .%0198! | ||
+ | .%0199! | ||
+ | .%0200! | ||
+ | .%0201! | ||
+ | |||
+ | Some working storage locations are necessary for this routine, since it is | ||
+ | designed to copy data a page at a time. The source (zp1+1) and destination | ||
+ | (zw1+1) page addresses are saved and later restored because this routine | ||
+ | alters them while copying. | ||
+ | routine dispatches to the REU fetch/stash code. | ||
+ | |||
+ | .%0202! | ||
+ | .%0203! | ||
+ | .%0204! | ||
+ | .%0205! | ||
+ | .%0206! | ||
+ | .%0207! | ||
+ | .%0208! | ||
+ | .%0209! | ||
+ | .%0210! | ||
+ | |||
+ | If the transfer is less than one page long, it can be done by calling the | ||
+ | fetchPage code directly. | ||
+ | |||
+ | .%0211! | ||
+ | .%0212! | ||
+ | .%0213! | ||
+ | .%0214! | ||
+ | .%0215! | ||
+ | .%0216! | ||
+ | |||
+ | If the (internal) page to be fetched is on RAM1, the common code routine is | ||
+ | called; otherwise, the copy is done here by switching RAM0 into context. | ||
+ | can copy between RAM0 locations without switching contexts for every byte. | ||
+ | |||
+ | .%0217! | ||
+ | .%0218! | ||
+ | .%0219! | ||
+ | .%0220! | ||
+ | .%0221! | ||
+ | .%0222! | ||
+ | .%0223! | ||
+ | .%0224! | ||
+ | .%0225! | ||
+ | .%0226! | ||
+ | .%0227! | ||
+ | .%0228! | ||
+ | .%0229! | ||
+ | .%0230! | ||
+ | .%0231! | ||
+ | .%0232! | ||
+ | .%0233! | ||
+ | |||
+ | This is called for long (>=256 byte) (internal) fetches. | ||
+ | fetchPage code repeatedly, after incrementing the source and destination page | ||
+ | numbers. | ||
+ | |||
+ | .%0234! | ||
+ | .%0235! | ||
+ | .%0236! | ||
+ | .%0237! | ||
+ | .%0238! | ||
+ | .%0239! | ||
+ | .%0240! | ||
+ | .%0241! | ||
+ | .%0242! | ||
+ | .%0243! | ||
+ | .%0244! | ||
+ | .%0245! | ||
+ | .%0246! | ||
+ | .%0247! | ||
+ | .%0248! | ||
+ | .%0249! | ||
+ | .%0250! | ||
+ | .%0251! | ||
+ | |||
+ | This fetches the last chunk of less than 256 bytes and then restores the zp1 | ||
+ | and zw1 parameters to what they were before this routine was called. | ||
+ | |||
+ | .%0252! | ||
+ | .%0253! | ||
+ | .%0254! | ||
+ | .%0255! | ||
+ | .%0256! | ||
+ | .%0257! | ||
+ | .%0258! | ||
+ | .%0259! | ||
+ | .%0260! | ||
+ | .%0261! | ||
+ | .%0262! | ||
+ | .%0263! | ||
+ | |||
+ | Stash has exactly the same structure as fetch. | ||
+ | |||
+ | .%0264! | ||
+ | .%0265! | ||
+ | .%0266! | ||
+ | .%0267! | ||
+ | .%0268! | ||
+ | .%0269! | ||
+ | .%0270! | ||
+ | .%0271! | ||
+ | .%0272! | ||
+ | .%0273! | ||
+ | .%0274! | ||
+ | .%0275! | ||
+ | .%0276! | ||
+ | .%0277! | ||
+ | .%0278! | ||
+ | .%0279! | ||
+ | .%0280! | ||
+ | .%0281! | ||
+ | .%0282! | ||
+ | .%0283! | ||
+ | .%0284! | ||
+ | .%0285! | ||
+ | .%0286! | ||
+ | .%0287! | ||
+ | .%0288! | ||
+ | .%0289! | ||
+ | .%0290! | ||
+ | .%0291! | ||
+ | .%0292! | ||
+ | .%0293! | ||
+ | .%0294! | ||
+ | .%0295! | ||
+ | .%0296! | ||
+ | .%0297! | ||
+ | .%0298! | ||
+ | .%0299! | ||
+ | .%0300! | ||
+ | .%0301! | ||
+ | .%0302! | ||
+ | .%0303! | ||
+ | .%0304! | ||
+ | .%0305! | ||
+ | .%0306! | ||
+ | .%0307! | ||
+ | .%0308! | ||
+ | .%0309! | ||
+ | .%0310! | ||
+ | .%0311! | ||
+ | .%0312! | ||
+ | .%0313! | ||
+ | .%0314! | ||
+ | .%0315! | ||
+ | .%0316! | ||
+ | .%0317! | ||
+ | .%0318! | ||
+ | .%0319! | ||
+ | .%0320! | ||
+ | .%0321! | ||
+ | .%0322! | ||
+ | .%0323! | ||
+ | .%0324! | ||
+ | .%0325! | ||
+ | |||
+ | This is the code that does the fetching and stashing from/to expansion | ||
+ | memory. | ||
+ | command code, so that is an input parameter. | ||
+ | set up, the clock is slowed, the transfer happens, and then the clock speed is | ||
+ | restored. | ||
+ | Interestingly, | ||
+ | expansion memory and then fetch it back again in order to achieve an internal | ||
+ | memory transfer (if you have an REU), but I didn't bother with that. | ||
+ | |||
+ | .%0326! | ||
+ | .%0327! | ||
+ | .%0328! | ||
+ | .%0329! | ||
+ | .%0330! | ||
+ | .%0331! | ||
+ | .%0332! | ||
+ | .%0333! | ||
+ | .%0334! | ||
+ | .%0335! | ||
+ | .%0336! | ||
+ | .%0337! | ||
+ | .%0338! | ||
+ | .%0339! | ||
+ | .%0340! | ||
+ | .%0341! | ||
+ | .%0342! | ||
+ | .%0343! | ||
+ | .%0344! | ||
+ | .%0345! | ||
+ | .%0346! | ||
+ | .%0347! | ||
+ | |||
+ | The work locations are used to store a string to the first four addresses of | ||
+ | each expansion memory bank and then fetch them back again in order to | ||
+ | determine whether the bank exists or not. Expansion bank #0 is also checked | ||
+ | after each bank to see if a bank number wrap-around occured. | ||
+ | " | ||
+ | banks have been sniffed. | ||
+ | $80 to $FE are available. | ||
+ | from using expansion memory reserved by another program. | ||
+ | program uses expansion banks 0 up to but not including " | ||
+ | |||
+ | .%0348! | ||
+ | .%0349! | ||
+ | .%0350! | ||
+ | .%0351! | ||
+ | .%0352! | ||
+ | |||
+ | Here I save the data in the memory " | ||
+ | there isn't a REU installed, this memory would otherwise be corrupted by I/O | ||
+ | addresses bleeding through to the underlying RAM. | ||
+ | |||
+ | .%0353! | ||
+ | .%0354! | ||
+ | .%0355! | ||
+ | .%0356! | ||
+ | .%0357! | ||
+ | .%0358! | ||
+ | .%0359! | ||
+ | .%0360! | ||
+ | .%0361! | ||
+ | |||
+ | Here I initialize the configuration REU Controller registers. | ||
+ | only once by this package. | ||
+ | |||
+ | .%0362! | ||
+ | .%0363! | ||
+ | .%0364! | ||
+ | .%0365! | ||
+ | |||
+ | The three-byte identifier string is copied into the source tag. The fourth | ||
+ | byte will be filled in by the bank number. | ||
+ | |||
+ | .%0366! | ||
+ | .%0367! | ||
+ | .%0368! | ||
+ | .%0369! | ||
+ | .%0370! | ||
+ | |||
+ | Initialization continues. | ||
+ | |||
+ | .%0371! | ||
+ | .%0372! | ||
+ | .%0373! | ||
+ | .%0374! | ||
+ | .%0375! | ||
+ | .%0376! | ||
+ | .%0377! | ||
+ | .%0378! | ||
+ | |||
+ | This is the main loop. It tests the current expansion bank and then goes on | ||
+ | to the next one if ok. Otherwise, it stops at the number of okay banks. | ||
+ | |||
+ | .%0379! | ||
+ | .%0380! | ||
+ | .%0381! | ||
+ | .%0382! | ||
+ | .%0383! | ||
+ | .%0384! | ||
+ | .%0385! | ||
+ | |||
+ | Restore the underlying RAM contents and exit. | ||
+ | |||
+ | .%0386! | ||
+ | .%0387! | ||
+ | .%0388! | ||
+ | .%0389! | ||
+ | .%0390! | ||
+ | .%0391! | ||
+ | .%0392! | ||
+ | .%0393! | ||
+ | .%0394! | ||
+ | .%0395! | ||
+ | .%0396! | ||
+ | .%0397! | ||
+ | .%0398! | ||
+ | |||
+ | First checks that the maximum number of allowed expansion banks has not been | ||
+ | exceeded. | ||
+ | see that the string has been stored correctly and that the string on expansion | ||
+ | bank 0 is still ok (it wouldn' | ||
+ | |||
+ | .%0399! | ||
+ | .%0400! | ||
+ | .%0401! | ||
+ | .%0402! | ||
+ | .%0403! | ||
+ | .%0404! | ||
+ | .%0405! | ||
+ | .%0406! | ||
+ | .%0407! | ||
+ | .%0408! | ||
+ | .%0409! | ||
+ | .%0410! | ||
+ | .%0411! | ||
+ | .%0412! | ||
+ | .%0413! | ||
+ | .%0414! | ||
+ | .%0415! | ||
+ | .%0416! | ||
+ | .%0417! | ||
+ | .%0418! | ||
+ | .%0419! | ||
+ | .%0420! | ||
+ | |||
+ | This routine reads the bytes at address [zp1] and makes sure they are the same | ||
+ | as the previous routine put there. | ||
+ | string found is not the same as what was previously put out. | ||
+ | |||
+ | .%0421! | ||
+ | .%0422! | ||
+ | .%0423! | ||
+ | .%0424! | ||
+ | .%0425! | ||
+ | .%0426! | ||
+ | .%0427! | ||
+ | .%0428! | ||
+ | .%0429! | ||
+ | .%0430! | ||
+ | .%0431! | ||
+ | .%0432! | ||
+ | .%0433! | ||
+ | .%0434! | ||
+ | .%0435! | ||
+ | .%0436! | ||
+ | .%0437! | ||
+ | .%0438! | ||
+ | |||
+ | This is the three-byte string put into the expansion banks. | ||
+ | "RAM identifier" | ||
+ | |||
+ | .%0439! | ||
+ | .%0440! | ||
+ | .%0441! | ||
+ | .%0442! | ||
+ | .%0443! | ||
+ | .%0444! | ||
+ | .%0445! | ||
+ | |||
+ | This routine calls " | ||
+ | bank. RAM0 is set to be free from $4000 to the top of BASIC memory, so you'll | ||
+ | have to change the " | ||
+ | that occupies memory higher than this address. | ||
+ | from $0400 to $FEFF | ||
+ | |||
+ | .%0446! | ||
+ | .%0447! | ||
+ | .%0448! | ||
+ | .%0449! | ||
+ | .%0450! | ||
+ | .%0451! | ||
+ | .%0452! | ||
+ | |||
+ | Set the memory allocation first free chunk pointer to Null and set the number | ||
+ | of bytes of free memory to 0. | ||
+ | |||
+ | .%0453! | ||
+ | .%0454! | ||
+ | .%0455! | ||
+ | .%0456! | ||
+ | .%0457! | ||
+ | .%0458! | ||
+ | .%0459! | ||
+ | |||
+ | Determine the length of free memory on RAM0 and free the memory. | ||
+ | |||
+ | .%0460! | ||
+ | .%0461! | ||
+ | .%0462! | ||
+ | .%0463! | ||
+ | .%0464! | ||
+ | .%0465! | ||
+ | .%0466! | ||
+ | .%0467! | ||
+ | .%0468! | ||
+ | .%0469! | ||
+ | |||
+ | Free the memory of RAM1 | ||
+ | |||
+ | .%0470! | ||
+ | .%0471! | ||
+ | .%0472! | ||
+ | .%0473! | ||
+ | .%0474! | ||
+ | |||
+ | For each existing expansion bank, free it from addresses $0000 to $FFF7. | ||
+ | cannot free all 65536 bytes since this would cause the length of the free | ||
+ | chunk to be set to $0000 which would cause problems later on. $FFF8 bytes are | ||
+ | set to free since then length has to be a multiple of eight bytes. | ||
+ | |||
+ | .%0475! | ||
+ | .%0476! | ||
+ | .%0477! | ||
+ | .%0478! | ||
+ | .%0479! | ||
+ | .%0480! | ||
+ | .%0481! | ||
+ | .%0482! | ||
+ | .%0483! | ||
+ | .%0484! | ||
+ | .%0485! | ||
+ | .%0486! | ||
+ | .%0487! | ||
+ | .%0488! | ||
+ | .%0489! | ||
+ | .%0490! | ||
+ | .%0491! | ||
+ | |||
+ | This routine is called for freeing banks RAM0 and RAM1. It does nothing other | ||
+ | than set parameters and is put in for convenience. | ||
+ | |||
+ | .%0492! | ||
+ | .%0493! | ||
+ | .%0494! | ||
+ | .%0495! | ||
+ | .%0496! | ||
+ | .%0497! | ||
+ | .%0498! | ||
+ | .%0499! | ||
+ | .%0500! | ||
+ | .%0501! | ||
+ | |||
+ | One of the biggies. | ||
+ | store the information at the start of the current free memory chunk. | ||
+ | is used to hold the length input parameter and " | ||
+ | previous free memory chunk whereas " | ||
+ | chunk. | ||
+ | other routines. | ||
+ | future assemblers to have. | ||
+ | |||
+ | .%0502! | ||
+ | .%0503! | ||
+ | .%0504! | ||
+ | .%0505! | ||
+ | .%0506! | ||
+ | .%0507! | ||
+ | |||
+ | Align the number of bytes requested to an even multiple of eight. | ||
+ | |||
+ | .%0508! | ||
+ | .%0509! | ||
+ | .%0510! | ||
+ | .%0511! | ||
+ | .%0512! | ||
+ | .%0513! | ||
+ | .%0514! | ||
+ | |||
+ | Set the current free chunk pointer to the first free chunk and set Q to Null. | ||
+ | |||
+ | .%0515! | ||
+ | .%0516! | ||
+ | .%0517! | ||
+ | .%0518! | ||
+ | .%0519! | ||
+ | .%0520! | ||
+ | .%0521! | ||
+ | .%0522! | ||
+ | |||
+ | Search for a free chunk that is long enough to satisfy the request. | ||
+ | |||
+ | .%0523! | ||
+ | |||
+ | If the current free chunk pointer is Null, then we are S.O.L. (Out of Luck) | ||
+ | since that means we have exhausted the list of free chunks and have to report | ||
+ | that insufficient free memory could be found. | ||
+ | |||
+ | .%0524! | ||
+ | .%0525! | ||
+ | .%0526! | ||
+ | .%0527! | ||
+ | .%0528! | ||
+ | .%0529! | ||
+ | .%0530! | ||
+ | .%0531! | ||
+ | .%0532! | ||
+ | .%0533! | ||
+ | .%0534! | ||
+ | .%0535! | ||
+ | .%0536! | ||
+ | .%0537! | ||
+ | |||
+ | Fetch the header information of the current free chunk and check the length. | ||
+ | If the current free chunk is not large enough, then we set the Q pointer to | ||
+ | the current pointer, and take the new value for the current pointer from the | ||
+ | header of the current free chunk (mallocMemNextPtr) and then continue | ||
+ | searching. | ||
+ | |||
+ | .%0538! | ||
+ | .%0539! | ||
+ | .%0540! | ||
+ | .%0541! | ||
+ | .%0542! | ||
+ | .%0543! | ||
+ | .%0544! | ||
+ | .%0545! | ||
+ | .%0546! | ||
+ | .%0547! | ||
+ | .%0548! | ||
+ | .%0549! | ||
+ | .%0550! | ||
+ | .%0551! | ||
+ | .%0552! | ||
+ | .%0553! | ||
+ | .%0554! | ||
+ | |||
+ | Now, we've found a block that is large enough. | ||
+ | |||
+ | .%0555! | ||
+ | .%0556! | ||
+ | |||
+ | Subtract the number of bytes requested from the total number of bytes free. | ||
+ | |||
+ | .%0557! | ||
+ | .%0558! | ||
+ | .%0559! | ||
+ | .%0560! | ||
+ | .%0561! | ||
+ | .%0562! | ||
+ | .%0563! | ||
+ | .%0564! | ||
+ | |||
+ | If the size of the current free chunk is exactly the same as the number of | ||
+ | bytes requested, then branch ahead. | ||
+ | |||
+ | .%0565! | ||
+ | .%0566! | ||
+ | .%0567! | ||
+ | .%0568! | ||
+ | .%0569! | ||
+ | .%0570! | ||
+ | |||
+ | Subtract the number of bytes requested from the length of the current free | ||
+ | chunk and then write the updated header back to the current free chunk. | ||
+ | |||
+ | .%0571! | ||
+ | .%0572! | ||
+ | .%0573! | ||
+ | .%0574! | ||
+ | .%0575! | ||
+ | .%0576! | ||
+ | .%0577! | ||
+ | .%0578! | ||
+ | .%0579! | ||
+ | .%0580! | ||
+ | |||
+ | Add the length of the free chunk to the pointer to the start of the free chunk | ||
+ | to determine the address of the memory that has just been allocated. | ||
+ | exit, returning this address. | ||
+ | |||
+ | .%0581! | ||
+ | .%0582! | ||
+ | .%0583! | ||
+ | .%0584! | ||
+ | .%0585! | ||
+ | .%0586! | ||
+ | .%0587! | ||
+ | .%0588! | ||
+ | .%0589! | ||
+ | .%0590! | ||
+ | |||
+ | Here, the size of the free chunk is exactly the same size as the request, so | ||
+ | the entire block has to be allocated and thus removed from the free chunk | ||
+ | list. This is why the Q pointer has been maintained. | ||
+ | |||
+ | .%0591! | ||
+ | |||
+ | If there is no previous block (Q == Null) then set the free chunk list head | ||
+ | pointer to the next free chunk after the current one. Then exit with the | ||
+ | current chunk as the return pointer. | ||
+ | |||
+ | .%0592! | ||
+ | .%0593! | ||
+ | .%0594! | ||
+ | .%0595! | ||
+ | .%0596! | ||
+ | .%0597! | ||
+ | .%0598! | ||
+ | .%0599! | ||
+ | .%0600! | ||
+ | .%0601! | ||
+ | |||
+ | If there is an actual previous chunk, then we have to set it to point to the | ||
+ | next chunk from the current chunk. | ||
+ | from the free chunk list, thereby allocating it. | ||
+ | |||
+ | First, we swap the Q and current pointers, since we can only access memory | ||
+ | through the " | ||
+ | |||
+ | .%0602! | ||
+ | .%0603! | ||
+ | .%0604! | ||
+ | .%0605! | ||
+ | .%0606! | ||
+ | .%0607! | ||
+ | .%0608! | ||
+ | |||
+ | Then we set the the NextPointer of the previous free chunk to point to the | ||
+ | next free chunk after the current chunk. | ||
+ | |||
+ | .%0609! | ||
+ | .%0610! | ||
+ | .%0611! | ||
+ | |||
+ | And then we restore the current chunk pointer and return it to the user. | ||
+ | |||
+ | .%0612! | ||
+ | .%0613! | ||
+ | .%0614! | ||
+ | .%0615! | ||
+ | .%0616! | ||
+ | .%0617! | ||
+ | .%0618! | ||
+ | .%0619! | ||
+ | .%0620! | ||
+ | .%0621! | ||
+ | |||
+ | And here is the real biggie, since Free is more complicated than Malloc. | ||
+ | variables are the same as for free, except that " | ||
+ | remember the input parameter to new chunk to be freed. | ||
+ | |||
+ | .%0622! | ||
+ | .%0623! | ||
+ | .%0624! | ||
+ | .%0625! | ||
+ | .%0626! | ||
+ | .%0627! | ||
+ | .%0628! | ||
+ | |||
+ | Again, align the length of the chunk. | ||
+ | chunk is assumed to be aligned (since malloc only returns aligned chunks). | ||
+ | the chunk pointer is not aligned, all hell can break loose. | ||
+ | |||
+ | .%0629! | ||
+ | .%0630! | ||
+ | .%0631! | ||
+ | .%0632! | ||
+ | .%0633! | ||
+ | .%0634! | ||
+ | .%0635! | ||
+ | |||
+ | Save the new chunk input parameter and set " | ||
+ | list. Also set Q to Null since Q will be used to remember the previous block | ||
+ | to " | ||
+ | |||
+ | .%0636! | ||
+ | .%0637! | ||
+ | .%0638! | ||
+ | .%0639! | ||
+ | .%0640! | ||
+ | .%0641! | ||
+ | .%0642! | ||
+ | .%0643! | ||
+ | .%0644! | ||
+ | .%0645! | ||
+ | |||
+ | Search for the two free chunks whose addresses straddle the new free chunk. | ||
+ | |||
+ | .%0646! | ||
+ | |||
+ | If the current free chunk pointer is Null or if the current free chunk' | ||
+ | number is less than the new chunk' | ||
+ | we have found a free chunk that is " | ||
+ | must straddle the address of the new chunk. | ||
+ | line 652, external memory free chunks will be allocated first. | ||
+ | a " | ||
+ | first. | ||
+ | |||
+ | .%0647! | ||
+ | .%0648! | ||
+ | .%0649! | ||
+ | .%0650! | ||
+ | .%0651! | ||
+ | .%0652! | ||
+ | |||
+ | Here we know that the bank number is not " | ||
+ | not equal, then we continue searching. | ||
+ | check the addresses within the bank to see if zp1 is higher than the new | ||
+ | chunk. | ||
+ | |||
+ | .%0653! | ||
+ | .%0654! | ||
+ | .%0655! | ||
+ | .%0656! | ||
+ | .%0657! | ||
+ | .%0658! | ||
+ | |||
+ | Here we continue searching. | ||
+ | and get the next free chunk pointer from the current chunk in memory. | ||
+ | go back to the top of the search. | ||
+ | |||
+ | .%0659! | ||
+ | .%0660! | ||
+ | .%0661! | ||
+ | .%0662! | ||
+ | .%0663! | ||
+ | .%0664! | ||
+ | .%0665! | ||
+ | .%0666! | ||
+ | .%0667! | ||
+ | .%0668! | ||
+ | .%0669! | ||
+ | .%0670! | ||
+ | |||
+ | Here we know that Q and zp1 straddle the new chunk, and we try to coalesce the | ||
+ | new chunk to the Q chunk. | ||
+ | |||
+ | .%0671! | ||
+ | .%0672! | ||
+ | .%0673! | ||
+ | .%0674! | ||
+ | .%0675! | ||
+ | .%0676! | ||
+ | |||
+ | If the Q pointer is Null, then there is no Q chunk to coalesce with, so the | ||
+ | free chunk head pointer is set to point to the new chunk and the new chunk | ||
+ | header is set to the size of the new chunk. | ||
+ | chunk is set to what was previously the head pointer. | ||
+ | |||
+ | .%0677! | ||
+ | .%0678! | ||
+ | .%0679! | ||
+ | .%0680! | ||
+ | .%0681! | ||
+ | .%0682! | ||
+ | .%0683! | ||
+ | .%0684! | ||
+ | .%0685! | ||
+ | .%0686! | ||
+ | .%0687! | ||
+ | .%0688! | ||
+ | .%0689! | ||
+ | .%0690! | ||
+ | .%0691! | ||
+ | .%0692! | ||
+ | |||
+ | Here there actually is a previous (Q) chunk, so its header is fetched. | ||
+ | is not on the same bank as the new chunk, then the new chunk cannot be | ||
+ | coalesced with it. Also, if the address of the new chunk does not exactly | ||
+ | follow the Q chunk, then they cannot be coalesced. | ||
+ | |||
+ | .%0693! | ||
+ | .%0694! | ||
+ | .%0695! | ||
+ | .%0696! | ||
+ | .%0697! | ||
+ | .%0698! | ||
+ | .%0699! | ||
+ | .%0700! | ||
+ | .%0701! | ||
+ | .%0702! | ||
+ | .%0703! | ||
+ | .%0704! | ||
+ | .%0705! | ||
+ | .%0706! | ||
+ | .%0707! | ||
+ | .%0708! | ||
+ | |||
+ | Here, we know that the previous chunk and the new chunk can be coalesced. | ||
+ | add the length of the new chunk to the length of the previous chunk and change | ||
+ | the new chunk pointer to point to the previous chunk. | ||
+ | |||
+ | .%0709! | ||
+ | .%0710! | ||
+ | .%0711! | ||
+ | .%0712! | ||
+ | .%0713! | ||
+ | .%0714! | ||
+ | .%0715! | ||
+ | .%0716! | ||
+ | .%0717! | ||
+ | .%0718! | ||
+ | .%0719! | ||
+ | .%0720! | ||
+ | .%0721! | ||
+ | .%0722! | ||
+ | |||
+ | Here, we know that the previous and new chunks cannot be coalesced. | ||
+ | the actual header of the pervious chunk to point to the new chunk and change | ||
+ | the new chunk header length to the free request length. | ||
+ | next chunk is already in the new chunk header from before. | ||
+ | are using " | ||
+ | header. | ||
+ | the " | ||
+ | |||
+ | .%0723! | ||
+ | .%0724! | ||
+ | .%0725! | ||
+ | .%0726! | ||
+ | .%0727! | ||
+ | .%0728! | ||
+ | .%0729! | ||
+ | .%0730! | ||
+ | |||
+ | At this point, we are finished trying to coalesce the new chunk with the | ||
+ | previous chunk, so we will attempt to coalesce the new chunk with the next | ||
+ | higher address free chunk. | ||
+ | the header information for the new chunk (the " | ||
+ | next free chunk), and " | ||
+ | the new chunk immediately preceeds the next chunk in the same way as before. | ||
+ | Note that the case of a Null next chunk pointer is handled here implicitly, | ||
+ | since the bank numbers won't match. | ||
+ | |||
+ | .%0731! | ||
+ | .%0732! | ||
+ | .%0733! | ||
+ | .%0734! | ||
+ | .%0735! | ||
+ | .%0736! | ||
+ | .%0737! | ||
+ | .%0738! | ||
+ | .%0739! | ||
+ | .%0740! | ||
+ | .%0741! | ||
+ | .%0742! | ||
+ | .%0743! | ||
+ | .%0744! | ||
+ | .%0745! | ||
+ | |||
+ | Here, we know that the new chunk can be coalesced with the next chunk. | ||
+ | have to fetch the header of the next chunk to know the length and the pointer | ||
+ | to the free chunk after the next chunk. | ||
+ | chunk to the length of the new chunk and keep the pointer to the chunk after | ||
+ | the next chunk for the new chunk header. | ||
+ | unlinked (since nothing is left to point to it) and the new chunk grows to | ||
+ | swallow it up. | ||
+ | |||
+ | .%0746! | ||
+ | .%0747! | ||
+ | .%0748! | ||
+ | .%0749! | ||
+ | .%0750! | ||
+ | .%0751! | ||
+ | .%0752! | ||
+ | .%0753! | ||
+ | .%0754! | ||
+ | .%0755! | ||
+ | .%0756! | ||
+ | .%0757! | ||
+ | .%0758! | ||
+ | .%0759! | ||
+ | .%0760! | ||
+ | .%0761! | ||
+ | .%0762! | ||
+ | .%0763! | ||
+ | .%0764! | ||
+ | .%0765! | ||
+ | |||
+ | Here, we wrap things up. We have the header for the new free chunk all | ||
+ | prepared and we have tried to coalesce the two neighboring chunks to the new | ||
+ | chunk. | ||
+ | increase the number of bytes free variable by the length of the (original) | ||
+ | free request. | ||
+ | |||
+ | .%0766! | ||
+ | .%0767! | ||
+ | .%0768! | ||
+ | .%0769! | ||
+ | .%0770! | ||
+ | .%0771! | ||
+ | .%0772! | ||
+ | .%0773! | ||
+ | .%0774! | ||
+ | .%0775! | ||
+ | .%0776! | ||
+ | .%0777! | ||
+ | .%0778! | ||
+ | .%0779! | ||
+ | .%0780! | ||
+ | .%0781! | ||
+ | .%0782! | ||
+ | |||
+ | We always return with carry cleared, since we don't check for any errors. | ||
+ | |||
+ | .%0783! | ||
+ | .%0784! | ||
+ | .%0785! | ||
+ | .%0786! | ||
+ | .%0787! | ||
+ | .%0788! | ||
+ | |||
+ | This is where the actual application code starts. | ||
+ | own program that uses the dynamic memory allocation package, then you can | ||
+ | follow the structure of this application. | ||
+ | |||
+ | We start off by declaring the storage areas for the current line being | ||
+ | processed and for the line being compared to the current line. The addresses | ||
+ | reflect the structure of the record for the input line that was discussed | ||
+ | earlier. | ||
+ | $8FF since the input line can only be 242 characters long. | ||
+ | |||
+ | .%0789! | ||
+ | .%0790! | ||
+ | .%0791! | ||
+ | .%0792! | ||
+ | .%0793! | ||
+ | .%0794! | ||
+ | .%0795! | ||
+ | .%0796! | ||
+ | |||
+ | These are the zero page locations that sort uses. | ||
+ | |||
+ | .%0797! | ||
+ | .%0798! | ||
+ | .%0799! | ||
+ | .%0800! | ||
+ | .%0801! | ||
+ | .%0802! | ||
+ | |||
+ | And these are the kernel routines that are called. | ||
+ | |||
+ | .%0803! | ||
+ | .%0804! | ||
+ | .%0805! | ||
+ | .%0806! | ||
+ | .%0807! | ||
+ | |||
+ | " | ||
+ | print status information out while it is working. | ||
+ | |||
+ | .%0808! | ||
+ | .%0809! | ||
+ | .%0810! | ||
+ | .%0811! | ||
+ | |||
+ | This routine reads a new line in from the current input channel and puts it | ||
+ | into the processing buffer. | ||
+ | lines to read or if a read error occurs. | ||
+ | |||
+ | .%0812! | ||
+ | .%0813! | ||
+ | |||
+ | The " | ||
+ | the new call was the last of the file. This overcomes the kernel' | ||
+ | way of setting EOI for the last character rather than when for when you go | ||
+ | beyond the last character. | ||
+ | |||
+ | .%0814! | ||
+ | .%0815! | ||
+ | .%0816! | ||
+ | .%0817! | ||
+ | .%0818! | ||
+ | .%0819! | ||
+ | .%0820! | ||
+ | .%0821! | ||
+ | |||
+ | It exits when the maximum line length is exceeded or when a carriage return is | ||
+ | encountered. | ||
+ | |||
+ | .%0822! | ||
+ | .%0823! | ||
+ | .%0824! | ||
+ | .%0825! | ||
+ | .%0826! | ||
+ | .%0827! | ||
+ | |||
+ | A trailing ' | ||
+ | length of the input line record is recorded. | ||
+ | rather than the length of just the text is more convenient to know when | ||
+ | working with the memory package. | ||
+ | |||
+ | .%0828! | ||
+ | .%0829! | ||
+ | .%0830! | ||
+ | .%0831! | ||
+ | .%0832! | ||
+ | .%0833! | ||
+ | .%0834! | ||
+ | .%0835! | ||
+ | .%0836! | ||
+ | .%0837! | ||
+ | |||
+ | On end of file, we exit with carry set. If, however, we have read characters | ||
+ | before the EOF was encountered, | ||
+ | line of the file. True EOF will be returned on the next call. | ||
+ | |||
+ | .%0838! | ||
+ | .%0839! | ||
+ | .%0840! | ||
+ | .%0841! | ||
+ | .%0842! | ||
+ | .%0843! | ||
+ | .%0844! | ||
+ | .%0845! | ||
+ | .%0846! | ||
+ | .%0847! | ||
+ | |||
+ | This routine simply writes out the current line (' | ||
+ | an additional carriage return, since the getline routine strips off the CR. | ||
+ | |||
+ | .%0848! | ||
+ | .%0849! | ||
+ | .%0850! | ||
+ | .%0851! | ||
+ | .%0852! | ||
+ | .%0853! | ||
+ | .%0854! | ||
+ | .%0855! | ||
+ | .%0856! | ||
+ | .%0857! | ||
+ | .%0858! | ||
+ | .%0859! | ||
+ | |||
+ | This routine fetches the line at the pointer sortP into RAM0 at the given | ||
+ | address. | ||
+ | to fetch. | ||
+ | |||
+ | .%0860! | ||
+ | .%0861! | ||
+ | .%0862! | ||
+ | .%0863! | ||
+ | .%0864! | ||
+ | .%0865! | ||
+ | .%0866! | ||
+ | .%0867! | ||
+ | .%0868! | ||
+ | .%0869! | ||
+ | .%0870! | ||
+ | .%0871! | ||
+ | .%0872! | ||
+ | .%0873! | ||
+ | .%0874! | ||
+ | .%0875! | ||
+ | .%0876! | ||
+ | |||
+ | This routine compares the lines stored in the " | ||
+ | and returns with carry set if the " | ||
+ | also takes into account the starting comparison positions and handles the case | ||
+ | of either or both lines not being as long as the start position of the string | ||
+ | comparison. | ||
+ | |||
+ | .%0877! | ||
+ | |||
+ | This section of code makes bit0 of .X a " | ||
+ | be compared, and makes bit1 a " | ||
+ | |||
+ | .%0878! | ||
+ | .%0879! | ||
+ | .%0880! | ||
+ | .%0881! | ||
+ | .%0882! | ||
+ | .%0883! | ||
+ | .%0884! | ||
+ | .%0885! | ||
+ | .%0886! | ||
+ | .%0887! | ||
+ | .%0888! | ||
+ | |||
+ | And here is where it takes action depending whether the lines are large enough | ||
+ | or not. The cases are: | ||
+ | |||
+ | . .X=%00000000 - strings are long enough to be compared, so continue | ||
+ | . .X=%00000001 - sortline is too short, cmpline ok, so return with carry clear | ||
+ | . .X=%00000010 - cmpline is too short, sortline ok, so return with carry set | ||
+ | . .X=%00000011 - both sortline and cmpline are too short; carry set | ||
+ | |||
+ | .%0889! | ||
+ | .%0890! | ||
+ | .%0891! | ||
+ | .%0892! | ||
+ | .%0893! | ||
+ | .%0894! | ||
+ | |||
+ | This section does the compare if both lines are long enough. | ||
+ | |||
+ | .%0895! | ||
+ | .%0896! | ||
+ | .%0897! | ||
+ | .%0898! | ||
+ | .%0899! | ||
+ | .%0900! | ||
+ | .%0901! | ||
+ | .%0902! | ||
+ | .%0903! | ||
+ | .%0904! | ||
+ | .%0905! | ||
+ | .%0906! | ||
+ | |||
+ | This routine searches for the correct position in the line list to insert the | ||
+ | new line, and returns sortQ and sortP to straddle the new line position. | ||
+ | that this routine causes the list to be in reverse order as discussed | ||
+ | earlier. | ||
+ | |||
+ | .%0907! | ||
+ | |||
+ | Set P to head and Q to Null. | ||
+ | |||
+ | .%0908! | ||
+ | .%0909! | ||
+ | .%0910! | ||
+ | .%0911! | ||
+ | .%0912! | ||
+ | .%0913! | ||
+ | .%0914! | ||
+ | .%0915! | ||
+ | .%0916! | ||
+ | |||
+ | This routine breaks out if the current line pointer is Null. Otherwise, it | ||
+ | fetches the current line pointer (sortP) into the cmpline buffer and calls the | ||
+ | string compare routine. | ||
+ | or equal to the current line already in the list, the search kicks out. The | ||
+ | " | ||
+ | pointers are updated in the usual way and the search continues. | ||
+ | |||
+ | .%0917! | ||
+ | .%0918! | ||
+ | .%0919! | ||
+ | .%0920! | ||
+ | .%0921! | ||
+ | .%0922! | ||
+ | .%0923! | ||
+ | .%0924! | ||
+ | .%0925! | ||
+ | .%0926! | ||
+ | .%0927! | ||
+ | .%0928! | ||
+ | .%0929! | ||
+ | .%0930! | ||
+ | .%0931! | ||
+ | .%0932! | ||
+ | .%0933! | ||
+ | .%0934! | ||
+ | |||
+ | At this point, sortP and sortQ straddle the position to put the new line, so | ||
+ | we return. | ||
+ | |||
+ | .%0935! | ||
+ | .%0936! | ||
+ | .%0937! | ||
+ | .%0938! | ||
+ | |||
+ | This routine actually stores the new line read in between the sortQ and sortP | ||
+ | lines. | ||
+ | |||
+ | .%0939! | ||
+ | |||
+ | First, space for the new line is allocated. | ||
+ | |||
+ | .%0940! | ||
+ | .%0941! | ||
+ | .%0942! | ||
+ | .%0943! | ||
+ | .%0944! | ||
+ | |||
+ | And the new line's next pointer is set to point to sortP. | ||
+ | |||
+ | .%0945! | ||
+ | .%0946! | ||
+ | .%0947! | ||
+ | .%0948! | ||
+ | .%0949! | ||
+ | |||
+ | And the new line is stashed out to main memory. | ||
+ | |||
+ | .%0950! | ||
+ | .%0951! | ||
+ | .%0952! | ||
+ | .%0953! | ||
+ | .%0954! | ||
+ | .%0955! | ||
+ | .%0956! | ||
+ | |||
+ | Now all that is left to is make the previous line record (sortQ) point to the | ||
+ | new line record. | ||
+ | |||
+ | .%0957! | ||
+ | .%0958! | ||
+ | .%0959! | ||
+ | |||
+ | If there is an actual previous line, the new line pointer is written out over | ||
+ | the next line pointer in its header. | ||
+ | |||
+ | .%0960! | ||
+ | .%0961! | ||
+ | .%0962! | ||
+ | .%0963! | ||
+ | .%0964! | ||
+ | .%0965! | ||
+ | .%0966! | ||
+ | .%0967! | ||
+ | .%0968! | ||
+ | .%0969! | ||
+ | .%0970! | ||
+ | .%0971! | ||
+ | .%0972! | ||
+ | |||
+ | If there is no actual previous line, then the line list head pointer is set to | ||
+ | point to the new line (which is now the first line on the list). | ||
+ | |||
+ | .%0973! | ||
+ | .%0974! | ||
+ | .%0975! | ||
+ | .%0976! | ||
+ | .%0977! | ||
+ | .%0978! | ||
+ | .%0979! | ||
+ | .%0980! | ||
+ | .%0981! | ||
+ | .%0982! | ||
+ | .%0983! | ||
+ | |||
+ | This routine reads in the file and puts the lines into their correct sorted | ||
+ | positions as it is reading. | ||
+ | |||
+ | .%0984! | ||
+ | |||
+ | Clear the line list by setting the head pointer to Null. | ||
+ | |||
+ | .%0985! | ||
+ | .%0986! | ||
+ | .%0987! | ||
+ | .%0988! | ||
+ | .%0989! | ||
+ | |||
+ | Set the EOF flag to 0 and set the current input channel to logical file #1 | ||
+ | which is assumed to be opened before the sort utility is invoked. | ||
+ | |||
+ | .%0990! | ||
+ | .%0991! | ||
+ | .%0992! | ||
+ | .%0993! | ||
+ | .%0994! | ||
+ | |||
+ | Until EOF, read the new line, find the position in the line list, store it, | ||
+ | print out a " | ||
+ | and repeat. | ||
+ | |||
+ | .%0995! | ||
+ | .%0996! | ||
+ | .%0997! | ||
+ | .%0998! | ||
+ | .%0999! | ||
+ | .%1000! | ||
+ | .%1001! | ||
+ | .%1002! | ||
+ | .%1003! | ||
+ | .%1004! | ||
+ | .%1005! | ||
+ | .%1006! | ||
+ | .%1007! | ||
+ | .%1008! | ||
+ | |||
+ | This routine writes the line list out to logical file number 2 which is | ||
+ | assumed to be opened before the sort utility is invoked. | ||
+ | the standard structure for processing a linked list. | ||
+ | |||
+ | .%1009! | ||
+ | .%1010! | ||
+ | .%1011! | ||
+ | .%1012! | ||
+ | .%1013! | ||
+ | .%1014! | ||
+ | .%1015! | ||
+ | .%1016! | ||
+ | .%1017! | ||
+ | .%1018! | ||
+ | .%1019! | ||
+ | .%1020! | ||
+ | .%1021! | ||
+ | .%1022! | ||
+ | .%1023! | ||
+ | .%1024! | ||
+ | .%1025! | ||
+ | .%1026! | ||
+ | .%1027! | ||
+ | .%1028! | ||
+ | .%1029! | ||
+ | .%1030! | ||
+ | .%1031! | ||
+ | .%1032! | ||
+ | .%1033! | ||
+ | .%1034! | ||
+ | .%1035! | ||
+ | .%1036! | ||
+ | .%1037! | ||
+ | .%1038! | ||
+ | |||
+ | This routine will reverse the order of the line list. Starting from the head | ||
+ | line, each line is extracted and is made to point to the previous line | ||
+ | extracted. | ||
+ | line records have to be changed. | ||
+ | |||
+ | .%1039! | ||
+ | .%1040! | ||
+ | .%1041! | ||
+ | .%1042! | ||
+ | .%1043! | ||
+ | .%1044! | ||
+ | .%1045! | ||
+ | .%1046! | ||
+ | .%1047! | ||
+ | .%1048! | ||
+ | .%1049! | ||
+ | .%1050! | ||
+ | .%1051! | ||
+ | |||
+ | Fetch the pointer from the current line into sortP and then replace it with | ||
+ | the value at sorthead (the previous line altered). | ||
+ | |||
+ | .%1052! | ||
+ | .%1053! | ||
+ | .%1054! | ||
+ | .%1055! | ||
+ | .%1056! | ||
+ | .%1057! | ||
+ | |||
+ | Make sorthead point to the current line, and then go to the next line whose | ||
+ | pointer was extracted from the current line (before the current line was | ||
+ | changed). | ||
+ | |||
+ | .%1058! | ||
+ | .%1059! | ||
+ | .%1060! | ||
+ | .%1061! | ||
+ | .%1062! | ||
+ | .%1063! | ||
+ | .%1064! | ||
+ | .%1065! | ||
+ | .%1066! | ||
+ | .%1067! | ||
+ | .%1068! | ||
+ | .%1069! | ||
+ | .%1070! | ||
+ | .%1071! | ||
+ | |||
+ | This routine scans through the lines in the line list and deallocates each | ||
+ | line record. | ||
+ | |||
+ | .%1072! | ||
+ | .%1073! | ||
+ | .%1074! | ||
+ | .%1075! | ||
+ | .%1076! | ||
+ | .%1077! | ||
+ | .%1078! | ||
+ | .%1079! | ||
+ | .%1080! | ||
+ | .%1081! | ||
+ | .%1082! | ||
+ | .%1083! | ||
+ | .%1084! | ||
+ | .%1085! | ||
+ | .%1086! | ||
+ | .%1087! | ||
+ | .%1088! | ||
+ | .%1089! | ||
+ | .%1090! | ||
+ | .%1091! | ||
+ | .%1092! | ||
+ | .%1093! | ||
+ | .%1094! | ||
+ | .%1095! | ||
+ | .%1096! | ||
+ | .%1097! | ||
+ | .%1098! | ||
+ | |||
+ | Finally! | ||
+ | subroutines for the different phases of the sort and prints out a letter | ||
+ | indicating what the program is currently doing. | ||
+ | |||
+ | .%1099! | ||
+ | .%1100! | ||
+ | .%1101! | ||
+ | .%1102! | ||
+ | .%1103! | ||
+ | .%1104! | ||
+ | .%1105! | ||
+ | .%1106! | ||
+ | .%1107! | ||
+ | .%1108! | ||
+ | .%1109! | ||
+ | .%1110! | ||
+ | .%1111! | ||
+ | .%1112! | ||
+ | .%1113! | ||
+ | .%1114! | ||
+ | .%1115! | ||
+ | .%1116! | ||
+ | .%1117! | ||
+ | .%1118! | ||
+ | .%1119! | ||
+ | .%1120! | ||
+ | .%1121! | ||
+ | .%1122! | ||
+ | .%1123! | ||
+ | |||
+ | It returns with .A set to zero in case the user calls sort again and forgets | ||
+ | to specify a value for the sorting column using the BASIC SYS statement. | ||
+ | |||
+ | .%1124! | ||
+ | .%1125! | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 5. FUTURE ENHANCEMENTS | ||
+ | |||
+ | This dynamic memory allocation package does not support expanded internal | ||
+ | memory (as specified in Twin Cities-128 Magazine) or RamLink memory. | ||
+ | planning to modify the memory allocation in the Zed-128 program to support | ||
+ | both of these kinds of memory. | ||
+ | accessed in a similar manner as RAM1 is, except that I will need to have some | ||
+ | special bank numbers for them, since they cannot be handled in exactly the | ||
+ | same way as RAM0 and RAM1. I will also have to modify that other MMU register | ||
+ | in order to select which real banks show up in the RAM2 and RAM3 positons. | ||
+ | |||
+ | The memory inside a RamLink can be accessed in a similar way to how memory is | ||
+ | accessed in an REU. One big difference is that the layout of the storage in a | ||
+ | RamLink is actually organized. | ||
+ | up to 31 partitons of various types. | ||
+ | the package will check to see if you have a RamLink and will then check to see | ||
+ | if you have partiton number 31 set up as a " | ||
+ | name " | ||
+ | length of the partition and will then use the RamLink memory instead of an | ||
+ | REU. This makes sense since an REU can be made to be part of the RamLink | ||
+ | and since you can get a lot more memory in a RamLink than I have ever heard of | ||
+ | in an expanded REU. I personally have an 8 Meg RamLink and I have set aside a | ||
+ | 1 Meg partition for the swap space. | ||
+ | use it. | ||
+ | |||
+ | These additional types of memory can be seemlessly implemented into this | ||
+ | package and the usage will be compeletely transparent to the user and to the | ||
+ | higher level routines. | ||
+ | |||
+ | Also, although I have not attempted to do this, the code presented here could | ||
+ | be ported to the Commodore 64. The common code routines would be removed | ||
+ | since the 64 has only one internal bank, and instead of using the MMU to | ||
+ | select RAM0, you would store into the processor I/O port to select the bare | ||
+ | internal RAM. (You would also have to worry about interrupts happening while | ||
+ | you are accessing this memory). | ||
+ | zpload, zpstore, fetch and stash routines would (probably) stay (pretty much) | ||
+ | the same, since they call the lower level routines to do the actual | ||
+ | machine-specific grunt work. | ||
+ | |||
+ | If you have any questions or comments about this article, feel free to drop me | ||
+ | a line. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | |||
+ | 6. UUENCODED BINARIES | ||
+ | |||
+ | Here are the BASIC program and the machine language subroutines for the | ||
+ | sorting utility. | ||
+ | extract them into separate files before you uudecode them. Enjoy! | ||
+ | |||
+ | begin 640 sort | ||
+ | M`1PF' | ||
+ | MLB)/ | ||
+ | M4T]25" | ||
+ | M`*4< | ||
+ | M*0#'' | ||
+ | M, | ||
+ | =H# | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 640 sort.bin | ||
+ | M`!-, | ||
+ | M_R!($R# | ||
+ | MC0# | ||
+ | M`/ | ||
+ | M_Y' | ||
+ | MWZ7ZC03? | ||
+ | M$Z; | ||
+ | M`/ | ||
+ | MA? | ||
+ | M_I' | ||
+ | M_" | ||
+ | M, | ||
+ | M`-^B`KVA%96`RA# | ||
+ | M/ | ||
+ | M# | ||
+ | M4LE$0`3[H@*I`)T@$ZG_G1T3RA# | ||
+ | M%: | ||
+ | M_*D`A? | ||
+ | M^X7\J0& | ||
+ | M(!/ | ||
+ | MHH" | ||
+ | MAY6' | ||
+ | M' | ||
+ | M^I6*M8" | ||
+ | MAH6# | ||
+ | M@Z6$98: | ||
+ | MI8AEA, | ||
+ | MH@*UAY7ZRA# | ||
+ | M)"#/ | ||
+ | M.&" | ||
+ | M# | ||
+ | MT/ | ||
+ | MRA# | ||
+ | MR? | ||
+ | M^ZD`A0*B`2#& | ||
+ | M(, | ||
+ | M`\H0]: | ||
+ | M^LH0^: | ||
+ | M" | ||
+ | , | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | ============================================================================ | ||
+ | </ | ||
+ | ====== Next Issue: (Hopefully!!! :-] ) ====== | ||
+ | < | ||
+ | The 1351 Mouse Demystified | ||
+ | |||
+ | An indepth look at how the 1351 mouse operates and how to access it within | ||
+ | your ML programs, in addition to a BASIC driver for the 80 column screen. | ||
+ | |||
+ | ML Tutor - Part 3 | ||
+ | |||
+ | In this edition we take a look at reading and writing commands to the disk | ||
+ | drive, including reading the disk directory. | ||
+ | the discussion of the C=128 and C=64 KERNAL jump tables of available routines. | ||
+ | |||
+ | KERNAL 64/ | ||
+ | |||
+ | The C=128 and C=64 jump table that points to many valuable system routines is | ||
+ | listed and discussed with example applications and how to use them. | ||
+ | |||
+ | Bursting your 128 | ||
+ | |||
+ | This article will examine the routines and mysteries about how to use Burst | ||
+ | commands on the 1571 and 1581, including the Fastload utility and the block | ||
+ | Read and Write calls. | ||
+ | ============================================================================ | ||
+ | END of C= Hacking Issue 2. | ||
+ | ============================================================================ | ||
+ | </ |
magazines/chacking2.txt · Last modified: 2015-04-17 04:34 by 127.0.0.1