cpu/z180: Added CSIO emulation. [Vas Crabb, Sandro Ronco]

This commit is contained in:
Vas Crabb 2023-03-11 05:01:47 +11:00
parent cdd5569296
commit 2eba8b2112
6 changed files with 403 additions and 50 deletions

View File

@ -2890,11 +2890,13 @@ if CPUS["Z180"] then
MAME_DIR .. "src/devices/cpu/z180/z180ed.hxx",
MAME_DIR .. "src/devices/cpu/z180/z180fd.hxx",
MAME_DIR .. "src/devices/cpu/z180/z180op.hxx",
MAME_DIR .. "src/devices/cpu/z180/z180xy.hxx",
MAME_DIR .. "src/devices/cpu/z180/z180ops.h",
MAME_DIR .. "src/devices/cpu/z180/z180tbl.h",
MAME_DIR .. "src/devices/cpu/z180/z180xy.hxx",
MAME_DIR .. "src/devices/cpu/z180/z180asci.cpp",
MAME_DIR .. "src/devices/cpu/z180/z180asci.h",
MAME_DIR .. "src/devices/cpu/z180/z180csio.cpp",
MAME_DIR .. "src/devices/cpu/z180/z180csio.h",
}
end

View File

@ -15,6 +15,32 @@
work. Currently, only timers are implemented. Ideally, the
burn_cycles routine would go away and halt processing be
implemented in cpu_execute.
- Documentation for RXS/CTS1 pin is contradictory.
From page 12:
"During RESET, this pin is initialized as RXS pin. If CTS1E
bit in ASCI status register ch1 (STAT1) is set to 1, CTS1
function is selected. If CTS1E bit is set to 0, RXS function
is selected."
However, there is no CTS1E bit in the ASCI status register.
From pages 43-44:
"CTS/PS: Clear to Send/Prescale (bit 5)—If bit 5 of the System
Configuration Register is 0, the CTS0/RxS pin features the
CTS0 function, and the state of the pin can be read in bit 5
of CNTLB0 in a real-time, positive-logic fashion (HIGH = 1 ,
LOW = 0). If bit 5 in the System Configuration Register is 0
to auto-enable CTS0, and the pin is negated (High), the TDRE
bit is inhibited (forced to 0). Bit 5 of CNTLB1 reads back as
0."
This contradicts everything else in the documentation as it
implies RXS shares a pin with CTS0 (rather than CTS1)
For now, the input is always sent to both RXS and CTS1.
*****************************************************************************/
/*****************************************************************************
@ -94,12 +120,12 @@ z180_device::z180_device(const machine_config &mconfig, device_type type, const
, m_io_config("io", ENDIANNESS_LITTLE, 8, 16, 0)
, m_decrypted_opcodes_config("opcodes", ENDIANNESS_LITTLE, 8, 20, 0, 16, 12, internal_map)
, m_asci(*this, "asci_%u", 0U)
, m_csio(*this, "csio")
, m_extended_io(extended_io)
, m_tend0_cb(*this)
, m_tend1_cb(*this)
{
// some arbitrary initial values
m_csio_trdr = 0;
m_tmdr[0].w = m_tmdr[1].w = 0;
m_rldr[0].w = m_rldr[1].w = 0xffff;
m_dma_sar0.d = 0;
@ -236,15 +262,6 @@ bool z180_device::get_tend1()
#define _LY m_IY.b.l
/* 0a CSI/O control/status register (EF is read-only) */
#define Z180_CNTR_EF 0x80
#define Z180_CNTR_EIE 0x40
#define Z180_CNTR_RE 0x20
#define Z180_CNTR_TE 0x10
#define Z180_CNTR_SS 0x07
#define Z180_CNTR_MASK 0xf7
/* 10 TIMER control register (TIF1 and TIF0 are read-only) */
#define Z180_TCR_TIF1 0x80
#define Z180_TCR_TIF0 0x40
@ -450,13 +467,11 @@ uint8_t z180_device::z180_internal_port_read(uint8_t port)
break;
case 0x0a:
data = m_csio_cntr | ~Z180_CNTR_MASK;
LOG("Z180 CNTR rd $%02x ($%02x)\n", data, m_csio_cntr);
data = m_csio->cntr_r();
break;
case 0x0b:
data = m_csio_trdr;
LOG("Z180 TRDR rd $%02x\n", data);
data = m_csio->trdr_r();
break;
case 0x0c:
@ -788,14 +803,11 @@ void z180_device::z180_internal_port_write(uint8_t port, uint8_t data)
break;
case 0x0a:
// Inhibit setting up TE & RE flags due to the lack of CSIO implementation
LOG("Z180 CNTR wr $%02x ($%02x)\n", data, data & ~(Z180_CNTR_EF | Z180_CNTR_RE | Z180_CNTR_TE));
m_csio_cntr = (m_csio_cntr & (Z180_CNTR_EF | Z180_CNTR_RE | Z180_CNTR_TE)) | (data & ~(Z180_CNTR_EF | Z180_CNTR_RE | Z180_CNTR_TE));
m_csio->cntr_w(data);
break;
case 0x0b:
LOG("Z180 TRDR wr $%02x\n", data);
m_csio_trdr = data;
m_csio->trdr_w(data);
break;
case 0x0c:
@ -1566,8 +1578,7 @@ void z180_device::device_start()
m_asci[0]->state_add(*this);
m_asci[1]->state_add(*this);
state_add(Z180_CNTR, "CNTR", m_csio_cntr).mask(Z180_CNTR_MASK);
state_add(Z180_TRDR, "TRDR", m_csio_trdr);
m_csio->state_add(*this);
state_add(Z180_TMDR0, "TMDR0", m_tmdr_value[0]);
state_add(Z180_RLDR0, "RLDR0", m_rldr[0].w);
@ -1627,8 +1638,6 @@ void z180_device::device_start()
save_item(NAME(m_tmdrh));
save_item(NAME(m_tmdr_latch));
save_item(NAME(m_csio_cntr));
save_item(NAME(m_csio_trdr));
save_item(NAME(m_tmdr[0].w));
save_item(NAME(m_tmdr[1].w));
save_item(NAME(m_rldr[0].w));
@ -1727,7 +1736,6 @@ void z180_device::device_reset()
m_frc_prescale = 0;
/* reset io registers */
m_csio_cntr = 0x07;
m_tcr = 0x00;
m_dma_iar1.b.h2 = 0x00;
m_dstat = Z180_DSTAT_DWE1 | Z180_DSTAT_DWE0;
@ -1750,6 +1758,8 @@ void z180_device::device_add_mconfig(machine_config &config)
Z180ASCI_CHANNEL_0(config, m_asci[0], DERIVED_CLOCK(1,2));
Z180ASCI_CHANNEL_1(config, m_asci[1], DERIVED_CLOCK(1,2));
Z180CSIO(config, m_csio, DERIVED_CLOCK(1,2));
}
void z8s180_device::device_add_mconfig(machine_config &config)
@ -1757,6 +1767,8 @@ void z8s180_device::device_add_mconfig(machine_config &config)
Z180ASCI_EXT_CHANNEL_0(config, m_asci[0], DERIVED_CLOCK(1,2));
Z180ASCI_EXT_CHANNEL_1(config, m_asci[1], DERIVED_CLOCK(1,2));
Z180CSIO(config, m_csio, DERIVED_CLOCK(1,2));
}
void z8s180_device::device_reset()
@ -1770,8 +1782,10 @@ void z8s180_device::device_reset()
void z8s180_device::device_clock_changed()
{
m_asci[0]->set_clock((m_cmr & 0x80) ? DERIVED_CLOCK(2,1) : (m_ccr & 0x80) ? DERIVED_CLOCK(1,1) : DERIVED_CLOCK(1,2));
m_asci[1]->set_clock((m_cmr & 0x80) ? DERIVED_CLOCK(2,1) : (m_ccr & 0x80) ? DERIVED_CLOCK(1,1) : DERIVED_CLOCK(1,2));
auto const rate = (m_cmr & 0x80) ? DERIVED_CLOCK(2,1) : (m_ccr & 0x80) ? DERIVED_CLOCK(1,1) : DERIVED_CLOCK(1,2);
m_asci[0]->set_clock(rate);
m_asci[1]->set_clock(rate);
m_csio->set_clock(rate);
}
/* Handle PRT timers, decreasing them after 20 clocks and returning the new icount base that needs to be used for the next check */
@ -1818,9 +1832,6 @@ void z180_device::clock_timers()
int z180_device::check_interrupts()
{
int i;
int cycles = 0;
/* check for IRQs before each instruction */
if (m_IFF1 && !m_after_EI)
{
@ -1833,19 +1844,26 @@ int z180_device::check_interrupts()
if (m_irq_state[2] != CLEAR_LINE && (m_itc & Z180_ITC_ITE2) == Z180_ITC_ITE2)
m_int_pending[Z180_INT_IRQ2] = 1;
m_int_pending[Z180_INT_CSIO] = m_csio->check_interrupt();
m_int_pending[Z180_INT_ASCI0] = m_asci[0]->check_interrupt();
m_int_pending[Z180_INT_ASCI1] = m_asci[1]->check_interrupt();
}
for (i = 0; i <= Z180_INT_MAX; i++)
int cycles = 0;
for (int i = 0; i <= Z180_INT_MAX; i++)
{
if (m_int_pending[i])
{
cycles += take_interrupt(i);
m_int_pending[i] = 0;
if (i == Z180_INT_ASCI0) m_asci[0]->clear_interrupt();
if (i == Z180_INT_ASCI1) m_asci[1]->clear_interrupt();
switch (i)
{
case Z180_INT_ASCI0: m_asci[0]->clear_interrupt(); break;
case Z180_INT_ASCI1: m_asci[1]->clear_interrupt(); break;
}
break;
}
}
return cycles;
}

