summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/machine/ti99/spchsyn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mess/machine/ti99/spchsyn.c')
-rw-r--r--src/mess/machine/ti99/spchsyn.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/mess/machine/ti99/spchsyn.c b/src/mess/machine/ti99/spchsyn.c
new file mode 100644
index 00000000000..10e38352932
--- /dev/null
+++ b/src/mess/machine/ti99/spchsyn.c
@@ -0,0 +1,308 @@
+/****************************************************************************
+
+ TI-99 Speech synthesizer
+
+ We emulate the Speech Synthesizer plugged onto a P-Box adapter. The original
+ Speech Synthesizer device was provided as a box to be plugged into the
+ right side of the console. In order to be used with Geneve and SGCPU, the
+ speech synthesizer must be moved into the Peripheral Box.
+
+ The Speech Synthesizer used for the TI was the TMS5200, aka TMC0285, a
+ predecessor of the TMS5220 which was used in other commercial products.
+
+ Note that this adapter also contains the speech roms.
+
+ Michael Zapf
+
+ February 2012: Rewritten as class
+
+*****************************************************************************/
+
+#include "spchsyn.h"
+#include "sound/wave.h"
+
+#define TMS5220_ADDRESS_MASK 0x3FFFFUL /* 18-bit mask for tms5220 address */
+
+#define VERBOSE 1
+#define LOG logerror
+
+#define SPEECHROM_TAG "speechrom"
+
+#define REAL_TIMING 0
+
+/****************************************************************************/
+
+ti_speech_synthesizer_device::ti_speech_synthesizer_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+: ti_expansion_card_device(mconfig, TI99_SPEECH, "TI-99 Speech synthesizer (on adapter card)", tag, owner, clock)
+{
+ m_shortname = "ti99_speech";
+}
+
+/*
+ Comments on real timing in the TMS5200 emulation
+
+ Real timing means that the synthesizer clears the /READY line (puts high)
+ whenever a read or write access is in progress. This is done by setting
+ /RS and /WS lines, according to the read or write operation. The /READY line
+ is asserted again after some time. Real timing is used once the tms5220_wsq_w
+ and tms5220_rsq_w are called.
+
+ Within the TI systems, the /RS and /WS lines are controlled directly by the
+ address bus. There is currently no way to insert wait states between
+ the address setting and the data bus sampling, since this is an atomic
+ operation in the emulator (read_byte). It would be necessary to somehow
+ announce the pending read before it actually happens so that devices
+ like this VSP may insert wait states before the read.
+
+ The TMS5220 implementation assumes that wait states are respected and
+ therefore delivers bad values when queried too early. It uses a latch that
+ gets the new value after some time has expired.
+
+ To fix this we have to modify the RS/WS methods in TMS5200 to immediately
+ set the status (after updating the sound status, or the status will be
+ outdated too early).
+
+ Also note that the /RS and /WS lines must be cleared (put to high) when the
+ read is done. Again, this is not possible with the current implementation.
+ So we do this in the ready callback.
+
+ On the bottom line we will stay with the not-REAL_TIMING for now and wait
+ for the core to allow for split read accesses.
+*/
+
+
+/*
+ Memory read
+*/
+#if REAL_TIMING
+// ====== This is the version with real timing =======
+READ8Z_MEMBER( ti_speech_synthesizer_device::readz )
+{
+ if ((offset & m_select_mask)==m_select_value)
+ {
+ m_vsp->wsq_w(TRUE);
+ m_vsp->rsq_w(FALSE);
+ *value = m_vsp->read(offset) & 0xff;
+ if (VERBOSE>4) LOG("spchsyn: read value = %02x\n", *value);
+ }
+}
+
+/*
+ Memory write
+*/
+WRITE8_MEMBER( ti_speech_synthesizer_device::write )
+{
+ if ((offset & m_select_mask)==(m_select_value | 0x0400))
+ {
+ m_vsp->rsq_w(m_vsp, TRUE);
+ m_vsp->wsq_w(m_vsp, FALSE);
+ if (VERBOSE>4) LOG("spchsyn: write value = %02x\n", data);
+ m_vsp->write(offset, data);
+ }
+}
+
+#else
+// ====== This is the version without real timing =======
+
+READ8Z_MEMBER( ti_speech_synthesizer_device::readz )
+{
+ if ((offset & m_select_mask)==m_select_value)
+ {
+ device_adjust_icount(machine().device("maincpu"),-(18+3)); /* this is just a minimum, it can be more */
+ *value = m_vsp->read(space, offset, 0xff) & 0xff;
+ if (VERBOSE>4) LOG("spchsyn: read value = %02x\n", *value);
+ }
+}
+
+/*
+ Memory write
+*/
+WRITE8_MEMBER( ti_speech_synthesizer_device::write )
+{
+ if ((offset & m_select_mask)==(m_select_value | 0x0400))
+ {
+ device_adjust_icount(machine().device("maincpu"),-(54+3)); /* this is just an approx. minimum, it can be much more */
+
+ /* RN: the stupid design of the tms5220 core means that ready is cleared */
+ /* when there are 15 bytes in FIFO. It should be 16. Of course, if */
+ /* it were the case, we would need to store the value on the bus, */
+ /* which would be more complex. */
+ if (!m_vsp->readyq())
+ {
+ attotime time_to_ready = attotime::from_double(m_vsp->time_to_ready());
+ int cycles_to_ready = machine().device<cpu_device>("maincpu")->attotime_to_cycles(time_to_ready);
+ if (VERBOSE>8) LOG("spchsyn: time to ready: %f -> %d\n", time_to_ready.as_double(), (int) cycles_to_ready);
+
+ device_adjust_icount(machine().device("maincpu"),-cycles_to_ready);
+ machine().scheduler().timer_set(attotime::zero, FUNC_NULL);
+ }
+ if (VERBOSE>4) LOG("spchsyn: write value = %02x\n", data);
+ m_vsp->write(space, offset, data);
+ }
+}
+#endif
+
+/****************************************************************************
+ Callbacks from TMS5220
+*****************************************************************************/
+/*
+ Read 'count' bits serially from speech ROM. The offset is used as the count.
+*/
+READ8_MEMBER( ti_speech_synthesizer_device::spchrom_read )
+{
+ int val;
+ int count = offset;
+
+ if (m_load_pointer != 0)
+ { // first read after load address is ignored
+ m_load_pointer = 0;
+ count--;
+ }
+
+ if (m_sprom_address < m_sprom_length)
+ {
+ if (count < m_rombits_count)
+ {
+ m_rombits_count -= count;
+ val = (m_speechrom[m_sprom_address] >> m_rombits_count) & (0xFF >> (8 - count));
+ }
+ else
+ {
+ val = ((int)m_speechrom[m_sprom_address]) << 8;
+
+ m_sprom_address = (m_sprom_address + 1) & TMS5220_ADDRESS_MASK;
+
+ if (m_sprom_address < m_sprom_length)
+ val |= m_speechrom[m_sprom_address];
+
+ m_rombits_count += 8 - count;
+
+ val = (val >> m_rombits_count) & (0xFF >> (8 - count));
+ }
+ }
+ else
+ val = 0;
+
+ return val;
+}
+
+/*
+ Write an address nibble to speech ROM. The address nibble is in the data,
+ the offset is ignored.
+*/
+WRITE8_MEMBER( ti_speech_synthesizer_device::spchrom_load_address )
+{
+ // tms5220 data sheet says that if we load only one 4-bit nibble, it won't work.
+ // This code does not care about this.
+ m_sprom_address = ((m_sprom_address & ~(0xf << m_load_pointer))
+ | (((unsigned long) (data & 0xf)) << m_load_pointer) ) & TMS5220_ADDRESS_MASK;
+ m_load_pointer += 4;
+ m_rombits_count = 8;
+}
+
+/*
+ Perform a read and branch command. We do not use the offset or data parameter.
+*/
+WRITE8_MEMBER( ti_speech_synthesizer_device::spchrom_read_and_branch )
+{
+ // tms5220 data sheet says that if more than one speech ROM (tms6100) is present,
+ // there is a bus contention. This code does not care about this. */
+ if (m_sprom_address < m_sprom_length-1)
+ m_sprom_address = (m_sprom_address & 0x3c000UL)
+ | (((((unsigned long) m_speechrom[m_sprom_address]) << 8)
+ | m_speechrom[m_sprom_address+1]) & 0x3fffUL);
+ else if (m_sprom_address == m_sprom_length-1)
+ m_sprom_address = (m_sprom_address & 0x3c000UL)
+ | ((((unsigned long) m_speechrom[m_sprom_address]) << 8) & 0x3fffUL);
+ else
+ m_sprom_address = (m_sprom_address & 0x3c000UL);
+
+ m_rombits_count = 8;
+}
+
+/****************************************************************************/
+
+/*
+ Callback interface instance
+*/
+static const tms52xx_config ti99_4x_tms5200interface =
+{
+ DEVCB_NULL, // no IRQ callback
+ DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, ti_speech_synthesizer_device, speech_ready),
+
+ DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, ti_speech_synthesizer_device, spchrom_read), // speech ROM read handler
+ DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, ti_speech_synthesizer_device, spchrom_load_address), // speech ROM load address handler
+ DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, ti_speech_synthesizer_device, spchrom_read_and_branch) // speech ROM read and branch handler
+};
+
+/****************************************************************************/
+
+WRITE_LINE_MEMBER( ti_speech_synthesizer_device::speech_ready )
+{
+ // The TMS5200 implementation uses TRUE/FALSE, not ASSERT/CLEAR semantics
+ // and we have to adapt a /READY to a READY line.
+ // The real synthesizer board uses a transistor for that purpose.
+ m_slot->set_ready((state==0)? ASSERT_LINE : CLEAR_LINE);
+ if (VERBOSE>5) LOG("spchsyn: READY = %d\n", (state==0));
+
+#if REAL_TIMING
+ // Need to do that here (see explanations above)
+ if (state==0)
+ {
+ m_vsp->rsq_w(TRUE);
+ m_vsp->wsq_w(TRUE);
+ }
+#endif
+}
+
+void ti_speech_synthesizer_device::device_start()
+{
+}
+
+void ti_speech_synthesizer_device::device_config_complete()
+{
+ m_vsp = subdevice<tmc0285n_device>("speechsyn");
+}
+
+void ti_speech_synthesizer_device::device_reset()
+{
+ m_speechrom = memregion(SPEECHROM_TAG)->base();
+ m_sprom_length = memregion(SPEECHROM_TAG)->bytes();
+ m_sprom_address = 0;
+ m_load_pointer = 0;
+ m_rombits_count = 0;
+
+ if (m_genmod)
+ {
+ m_select_mask = 0x1ffc01;
+ m_select_value = 0x179000;
+ }
+ else
+ {
+ m_select_mask = 0x7fc01;
+ m_select_value = 0x79000;
+ }
+}
+
+MACHINE_CONFIG_FRAGMENT( ti99_speech )
+ MCFG_SPEAKER_STANDARD_MONO("mono")
+ MCFG_SOUND_ADD("speechsyn", TMC0285N, 640000L)
+ MCFG_SOUND_CONFIG(ti99_4x_tms5200interface)
+ MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.50)
+MACHINE_CONFIG_END
+
+ROM_START( ti99_speech )
+ ROM_REGION(0x8000, SPEECHROM_TAG, 0)
+ ROM_LOAD_OPTIONAL("spchrom.bin", 0x0000, 0x8000, CRC(58b155f7) SHA1(382292295c00dff348d7e17c5ce4da12a1d87763)) /* system speech ROM */
+ROM_END
+
+machine_config_constructor ti_speech_synthesizer_device::device_mconfig_additions() const
+{
+ return MACHINE_CONFIG_NAME( ti99_speech );
+}
+
+const rom_entry *ti_speech_synthesizer_device::device_rom_region() const
+{
+ return ROM_NAME( ti99_speech );
+}
+const device_type TI99_SPEECH = &device_creator<ti_speech_synthesizer_device>;