diff options
Diffstat (limited to 'src/devices/machine/tms9901.cpp')
-rw-r--r-- | src/devices/machine/tms9901.cpp | 754 |
1 files changed, 366 insertions, 388 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<<INT3); + LOGMASKED(LOG_INT, "INT3 (timer) asserted\n"); + } + + m_int_level = 1; + masked_ints = (masked_ints & m_int_mask)>>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<<pos); + else bitfield &= ~(1<<pos); +} + +void tms9901_device::signal_int() +{ + if (m_int_level == m_last_level) return; - m_old_int_state = current_ints; + m_last_level = m_int_level; - if (current_ints != 0) + if (m_int_pending) { - // find which interrupt tripped us: - // the number of the first (i.e. least significant) non-zero bit among - // the 16 first bits - // we simply look for the first bit set to 1 in current_ints... - int level = 0; - - while ((current_ints & 1)==0) - { - current_ints >>= 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)); |