diff options
Diffstat (limited to 'src/devices/bus/ti99x/datamux.c')
-rw-r--r-- | src/devices/bus/ti99x/datamux.c | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/src/devices/bus/ti99x/datamux.c b/src/devices/bus/ti99x/datamux.c new file mode 100644 index 00000000000..c9db97f1788 --- /dev/null +++ b/src/devices/bus/ti99x/datamux.c @@ -0,0 +1,489 @@ +// license:LGPL-2.1+ +// copyright-holders:Michael Zapf +/*************************************************************************** + + TI-99/4(A) databus multiplexer circuit + + The DMUX is used to convert the 16-bit databus of the TMS9900 into + an 8-bit databus. The processor writes a 16 bit word which is split + by this circuit into two bytes that are sent subsequently over the 8-bit bus. + In the opposite direction, one 16-bit read request from the CPU is + translated into two 8-bit read requests (odd address / even address) from + this datamux. Its 8-bit latch (LS373) holds the first (odd address) byte, + while the datamux puts the CPU on hold, gets the second byte, + and routes that second byte to the D0-D7 lines, while the latch now puts + the first byte on D8-D15. Since we get two memory accesses each time, + there are twice as many wait states than for a direct 16-bit access + (order LSB, MSB). + + In addition, since the TMS 9900 also supports byte operations, all write + operations are automatically preceded by a read operation, so this adds even + more delays. + + Within the TI-99/4(A) console, only the internal ROM and the small internal + RAM ("scratch pad RAM") are directly connected to the 16-bit bus. All other + devices (video, audio, speech, GROM, and the complete P-Box system are + connected to the datamux. + + The TMS9995 which is used in the Geneve has an internal multiplex, and + the byte order is reversed: MSB, LSB + + ROM = 4K * 16 bit (8 KiB) system ROM (kind of BIOS, plus the GPL interpreter) + RAM = 128 * 16 bit (256 byte) system RAM ("scratch pad") + + Many users (me too) used to solder a 16K * 16 bit (32 KiB) SRAM circuit into + the console, before the datamux, decoded to 0x2000-0x3fff and 0xa000-0xffff. + (This expansion was also called 0-waitstate, since it could be accessed + with the full databus width, and the datamux did not create waitstates.) + + +---+ +-------+ + | |===##========##== D0-D7 ==========##===============|TMS9918| Video + | | || || || +-------+ + | T | +-----+ +-----+ LS245 +----+ + | M | | ROM | | RAM | +----+ + | S | +-----+ +-----+ || | : + | |---||-||-----||-||----------------||-|---------------------: + | 9 | || || A0 - A14 || | A0 : Sound + | 9 |---||--------||-------------------||-|----------+ -A15 : GROM + | 0 | || || LS373 +-+ || | +----A15-+----------: Cartridges + | 0 | || || ##========|<|===## | | : Speech + | | || || || +-+ +-+ || | | : Expansion + | |===## D8-D15 ##===##==|>|=====|===##=|=|=========== D0-D7 =: cards + +---+ +-+ | | | : + ^ LS244| | | | + | | +--+---+-++ + | +--------| DMUX |---------------<--: READY + +--- READY -------------------------+------+ + + Databus width + :------------- 16 bit ---------------|---------- 8 bit -----: + + A0=MSB; A15=LSB + D0=MSB; D15=LSB + + We integrate the 16 bit memory expansion in this datamux component + (pretending that the memory expansion was soldered on top of the datamux) + + January 2012: Rewritten as class + +***************************************************************************/ + +#include "emu.h" +#include "datamux.h" + +/* + Constructor +*/ +ti99_datamux_device::ti99_datamux_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) +: device_t(mconfig, DATAMUX, "Databus multiplexer", tag, owner, clock, "ti99_datamux", __FILE__), + m_ready(*this) { } + +#define TRACE_READY 0 +#define TRACE_ACCESS 0 +#define TRACE_ADDRESS 0 +#define TRACE_WAITCOUNT 0 +#define TRACE_SETUP 0 + +/*************************************************************************** + DEVICE ACCESSOR FUNCTIONS +***************************************************************************/ + +void ti99_datamux_device::read_all(address_space& space, UINT16 addr, UINT8 *target) +{ + attached_device *dev = m_devices.first(); + + // Reading the odd address first (addr+1) + while (dev != NULL) + { + if (dev->m_config->write_select != 0xffff) // write-only + { + if ((addr & dev->m_config->address_mask)==dev->m_config->select) + { + // Cast to the bus8z_device (see ti99defs.h) + bus8z_device *devz = static_cast<bus8z_device *>(dev->m_device); + devz->readz(space, addr, target); + } + // hope we don't have two devices answering... + // consider something like a logical OR and maybe some artificial smoke + } + dev = dev->m_next; + } +} + +void ti99_datamux_device::write_all(address_space& space, UINT16 addr, UINT8 value) +{ + attached_device *dev = m_devices.first(); + while (dev != NULL) + { + if ((addr & dev->m_config->address_mask)==(dev->m_config->select | dev->m_config->write_select)) + { + bus8z_device *devz = static_cast<bus8z_device *>(dev->m_device); + devz->write(space, addr, value); + } + dev = dev->m_next; + } +} + +void ti99_datamux_device::setaddress_all(address_space& space, UINT16 addr) +{ + attached_device *dev = m_devices.first(); + while (dev != NULL) + { + if ((addr & dev->m_config->address_mask)==(dev->m_config->select | dev->m_config->write_select)) + { + bus8z_device *devz = static_cast<bus8z_device *>(dev->m_device); + devz->setaddress_dbin(space, addr, m_read_mode? ASSERT_LINE : CLEAR_LINE); + } + dev = dev->m_next; + } +} + +/* + Special debugger access; these routines have no influence on the wait + state generation. +*/ +UINT16 ti99_datamux_device::debugger_read(address_space& space, UINT16 addr) +{ + UINT16 base32k = 0; + UINT8 lval, hval; + + UINT16 addrb = addr << 1; + if (m_use32k) + { + if ((addrb & 0xe000)==0x2000) base32k = 0x1000; + if (((addrb & 0xe000)==0xa000) || ((addrb & 0xc000)==0xc000)) base32k = 0x4000; + } + if (base32k != 0) + { + return m_ram16b[addr - base32k]; + } + else + { + lval = hval = 0; + read_all(space, addrb+1, &lval); + read_all(space, addrb, &hval); + return ((hval << 8)&0xff00) | (lval & 0xff); + } +} + +void ti99_datamux_device::debugger_write(address_space& space, UINT16 addr, UINT16 data) +{ + UINT16 base32k = 0; + + UINT16 addrb = addr << 1; + if (m_use32k) + { + if ((addrb & 0xe000)==0x2000) base32k = 0x1000; + if (((addrb & 0xe000)==0xa000) || ((addrb & 0xc000)==0xc000)) base32k = 0x4000; + } + if (base32k != 0) + { + m_ram16b[addr - base32k] = data; + } + else + { + write_all(space, addrb+1, data & 0xff); + write_all(space, addrb, (data >> 8) & 0xff); + } +} + +/* + Read access. We are using two loops because the delay between both + accesses must not occur within the loop. So we have one access on the bus, + a delay, and then the second access (each one with possibly many attached + devices) +*/ +READ16_MEMBER( ti99_datamux_device::read ) +{ + // Care for debugger + if (space.debugger_access()) + { + return debugger_read(space, offset); + } + + // Looks ugly, but this is close to the real thing. If the 16bit + // memory expansion is installed in the console, and the access hits its + // space, just respond to the memory access and don't bother the + // datamux in any way. In particular, do not make the datamux insert wait + // states. + + if (m_base32k != 0) + { + UINT16 reply = m_ram16b[offset-m_base32k]; + return reply & mem_mask; + } + else + { + // The byte from the odd address has already been read into the latch + // Reading the even address now (addr) + UINT8 hbyte = 0; + read_all(space, m_addr_buf, &hbyte); + if (TRACE_ACCESS) logerror("datamux: read even byte from address %04x -> %02x\n", m_addr_buf, hbyte); + + return ((hbyte<<8) | m_latch) & mem_mask; + } +} + +/* + Write access. +*/ +WRITE16_MEMBER( ti99_datamux_device::write ) +{ + // Addresses below 0x2000 are ROM and should be handled in the address map + // by the ROM entry, but as the write handler for ROM is not mapped, we end up + // here when there are invalid accesses, and this will mess up everything. + if (offset < 0x1000) return; + + if (space.debugger_access()) + { + debugger_write(space, offset, data); + return; + } + + // Handle the internal 32K expansion + if (m_base32k != 0) + { + m_ram16b[offset-m_base32k] = data; + } + else + { + // Otherwise the datamux is in normal operation which means it puts + // the even value into the latch and outputs the odd value now. + m_latch = (data >> 8) & 0xff; + + // write odd byte + if (TRACE_ACCESS) logerror("datamux: write odd byte to address %04x <- %02x\n", m_addr_buf+1, data & 0xff); + write_all(space, m_addr_buf+1, data & 0xff); + } +} + +/* + Called when the memory access starts by setting the address bus. From that + point on, we suspend the CPU until all operations are done. +*/ +SETOFFSET_MEMBER( ti99_datamux_device::setoffset ) +{ + if (TRACE_ADDRESS) logerror("datamux: set address %04x\n", offset << 1); + // Initialize counter + // 1 cycle for loading into the datamux + // 2 subsequent wait states (LSB) + // 2 subsequent wait states (MSB) + // clock cycle 6 is the nominal follower of the last wait state + m_waitcount = 5; + m_addr_buf = offset << 1; + m_spacep = &space; + + m_base32k = 0; + if (m_use32k) + { + if ((m_addr_buf & 0xe000)==0x2000) m_base32k = 0x1000; + if (((m_addr_buf & 0xe000)==0xa000) || ((m_addr_buf & 0xc000)==0xc000)) m_base32k = 0x4000; + } + + // Suspend the CPU if not using the 32K + if (m_base32k == 0) + { + // propagate the setaddress operation + // First the odd address + setaddress_all(space, m_addr_buf+1); + m_muxready = CLEAR_LINE; + ready_join(); + } + else m_waitcount = 0; +} + +/* + The datamux is connected to the clock line in order to operate + the wait state counter and to read/write the bytes. +*/ +WRITE_LINE_MEMBER( ti99_datamux_device::clock_in ) +{ + // return immediately if the datamux is currently inactive + if (m_waitcount>0) + { + if (TRACE_WAITCOUNT) logerror("datamux: wait count %d\n", m_waitcount); + if (m_sysready==CLEAR_LINE) + { + if (TRACE_READY) logerror("datamux: stalled due to external READY=0\n"); + return; + } + if (m_read_mode) + { + // Reading + if (state==ASSERT_LINE) + { // raising edge + m_waitcount--; + if (m_waitcount==0) + { + m_muxready = ASSERT_LINE; + ready_join(); + } + if (m_waitcount==2) + { + // read odd byte + read_all(*m_spacep, m_addr_buf+1, &m_latch); + if (TRACE_ACCESS) logerror("datamux: read odd byte from address %04x -> %02x\n", m_addr_buf+1, m_latch); + // do the setaddress for the even address + setaddress_all(*m_spacep, m_addr_buf); + } + } + } + else + { + if (state==ASSERT_LINE) + { // raising edge + m_waitcount--; + if (m_waitcount==0) + { + m_muxready = ASSERT_LINE; + ready_join(); + } + } + else + { // falling edge + if (m_waitcount==2) + { + // do the setaddress for the even address + setaddress_all(*m_spacep, m_addr_buf); + // write even byte + if (TRACE_ACCESS) logerror("datamux: write even byte to address %04x <- %02x\n", m_addr_buf, m_latch); + write_all(*m_spacep, m_addr_buf, m_latch); + } + } + } + } +} + +/* + Combine the external (sysready) and the own (muxready) READY states. +*/ +void ti99_datamux_device::ready_join() +{ + m_ready((m_sysready==CLEAR_LINE || m_muxready==CLEAR_LINE)? CLEAR_LINE : ASSERT_LINE); +} + +WRITE_LINE_MEMBER( ti99_datamux_device::dbin_in ) +{ + m_read_mode = (state==ASSERT_LINE); + if (TRACE_ADDRESS) logerror("datamux: data bus in = %d\n", m_read_mode? 1:0 ); +} + +WRITE_LINE_MEMBER( ti99_datamux_device::ready_line ) +{ + if (TRACE_READY) + { + if (state != m_sysready) logerror("datamux: READY line from PBox = %d\n", state); + } + m_sysready = (line_state)state; + // Also propagate to CPU via driver + ready_join(); +} + +/*************************************************************************** + DEVICE LIFECYCLE FUNCTIONS +***************************************************************************/ + +void ti99_datamux_device::device_start(void) +{ + m_ram16b = NULL; + m_muxready = ASSERT_LINE; + m_ready.resolve(); +} + +void ti99_datamux_device::device_stop(void) +{ + if (m_ram16b) global_free_array(m_ram16b); +} + +void ti99_datamux_device::device_reset(void) +{ + const datamux_config *conf = reinterpret_cast<const datamux_config *>(static_config()); + + const dmux_device_list_entry *list = conf->devlist; + + m_cpu = machine().device("maincpu"); + // m_space = &m_cpu->memory().space(AS_PROGRAM); + + m_devices.reset(); // clear the list + m_use32k = (ioport("RAM")->read()==1); + + // better use a region? + if (m_ram16b==NULL) + { + m_ram16b = global_alloc_array_clear(UINT16, 32768/2); + } + + // Now building the list of active devices at this databus multiplex. + // We allow for turning off devices according to configuration switch settings. + // In particular, the HSGPL card cannot function unless the console GROMs are + // removed. + if ( list != NULL ) + { + bool done = false; + for (int i=0; !done; i++) + { + if (list[i].name == NULL) + { + done = true; + } + else + { + UINT32 set = 0; + bool active_device = true; + if (list[i].setting!=NULL) + { + set = ioport(list[i].setting)->read(); + active_device = ((set & list[i].set)==list[i].set) && ((set & list[i].unset)==0); + } + if (active_device) + { + device_t *dev = machine().device(list[i].name); + if (dev != NULL) + { + attached_device *ad = new attached_device(dev, list[i]); + m_devices.append(*ad); + if (TRACE_SETUP) logerror("datamux: Device %s mounted at index %d.\n", list[i].name, i); + } + else + { + if (TRACE_SETUP) logerror("datamux: Device %s not found.\n", list[i].name); + } + } + else + { + if (TRACE_SETUP) logerror("datamux: Device %s not mounted due to configuration setting %s.\n", list[i].name, list[i].setting); + } + } + } + } + if (TRACE_SETUP) logerror("datamux: Device count = %d\n", m_devices.count()); + + m_sysready = ASSERT_LINE; + m_muxready = ASSERT_LINE; + ready_join(); + + m_waitcount = 0; + m_latch = 0; + + m_read_mode = true; +} + +INPUT_PORTS_START( datamux ) + PORT_START( "RAM" ) /* config */ + PORT_CONFNAME( 0x01, 0x00, "Console 32 KiB RAM upgrade (16 bit)" ) + PORT_CONFSETTING( 0x00, DEF_STR( Off ) ) + PORT_CONFSETTING( 0x01, DEF_STR( On ) ) + + PORT_START( "GROMENA" ) + PORT_CONFNAME( 0x01, 0x01, "Console GROMs" ) + PORT_CONFSETTING( 0x00, DEF_STR( Off ) ) + PORT_CONFSETTING( 0x01, DEF_STR( On ) ) + +INPUT_PORTS_END + +ioport_constructor ti99_datamux_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(datamux); +} + +const device_type DATAMUX = &device_creator<ti99_datamux_device>; |