Moved TMS9901/9902 from mess/machine into emu/machine subtree

This commit is contained in:
Michael Zapf 2012-07-23 19:02:56 +00:00
parent 799b4d1951
commit f79593ee04
6 changed files with 1722 additions and 0 deletions

4
.gitattributes vendored
View File

@ -1016,6 +1016,10 @@ src/emu/machine/tmp68301.c svneol=native#text/plain
src/emu/machine/tmp68301.h svneol=native#text/plain
src/emu/machine/tms6100.c svneol=native#text/plain
src/emu/machine/tms6100.h svneol=native#text/plain
src/emu/machine/tms9901.c svneol=native#text/plain
src/emu/machine/tms9901.h svneol=native#text/plain
src/emu/machine/tms9902.c svneol=native#text/plain
src/emu/machine/tms9902.h svneol=native#text/plain
src/emu/machine/upd1990a.c svneol=native#text/plain
src/emu/machine/upd1990a.h svneol=native#text/plain
src/emu/machine/upd4701.c svneol=native#text/plain

View File

@ -253,6 +253,8 @@ EMUMACHINEOBJS = \
$(EMUMACHINE)/timekpr.o \
$(EMUMACHINE)/tmp68301.o \
$(EMUMACHINE)/tms6100.o \
$(EMUMACHINE)/tms9901.o \
$(EMUMACHINE)/tms9902.o \
$(EMUMACHINE)/upd1990a.o \
$(EMUMACHINE)/upd4701.o \
$(EMUMACHINE)/upd7201.o \

542
src/emu/machine/tms9901.c Normal file
View File

