We stopped last time in mid-description. It seems our Good Editor has such a wealth of excellent contributions last issue (besides mine, I mean) that my article had to be cut to fit the space remaining. This is just fine; my voluminous verbage often takes up so much space that I worry that I am pushing many worthy authors from these pages.
I don't have to worry about that anymore.
In any case, we'll just continue where we left off last time. We were discussing Flowchart 4, which shows the building of the SYSCON table. You'll want to turn back to the last issue, read the final 4 paragraphs of the last installment, and continue here (back issues are available)...
We know that expansion banks could control extra hardware (printers, disk drives, ect.), but they also could have caused an interrupt, by grounding the INT line on the backplane, with an Open Collector driver.
The subject of interrupts is far outsioe the scope of this series, but they effectively cause a special subroutine to be run due to an external hardware signal, rather than the execution of a CALL statement. In the standard TS2068, an interrupt occurs every 1/60 second, causing the keyboard scanner to be run. Depending on the hardware causing the interrupt, it may need a fast response, or it may be prepared to wait all day to be serviced.
A bank switching interrupt handler would have had to poll each bank to determine who caused the interrupt (or wether it was just a request to scan the keyboard) and this takes time. The ability to renumber banks according to an interrupt priority would ensure that the banks that must be serviced fastest also have the lowest bank numbers. This makes it easy to check the critical ones, first.
Now suppose that when we installed the bank number, the bank really DID exist. We put the bank number into SYSCON 01 for that bank, but with bit 7 also set. This flag states that this bank hasn't been renumbered yet.
Starting with X0A58, we try several things at once. The bank can be either RAM or ROM bank. The best way to check for RAM is to write a number, and then read it back, to see if it "took". Of course a ROM bank just MIGHT have the same value by coincioence, so it pays to write a second value to the same location, and check it again.
Unfortunately, as we can see from the flowchart, the routine to move bytes from one bank to another has been misapplied; it errantly tries to copy bytes from the RAM bank to the EXROM bank. It's obvious that the folks at Timex didn't have any expansion bank hardware, at least when the code for the ROM was frozen. These are the simple, "preliminary" mistakes a programmer makes when writing code in a hurry, and prior to debugging. If any hardware were available to check this code out, these problems would never have existed.
Another thing that's done (properly this time) is to move a 24 byte block from location 0000 of the expansion bank into SYSCON 2 and the bytes following. If it's a RAM bank, this is harmless "power up" garbage. If it's a ROM bank, then these are the overhead bytes for the bank, and they'll follow a pattern like the SYSCON Table Configuration, given in pert 2. (Note that a couple of items got left out of that table; we'll put in corrections as needed.)
Having done all this, we execute code to do the final setup for the bank. If it's a RAM bank, we end up at X0AB7, where we CALL a routine to check which chunks of the bank actually contain RAM. (It was also supposed to copy in the interrupt handler from the EXROM, but if fouls that up.) In any case, SYSCON O2 gets a byte whose bits specify the chunks where RAM is available.
If it's instead a ROM bank, we reset bit 5 of SYSCON 02. My SYSCON configuration could use a tad of clarification on this point. This could have been an ASCII character representing a channel specification. This is because the bank switching system could have allowed for additional channels, which could have been linked to an expansion bank. We'll talk more about I/O in a minute, but for now, we'll just point out that resetting the bit will shift the character to upper case.
Now, at XOAC7 and following, we CALL a routine to check SYSCON 15 for the bank, and if it contains 01, we get the aodress of the initialization code from SYSCON 07 and 08, and run the code at that address in the expansion bank. Thus, for a little while, the expasion bank is in control of the system. There are lots of things we may want to do with this. There are cases where it would be more convenient to permanently add a channel, without using the OPEN# routine (this should become clearer, in a bit) or, you may wish for it to install some special code in the Home Bank. It could even take over the entire system, or prompt you as to whether you'd like it to do so.
Whether it was RAM or ROM, initialize or not, we always end up at X0ACA, which steps the daisychain to the next bank, and we loop to X0A4C and try to find anotoher bank.
Now that we've seen the TS2068 builds the SYSCON table, we can take a better look at Flowchart 3, which actually installs the bank number. First let's note that the system variable MAXBNK normally contains the number of expansion banks plugged into your system. But during initialization, it's the number of the bank presently being assigned. Since the very last bank number assigned equals the number of expansion banks, everything works out nicely.
We start by assuming that there's another bank to initialize and increment MAXBNK. If it turns out we're wrong, we'll correct it later. At X0BDB we try to install that number in the next bank, which is selected by the Daisychain. That bank now has then number (MAXBNK). By sending that number to register 80 (Bank Number Access) we can access its Horizontal Select Register.
At X0BE7, we send 00 to register 40 (Horizontal Select). This does not remove "power on garbage" as the flowchart says; at power on, the bank resets itself. However, this DOES make this routine useful to another (presently unused) routine in the EXROM that RESETS the SYSCON table, by fixing up any values that we may have changed there. It's at X0C4C; check it out and see if you can find some use for it. It appears that Timex may have once had plans to access this through one form of the RESET command, from BASIC. We'll talk about that next time.
In any case, at X0BEE, we save the maximum bank number, the contents of location A000, and then write 04 there; this is NOT an unlock command, as the flowchart says. 0ne of the bank's status registers (the AO register) is memory mapped into location A000. But we don't yet know if the bank we're setting up actually exists, yet! If it does, then when we read register A0, we'll find bit 2 = 0, BUT IF IT'S NOT THERE, WE'LL JUST GET THE CONTENTS 0F LOCATI0N A000 WHEN WE TRY TO READ IT. As such, we first set the contents of A000 so that bit 2 = l. If the bank doesn't exist, we are guaranteed to see a "1" there.
Almost. What if N0 bank has location A000's chunk allocated to it? This can happen, since that routine that resets the SYSCON table also CALLs this, and the chunk could have been "lost" through some code of our own. Looking at the TS2068 schematic, we see that data line D2, and only D2, has a pullup resistor on it. Even if no bank will respond to location A000, we're still covered, and the lack of an expansion bank will show us a 1, This would also have been needed if Timex sold versions of the TS2068 that only had 16k of RAM in them. (They did announce such plans, though they wisely discarded them.) This is a somewhat more complete (and slightly more accurate) explanation of the resistor than was given in the past. If this explanation makes any sense, you may see why I simplified the description, earlier.
All right, NOW we can read the C0/A0 register pair, and check bit 2 to see if the bank exists. In either case, we'll restore the contents of location A000, that we wiped out earlier. If the bank is there, we set the CY flag, and return to the CALLing routine with the bank's number installed, and MAXBNK properly updated. If the bank isn't there, we return with the CY flag reset, we decrement MAXBNK, to correct our original assumption that another bank existed, and we send 04 to register C0, to end the setup mode, since we won't be using the daisychain, anymore. That's it!
Flowchart 5 shows the GET_STATUS routine, in the RAM resident code, after the modifications in TM6.5.2 have been installed. For a specified bank, it will return the Horizontal select byte, and will also return the status byte for an expansion bank. Note that in normal use, this routine is called once for each bank, and the information is used as a whole. This is because the Horizontal Select register for the standard banks "claims" all 8 chunks for those banks. Remember, an expansion bank has to override this, by applying the /BE signal at the computer's backplane connector. As such, the horizontal select information for the standard banks is only valid for those chunks not claimed by an expansion bank.
There's not too much to say about Flowchart 6. This is CALLed when the initialization code finds a ROM bank. It marks it as such in the SYSCON table, and checks bit 0 of SYSC0N 15, If it's a "1", then the initialization code for the bank is run. This allows each bank the option of participating in system initialization. It's not mandatory, but it's nice if it's needed; particularly if the bank has some I/O hardware that needs some initial massaging. Note that HL is used throughout the initialization as an address pointer into the SYSCON table. As the flowchart shows, this routine has a major bug in that it wipes out that pointer by accident. This does not seem difficult to fix, but as is, it seems unlikely that the system could initialize with a ROM bank present.
Many readers know about the working TS2068 commands that aren't documented in your owner's manual. For example, OPEN #2, "p" will redirect all output from a PRINT statement to your printer, rather than your screen. There are also commands that are only "half there". Turn on your TS2068, and type in the following "program":
10 LOAD *"m",3,"test" 20 CAT "d" ,3,4 30 FORMAT "m",1,2,3,"junk" 40 OPEN #3,"j",1,2,"moretrash" 50 MOVE "a" ,"garbage",2,5 60 ERASE "b" ,1,"nonsense"
You may be surprised to find that every one of these commands can be entered into your machine, and it will accept them, but not one of them will actually RUN! (You'll get an error message, instead.) Furthermore, each one will take as long a list of string and numberic items as you'd like to give, provided you give at least one, following the single letter in quotes. (Except for the OPEN# command, which normally needs no extra list following the letter.
What gives? The Timex/Sinclair machines are supposed to do complete syntax checking when you type your lines in; how did it miss these? Well, there is a class of commands that only work when extra hardware is plugged into your machine. There are two ways these could have been implemented, and the TS206B designers seem to have left both options open. The first method is largely copied from the Sinclair Spectrum, and it works like this. BASIC can do two things when it "sees" a program line. If you're typing the line in, it runs the Syntax Checker. If it's RUNning a program, it looks up the address of the routine that executes the command and runs it. (If you type in a line with no line number, it does both.)
This is also true for the above extended commands. We think we see a difference because the routine that runs the command is designed to end up with the printing of an error message. Thus, if you type in the proper syntax, the machine wlll properly accept the line, and when you RUN it, it "properly" prints an error code.
Whatever for? The program that prints error messages (for both the TS2068 and the Spectrum) is at location 0008. Those familiar with Sinclair's Interface One, for the Spectrum, know that it switches in it's "shadow ROM" whenever the instruction at 0008 is run. The shadow ROM then checks the cause of the error, scans the present BASIC line, determines if it's supposed to be running an extended command, and acts accordingly. Whlle this might seem llke an odd way to add commands, it contains a perverse sort of beauty. It makes it possible to design a computer and include all the ROM code necessary to run future add-ons, without really knowing what those add-ons wlll look like, or what real software is needed to run them. It's a great way to "buy time".
If this method were used, we can guess that the BEU would have contained extra hardware to switch in the "Superbank" mentioned in the past. This would be analagous to the shadow ROM. Since the Home ROM code contains nothing to link it to a bank switching interrupt handler, perhaps the "Superbank" may have switched in when the code at 0038 (the keyboard interrupt handler) was run.
There is a second option. In routines to "run" the extended commands, we tend to find a JUMP instruction to code to print the error message. But following that JUMP is usually found extra code that appears to look up an address in the SYSCON table and CALL the routine in its expansion bank. It also passes on whatever list of information was tacked onto the end of the statement. As such, if the JUMP is NOPed out, it appears that the system should find code in an expansion bank to actually handle the command. Furthermore, these extra blocks of code are not used anywhere else in the ROM! They were almost certainly intended to link the extended commands to the expansion banks. Why were they blocked from that purpose?
Nothing is simple. Once again, there are two fairly reasonable options. In essentially every case, the llttle packet of code that's blocked off contains one or more fatal bugs that could really gum up the system if allowed to run. Since it would have been fairly clear to the designers that new, bugless ROMs would be needed for bank switching anyway, they could save debug time by simply hiding the code that the original ROMs would really not need.
The other option centers around the timing of separate Timex and Sinclair developments. The Sinclair Interface One was released around the same time as the TS2068 was, and its relative simpllcity suggests that design on the TS2068 was begun a good deal BEFORE the Interface 0ne. As such, the original TS2068 designs could not have considered it, and if Timex did eventually plan to copy the Interface One's method into its own microdrive interface, they would have had to make some changes.
While blocking off some of their code might seem a sloppy way to do this, it would have worked, and the rest of the Timex modifications to the Spectrum code aren't very neat, either. As a glaring example, we can find several routines in the ROM that were probably used by the programmers to debug the code, but aren't used by the ROM, itself. This method is fairly universal, but the common practice is to remove your debug garbage before assemling the version that's to go into ROM.
It's a simllar bit of sloppiness that makes this second option the most likely. You see, one of these "blocked off" bits of code seems to have quite a few instructions missing from it; it could never work as is. Now, I know that some of you have bootleg copies of Timex's original source code listing for the ROMs. If you'll look at the code following the JP at 25E1 in the Home ROM, you'll see that Timex "commented out" a full 28 lines of code, which would have assembled into about 43 bytes. These would have restored the missing functions, but the Home ROM only has 36 spare bytes in it (3CDC, and following), so these extra bytes wouldn't have fit. Rather than to search for debug garbage to delete, they simply hacked out some code that might otherwise have been functional! Clearly, it wasn't too important to them. As such, they were probably going to copy the method (and as much software as possible!) from the Interface One.
While we can bounce these, and a whole lot of other bits of circumstantial evidence around, we can get no conclusive answer. In the end, it doesn't matter. If we wish to restore the bank switching functions, we can use either option. But it does help to understand that both options are there. It's also worthwhile to note that restoring the blocked out code would make the hardware design somewhat simpler for us.
Take some time and get cozy with TM4.1, on I/O channels. This is not a great treatment of the subject, but it's a start. Next, if you'll read the definitions of the system variables STRMS and CHANS, on pages 262, 263 of your TS2068 User Manual, you'll notice some subtle inconsistencies with the Technical Manual. The User Manual implies that channels and streams are two different things, and that channels are "attached" to streams. The Technical Manual suggests that the two things are identical.
Actually, a channel is a block of information providing a link to an I/O device. At a minimum, it contains a 2 byte output address for the device, a 2 byte input address, and a 1 byte device specification, which is an ASCII character. All of the normal channels that appear when you power up your computer, ("K", for keyboard & lower screen, "S", formain screen, "P", for printer, and "R", which isn't used, but is there anyway) follow this 5 byte format. It doesn't have to be this way though; an "m" (microdrive) channel on the Spectrum, is an incredible 595 bytes long!
A stream is normally a displacement into the channel area. There are 19 available streams, and the system does most of it's I/O through them. It must look up the channels they point to, find the addresses of the appropriate input or output routines, and then jump to them. Normally, stream 0 points to the "K" channel, stream 2 to the "S" channel, and stream 3 to the "P" channel.
Can we use this from BASIC? Sure thing! If you type:
PRINT #0;"test":PAUSE 0
you'll find that it prints on the bottom line of the screen,
where BASIC can't usually PRINT. The PAUSE 0 is simply there to
keep the system from printing its "OK" down there before you can
see what you printed. If you instead use #2, it will PRINT
normally on the screen. Using #3 will send the information to the
printer. What we're doing is telling BASIC which stream to use
when it sends out the PRINT data. When we don't give it a stream
number, it uses #2, as a default value.
Conversely, we can do the same thing with the INPUT command.
INPUT #1,A will input a character through stream 1.
This is what it does by default, and so doesn't demonstrate as
much as we'd like, but it shows how we'd use INPUT to take data
directly into BASIC from an I/O device. None of the other
channels has a true "input address"; the addresses given will
just cause the printing of an error message. Clearly, there's a
lot of I/O power here that just isn't being used!
The "print drivers" that allow LPRINTand LLIST commands to run a large printer work because they modify the output address in the "p" channel. (Ordinarily, it points to a routine in the ROM thst accommodates the TS2040 printer.) The COPY command needs a separate routine because the coey command doesn't work through an I/O stream.
Most of this can be gleaned from the Technical Manual, but there are additional capabilities that have not been documented. Above, I said that a stream is NORMALLY a displacement into the channels area. Actually, only the lower 15 bits of the value are a displacement. (The most significant bit is then normally "0".) However, if the most significant bit contains a "1", then the other bits represent a displacement into the SYSCON table, and can give us an I/O link to routines in an expansion bank.
Some machine code programmers use the RST 10 command to
print the character in the A register on the screen. Actually,
RST 10 will send it to the "current channel" (whose address is in
the system variable CURCHL), and BASIC will have set this to the
"S" channel somewhat before it executes the USR funtion that
hands control to our machine code. It can be changed by putting a
stream number in A and CALLing 1230. For example, stream 3
normally points to the "P" (printer) channel. If we
LD A,3 and
CALL 1230 then subsequent RST 10 commands will send the
character in A to the printer.
Now, this RST 10 business is standard Spectrum stuff, but Timex added a lot more for bank switching (mostly inoperable, due to bugs). There is also a "current channel bank number", in the system variable CURCBN. For expansion banks, this is the bank number, and for Home Bank, it's set to 0, The Dock and EXROM banks aren't supported by this. As such, RST 10 was intended to be able to send the value in the A register to a routine in any expansion bank. If it were'nt for a bug, it could also be used to INPUT a character through a routine in an expansion bank numbered 2 or greater. Apparently, Timex had a spacial purpose in mind for bank #1 (the superbank, perhaps?).
If you're comfortable with streams and channels, you probably
realize that the primary function of the OPEN # command in
the standard TS2068 is to modify a stream to point to a
particular channel (
However, there is also
a "channel specific" portion run, since there may also be some
system flags that need massaging. To make it possible to OPEN a
channel into an expansion bank, placing a comma after the
standard OPEN # format will allow you to add any additional
garbage you'd like to the line; it needn't be a list at all. This
will pass the syntax check, but trigger an error on execution,
kicking in the superbank, (if that method were used) and handling
whatever channel specific operations may be needed, Like actually
inserting the new channel. Or scanning the SYSCON table for the
proper channel specifier and running a routine to open the
channel from that bank. (The OPEN # code address would be at
SYSCON 03 & 04; this got left out of the table, in part 2.
The CLOSE # command looks a bit more boring, but it does a lot. In the standard TS2068, it largely just returns to its power on value, but if the stream was attached to an expansion bank channel, it will also run some code form that bank. The address is found at SYSCON 05 and 06 this WAS included in the table in part 2. (Well, every now and then, SOMETHING goes right!) In order to get some use out of extended bank switching, the I/O routines must be understood and debugged. This is a bit far from the topic of this series, and space won't allow a detailed examination, but here are some memory addresses to help (keep your bug-spray handy!!!)
11AA-11BE Initial Channel Data 11Cl-11CD Initial Stream Data 11ED-122F Outputs A to current channel (used by RST 10) 1230-1292 Set current channel according to stream # in A 1374-139E Search SVSCON table for channel specifier in C 139F-1429 CLOSE routines 142A-14C6 OPEN routines - Note that location 1486 contains a JR that is reached through another JR. This second JR is one of those JUMPS that blocks off some of the ROM code. Depending on how Vou may want to implement things, this JR might be NOPed to allow OPENing a stream through an expansion bank.
I began this series with the cautionary note that I'd be presenting only the results of my foray through the ROMs; not giving a construction project. But perhaps I can break my own rule just this once. Some readers are a bit scared by the idea of changing the ROM code, to make the bank switching work properly. Cutting up their computers, and opening it repeatedly to switch EPROMs just seems too bothersome. Actually, there's a better way, which is so simple that it is by far the easiest part of implementing bank switching,
Figure 5 shows a circuit I use in order to run EPROMs in place of the ROMs. I was able to build mine on a small card that plugs into the cartridge slot, although it's slightly too large to get the door closed. This is not really too bothersome, since it's only a temporary modification, used when debugging the actual ROM code. One fly in the pointment is that one necessary signal is not available on either connector, and you'll have to open up your computer to tack a wire on to it.
The jumper marked W2 is the point to which the wire must be attached. Although it looks like a resistor, it's merely a wire jumper in disguise, so you can hook the wire to either side. While you've got your computer open, don't forget to remove the ROMs. It's also a good idea to put a label on each, telling which is which; if you ever want to put them back, you'll need to know.
Depending on the installation of the jumpers as shown in Figure 5, you can run either EPROMs or the original ROMs, or one of each. This is helpful in debugging the board, and also in debugging your ROM modifications, since you may frequently want to switch back to the original ROMs, to see how they react to a certain set of circumstances. 1 find it most convenient to keep a set of EPROMs that contain an exact copy of the ROM code for this. That way, I don't have to fool with the jumpers, much. Still, you can do whatever suits you best. Also note that the extra wire is only needed if you want to make changes to the Home ROM code. You can simulate EXROM externally without any extra wires tacked on, but you'll still want to open the computer and remove the real EXROM, first.
Since you'll be doing a lot of plugging and unplugging, in- vest the extra ten or fifteen bucks to install ZIF sockets. This will be cheaper in the long run, since you can't plug a chip too many times into a normal socket, without breaking off a pin. The cost of the sockets will far outweigh the cost of the ruined EPROMs, not to mention the wasted time and frustration.
That's all for now; we'll wrap up this series next time. Don't forget to write or call with your questions, ideas or observations. I'll be glad to hear from you!