View File

@ -5,8 +5,11 @@
#pragma once
#include "machine/z80daisy.h"
#include "z180asci.h"
#include "z180csio.h"
#include "machine/z80daisy.h"
enum
{
@ -90,16 +93,16 @@ enum
Z180_TABLE_ed,
Z180_TABLE_xy,
Z180_TABLE_xycb,
Z180_TABLE_ex /* cycles counts for taken jr/jp/call and interrupt latency (rst opcodes) */
Z180_TABLE_ex // cycles counts for taken jr/jp/call and interrupt latency (rst opcodes) */
};
// input lines
enum {
Z180_INPUT_LINE_IRQ0, /* Execute IRQ1 */
Z180_INPUT_LINE_IRQ1, /* Execute IRQ1 */
Z180_INPUT_LINE_IRQ2, /* Execute IRQ2 */
Z180_INPUT_LINE_DREQ0, /* Start DMA0 */
Z180_INPUT_LINE_DREQ1 /* Start DMA1 */
Z180_INPUT_LINE_IRQ0, // Execute IRQ1
Z180_INPUT_LINE_IRQ1, // Execute IRQ1
Z180_INPUT_LINE_IRQ2, // Execute IRQ2
Z180_INPUT_LINE_DREQ0, // Start DMA0
Z180_INPUT_LINE_DREQ1 // Start DMA1
};
class z180_device : public cpu_device, public z80_daisy_chain_interface
@ -112,17 +115,20 @@ public:
auto rts0_wr_callback() { return subdevice<z180asci_channel_base>("asci_0")->rts_handler(); }
auto cka0_wr_callback() { return subdevice<z180asci_channel_base>("asci_0")->cka_handler(); }
auto cka1_wr_callback() { return subdevice<z180asci_channel_base>("asci_1")->cka_handler(); }
auto cks_wr_callback() { return subdevice<z180csio_device>("csio")->cks_handler(); }
auto txs_wr_callback() { return subdevice<z180csio_device>("csio")->txs_handler(); }
bool get_tend0();
bool get_tend1();
DECLARE_WRITE_LINE_MEMBER( rxa0_w ) { m_asci[0]->rxa_wr(state); }
DECLARE_WRITE_LINE_MEMBER( rxa1_w ) { m_asci[1]->rxa_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cts0_w ) { m_asci[0]->cts_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cts1_w ) { m_asci[1]->cts_wr(state); }
DECLARE_WRITE_LINE_MEMBER( dcd0_w ) { m_asci[0]->dcd_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cka0_w ) { m_asci[0]->cka_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cka1_w ) { m_asci[1]->cka_wr(state); }
DECLARE_WRITE_LINE_MEMBER( rxa0_w ) { m_asci[0]->rxa_wr(state); }
DECLARE_WRITE_LINE_MEMBER( rxa1_w ) { m_asci[1]->rxa_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cts0_w ) { m_asci[0]->cts_wr(state); }
DECLARE_WRITE_LINE_MEMBER( rxs_cts1_w ) { m_asci[1]->cts_wr(state); m_csio->rxs_wr(state); }
DECLARE_WRITE_LINE_MEMBER( dcd0_w ) { m_asci[0]->dcd_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cka0_w ) { m_asci[0]->cka_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cka1_w ) { m_asci[1]->cka_wr(state); }
DECLARE_WRITE_LINE_MEMBER( cks_w ) { m_csio->cks_wr(state); }
protected:
// construction/destruction
@ -165,6 +171,7 @@ protected:
address_space_config m_io_config;
address_space_config m_decrypted_opcodes_config;
required_device_array<z180asci_channel_base, 2> m_asci;
required_device<z180csio_device> m_csio;
void set_address_width(int bits);
@ -184,8 +191,6 @@ private:
uint8_t m_tmdr_latch; // flag latched TMDR0H, TMDR1H values
uint8_t m_read_tcr_tmdr[2]; // flag to indicate that TCR or TMDR was read
uint32_t m_iol; // I/O line status bits
uint8_t m_csio_cntr; // CSI/O control/status register
uint8_t m_csio_trdr; // CSI/O transmit/receive register
PAIR16 m_tmdr[2]; // PRT data register ch 0-1
PAIR16 m_rldr[2]; // PRT reload register ch 0-1
uint8_t m_tcr; // PRT control register

