The idea behind p-code is very elegant: it is an "ideal" assembly language to be run on a virtual microprocessor. Concretely, it means that it requires an interpreter, the p-machine emulator or PME, to emulate this virtual microprocessor. The big advantage is that you can write p-code "assembly" programs (or compile higher languages into p-code) and run them on many different machines. The language was optimized so as to be easy to emulate and to run smoothly on any machine. Apart from the TI-99/4A there were emulators written for the Z80, Z8000, 6502, 6509, Motorola 6800 and 68000 (Apple computers), HP-87, PDP-11, Intel 8080 and 8086 (PCs) , and GA440 microprocessors (and what on earth is the latter?).
This article is split in two pages. One that discusses the p-system theory of operations and the present one that describes the TI-99/4A p-code card and accompanying software.
The p-code card is basically a ROM card. It contains two ROM chips (4732 and 4764) and eight GROM chips. A peculiarity of the card is that the GROMs are not accessed via the usual GROM ports at >98xx. Instead, they map inside the card DSR space. This makes the GROM "private" to the p-code card.
A switch is located at the back of the card, that can easily be reached with the card installed in the PE-Box. This switch is used to disable the card and prevent the p-system from taking over at power-up time.
Since the GROMs need both +5V and -5V, there are two voltage regulators on the p-code card. A 78M05 provides +5 volts to all chips, and a 79M05 provides an additional -5 volts power to the GROMs.
As recommended by Texas Instruments, all lines to/from the peripheral bus are buffered on-card. This is done with three 74LS244 for the 16-bit address bus and some of the control lines: MEMEN*, CLKOUT*, CRUCLK*, DBIN, AMA, AMB, AMC (the last 3 are not used in the PE-Box and hard-wired at +5V). For some reason, the RESET* line has an independent buffer.
The data bus, of course needs a bidirectional buffer, whose DIRection is controlled by the DBIN signal from the peripheral bus, and the ENable pin by a signal generated by the card. This signal also controls the RDBENA* line that activates the bus drivers in the peripheral cable. Possibly because it must interacts with GROMs, the bus buffer is a 74SC245 instead of a LS.
The following schematics illustrates both the bus buffering, and the address decoding logic described in the next section.
74LS244 +5V---WWW---, ,-WWW--+5V
Most of the address decoding, both for memory and for CRU, is performed by a custom control chip. Namely, a 12L6 programable array logic (PAL). To save some input pins, there are also a few accessory chips: an 8-input NAND gate (74LS30), a dual 4-input AND gates (74LS21) and a OR gate (74LS32).
The input pins of the PAL receive the following.
Pin #1: CRU bit 0 and AMA and AMB and AMC.
bit 0, since AMA-AMC are always high.
Pin #2: A0 or A2
Pin #3: A1 Probably combined with pin #2 to form the DSR address space: >4xxx.
Pin #4: A3 Could be used for ROM selection and GROM port (weight >1000).
Pin #5: A4 and A5 and A6 and A7. Probably combined with pin #4 for CRU address: >1Fxx.
Pin #6: A9 Probably combined with pin #7 for GROM port address: >xBFC
Pin #7: Not(A4 and A6 and A7 and A8 and A10 and A11 and A12 and A13).
Pin #8: CRUCLK*
Pin #9: DBIN
Pin #11: MEMEN*
Pin #12: Main switch, via a debouncing circuit comprising two OC-buffers (74LS17), resistors and capacitor.
Pin #19: A5 Probably used for GROM read/write (weight >0400).
The output pins provide the following signals:
Pin #13: ROM2*. Selects the 4764 ROM chip.
Pin #14: ROM1*. Selects the 4732 ROM chip.
Pin #15: BusEn*. Enables the data bus.
Pin #16: GSel*. Enables GROM access.
Pin #17: CruSel*. Enables the 74LS259 for CRU operations.
Pin#18: Loops back to DBIN. I'm not sure what's the idea here?
Power pins: Pin #10 ground, Pin #20 +5V.
Of course, we don't know what equations were programmed inside the PAL, but we know their results:
NB One wonders why the GROM ports were not installed closer to each other (e.g. >5FF8, >5FFA, >5FFC and >5FFE). It's probably to be consistent with the convention of having GROM write ports >0400 bytes apart from the read ports. This way, to access one or the other type of GROM, is suffices to toggle the port between >5BFC and >9800. So as not to waste space, only these four words map to GROMs, the rest of the area >5C00-5FFB maps to the ROM. (The code in the ROM just jumps over the reserved addresses).
The CRU logic is exceedingly simple. Only two bits are used: bit 0 turns the card on and controls the LED, bit 4 switches pages in the 4764 ROM. An optional jumper allows to control the LED with cru bit 7 instead of bit 0. No provision is made to read back the CRU bits.
The CruSel* signal is produced by the PAL chip when the CRU address is >1Fxx. Therefore, bit 0 answers at address >1F00. Due to the fact that A8 is used instead of A12, bit 4 answers at CRU address >1F80 and bit 7 at >1F86.
As mentionned above, there are two ROM chips, a 4 Kbytes 4732 and an 8 Kbytes 4764.
Address and data pins are connected to the address and data bus in a fairly straightforward manner. Note that, because Texas Instrument numbers its pins the wrong way around (A0 being the MSB), the numbers do not match. But this is not critical with ROMs anyhow.
Each chip has its own selection line, from the PAL control chip. In addition, the 4732 is enabled by A3, i.e. it will map at >4000-4FFF only. The extra address line of the 4764 is controlled by CRU bit 4, which lets us page the area >5000-5FFF.
The p-code card contains 8 GROM chips, each 6 Kbytes, installed at 8K boundaries.
All pins are common for the 8 GROM chips. This is because each chip recognizes its own address range and does not answer otherwise. Address and data are passed via the data bus, as usual with GROMs.
The three control pins are masked by the GSel* signal from the custom control IC by means of three OR gates, then amplified by three open-collector buffers (74LS17 ?) with associated 470 Ohms pull-up resistors. Pin M ( Read/Write*) receives DBIN, pin MO (Address/Data*) receives A14 just like in the console, and the selection pin GS* receives A15 so that the GROMs only answer at even addresses.
The GSel* signal also enables a 74LS125 3-state buffer that lets the GREADY signal reach the PE-Box bus. This allows the GROMs to put the TMS9900 microprocessor on hold by pulling down the READY line, until they are ready to answer. Note the pull-up resistor on the line.
Finally, a 74LS161 counter is used to generate the clock signal for the GROMs. It is wired so as to divide by 8 the system clock CLKOUT (a.k.a. PHI3*). Assuming a 3 MHz clock, this boils down to 375 kHz, which is quite a bit slower than the 447.4 kHz used for the console GROMs. But I suspect this was done for the sake of simplicity.
System implementation on the TI-99/4A
The p-code card takes control of the TI-99/4A at power-up time. It enters the p-code operating system which runs in text mode (40-column, monochrome) and presents you with a main menu in the form of a promptline.
A promptline always starts with the name of the current system module, followed by a column, a list of available commands, and a version number in square brackets. You can select a command by typing its first letter. If a promptline is too long to be displayed on a single line, it ends with a ? question mark. Typing a ? causes the next set of commands to be displayed. However, it is not necessary that an command is displayed for it to be available: if you remember its name you can enter its first letter at any time.
Available commands at the system level are: E(dit), R(un), F(ile), C(omp), L(ink), X(ecute), A(ssem), H(alt), I(nitialize), U(ser restart), and M(onitor). In addition, there is a D(ebug) command that is not available with the TI system.
The main menu and several of its command are implemented by a p-code program called SYSTEM.PASCAL. This program resides in the p-code card GROMs, and is accessed via unit #14. Other functions require a dedicated program that will be loaded from disk: Editor, Compiler, Assembler, Linker, or Filer. Extra utilities are provided on disks, that can be launched with X(ecute).
Apart from the arrow keys (Fctn-ESDX) and the <enter> key, several function keys are also active:
Fctn-1 <del> Deletes a char
Fctn-2 <ins> Inserts a char
Fctn-3 <flush> Stops writing the output to the screen
Fctn-4 <break> Stops a program and initializes the system
Fctn-5 <stop> Suspends a program until a key is pressed
Fctn-6 <alpha-lock> Toggle to convert upper-case to lower-case and back again
Fctn-7 <screen left> Scrolls the screen to the left by 20 columns
Fctn-8 <screen right> Scrolls the screen to the right by 20 columns
Fctn-9 <line delete> Deletes the current line
Ctrl-C <etx/eof> Indicates the end of a file (fills it with zeros in the Editor)
Ctrl-. <esc> Tells a program to ignore the previous text
Ctrl-I <tab> Moves the cursor to the next tab
The TI-99/4A implementation of the p-system defines the following units:
#4: <diskname in DSK1>
#5: <diskname in DSK2>
#9: <diskname in DSK3>
#10: <diskname> (For extra drives, if any)
#13: <unit loaded from tape>
#14: OS (Card GROMs: contain the system files)
#31: TAPE (Cassette tape recorder)
#32: TP (Thermal printer)
When prompted for a filename, you could either type #4:MYFILE.TEXT, or MYDISK:MYFILE.TEXT, where MYDISK is the name of the disk in DSK1. If you omit the unit name and number, the system will look for the file on every units. If no ambiguity is possible, you can dispense with the final .TEXT or .CODE as the system automatically appends the appropriate suffix. To prevent the system from adding a suffix, follow the filename with a dot.
Some commands expect a filename as input. Filenames can be up to 15 characters in length and can optionally be preceded by a unit name. Often, if you are prompted for a text file or a code file, you can ommit the final .TEXT or .CODE suffix.
The system provides default names for either file type: SYSTEM.WRK.TEXT for text files, and SYSTEM.WRK.CODE for code files. If these files are present, programs like the Editor, the Compiler and the Assembler will use them without prompting you for a filename.
Reboots the system and executes the code file SYSTEM.STARTUP, if present. Note that all execution errors that are not fatal cause the system to automatically initialise.
Exits the p-system and reboots the TI. This time, the p-code card will not take over and you can use TI-Basic or whatever module is available. To restart the p-system, turn the power off for both the console and the PE-box during 10 seconds.
Launches the text editor from the code file SYSTEM.EDITOR and automatically loads the file SYSTEM.WRK.TEXT for editing, if it is found. Otherwise, you are prompted for a filename and have to option to create a new one by entering the editor with no file at all.
The editor that comes with the TI p-system is a fairly nice one: for instance, it lets you define markers inside your text file and jump to them easily. A complete description is beyond the scope of these pages, but just to make your mouth water, here is a list of availble edition commands:
A(djust), C(opy), D(elete), F(ind), I(nsert), J(ump), K(olumn), Q(uit), R(eplace), X(change), Z(ap)
Launches the default compiler from the code file SYSTEM.COMPILER and automatically compiles the file SYSTEM.WRK.TEXT if it can be found. The resulting code file will be SYSTEM.WRK.CODE. If the default workfile is not found, you are prompted for the names of the TEXT and CODE file you want to use.
The TI p-system comes with a sophisticated UCSD-Pascal compiler. For instance, if a compilation error occurs, the compiler gives you the option to continue compilation, to stop it, or to enter the Editor with the cursor positionned at the point where the error occured in the source file (ain't that nice?).
Launches the assembler from the code file SYSTEM.ASSEMBLER and starts assembling the file SYSTEM.WRK.FILE if it is found. If it is not found you are prompted for a source file, a code file and a list file. The defaults are: SYSTEM.WRK.CODE and no list file. In case of an assembly error, you have the same options as with the compiler: continue, stop or edit the source file.
Launches the linker from the file SYSTEM.LINKER. The linker takes assembly programs, links them together and with compiled files and turns them into a file that can be loaded and executed.
Prompts you for the name of a code file, then loads and executes it. Optionally, the filename can be followed by some option switches (in fact, your input can consist uniquely in option switches).
The system will complain if the file cannot be found, if it needs to be linked before execution, or if it does not contain a program.
This is a batch command. It tries to load and execute the workfile SYSTEM.WRK.CODE. If this file is not found (or if it does not contain a program) run calls the compiler and possibly the linker to try to produce a valid code file. If SYSTEM.WRK.TEXT is present it will be compiler, otherwise you'll be prompted for filenames.
Causes the last system or user-defined application to run again, with the same input parameters as before. However, U(ser restart) will not reenter the compiler nor the assembler (it will reenter the editor though).
Launches the filer from the file SYSTEM.FILER The filer is the p-system disk manager.
In the p-system, files are never fractured, rather they are saved as a continuum of blocks. Which often results in creating gaps between files, that are too small to be reused efficiently. One of the most usefull functions of Filer is to pack the files together so as to reclaim this space. Then of course it offers standard functions like directory listing, file copy, file deletion, disk formatting, etc.
One word on disk format: this is a two-level formatting, which means the disk should already have been formatted with the DiskManager cartridge or a similar program. What Filer does is to fill the disk with a single, huge DF128 file called "PASCAL". The p-system files are then part of the PASCAL file. Concretely, this means that you cannot manage p-code files with your standard disk manager, you need Filer...which was sold separately by Texas Intruments (so were the Editor, Compiler, Assembler, Linker, etc.).
Again, a detailed description of the filer commands is beyond the scope of this document, but here is its main menu, just to give you a taste of it:
B(ad-blocks), C(hange), D(ate), E(xtended-directory), G(et), K(runch), L(ist-directory), M(ake), N(ew), P(refix), Q(uit), R(emove), S(ave), T(ransfer), V(olumes), W(hat), X(amine), Z(ero).
Allows you to record your actions into a file, in the form of keystrokes, so that they can be replayed later on unsing the I= switch in the X(ecute) command. This is a nice way to implement automated tasks, and batch files. The resulting file can be a TEXT file or a data file. If it is a TEXT file it can be edited with the Editor. however it cannot contain special keystrokes such as the arrow keys.
Once you select Monitor, you are presented with the following options: B(egin), E(nd), A(bort), S(uspend), R(esume)
B(egin): starts a monitoring session. You are prompted for a filename optionally followed by a file length in brackets. The length is in blocks of 512 bytes and generally a lentgh of (1) is more than enough.
R(esume): Returns to the main menu and starts recording all your input into the file.
S(uspend): Returns to the main menu and temporarily stops recording your input. To resume loging, use the R(esume) command.
E(nd): leaves monitor mode and saves the resulting file. To return to the main menu, select S(uspend).
A(borts): leaves monitor mode without saving the file. To return to the main menu, select S(uspend) or R(esume).
The X(ecute) command also lets you enter a string of options to configurate your system. An option consists in one or two letters, optionally followed with an equal sign and a parameter. Several options can be specified, by using spaces as separators. If your input also contains the name of a file to be executed, it must precede the options, although they will be processed by the system before execution of the file.
PI= filename or PI= "string" or PI=
Redirects program input. When followed with a filename, all input to the program will be taken from this file until an EOF is encountered. The name can also be that of a no-disk unit (such as REMIN:), in which case redirection continues until you explicitely cancel it.
When followed with a string in double quotes, this string will be the program input. To include double quotes in the string, replace them with two single quotes. Commas are understood as <enter>. Note that you can enter several strings with successive PI= commands, in which case will be processed in order of appearance. You can also specify both a file and one or more strings for input, in which case the strings are processed first, and then the file is used. Input redirection ends when the program terminates and all unused characters are discarded.
If nothing follows the = sign, the program input will be the same as the system input.
I= filename or I= "string" or I=
Same as PI= but affects the system input, i.e. main menu commands. Redirection terminates when an execution error is encountered or when the H(alt) command is executed. Canceling redirection results in using CONSOLE: as input.
PO= filename or PO=
Redirects program output to the file (or unit) specified. The file will be closed when redirection is changed. To cancel redirection, just type PO= this will cause the output to be the same as the system output.
O= filename or O=
Same as PO= but redirects the output from the system. O= cancels redirection and sends output to CONSOLE:.
Note that redirection only applies to the standard input and output files. If a program uses low-level procedures like UNITREAD and UNITWRITE no redirection will occur. A program can also specify its own redirections with the procedure REDIRECT, cancel it with EXCEPTION or append a string to the input buffer with the procedure CHAIN.
Changes the default prefix, i.e. the unit name to be used when a filename does not specify a unit. You don't need to type the final : after the unit name. This can also be achieved from inside the Filer, with the command P(refix).
Changes the default library textfile (normally USERLIB.TEXT). You don't need to type the final .TEXT since it is added automatically. The library textfile is a text file that contains a list of user libraries. When a program is run that calls an external unit, the system looks for it in the library files listed in the library textfile and then in SYSTEM.LIBRARY.
The TI-99/4A p-system package includes several floppy disks. These contain the Editor, Filer, Compiler, Assembler and Linker programs to be launched directly from the main menu. In addition, there are several utilities that you can launch with the X(ecute) command:
|COPYDUPDIR||Rescue damaged directory with the duplicated one|
|DECODE||Analyses a code file and disassembles p-code procedures in it.|
|DFORMAT||Format a unit.|
|DSKTOCASS||Copy a disk file to the cassette tape recorder|
|LIBRARY||Manages library files.|
|MARKDUPDIR||Installs a duplicate directory on disk.|
|MODRS232||Modifies the transmission parameters for PRINTER, REMIN and REMOUT units.|
|PATCH||Sector editor for files and whole units.|
|RECOVER||Undelete and disk doctor utility.|
|SETLTYPE||Change segment type in a code file.|
|SETUP||Selects the keyboard function keys.|
|XDISK||Cassette program creator|
Formats a floppy disk, installing the big DF128 file named PASCAL that takes up all disk space. The p-system "files" are actually records inside the PASCAL file.
MARKDUPDIR and COPYDUPDIR
As a safety measure, the p-system can maintain two copies of the disk directory on a given unit. MARKDUPDIR installs such a duplicate directory in the first blocks of a given unit. If a crash were to destroy the main directory, you could run COPYDUPDIR to copy the duplicate directory over the damaged one.
This utility analyses to content of a unit, and can look for missing files. It is usefull to recover accidentally deleted files, or crashed directories.
Is used to copy a file from a floppy disk unit to the cassette tape recorder.
Is a cassette program creator.
This program is a library file manager. It lets you insert files into a library, decide whether you want to include the interfacet text, display a given slot, etc.
Lets you select the key combinations you want to perform various functions in the text-oriented Editor. For instance: backspace, delete, insert, escape, etc. It also lets you set several flags describing your system: what's the byte sex of your machine, does it have a clock, what's the screen width and height. You can either modify the current parameters in the p-system memory, or save the results are saved on a file called NEW.MISCINFO. By renaming this file SYSTEM.MISCINFO you can cause your choices to be automatically loaded at boot time.
Lets you modify the transmission parameters of the RS232C serial port. This can affect either the unit PRINTER: or the units REMIN: and REMOUT: After you pick one of these, you just type a DSR name in the style: RS232/1.BA=9600.DA=8/PA=O.EC
Interestingly, the Pascal source file for this program is also provided.
This very nice program is a p-code disassembler, coupled with a code file analyser. DECODE lets you view the segment directory, display a summary of the contents of a segment, disassemble a p-code procedure, dump the symbol table, view the segment reference list, the interface text, or the linker information. The disassembler is not very user-friendly though, in that it dumps the whole procedure, no matter what. Obviously, this utility is meant for printout purposes.
This tiny utility lets you change the segment type inside a code file, e.g. from p-code to assembly.
This usefull program is basically a sector editor. It lets you view the contents of a unit, or of a file in the unit, either as hex bytes or as ascii characters. You can also dump this info into a file or print it out and set several options governing the appearance of the output. Finally, you can type in new values and save back the sector.
The intrinsics of the p-system are described in another page. I have very few details on how the p-system is implemented on the TI-99/4A. Here is the little I know...
The GROMs hold unit #14, a.k.a. OS: They contain three files: SYSTEM.PASCAL which implements the main menu and some of its functions, SYSTEM.MISCINFO and SYSTEM.CHARAC which hold the default system parameters.
The ROMs must therefore contains the PME, the BIOS (together with the card DSRs) and possibly part of the OS. The second ROM page at >5000-5FFF contains some unit BIOS: console, RS232, TP, cassette, etc.
To speed up execution, part of the code used to interpret p-codes is copies into the 16-bit scratch-pad RAM (>8300 and up), but since that's only 256 bytes in size, not much can go there. The scratch-pad also holds several workspaces used by the p-system.
The heap is at the bottom of the high memory expansion, growing upwards. The stack grows downwards from the end of the high memory expansion. The internal code pool is placed in between them, but p-code programs can also be executed in several external code pools: in the VDP RAM or in GROMs (in the case of SYSTEM.PASCAL).
The low memory expansion is reserved for system use, upto >390C. I'm not sure whether the remainder of it could also be used as a code pool. A detailed description of its contents can be found on this file, once published by Anders Persson.
Back to the TI-99/4A Tech Pages