Graphic Read-Only Memories (GROMs) are a very peculiar type of ROM manufactured by Texas Instruments. They have a multiplexed address and data port, which means you must telll the GROM which address you want to access and then reach data in there. The GROM has an internal counter that is automatically incremented each time you access data, so that you don't need to re-enter the address each time.

Theory of operation

The TI-99/4A GROMs
GROM port
GROM headers
Console GROMs


AD7 |1 o G 16| Vss
AD6 |2 R 15| GR
AD5 |3 O 14| Vdd
AD4 |4 M 13| GRC
AD3 |5 12| M
AD2 |6 11| MO
AD1 |7 10| GS
AD0 |8 9| Vcc

Power supply
Vcc +5V
Vdd -5V
Vss Ground. Actually -0.72V in the TI-99/4A console due to the following circuit (whose purpose is totally mysterious to me):

-5V--WWW-----+---+---|Vss    |
270 | | +-------+
PG1992 ^ = 0.1uF
| |
Gnd Gnd

CPU interface
AD0-AD7 Multiplexed address and data bus.
M Direction: Read (high) or Write (low). Connected to DBIN in the console.
MO Access mode: Address counter (high) or GROM data (low). Connected to A14 in the console (via a TTL buffer).
GS* GROM select. Active low, chip select signal.

GREADY Active high: signals that the GROM is ready to operate.
GRCLK Clock signal, 3.5 MHz generated by the VDP (pin 38) or 447.4 kHz generated by the VDP (pin. 37). The actual signal is selected via a jumper in the console. As far as I know, it's the 447.4 kHz that's used.

Theory of operation

The GREADY line is necessary because GROMs are slow memory devices: this line puts the TMS9900 microprocessor on hold until the GROM is ready to answer.

The M line determines the direction of access: read or write. Obviously writing data to a GROM is meaningless, but this line is required to write an address.

The MO pin determines whether the AD0-AD7 port is accessing the address counter or the data. When MO is low, data can be accessed on a byte-wise manner. The counter will be incremented after each operation, whether it's a read or a write. When the top of the GROM is reached, the counter loops back to zero.

If MO is high, writing operations load an address into the counter. As an single-byte address would limit GROM sizes to 256 bytes, the address must be passed as two bytes. The GROM has an internal flip-flop that keeps track of the bytes entered and "knows" whether it is the first, least significant byte, or the second. Any data access resets this flip-flop to "first byte expected".

