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:
Michael Zapf 2019-05-15 22:43:40 +02:00
parent c9b625bb2a
commit 13c3ec0f00
8 changed files with 971 additions and 800 deletions

View File

@ -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));

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;