ti99: Fixed cassette instabilities, caused by improper timer usage; introduced synchronous clock input for 9901

This commit is contained in:
Michael Zapf 2019-02-12 01:41:33 +01:00
parent fe502c0d41
commit 5755f55af4
8 changed files with 91 additions and 37 deletions

View File

@ -38,7 +38,6 @@
#include "emu.h"
#include "handset.h"
#include "machine/tms9901.h"
#define LOG_WARN (1U<<1) // Warnings
#define LOG_CONFIG (1U<<2) // Configuration

View File

@ -53,6 +53,16 @@ Pins:
(This is mostly obvious, but it implies that you cannot trigger an
interrupt by setting the output state of a pin, which is not SO obvious.)
Clock mode:
The "clock mode" is entered by setting bit 0 to 1. This means that the
clock register becomes accessible to changes and inspection. The clock
itself runs in the interrupt mode. Accordingly, the typical setup
involves first setting bit 0 to 1, then loading some or all of the
clock register bits, and then switching to interrupt mode again. From then
on, INT3 is asserted whenever the clock reaches 0, and is cleared by
writing 0 or 1 to bit 3. The clock can only be stopped by setting the
register to 0 or by a reset.
Interrupt handling:
After each clock cycle, TMS9901 latches the state of INT1*-INT15* (except
pins which are set as output pins). If the clock is enabled, it replaces
@ -105,6 +115,9 @@ MZ: According to the description in
MZ: Turned to class (January 2012)
MZ: Added a synchronous clock input (Phi line) as an alternative to the
emu_timer.
TODO: Tests on a real machine
- Set an interrupt input (e.g. keyboard for Geneve), trigger RST2*, check whether
interrupt mask has been reset
@ -124,24 +137,23 @@ TODO: Tests on a real machine
#include <math.h>
#define LOG_GENERAL (1U << 0)
#define LOG_PINS (1U << 1)
#define LOG_CLOCK (1U << 2)
#define LOG_MODE (1U << 3)
#define LOG_GENERAL (1U << 0)
#define LOG_PINS (1U << 1)
#define LOG_CONFIG (1U << 2)
#define LOG_MODE (1U << 3)
#define LOG_INT (1U << 4)
#define LOG_DECVALUE (1U << 5)
//#define VERBOSE (LOG_PINS | LOG_CLOCK | LOG_MODE)
#define VERBOSE ( 0 )
#include "logmacro.h"
#define LOGPINS(...) LOGMASKED(LOG_PINS, __VA_ARGS__)
#define LOGCLOCK(...) LOGMASKED(LOG_CLOCK, __VA_ARGS__)
#define LOGMODE(...) LOGMASKED(LOG_MODE, __VA_ARGS__)
/*
Constructor
*/
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_clockdiv(0),
m_read_block(*this),
m_write_p{{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this}},
m_interrupt(*this)
@ -162,12 +174,12 @@ void tms9901_device::field_interrupts()
// if timer is enabled, INT3 pin is overridden by timer
if (m_timer_int_pending)
{
LOGCLOCK("timer fires\n");
LOGMASKED(LOG_INT, "INT3 (timer) asserted\n");
current_ints |= INT3;
}
else
{
LOGCLOCK("timer clear\n");
LOGMASKED(LOG_INT, "INT3 (timer) cleared\n");
current_ints &= ~INT3;
}
}
@ -197,6 +209,7 @@ void tms9901_device::field_interrupts()
current_ints >>= 1; /* try next bit */
level++;
}
LOGMASKED(LOG_INT, "Triggering interrupt, level %d\n", level);
m_int_pending = true;
if (!m_interrupt.isnull())
m_interrupt(level, 1, 0xff); // the offset carries the IC0-3 level
@ -237,11 +250,11 @@ void tms9901_device::timer_reload()
if (m_clock_register != 0)
{ /* reset clock interval */
m_decrementer_value = m_clock_register;
m_decrementer->enable(true);
m_clock_active = true;
}
else
{ /* clock interval == 0 -> no timer */
m_decrementer->enable(false);
m_clock_active = false;
}
}
@ -291,7 +304,7 @@ READ8_MEMBER( tms9901_device::read )
// Set those bits here
answer |= (m_pio_output_mirror & m_pio_direction_mirror) & 0xFF;
}
LOGPINS("input on lines INT7..CB = %02x\n", answer);
LOGMASKED(LOG_PINS, "Input on lines INT7..CB = %02x\n", answer);
break;
case 1:
if (m_clock_mode)
@ -311,7 +324,7 @@ READ8_MEMBER( tms9901_device::read )
answer &= ~(m_pio_direction_mirror >> 8);
answer |= (m_pio_output_mirror & m_pio_direction_mirror) >> 8;
}
LOGPINS("input on lines INT15..INT8 = %02x\n", answer);
LOGMASKED(LOG_PINS, "Input on lines INT15..INT8 = %02x\n", answer);
break;
case 2:
/* exit timer mode */
@ -325,7 +338,7 @@ READ8_MEMBER( tms9901_device::read )
answer &= ~m_pio_direction;
answer |= (m_pio_output & m_pio_direction) & 0xFF;
LOGPINS("input on lines P7..P0 = %02x\n", answer);
LOGMASKED(LOG_PINS, "Input on lines P7..P0 = %02x\n", answer);
break;
case 3:
@ -338,7 +351,7 @@ READ8_MEMBER( tms9901_device::read )
answer &= ~(m_pio_direction >> 8);
answer |= (m_pio_output & m_pio_direction) >> 8;
LOGPINS("input on lines P15..P8 = %02x\n", answer);
LOGMASKED(LOG_PINS, "Input on lines P15..P8 = %02x\n", answer);
break;
}
@ -367,7 +380,7 @@ WRITE8_MEMBER ( tms9901_device::write )
if (offset >= 0x10)
{
int pin = offset & 0x0F;
LOGPINS("output on P%d = %d\n", pin, data);
LOGMASKED(LOG_PINS, "Output on P%d = %d\n", pin, data);
int bit = (1 << pin);
@ -408,18 +421,20 @@ WRITE8_MEMBER ( tms9901_device::write )
{
// Switch to interrupt mode; quit clock mode
m_clock_mode = false;
LOGMODE("int mode\n");
LOGMASKED(LOG_MODE, "Enter interrupt mode\n");
}
else
{
m_clock_mode = true;
LOGMODE("clock mode\n");
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)
@ -437,7 +452,7 @@ WRITE8_MEMBER ( tms9901_device::write )
// 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;
LOGMODE("Soft reset (RST2*)\n");
LOGMASKED(LOG_MODE, "Soft reset (RST2*)\n");
}
}
else
@ -447,7 +462,7 @@ WRITE8_MEMBER ( tms9901_device::write )
else
m_enabled_ints &= ~0x4000; /* unset bit */
LOGMODE("interrupts = %04x\n", m_enabled_ints);
LOGMASKED(LOG_CONFIG, "Enabled interrupts = %04x\n", m_enabled_ints);
field_interrupts(); /* changed interrupt state */
}
}
@ -469,7 +484,7 @@ WRITE8_MEMBER ( tms9901_device::write )
m_clock_register &= ~bit; /* clear bit */
/* reset clock timer (page 8) */
LOGCLOCK("clock register = %04x\n", m_clock_register);
LOGMASKED(LOG_CONFIG, "Clock register = %04x\n", m_clock_register);
timer_reload();
}
else
@ -484,7 +499,7 @@ WRITE8_MEMBER ( tms9901_device::write )
if (offset == 3)
m_timer_int_pending = false; /* SBO 3 clears pending timer interrupt (??) */
LOGMODE("enabled interrupts = %04x\n");
LOGMASKED(LOG_CONFIG, "Enabled interrupts = %04x\n", m_enabled_ints);
field_interrupts(); /* changed interrupt state */
}
}
@ -499,11 +514,21 @@ WRITE8_MEMBER ( tms9901_device::write )
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);
}
}
void tms9901_device::clock_in(line_state clk)
{
if (m_clock_active && clk == ASSERT_LINE)
{
m_decrementer_value--;
LOGCLOCK("decrementer = %d\n", m_decrementer_value);
LOGMASKED(LOG_DECVALUE, "Decrementer = %d\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();
m_decrementer_value = m_clock_register;
@ -511,6 +536,21 @@ void tms9901_device::device_timer(emu_timer &timer, device_timer_id id, int para
}
}
/*
Synchronous clock input. This may be used for systems which have
a CLK line controlled by the CPU, like the TMS99xx systems.
In that case, clock is set to 0.
*/
WRITE_LINE_MEMBER( tms9901_device::phi_line )
{
// Divider by 64
if (state==ASSERT_LINE)
m_clockdiv = (m_clockdiv+1) % 0x40;
if (m_clockdiv==0)
clock_in((line_state)state);
}
/*-------------------------------------------------
device_stop - device-specific stop
-------------------------------------------------*/
@ -565,9 +605,12 @@ void tms9901_device::do_reset()
void tms9901_device::device_start()
{
m_decrementer = timer_alloc(DECREMENTER);
m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.));
m_decrementer->enable(false);
// Allow for using asynchronous and synchronous clocks
if (clock() != 0)
{
m_decrementer = timer_alloc(DECREMENTER);
m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.));
}
m_read_block.resolve();
for (auto &cb : m_write_p)