View File

@ -220,7 +220,7 @@ uint8_t z180asci_channel_base::cntla_r()
uint8_t z180asci_channel_base::cntlb_r()
{
uint8_t data = (m_asci_cntlb & 0x0d) | (m_cts << 5);
uint8_t data = (m_asci_cntlb & 0xdf) | (m_cts << 5);
LOG("Z180 CNTLB%d rd $%02x\n", m_id, data);
return data;
}

View File

@ -0,0 +1,258 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb, Sandro Ronco, Miodrag Milanovic
/*********************************************************************
z180csio.cpp
*********************************************************************/
/*********************************************************************
TODO:
- Handle entering IOSTOP mode.
- Handle mid-transfer clock frequency changes.
*********************************************************************/
#include "emu.h"
#include "z180.h"
//#define VERBOSE 1
#include "logmacro.h"
// 0x0a CSI/O control/status register (EF is read-only)
static constexpr u8 Z180_CNTR_EF = 0x80;
static constexpr u8 Z180_CNTR_EIE = 0x40;
static constexpr u8 Z180_CNTR_RE = 0x20;
static constexpr u8 Z180_CNTR_TE = 0x10;
static constexpr u8 Z180_CNTR_SS = 0x07;
static constexpr u8 Z180_CNTR_MASK = 0xf7;
//**************************************************************************
// z180csio_device
//**************************************************************************
z180csio_device::z180csio_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, Z180CSIO, tag, owner, clock)
, m_cks_cb(*this)
, m_txs_cb(*this)
, m_internal_clock(nullptr)
, m_cntr(0)
, m_trdr(0)
, m_shift_cnt(0)
, m_irq(0)
, m_cks_in(1)
, m_rxs_in(1)
, m_cks_out(1)
, m_txs_out(1)
{
}
void z180csio_device::device_resolve_objects()
{
// resolve callbacks
m_cks_cb.resolve_safe();
m_txs_cb.resolve_safe();
// set default input line state
m_cks_in = 1;
m_rxs_in = 1;
}
void z180csio_device::device_start()
{
// TRDR is not affected by reset - set it here to make behaviour deterministic
m_trdr = 0;
save_item(NAME(m_cntr));
save_item(NAME(m_trdr));
save_item(NAME(m_shift_cnt));
save_item(NAME(m_irq));
save_item(NAME(m_cks_in));
save_item(NAME(m_rxs_in));
save_item(NAME(m_cks_out));
save_item(NAME(m_txs_out));
m_internal_clock = timer_alloc(timer_expired_delegate(FUNC(z180csio_device::internal_clock), this));
}
void z180csio_device::device_reset()
{
m_cntr = 0x07;
m_shift_cnt = 0;
m_irq = 0;
m_cks_out = 1;
m_txs_out = 1; // TODO: is this affected by reset?
m_internal_clock->adjust(attotime::never);
m_cks_cb(m_cks_out);
m_txs_cb(m_txs_out);
}
void z180csio_device::state_add(device_state_interface &parent)
{
parent.state_add(Z180_CNTR, "CNTR", m_cntr).mask(Z180_CNTR_MASK);
parent.state_add(Z180_TRDR, "TRDR", m_trdr);
}
u8 z180csio_device::cntr_r()
{
LOG("Z180 CNTR rd $%02x\n", m_cntr);
return m_cntr & Z180_CNTR_MASK;
}
u8 z180csio_device::trdr_r()
{
// TODO: from manual page 47: "Program access of TRDR only occurs if EF = 1."
// Should access be suppressed if EF is clear?
LOG("Z180 TRDR rd $%02x\n", m_trdr);
if (!machine().side_effects_disabled())
m_cntr &= ~Z180_CNTR_EF;
return m_trdr;
}
void z180csio_device::cntr_w(u8 data)
{
// TODO:
// From manual page 47: "TE and RE are never both set to 1 at the same time."
// If one attempts to write 1 to both at the same time, which takes precedence?
LOG("Z180 CNTR wr $%02x\n", data);
if (data & (Z180_CNTR_RE | Z180_CNTR_TE))
{
// if receive or transmit will be enabled, start clock if necessary
if (!(m_cntr & (Z180_CNTR_RE | Z180_CNTR_TE)))
{
if ((data & Z180_CNTR_SS) != 7)
m_internal_clock->adjust(attotime::from_hz(clock()));
else
m_internal_clock->adjust(attotime::never);
}
}
else
{
m_shift_cnt = 0;
if (!m_cks_out)
{
// this probably takes at least one clock to take actually happen
m_cks_out = 1;
if ((data & Z180_CNTR_SS) != 7)
m_cks_cb(1);
}
m_internal_clock->adjust(attotime::never);
}
// TODO: if switching internal/external clock, update CKS output and trigger a clock edge if necessary
m_cntr = (m_cntr & Z180_CNTR_EF) | (data & ~Z180_CNTR_EF & Z180_CNTR_MASK); // EF is read-only
}
void z180csio_device::trdr_w(u8 data)
{
// TODO: from manual page 47: "Program access of TRDR only occurs if EF = 1."
// Should access be suppressed if EF is clear?
LOG("Z180 TRDR wr $%02x\n", data);
m_cntr &= ~Z180_CNTR_EF;
m_trdr = data;
}
TIMER_CALLBACK_MEMBER(z180csio_device::internal_clock)
{
if ((m_cntr & Z180_CNTR_SS) != 7)
{
m_cks_out ^= 1;
m_cks_cb(m_cks_out);
clock_edge(m_cks_out);
if (m_cntr & (Z180_CNTR_RE | Z180_CNTR_TE))
{
int div;
switch (m_cntr & Z180_CNTR_SS)
{
default: // just to pacify compilers
case 0: div = 20; break;
case 1: div = 40; break;
case 2: div = 80; break;
case 3: div = 160; break;
case 4: div = 320; break;
case 5: div = 640; break;
case 6: div = 1280; break;
}
m_internal_clock->adjust(attotime::from_hz(clock() / div));
}
}
}
WRITE_LINE_MEMBER(z180csio_device::cks_wr)
{
state = state ? 1 : 0;
if (m_cks_in != state)
{
m_cks_in = state;
if ((m_cntr & Z180_CNTR_SS) == 0x07)
clock_edge(m_cks_in);
}
}
WRITE_LINE_MEMBER(z180csio_device::rxs_wr)
{
m_rxs_in = state ? 1 : 0;
}
void z180csio_device::clock_edge(u8 cks)
{
if (!cks)
{
// TXS updated on falling edge
if (m_cntr & Z180_CNTR_TE)
{
u8 const txs = BIT(m_trdr, m_shift_cnt);
if (m_txs_out != txs)
{
m_txs_out = txs;
m_txs_cb(txs);
}
}
}
else
{
// Sample RXS on rising edge (ignore minimum setup/sampling time)
if (m_cntr & Z180_CNTR_RE)
m_trdr = (m_trdr & ~(u8(1) << m_shift_cnt)) | (m_rxs_in << m_shift_cnt);
// EF/RE/TE updated on rising edge
if (m_cntr & (Z180_CNTR_RE | Z180_CNTR_TE))
{
m_shift_cnt = (m_shift_cnt + 1) & 7;
if (!m_shift_cnt)
m_cntr = Z180_CNTR_EF | (m_cntr & ~(Z180_CNTR_RE | Z180_CNTR_TE));
}
}
}
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(Z180CSIO, z180csio_device, "z180csio", "Z180 CSIO")

