mirror of
https://github.com/holub/mame
synced 2025-10-06 17:08:28 +03:00
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.
This commit is contained in:
parent
c9b625bb2a
commit
13c3ec0f00
@ -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.)
|
||||
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)
|
||||
|
||||
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.
|
||||
Three groups of I/O pins:
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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.)
|
||||
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.
|
||||
|
||||
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).
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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---<---+
|
||||
|
||||
MZ: Turned to class (January 2012)
|
||||
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.
|
||||
|
||||
MZ: Added a synchronous clock input (Phi line) as an alternative to the
|
||||
emu_timer.
|
||||
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.
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
// 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)
|
||||
// Skip the rightmost bit
|
||||
uint16_t masked_ints = m_int_line;
|
||||
|
||||
// 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
|
||||
n=1..15
|
||||
*/
|
||||
void tms9901_device::set_single_int(int pin_number, int state)
|
||||
void tms9901_device::set_int_line(int n, 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
|
||||
*/
|
||||
void tms9901_device::timer_reload()
|
||||
{
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
bit 16-31: current status of the P0-P15 pins (quits timer mode, too...)
|
||||
Reading an output port delivers the latched output value.
|
||||
|
||||
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);
|
||||
|
||||
break;
|
||||
case 3:
|
||||
// MZ: see above
|
||||
// m_clock_mode = false;
|
||||
if (!m_read_block.isnull())
|
||||
answer = m_read_block(P8_P15);
|
||||
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 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...)
|
||||
*/
|
||||
void tms9901_device::write(offs_t offset, uint8_t data)
|
||||
{
|
||||
data &= 1; /* clear extra bits */
|
||||
offset &= 0x01F;
|
||||
|
||||
if (offset >= 0x10)
|
||||
// If we are here, crubit=1..15
|
||||
if (m_clock_mode)
|
||||
{
|
||||
int pin = offset & 0x0F;
|
||||
LOGMASKED(LOG_PINS, "Output on P%d = %d\n", pin, data);
|
||||
if (crubit == 15) // bit 15 in clock mode = /INTREQ
|
||||
return (m_int_pending)? 0 : 1;
|
||||
|
||||
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);
|
||||
}
|
||||
else if (offset == 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 = 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);
|
||||
}
|
||||
}
|
||||
else if (offset == 0x0f)
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
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 */
|
||||
}
|
||||
return BIT(m_clock_read_register, crubit-1);
|
||||
}
|
||||
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)
|
||||
// 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
|
||||
return m_read_port.isnull()? 1 : m_read_port(crubit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
WRITE8_MEMBER( tms9901_device::write )
|
||||
{
|
||||
data &= 1; // clear extra bits
|
||||
int crubit = offset & 0x001f;
|
||||
|
||||
if (crubit >= 16)
|
||||
{
|
||||
LOGMASKED(LOG_PINS, "Output on P%d = %d\n", crubit-16, data);
|
||||
set_and_latch_output(crubit-P0, data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (crubit)
|
||||
{
|
||||
case 0:
|
||||
// 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)
|
||||
{ /* modify clock interval */
|
||||
int bit = 1 << ((offset & 0x0F) - 1); /* corresponding mask */
|
||||
{
|
||||
// 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;
|
||||
|
||||
if (data)
|
||||
m_clock_register |= bit; /* set bit */
|
||||
else
|
||||
m_clock_register &= ~bit; /* clear bit */
|
||||
|
||||
/* reset clock timer (page 8) */
|
||||
LOGMASKED(LOG_CONFIG, "Clock register = %04x\n", m_clock_register);
|
||||
timer_reload();
|
||||
case 15:
|
||||
if (m_clock_mode)
|
||||
{
|
||||
// In clock mode, bit 15 is /RST2
|
||||
if (data == 0) soft_reset();
|
||||
}
|
||||
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 */
|
||||
|
||||
if (offset == 3)
|
||||
m_timer_int_pending = false; /* SBO 3 clears pending timer interrupt (??) */
|
||||
|
||||
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
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Bits 1..14
|
||||
if (m_clock_mode)
|
||||
{
|
||||
// Modify clock interval
|
||||
set_bit(m_clock_register, crubit-1, data!=0);
|
||||
|
||||
// Reset clock timer (page 8)
|
||||
m_decrementer_value = m_clock_register;
|
||||
|
||||
LOGMASKED(LOG_CLOCK, "Clock register = %04x\n", m_clock_register);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Modify interrupt enable mask
|
||||
set_bit(m_int_mask, crubit, data!=0);
|
||||
|
||||
// [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_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);
|
||||
|
||||
if (m_clockdiv==0)
|
||||
clock_in((line_state)state);
|
||||
// We signal the interrupt in sync with the clock line
|
||||
signal_int();
|
||||
|
||||
// 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));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
// External interrupt (INTA)
|
||||
case tms9901_device::INT1:
|
||||
return (m_inta==CLEAR_LINE)? 1 : 0;
|
||||
|
||||
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;
|
||||
// Video interrupt
|
||||
case tms9901_device::INT2:
|
||||
return (m_int2==CLEAR_LINE)? 1 : 0;
|
||||
|
||||
case tms9901_device::P0_P7:
|
||||
// Read pins P0-P7 of TMS9901. All pins are configured as outputs, so nothing here.
|
||||
break;
|
||||
// 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);
|
||||
|
||||
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;
|
||||
// Keyboard interrupt
|
||||
case tms9901_device::INT8_P14:
|
||||
return (m_keyint==CLEAR_LINE)? 1 : 0;
|
||||
|
||||
// Joystick up (mirror of bit 7)
|
||||
if ((m_joyport->read_port() & 0x10)==0) answer |= 0x80;
|
||||
break;
|
||||
// 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
|
||||
|
@ -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));
|
||||
|
@ -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<tms9900_device> m_cpu;
|
||||
required_device<tms9901_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)
|
||||
{
|
||||
return BIT(m_alpha->read(), offset-tms9901_device::INT3);
|
||||
}
|
||||
else
|
||||
return BIT(m_keyboard[m_keyboard_column]->read(), offset-tms9901_device::INT3);
|
||||
}
|
||||
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 >= 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<<line);
|
||||
else m_9901_int &= ~(1<<line);
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER( ti99_4p_state::extint )
|
||||
{
|
||||
LOGMASKED(LOG_INT, "EXTINT level = %02x\n", state);
|
||||
set_9901_int(1, (line_state)state);
|
||||
LOGMASKED(LOG_INTERRUPTS, "EXTINT level = %02x\n", state);
|
||||
m_int1 = (line_state)state;
|
||||
m_tms9901->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);
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
TMS9901 interrupt handling on a TI99/4(a).
|
||||
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?)
|
||||
|
||||
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)
|
||||
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?)
|
||||
|
||||
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).
|
||||
enum { INT1, ... INT7_P15, INT8_P14, ..., P5, P6 }
|
||||
|
||||
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.
|
||||
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.
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
uint8_t ti99_4x_state::read_by_9901(offs_t offset)
|
||||
READ8_MEMBER( ti99_4x_state::psi_input_4)
|
||||
{
|
||||
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_model==MODEL_4? 5:6)) // joy 1, 2, handset
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
READ8_MEMBER( ti99_4x_state::psi_input_4a )
|
||||
{
|
||||
int alphabias=0;
|
||||
|
||||
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:
|
||||
// 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_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());
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
answer = m_keyboard[m_keyboard_column]->read();
|
||||
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);
|
||||
}
|
||||
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;
|
||||
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
|
||||
|
@ -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).
|
||||
- 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.
|
||||
|
||||
TODO: Emulate a Hexbus floppy.
|
||||
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
|
||||
case tms9901_device::INT1:
|
||||
return (m_int1==CLEAR_LINE)? 1 : 0;
|
||||
case tms9901_device::INT2:
|
||||
return (m_int2==CLEAR_LINE)? 1 : 0;
|
||||
|
||||
// |K|K|-|-|-|I2|I1|C|
|
||||
case tms9901_device::INT6:
|
||||
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(),0);
|
||||
|
||||
case tms9901_device::INT7_P15:
|
||||
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;
|
||||
return BIT(m_joyport->read_port(),4);
|
||||
|
||||
case tms9901_device::P0_P7:
|
||||
// Read pins P0-P7 of TI99's 9901. None here.
|
||||
break;
|
||||
case tms9901_device::INT8_P14:
|
||||
if (m_keyboard_column >= 14)
|
||||
return BIT(m_joyport->read_port(),1);
|
||||
|
||||
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;
|
||||
case tms9901_device::INT9_P13:
|
||||
if (m_keyboard_column >= 14)
|
||||
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);
|
||||
|
@ -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)
|
||||
// |-|Cass|K|K|K|K|K|C|
|
||||
static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7", "LINE8" };
|
||||
|
||||
offset &= 0x0F;
|
||||
switch (offset)
|
||||
{
|
||||
static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7", "LINE8" };
|
||||
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;
|
||||
|
||||
/* keyboard read */
|
||||
if (m_digitsel < 9)
|
||||
data |= ioport(keynames[m_digitsel])->read() << 1;
|
||||
|
||||
/* tape input */
|
||||
if (m_cass->input() > 0.0)
|
||||
data |= 0x40;
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user