@ -0,0 +1,542 @@
/****************************************************************************
TMS9901 Programmable System Interface
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).
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
and 99000 based systems: other CPUs can support interrupts, but not the 16
distinct interrupt vectors.
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.)
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.
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.
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.)
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).
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.
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.
MZ: Turned to class (January 2012)
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)
Raphael Nabet, 2000-2004
Michael Zapf
February 2012: Rewritten as class
*****************************************************************************/
#include <math.h>
#include "emu.h"
#include "tms9901.h"
#define VERBOSE 1
#define LOG logerror
/*
Constructor
*/
tms9901_device::tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, TMS9901, "TMS9901 Programmable System Interface", tag, owner, clock)
{
}
/*
should be called after any change to int_state or enabled_ints.
*/
void tms9901_device::field_interrupts(void)
{
int current_ints;
/* int_state: state of lines int1-int15 */
current_ints = m_int_state;
if (m_clock_register != 0)
{ /* if timer is enabled, INT3 pin is overriden by timer */
if (m_timer_int_pending)
{
if (VERBOSE>8) LOG("tms9901: timer fires\n");
current_ints |= TMS9901_INT3;
}
else
{
if (VERBOSE>8) LOG("tms9901: timer clear\n");
current_ints &= ~TMS9901_INT3;
}
}
/* enabled_ints: enabled interrupts */
/* mask out all int pins currently set as output */
current_ints &= m_enabled_ints & (~m_pio_direction_mirror);
// 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)
return;
m_old_int_state = current_ints;
if (current_ints)
{
// 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++;
}
m_int_pending = true;
if (!m_interrupt.isnull())
m_interrupt(level, 1); // the offset carries the IC0-3 level
}
else
{
m_int_pending = false;
if (!m_interrupt.isnull())
m_interrupt(0xf, 0); //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)
state == 0: INTn* is inactive (high)
state != 0: INTn* is active (low)
0<=pin_number<=15
*/
void tms9901_device::set_single_int(int pin_number, 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);
/* we do not need to always call this function - time for an optimization */
field_interrupts();
}
/*
load the content of m_clock_register into the decrementer
*/
void tms9901_device::timer_reload(void)
{
if (m_clock_register != 0)
{ /* reset clock interval */
m_decrementer_value = m_clock_register;
m_decrementer->enable(true);
}
else
{ /* clock interval == 0 -> no timer */
m_decrementer->enable(false);
}
}
/*----------------------------------------------------------------
TMS9901 CRU interface.
----------------------------------------------------------------*/
/*
Read a 8 bit chunk 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 16-31: current status of the P0-P15 pins (quits timer mode, too...)
*/
READ8_MEMBER( tms9901_device::read )
{
int answer = 0;
offset &= 0x003;
switch (offset)
{
case 0:
if (m_clock_mode)
{ /* clock mode */
answer = ((m_clock_read_register & 0x7F) << 1) | 0x01;
}
else
{ /* interrupt mode */
// m_int_state stores the INTx values, which are inverted to the pin levels (INTx*)
answer = ((~m_int_state) & m_supported_int_mask) & 0xFF;
if (!m_read_block.isnull())
answer |= m_read_block(TMS9901_CB_INT7);
answer &= ~ m_pio_direction_mirror;
answer |= (m_pio_output_mirror & m_pio_direction_mirror) & 0xFF;
}
break;
case 1:
if (m_clock_mode)
{ /* clock mode */
answer = (m_clock_read_register & 0x3F80) >> 7;
if (!m_int_pending)
answer |= 0x80;
}
else
{ /* interrupt mode */
answer = ((~m_int_state) & m_supported_int_mask) >> 8;
if (!m_read_block.isnull())
answer |= m_read_block(TMS9901_INT8_INT15);
answer &= ~ (m_pio_direction_mirror >> 8);
answer |= (m_pio_output_mirror & m_pio_direction_mirror) >> 8;
}
break;
case 2:
/* exit timer mode */
// MZ: See comments at the beginning. I'm pretty sure this is not correct.
// m_clock_mode = false;
if (!m_read_block.isnull())
answer = m_read_block(TMS9901_P0_P7);
else
answer = 0;
answer &= ~ m_pio_direction;
answer |= (m_pio_output & m_pio_direction) & 0xFF;
break;
case 3:
// MZ: see above
// m_clock_mode = false; // exit timer mode
if (!m_read_block.isnull())
answer = m_read_block(TMS9901_P8_P15);
else
answer = 0;
answer &= ~ (m_pio_direction >> 8);
answer |= (m_pio_output & m_pio_direction) >> 8;
break;
}
return answer;
}
/*
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...)
*/
WRITE8_MEMBER ( tms9901_device::write )
{
data &= 1; /* clear extra bits */
offset &= 0x01F;
switch (offset)
{
case 0x00: /* write to mode bit */
if (data == 0)
{
/* we are quitting clock mode */
m_clock_mode = false;
if (VERBOSE>5) LOG("tms9901: int mode\n");
}
else
{
m_clock_mode = true;
if (VERBOSE>5) LOG("tms9901: 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... */
}
break;
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
case 0x08:
case 0x09:
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0D:
case 0x0E:
// 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)
if (m_clock_mode)
{ /* modify clock interval */
int mask = 1 << ((offset & 0x0F) - 1); /* corresponding mask */
if (data)
m_clock_register |= mask; /* set bit */
else
m_clock_register &= ~mask; /* clear bit */
/* reset clock timer (page 8) */
if (VERBOSE>6) LOG("tms9901: clock register = %04x\n", m_clock_register);
timer_reload();
}
else
{ /* modify interrupt enable mask */
int mask = 1 << (offset & 0x0F); /* corresponding mask */
if (data)
m_enabled_ints |= mask; /* set bit */
else
m_enabled_ints &= ~mask; /* unset bit */
if (offset == 3)
m_timer_int_pending = false; /* SBO 3 clears pending timer interrupt (??) */
if (VERBOSE>6) LOG("tms9901: interrupts = %04x\n", m_enabled_ints);
field_interrupts(); /* changed interrupt state */
}
break;
case 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;
if (VERBOSE>5) LOG("tms9901: Soft reset (RST2*)\n");
}
}
else
{ /* modify interrupt enable mask */
if (data)
m_enabled_ints |= 0x4000; /* set bit */
else
m_enabled_ints &= ~0x4000; /* unset bit */
if (VERBOSE>6) LOG("tms9901: interrupts = %04x\n", m_enabled_ints);
field_interrupts(); /* changed interrupt state */
}
break;
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
int pin = offset & 0x0F;
if (VERBOSE>6) LOG("tms9901: output on P%d = %d\n", pin, data);
int mask = (1 << pin);
// MZ: see above - I think this is wrong
// m_clock_mode = false; // exit timer mode
m_pio_direction |= mask; /* set up as output pin */
if (data)
m_pio_output |= mask;
else
m_pio_output &= ~mask;
if (pin >= 7)
{ /* pins P7-P15 are mirrored as INT15*-INT7* */
int pin2 = 22 - pin;
int mask2 = (1 << pin2);
m_pio_direction_mirror |= mask2; /* set up as output pin */
if (data)
m_pio_output_mirror |= mask2;
else
m_pio_output_mirror &= ~ mask2;
}
if (!m_write_line[pin].isnull())
(m_write_line[pin])(data);
break;
}
}
/*
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
{
m_decrementer_value--;
if (VERBOSE>6) LOG("tms9901: decrementer = %d\n", m_decrementer_value);
if (m_decrementer_value<=0)
{
m_timer_int_pending = true; // decrementer interrupt requested
field_interrupts();
m_decrementer_value = m_clock_register;
}
}
}
/*-------------------------------------------------
DEVICE_STOP( tms9901 )
-------------------------------------------------*/
void tms9901_device::device_stop(void)
{
}
/*-------------------------------------------------
DEVICE_RESET( tms9901 )
-------------------------------------------------*/
void tms9901_device::device_reset(void)
{
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;
m_int_state = 0;
m_old_int_state = -1;
field_interrupts();
m_clock_mode = false;
m_clock_register = 0;
timer_reload();
}
/*-------------------------------------------------
DEVICE_START( tms9901 )
-------------------------------------------------*/
void tms9901_device::device_start(void)
{
const tms9901_interface *intf = reinterpret_cast<const tms9901_interface *>(static_config());
m_supported_int_mask = intf->interrupt_mask;
m_decrementer = timer_alloc(DECREMENTER);
m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.));
m_decrementer->enable(false);
m_read_block.resolve(intf->read_handler, *this);
for (int i=0; i < 16; i++)
{
m_write_line[i].resolve(intf->write_handler[i], *this);
}
m_interrupt.resolve(intf->interrupt_callback, *this);
}
const device_type TMS9901 = &device_creator<tms9901_device>;

142
src/emu/machine/tms9901.h Normal file
View File

