diff options
Diffstat (limited to 'src/devices/machine/tms9901.cpp')
-rw-r--r-- | src/devices/machine/tms9901.cpp | 130 |
1 files changed, 76 insertions, 54 deletions
diff --git a/src/devices/machine/tms9901.cpp b/src/devices/machine/tms9901.cpp index bb7148a91a6..46779cd13c0 100644 --- a/src/devices/machine/tms9901.cpp +++ b/src/devices/machine/tms9901.cpp @@ -33,7 +33,8 @@ Reference: [1] TMS9901 Programmable Systems Interface Data Manual, July 1977 Overview: TMS9901 is a support chip for TMS9900. It handles interrupts, provides several I/O pins, and a timer, which is a register which - decrements regularly and can generate an interrupt when it reaches 0. + decrements continuously and can be set to 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. @@ -79,7 +80,8 @@ 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. + commands (1=arm, 0=disarm) on each of the 15 bits. A disarmed interrupt + input can still be read like a normal input port via CRU access. After each clock cycle, the TMS9901 latches the state of INT1*-INT15* (except those pins which are set as output pins). @@ -98,8 +100,7 @@ Group 2 pins (shared I/O and INT*) 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. + output, and the written value appears on the pin. 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 @@ -108,16 +109,20 @@ Group 2 pins (shared I/O and INT*) 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. + counts down by 1 every 64 clock ticks. On every update, the value is copied + into the read register, but only in interrupt mode. In clock mode, the read + register is locked so that it can be read without being changed. + Whenever the counter reaches 0, it is reloaded from the clock register on + the next update. + Setting the clock register is possible via CRU addresses 1 to 14 in clock + mode, with bit 1 being the LSB and bit 14 being the MSB. On each bit write + operation, the current state of the clock register is copied into the counter. + + Interrupt ^ | - [Clock register] -> [Decrementer] -> [Clock read register] + [Clock register] -> [Decrementer] -> [Read register] ^ | | v +--<--- CRU write CRU read---<---+ @@ -126,7 +131,7 @@ Clock mode: 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. + or "disable" most likely refers 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 @@ -147,16 +152,15 @@ Clock mode: #include "emu.h" #include "tms9901.h" -#include <math.h> +#include <cmath> -#define LOG_GENERAL (1U << 0) #define LOG_PINS (1U << 1) #define LOG_MASK (1U << 2) #define LOG_MODE (1U << 3) #define LOG_INT (1U << 4) #define LOG_CLOCK (1U << 5) -#define VERBOSE ( LOG_GENERAL ) +#define VERBOSE (LOG_GENERAL) #include "logmacro.h" /* @@ -171,8 +175,8 @@ tms9901_device::tms9901_device(const machine_config &mconfig, const char *tag, d m_poll_lines(false), m_clockdiv(0), 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_read_port(*this, 0), + m_write_p(*this), m_interrupt(*this) { } @@ -261,14 +265,12 @@ void tms9901_device::signal_int() if (m_int_pending) { LOGMASKED(LOG_INT, "Triggering interrupt, level %d\n", m_int_level); - if (!m_interrupt.isnull()) - m_interrupt(ASSERT_LINE); + m_interrupt(ASSERT_LINE); } else { LOGMASKED(LOG_INT, "Clear all interrupts\n"); - if (!m_interrupt.isnull()) - m_interrupt(CLEAR_LINE); //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111 + m_interrupt(CLEAR_LINE); //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111 } } @@ -314,12 +316,17 @@ void tms9901_device::set_int_line(int n, int state) (in this order). When configured as outputs, reading returns the latched values. */ -READ8_MEMBER( tms9901_device::read ) +uint8_t tms9901_device::read(offs_t offset) { - int crubit = offset & 0x01f; + return read_bit(offset)? 0x01 : 0x00; +} + +bool tms9901_device::read_bit(int bit) +{ + int crubit = bit & 0x01f; if (crubit == 0) - return m_clock_mode? 1 : 0; + return m_clock_mode; if (crubit > 15) { @@ -329,8 +336,8 @@ READ8_MEMBER( tms9901_device::read ) else { // 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); + if (m_read_port.isunset()) return false; + return m_read_port((crubit<=P6) ? crubit : P6+P0-crubit)!=0; } } @@ -338,9 +345,9 @@ READ8_MEMBER( tms9901_device::read ) if (m_clock_mode) { if (crubit == 15) // bit 15 in clock mode = /INTREQ - return (m_int_pending)? 0 : 1; + return !m_int_pending; - return BIT(m_clock_read_register, crubit-1); + return BIT(m_clock_read_register, crubit-1) != 0; } else { @@ -352,7 +359,7 @@ READ8_MEMBER( tms9901_device::read ) if (crubit>INT6 && is_output(22-crubit)) return output_value(22-crubit); else - return m_read_port.isnull()? 1 : m_read_port(crubit); + return m_read_port.isunset() ? true : (m_read_port(crubit)!=0); } } @@ -367,9 +374,13 @@ READ8_MEMBER( tms9901_device::read ) 15 Clock mode: /RST2; Interrupt mode: Set Mask 15 16..31 Set P(n-16) as output, latch value, and output it */ -WRITE8_MEMBER( tms9901_device::write ) +void tms9901_device::write(offs_t offset, uint8_t data) +{ + write_bit(offset, data!=0); +} + +void tms9901_device::write_bit(int offset, bool data) { - data &= 1; // clear extra bits int crubit = offset & 0x001f; if (crubit >= 16) @@ -385,14 +396,6 @@ WRITE8_MEMBER( tms9901_device::write ) // Write to control bit (CB) m_clock_mode = (data!=0); LOGMASKED(LOG_MODE, "Enter %s mode\n", m_clock_mode? "clock" : "interrupt"); - - if (m_clock_mode) - { - // we are switching to clock mode: latch the current value of - // the decrementer register - m_clock_read_register = m_decrementer_value; - LOGMASKED(LOG_MODE, "Clock setting = %d\n", m_clock_read_register); - } break; case 15: @@ -440,17 +443,34 @@ WRITE8_MEMBER( tms9901_device::write ) } /* - Timer callback + Update clock line. This is not a real connection to the 9901; it represents + the effect of setting selection line S0 to 1. Since we use a separate + I/O address space, and in the real machine, the same address lines as for + memory access are used, and one of the address lines is connected to S0, + we have settings of S0 even in situations when there is no I/O access but + ordinary memory access. + + Offering a method explicitly for S0 looks inconsistent with the way of + addressing the bits in this chip (that is, we should then offer S1 to S4 as + well). + + Drivers may use this line for higher emulation precision concerning the + clock. +*/ +void tms9901_device::update_clock() +{ + m_clock_read_register = m_decrementer_value; +} + +/* + decrement_tick Decrementer counts down the value set in clock mode; when it reaches 0, raises an interrupt and resets to the start value */ -void tms9901_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +TIMER_CALLBACK_MEMBER(tms9901_device::decrement_tick) { - if (id==DECREMENTER) // we have only that one - { - timer_clock_in(ASSERT_LINE); - timer_clock_in(CLEAR_LINE); - } + timer_clock_in(ASSERT_LINE); + timer_clock_in(CLEAR_LINE); } void tms9901_device::timer_clock_in(line_state clk) @@ -458,6 +478,10 @@ void tms9901_device::timer_clock_in(line_state clk) if (clk == ASSERT_LINE) { m_decrementer_value = (m_decrementer_value - 1) & 0x3FFF; + + if (!m_clock_mode) + m_clock_read_register = m_decrementer_value; + LOGMASKED(LOG_CLOCK, "Clock = %04x\n", m_decrementer_value); if (m_decrementer_value==0) { @@ -477,7 +501,7 @@ void tms9901_device::timer_clock_in(line_state clk) a CLK line controlled by the CPU, like the TMS99xx systems. In that case, clock is set to 0. */ -WRITE_LINE_MEMBER( tms9901_device::phi_line ) +void tms9901_device::phi_line(int state) { if (state==ASSERT_LINE) { @@ -487,6 +511,9 @@ WRITE_LINE_MEMBER( tms9901_device::phi_line ) { timer_clock_in(ASSERT_LINE); + if (!m_clock_mode) + m_clock_read_register = m_decrementer_value; + // We signal the interrupt in sync with the clock line signal_int(); @@ -564,7 +591,7 @@ void tms9901_device::device_reset() /* RST1 input line (active low; using ASSERT/CLEAR). */ -WRITE_LINE_MEMBER( tms9901_device::rst1_line ) +void tms9901_device::rst1_line(int state) { if (state==ASSERT_LINE) do_reset(); } @@ -594,15 +621,10 @@ void tms9901_device::device_start() // Allow for using asynchronous and synchronous clocks if (clock() != 0) { - m_decrementer = timer_alloc(DECREMENTER); + m_decrementer = timer_alloc(FUNC(tms9901_device::decrement_tick), this); m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.)); } - 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_line)); |