summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99x/grom.c
blob: 3c20eeb35d788e950b28d0e68b550262be7329da (plain) (blame)
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>;