New GROM implementation (TMC0430)

This commit is contained in:
Michael Zapf 2016-03-30 23:49:48 +02:00
parent ee6684ac85
commit ac3d3e5e41
2 changed files with 118 additions and 118 deletions

View File

@ -21,71 +21,71 @@
MO = Mode. 1=address counter access, 0=data access MO = Mode. 1=address counter access, 0=data access
GS* = GROM select. 0=select, 1=deselect GS* = GROM select. 0=select, 1=deselect
GROMs are slow ROM devices, which are interfaced via a 8-bit data bus, GROMs are slow ROM devices, which are interfaced via a 8-bit data bus,
and which include an internal address pointer which is incremented and which include an internal address pointer which is incremented
after each read. This implies that accesses are faster when reading 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. 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 GROMs are generally used to store programs written in GPL (Graphic Programming
Language): a proprietary, interpreted language from Texas Instruments which Language): a proprietary, interpreted language from Texas Instruments which
is the machine language in some kind of virtual machine that is running is the machine language in some kind of virtual machine that is running
inside the TI-99/4, TI-99/4A, and TI-99/8 computers. inside the TI-99/4, TI-99/4A, and TI-99/8 computers.
Communication with GROM is done by writing and reading data over the Communication with GROM is done by writing and reading data over the
AD0-AD7 lines. The M line determines whether the circuit will input or AD0-AD7 lines. The M line determines whether the circuit will input or
output data over the bus. For GROMs, writing data is only done for setting output data over the bus. For GROMs, writing data is only done for setting
the internal address register. The MO line must be asserted for accessing the internal address register. The MO line must be asserted for accessing
this address register; otherwise data from the memory banks can be read. this address register; otherwise data from the memory banks can be read.
Clearing MO and M means writing data, and although so-called GRAMs were Clearing MO and M means writing data, and although so-called GRAMs were
mentioned by Texas Instruments in the specifications and manuals, they mentioned by Texas Instruments in the specifications and manuals, they
were never seen. GROMs ignore this setting. were never seen. GROMs ignore this setting.
Setting the address is done by writing two bytes to the circuit with Setting the address is done by writing two bytes to the circuit with
M=0 and MO=1. In real systems, these lines are usually controlled by M=0 and MO=1. In real systems, these lines are usually controlled by
address bus lines, which maps the circuit into the memory space at specific address bus lines, which maps the circuit into the memory space at specific
addresses. addresses.
The GROM address counter is 13 bits long (8 KiB), and judging from its The GROM address counter is 13 bits long (8 KiB), and judging from its
behavior, the memory is organized as three banks of 2 KiB each: behavior, the memory is organized as three banks of 2 KiB each:
00 -> Bank 0 00 -> Bank 0
01 -> Bank 1 01 -> Bank 1
10 -> Bank 2 10 -> Bank 2
11 -> Bank 1 OR Bank 2 11 -> Bank 1 OR Bank 2
The fourth 2 KiB block seems to be a logical OR of the contents of The fourth 2 KiB block seems to be a logical OR of the contents of
bank 1 and 2. This means that one GROM delivers a maximum of 6 KiB of data. bank 1 and 2. This means that one GROM delivers a maximum of 6 KiB of data.
Nevertheless, the address counter advances into the forbidden bank, and Nevertheless, the address counter advances into the forbidden bank, and
wraps at its end to bank 0. wraps at its end to bank 0.
8 GROMs can be used to cover a whole 16-bit address space, but only 8 GROMs can be used to cover a whole 16-bit address space, but only
48 KiB of memory can be used. Each GROM has a burnt-in 3 bit identifier 48 KiB of memory can be used. Each GROM has a burnt-in 3 bit identifier
which allows us to put 8 GROMs in parallel, each one answering only which allows us to put 8 GROMs in parallel, each one answering only
when its area is currently selected. when its area is currently selected.
The address that is loaded into the address register contains two parts: The address that is loaded into the address register contains two parts:
[ I I I A A A A A A A A A A A A A ] [ I I I A A A A A A A A A A A A A ]
The I bits indicate which GROM to use. They are latched inside every GROM, The I bits indicate which GROM to use. They are latched inside every GROM,
and when the address is read from the register, they are delivered as the and when the address is read from the register, they are delivered as the
most significant three address bits. most significant three address bits.
All GROMs are wired in parallel, and only the circuits whose ID matches the All GROMs are wired in parallel, and only the circuits whose ID matches the
prefix actually delivers the data to the outside. Apart from that, all prefix actually delivers the data to the outside. Apart from that, all
GROMs perform the same internal operations. This means that each one of GROMs perform the same internal operations. This means that each one of
them holds the same address value and delivers it on request. them holds the same address value and delivers it on request.
Timing. GROMs have a CPU-bound operation phase and a non-CPU-bound operation Timing. GROMs have a CPU-bound operation phase and a non-CPU-bound operation
phase. After being selected, the READY line is immediately lowered, and phase. After being selected, the READY line is immediately lowered, and
it raises as soon as the data is ready for access. After that, a prefetch it raises as soon as the data is ready for access. After that, a prefetch
is done to get the next data byte from the memory banks, advancing the is done to get the next data byte from the memory banks, advancing the
address counter. This prefetch is also done when loading the address. address counter. This prefetch is also done when loading the address.
Hence, reading the address register will always deliver a value increased Hence, reading the address register will always deliver a value increased
by one. by one.
[1] Michael L. Bunyard: Hardware Manual for the Texas Instruments 99/4A Home Computer, section 2.5 [1] Michael L. Bunyard: Hardware Manual for the Texas Instruments 99/4A Home Computer, section 2.5
Michael Zapf, August 2010 Michael Zapf, August 2010
January 2012: rewritten as class January 2012: rewritten as class
@ -104,54 +104,54 @@
Constructor. Constructor.
*/ */
tmc0430_device::tmc0430_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) tmc0430_device::tmc0430_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, TMC0430, "TMC0430 device (GROM)", tag, owner, clock, "grom", __FILE__), : device_t(mconfig, TMC0430, "TMC0430 device (GROM)", tag, owner, clock, "grom", __FILE__),
m_gromready(*this), m_gromready(*this),
m_current_clock_level(CLEAR_LINE), m_current_clock_level(CLEAR_LINE),
m_current_ident(0), m_current_ident(0),
m_phase(0), m_phase(0),
m_address_mode(false), m_address_mode(false),
m_read_mode(false), m_read_mode(false),
m_selected(false), m_selected(false),
m_address_lowbyte(false), m_address_lowbyte(false),
m_regionname(nullptr), m_regionname(nullptr),
m_ident(0), m_ident(0),
m_address(0), m_address(0),
m_buffer(0), m_buffer(0),
m_memptr(nullptr) m_memptr(nullptr)
{ {
} }
// ======================================================================== // ========================================================================
// Select lines. We have three lines for the direction, the mode, and // Select lines. We have three lines for the direction, the mode, and
// the selection. You can call one function for each of them, but it is // the selection. You can call one function for each of them, but it is
// recommended to use the combined set_lines function, in particular when // recommended to use the combined set_lines function, in particular when
// there are lots of GROMs (see, for example, TI-99/8) // there are lots of GROMs (see, for example, TI-99/8)
// ======================================================================== // ========================================================================
/* /*
Direction. When ASSERTed, GROM is set to be read by CPU. Direction. When ASSERTed, GROM is set to be read by CPU.
*/ */
WRITE_LINE_MEMBER( tmc0430_device::m_line ) WRITE_LINE_MEMBER( tmc0430_device::m_line )
{ {
m_read_mode = (state==ASSERT_LINE); m_read_mode = (state==ASSERT_LINE);
if (TRACE_LINE) logerror("GROM %d dir %s\n", m_ident>>13, m_read_mode? "READ" : "WRITE"); if (TRACE_LINE) logerror("GROM %d dir %s\n", m_ident>>13, m_read_mode? "READ" : "WRITE");
} }
/* /*
Mode. When ASSERTed, the address counter will be accessed (read or write). Mode. When ASSERTed, the address counter will be accessed (read or write).
*/ */
WRITE_LINE_MEMBER( tmc0430_device::mo_line ) WRITE_LINE_MEMBER( tmc0430_device::mo_line )
{ {
m_address_mode = (state==ASSERT_LINE); m_address_mode = (state==ASSERT_LINE);
if (TRACE_LINE) logerror("GROM %d mode %s\n", m_ident>>13, m_address_mode? "ADDR" : "DATA"); if (TRACE_LINE) logerror("GROM %d mode %s\n", m_ident>>13, m_address_mode? "ADDR" : "DATA");
} }
/* /*
Select. When ASSERTed, the read/write operation is started. Select. When ASSERTed, the read/write operation is started.
*/ */
WRITE_LINE_MEMBER( tmc0430_device::gsq_line ) WRITE_LINE_MEMBER( tmc0430_device::gsq_line )
{ {
if (state==ASSERT_LINE && !m_selected) // check for edge if (state==ASSERT_LINE && !m_selected) // check for edge
{ {
if (TRACE_READY) logerror("GROM %d selected, pulling down READY\n", m_ident>>13); if (TRACE_READY) logerror("GROM %d selected, pulling down READY\n", m_ident>>13);
m_gromready(CLEAR_LINE); m_gromready(CLEAR_LINE);
@ -161,20 +161,20 @@ WRITE_LINE_MEMBER( tmc0430_device::gsq_line )
} }
/* /*
Combined select lines. Avoids separate calls to the chip. Combined select lines. Avoids separate calls to the chip.
Address: Address:
0 -> MO=0, M=0 0 -> MO=0, M=0
1 -> MO=0, M=1 1 -> MO=0, M=1
2 -> MO=1, M=0 2 -> MO=1, M=0
3 -> MO=1, M=1 3 -> MO=1, M=1
Data: gsq line (ASSERT, CLEAR) Data: gsq line (ASSERT, CLEAR)
*/ */
WRITE8_MEMBER( tmc0430_device::set_lines ) WRITE8_MEMBER( tmc0430_device::set_lines )
{ {
m_read_mode = ((offset & GROM_M_LINE)!=0); m_read_mode = ((offset & GROM_M_LINE)!=0);
m_address_mode = ((offset & GROM_MO_LINE)!=0); m_address_mode = ((offset & GROM_MO_LINE)!=0);
if (data!=CLEAR_LINE && !m_selected) // check for edge if (data!=CLEAR_LINE && !m_selected) // check for edge
{ {
if (TRACE_READY) logerror("GROM %d selected, pulling down READY\n", m_ident>>13); if (TRACE_READY) logerror("GROM %d selected, pulling down READY\n", m_ident>>13);
m_gromready(CLEAR_LINE); m_gromready(CLEAR_LINE);
@ -184,20 +184,20 @@ WRITE8_MEMBER( tmc0430_device::set_lines )
} }
/* /*
Clock in. Clock in.
Note about the GREADY line: Note about the GREADY line:
Inside the TI-99/4A console, the GREADY outputs of all GROMs are directly Inside the TI-99/4A console, the GREADY outputs of all GROMs are directly
connected in parallel and pulled up. This implies that the GROMs are connected in parallel and pulled up. This implies that the GROMs are
open-drain outputs pulling down. There are two options: open-drain outputs pulling down. There are two options:
- Only the currently addressed GROM pulls down the line; all others keep - Only the currently addressed GROM pulls down the line; all others keep
their output open. their output open.
- All GROMs act strictly in parallel. In the case that some circuits are - All GROMs act strictly in parallel. In the case that some circuits are
slightly out of sync, the GREADY line goes up when the last circuit releases slightly out of sync, the GREADY line goes up when the last circuit releases
the line. the line.
For the emulation we may assume that all GROMs at the same clock line For the emulation we may assume that all GROMs at the same clock line
raise their outputs synchronously. raise their outputs synchronously.
*/ */
WRITE_LINE_MEMBER( tmc0430_device::gclock_in ) WRITE_LINE_MEMBER( tmc0430_device::gclock_in )
{ {
@ -212,29 +212,29 @@ WRITE_LINE_MEMBER( tmc0430_device::gclock_in )
return; return;
if (TRACE_CLOCK) logerror("GROMCLK in, phase=%d, m_add=%d\n", m_phase, m_address); if (TRACE_CLOCK) logerror("GROMCLK in, phase=%d, m_add=%d\n", m_phase, m_address);
switch (m_phase) switch (m_phase)
{ {
case 0: case 0:
break; break;
case 1: case 1:
// Get the next value into the buffer // Get the next value into the buffer
// 000b b000 0000 0000 // 000b b000 0000 0000
baddr = m_address & 0x07ff; baddr = m_address & 0x07ff;
bank = (m_address & 0x1800)>>11; bank = (m_address & 0x1800)>>11;
// This is a theory how the behavior of the GROM can be explained // This is a theory how the behavior of the GROM can be explained
// We don't have decapped GROMs (yet) // We don't have decapped GROMs (yet)
m_buffer = 0; m_buffer = 0;
if (bank == 0) m_buffer |= m_memptr[baddr]; if (bank == 0) m_buffer |= m_memptr[baddr];
if (bank & 1) m_buffer |= m_memptr[baddr | 0x0800]; if (bank & 1) m_buffer |= m_memptr[baddr | 0x0800];
if (bank & 2) m_buffer |= m_memptr[baddr | 0x1000]; if (bank & 2) m_buffer |= m_memptr[baddr | 0x1000];
if (TRACE_ADDRESS) logerror("G>%04x\n", m_address); if (TRACE_ADDRESS) logerror("G>%04x\n", m_address);
if (TRACE_DETAIL) logerror("GROM %d preload %04x (bank %d) -> %02x\n", m_ident>>13, m_address, bank, m_buffer); if (TRACE_DETAIL) logerror("GROM %d preload %04x (bank %d) -> %02x\n", m_ident>>13, m_address, bank, m_buffer);
m_phase = 3; m_phase = 3;
break; break;
case 2: // Do nothing if other ident case 2: // Do nothing if other ident
m_phase = 3; m_phase = 3;
break; break;
case 3: case 3:
@ -245,7 +245,7 @@ WRITE_LINE_MEMBER( tmc0430_device::gclock_in )
// In read mode, READY must have already been raised; in write mode, the ready line is still low // In read mode, READY must have already been raised; in write mode, the ready line is still low
m_phase = m_read_mode? 0 : 7; m_phase = m_read_mode? 0 : 7;
break; break;
case 4: // Starting here when the chip is selected case 4: // Starting here when the chip is selected
m_phase = 5; m_phase = 5;
break; break;
@ -266,19 +266,19 @@ WRITE_LINE_MEMBER( tmc0430_device::gclock_in )
} }
/* /*
Read operation. For MO=Address, delivers the address register (and destroys its contents). Read operation. For MO=Address, delivers the address register (and destroys its contents).
For MO=Data, delivers the byte inside the buffer and prefetches the next one. For MO=Data, delivers the byte inside the buffer and prefetches the next one.
*/ */
READ8Z_MEMBER( tmc0430_device::readz ) READ8Z_MEMBER( tmc0430_device::readz )
{ {
if (!m_selected) return; if (!m_selected) return;
if (m_address_mode) if (m_address_mode)
{ {
// Address reading is destructive // Address reading is destructive
*value = (m_address & 0xff00)>>8; *value = (m_address & 0xff00)>>8;
UINT8 lsb = (m_address & 0x00ff); UINT8 lsb = (m_address & 0x00ff);
m_address = (lsb << 8) | lsb; // see [1], section 2.5.3 m_address = (lsb << 8) | lsb; // see [1], section 2.5.3
if (TRACE_DETAIL) logerror("GROM %d return address %02x\n", m_ident>>13, *value); if (TRACE_DETAIL) logerror("GROM %d return address %02x\n", m_ident>>13, *value);
} }
else else
@ -289,7 +289,7 @@ READ8Z_MEMBER( tmc0430_device::readz )
*value = m_buffer; *value = m_buffer;
m_phase = 1; m_phase = 1;
} }
else else
{ {
m_phase = 2; m_phase = 2;
} }
@ -298,13 +298,13 @@ READ8Z_MEMBER( tmc0430_device::readz )
} }
/* /*
Write operation. For MO=Address, shifts value in the address register Write operation. For MO=Address, shifts value in the address register
by 8 bits and copies the new value into the low byte. After every two by 8 bits and copies the new value into the low byte. After every two
write operations, prefetches the byte from the new location. For MO=Data, write operations, prefetches the byte from the new location. For MO=Data,
do nothing, because GRAMs were never seen in the wild. do nothing, because GRAMs were never seen in the wild.
This operation occurs in parallel to phase 4. The real GROM will pick up This operation occurs in parallel to phase 4. The real GROM will pick up
the value from the data bus some phases later. the value from the data bus some phases later.
*/ */
WRITE8_MEMBER( tmc0430_device::write ) WRITE8_MEMBER( tmc0430_device::write )
{ {
@ -312,19 +312,19 @@ WRITE8_MEMBER( tmc0430_device::write )
if (m_address_mode) if (m_address_mode)
{ {
m_address = ((m_address << 8) | data); // [1], section 2.5.7 m_address = ((m_address << 8) | data); // [1], section 2.5.7
m_current_ident = m_address & 0xe000; m_current_ident = m_address & 0xe000;
if (TRACE_DETAIL) logerror("GROM %d new address %04x (%s)\n", m_ident>>13, m_address, m_address_lowbyte? "complete" : "incomplete"); if (TRACE_DETAIL) logerror("GROM %d new address %04x (%s)\n", m_ident>>13, m_address, m_address_lowbyte? "complete" : "incomplete");
// Toggle the lowbyte flag // Toggle the lowbyte flag
m_address_lowbyte = !m_address_lowbyte; m_address_lowbyte = !m_address_lowbyte;
if (!m_address_lowbyte) if (!m_address_lowbyte)
{ {
// Do a prefetch if addressed, else increase your address // Do a prefetch if addressed, else increase your address
m_phase = (m_current_ident == m_ident)? 1 : 2; m_phase = (m_current_ident == m_ident)? 1 : 2;
} }
} }
// TODO: Check the duration of the access. In sum, both write accesses show correct timing, // TODO: Check the duration of the access. In sum, both write accesses show correct timing,
// but in this implementation, the first one (!m_address_lowbyte) is slower (phase=4-5-6-7-0) while the // but in this implementation, the first one (!m_address_lowbyte) is slower (phase=4-5-6-7-0) while the
// second is faster (phase=1-3-7-0 or 1-3-0) // second is faster (phase=1-3-7-0 or 1-3-0)

View File

@ -35,60 +35,60 @@ public:
DECLARE_READ8Z_MEMBER(readz); DECLARE_READ8Z_MEMBER(readz);
DECLARE_WRITE8_MEMBER(write); DECLARE_WRITE8_MEMBER(write);
DECLARE_WRITE_LINE_MEMBER(m_line); DECLARE_WRITE_LINE_MEMBER(m_line);
DECLARE_WRITE_LINE_MEMBER(mo_line); DECLARE_WRITE_LINE_MEMBER(mo_line);
DECLARE_WRITE_LINE_MEMBER(gsq_line); DECLARE_WRITE_LINE_MEMBER(gsq_line);
DECLARE_WRITE_LINE_MEMBER(gclock_in); DECLARE_WRITE_LINE_MEMBER(gclock_in);
DECLARE_WRITE8_MEMBER( set_lines ); DECLARE_WRITE8_MEMBER( set_lines );
static void set_region_and_ident(device_t &device, const char *regionname, int offset, int ident) static void set_region_and_ident(device_t &device, const char *regionname, int offset, int ident)
{ {
downcast<tmc0430_device &>(device).m_regionname = regionname; downcast<tmc0430_device &>(device).m_regionname = regionname;
downcast<tmc0430_device &>(device).m_offset = offset; downcast<tmc0430_device &>(device).m_offset = offset;
downcast<tmc0430_device &>(device).m_ident = ident<<13; downcast<tmc0430_device &>(device).m_ident = ident<<13;
} }
int debug_get_address(); int debug_get_address();
protected: protected:
void device_start(void) override; void device_start(void) override;
void device_reset(void) override; void device_reset(void) override;
private: private:
// Ready callback. This line is usually connected to the READY pin of the CPU. // Ready callback. This line is usually connected to the READY pin of the CPU.
devcb_write_line m_gromready; devcb_write_line m_gromready;
// Clock line level // Clock line level
line_state m_current_clock_level; line_state m_current_clock_level;
// Currently active GROM ident // Currently active GROM ident
int m_current_ident; int m_current_ident;
// Phase of the state machine // Phase of the state machine
int m_phase; int m_phase;
// Address or data mode? // Address or data mode?
bool m_address_mode; bool m_address_mode;
// Reading or writing? // Reading or writing?
bool m_read_mode; bool m_read_mode;
// Selected? // Selected?
bool m_selected; bool m_selected;
// Toggle for address loading // Toggle for address loading
bool m_address_lowbyte; bool m_address_lowbyte;
// Region name // Region name
const char* m_regionname; const char* m_regionname;
// Offset in the region. We cannot rely on the ident because the GROMs // Offset in the region. We cannot rely on the ident because the GROMs
// in the cartridges begin with ident 3, but start at the beginning of their region. // in the cartridges begin with ident 3, but start at the beginning of their region.
int m_offset; int m_offset;
// Identification of this GROM (0-7 <<13) // Identification of this GROM (0-7 <<13)
int m_ident; int m_ident;
@ -107,5 +107,5 @@ private:
MCFG_DEVICE_ADD(_tag, TMC0430, 0) \ MCFG_DEVICE_ADD(_tag, TMC0430, 0) \
tmc0430_device::set_region_and_ident(*device, _region, _offset, _ident); \ tmc0430_device::set_region_and_ident(*device, _region, _offset, _ident); \
tmc0430_device::set_ready_wr_callback(*device, DEVCB_##_ready); tmc0430_device::set_ready_wr_callback(*device, DEVCB_##_ready);
#endif #endif