1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/***************************************************************************
GROM emulation (aka TMC0430)
+----+--+----+
AD7 |1 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
+------------+
GR = GROM Ready. Should be connected to processor's READY/HOLD*.
GRC = GROM clock. Typically in the range of 400-500 kHz.
M = Direction. 1=read, 0=write
MO = Mode. 1=address counter access, 0=data access
GS* = GROM select. 0=select, 1=deselect
GROMs are slow ROM devices, which are
interfaced via a 8-bit data bus, and include an internal address pointer
which is incremented after each read. This implies that accesses are
faster when reading consecutive bytes, although the address pointer can be
read and written at any time.
GROMs are generally used to store programs written in GPL (Graphic Programming
Language): a proprietary, interpreted language. The GPL interpreter takes
most space of the TI-99/4A system ROMs.
Both TI-99/4 and TI-99/4A include three GROMs, with some start-up code,
system routines and TI-Basic. TI99/4 includes an additional Equation
Editor. According to the preliminary schematics found on ftp.whtech.com,
TI-99/8 includes the three standard GROMs and 16 GROMs for the UCSD
p-system. TI99/2 does not include GROMs at all, and was not designed to
support any, although it should be relatively easy to create an expansion
card with the GPL interpreter and a /4a cartridge port.
Communication with GROM is done by writing and reading data over the
AD0-AD7 lines. Within the TI-99 systems, the address bus is decoded for
the M, GS*, and MO lines: Writing a byte to address 9c02 asserts the GS* and
MO line, and clears the M line, which means the transmitted byte is put into
the internal address register. Two bytes must be written to set up the
complete address.
It was obviously planned to offer GRAM circuits as well, since the
programming manuals refer to writing to a special address, clearing the MO
line. Although the TI-99 systems reserve a port in the memory space, no one
has ever seen a GRAM circuit in the wild. However, third-party products like
HSGPL or GRAM Kracker simulate GRAMs using conventional RAM with some
addressing circuitry, usually in a custom chip.
Each GROM is logically 8 KiB long. Original TI-built GROM are 6 KiB long;
the extra 2kb can be read, and follow the following formula:
GROM[0x1800+offset] = GROM[0x0800+offset] | GROM[0x1000+offset];
(sounds like address decoding is incomplete - we are lucky we don't burn
any silicon when doing so... Needless to say, some hackers simulated 8kb
GRAMs and GROMs with normal RAM/PROM chips and glue logic.)
The address pointer is incremented after each GROM operation, but it will
always remain within the bounds of the currently selected GROM (e.g. after
0x3fff comes 0x2000).
Since address are 16-bit long, you can have up to 8 GROMs. Accordingly,
a cartridge may include up to 5 GROMs.
Every GROM has an internal ID which represents the high-order three
address bits. The address counter can be set to any value from 0
to 0xffff; the GROM will only react when selected and when the current
address counter's high-order bits match the ID of the chip.
Example: When the ID is 6, the GROM will react when the address
counter contains a value from 0xc000 to 0xdfff.
CHECK: Reading the address increases the counter only once. The first access
returns the MSB, the second (and all following accesses) return the LSB.
Michael Zapf, August 2010
January 2012: rewritten as class
***************************************************************************/
#include "emu.h"
#include "grom.h"
#define TRACE_ADDRESS 0
/*
Constructor.
*/
ti99_grom_device::ti99_grom_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: bus8z_device(mconfig, GROM, "TI-99 GROM device", tag, owner, clock, "ti99_grom", __FILE__),
m_gromready(*this)
{
}
/*
Reading from the chip. Represents an access with M=1, GS*=0. The MO bit is
defined by the offset (0 or 1). This is the enhanced read function with
Z state.
*/
READ8Z_MEMBER( ti99_grom_device::readz )
{
// Prevent debugger access
if (space.debugger_access()) return;
if (offset & 2)
{
// GROMs generally answer the address read request
// (important if GROM simulators do not serve the request but rely on
// the console GROMs) so we don't check the ident
/* When reading, reset the hi/lo flag byte for writing. */
/* TODO: Verify this with a real machine. */
m_waddr_LSB = false;
/* Address reading is done in two steps; first, the high byte */
/* is transferred, then the low byte. */
if (m_raddr_LSB)
{
/* second pass */
*value = m_address & 0x00ff;
m_raddr_LSB = false;
}
else
{
/* first pass */
*value = (m_address & 0xff00)>>8;
m_raddr_LSB = true;
}
}
else
{
if (((m_address >> 13)&0x07)==m_ident)
{
// GROMs are buffered. Data is retrieved from a buffer,
// while the buffer is replaced with the next cell content.
if (TRACE_ADDRESS) if (m_ident==0) logerror("grom0: %04x = %02x\n", m_address-1, m_buffer);
*value = m_buffer;
// Get next value, put it in buffer. Note that the GROM
// wraps at 8K boundaries.
UINT16 addr = m_address-(m_ident<<13);
if (m_size == 0x1800 && ((m_address&0x1fff)>=0x1800))
m_buffer = m_memptr[addr-0x1000] | m_memptr[addr-0x0800];
else
m_buffer = m_memptr[addr];
}
// Note that all GROMs update their address counter.
// TODO: Check this on a real console
m_address = (m_address & 0xE000) | ((m_address + 1)&0x1FFF);
// Reset the read and write address flipflops.
m_raddr_LSB = m_waddr_LSB = false;
// Maybe the timer is also required for address reading/setting, but
// we don't have such technical details on GROMs.
clear_ready();
}
}
/*
Writing to the chip. Represents an access with M=0, GS*=0. The MO bit is
defined by the offset (0 or 1).
*/
WRITE8_MEMBER( ti99_grom_device::write )
{
// Prevent debugger access
if (space.debugger_access()) return;
if (offset & 2)
{
/* write GROM address */
/* see comments above */
m_raddr_LSB = false;
/* Implements the internal flipflop. */
/* The Editor/Assembler manuals says that the current address */
/* plus one is returned. This effect is properly emulated */
/* by using a read-ahead buffer. */
if (m_waddr_LSB)
{
/* Accept low byte (2nd write) */
m_address = (m_address & 0xFF00) | data;
/* Setting the address causes a new prefetch */
if (is_selected())
{
m_buffer = m_memptr[m_address-(m_ident<<13)];
}
m_waddr_LSB = false;
if (TRACE_ADDRESS) if (m_ident==0) logerror("grom0: %04x\n", m_address);
}
else
{
/* Accept high byte (1st write). Do not advance the address conter. */
m_address = (data << 8) | (m_address & 0xFF);
m_waddr_LSB = true;
return;
}
}
else
{
/* write GRAM data */
if ((((m_address >> 13)&0x07)==m_ident) && m_writable)
{
UINT16 write_addr;
// We need to rewind by 1 because the read address has already advanced.
// However, do not change the address counter!
write_addr = (m_address & 0xE000) | ((m_address - 1)&0x1FFF);
// UINT16 addr = m_address-(m_ident<<13);
if (m_size > 0x1800 || ((m_address&0x1fff)<0x1800))
m_memptr[write_addr-(m_ident<<13)] = data;
}
m_raddr_LSB = m_waddr_LSB = false;
clear_ready();
}
m_address = (m_address & 0xE000) | ((m_address + 1)&0x1FFF);
}
/*
Timing. We assume that each data read results in READY going down for
one cycle at the given frequency.
*/
void ti99_grom_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
m_gromready(ASSERT_LINE);
}
void ti99_grom_device::clear_ready()
{
m_gromready(CLEAR_LINE);
m_timer->adjust(attotime::from_hz(m_clockrate));
}
/***************************************************************************
DEVICE FUNCTIONS
***************************************************************************/
void ti99_grom_device::device_start(void)
{
const ti99grom_config *conf = reinterpret_cast<const ti99grom_config *>(static_config());
m_memptr = owner()->memregion(conf->regionname)->base();
assert (m_memptr!=NULL);
m_memptr += conf->offset_reg;
m_size = conf->size;
m_clockrate = conf->clockrate;
m_writable = conf->writable;
m_ident = conf->ident;
m_gromready.resolve_safe();
m_timer = timer_alloc(0);
}
void ti99_grom_device::device_reset(void)
{
m_address = 0;
m_raddr_LSB = false;
m_waddr_LSB = false;
m_buffer = 0;
}
const device_type GROM = &device_creator<ti99_grom_device>;
|