View File

@ -0,0 +1,70 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb, Sandro Ronco, Miodrag Milanovic
/*********************************************************************
z180csio.h
*********************************************************************/
#ifndef MAME_CPU_Z180_Z180CSIO_H
#define MAME_CPU_Z180_Z180CSIO_H
#pragma once
//**************************************************************************
// z180csio_device
//**************************************************************************
class z180csio_device : public device_t
{
public:
z180csio_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
auto cks_handler() { return m_cks_cb.bind(); }
auto txs_handler() { return m_txs_cb.bind(); }
DECLARE_WRITE_LINE_MEMBER(cks_wr);
DECLARE_WRITE_LINE_MEMBER(rxs_wr);
u8 cntr_r();
u8 trdr_r();
void cntr_w(u8 data);
void trdr_w(u8 data);
void state_add(device_state_interface &parent);
int check_interrupt() { return BIT(m_cntr, 7) && BIT(m_cntr, 6); }
protected:
// device_t implementation
virtual void device_resolve_objects() override;
virtual void device_start() override;
virtual void device_reset() override;
void clock_edge(u8 cks);
TIMER_CALLBACK_MEMBER(internal_clock);
devcb_write_line m_cks_cb;
devcb_write_line m_txs_cb;
emu_timer *m_internal_clock;
u8 m_cntr; // CSI/O Control/Status Register
u8 m_trdr; // CSI/O Transmit/Receive Data Register
u8 m_shift_cnt;
u8 m_irq;
u8 m_cks_in;
u8 m_rxs_in;
u8 m_cks_out;
u8 m_txs_out;
};
//**************************************************************************
// DEVICE TYPE DEFINITIONS
//**************************************************************************
DECLARE_DEVICE_TYPE(Z180CSIO, z180csio_device)
#endif // MAME_CPU_Z180_Z180CSIO_H