@ -0,0 +1,142 @@
/****************************************************************************
TMS9901 Programmable System Interface
See tms9901.c for documentation
Raphael Nabet
Michael Zapf
February 2012: Rewritten as class
*****************************************************************************/
#ifndef __TMS9901_H__
#define __TMS9901_H__
#include "emu.h"
extern const device_type TMS9901;
/***************************************************************************
MACROS
***************************************************************************/
/* Masks for the interrupts levels available on TMS9901 */
#define TMS9901_INT1 0x0002
#define TMS9901_INT2 0x0004
#define TMS9901_INT3 0x0008 // overriden by the timer interrupt
#define TMS9901_INT4 0x0010
#define TMS9901_INT5 0x0020
#define TMS9901_INT6 0x0040
#define TMS9901_INT7 0x0080
#define TMS9901_INT8 0x0100
#define TMS9901_INT9 0x0200
#define TMS9901_INTA 0x0400
#define TMS9901_INTB 0x0800
#define TMS9901_INTC 0x1000
#define TMS9901_INTD 0x2000
#define TMS9901_INTE 0x4000
#define TMS9901_INTF 0x8000
enum
{
TMS9901_CB_INT7 = 0,
TMS9901_INT8_INT15 = 1,
TMS9901_P0_P7 = 2,
TMS9901_P8_P15 = 3
};
/***************************************************************************
CLASS DEFINITION
***************************************************************************/
typedef struct _tms9901_interface
{
int interrupt_mask; // a bit for each input pin whose state is always notified to the TMS9901 core
devcb_read8 read_handler; // 4*8 bits, to be selected using the offset (0-3)
devcb_write_line write_handler[16]; // 16 Pn outputs
devcb_write8 interrupt_callback; // called when interrupt bus state changes
} tms9901_interface;
class tms9901_device : public device_t
{
public:
tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
void set_single_int(int pin_number, int state);
DECLARE_READ8_MEMBER( read );
DECLARE_WRITE8_MEMBER( write );
private:
static const device_timer_id DECREMENTER = 0;
void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);
void timer_reload(void);
void field_interrupts(void);
void device_start(void);
void device_stop(void);
void device_reset(void);
/* interrupt registers */
// mask: bit #n is set if pin #n is supported as an interrupt pin,
// i.e. the driver sends a notification whenever the pin state changes
// setting these bits is not required, but it saves you the trouble of
// saving the state of interrupt pins and feeding it to the port read
// handlers again
int m_supported_int_mask;
int m_int_state; // state of the int1-int15 lines (must be inverted when queried)
int m_old_int_state; // stores the previous value to avoid useless INT line assertions
int m_enabled_ints; // interrupt enable mask
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)
// PIO registers
int m_pio_direction; // direction register for PIO
// current PIO output (to be masked with pio_direction)
int m_pio_output;
// mirrors used for INT7*-INT15*
int m_pio_direction_mirror;
int m_pio_output_mirror;
// =======================================================================
// TMS9901 clock mode
// false = so-called interrupt mode (read interrupt state, write interrupt enable mask)
// true = clock mode (read/write clock interval)
bool m_clock_mode;
// MESS 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;
// Current decrementer value
int 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;
// =======================================================================
// Callbacks
devcb_resolved_read8 m_read_block;
devcb_resolved_write_line m_write_line[16];
devcb_resolved_write8 m_interrupt; // also delivers the interrupt level
};
/***************************************************************************
DEVICE CONFIGURATION MACROS
***************************************************************************/
#define MCFG_TMS9901_ADD(_tag, _intrf, _rate) \
MCFG_DEVICE_ADD(_tag, TMS9901, _rate) \
MCFG_DEVICE_CONFIG(_intrf)
#endif /* __TMS9901_H__ */

837
src/emu/machine/tms9902.c Normal file
View File

