summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99x/grom.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/ti99x/grom.cpp')
-rw-r--r--src/devices/bus/ti99x/grom.cpp269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/devices/bus/ti99x/grom.cpp b/src/devices/bus/ti99x/grom.cpp
new file mode 100644
index 00000000000..3c20eeb35d7
--- /dev/null
+++ b/src/devices/bus/ti99x/grom.cpp
@@ -0,0 +1,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>;