View File

@ -60,6 +60,9 @@ public:
DECLARE_WRITE_LINE_MEMBER( rst1_line );
// Synchronous clock input
DECLARE_WRITE_LINE_MEMBER( phi_line );
DECLARE_READ8_MEMBER( read );
DECLARE_WRITE8_MEMBER( write );
@ -83,6 +86,9 @@ private:
// Common method for device_reset and rst1_line
void do_reset();
// Common clock handling
void clock_in(line_state clk);
// 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.
@ -93,6 +99,9 @@ private:
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)
bool m_clock_active;
int m_clockdiv; // Clock divider counter (for synchronous clock)
// PIO registers
int m_pio_direction; // direction register for PIO
@ -110,7 +119,7 @@ private:
// true = clock mode (read/write clock interval)
bool m_clock_mode;
// MESS timer, used to emulate the decrementer register
// Timer, used to emulate the decrementer register
emu_timer *m_decrementer;
// clock interval, loaded in decrementer when it reaches 0.

View File

@ -647,6 +647,7 @@ WRITE8_MEMBER( geneve_state::external_operation )
*/
WRITE_LINE_MEMBER( geneve_state::clock_out )
{
m_tms9901->phi_line(state);
m_mapper->clock_in(state);
}
@ -749,7 +750,7 @@ MACHINE_CONFIG_START(geneve_state::geneve_common)
screen.set_screen_update(TI_VDP_TAG, FUNC(v99x8_device::screen_update));
// Main board components
TMS9901(config, m_tms9901, 3000000);
TMS9901(config, m_tms9901, 0);
m_tms9901->read_cb().set(FUNC(geneve_state::read_by_9901));
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));

