From 13c3ec0f0079a1a5e6e77640b8d503a6dd6e6cf9 Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Wed, 15 May 2019 22:43:40 +0200 Subject: tms9901: Changed input line reading from 8-bit units to single bits, adapted clients. Decrementer now continues counting even when initial value is 0, clearing up a misunderstanding of specs. --- src/devices/machine/tms9901.cpp | 754 +++++++++++++++++++--------------------- src/devices/machine/tms9901.h | 134 ++++--- src/mame/drivers/geneve.cpp | 163 +++++---- src/mame/drivers/si5500.cpp | 25 +- src/mame/drivers/ti99_4p.cpp | 189 +++++----- src/mame/drivers/ti99_4x.cpp | 312 +++++++++++------ src/mame/drivers/ti99_8.cpp | 136 +++----- src/mame/drivers/tm990189.cpp | 66 ++-- 8 files changed, 975 insertions(+), 804 deletions(-) diff --git a/src/devices/machine/tms9901.cpp b/src/devices/machine/tms9901.cpp index 4e7341feb61..7f85114128a 100644 --- a/src/devices/machine/tms9901.cpp +++ b/src/devices/machine/tms9901.cpp @@ -27,109 +27,121 @@ P5 |20 21| P4 +--------------+ +Reference: [1] TMS9901 Programmable Systems Interface Data Manual, July 1977 + [2] A. Osborne, G. Kane: Osborne 16-bit microprocessor handbook + Overview: TMS9901 is a support chip for TMS9900. It handles interrupts, provides - several I/O pins, and a timer (a.k.a. clock: it is merely a register which - decrements regularly and can generate an interrupt when it reaches 0). + several I/O pins, and a timer, which is a register which + decrements regularly and can generate an interrupt when it reaches 0. It communicates with the TMS9900 with the CRU bus, and with the rest of the world with a number of parallel I/O pins. I/O and timer functions should work with any other 990/99xx/99xxx CPU. - On the other hand, interrupt handling was primarily designed for tms9900 + On the other hand, interrupt handling was primarily designed for TMS9900 and 99000 based systems: other CPUs can support interrupts, but not the 16 distinct interrupt vectors. + For the 9980A, the IC1-IC3 lines are connected to the IC0-IC2 lines of the + 9980A. + Pins: Vcc, Vss: power supply - Phi*: system clock (connected to TMS9900 Phi3* or TMS9980 CLKOUT*) - RST1*: reset input - CRUIN, CRUOUT, CRUCLK, CE*, S0-S4: CRU bus (CPU interface) - INTREQ*, IC0-IC3: interrupt bus (CPU interface) - INT*1-INT*6: used as interrupt/input pins. - P0-P6: used as input/output pins. - INT*7/P15-INT*15/P7: used as either interrupt/input or input/output pins. - Note that a pin cannot be used simultaneously as output and as interrupt. - (This is mostly obvious, but it implies that you cannot trigger an - interrupt by setting the output state of a pin, which is not SO obvious.) - -Clock mode: - The "clock mode" is entered by setting bit 0 to 1. This means that the - clock register becomes accessible to changes and inspection. The clock - itself runs in the interrupt mode. Accordingly, the typical setup - involves first setting bit 0 to 1, then loading some or all of the - clock register bits, and then switching to interrupt mode again. From then - on, INT3 is asserted whenever the clock reaches 0, and is cleared by - writing 0 or 1 to bit 3. The clock can only be stopped by setting the - register to 0 or by a reset. - -Interrupt handling: - After each clock cycle, TMS9901 latches the state of INT1*-INT15* (except - pins which are set as output pins). If the clock is enabled, it replaces - INT3* with an internal timer interrupt flag. Then it inverts the value and - performs a bit-wise AND with the interrupt mask. + Phi* : system clock (connected to TMS9900 Phi3* or TMS9980A CLKOUT*) + RST1*: Reset input + CRUIN, + CRUOUT, + CRUCLK: CRU bus + CE*: Chip enable; typically driven by a decoder for the CRU address + S0-S4: CRU access bits (0..31; S0 is MSB) + INTREQ*: Interrupt request; active (0) when an interrupt is signaled to the CPU + IC0-IC3: Interrupt level (0..15, IC0 is MSB) + + Three groups of I/O pins: + + Group 1: INT1*-INT6*: Interrupt inputs. + Group 2: INT7*_P15 - INT15*_P7: Interrupt inputs or I/O ports + Group 3: P0-P6: I/O ports + + In group 2, the interrupt inputs and I/O ports share their pins, which + leads to mirroring in the CRU address space. + +Input/Output ports: + P0 to P15 are preconfigured as input ports. By writing a value to a port, + it is configured as an output. Caution must be taken that the pin is not + fed with some logic level when setting it as output, because this may + damage the port. To reconfigure it as an input, the chip must be reset + by the hard RST1* line or the soft RST2* operation (setting bit 15 to 0 + in clock mode). Output pins can be read and return the currently set value. + +Interrupt inputs (group 1 and 2) + The interrupt inputs (INT1*-INT15*) are sampled on each falling edge of + the phi* clock. An interrupt mask is applied to mask away levels that + shall not trigger an interrupt. The mask can be set using the SBO/SBZ + commands (1=arm, 0=disarm) on each of the 15 bits. + + After each clock cycle, the TMS9901 latches the state of INT1*-INT15* + (except those pins which are set as output pins). If there are some unmasked interrupt bits, INTREQ* is asserted and the code - of the lowest active interrupt is placed on IC0-IC3. If these pins are - duly connected to the tms9900 INTREQ* and IC0-IC3 pins, the result is that - asserting an INTn* on tms9901 will cause a level-n interrupt request on the - tms9900, provided that this interrupt pin is not masked in tms9901, and - that no unmasked higher-priority (i.e. lower-level) interrupt pin is set. - - This interrupt request lasts for as long as the interrupt pin and the - relevant bit in the interrupt mask are set (level-triggered interrupts). - (The request may be shadowed by a higher-priority interrupt request, but - it will resume when the higher-priority request ends.) - - TIMER interrupts are kind of an exception, since they are not associated - with an external interrupt pin. I think there is an internal timer - interrupt flag that is set when the decrementer reaches 0, and is cleared - by a write to the 9901 int*3 enable bit ("SBO 3" in interrupt mode). - -TODO: - * Emulate the RST1* input. Note that RST1* active (low) makes INTREQ* - inactive (high) with IC0-IC3 = 0. - * the clock read register is updated every time the timer decrements when - the TMS9901 is not in clock mode. This probably implies that if the - clock mode is cleared and re-asserted immediately, the tms9901 may fail - to update the clock read register: this is not emulated. - * The clock mode is entered when a 1 is written to the control bit. It is - exited when a 0 is written to the control bit or the a tms9901 select bit - greater than 15 is accessed. According to the data sheet, "when CE* is - inactive (HIGH), the PSI is not disabled from seeing the select lines. - As the CPU is accessing memory, A10-A14 could very easily have a value of - 15 or greater" (this is assuming that S0-S4 are connected to A10-A14, - which makes sense with most tms9900 family members). There is no way - this "feature" (I would call it a hardware bug) can be emulated - efficiently, as we would need to watch every memory access. - -MZ: According to the description in - A. Osborne, G. Kane: Osborne 16-bit microprocessor handbook - page 3-81 - the 9901 only temporarily leaves the timer mode as long as S0 is set to 1. - In the meantime the timer function continues but cannot be queried. This - makes it possible to continue using the chip as a timer while working with - its I/O pins. Thus I believe the above TODO concering the exit of the timer - mode is not applicable. - The problem is that the original 9901 specification is not clear about this. - -MZ: Turned to class (January 2012) - -MZ: Added a synchronous clock input (Phi line) as an alternative to the - emu_timer. - -TODO: Tests on a real machine -- Set an interrupt input (e.g. keyboard for Geneve), trigger RST2*, check whether - interrupt mask has been reset -- Check whether the clock_read_register is updated whenever clock mode is exited - (in particular when S0=1, i.e. A10=1 -> addresses xxxx xxxx xx1x xxxx - requires to write a program that fits into 32 bytes; workspace elsewhere) + of the lowest active interrupt is placed on IC0-IC3. The interrupt line + and the level lines should be connected to the respective inputs of the + TMS9900 or TMS9980A. + +Group 2 pins (shared I/O and INT*) + Pins of group 2 are shared between the I/O ports and the interrupt inputs. + Internally, they are treated as different signals: There are interrupt + mask bits for INT7*..INT15*, and there is a CRU bit for each of the I/O + ports. For example, INT7* can be read by bit 7, and the same pin can be + read as P15 via bit 31. When setting bit 7 to 1, the INT7* input is armed + and triggers an interrupt at level 7 when asserted. + + In contrast, when writing to bit 31, P15 (same pin) is configured as an + output, and the written value appears on the pin. When the port is set + as output, the interrupt input on the shared pin is deactivated. + + According to [1], the interrupt mask should be set to 0 for those group 2 + pins that are used as input/output pins so that no unwanted interrupts are + triggered. + +Clock mode: + The "clock mode" is entered by setting bit 0 to 1; setting to 0 enters + "interrupt mode". The internal clock is a 14-bit decrementer that + counts down by 1 every 64 clock ticks. On entering clock mode, the current + value of the decrementer is copied to the clock read register and can be + read by the CRU bits 1 to 14. Writing to these CRU bits modifies the + respective bit of the clock register that serves as the start value. Every + time a bit is written, the decrementer is loaded with the current clock + register value. + Interrupt + ^ + | + [Clock register] -> [Decrementer] -> [Clock read register] + ^ | + | v + +--<--- CRU write CRU read---<---+ + + The specs somewhat ambiguously say that "writing a non-zero value enables the clock" + and "the clock is disabled by RST1* or by writing a zero value into the clock register". + Tests show that when a 0 has been written, the chip still counts down from + 0x3FFF to 0. However, no interrupt is raised when reaching 0, so "enable" + or "disable" most likely refer to the interrupt. + + When enabled, the clock raises an interrupt level 3 when reaching 0, + overriding the input from the INT3* input. CRU bit 3 is the mask bit for + both clock and INT3* input. Writing any value to it changes the mask bit, + and as a side effect it also clears the clock interrupt. + + According to [2], the clock mode is temporarily left when the CRU bits of + the I/O ports (bits 16-31) are accessed. Thus, the 9901 can control its + I/O ports even when it has been set to clock mode before. (Keep in mind + that clock mode simply means to access the clock register; the clock is + counting all the time.) Raphael Nabet, 2000-2004 Michael Zapf - February 2012: Rewritten as class - *****************************************************************************/ #include "emu.h" @@ -139,12 +151,12 @@ TODO: Tests on a real machine #define LOG_GENERAL (1U << 0) #define LOG_PINS (1U << 1) -#define LOG_CONFIG (1U << 2) +#define LOG_MASK (1U << 2) #define LOG_MODE (1U << 3) #define LOG_INT (1U << 4) -#define LOG_DECVALUE (1U << 5) +#define LOG_CLOCK (1U << 5) -#define VERBOSE ( 0 ) +#define VERBOSE ( LOG_GENERAL ) #include "logmacro.h" /* @@ -152,109 +164,126 @@ TODO: Tests on a real machine */ tms9901_device::tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, TMS9901, tag, owner, clock), - m_clock_active(false), + m_int_level(0), + m_last_level(0), + m_int_mask(0), + m_int_pending(false), + m_poll_lines(false), m_clockdiv(0), - m_read_block(*this), + m_timer_int_pending(false), + m_read_port(*this), m_write_p{{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this}}, m_interrupt(*this) { } /* - should be called after any change to int_state or enabled_ints. + Determine the most significant interrupt (lowest number) */ -void tms9901_device::field_interrupts() +void tms9901_device::prioritize_interrupts() { - int current_ints; + // Prioritizer: Search for the interrupt with the highest level + bool found = false; + + // Skip the rightmost bit + uint16_t masked_ints = m_int_line; - // m_int_state: inverted state of lines INT1*-INT15*. Bits are set by set_single_int only. - current_ints = m_int_state; - if (m_clock_register != 0) + // Do we have a timer interrupt? + if (m_clock_register != 0 && m_timer_int_pending) { - // if timer is enabled, INT3 pin is overridden by timer - if (m_timer_int_pending) - { - LOGMASKED(LOG_INT, "INT3 (timer) asserted\n"); - current_ints |= INT3; - } + masked_ints |= (1<>1; + + while ((masked_ints!=0) && !found) + { + // If INTn is set, stop searching. Consider, however, that + // within INT7-INT15, those pins configured as outputs are not sampled + // (shared pins with P15-P7) + + if ((masked_ints & 1) && ((m_int_level < 7) || !is_output(22-m_int_level))) + found = true; else { - LOGMASKED(LOG_INT, "INT3 (timer) cleared\n"); - current_ints &= ~INT3; + m_int_level++; + masked_ints >>= 1; } } - // enabled_ints: enabled interrupts - // Remove all settings from pins that are set as outputs (INT7*-INT15* share the same pins as P15-P7) - current_ints &= m_enabled_ints & (~m_pio_direction_mirror); + if (!found) m_int_level = 15; - // Check whether we have a new state. For systems that use level-triggered - // interrupts it should not do any harm if the line is re-asserted - // but we may as well avoid this. - if (current_ints == m_old_int_state) + m_int_pending = found; + + // Only for asynchronous emulation + if (clock()!=0) signal_int(); +} + +bool tms9901_device::is_output(int p) +{ + return BIT(m_pio_direction, p); +} + +bool tms9901_device::output_value(int p) +{ + return BIT(m_pio_output, p); +} + +void tms9901_device::set_and_latch_output(int p, bool val) +{ + set_bit(m_pio_direction, p, true); + set_bit(m_pio_output, p, val); + m_write_p[p](val); +} + +void tms9901_device::set_bit(uint16_t& bitfield, int pos, bool val) +{ + if (val) bitfield |= (1<>= 1; /* try next bit */ - level++; - } - LOGMASKED(LOG_INT, "Triggering interrupt, level %d\n", level); - m_int_pending = true; + LOGMASKED(LOG_INT, "Triggering interrupt, level %d\n", m_int_level); if (!m_interrupt.isnull()) - m_interrupt(level, 1, 0xff); // the offset carries the IC0-3 level + m_interrupt(ASSERT_LINE); } else { - m_int_pending = false; + LOGMASKED(LOG_INT, "Clear all interrupts\n"); if (!m_interrupt.isnull()) - m_interrupt(0xf, 0, 0xff); //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111 + m_interrupt(CLEAR_LINE); //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111 } } /* - function which should be called by the driver when the state of an INTn* - pin changes (only required if the pin is set up as an interrupt pin) + Signal an interrupt line change to the 9901. + The real circuit samples all active interrupt inputs on every falling edge + of the phi* clock, which is very inefficient to emulate. + + Accordingly, we let the interrupt producer calls this method and + so push the interrupt line change. state == CLEAR_LINE: INTn* is inactive (high) state == ASSERT_LINE: INTn* is active (low) - 0<=pin_number<=15 -*/ -void tms9901_device::set_single_int(int pin_number, int state) -{ - /* remember new state of INTn* pin state */ - if (state==ASSERT_LINE) - m_int_state |= 1 << pin_number; - else - m_int_state &= ~(1 << pin_number); - - field_interrupts(); -} - -/* - load the content of m_clock_register into the decrementer + n=1..15 */ -void tms9901_device::timer_reload() +void tms9901_device::set_int_line(int n, int state) { - if (m_clock_register != 0) - { /* reset clock interval */ - m_decrementer_value = m_clock_register; - m_clock_active = true; - } - else - { /* clock interval == 0 -> no timer */ - m_clock_active = false; + if ((n >= 1) && (n <= 15)) + { + set_bit(m_int_line, n, state==ASSERT_LINE); + prioritize_interrupts(); } } @@ -263,245 +292,143 @@ void tms9901_device::timer_reload() ----------------------------------------------------------------*/ /* - Read a 8 bit chunk from tms9901. + Read a bit from tms9901. + + Bit Meaning + --------------- + 0 Control bit (0=Interrupt mode, 1=Clock mode) + 1..14 /INTn (int mode), CLKn (clock mode) + 15 /INT15 (int mode), /INTREQ (clock mode) + 16..31 P0...P15 input - signification: - bit 0: m_clock_mode - if (m_clock_mode == false) - bit 1-15: current status of the INT1*-INT15* pins - else - bit 1-14: current timer value - bit 15: value of the INTREQ* (interrupt request to TMS9900) pin. + Reading an output port delivers the latched output value. - bit 16-31: current status of the P0-P15 pins (quits timer mode, too...) + Ports P7 to P15 share pins with the interrupt inputs /INT15 to /INT7 + (in this order). When configured as outputs, reading returns the latched + values. */ -uint8_t tms9901_device::read(offs_t offset) +READ8_MEMBER( tms9901_device::read ) { - int answer = 0; + int crubit = offset & 0x01f; - offset &= 0x01f; + if (crubit == 0) + return m_clock_mode? 1 : 0; - switch (offset >> 3) + if (crubit > 15) { - case 0: - if (m_clock_mode) - { - // Clock mode. The LSB reflects the CB bit which is set to 1 for clock mode. - answer = ((m_clock_read_register & 0x7F) << 1) | 0x01; - } + // I/O lines + if (is_output(crubit-16)) + return output_value(crubit-16); else { - // Interrupt mode - // Note that we rely on the read function to deliver the same - // INTx levels that have been signaled via the set_single_int method. - // This may mean that those levels must be latched by the callee. - if (!m_read_block.isnull()) - answer |= m_read_block(CB_INT7); - - // Remove the bits that are set as outputs (can only be INT7*) - answer &= ~m_pio_direction_mirror; - - // Set those bits here - answer |= (m_pio_output_mirror & m_pio_direction_mirror) & 0xFF; + // Positive logic; should be 0 if there is no connection. + if (m_read_port.isnull()) return 0; + return m_read_port((crubit<=P6)? crubit : P6+P0-crubit); } - LOGMASKED(LOG_PINS, "Input on lines INT7..CB = %02x\n", answer); - break; - case 1: - if (m_clock_mode) - { - // clock mode - answer = (m_clock_read_register & 0x3F80) >> 7; - if (!m_int_pending) - answer |= 0x80; - } - else - { - // See above concerning the INT levels. - if (!m_read_block.isnull()) - answer |= m_read_block(INT8_INT15); - - // Remove the bits that are set as outputs (can be any line) - answer &= ~(m_pio_direction_mirror >> 8); - answer |= (m_pio_output_mirror & m_pio_direction_mirror) >> 8; - } - LOGMASKED(LOG_PINS, "Input on lines INT15..INT8 = %02x\n", answer); - break; - case 2: - /* exit timer mode */ - // MZ: See comments at the beginning. I'm sure that we do not quit clock mode. - // m_clock_mode = false; - - if (!m_read_block.isnull()) - answer = m_read_block(P0_P7); - else - answer = 0; + } - answer &= ~m_pio_direction; - answer |= (m_pio_output & m_pio_direction) & 0xFF; - LOGMASKED(LOG_PINS, "Input on lines P7..P0 = %02x\n", answer); + // If we are here, crubit=1..15 + if (m_clock_mode) + { + if (crubit == 15) // bit 15 in clock mode = /INTREQ + return (m_int_pending)? 0 : 1; - break; - case 3: - // MZ: see above - // m_clock_mode = false; - if (!m_read_block.isnull()) - answer = m_read_block(P8_P15); + return BIT(m_clock_read_register, crubit-1); + } + else + { + // We trust the read_port method to deliver the same INTx levels that + // have been signaled via the set_int_line method. + // Thus, those levels must be latched by the component that hosts + // this 9901. Alternatively, use the interrupt line polling + // which has a bad impact on performance. + if (crubit>INT6 && is_output(22-crubit)) + return output_value(22-crubit); else - answer = 0; - - answer &= ~(m_pio_direction >> 8); - answer |= (m_pio_output & m_pio_direction) >> 8; - LOGMASKED(LOG_PINS, "Input on lines P15..P8 = %02x\n", answer); - - break; + return m_read_port.isnull()? 1 : m_read_port(crubit); } - - return BIT(answer, offset & 7); } -/* - Write 1 bit to tms9901. - - signification: - bit 0: write m_clock_mode - if (!m_clock_mode) - bit 1-15: write interrupt mask register - else - bit 1-14: write timer period - bit 15: if written value == 0, soft reset (just resets all I/O pins as input) - bit 16-31: set output state of P0-P15 (and set them as output pin) (quit timer mode, too...) +/* + Write one bit to the tms9901. + + Bit Meaning + ---------------------------------------------------------- + 0 0=Interrupt mode, 1=Clock mode + 1..14 Clock mode: Set CLKn; Interrupt mode: Set Mask n + 15 Clock mode: /RST2; Interrupt mode: Set Mask 15 + 16..31 Set P(n-16) as output, latch value, and output it */ -void tms9901_device::write(offs_t offset, uint8_t data) +WRITE8_MEMBER( tms9901_device::write ) { - data &= 1; /* clear extra bits */ - offset &= 0x01F; + data &= 1; // clear extra bits + int crubit = offset & 0x001f; - if (offset >= 0x10) + if (crubit >= 16) { - int pin = offset & 0x0F; - LOGMASKED(LOG_PINS, "Output on P%d = %d\n", pin, data); - - int bit = (1 << pin); - - // MZ: see above - I think this is wrong - // m_clock_mode = false; // exit timer mode - - // Once a value is written to a pin, the pin remains in output mode - // until the chip is reset - m_pio_direction |= bit; - - // Latch the value - if (data) - m_pio_output |= bit; - else - m_pio_output &= ~bit; - - if (pin >= 7) - { - // pins P7-P15 are mirrored as INT15*-INT7*, - // also using the same pins in the package - int mirror_bit = (1 << (22 - pin)); - - // See above - m_pio_direction_mirror |= mirror_bit; - - if (data) - m_pio_output_mirror |= mirror_bit; - else - m_pio_output_mirror &= ~mirror_bit; - } - - m_write_p[offset - 0x10](data); + LOGMASKED(LOG_PINS, "Output on P%d = %d\n", crubit-16, data); + set_and_latch_output(crubit-P0, data); + return; } - else if (offset == 0) + + switch (crubit) { + case 0: // Write to control bit (CB) - if (data == 0) - { - // Switch to interrupt mode; quit clock mode - m_clock_mode = false; - LOGMASKED(LOG_MODE, "Enter interrupt mode\n"); - } - else + m_clock_mode = (data!=0); + LOGMASKED(LOG_MODE, "Enter %s mode\n", m_clock_mode? "clock" : "interrupt"); + + if (m_clock_mode) { - m_clock_mode = true; - LOGMASKED(LOG_MODE, "Enter clock mode\n"); // we are switching to clock mode: latch the current value of // the decrementer register - if (m_clock_register != 0) - m_clock_read_register = m_decrementer_value; - else - m_clock_read_register = 0; /* timer inactive... */ - - LOGMASKED(LOG_CONFIG, "Clock setting = %d\n", m_clock_read_register); + m_clock_read_register = m_decrementer_value; + LOGMASKED(LOG_MODE, "Clock setting = %d\n", m_clock_read_register); } - } - else if (offset == 0x0f) - { + break; + + case 15: if (m_clock_mode) - { /* in clock mode this is the soft reset bit */ - if (!data) - { // TMS9901 soft reset (RST2*) - // Spec: "Writing a 0 to bit 15 while in the clock mode executes a soft reset on the I/O pins. - // [...] RST2* will program all ports to the input mode" - m_pio_direction = 0; - m_pio_direction_mirror = 0; - - // "RST1* (power-up reset) will reset all mask bits low." - // Spec is not clear on whether the mask bits are also reset by RST2* - // TODO: Check on a real machine. (I'd guess from the text they are not touched) - m_enabled_ints = 0; - LOGMASKED(LOG_MODE, "Soft reset (RST2*)\n"); - } + { + // In clock mode, bit 15 is /RST2 + if (data == 0) soft_reset(); } else - { /* modify interrupt enable mask */ - if (data) - m_enabled_ints |= 0x4000; /* set bit */ - else - m_enabled_ints &= ~0x4000; /* unset bit */ - - LOGMASKED(LOG_CONFIG, "Enabled interrupts = %04x\n", m_enabled_ints); - field_interrupts(); /* changed interrupt state */ + { + set_bit(m_int_mask, 15, data!=0); + LOGMASKED(LOG_MASK, "/INT15 is %s\n", data? "enabled" : "disabled"); + prioritize_interrupts(); // changed interrupt state } - } - else - { - // write one bit to 9901 (bits 1-14) - // - // m_clock_mode==false ? Disable/Enable an interrupt - // : Bit in clock interval - // - // offset is the index of the modified bit of register (-> interrupt number -1) + break; + + default: + // Bits 1..14 if (m_clock_mode) - { /* modify clock interval */ - int bit = 1 << ((offset & 0x0F) - 1); /* corresponding mask */ + { + // Modify clock interval + set_bit(m_clock_register, crubit-1, data!=0); - if (data) - m_clock_register |= bit; /* set bit */ - else - m_clock_register &= ~bit; /* clear bit */ + // Reset clock timer (page 8) + m_decrementer_value = m_clock_register; - /* reset clock timer (page 8) */ - LOGMASKED(LOG_CONFIG, "Clock register = %04x\n", m_clock_register); - timer_reload(); + LOGMASKED(LOG_CLOCK, "Clock register = %04x\n", m_clock_register); } else - { /* modify interrupt enable mask */ - int bit = 1 << (offset & 0x0F); /* corresponding mask */ - - if (data) - m_enabled_ints |= bit; /* set bit */ - else - m_enabled_ints &= ~bit; /* unset bit */ + { + // Modify interrupt enable mask + set_bit(m_int_mask, crubit, data!=0); - if (offset == 3) - m_timer_int_pending = false; /* SBO 3 clears pending timer interrupt (??) */ + // [1] sect 2.5: "When the clock interrupt is active, the clock mask + // (mask bit 3) must be written into (with either a "0" or "1") + // to clear the interrupt." + if (crubit == 3) + m_timer_int_pending = false; - LOGMASKED(LOG_CONFIG, "Enabled interrupts = %04x\n", m_enabled_ints); - field_interrupts(); /* changed interrupt state */ + LOGMASKED(LOG_MASK, "/INT%d is %s\n", crubit, data? "enabled" : "disabled"); + prioritize_interrupts(); } + break; } } @@ -509,28 +436,30 @@ void tms9901_device::write(offs_t offset, uint8_t data) Timer callback Decrementer counts down the value set in clock mode; when it reaches 0, raises an interrupt and resets to the start value - The decrementer works as long as the clock_register contains a non-zero value. */ void tms9901_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { if (id==DECREMENTER) // we have only that one { - clock_in(ASSERT_LINE); - clock_in(CLEAR_LINE); + timer_clock_in(ASSERT_LINE); + timer_clock_in(CLEAR_LINE); } } -void tms9901_device::clock_in(line_state clk) +void tms9901_device::timer_clock_in(line_state clk) { - if (m_clock_active && clk == ASSERT_LINE) + if (clk == ASSERT_LINE) { - m_decrementer_value--; - LOGMASKED(LOG_DECVALUE, "Decrementer = %d\n", m_decrementer_value); - if (m_decrementer_value<=0) + m_decrementer_value = (m_decrementer_value - 1) & 0x3FFF; + LOGMASKED(LOG_CLOCK, "Clock = %04x\n", m_decrementer_value); + if (m_decrementer_value==0) { - LOGMASKED(LOG_INT, "Timer expired\n"); - m_timer_int_pending = true; // decrementer interrupt requested - field_interrupts(); + if (m_clock_register != 0) + { + LOGMASKED(LOG_INT, "Timer expired\n"); + m_timer_int_pending = true; // decrementer interrupt requested + prioritize_interrupts(); + } m_decrementer_value = m_clock_register; } } @@ -543,12 +472,69 @@ void tms9901_device::clock_in(line_state clk) */ WRITE_LINE_MEMBER( tms9901_device::phi_line ) { - // Divider by 64 if (state==ASSERT_LINE) - m_clockdiv = (m_clockdiv+1) % 0x40; + { + // Divider by 64 + m_clockdiv = (m_clockdiv+1) & 0x3f; + if (m_clockdiv==0) + { + timer_clock_in(ASSERT_LINE); + + // We signal the interrupt in sync with the clock line + signal_int(); - if (m_clockdiv==0) - clock_in((line_state)state); + // For the next phi assert + // MZ: This costs a lot of performance for a minimum of benefit. + if (m_poll_lines) sample_interrupt_inputs(); + } + else + { + if (m_clockdiv==32) + timer_clock_in(CLEAR_LINE); + } + } +} + +/* + All unmasked interrupt ports are sampled at the rising edge of phi. + Doing it this way (also for performance issues): For each mask bit 1, + fetch the pin level. Stop at the first asserted INT line. + + Good idea in terms of emulation, bad in terms of performance. The Geneve + 9640 bench dropped from 680% to 180%. Not recommended to use, except in + very special situations. Enable by calling set_poll_int_lines(true). +*/ +void tms9901_device::sample_interrupt_inputs() +{ + int mask = m_int_mask; + m_int_level = 0; + m_int_pending = false; + + while (mask != 0 && !m_int_pending) + { + m_int_level++; + if ((mask & 1)!=0) + { + // Negative logic + if (m_read_port(m_int_level)==0) + m_int_pending = true; + } + mask >>= 1; + } +} + +void tms9901_device::soft_reset() +{ + // TMS9901 soft reset (RST2*) + // Spec: "Writing a 0 to bit 15 while in the clock mode executes a soft reset on the I/O pins. + // [...] RST2* will program all ports to the input mode" + m_pio_direction = 0; + m_pio_output = 0; + + // We assume that the interrupt mask is also reset. + m_int_mask = 0; + + LOGMASKED(LOG_MODE, "Soft reset (RST2*)\n"); } /*------------------------------------------------- @@ -579,23 +565,16 @@ WRITE_LINE_MEMBER( tms9901_device::rst1_line ) void tms9901_device::do_reset() { m_timer_int_pending = false; - m_enabled_ints = 0; - - m_pio_direction = 0; - m_pio_direction_mirror = 0; - m_pio_output = m_pio_output_mirror = 0; + soft_reset(); // This is an interrupt level latch, positive logic (bit 0 = no int) // The inputs are negative logic (INTx*) - m_int_state = 0; + m_int_line = 0; - m_old_int_state = -1; - field_interrupts(); + prioritize_interrupts(); m_clock_mode = false; - - m_clock_register = 0; - timer_reload(); + m_decrementer_value = m_clock_register = 0; } @@ -612,22 +591,21 @@ void tms9901_device::device_start() m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.)); } - m_read_block.resolve(); + m_read_port.resolve(); for (auto &cb : m_write_p) cb.resolve_safe(); m_interrupt.resolve(); m_clock_register = 0; - save_item(NAME(m_int_state)); - save_item(NAME(m_old_int_state)); - save_item(NAME(m_enabled_ints)); + save_item(NAME(m_int_line)); + save_item(NAME(m_pio_output)); + save_item(NAME(m_pio_direction)); + save_item(NAME(m_int_level)); + save_item(NAME(m_int_mask)); + save_item(NAME(m_last_level)); save_item(NAME(m_int_pending)); save_item(NAME(m_timer_int_pending)); - save_item(NAME(m_pio_direction)); - save_item(NAME(m_pio_output)); - save_item(NAME(m_pio_direction_mirror)); - save_item(NAME(m_pio_output_mirror)); save_item(NAME(m_clock_mode)); save_item(NAME(m_clock_register)); save_item(NAME(m_decrementer_value)); diff --git a/src/devices/machine/tms9901.h b/src/devices/machine/tms9901.h index eda01f253ad..a962ceb28f7 100644 --- a/src/devices/machine/tms9901.h +++ b/src/devices/machine/tms9901.h @@ -29,55 +29,65 @@ DECLARE_DEVICE_TYPE(TMS9901, tms9901_device) class tms9901_device : public device_t { public: - // Masks for the interrupts levels available on TMS9901 - static constexpr int INT1 = 0x0002; - static constexpr int INT2 = 0x0004; - static constexpr int INT3 = 0x0008; // overridden by the timer interrupt - static constexpr int INT4 = 0x0010; - static constexpr int INT5 = 0x0020; - static constexpr int INT6 = 0x0040; - static constexpr int INT7 = 0x0080; - static constexpr int INT8 = 0x0100; - static constexpr int INT9 = 0x0200; - static constexpr int INTA = 0x0400; - static constexpr int INTB = 0x0800; - static constexpr int INTC = 0x1000; - static constexpr int INTD = 0x2000; - static constexpr int INTE = 0x4000; - static constexpr int INTF = 0x8000; - + // I/O pins enum { - CB_INT7 = 0, - INT8_INT15 = 1, - P0_P7 = 2, - P8_P15 = 3 - }; + INT1=1, + INT2, + INT3, + INT4, + INT5, + INT6, + INT7_P15, + INT8_P14, + INT9_P13, + INT10_P12, + INT11_P11, + INT12_P10, + INT13_P9, + INT14_P8, + INT15_P7, + P0, + P1, + P2, + P3, + P4, + P5, + P6 + } pins; tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - void set_single_int(int pin_number, int state); + void set_int_line(int pin_number, int state); DECLARE_WRITE_LINE_MEMBER( rst1_line ); // Synchronous clock input DECLARE_WRITE_LINE_MEMBER( phi_line ); - uint8_t read(offs_t offset); - void write(offs_t offset, uint8_t data); + DECLARE_READ8_MEMBER( read ); + DECLARE_WRITE8_MEMBER( write ); auto p_out_cb(int n) { return m_write_p[n].bind(); } - auto read_cb() { return m_read_block.bind(); } - auto intlevel_cb() { return m_interrupt.bind(); } + auto read_cb() { return m_read_port.bind(); } + auto intreq_cb() { return m_interrupt.bind(); } + + // Pins IC3...IC0 + // When no interrupt is active, the IC lines are all set to 1 + // The difference to /INT15 is that INTREQ is cleared. + int get_int_level() { return m_int_pending? m_int_level : 15; } + // Return PIO all outputs; ports configured as inputs return 1 + // Used by si5500 uint16_t pio_outputs() const { return m_pio_output | ~m_pio_direction; } + void set_poll_int_lines(bool poll) { m_poll_lines = poll; } + private: static constexpr device_timer_id DECREMENTER = 0; virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; - void timer_reload(); - void field_interrupts(); + void soft_reset(); void device_start() override; void device_stop() override; @@ -87,62 +97,76 @@ private: void do_reset(); // Common clock handling - void clock_in(line_state clk); + void timer_clock_in(line_state clk); + + // Direction and value of P pins + bool is_output(int p); + bool output_value(int p); + void set_and_latch_output(int p, bool val); + void set_bit(uint16_t& bitfield, int pos, bool val); + + int m_int_level; + int m_last_level; // State of the INT1-INT15 lines (must be inverted when queried) // Note that the levels must also be delivered when reading the pins, which - // may require to latch the int levels. - int m_int_state; - int m_old_int_state; // stores the previous value to avoid useless INT line assertions - int m_enabled_ints; // interrupt enable mask + // may require to latch the int levels on the caller's side. + uint16_t m_int_line; + uint16_t m_int_mask; + bool m_int_pending; + bool m_poll_lines; - bool m_int_pending; // status of the int* pin (connected to TMS9900) - bool m_timer_int_pending; // timer int pending (overrides int3 pin if timer enabled) + // Find the interrupt with the lowest level (most important) + void prioritize_interrupts(); - bool m_clock_active; - int m_clockdiv; // Clock divider counter (for synchronous clock) + // Outgoing INTREQ* line + void signal_int(); - // PIO registers - int m_pio_direction; // direction register for PIO + // Sample the interrupt inputs. + void sample_interrupt_inputs(); - // current PIO output (to be masked with pio_direction) - int m_pio_output; + // P15..P0; 1=output H, 0=output L + uint16_t m_pio_output; - // mirrors used for INT7*-INT15* - int m_pio_direction_mirror; - int m_pio_output_mirror; + // For P15..P0; 1=output, 0=input + // Once set to 1, a reset is necessary to return the pin to input + uint16_t m_pio_direction; // ======================================================================= // TMS9901 clock mode - // false = so-called interrupt mode (read interrupt state, write interrupt enable mask) + // false = interrupt mode (read interrupt state, write interrupt enable mask) // true = clock mode (read/write clock interval) bool m_clock_mode; + // Clock divider + int m_clockdiv; + + // Clock has reached 0 + bool m_timer_int_pending; + // Timer, used to emulate the decrementer register emu_timer *m_decrementer; - // clock interval, loaded in decrementer when it reaches 0. - // 0 means decrementer off - int m_clock_register; + // Clock interval, loaded in decrementer when it reaches 0. + uint16_t m_clock_register; // Current decrementer value - int m_decrementer_value; + uint16_t m_decrementer_value; // when we go into timer mode, the decrementer is copied there to allow to read it reliably - int m_clock_read_register; + uint16_t m_clock_read_register; // ======================================================================= // Read callback. - devcb_read8 m_read_block; + devcb_read8 m_read_port; // I/O lines, used for output. When used as inputs, the levels are delivered via the m_read_block devcb_write_line m_write_p[16]; - // The invocation corresponds to the INTREQ signal (with the level passed as data) - // and the address delivers the interrupt level (0-15) - devcb_write8 m_interrupt; + // INTREQ pin + devcb_write_line m_interrupt; }; #endif // MAME_MACHINE_TMS9901_H diff --git a/src/mame/drivers/geneve.cpp b/src/mame/drivers/geneve.cpp index 84f3f28e12b..44ac3436f68 100644 --- a/src/mame/drivers/geneve.cpp +++ b/src/mame/drivers/geneve.cpp @@ -203,7 +203,7 @@ #define LOG_CRU (1U<<4) // Minimum log should be settings and warnings -#define VERBOSE (LOG_GENERAL | LOG_WARN) +#define VERBOSE ( LOG_GENERAL | LOG_WARN ) #include "logmacro.h" @@ -234,7 +234,7 @@ private: void cruwrite(offs_t offset, uint8_t data); // Connections with the system interface TMS9901 - uint8_t read_by_9901(offs_t offset); + uint8_t psi_input(offs_t offset); DECLARE_WRITE_LINE_MEMBER(peripheral_bus_reset); DECLARE_WRITE_LINE_MEMBER(VDP_reset); DECLARE_WRITE_LINE_MEMBER(joystick_select); @@ -453,65 +453,104 @@ uint8_t geneve_state::cruread(offs_t offset) CRU callbacks ***********************************************************************/ -uint8_t geneve_state::read_by_9901(offs_t offset) +uint8_t geneve_state::psi_input(offs_t offset) { - int answer = 0; - - switch (offset & 0x03) + switch (offset) { - case tms9901_device::CB_INT7: - // - // Read pins INT3*-INT7* of Geneve's 9901. - // bit 1: INTA status - // bit 2: INT2 status - // bit 3-7: joystick status - // - // |K|K|K|K|K|I2|I1|C| - // negative logic - if (m_inta==CLEAR_LINE) answer |= 0x02; - if (m_int2==CLEAR_LINE) answer |= 0x04; - answer |= m_joyport->read_port()<<3; - break; - - case tms9901_device::INT8_INT15: - // Read pins int8_t*-INT15* of Geneve 9901. - // - // bit 0: keyboard interrupt - // bit 1: unused - // bit 2: mouse left button - // (bit 3: clock interrupt) - // bit 4: INTB from PE-bus - // bit 5 & 7: used as output - // bit 6: unused - if (m_keyint==CLEAR_LINE) answer |= 0x01; - if (m_colorbus->left_button()==CLEAR_LINE) answer |= 0x04; - // TODO: add clock interrupt - if (m_intb==CLEAR_LINE) answer |= 0x10; - if (m_video_wait==ASSERT_LINE) answer |= 0x20; - // TODO: PAL pin 5 - LOGMASKED(LOG_LINES, "INT15-8 = %02x\n", answer); - break; - - case tms9901_device::P0_P7: - // Read pins P0-P7 of TMS9901. All pins are configured as outputs, so nothing here. - break; - - case tms9901_device::P8_P15: - // Read pins P8-P15 of TMS 9901. - // bit 4: mouse left button - // video wait is an output; no input possible here - if (m_intb==CLEAR_LINE) answer |= 0x04; // mirror from above - // TODO: 0x08 = real-time clock int - if (m_colorbus->left_button()==CLEAR_LINE) answer |= 0x10; // mirror from above - if (m_keyint==CLEAR_LINE) answer |= 0x40; - - // Joystick up (mirror of bit 7) - if ((m_joyport->read_port() & 0x10)==0) answer |= 0x80; - break; + // External interrupt (INTA) + case tms9901_device::INT1: + return (m_inta==CLEAR_LINE)? 1 : 0; + + // Video interrupt + case tms9901_device::INT2: + return (m_int2==CLEAR_LINE)? 1 : 0; + + // Joystick port + case tms9901_device::INT3: + case tms9901_device::INT4: + case tms9901_device::INT5: + case tms9901_device::INT6: + case tms9901_device::INT7_P15: + return BIT(m_joyport->read_port(), offset-tms9901_device::INT3); + + // Keyboard interrupt + case tms9901_device::INT8_P14: + return (m_keyint==CLEAR_LINE)? 1 : 0; + + // Left mouse button + case tms9901_device::INT10_P12: + LOG("Mouse button = %d\n", m_colorbus->left_button()); + return (m_colorbus->left_button()==CLEAR_LINE)? 1 : 0; + + // TODO: Real time clock interrupt + case tms9901_device::INT11_P11: + return 1; + + // INTB interrupt + case tms9901_device::INT12_P10: + return (m_intb==CLEAR_LINE)? 1 : 0; + + default: + LOG("Unknown pin %d\n", offset); + return 1; } - return answer; } +/* + switch (offset & 0x03) + { + case tms9901_device::CB_INT7: + // + // Read pins INT3*-INT7* of Geneve's 9901. + // bit 1: INTA status + // bit 2: INT2 status + // bit 3-7: joystick status + // + // |K|K|K|K|K|I2|I1|C| + // negative logic + if (m_inta==CLEAR_LINE) answer |= 0x02; + if (m_int2==CLEAR_LINE) answer |= 0x04; + answer |= m_joyport->read_port()<<3; + break; + + case tms9901_device::INT8_INT15: + // Read pins int8_t*-INT15* of Geneve 9901. + // + // bit 0: keyboard interrupt + // bit 1: unused + // bit 2: mouse left button + // (bit 3: clock interrupt) + // bit 4: INTB from PE-bus + // bit 5 & 7: used as output + // bit 6: unused + if (m_keyint==CLEAR_LINE) answer |= 0x01; + if (m_colorbus->left_button()==CLEAR_LINE) answer |= 0x04; + // TODO: add clock interrupt + if (m_intb==CLEAR_LINE) answer |= 0x10; + if (m_video_wait==ASSERT_LINE) answer |= 0x20; + // TODO: PAL pin 5 + LOGMASKED(LOG_LINES, "INT15-8 = %02x\n", answer); + break; + + case tms9901_device::P0_P7: + // Read pins P0-P7 of TMS9901. All pins are configured as outputs, so nothing here. + break; + + case tms9901_device::P8_P15: + // Read pins P8-P15 of TMS 9901. + // bit 4: mouse left button + // video wait is an output; no input possible here + if (m_intb==CLEAR_LINE) answer |= 0x04; // mirror from above + // TODO: 0x08 = real-time clock int + if (m_colorbus->left_button()==CLEAR_LINE) answer |= 0x10; // mirror from above + if (m_keyint==CLEAR_LINE) answer |= 0x40; + + // Joystick up (mirror of bit 7) + if ((m_joyport->read_port() & 0x10)==0) answer |= 0x80; + break; + } + */ + /* Write PE bus reset line */ @@ -577,7 +616,7 @@ void geneve_state::tms9901_interrupt(offs_t offset, uint8_t data) WRITE_LINE_MEMBER( geneve_state::inta ) { m_inta = (state!=0)? ASSERT_LINE : CLEAR_LINE; - m_tms9901->set_single_int(1, state); + m_tms9901->set_int_line(1, state); m_cpu->set_input_line(INT_9995_INT4, state); } @@ -587,7 +626,7 @@ WRITE_LINE_MEMBER( geneve_state::inta ) WRITE_LINE_MEMBER( geneve_state::intb ) { m_intb = (state!=0)? ASSERT_LINE : CLEAR_LINE; - m_tms9901->set_single_int(12, state); + m_tms9901->set_int_line(12, state); } WRITE_LINE_MEMBER( geneve_state::ext_ready ) @@ -614,7 +653,7 @@ WRITE_LINE_MEMBER(geneve_state::set_tms9901_INT2_from_v9938) if (state != m_int2) { m_int2 = (state!=0)? ASSERT_LINE : CLEAR_LINE; - m_tms9901->set_single_int(2, state); + m_tms9901->set_int_line(2, state); if (state!=0) { m_colorbus->poll(); @@ -628,7 +667,7 @@ WRITE_LINE_MEMBER(geneve_state::set_tms9901_INT2_from_v9938) WRITE_LINE_MEMBER( geneve_state::keyboard_interrupt ) { m_keyint = (state!=0)? ASSERT_LINE : CLEAR_LINE; - m_tms9901->set_single_int(8, state); + m_tms9901->set_int_line(8, state); } void geneve_state::external_operation(offs_t offset, uint8_t data) @@ -741,7 +780,7 @@ void geneve_state::geneve_common(machine_config &config) // Main board components TMS9901(config, m_tms9901, 0); - m_tms9901->read_cb().set(FUNC(geneve_state::read_by_9901)); + m_tms9901->read_cb().set(FUNC(geneve_state::psi_input)); m_tms9901->p_out_cb(0).set(FUNC(geneve_state::peripheral_bus_reset)); m_tms9901->p_out_cb(1).set(FUNC(geneve_state::VDP_reset)); m_tms9901->p_out_cb(2).set(FUNC(geneve_state::joystick_select)); @@ -751,7 +790,7 @@ void geneve_state::geneve_common(machine_config &config) m_tms9901->p_out_cb(7).set(FUNC(geneve_state::extbus_wait_states)); m_tms9901->p_out_cb(9).set(FUNC(geneve_state::video_wait_states)); m_tms9901->p_out_cb(13).set(GENEVE_MAPPER_TAG, FUNC(bus::ti99::internal::geneve_mapper_device::pfm_select_msb)); - m_tms9901->intlevel_cb().set(FUNC(geneve_state::tms9901_interrupt)); + m_tms9901->intreq_cb().set(FUNC(geneve_state::tms9901_interrupt)); // Clock MM58274C(config, GENEVE_CLOCK_TAG, 0).set_mode_and_day(1, 0); // 24h, sunday diff --git a/src/mame/drivers/si5500.cpp b/src/mame/drivers/si5500.cpp index 605466f5a07..e9ed91ab180 100644 --- a/src/mame/drivers/si5500.cpp +++ b/src/mame/drivers/si5500.cpp @@ -72,20 +72,31 @@ void si5500_state::mainic_w(offs_t offset, u8 data) WRITE_LINE_MEMBER(si5500_state::gpib_int_w) { - m_mainpsi->set_single_int(4, state); + m_mainpsi->set_int_line(4, state); } WRITE_LINE_MEMBER(si5500_state::acc_int_w) { - m_mainpsi->set_single_int(5, state); + m_mainpsi->set_int_line(5, state); } u8 si5500_state::gpibpsi_input_r(offs_t offset) { - if (offset == tms9901_device::P0_P7) - return m_gpib_data; - - return 0xff; + switch (offset) + { + case tms9901_device::P0: + case tms9901_device::P1: + case tms9901_device::P2: + case tms9901_device::P3: + case tms9901_device::P4: + case tms9901_device::P5: + case tms9901_device::P6: + return BIT(m_gpib_data, offset-tms9901_device::P0); + case tms9901_device::INT15_P7: + return BIT(m_gpib_data, 8); + default: + return 1; + } } WRITE_LINE_MEMBER(si5500_state::gpibc_we_w) @@ -175,7 +186,7 @@ void si5500_state::si5500(machine_config &config) m_maincpu->set_addrmap(AS_IO, &si5500_state::cru_map); TMS9901(config, m_mainpsi, 10_MHz_XTAL / 4); - m_mainpsi->intlevel_cb().set(FUNC(si5500_state::mainic_w)); + m_mainpsi->intreq_cb().set(FUNC(si5500_state::mainic_w)); tms9902_device &acc(TMS9902(config, "acc", 10_MHz_XTAL / 4)); acc.int_cb().set(FUNC(si5500_state::acc_int_w)); diff --git a/src/mame/drivers/ti99_4p.cpp b/src/mame/drivers/ti99_4p.cpp index 3b0be43a5b4..215916f68e6 100644 --- a/src/mame/drivers/ti99_4p.cpp +++ b/src/mame/drivers/ti99_4p.cpp @@ -132,12 +132,12 @@ #define LOG_WARN (1U<<1) // Warnings #define LOG_ILLWRITE (1U<<2) #define LOG_READY (1U<<3) -#define LOG_INT (1U<<4) +#define LOG_INTERRUPTS (1U<<4) #define LOG_ADDRESS (1U<<5) #define LOG_MEM (1U<<6) #define LOG_MUX (1U<<7) -#define VERBOSE ( LOG_WARN ) +#define VERBOSE ( LOG_GENERAL | LOG_WARN ) #include "logmacro.h" @@ -177,7 +177,7 @@ private: // CRU (Communication Register Unit) handling uint8_t cruread(offs_t offset); void cruwrite(offs_t offset, uint8_t data); - uint8_t read_by_9901(offs_t offset); + uint8_t psi_input(offs_t offset); DECLARE_WRITE_LINE_MEMBER(keyC0); DECLARE_WRITE_LINE_MEMBER(keyC1); DECLARE_WRITE_LINE_MEMBER(keyC2); @@ -195,6 +195,10 @@ private: void datamux_clock_in(int clock); + // Latch for 9901 INT1 and INT2 lines + int m_int1; + int m_int2; + // Devices required_device m_cpu; required_device m_tms9901; @@ -217,7 +221,7 @@ private: uint16_t *m_rom; // First joystick. 6 for TI-99/4A - int m_firstjoy; + static constexpr int FIRSTJOY=6; int m_keyboard_column; int m_check_alphalock; @@ -274,10 +278,6 @@ private: // Mapper registers uint8_t m_mapper[16]; - // Latch for 9901 INT2, INT1 lines - int m_9901_int; - void set_9901_int(int line, line_state state); - int m_ready_prev; // for debugging purposes only }; @@ -377,7 +377,7 @@ static INPUT_PORTS_START(ti99_4p) PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_SLASH) PORT_CHAR('/') PORT_CHAR('-') PORT_START("ALPHA") /* one more port for Alpha line */ - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Alpha Lock") PORT_CODE(KEYCODE_CAPSLOCK) PORT_TOGGLE + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Alpha Lock") PORT_CODE(KEYCODE_CAPSLOCK) PORT_TOGGLE INPUT_PORTS_END @@ -725,61 +725,103 @@ uint8_t ti99_4p_state::cruread(offs_t offset) Keyboard/tape control ****************************************************************************/ -uint8_t ti99_4p_state::read_by_9901(offs_t offset) +uint8_t ti99_4p_state::psi_input(offs_t offset) { - int answer=0; - - switch (offset & 0x03) + switch (offset) { - case tms9901_device::CB_INT7: - // Read pins INT3*-INT7* of TI99's 9901. - // bit 1: INT1 status - // bit 2: INT2 status - // bit 3-7: keyboard status bits 0 to 4 - // - // |K|K|K|K|K|I2|I1|C| - // - if (m_keyboard_column >= m_firstjoy) // joy 1 and 2 - { - answer = m_joyport->read_port(); - } + case tms9901_device::INT1: + return (m_int1==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT2: + return (m_int2==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT3: + case tms9901_device::INT4: + case tms9901_device::INT5: + case tms9901_device::INT6: + // Keyboard ACTIVE_LOW (s.o.) + // Joysticks ACTIVE_LOW (handset.cpp) + if (m_keyboard_column >= 6) + return BIT(m_joyport->read_port(), offset-tms9901_device::INT3); + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + + case tms9901_device::INT7_P15: + if (m_keyboard_column >= 6) // Joysticks + return BIT(m_joyport->read_port(), offset-tms9901_device::INT3); else { - answer = m_keyboard[m_keyboard_column]->read(); - } - if (m_check_alphalock) - { - answer &= ~(m_alpha->read()); + if (m_check_alphalock) + { + return BIT(m_alpha->read(), offset-tms9901_device::INT3); + } + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); } - answer = (answer << 3) | m_9901_int; - break; - - case tms9901_device::INT8_INT15: - // Read pins int8_t*-INT15* of TI99's 9901. - // bit 0-2: keyboard status bits 5 to 7 - // bit 3: tape input mirror - // bit 5-7: weird, not emulated - // |1|1|1|1|0|K|K|K| - if (m_keyboard_column >= m_firstjoy) answer = 0x07; - else answer = ((m_keyboard[m_keyboard_column]->read())>>5) & 0x07; - answer |= 0xf0; - break; - - case tms9901_device::P0_P7: - break; - - case tms9901_device::P8_P15: - // Read pins P8-P15 of TI99's 9901. - // bit 26: high - // bit 27: tape input - answer = 4; - if (m_cassette->input() > 0) answer |= 8; - break; + case tms9901_device::INT8_P14: + case tms9901_device::INT9_P13: + case tms9901_device::INT10_P12: + if (m_keyboard_column >= FIRSTJOY) // no joystick lines after /INT7 + return 1; + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + case tms9901_device::INT11_P11: + // CS2 is write-only + return (m_cassette->input() > 0); + default: + return 1; } - return answer; } +/* switch (offset & 0x03) + { + case tms9901_device::CB_INT7: + // Read pins INT3*-INT7* of TI99's 9901. + // bit 1: INT1 status + // bit 2: INT2 status + // bit 3-7: keyboard status bits 0 to 4 + // + // |K|K|K|K|K|I2|I1|C| + // + if (m_keyboard_column >= FIRSTJOY) // joy 1 and 2 + { + answer = m_joyport->read_port(); + } + else + { + answer = m_keyboard[m_keyboard_column]->read(); + } + if (m_check_alphalock) + { + answer &= ~(m_alpha->read()); + } + answer = (answer << 3) | m_9901_int; + break; + + case tms9901_device::INT8_INT15: + // Read pins int8_t*-INT15* of TI99's 9901. + // bit 0-2: keyboard status bits 5 to 7 + // bit 3: tape input mirror + // bit 5-7: weird, not emulated + + // |1|1|1|1|0|K|K|K| + if (m_keyboard_column >= FIRSTJOY) answer = 0x07; + else answer = ((m_keyboard[m_keyboard_column]->read())>>5) & 0x07; + answer |= 0xf0; + break; + + case tms9901_device::P0_P7: + break; + + case tms9901_device::P8_P15: + // Read pins P8-P15 of TI99's 9901. + // bit 26: high + // bit 27: tape input + answer = 4; + if (m_cassette->input() > 0) answer |= 8; + break; + } + */ + /* WRITE key column select (P2-P4) */ @@ -788,9 +830,9 @@ void ti99_4p_state::set_keyboard_column(int number, int data) if (data!=0) m_keyboard_column |= 1 << number; else m_keyboard_column &= ~(1 << number); - if (m_keyboard_column >= m_firstjoy) + if (m_keyboard_column >= FIRSTJOY) { - m_joyport->write_port(m_keyboard_column - m_firstjoy + 1); + m_joyport->write_port(m_keyboard_column - FIRSTJOY + 1); } } @@ -874,23 +916,16 @@ WRITE_LINE_MEMBER( ti99_4p_state::ready_line ) ready_join(); } -void ti99_4p_state::set_9901_int( int line, line_state state) -{ - m_tms9901->set_single_int(line, state); - // We latch the value for the read operation. Mind the negative logic. - if (state==CLEAR_LINE) m_9901_int |= (1<set_int_line(1, state); } WRITE_LINE_MEMBER( ti99_4p_state::notconnected ) { - LOGMASKED(LOG_INT, "Setting a not connected line ... ignored\n"); + LOGMASKED(LOG_INTERRUPTS, "Setting a not connected line ... ignored\n"); } /* @@ -931,14 +966,13 @@ void ti99_4p_state::driver_start() m_peribox->senila(CLEAR_LINE); m_peribox->senilb(CLEAR_LINE); - m_firstjoy = 6; - m_sysready = ASSERT_LINE; m_muxready = true; m_rom = (uint16_t*)(memregion("maincpu")->base()); - save_item(NAME(m_firstjoy)); + save_item(NAME(m_int1)); + save_item(NAME(m_int2)); save_item(NAME(m_keyboard_column)); save_item(NAME(m_check_alphalock)); save_item(NAME(m_internal_dsr)); @@ -960,7 +994,6 @@ void ti99_4p_state::driver_start() save_item(NAME(m_highbyte)); save_item(NAME(m_latch)); save_pointer(NAME(m_mapper),16); - save_item(NAME(m_9901_int)); } /* @@ -968,7 +1001,9 @@ void ti99_4p_state::driver_start() */ WRITE_LINE_MEMBER(ti99_4p_state::video_interrupt_in) { - set_9901_int(2, (line_state)state); + LOGMASKED(LOG_INTERRUPTS, "VDP INT2 from EVPC on tms9901, level=%d\n", state); + m_int2 = (line_state)state; + m_tms9901->set_int_line(2, state); } /* @@ -976,11 +1011,9 @@ WRITE_LINE_MEMBER(ti99_4p_state::video_interrupt_in) */ void ti99_4p_state::driver_reset() { - set_9901_int(12, CLEAR_LINE); - m_cpu->set_ready(ASSERT_LINE); m_cpu->set_hold(CLEAR_LINE); - m_9901_int = 0x03; // INT2* and INT1* set to 1, i.e. inactive + m_int1 = m_int2 = CLEAR_LINE; } /* @@ -1000,7 +1033,7 @@ void ti99_4p_state::ti99_4p_60hz(machine_config& config) // tms9901 TMS9901(config, m_tms9901, 0); - m_tms9901->read_cb().set(FUNC(ti99_4p_state::read_by_9901)); + m_tms9901->read_cb().set(FUNC(ti99_4p_state::psi_input)); m_tms9901->p_out_cb(2).set(FUNC(ti99_4p_state::keyC0)); m_tms9901->p_out_cb(3).set(FUNC(ti99_4p_state::keyC1)); m_tms9901->p_out_cb(4).set(FUNC(ti99_4p_state::keyC2)); @@ -1008,7 +1041,7 @@ void ti99_4p_state::ti99_4p_60hz(machine_config& config) m_tms9901->p_out_cb(6).set(FUNC(ti99_4p_state::cs_motor)); m_tms9901->p_out_cb(8).set(FUNC(ti99_4p_state::audio_gate)); m_tms9901->p_out_cb(9).set(FUNC(ti99_4p_state::cassette_output)); - m_tms9901->intlevel_cb().set(FUNC(ti99_4p_state::tms9901_interrupt)); + m_tms9901->intreq_cb().set(FUNC(ti99_4p_state::tms9901_interrupt)); // Peripheral expansion box (SGCPU composition) TI99_PERIBOX_SG(config, m_peribox, 0); diff --git a/src/mame/drivers/ti99_4x.cpp b/src/mame/drivers/ti99_4x.cpp index 6a7bf511754..8091947f556 100644 --- a/src/mame/drivers/ti99_4x.cpp +++ b/src/mame/drivers/ti99_4x.cpp @@ -65,7 +65,7 @@ #define LOG_CRUREAD (1U<<6) #define LOG_RESETLOAD (1U<<7) -#define VERBOSE ( LOG_CONFIG | LOG_WARN | LOG_RESETLOAD ) +#define VERBOSE ( LOG_GENERAL | LOG_CONFIG | LOG_WARN | LOG_RESETLOAD ) #include "logmacro.h" @@ -145,14 +145,15 @@ private: DECLARE_WRITE_LINE_MEMBER( handset_interrupt_in ); // Connections with the system interface TMS9901 - uint8_t read_by_9901(offs_t offset); + DECLARE_READ8_MEMBER(psi_input_4); + DECLARE_READ8_MEMBER(psi_input_4a); DECLARE_WRITE_LINE_MEMBER(keyC0); DECLARE_WRITE_LINE_MEMBER(keyC1); DECLARE_WRITE_LINE_MEMBER(keyC2); DECLARE_WRITE_LINE_MEMBER(cs1_motor); DECLARE_WRITE_LINE_MEMBER(audio_gate); DECLARE_WRITE_LINE_MEMBER(cassette_output); - void tms9901_interrupt(offs_t offset, uint8_t data); + DECLARE_WRITE_LINE_MEMBER(tms9901_interrupt); DECLARE_WRITE_LINE_MEMBER(handset_ack); DECLARE_WRITE_LINE_MEMBER(cs2_motor); DECLARE_WRITE_LINE_MEMBER(alphaW); @@ -387,13 +388,13 @@ static INPUT_PORTS_START(ti99_4a) PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_SLASH) PORT_CHAR('/') PORT_CHAR('-') PORT_START("ALPHA") /* one more port for Alpha line */ - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Alpha Lock") PORT_CODE(KEYCODE_CAPSLOCK) PORT_TOGGLE + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Alpha Lock") PORT_CODE(KEYCODE_CAPSLOCK) PORT_TOGGLE /* another version of Alpha Lock which is non-toggling; this is useful when we want to attach a real TI keyboard for input. For home computers, the Alpha Lock / Shift Lock was a physically locking key. */ PORT_START("ALPHA1") - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Alpha Lock non-toggle") PORT_CODE(KEYCODE_RWIN) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Alpha Lock non-toggle") PORT_CODE(KEYCODE_RWIN) INPUT_PORTS_END @@ -436,101 +437,208 @@ void ti99_4x_state::external_operation(offs_t offset, uint8_t data) /*************************************************************************** TI99/4x-specific tms9901 I/O handlers - See mess/machine/tms9901.c for generic tms9901 CRU handlers. - - TMS9901 interrupt handling on a TI99/4(a). - - TI99/4(a) uses the following interrupts: - INT1: external interrupt (used by RS232 controller, for instance) - INT2: VDP interrupt - TMS9901 timer interrupt (overrides INT3) - INT12: handset interrupt (only on a TI-99/4 with the handset prototypes) - - Three (occasionally four) interrupts are used by the system (INT1, INT2, - timer, and INT12 on a TI-99/4 with remote handset prototypes), out of 15/16 - possible interrupts. Keyboard pins can be used as interrupt pins, too, but - this is not emulated (it's a trick, anyway, and I don't know any program - which uses it). - - When an interrupt line is set (and the corresponding bit in the interrupt mask is set), - a level 1 interrupt is requested from the TMS9900. This interrupt request lasts as long as - the interrupt pin and the relevant bit in the interrupt mask are set. - + Bit Meaning + 0 - (control) - + 1 /INT1 (input) EXTINT + 2 /INT2 (input) VDP + 3 /INT3 (input) Keyboard = line / Joystick button + 4 /INT4 (input) Keyboard Space line / Joystick left + 5 /INT5 (input) Keyboard Enter line / Joystick right + 6 /INT6 (input) Keyboard 0 line / Joystick down + 7 31 /INT7 P15 (input) Keyboard Fctn line / Joystick up / AlphaLock + + 8 30 /INT8 P14 (input) Keyboard Shift line + 9 29 /INT9 P13 (input) Keyboard Ctrl line + 10 28 /INT10 P12 (input) Keyboard Z line + 11 27 /INT11 P11 (input) Cassette audio + 12 26 /INT12 P10 (input) 99/4: Handset, 99/4A: 1 + 13 25 /INT13 P9 (output) Cassette audio (1?) + 14 24 /INT14 P8 (output) Audio Gate (1?) + 15 23 /INT15 P7 (output) Motor CS2 (1?) + + 16 P0 (output) 99/4: Handset ack (1?) + 17 P1 (input) 99/4: Handset (1?) + 18 P2 (output) Key col 0 (1?) + 19 P3 (output) Key col 1 (1?) + 20 P4 (output) Key col 2 (1?) + 21 P5 (output) AlphaLock select (1) + 22 P6 (output) Motor CS1 (1?) + + enum { INT1, ... INT7_P15, INT8_P14, ..., P5, P6 } + + The hardware bug of the TI-99/4A keyboard: You have to release the + AlphaLock key when using joysticks. + The AlphaLock key was obviously added to the 99/4 matrix in a quite adhoc + way; a separate 9901 line (P5) is used to deliver the 0 level to be routed + through the switch. When AlphaLock is depressed, it connects the /INT7 + line via two 470 ohm resistors to P5. When the AlphaLock key is not + scanned, P5 is 1, pulling up the /INT7 line. Moving the joystick lever up + should pull it down, but due to the additional resistance in the long + cable in the joystick, the sum resistance becomes too high to safely + pull down the level, and the 9901 does not sense a 0 on its /INT7 input. + + Alpha + P5 -----[470]-----/ + + | Joy up + /INT7--+--[470]-----+----[xxx]-----/ ---[280]--- 0 (if column=110 or 111) + | + +---[10k]--- 1 + pull-up + + The typical fix was to insert a diode at the Alphalock key. ***************************************************************************/ +READ8_MEMBER( ti99_4x_state::psi_input_4) +{ + switch (offset) + { + case tms9901_device::INT1: + return (m_int1==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT2: + return (m_int2==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT3: + case tms9901_device::INT4: + case tms9901_device::INT5: + case tms9901_device::INT6: + case tms9901_device::INT7_P15: + // Keyboard ACTIVE_LOW, Joysticks ACTIVE_LOW + if (m_keyboard_column >= 5) + return BIT(m_joyport->read_port(), offset-tms9901_device::INT3); + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + case tms9901_device::INT8_P14: + case tms9901_device::INT9_P13: + case tms9901_device::INT10_P12: + if (m_keyboard_column >= 5) // no joystick lines after /INT7 + return 1; + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + case tms9901_device::INT11_P11: + return (m_cassette1->input() > 0); + case tms9901_device::INT12_P10: + return (m_int12==CLEAR_LINE)? 1 : 0; + case tms9901_device::P1: + return BIT(m_joyport->read_port(), 5); // 0x20 + default: + return 1; + } +} -uint8_t ti99_4x_state::read_by_9901(offs_t offset) +READ8_MEMBER( ti99_4x_state::psi_input_4a ) { - int answer=0; + int alphabias=0; - switch (offset & 0x03) + switch (offset) { - case tms9901_device::CB_INT7: - // - // Read pins INT3*-INT7* of TI99's 9901. - // bit 1: INT1 status - // bit 2: INT2 status - // bit 3-7: keyboard status bits 0 to 4 - // - // |K|K|K|K|K|I2|I1|C| - // - if (m_keyboard_column >= (m_model==MODEL_4? 5:6)) // joy 1, 2, handset - { - answer = m_joyport->read_port(); - // The hardware bug of the TI-99/4A: you have to release the - // Alphalock key when using joysticks. This is a maldesign of the - // board: When none of the other keyboard lines are selected the - // depressed Alphalock key pulls up the /INT7 line which is also - // used for joystick up. The joystick switch then fails to lower - // the line enough to make the TMS9901 sense the low level. - // A reported, feasible fix was to cut the line and insert a diode - // below the Alphalock key. - if ((m_model!=MODEL_4) && (m_alphabug->read()!=0) ) answer |= (m_alpha->read() | m_alpha1->read()); - } + case tms9901_device::INT1: + return (m_int1==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT2: + return (m_int2==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT3: + case tms9901_device::INT4: + case tms9901_device::INT5: + case tms9901_device::INT6: + // Keyboard ACTIVE_LOW (s.o.) + // Joysticks ACTIVE_LOW (handset.cpp) + if (m_keyboard_column >= 6) + return BIT(m_joyport->read_port(), offset-tms9901_device::INT3); else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + + case tms9901_device::INT7_P15: + if (m_keyboard_column >= 6) // Joysticks { - answer = m_keyboard[m_keyboard_column]->read(); - } - if (m_check_alphalock) // never true for TI-99/4 - { - answer &= ~(m_alpha->read() | m_alpha1->read()); + // If the Alpha Lock bug is not fixed + if (m_alphabug->read()!=0) + alphabias = ~(m_alpha->read() & m_alpha1->read()); + + return BIT(m_joyport->read_port() | alphabias, offset-tms9901_device::INT3); } - answer = (answer << 3); - if (m_int1 == CLEAR_LINE) answer |= 0x02; - if (m_int2 == CLEAR_LINE) answer |= 0x04; - - break; - - case tms9901_device::INT8_INT15: - // |1|1|1|INT12|0|K|K|K| - if (m_keyboard_column >= (m_model==MODEL_4? 5:6)) answer = 0x07; - else answer = ((m_keyboard[m_keyboard_column]->read())>>5) & 0x07; - answer |= 0xe0; - if (m_model != MODEL_4 || m_int12==CLEAR_LINE) answer |= 0x10; - break; - - case tms9901_device::P0_P7: - // Required for the handset (only on TI-99/4) - if ((m_joyport->read_port() & 0x20)!=0) answer |= 2; - break; - - case tms9901_device::P8_P15: - // Preset to 1 - answer = 4; - - // Interrupt pin of the handset (only on TI-99/4) - // Negative logic (interrupt pulls line down) - if ((m_joyport->read_port() & 0x40)==0) answer = 0; - - // we don't take CS2 into account, as CS2 is a write-only unit - if (m_cassette1->input() > 0) + else { - answer |= 8; + if (m_check_alphalock) + { + return BIT(m_alpha->read() & m_alpha1->read(), offset-tms9901_device::INT3); + } + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); } - break; + + case tms9901_device::INT8_P14: + case tms9901_device::INT9_P13: + case tms9901_device::INT10_P12: + if (m_keyboard_column >= 6) // no joystick lines after /INT7 + return 1; + else + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3); + case tms9901_device::INT11_P11: + // CS2 is write-only + return (m_cassette1->input()>0); + default: + return 1; } - return answer; } +/* switch (offset & 0x03) + { + case tms9901_device::CB_INT7: + // + // Read pins INT3*-INT7* of TI99's 9901. + // bit 1: INT1 status + // bit 2: INT2 status + // bit 3-7: keyboard status bits 0 to 4 + // + // |K|K|K|K|K|I2|I1|C| + // + if (m_keyboard_column >= (m_model==MODEL_4? 5:6)) // joy 1, 2, handset + { + answer = m_joyport->read_port(); + + if ((m_model!=MODEL_4) && (m_alphabug->read()!=0) ) answer |= (m_alpha->read() | m_alpha1->read()); + } + else + { + answer = m_keyboard[m_keyboard_column]->read(); + } + if (m_check_alphalock) // never true for TI-99/4 + { + answer &= ~(m_alpha->read() | m_alpha1->read()); + } + answer = (answer << 3); + if (m_int1 == CLEAR_LINE) answer |= 0x02; + if (m_int2 == CLEAR_LINE) answer |= 0x04; + + break; + + case tms9901_device::INT8_INT15: + // |1|1|1|INT12|0|K|K|K| + if (m_keyboard_column >= (m_model==MODEL_4? 5:6)) answer = 0x07; + else answer = ((m_keyboard[m_keyboard_column]->read())>>5) & 0x07; + answer |= 0xe0; + if (m_model != MODEL_4 || m_int12==CLEAR_LINE) answer |= 0x10; + break; + + case tms9901_device::P0_P7: + // Required for the handset (only on TI-99/4) + if ((m_joyport->read_port() & 0x20)!=0) answer |= 2; + break; + + case tms9901_device::P8_P15: + // Preset to 1 + answer = 4; + + // Interrupt pin of the handset (only on TI-99/4) + // Negative logic (interrupt pulls line down) + if ((m_joyport->read_port() & 0x40)==0) answer = 0; + + // we don't take CS2 into account, as CS2 is a write-only unit + if (m_cassette1->input() > 0) + { + answer |= 8; + } + break; + } + */ + /* Handler for TMS9901 P0 pin (handset data acknowledge); only for 99/4 @@ -628,17 +736,15 @@ WRITE_LINE_MEMBER( ti99_4x_state::cassette_output ) m_cassette2->output(state==ASSERT_LINE? +1 : -1); } -void ti99_4x_state::tms9901_interrupt(offs_t offset, uint8_t data) +WRITE_LINE_MEMBER( ti99_4x_state::tms9901_interrupt ) { - // offset contains the interrupt level (0-15) - // However, the TI board just ignores that level and hardwires it to 1 - // See below (interrupt_level) - m_cpu->set_input_line(INT_9900_INTREQ, data); + m_cpu->set_input_line(INT_9900_INTREQ, state); } uint8_t ti99_4x_state::interrupt_level() { - // On the TI-99 systems these IC lines are not used; the input lines + // The interrupt level must be fetched from the 9901; + // on the TI-99 systems these IC lines are not used; the input lines // at the CPU are hardwired to level 1. return 1; } @@ -680,7 +786,7 @@ WRITE_LINE_MEMBER( ti99_4x_state::video_interrupt_evpc_in ) { LOGMASKED(LOG_INTERRUPTS, "VDP INT2 from EVPC on tms9901, level=%d\n", state); m_int2 = (line_state)state; - m_tms9901->set_single_int(2, state); + m_tms9901->set_int_line(2, state); } /* @@ -688,13 +794,11 @@ WRITE_LINE_MEMBER( ti99_4x_state::video_interrupt_evpc_in ) */ WRITE_LINE_MEMBER( ti99_4x_state::video_interrupt_in ) { - LOGMASKED(LOG_INTERRUPTS, "VDP INT2 on tms9901, level=%d\n", state); - + LOGMASKED(LOG_INTERRUPTS, "VDP %s /INT2 on TMS9901\n", (state==ASSERT_LINE)? "asserts" : "clears"); + m_int2 = (line_state)state; + m_tms9901->set_int_line(2, state); // Pulse for the handset if (m_model == MODEL_4) m_joyport->pulse_clock(); - - m_int2 = (line_state)state; - m_tms9901->set_single_int(2, state); } /* @@ -704,7 +808,7 @@ WRITE_LINE_MEMBER( ti99_4x_state::handset_interrupt_in) { LOGMASKED(LOG_INTERRUPTS, "joyport INT12 on tms9901, level=%d\n", state); m_int12 = (line_state)state; - m_tms9901->set_single_int(12, state); + m_tms9901->set_int_line(12, state); } /* @@ -788,7 +892,7 @@ WRITE_LINE_MEMBER( ti99_4x_state::extint ) { LOGMASKED(LOG_INTERRUPTS, "EXTINT level = %02x\n", state); m_int1 = (line_state)state; - m_tms9901->set_single_int(1, state); + m_tms9901->set_int_line(1, state); } WRITE_LINE_MEMBER( ti99_4x_state::notconnected ) @@ -845,7 +949,6 @@ void ti99_4x_state::ti99_4_common(machine_config& config) // Programmable system interface (driven by CLKOUT) TMS9901(config, m_tms9901, 0); - m_tms9901->read_cb().set(FUNC(ti99_4x_state::read_by_9901)); m_tms9901->p_out_cb(2).set(FUNC(ti99_4x_state::keyC0)); m_tms9901->p_out_cb(3).set(FUNC(ti99_4x_state::keyC1)); m_tms9901->p_out_cb(4).set(FUNC(ti99_4x_state::keyC2)); @@ -853,7 +956,7 @@ void ti99_4x_state::ti99_4_common(machine_config& config) m_tms9901->p_out_cb(7).set(FUNC(ti99_4x_state::cs2_motor)); m_tms9901->p_out_cb(8).set(FUNC(ti99_4x_state::audio_gate)); m_tms9901->p_out_cb(9).set(FUNC(ti99_4x_state::cassette_output)); - m_tms9901->intlevel_cb().set(FUNC(ti99_4x_state::tms9901_interrupt)); + m_tms9901->intreq_cb().set(FUNC(ti99_4x_state::tms9901_interrupt)); // Databus multiplexer TI99_DATAMUX(config, m_datamux, 0).ready_cb().set(FUNC(ti99_4x_state::console_ready_dmux)); @@ -896,6 +999,7 @@ void ti99_4x_state::ti99_4(machine_config& config) // Main board // Add handset interrupt to 9901 m_tms9901->p_out_cb(0).set(FUNC(ti99_4x_state::handset_ack)); + m_tms9901->read_cb().set(FUNC(ti99_4x_state::psi_input_4)); // use a separate one for 99/4 // Input/output port: normal config TI99_IOPORT(config, m_ioport, 0, ti99_ioport_options_plain, nullptr); @@ -956,6 +1060,7 @@ void ti99_4x_state::ti99_4a(machine_config& config) // Main board // Add Alphalock to 9901 m_tms9901->p_out_cb(5).set(FUNC(ti99_4x_state::alphaW)); + m_tms9901->read_cb().set(FUNC(ti99_4x_state::psi_input_4a)); // Input/output port: Normal config TI99_IOPORT(config, m_ioport, 0, ti99_ioport_options_plain, nullptr); @@ -1046,6 +1151,7 @@ void ti99_4x_state::ti99_4ev_60hz(machine_config& config) // Main board // Add Alphalock m_tms9901->p_out_cb(5).set(FUNC(ti99_4x_state::alphaW)); + m_tms9901->read_cb().set(FUNC(ti99_4x_state::psi_input_4a)); // EVPC connector // This is needed for delivering the video interrupt from the diff --git a/src/mame/drivers/ti99_8.cpp b/src/mame/drivers/ti99_8.cpp index ce6cacc6b39..083e5c924b2 100644 --- a/src/mame/drivers/ti99_8.cpp +++ b/src/mame/drivers/ti99_8.cpp @@ -133,31 +133,22 @@ November 2013: Included new dumps [Michael Zapf] =========================================================================== -Known Issues (MZ, 2010-11-07) +Known Issues (MZ, 2019-05-10) KEEP IN MIND THAT TEXAS INSTRUMENTS NEVER RELEASED THE TI-99/8 AND THAT THERE ARE ONLY A FEW PROTOTYPES OF THE TI-99/8 AVAILABLE. ALL SOFTWARE MUST BE ASSUMED TO HAVE REMAINED IN A PRELIMINARY STATE. -- Extended Basic II does not start when a floppy controller is present. This is - a problem of the prototypical XB II which we cannot solve. It seems as if only - hexbus devices are properly supported, but we currently do not have an - emulation for those. Thus you can currently only use cassette to load and - save programs. You MUST not plug in any floppy controller when you intend to - start XB II. Other cartridges (like Editor/Assembler) - seem to be unaffected by this problem and can make use of the floppy - controllers. - Technical detail: The designers of XB II seem to have decided to put PABs - (Peripheral access block; contains pointers to buffers, the file name, and - the access modes) into CPU RAM instead of the traditional storage in VDP - RAM. The existing peripheral cards are hard-coded to interpret the given - pointer to the PAB as pointing to a VDP RAM address. That is, as soon as - the card is found, control is passed to the DSR (device service routine), - the file name will not be found, and control returns with an error. It seems - as if XB II does not properly handle this situation and may lock up - (sometimes it starts up, but file access is still not possible). - - TODO: Emulate a Hexbus floppy. +- TI-99/4A disk controllers cannot be used with the TI-99/8 in Extended Basic II. + In the 99/8, the peripheral access block (PAB, set of data defining the + access to the device, like floppy) may be located in CPU RAM, while the + controllers of the 99/4A expect the PAB to be in video RAM only. Exbasic II + sets up the PAB in CPU RAM, which leads to a crash. Other cartridges from + the 99/4A certainly use video RAM, and so the disk controller works. + Therefore, the Hexbus floppy drive HX5102 is recommended for use with the + TI-99/8. You do not even need to attach the Peripheral Box. + + mame ti99_8 -hexbus hx5102 -flop1 somedisk.dsk - Multiple cartridges are not shown in the startup screen; only one cartridge is presented. You have to manually select the cartridges with the @@ -266,7 +257,7 @@ private: DECLARE_WRITE_LINE_MEMBER( video_interrupt ); // Connections with the system interface TMS9901 - uint8_t read_by_9901(offs_t offset); + uint8_t psi_input(offs_t offset); DECLARE_WRITE_LINE_MEMBER(keyC0); DECLARE_WRITE_LINE_MEMBER(keyC1); DECLARE_WRITE_LINE_MEMBER(keyC2); @@ -464,75 +455,44 @@ void ti99_8_state::cruwrite(offs_t offset, uint8_t data) keyboard column selection.) ***************************************************************************/ -uint8_t ti99_8_state::read_by_9901(offs_t offset) +uint8_t ti99_8_state::psi_input(offs_t offset) { - int answer=0; - uint8_t joyst; - switch (offset & 0x03) + switch (offset) { - case tms9901_device::CB_INT7: - // Read pins INT3*-INT7* of TI99's 9901. - // - // bit 1: INT1 status - // bit 2: INT2 status - // bits 3-4: unused? - // bit 5: ??? - // bit 6-7: keyboard status bits 0 through 1 - - // |K|K|-|-|-|I2|I1|C| + case tms9901_device::INT1: + return (m_int1==CLEAR_LINE)? 1 : 0; + case tms9901_device::INT2: + return (m_int2==CLEAR_LINE)? 1 : 0; + + case tms9901_device::INT6: + if (m_keyboard_column >= 14) + return BIT(m_joyport->read_port(),0); + + case tms9901_device::INT7_P15: + if (m_keyboard_column >= 14) + return BIT(m_joyport->read_port(),4); + + case tms9901_device::INT8_P14: if (m_keyboard_column >= 14) - { - // TI-99/8's wiring differs from the TI-99/4A - joyst = m_joyport->read_port(); - answer = (joyst & 0x01) | ((joyst & 0x10)>>3); - } - else - { - answer = m_keyboard[m_keyboard_column]->read(); - } - answer = (answer << 6); - if (m_int1 == CLEAR_LINE) answer |= 0x02; - if (m_int2 == CLEAR_LINE) answer |= 0x04; - - break; - - case tms9901_device::INT8_INT15: - // Read pins int8_t*-INT15* of TI99's 9901. - // - // bit 0-2: keyboard status bits 2 to 4 - // bit 3: tape input mirror - // bit 4: unused - // bit 5-7: weird, not emulated - - // |0|0|0|0|0|K|K|K| + return BIT(m_joyport->read_port(),1); + case tms9901_device::INT9_P13: if (m_keyboard_column >= 14) - { - joyst = m_joyport->read_port(); - answer = joyst << 1; - } - else - { - answer = m_keyboard[m_keyboard_column]->read(); - } - answer = (answer >> 2) & 0x07; - break; - - case tms9901_device::P0_P7: - // Read pins P0-P7 of TI99's 9901. None here. - break; - - case tms9901_device::P8_P15: - // Read pins P8-P15 of TI99's 9901. (TI-99/8) - // - // bit 26: high - // bit 27: tape input - answer = 4; - if (m_cassette->input() > 0) - answer |= 8; - break; + return BIT(m_joyport->read_port(),2); + + case tms9901_device::INT10_P12: + if (m_keyboard_column >= 14) + return BIT(m_joyport->read_port(),3); + + // return for last 5 cases if column<14 + return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT6); + + case tms9901_device::INT11_P11: + return (m_cassette->input() > 0); + + default: + return 1; } - return answer; } /* @@ -613,7 +573,7 @@ WRITE_LINE_MEMBER( ti99_8_state::video_interrupt ) { LOGMASKED(LOG_INTERRUPTS, "VDP int 2 on tms9901, level=%02x\n", state); m_int2 = (line_state)state; - m_tms9901->set_single_int(2, state); + m_tms9901->set_int_line(2, state); } /*********************************************************** @@ -665,7 +625,7 @@ WRITE_LINE_MEMBER( ti99_8_state::extint ) { LOGMASKED(LOG_INTERRUPTS, "EXTINT level = %02x\n", state); m_int1 = (line_state)state; - m_tms9901->set_single_int(1, state); + m_tms9901->set_int_line(1, state); } WRITE_LINE_MEMBER( ti99_8_state::notconnected ) @@ -735,7 +695,7 @@ void ti99_8_state::ti99_8(machine_config& config) // 9901 configuration TMS9901(config, m_tms9901, 0); - m_tms9901->read_cb().set(FUNC(ti99_8_state::read_by_9901)); + m_tms9901->read_cb().set(FUNC(ti99_8_state::psi_input)); m_tms9901->p_out_cb(0).set(FUNC(ti99_8_state::keyC0)); m_tms9901->p_out_cb(1).set(FUNC(ti99_8_state::keyC1)); m_tms9901->p_out_cb(2).set(FUNC(ti99_8_state::keyC2)); @@ -745,7 +705,7 @@ void ti99_8_state::ti99_8(machine_config& config) m_tms9901->p_out_cb(6).set(FUNC(ti99_8_state::cassette_motor)); m_tms9901->p_out_cb(8).set(FUNC(ti99_8_state::audio_gate)); m_tms9901->p_out_cb(9).set(FUNC(ti99_8_state::cassette_output)); - m_tms9901->intlevel_cb().set(FUNC(ti99_8_state::tms9901_interrupt)); + m_tms9901->intreq_cb().set(FUNC(ti99_8_state::tms9901_interrupt)); // Mainboard with custom chips TI99_MAINBOARD8(config, m_mainboard, 0); diff --git a/src/mame/drivers/tm990189.cpp b/src/mame/drivers/tm990189.cpp index 154f456b9cb..4aa4acbb96e 100644 --- a/src/mame/drivers/tm990189.cpp +++ b/src/mame/drivers/tm990189.cpp @@ -112,9 +112,9 @@ private: DECLARE_WRITE_LINE_MEMBER(usr9901_led1_w); DECLARE_WRITE_LINE_MEMBER(usr9901_led2_w); DECLARE_WRITE_LINE_MEMBER(usr9901_led3_w); - DECLARE_WRITE8_MEMBER(usr9901_interrupt_callback); + DECLARE_WRITE_LINE_MEMBER(usr9901_interrupt_callback); - DECLARE_WRITE8_MEMBER(sys9901_interrupt_callback); + DECLARE_WRITE_LINE_MEMBER(sys9901_interrupt_callback); DECLARE_READ8_MEMBER(sys9901_r); DECLARE_WRITE_LINE_MEMBER(sys9901_digitsel0_w); DECLARE_WRITE_LINE_MEMBER(sys9901_digitsel1_w); @@ -282,13 +282,13 @@ TIMER_DEVICE_CALLBACK_MEMBER(tm990189_state::display_callback) tms9901 code */ -WRITE8_MEMBER( tm990189_state::usr9901_interrupt_callback ) +WRITE_LINE_MEMBER( tm990189_state::usr9901_interrupt_callback ) { // Triggered by internal timer (set by ROM to 1.6 ms cycle) on level 3 // or by keyboard interrupt (level 6) if (!m_load_state) { - m_tms9980a->set_input_line(offset & 7, ASSERT_LINE); + m_tms9980a->set_input_line(m_tms9901_usr->get_int_level() & 7, ASSERT_LINE); } } @@ -320,31 +320,51 @@ WRITE_LINE_MEMBER( tm990189_state::usr9901_led3_w ) led_set(3, state); } -WRITE8_MEMBER( tm990189_state::sys9901_interrupt_callback ) +WRITE_LINE_MEMBER( tm990189_state::sys9901_interrupt_callback ) { // TODO: Check this - m_tms9901_usr->set_single_int(5, (data!=0)? ASSERT_LINE:CLEAR_LINE); + m_tms9901_usr->set_int_line(5, state); } READ8_MEMBER( tm990189_state::sys9901_r ) { - uint8_t data = 0; - if (offset == tms9901_device::CB_INT7) - { - static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7", "LINE8" }; - - /* keyboard read */ - if (m_digitsel < 9) - data |= ioport(keynames[m_digitsel])->read() << 1; + // |-|Cass|K|K|K|K|K|C| + static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7", "LINE8" }; - /* tape input */ - if (m_cass->input() > 0.0) - data |= 0x40; + offset &= 0x0F; + switch (offset) + { + case tms9901_device::INT1: + case tms9901_device::INT2: + case tms9901_device::INT3: + case tms9901_device::INT4: + case tms9901_device::INT5: + if (m_digitsel < 9) + return BIT(ioport(keynames[m_digitsel])->read(), offset-tms9901_device::INT1); + else return 0; + + case tms9901_device::INT6: + return (m_cass->input() > 0); + default: + return 0; } - - return data; } +/* + uint8_t data = 0; + if (offset == tms9901_device::CB_INT7) + { + static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7", "LINE8" }; + // keyboard read + if (m_digitsel < 9) + data |= ioport(keynames[m_digitsel])->read() << 1; + // tape input + if (m_cass->input() > 0.0) + data |= 0x40; + } + return data; +*/ + void tm990189_state::digitsel(int offset, bool state) { if (state) @@ -843,7 +863,7 @@ void tm990189_state::tm990_189(machine_config &config) m_tms9901_usr->p_out_cb(1).set(FUNC(tm990189_state::usr9901_led1_w)); m_tms9901_usr->p_out_cb(2).set(FUNC(tm990189_state::usr9901_led2_w)); m_tms9901_usr->p_out_cb(3).set(FUNC(tm990189_state::usr9901_led3_w)); - m_tms9901_usr->intlevel_cb().set(FUNC(tm990189_state::usr9901_interrupt_callback)); + m_tms9901_usr->intreq_cb().set(FUNC(tm990189_state::usr9901_interrupt_callback)); TMS9901(config, m_tms9901_sys, 8_MHz_XTAL / 4); m_tms9901_sys->read_cb().set(FUNC(tm990189_state::sys9901_r)); @@ -863,7 +883,7 @@ void tm990189_state::tm990_189(machine_config &config) m_tms9901_sys->p_out_cb(13).set(FUNC(tm990189_state::sys9901_shiftlight_w)); m_tms9901_sys->p_out_cb(14).set(FUNC(tm990189_state::sys9901_spkrdrive_w)); m_tms9901_sys->p_out_cb(15).set(FUNC(tm990189_state::sys9901_tapewdata_w)); - m_tms9901_sys->intlevel_cb().set(FUNC(tm990189_state::sys9901_interrupt_callback)); + m_tms9901_sys->intreq_cb().set(FUNC(tm990189_state::sys9901_interrupt_callback)); TMS9902(config, m_tms9902, 8_MHz_XTAL / 4); m_tms9902->xmit_cb().set(FUNC(tm990189_state::xmit_callback)); // called when a character is transmitted @@ -907,7 +927,7 @@ void tm990189_state::tm990_189_v(machine_config &config) m_tms9901_usr->p_out_cb(1).set(FUNC(tm990189_state::usr9901_led1_w)); m_tms9901_usr->p_out_cb(2).set(FUNC(tm990189_state::usr9901_led2_w)); m_tms9901_usr->p_out_cb(3).set(FUNC(tm990189_state::usr9901_led3_w)); - m_tms9901_usr->intlevel_cb().set(FUNC(tm990189_state::usr9901_interrupt_callback)); + m_tms9901_usr->intreq_cb().set(FUNC(tm990189_state::usr9901_interrupt_callback)); TMS9901(config, m_tms9901_sys, 8_MHz_XTAL / 4); m_tms9901_sys->read_cb().set(FUNC(tm990189_state::sys9901_r)); @@ -927,7 +947,7 @@ void tm990189_state::tm990_189_v(machine_config &config) m_tms9901_sys->p_out_cb(13).set(FUNC(tm990189_state::sys9901_shiftlight_w)); m_tms9901_sys->p_out_cb(14).set(FUNC(tm990189_state::sys9901_spkrdrive_w)); m_tms9901_sys->p_out_cb(15).set(FUNC(tm990189_state::sys9901_tapewdata_w)); - m_tms9901_sys->intlevel_cb().set(FUNC(tm990189_state::sys9901_interrupt_callback)); + m_tms9901_sys->intreq_cb().set(FUNC(tm990189_state::sys9901_interrupt_callback)); TMS9902(config, m_tms9902, 8_MHz_XTAL / 4); m_tms9902->xmit_cb().set(FUNC(tm990189_state::xmit_callback)); // called when a character is transmitted; -- cgit v1.2.3