From d3e9cded17be9fe58d93b789e638c6d7676964db Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Wed, 26 Feb 2025 22:03:02 +0100 Subject: [PATCH] tms52xx: Enable command buffering during speak operation. --- src/devices/sound/tms5220.cpp | 111 ++++++++++++++++++++++++++++------ src/devices/sound/tms5220.h | 8 ++- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/src/devices/sound/tms5220.cpp b/src/devices/sound/tms5220.cpp index dd02cf8d714..e63862b4f46 100644 --- a/src/devices/sound/tms5220.cpp +++ b/src/devices/sound/tms5220.cpp @@ -397,8 +397,10 @@ emulating the tms5220 in MCU code). Look for a 16-pin chip at U6 labeled #define LOG_IO_READY (1U << 14) // debugs the tms5220_data_r and data_w access methods which actually respect rs and ws #define LOG_RS_WS (1U << 15) +// shows the byte being written by the CPU to the VSP +#define LOG_DATA_W (1U << 16) -//#define VERBOSE (LOG_GENERAL | LOG_DUMP_INPUT_DATA | LOG_FIFO | LOG_PARSE_FRAME_DUMP_HEX | LOG_FRAME_ERRORS | LOG_COMMAND_DUMP | LOG_COMMAND_VERBOSE | LOG_PIN_READS | LOG_GENERATION | LOG_GENERATION_VERBOSE | LOG_LATTICE | LOG_CLIP | LOG_IO_READY | LOG_RS_WS) +//#define VERBOSE (LOG_GENERAL | LOG_DUMP_INPUT_DATA | LOG_FIFO | LOG_PARSE_FRAME_DUMP_HEX | LOG_FRAME_ERRORS | LOG_COMMAND_DUMP | LOG_COMMAND_VERBOSE | LOG_PIN_READS | LOG_GENERATION | LOG_GENERATION_VERBOSE | LOG_LATTICE | LOG_CLIP | LOG_IO_READY | LOG_RS_WS | LOG_DATA_W) #include "logmacro.h" #define MAX_SAMPLE_CHUNK 512 @@ -438,6 +440,8 @@ emulating the tms5220 in MCU code). Look for a 16-pin chip at U6 labeled static const uint8_t reload_table[4] = { 0, 2, 4, 6 }; //sample count reload for 5220c and cd2501ecd only; 5200 and 5220 always reload with 0; keep in mind this is loaded on IP=0 PC=12 subcycle=1 so it immediately will increment after one sample, effectively being 1,3,5,7 as in the comments above. +#define NOCOMMAND 0xff + // Pull in the ROM tables #include "tms5110r.hxx" @@ -480,6 +484,9 @@ void tms5220_device::register_for_save_states() save_item(NAME(m_irq_pin)); save_item(NAME(m_ready_pin)); + save_item(NAME(m_command_register)); + save_item(NAME(m_data_latched)); + // current and previous frames save_item(NAME(m_OLDE)); save_item(NAME(m_OLDP)); @@ -663,12 +670,14 @@ void tms5220_device::data_write(int data) LOGMASKED(LOG_FIFO, "data_write: Ran out of room in the tms52xx FIFO! this should never happen!\n"); // at this point, /READY should remain HIGH/inactive until the FIFO has at least one byte open in it. } - - + m_data_latched = false; } else //(! m_DDIS) // R Nabet : we parse commands at once. It is necessary for such commands as read. process_command(data); + + if (!m_data_latched) + m_io_ready = true; } /********************************************************************************************** @@ -736,9 +745,30 @@ void tms5220_device::update_fifo_status_and_ints() // also, in this case, regardless if DDIS was set, unset it. if (m_previous_talk_status && !talk_status()) { - LOG("Talk status WAS 1, is now 0, unsetting DDIS and firing an interrupt!\n"); + LOG("Talk status 1 -> 0, unsetting DDIS and firing an interrupt.\n"); set_interrupt_state(1); m_DDIS = false; + + m_previous_talk_status = false; + + // Is there a command stuck in the command register due to speech output? + // Then resume it. + if (m_command_register != NOCOMMAND) + { + LOGMASKED(LOG_COMMAND_DUMP, "Resume command execution: %02X\n", m_command_register); + process_command(m_command_register); + + // Is there another data transfer pending? Resume it as well. + if (m_data_latched) + { + LOGMASKED(LOG_COMMAND_DUMP, "Pending byte write: %02X\n", m_write_latch); + m_timer_io_ready->adjust(clocks_to_attotime(16), 1); + } + else + m_io_ready = true; + + update_ready_state(); + } } m_previous_talk_status = talk_status(); @@ -1296,14 +1326,27 @@ int32_t tms5220_device::lattice_filter() /********************************************************************************************** - process_command -- extract a byte from the FIFO and interpret it as a command + process_command -- decode byte and run the command + + During SPEAK, the address counter is locked against modification by + LOAD_ADDRESS. In that time, a LOAD_ADDRESS command does not terminate but + remains in the command register. + When another command is fed into the speech processor, the READY line is + lowered until the command register is cleared, then the new command is + loaded into the command register. + + Note that the running SPEAK command does not block the command register; + it is a command that attempts to change the address during the SPEAK + process. [mz] ***********************************************************************************************/ -void tms5220_device::process_command(unsigned char cmd) +void tms5220_device::process_command(uint8_t cmd) { LOGMASKED(LOG_COMMAND_DUMP, "process_command called with parameter %02X\n", cmd); + m_command_register = cmd; + /* parse the command */ switch (cmd & 0x70) { @@ -1320,12 +1363,14 @@ void tms5220_device::process_command(unsigned char cmd) if (m_speechrom) m_read_byte_register = m_speechrom->read(8); /* read one byte from speech ROM... */ m_RDB_flag = true; + m_command_register = NOCOMMAND; } else - LOGMASKED(LOG_COMMAND_VERBOSE, "Read Byte command received during TALK state, ignoring!\n"); + LOGMASKED(LOG_COMMAND_VERBOSE, "Read Byte command received during TALK state, suspended.\n"); break; - case 0x00: case 0x20: /* set rate (tms5220c and cd2501ecd only), otherwise NOP */ + case 0x00: + case 0x20: /* set rate (tms5220c and cd2501ecd only), otherwise NOP */ if (TMS5220_HAS_RATE_CONTROL) { LOGMASKED(LOG_COMMAND_VERBOSE, "Set Rate (or NOP) command received\n"); @@ -1333,6 +1378,8 @@ void tms5220_device::process_command(unsigned char cmd) } else LOGMASKED(LOG_COMMAND_VERBOSE, "NOP command received\n"); + + m_command_register = NOCOMMAND; break; case 0x30 : /* read and branch */ @@ -1342,21 +1389,28 @@ void tms5220_device::process_command(unsigned char cmd) m_RDB_flag = false; if (m_speechrom) m_speechrom->read_and_branch(); + m_command_register = NOCOMMAND; } break; case 0x40 : /* load address */ - LOGMASKED(LOG_COMMAND_VERBOSE, "Load Address command received\n"); if (!talk_status()) /* TALKST must be clear for LA */ { + LOGMASKED(LOG_COMMAND_VERBOSE, "Load Address command received\n"); /* tms5220 data sheet says that if we load only one 4-bit nibble, it won't work. This code does not care about this. */ if (m_speechrom) m_speechrom->load_address(cmd & 0x0f); m_schedule_dummy_read = true; + m_command_register = NOCOMMAND; } else - LOGMASKED(LOG_COMMAND_VERBOSE, "Load Address command received during TALK state, ignoring!\n"); + { + // The Load Address command is not ignored during speech output, + // as tests show. In fact, it is normally executed when the speech + // terminates. + LOGMASKED(LOG_COMMAND_VERBOSE, "Load Address command received during TALK state, suspended.\n"); + } break; case 0x50 : /* speak */ @@ -1389,6 +1443,8 @@ void tms5220_device::process_command(unsigned char cmd) m_new_frame_k_idx[i] = 0xF; for (int i = 7; i < m_coeff->num_k; i++) m_new_frame_k_idx[i] = 0x7; + + m_command_register = NOCOMMAND; break; case 0x60 : /* speak external */ @@ -1417,6 +1473,8 @@ void tms5220_device::process_command(unsigned char cmd) for (int i = 7; i < m_coeff->num_k; i++) m_new_frame_k_idx[i] = 0x7; m_RDB_flag = false; + + m_command_register = NOCOMMAND; break; case 0x70 : /* reset */ @@ -1428,6 +1486,7 @@ void tms5220_device::process_command(unsigned char cmd) m_speechrom->read(1); } reset(); + m_command_register = NOCOMMAND; break; } @@ -1666,6 +1725,9 @@ void tms5220_device::device_reset() update_ready_state(); m_buffer_empty = m_buffer_low = true; + m_command_register = NOCOMMAND; + m_data_latched = false; + m_RDB_flag = false; /* initialize the energy/pitch/k states */ @@ -1716,6 +1778,7 @@ TIMER_CALLBACK_MEMBER(tms5220_device::set_io_ready) { /* bring up to date first */ m_stream->update(); + LOGMASKED(LOG_IO_READY, "m_timer_io_ready timer fired, param = %02x, m_rs_ws = %02x\n", param, m_rs_ws); if (param) // low->high ready state { @@ -1732,24 +1795,34 @@ TIMER_CALLBACK_MEMBER(tms5220_device::set_io_ready) } else { - LOGMASKED(LOG_IO_READY, "m_timer_io_ready: Serviced write: %02x\n", m_write_latch); - data_write(m_write_latch); - m_io_ready = param; + if (m_command_register != NOCOMMAND) + { + // The command register is still busy; do not activate /READY + // and keep the data latch + LOGMASKED(LOG_IO_READY, "m_timer_io_ready: Command register not ready\n"); + } + else + { + LOGMASKED(LOG_IO_READY, "m_timer_io_ready: Serviced write: %02X\n", m_write_latch); + m_data_latched = false; + data_write(m_write_latch); + } + break; } case 0x01: /* Read */ m_read_latch = status_read(true); - LOGMASKED(LOG_IO_READY, "m_timer_io_ready: Serviced read, returning %02x\n", m_read_latch); - m_io_ready = param; + LOGMASKED(LOG_IO_READY, "m_timer_io_ready: Serviced read, returning %02X\n", m_read_latch); + m_io_ready = true; break; case 0x03: /* High Impedance */ - m_io_ready = param; + m_io_ready = true; break; case 0x00: /* illegal */ - m_io_ready = param; + m_io_ready = true; break; } } @@ -1940,10 +2013,12 @@ void tms5220_device::combined_rsq_wsq_w(u8 data) void tms5220_device::data_w(uint8_t data) { - LOGMASKED(LOG_RS_WS, "tms5220_write_data: data %02x\n", data); + LOGMASKED(LOG_DATA_W, "tms5220_write_data: data %02X\n", data); /* bring up to date first */ m_stream->update(); m_write_latch = data; + m_data_latched = true; + if (!m_true_timing) // if we're in the default hacky mode where we don't bother with rsq_w and wsq_w... data_write(m_write_latch); // ...force the write through instantly. else diff --git a/src/devices/sound/tms5220.h b/src/devices/sound/tms5220.h index 33747f5d561..8b49852aebc 100644 --- a/src/devices/sound/tms5220.h +++ b/src/devices/sound/tms5220.h @@ -97,7 +97,7 @@ private: int16_t clip_analog(int16_t cliptemp) const; int32_t matrix_multiply(int32_t a, int32_t b) const; int32_t lattice_filter(); - void process_command(unsigned char cmd); + void process_command(uint8_t cmd); void parse_frame(); void set_interrupt_state(int state); void update_ready_state(); @@ -157,6 +157,12 @@ private: bool m_irq_pin; /* state of the IRQ pin (output) */ bool m_ready_pin; /* state of the READY pin (output) */ + /* Currently processed command */ + uint8_t m_command_register; + + /* Indicates a value latched from the data lines and not yet processed */ + bool m_data_latched; + /* these contain data describing the current and previous voice frames */ bool m_OLDE; bool m_OLDP;