View File

@ -164,7 +164,6 @@
#include "emu.h"
#include "bus/ti99/ti99defs.h"
#include "machine/tms9901.h"
#include "cpu/tms9900/tms9995.h"
#include "bus/ti99/internal/992board.h"
#include "machine/ram.h"

View File

@ -919,6 +919,7 @@ WRITE_LINE_MEMBER( ti99_4p_state::notconnected )
*/
WRITE_LINE_MEMBER( ti99_4p_state::clock_out )
{
m_tms9901->phi_line(state);
datamux_clock_in(state);
m_peribox->clock_in(state);
}
@ -1020,7 +1021,7 @@ void ti99_4p_state::ti99_4p_60hz(machine_config& config)
m_cpu->dbin_cb().set(FUNC(ti99_4p_state::dbin_line));
// tms9901
TMS9901(config, m_tms9901, 3000000);
TMS9901(config, m_tms9901, 0);
m_tms9901->read_cb().set(FUNC(ti99_4p_state::read_by_9901));
m_tms9901->p_out_cb(2).set(FUNC(ti99_4p_state::keyC0));
m_tms9901->p_out_cb(3).set(FUNC(ti99_4p_state::keyC1));

View File

@ -664,6 +664,7 @@ READ8_MEMBER( ti99_4x_state::interrupt_level )
*/
WRITE_LINE_MEMBER( ti99_4x_state::clock_out )
{
m_tms9901->phi_line(state);
m_datamux->clock_in(state);
m_ioport->clock_in(state);
}
@ -867,8 +868,8 @@ void ti99_4x_state::ti99_4_common(machine_config& config)
m_cpu->clkout_cb().set(FUNC(ti99_4x_state::clock_out));
m_cpu->dbin_cb().set(FUNC(ti99_4x_state::dbin_line));
// Main board
TMS9901(config, m_tms9901, 3000000);
// 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));

View File

@ -691,6 +691,7 @@ WRITE8_MEMBER( ti99_8_state::external_operation )
*/
WRITE_LINE_MEMBER( ti99_8_state::clock_out )
{
m_tms9901->phi_line(state);
m_mainboard->clock_in(state);
}
@ -747,7 +748,7 @@ void ti99_8_state::ti99_8(machine_config& config)
m_cpu->holda_cb().set(TI998_MAINBOARD_TAG, FUNC(mainboard8_device::holda_line));
// 9901 configuration
TMS9901(config, m_tms9901, XTAL(10'738'635)/4.0);
TMS9901(config, m_tms9901, 0);
m_tms9901->read_cb().set(FUNC(ti99_8_state::read_by_9901));
m_tms9901->p_out_cb(0).set(FUNC(ti99_8_state::keyC0));
m_tms9901->p_out_cb(1).set(FUNC(ti99_8_state::keyC1));