@ -0,0 +1,837 @@
/****************************************************************************
TMS9902 Asynchronous Communication Controller
TMS9902 is an asynchronous serial controller for use with the TI990 and
TMS9900 family. It provides serial I/O, three extra I/O pins (namely RTS,
DSR and CTS), and a timer. It communicates with the CPU through the CRU
I/O bus, and one interrupt pin.
+----+--+----+
<- /INT |1 \--/ 18| VCC
<- XOUT |2 17| /CE <-
-> RIN |3 16| /PHI <-
<- CRUIN |4 15| CRUCLK <-
<- /RTS |5 14| S0 <-
-> /CTS |6 13| S1 <-
-> /DSR |7 12| S2 <-
-> CRUOUT |8 11| S3 <-
VSS |9 10| S4 <-
+------------+
The CRUIN line borrows its name from the connector of the connected CPU
where it is an input, so CRUIN is an output of this chip. The same is true
for CRUOUT.
/PHI is a TTL clock input with 4 MHz maximum rate.
IMPORTANT NOTE: The previous versions of TMS9902 attempted to write their
output to a file. This implementation is able to communicate with an external
UART via a socket connection and an external bridge. However, the work is
not done yet, and until then the file writing is disabled.
Raphael Nabet, 2003
Michael Zapf, 2011
February 2012: Rewritten as class
*****************************************************************************/
#include <math.h>
#include "tms9902.h"
#define VERBOSE 1
#define LOG logerror
enum
{
DECTIMER,
RECVTIMER,
SENDTIMER
};
// Polling frequency. We use a much higher value to allow for line state changes
// happening between character transmissions (which happen in parallel in real
// communications but which must be serialized here)
#define POLLING_FREQ 20000
/*
Constructor
*/
tms9902_device::tms9902_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, TMS9902, "TMS9902 Asynchronous Communication Controller", tag, owner, clock)
{
}
/*
should be called after any change to int_state or enabled_ints.
*/
void tms9902_device::field_interrupts()
{
bool new_int = (m_DSCH && m_DSCENB)
|| (m_RBRL && m_RIENB)
|| (m_XBRE && m_XBIENB)
|| (m_TIMELP && m_TIMENB);
// if (VERBOSE>3) LOG("TMS9902: interrupt flags (DSCH = %02x, DSCENB = %02x), (RBRL = %02x, RIENB = %02x), (XBRE = %02x, XBIENB = %02x), (TIMELP = %02x, TIMENB = %02x)\n",
// m_DSCH, m_DSCENB, m_RBRL, m_RIENB, m_XBRE, m_XBIENB, m_TIMELP, m_TIMENB);
if (new_int != m_INT)
{
// Only consider edges
m_INT = new_int;
if (VERBOSE>3) LOG("TMS9902: /INT = %s\n", (m_INT)? "asserted" : "cleared");
int_callback(m_INT? ASSERT_LINE : CLEAR_LINE);
}
}
/*
Called whenever the incoming CTS* line changes. This should be called by
the device that contains the UART.
*/
void tms9902_device::rcv_cts(line_state state)
{
bool previous = m_CTSin;
// CTSin is an internal register of the TMS9902 with positive logic
m_CTSin = (state==ASSERT_LINE);
if (VERBOSE>3) LOG("TMS9902: CTS* = %s\n", (state==ASSERT_LINE)? "asserted" : "cleared");
if (m_CTSin != previous)
{
m_DSCH = true;
field_interrupts();
// If CTS becomes asserted and we have been sending
if (state==ASSERT_LINE && m_RTSout)
{
// and if the byte buffer is empty
if (m_XBRE)
{
// and we want to have a BRK, send it
if (m_BRKON) send_break(true);
}
else
{
// Buffer is not empty, we can send it
// If the shift register is empty, transfer the data
if (m_XSRE && !m_BRKout)
{
initiate_transmit();
}
}
}
}
else
{
m_DSCH = false;
if (VERBOSE>4) LOG("TMS9902: no change in CTS line, no interrupt.");
}
}
void tms9902_device::set_clock(bool state)
{
if (state)
m_recvtimer->adjust(attotime::from_msec(1), 0, attotime::from_hz(POLLING_FREQ));
else
m_recvtimer->reset();
}
/*
Called whenever the incoming DSR* line changes. This should be called by
the device that contains the UART.
*/
void tms9902_device::rcv_dsr(line_state state)
{
bool previous = m_DSRin;
if (VERBOSE>3) LOG("TMS9902: DSR* = %s\n", (state==ASSERT_LINE)? "asserted" : "cleared");
m_DSRin = (state==ASSERT_LINE);
if (m_DSRin != previous)
{
m_DSCH = true;
field_interrupts();
}
else
{
m_DSCH = false;
if (VERBOSE>4) LOG("TMS9902: no change in DSR line, no interrupt.");
}
}
/*
Called whenever the incoming RIN line changes. This should be called by
the device that contains the UART. Unlike the real thing, we deliver
complete bytes in one go.
*/
void tms9902_device::rcv_data(UINT8 data)
{
// Put the received byte into the 1-byte receive buffer
m_RBR = data;
// Clear last errors
m_RFER = false;
m_RPER = false;
if (!m_RBRL)
{
// Receive buffer was empty
m_RBRL = true;
m_ROVER = false;
if (VERBOSE>3) LOG("TMS9902: Receive buffer loaded with byte %02x\n", data);
field_interrupts();
}
else
{
// Receive buffer was full
m_ROVER = true;
if (VERBOSE>1) LOG("TMS9902: Receive buffer still loaded; overflow error\n");
}
}
//------------------------------------------------
/*
Framing error. This can only be detected by a remotely attached real UART;
if we get a report on a framing error we use it to announce the framing error
as if it occurred here.
The flag is reset by the next correctly received byte.
*/
void tms9902_device::rcv_framing_error()
{
if (VERBOSE>2) LOG("TMS9902: Detected framing error\n");
m_RFER = true;
}
/*
Parity error. This can only be detected by a remotely attached real UART;
if we get a report on a parity error we use it to announce the parity error
as if it occurred here.
The flag is reset by the next correctly received byte.
*/
void tms9902_device::rcv_parity_error()
{
if (VERBOSE>2) LOG("TMS9902: Detected parity error\n");
m_RPER = true;
}
/*
Incoming BREAK condition. The TMS9902 does not show any directly visible
reactions on a BREAK (no interrupt, no flag set). A BREAK is a time period
of low level on the RIN pin which makes the chip re-synchronize on the
next rising edge.
*/
void tms9902_device::rcv_break(bool value)
{
if (VERBOSE>2) LOG("TMS9902: Receive BREAK=%d (no effect)\n", value? 1:0);
}
//------------------------------------------------
/*
Timer callback
*/
void tms9902_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
// This call-back is called by the MESS timer system when the decrementer
// reaches 0.
case DECTIMER:
m_TIMERR = m_TIMELP;
break;
// Callback for the autonomous operations of the chip. This is normally
// controlled by an external clock of 3-4 MHz, internally divided by 3 or 4,
// depending on CLK4M. With this timer, reception of characters becomes
// possible.
case RECVTIMER:
rcv_callback(ASSERT_LINE);
break;
case SENDTIMER:
// Byte has been sent
m_XSRE = true;
// In the meantime, the CPU may have pushed a new byte into the XBR
// so we loop until all data are transferred
if (!m_XBRE && m_CTSin)
{
initiate_transmit();
}
break;
}
}
/*
load the content of clockinvl into the decrementer
*/
void tms9902_device::reload_interval_timer()
{
if (m_TMR)
{ /* reset clock interval */
m_dectimer->adjust(
attotime::from_double((double) m_TMR / (m_clock_rate / ((m_CLK4M) ? 4. : 3.) / 64.)),
0,
attotime::from_double((double) m_TMR / (m_clock_rate / ((m_CLK4M) ? 4. : 3.) / 64.)));
}
else
{ /* clock interval == 0 -> no timer */
m_dectimer->enable(0);
}
}
void tms9902_device::send_break(bool state)
{
if (state != m_BRKout)
{
m_BRKout = state;
if (VERBOSE>2) LOG("TMS9902: Sending BREAK=%d\n", state? 1:0);
// Signal BRK (on/off) to the remote site
ctrl_callback((EXCEPT | BRK), state? 1:0);
}
}
/*
Baudpoll value allows the callback function to know when the next data byte shall be delivered.
*/
double tms9902_device::get_baudpoll()
{
return m_baudpoll;
}
// ==========================================================================
/*
Sets the data rate for the receiver part. If a remote UART is attached,
propagate this setting.
The TMS9902 calculates the baud rate from the external clock, and the result
does not match the known baud rates precisely (e.g. for 9600 baud the
closest value is 9615). Other UARTs may have a different way to set baud
rates. Thus we transmit the bit pattern and leave it up to the remote UART
to calculate its own baud rate from it. Apart from that, the callback
function should add information about the UART.
CLK4M RDV8 RDR9 RDR8 | RDR7 RDR6 RDR5 RDR4 | RDR3 RDR2 RDR1 RDR0
*/
void tms9902_device::set_receive_data_rate()
{
int value = (m_CLK4M? 0x800 : 0) | (m_RDV8? 0x400 : 0) | m_RDR;
if (VERBOSE>3) LOG("TMS9902: receive rate = %04x\n", value);
// Calculate the ratio between receive baud rate and polling frequency
double fint = m_clock_rate / ((m_CLK4M) ? 4.0 : 3.0);
double baud = fint / (2.0 * ((m_RDV8)? 8:1) * m_RDR);
// We assume 10 bit per character (7 data usually add 1 parity; 1 start, 1 stop)
// This value represents the ratio of data inputs of one poll.
// Thus the callback function should add up this value on each poll
// and deliver a data input not before it sums up to 1.
m_baudpoll = (double)(baud / (10*POLLING_FREQ));
if (VERBOSE>3) LOG ("TMS9902: baudpoll = %lf\n", m_baudpoll);
m_last_config_value = value;
ctrl_callback(CONFIG, RATERECV);
}
/*
Sets the data rate for the sender part. If a remote UART is attached,
propagate this setting.
*/
void tms9902_device::set_transmit_data_rate()
{
int value = (m_CLK4M? 0x800 : 0) | (m_XDV8? 0x400 : 0) | m_XDR;
if (VERBOSE>3) LOG("TMS9902: set transmit rate = %04x\n", value);
m_last_config_value = value;
ctrl_callback(CONFIG, RATEXMIT);
}
void tms9902_device::set_stop_bits()
{
int value = m_STOPB;
if (VERBOSE>3) LOG("TMS9902: set stop bits = %02x\n", value);
m_last_config_value = value;
ctrl_callback(CONFIG, STOPBITS);
}
void tms9902_device::set_data_bits()
{
int value = m_RCL;
if (VERBOSE>3) LOG("TMS9902: set data bits = %02x\n", value);
m_last_config_value = value;
ctrl_callback(CONFIG, DATABITS);
}
void tms9902_device::set_parity()
{
int value = (m_PENB? 2:0) | (m_ODDP? 1:0);
if (VERBOSE>3) LOG("TMS9902: set parity = %02x\n", value);
m_last_config_value = value;
ctrl_callback(CONFIG, PARITY);
}
void tms9902_device::transmit_line_state()
{
// 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f
// The 9902 only outputs RTS and BRK
if (VERBOSE>3) LOG("TMS9902: transmitting line state (only RTS) = %02x\n", (m_RTSout)? 1:0);
m_last_config_value = (m_RTSout)? RTS : 0;
ctrl_callback(LINES, RTS);
}
void tms9902_device::set_rts(line_state state)
{
bool lstate = (state==ASSERT_LINE);
if (lstate != m_RTSout)
{
// Signal RTS to the modem
if (VERBOSE>3) LOG("TMS9902: Set RTS=%d\n", lstate? 1:0);
m_RTSout = lstate;
transmit_line_state();
}
}
int tms9902_device::get_config_value()
{
return m_last_config_value;
}
// ==========================================================================
void tms9902_device::initiate_transmit()
{
if (m_BRKON && m_CTSin)
/* enter break mode */
send_break(true);
else
{
if (!m_RTSON && (!m_CTSin || (m_XBRE && !m_BRKout)))
/* clear RTS output */
set_rts(CLEAR_LINE);
else
{
if (VERBOSE>5) LOG("TMS9902: transferring XBR to XSR; XSRE=false, XBRE=true\n");
m_XSR = m_XBR;
m_XSRE = false;
m_XBRE = true;
field_interrupts();
if (VERBOSE>4) LOG("TMS9902: transmit XSR=%02x, RCL=%02x\n", m_XSR, m_RCL);
xmit_callback(0, m_XSR & (0xff >> (3-m_RCL)));
// Should store that somewhere (but the CPU is fast enough, can afford to recalc :-) )
double fint = m_clock_rate / ((m_CLK4M) ? 4.0 : 3.0);
double baud = fint / (2.0 * ((m_RDV8)? 8:1) * m_RDR);
// Time for transmitting 10 bit (8 bit + start + stop)
m_sendtimer->adjust(attotime::from_hz(baud/10.0));
}
}
}
/*----------------------------------------------------------------
TMS9902 CRU interface.
----------------------------------------------------------------*/
/*
Read a 8 bit chunk from tms9902.
signification:
bit 0-7: RBR0-7 Receive Buffer register
bit 8: not used (always 0)
bit 9: RCVERR Receive Error (RFER | ROVER | RPER)
bit 10: RPER Receive Parity Error
bit 11: ROVER Receive Overrun Error
bit 12: RFER Receive Framing Error
bit 13-15: not emulated, normally used for diagnostics
bit 16: RBINT (RBRL&RIENB)
*/
READ8_MEMBER( tms9902_device::cruread )
{
UINT8 answer = 0;
offset &= 0x0003;
switch (offset)
{
case 3: // Bits 31-24
if (m_INT) answer |= 0x80;
if (m_LDCTRL || m_LDIR || m_LRDR || m_LXDR || m_BRKON) answer |= 0x40;
if (m_DSCH) answer |= 0x20;
if (m_CTSin) answer |= 0x10;
if (m_DSRin) answer |= 0x08;
if (m_RTSout) answer |= 0x04;
if (m_TIMELP) answer |= 0x02;
if (m_TIMERR) answer |= 0x01;
break;
case 2: // Bits 23-16
if (m_XSRE) answer |= 0x80;
if (m_XBRE) answer |= 0x40;
if (m_RBRL) answer |= 0x20;
if (m_DSCH && m_DSCENB) answer |= 0x10;
if (m_TIMELP && m_TIMENB) answer |= 0x08;
if (m_XBRE && m_XBIENB) answer |= 0x02;
if (m_RBRL && m_RIENB) answer |= 0x01;
break;
case 1: // Bits 15-8
if (m_RIN) answer |= 0x80;
if (m_RSBD) answer |= 0x40;
if (m_RFBD) answer |= 0x20;
if (m_RFER) answer |= 0x10;
if (m_ROVER) answer |= 0x08;
if (m_RPER) answer |= 0x04;
if (m_RPER || m_RFER || m_ROVER) answer |= 0x02;
break;
case 0: // Bits 7-0
answer = m_RBR;
break;
}
if (VERBOSE>7) LOG("TMS9902: Reading flag bits %d - %d = %02x\n", ((offset+1)*8-1), offset*8, answer);
return answer;
}
static inline void set_bits8(UINT8 *reg, UINT8 bits, bool set)
{
if (set)
*reg |= bits;
else
*reg &= ~bits;
}
static inline void set_bits16(UINT16 *reg, UINT16 bits, bool set)
{
if (set)
*reg |= bits;
else
*reg &= ~bits;
}
void tms9902_device::reset_uart()
{
if (VERBOSE>1) LOG("TMS9902: resetting\n");
/* disable all interrupts */
m_DSCENB = false; // Data Set Change Interrupt Enable
m_TIMENB = false; // Timer Interrupt Enable
m_XBIENB = false; // Transmit Buffer Interrupt Enable
m_RIENB = false; // Read Buffer Interrupt Enable
/* initialize transmitter */
m_XBRE = true; // Transmit Buffer Register Empty
m_XSRE = true; // Transmit Shift Register Empty
/* initialize receiver */
m_RBRL = false; // Read Buffer Register Loaded
/* clear RTS */
m_RTSON = false; // Request-to-send on (flag)
m_RTSout = true; // Note we are doing this to ensure the state is sent to the interface
set_rts(CLEAR_LINE);
m_RTSout = false; // what we actually want
/* set all register load flags to 1 */
m_LDCTRL = true;
m_LDIR = true;
m_LRDR = true;
m_LXDR = true;
/* clear break condition */
m_BRKON = false;
m_BRKout = false;
m_TMR = 0;
m_STOPB = 0;
m_RCL = 0;
m_XDR = 0;
m_RDR = 0;
m_RBR = 0;
m_XBR = 0;
m_XSR = 0;
field_interrupts();
}
/*
TMS9902 CRU write
*/
WRITE8_MEMBER( tms9902_device::cruwrite )
{
data &= 1; /* clear extra bits */
offset &= 0x1F;
if (VERBOSE>5) LOG("TMS9902: Setting bit %d = %02x\n", offset, data);
if (offset <= 10)
{
UINT16 mask = (1 << offset);
if (m_LDCTRL)
{ // Control Register mode. Values written to bits 0-7 are copied
// into the control register.
switch (offset)
{
case 0:
set_bits8(&m_RCL, 0x01, (data!=0));
// we assume that bits are written in increasing order
// so we do not transmit the data bits twice
// (will fail when bit 1 is written first)
break;
case 1:
set_bits8(&m_RCL, 0x02, (data!=0));
set_data_bits();
break;
case 2:
break;
case 3:
m_CLK4M = (data!=0);
break;
case 4:
m_ODDP = (data!=0);
// we also assume that the parity type is set before the parity enable
break;
case 5:
m_PENB = (data!=0);
set_parity();
break;
case 6:
set_bits8(&m_STOPB, 0x01, (data!=0));
break;
case 7:
set_bits8(&m_STOPB, 0x02, (data!=0));
// When bit 7 is written the control register mode is automatically terminated.
m_LDCTRL = false;
set_stop_bits();
break;
default:
if (VERBOSE>1) LOG("tms9902: Invalid control register address %d\n", offset);
}
}
else if (m_LDIR)
{ // Interval Register mode. Values written to bits 0-7 are copied
// into the interval register.
if (offset <= 7)
{
set_bits8(&m_TMR, mask, (data!=0));
if (offset == 7)
{
reload_interval_timer();
// When bit 7 is written the interval register mode is automatically terminated.
m_LDIR = false;
}
}
}
else if (m_LRDR || m_LXDR)
{
if (m_LRDR)
{ // Receive rate register mode. Values written to bits 0-10 are copied
// into the receive rate register.
if (offset < 10)
{
set_bits16(&m_RDR, mask, (data!=0));
}
else
{
// When bit 10 is written the receive register mode is automatically terminated.
m_RDV8 = (data!=0);
m_LRDR = false;
set_receive_data_rate();
}
}
if (m_LXDR)
{
// The transmit rate register can be set together with the receive rate register.
if (offset < 10)
{
set_bits16(&m_XDR, mask, (data!=0));
}
else
{
// Note that the transmit rate register is NOT terminated when
// writing bit 10. This must be done by unsetting bit 11.
m_XDV8 = (data!=0);
set_transmit_data_rate();
}
}
}
else
{ // LDCTRL=LDIR=LRDR=LXRD=0: Transmit buffer register mode. Values
// written to bits 0-7 are transferred into the transmit buffer register.
if (offset <= 7)
{
set_bits8(&m_XBR, mask, (data!=0));
if (offset == 7)
{ /* transmit */
m_XBRE = false;
// Spec: When the transmitter is active, the contents of the Transmit
// Buffer Register are transferred to the Transmit Shift Register
// each time the previous character has been completely transmitted
// We need to check XSRE=true as well, as the implementation
// makes use of a timed transmission, during which XSRE=false
if (m_XSRE && m_RTSout && m_CTSin && !m_BRKout)
{
initiate_transmit();
}
}
}
}
return;
}
switch (offset)
{
case 11:
m_LXDR = (data!=0);
break;
case 12:
m_LRDR = (data!=0);
break;
case 13:
m_LDIR = (data!=0);
// Spec: Each time LDIR is reset the contents of the Interval
// Register are loaded into the Interval Timer, thus restarting
// the timer.
if (data==0)
reload_interval_timer();
break;
case 14:
m_LDCTRL = (data!=0);
break;
case 15:
m_TSTMD = (data!=0); // Test mode not implemented
break;
case 16:
if (data!=0)
{
m_RTSON = true;
set_rts(ASSERT_LINE);
if (m_CTSin)
{
if (m_XSRE && !m_XBRE && !m_BRKout)
initiate_transmit();
else if (m_BRKON)
send_break(true);
}
}
else
{
m_RTSON = false;
if (m_XBRE && m_XSRE && !m_BRKout)
{
set_rts(CLEAR_LINE);
}
}
return;
case 17:
if (VERBOSE>3) LOG("TMS9902: set BRKON=%d; BRK=%d\n", data, m_BRKout? 1:0);
m_BRKON = (data!=0);
if (m_BRKout && data==0)
{
// clear BRK
m_BRKout = false;
if ((!m_XBRE) && m_CTSin)
{
/* transmit next byte */
initiate_transmit();
}
else if (!m_RTSON)
{
/* clear RTS */
set_rts(CLEAR_LINE);
}
}
else if (m_XBRE && m_XSRE && m_RTSout && m_CTSin)
{
send_break(data!=0);
}
return;
case 18:
// Receiver Interrupt Enable
// According to spec, (re)setting this flag clears the RBRL flag
// (the only way to clear the flag!)
m_RIENB = (data!=0);
m_RBRL = false;
if (VERBOSE>4) LOG("TMS9902: set RBRL=0, set RIENB=%d\n", data);
field_interrupts();
return;
case 19:
/* Transmit Buffer Interrupt Enable */
m_XBIENB = (data!=0);
if (VERBOSE>4) LOG("TMS9902: set XBIENB=%d\n", data);
field_interrupts();
return;
case 20:
/* Timer Interrupt Enable */
m_TIMENB = (data!=0);
m_TIMELP = false;
m_TIMERR = false;
field_interrupts();
return;
case 21:
/* Data Set Change Interrupt Enable */
m_DSCENB = (data!=0);
m_DSCH = false;
if (VERBOSE>4) LOG("TMS9902: set DSCH=0, set DSCENB=%d\n", data);
field_interrupts();
return;
case 31:
/* RESET */
reset_uart();
return;
default:
if (VERBOSE>1) LOG("TMS9902: Writing to undefined flag bit position %d = %01x\n", offset, data);
}
}
/*-------------------------------------------------
DEVICE_STOP( tms9902 )
-------------------------------------------------*/
void tms9902_device::device_stop()
{
if (m_dectimer)
{
m_dectimer->reset();
m_dectimer = NULL;
}
}
/*-------------------------------------------------
DEVICE_RESET( tms9902 )
-------------------------------------------------*/
void tms9902_device::device_reset()
{
reset_uart();
}
/*-------------------------------------------------
DEVICE_START( tms9902 )
-------------------------------------------------*/
void tms9902_device::device_start()
{
const tms9902_interface *intf = reinterpret_cast<const tms9902_interface *>(static_config());
m_clock_rate = clock();
int_callback.resolve(intf->int_callback, *this);
rcv_callback.resolve(intf->rcv_callback, *this);
xmit_callback.resolve(intf->xmit_callback, *this);
ctrl_callback.resolve(intf->ctrl_callback, *this);
m_dectimer = timer_alloc(DECTIMER);
m_recvtimer = timer_alloc(RECVTIMER);
m_sendtimer = timer_alloc(SENDTIMER);
}
const device_type TMS9902 = &device_creator<tms9902_device>;