Reading from the GROM with MO high returns the current value of the counter *PLUS ONE* (don't ask me why). Here also, the lower byte is passed first, then the high-order byte.

The standard TI-GROM size is >1800 bytes, wich means any address can be expressed as a 13-bit number. The remaining 3-bits are used as GROM identifier: only the GROM with a matchin internal ID will respond to such an address.

Exemple (address >54A6):

0101 0100 1010 0110
iiiA AAAA AAAA AAAA Address = >1456, ID = 2

This allows to install upto 8 GROMs on the same data bus: they all respond to every address operation, latching the current address or returning the counter value (plus one). But only the GROM with an ID matching that in the high-order address byte will accept data operations. Typically, you would have :

GROM 0: >0000-17FF
GROM 1: >2000-37FF
GROM 2: >4000-57FF
GROM 3: >6000-77FF
GROM 4: >8000-97FF
GROM 5: >A000-B7FF
GROM 6: >C000-D7FF
GROM 7: >E000-F7FF

That's kind of a waste of space since >0800 bytes in each GROM are lost in addressing gaps. I know there were a few cartridges around that had full-size GROMs of >2000 bytes, but I'm not sure whether these were real GROMs or ROMs with an emulation circuitery.

The TI-99/4A GROMs

There are three places GROMs can be found in the TI-99/4A: in the console, in a cartridge or in a peripheral card.

The console contains three >1800-byte GROMs, with ID numbers 0, 1 and 2. Which means they map at addresses >0000-17FF, >2000-37FF, and >4000-57FF. These GROMs contain most of the Basic Interpreter, routines for mathematical operations with real numbers, and DSRs for cassette tape operations.

In addition, GROMs 3 to 7 (addresses >6000-FFFF) can be implemented in cartidges to be plugged in the front panel of the console. For more information, see my pages (including commented listings) for the following cartridges: Editor/Assembler, Mini-Memory and TI-Writer.

Apart for GRAM cards, the only peripheral card with GROMs I'm aware of is the p-code card.

GROM port

No matter where they are installed, GROMs are always accessed through a set of 4 addresses in CPU memory. The first address is known as the "GROM base":

>9800: read data (GROM base)
>9802: read address+1
>9C00: write data
>9C02: write address

As you may have guessed, addess line A5 (weight >0400) is connected to the MO pins of the GROMs. Different addresses are used to read and write because the TMS9900 always preceedes a write with a read operation. This would have the annoying result of bumping the GROM counter by two on a data write operation! The solution is straightforward: a simple TTL circuitery filters out reading operations at address >9Cxx. Only the write goes through so the counter increments only once.

Texas Instruments probably sensed that 8 times >1800 bytes wouldn't be enough memory, so they implemented an additional level of subtility in the TI-99/4A. There are upto 16 sets of 4 addresses that can be used for GROM access. The GROM base for each set is >0004 apart: >9800, >9804, >9808, >980C ... >983C. In fact, the address space is free upto >9C00, so there could technically be 256 such sets. However the console ROM routines that search GROMs for subprograms only consider the first 16 bases. But still, this provides an address space of 16 * 64K, which is 1 megabyte. Even with gaps, it's not bad for a 1980s computer!

Mind you, the console does not contain any circuitery to decode the GROM base, which means that the console GROMs can be accessed from any GROM base. Ditto for the classical TI cartridges like Extended Basic or Editor/Assembler. But the possibility is here: one could design an extension device that would let you plug in several cartridges and have one at base >9800, another at base >9804, etc. To my knowledge, such a gadget was never released, but several GRAM-cards make use of this trick. For instance the german 128K GramKarte uses bases >9800 and >9820.

GROM Header

Optionally, a GROM can start with a standard header that contains lists of programs, subprograms, DSRs, etc. The first byte in the GROM must be >AA to indicate that this GROM contains a header. The structure of such a header is the following:

Bytes Content
>x000 >AA indicates a standard header
>x001 Version number
>0xx2 Number of programs (optional)
>x003 Not used
>x004 Pointer to power-up list (>0000 if none)
>x006 Pointer to program list (>0000 if none)
>x008 Pointer the DSR list (>0000 if none)
>x00A Pointer to subprogram list (>0000 if none)

Structure of a list:

Link to next item --+
Address |
Name length |
Name |
Link to next item: >0000
Name length
N.B. Name lengh and name are not necessary for power-up routines

If you would like more details on standard headers and how to write subprograms and/or DSRs, I have a whole page on the subject.

Console GROMs

A commented listing of the console GROMs can be found in Heiner Martin's book "TI-99/4A intern" (pdf file 560k). Here I'm just providing a brief description of the contents of these GROMs.

Only GROMs 0 and 1 have a standard header, GROM 2 is the continuation of GROM 1 and has no header.

At the beginning of GROM 0 is a table of subprograms, that contains only branching instructions (mostly BR, and a few B). The location of these subprograms may vary among GROM versions, but the table is always at the same place. The way to use these routine is described in my page on GPL language, clicking on one of the addresses in the table below will take you there.

N.B. FAC (floating point accumulator) corresponds to >834A-8351, ARG (argument) to >835C-8363

Mnemonic Address Use
LINK >0010 Subroutine/DSR call: FETCh >0A/>08, name ptr in >8356
RETURN >0012 Return from a subroutine/DSR
CNS >0014 Convert number to string (from FAC to FAC, infos in >8355-57)
STCASE >0016 Load title screen character patterns at VDP address in >834A
UPCASE >0018 Load upper case character patterns at VDP address in >834A
BWARN >001A Issue warning message
BERR >001C Issue error message
BEXEC >001E Begins Basic excution: FETCh 4 bytes: address of first and last lines
PWRUP >0020 System reset
INT >0022 Convert real to integer (from FAC to FAC)
PWR >0024 Power-of-ten routine FAC=FAC * 10^ARG
SQR >0026 Square root routine FAC=SQR(FAC)
EXP >0028 Exponentiation routine FAC=e^FAC
LOG >002A Logarithm calculation FAC=ln(FAC)
COS >002C Cosine calculation FAC=cos(FAC)
SIN >002E Sine calculation FAC=sin(FAC)
TAN >0030 Tangent calculation FAC=tgn(FAC)
ATN >0032 Arctangent calculation FAC=atn(FAC)
BEEP >0034 Issue acceptation sound
HONK >0036 Issue error sound
GETSPA >0038 Get VDP memory space for a string
BITREV >003B Bit reversal routine >834A: VDP address, >834C: number of bytes
NAMLNK >003D Part of LINK: searches in GROM only
PABSPA >003F Check memory space for PAB
TOKEN >0042 Get next token, set Basic pointers
LOCASE >004A Load lower case character patterns

The address of the following routines may vary, since they are not included in a vector (branch) table.

Address Use
>1387 OPEN cassette.
>13CF READ cassette.
>13DA WRITE cassette.
>13F2 OLD cassette.
>140E CLOSE cassette.
>1444 Verify cassette.
>1489 SAVE cassette.
>216F Start of Basic interpreter (Entry point for NEW).
>2214 Address table for RUN, NEW, CONTINUE, LIST, BYE,
>27E3 Clears screen, resets cursor and continues as below:
>27F1 Loads char patterns, resets colors and VDP registers 2,3 and 4.
>2A42 Start line editor with default position and length.
>2A49 Ditto with max length in >835E.
>2A4F Ditto with starting screen position in >8361.
>3450 Checks if a char is valid for a variable name (A-Z, a-z, 0-9..).
>3643 CALL CHAR.
>3708 CALL KEY.
>401E OPEN a file.
>4160 DELETE a file.
>4174 CLOSE a file
>41CF Closes all files.
>41D7 RESTORE a file.
>4227 PRINT in a file or on screen.
>426C DISPLAY on screen.
>4344 INPUT from files or keyboard.
>45E3 READ the DATA inserted in a program.
>4641 OLD loads a program.
>46FC SAVE a program.
>474C LIST a program.
>482B EOF tests for end of file.
>4D7C Prints "Bad Value".
>4D81 Prints "String-number mismatch".
>566C Prints "Can't do that".
>56CD Scrolls up.

Finally, here are some usefull data in GROM. The exact addresses may also vary according to GROM versions:

Address Contents
>0451 Default values of the 8 VDP registers.
>0459 Content of the color table, for title screen.
>04B4 Characters 32 to 95 patterns, for title screen.
>06B4 Regular upper case character patterns.
>0874 Lower case character patterns.
>16E0 Joysticks codes returned by SCAN.
>1700 Key codes returned by SCAN.
>1730 Ditto with SHIFT.
>1760 Ditto with FCTN.
>1790 Ditto with CTRL.
>17C0 Key codes in keyboard modes 1 et 2 (half-keyboards).
>2022 Error messages (with Basic bias of >60, and lenght byte).
>285C Reserved words in Basic, and corresponding tokens.

Revision 1. 3/2/00 Preliminary
Revision 2. 3/4/00 OK to release
Revision 3. 5/25/01 Corrected clock and Vss info.

Back to the TI-99/4A Tech Pages