mirror of
https://github.com/holub/mame
synced 2025-07-01 08:18:59 +03:00
ti99: Fixed cassette instabilities, caused by improper timer usage; introduced synchronous clock input for 9901
This commit is contained in:
parent
fe502c0d41
commit
5755f55af4
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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"
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user