195
src/emu/machine/tms9902.h Normal file
View File

@ -0,0 +1,195 @@
/****************************************************************************
TMS9902 Asynchronous Communication Controller
See tms9902.c for documentation
Michael Zapf
February 2012: Rewritten as class
*****************************************************************************/
#ifndef __TMS9902_H__
#define __TMS9902_H__
#include "emu.h"
// Serial control protocol values
#define TYPE_TMS9902 0x01
// Configuration (output only)
#define CONFIG 0x80
#define RATERECV 0x70
#define RATEXMIT 0x60
#define DATABITS 0x50
#define STOPBITS 0x40
#define PARITY 0x30
// Exceptional states (BRK: both directions; FRMERR/PARERR: input only)
#define EXCEPT 0x40
#define BRK 0x02
#define FRMERR 0x04
#define PARERR 0x06
// Line states (RTS, DTR: output; CTS, DSR, RI, DCD: input)
#define LINES 0x00
#define RTS 0x20
#define CTS 0x10
#define DSR 0x08
#define DCD 0x04
#define DTR 0x02
#define RI 0x01
extern const device_type TMS9902;
typedef struct _tms9902_interface
{
devcb_write_line int_callback;
devcb_write_line rcv_callback;
devcb_write8 xmit_callback;
devcb_write8 ctrl_callback;
} tms9902_interface;
class tms9902_device : public device_t
{
public:
tms9902_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
void set_clock(bool state);
void rcv_cts(line_state state);
void rcv_dsr(line_state state);
void rcv_data(UINT8 data);
void rcv_break(bool value);
void rcv_framing_error();
void rcv_parity_error();
double get_baudpoll();
int get_config_value();
DECLARE_READ8_MEMBER( cruread );
DECLARE_WRITE8_MEMBER( cruwrite );
protected:
void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);
void device_start();
void device_reset();
void device_stop();
private:
void field_interrupts();
void reload_interval_timer();
void send_break(bool state);
void set_receive_data_rate();
void set_transmit_data_rate();
void set_stop_bits();
void set_data_bits();
void set_parity();
void transmit_line_state();
void set_rts(line_state state);
void initiate_transmit();
void reset_uart();
devcb_resolved_write_line int_callback;
devcb_resolved_write_line rcv_callback;
devcb_resolved_write8 xmit_callback;
devcb_resolved_write8 ctrl_callback; // needs to be used with get_config_value
// tms9902 clock rate (PHI* pin, normally connected to TMS9900 Phi3*)
// Official range is 2MHz-3.3MHz. Some tms9902s were sold as "MP9214", and
// were tested for speeds up to 4MHz, provided the clk4m control bit is set.
// (warning: 3MHz on a tms9900 is equivalent to 12MHz on a tms9995 or tms99000)
double m_clock_rate;
/* Modes */
bool m_LDCTRL; // Load control register
bool m_LDIR; // Load interval register
bool m_LRDR; // Load receive data register
bool m_LXDR; // Load transmit data register
bool m_TSTMD; // Test mode
/* output pin */
bool m_RTSON; // RTS-on request
/* transmitter registers */
bool m_BRKON; // BRK-on request
bool m_BRKout; // indicates the current BRK state
UINT8 m_XBR; // transmit buffer register
UINT8 m_XSR; // transmit shift register
/* receiver registers */
UINT8 m_RBR; // Receive buffer register
/* Interrupt enable flags */
bool m_DSCENB; // Data set change interrupt enable
bool m_RIENB; // Receiver interrupt enable
bool m_XBIENB; // Tansmit buffer interrupt enable
bool m_TIMENB; // Timer interrupt enable
/*
Rate registers. The receive bit rate calculates as
bitrate = clock1 / (2 * (8 ^ RDV8) * RDR)
(similarly for transmit)
where clock1 = clock_rate / (CLK4M? 4:3)
*/
UINT16 m_RDR; // Receive data rate
bool m_RDV8; // Receive data rate divider
UINT16 m_XDR; // Transmit data rate
bool m_XDV8; // Transmit data rate divider
/* Status flags */
bool m_INT; // mirrors /INT output line, inverted
bool m_DSCH; // Data set status change
bool m_CTSin; // Inverted /CTS input (i.e. CTS)
bool m_DSRin; // Inverted /DSR input (i.e. DSR)
bool m_RTSout; // Current inverted /RTS line state (i.e. RTS)
bool m_TIMELP; // Timer elapsed
bool m_TIMERR; // Timer error
bool m_XSRE; // Transmit shift register empty
bool m_XBRE; // Transmit buffer register empty
bool m_RBRL; // Receive buffer register loaded
bool m_RIN; // State of the RIN pin
bool m_RSBD; // Receive start bit detect
bool m_RFBD; // Receive full bit detect
bool m_RFER; // Receive framing error
bool m_ROVER; // Receiver overflow
bool m_RPER; // Receive parity error
UINT8 m_RCL; // Character length
bool m_ODDP;
bool m_PENB;
UINT8 m_STOPB;
bool m_CLK4M; // /PHI input divide select
UINT8 m_TMR; /* interval timer */
/* clock registers */
emu_timer *m_dectimer; /* MESS timer, used to emulate the decrementer register */
emu_timer *m_recvtimer;
emu_timer *m_sendtimer;
// This value is the ratio of data input versus the poll rate. The
// data source should deliver data bytes at every 1/baudpoll call.
// This is to ensure that data is delivered at a rate that is expected
// from the emulated program.
double m_baudpoll;
// Caches the last configuration setting (used with the ctrl_callback)
int m_last_config_value;
};
/***************************************************************************
DEVICE CONFIGURATION MACROS
***************************************************************************/
#define MCFG_TMS9902_ADD(_tag, _intrf, _clock) \
MCFG_DEVICE_ADD(_tag, TMS9902, _clock) \
MCFG_DEVICE_CONFIG(_intrf)
#endif /* __TMS9902_H__ */