// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** RIOT 6532 emulation The timer seems to follow these rules: - When the timer flag changes from 0 to 1 the timer continues to count down at a 1 cycle rate. - When the timer is being read or written the timer flag is reset. - When the timer flag is set and the timer contents are 0, the counting stops. ***************************************************************************/ #include "emu.h" #include "6532riot.h" //************************************************************************** // CONSTANTS //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(RIOT6532, riot6532_device, "riot6532", "6532 RIOT") enum { TIMER_IDLE, TIMER_COUNTING, TIMER_FINISHING }; #define TIMER_FLAG 0x80 #define PA7_FLAG 0x40 /*************************************************************************** INTERNAL FUNCTIONS ***************************************************************************/ /*------------------------------------------------- update_irqstate - update the IRQ state based on interrupt enables -------------------------------------------------*/ void riot6532_device::update_irqstate() { int irq = (m_irqstate & m_irqenable) ? ASSERT_LINE : CLEAR_LINE; if (m_irq != irq) { m_irq_cb(irq); m_irq = irq; } } /*------------------------------------------------- apply_ddr - combine inputs and outputs according to the DDR -------------------------------------------------*/ uint8_t riot6532_device::apply_ddr(const riot6532_port *port) { return (port->m_out & port->m_ddr) | (port->m_in & ~port->m_ddr); } /*------------------------------------------------- update_pa7_state - see if PA7 has changed and signal appropriately -------------------------------------------------*/ void riot6532_device::update_pa7_state() { uint8_t data = apply_ddr(&m_port[0]) & 0x80; /* if the state changed in the correct direction, set the PA7 flag and update IRQs */ if ((m_pa7prev ^ data) && (m_pa7dir ^ data) == 0) { m_irqstate |= PA7_FLAG; update_irqstate(); } m_pa7prev = data; } /*------------------------------------------------- get_timer - return the current timer value -------------------------------------------------*/ uint8_t riot6532_device::get_timer() { /* if idle, return 0 */ if (m_timerstate == TIMER_IDLE) { return 0; } /* if counting, return the number of ticks remaining */ else if (m_timerstate == TIMER_COUNTING) { return m_timer->remaining().as_ticks(clock()) >> m_timershift; } /* if finishing, return the number of ticks without the shift */ else { return m_timer->remaining().as_ticks(clock()); } } void riot6532_device::timer_end() { assert(m_timerstate != TIMER_IDLE); /* if we finished counting, switch to the finishing state */ if(m_timerstate == TIMER_COUNTING) { m_timerstate = TIMER_FINISHING; m_timer->adjust(attotime::from_ticks(256, clock())); /* signal timer IRQ as well */ m_irqstate |= TIMER_FLAG; update_irqstate(); } /* if we finished finishing, keep spinning */ else if (m_timerstate == TIMER_FINISHING) { m_timer->adjust(attotime::from_ticks(256, clock())); } } /*************************************************************************** I/O ACCESS ***************************************************************************/ /*------------------------------------------------- riot6532_w - master I/O write access -------------------------------------------------*/ WRITE8_MEMBER( riot6532_device::write ) { reg_w(offset, data); } void riot6532_device::reg_w(uint8_t offset, uint8_t data) { /* if A4 == 1 and A2 == 1, we are writing to the timer */ if ((offset & 0x14) == 0x14) { static const uint8_t timershift[4] = { 0, 3, 6, 10 }; attotime curtime = machine().time(); int64_t target; /* A0-A1 contain the timer divisor */ m_timershift = timershift[offset & 3]; /* A3 contains the timer IRQ enable */ if (offset & 8) m_irqenable |= TIMER_FLAG; else m_irqenable &= ~TIMER_FLAG; /* writes here clear the timer flag */ if (m_timerstate != TIMER_FINISHING || get_timer() != 0xff) { m_irqstate &= ~TIMER_FLAG; } update_irqstate(); /* update the timer */ m_timerstate = TIMER_COUNTING; target = curtime.as_ticks(clock()) + 1 + (data << m_timershift); m_timer->adjust(attotime::from_ticks(target, clock()) - curtime); } /* if A4 == 0 and A2 == 1, we are writing to the edge detect control */ else if ((offset & 0x14) == 0x04) { /* A1 contains the A7 IRQ enable */ if (offset & 2) { m_irqenable |= PA7_FLAG; } else { m_irqenable &= ~PA7_FLAG; } /* A0 specifies the edge detect direction: 0=negative, 1=positive */ m_pa7dir = (offset & 1) << 7; } /* if A4 == anything and A2 == 0, we are writing to the I/O section */ else { /* A1 selects the port */ riot6532_port *port = &m_port[BIT(offset, 1)]; /* if A0 == 1, we are writing to the port's DDR */ if (offset & 1) { port->m_ddr = data; } /* if A0 == 0, we are writing to the port's output */ else { port->m_out = data; (*port->m_out_cb)((offs_t)0, data); } /* writes to port A need to update the PA7 state */ if (port == &m_port[0]) { update_pa7_state(); } } } /*------------------------------------------------- riot6532_r - master I/O read access -------------------------------------------------*/ READ8_MEMBER( riot6532_device::read ) { return reg_r(offset, machine().side_effects_disabled()); } uint8_t riot6532_device::reg_r(uint8_t offset, bool debugger_access) { uint8_t val; /* if A2 == 1 and A0 == 1, we are reading interrupt flags */ if ((offset & 0x05) == 0x05) { val = m_irqstate; if ( ! debugger_access ) { /* implicitly clears the PA7 flag */ m_irqstate &= ~PA7_FLAG; update_irqstate(); } } /* if A2 == 1 and A0 == 0, we are reading the timer */ else if ((offset & 0x05) == 0x04) { val = get_timer(); if ( ! debugger_access ) { /* A3 contains the timer IRQ enable */ if (offset & 8) { m_irqenable |= TIMER_FLAG; } else { m_irqenable &= ~TIMER_FLAG; } /* implicitly clears the timer flag */ if (m_timerstate != TIMER_FINISHING || val != 0xff) { m_irqstate &= ~TIMER_FLAG; } update_irqstate(); } } /* if A2 == 0 and A0 == anything, we are reading from ports */ else { /* A1 selects the port */ riot6532_port *port = &m_port[BIT(offset, 1)]; /* if A0 == 1, we are reading the port's DDR */ if (offset & 1) { val = port->m_ddr; } /* if A0 == 0, we are reading the port as an input */ else { /* call the input callback if it exists */ if (!(*port->m_in_cb).isnull()) { port->m_in = (*port->m_in_cb)(0); /* changes to port A need to update the PA7 state */ if (port == &m_port[0]) { if (!debugger_access) { update_pa7_state(); } } } /* apply the DDR to the result */ val = apply_ddr(port); } } return val; } /*------------------------------------------------- porta_in_set - set port A input value -------------------------------------------------*/ void riot6532_device::porta_in_set(uint8_t data, uint8_t mask) { m_port[0].m_in = (m_port[0].m_in & ~mask) | (data & mask); update_pa7_state(); } /*------------------------------------------------- portb_in_set - set port B input value -------------------------------------------------*/ void riot6532_device::portb_in_set(uint8_t data, uint8_t mask) { m_port[1].m_in = (m_port[1].m_in & ~mask) | (data & mask); } /*------------------------------------------------- porta_in_get - return port A input value -------------------------------------------------*/ uint8_t riot6532_device::porta_in_get() { return m_port[0].m_in; } /*------------------------------------------------- portb_in_get - return port B input value -------------------------------------------------*/ uint8_t riot6532_device::portb_in_get() { return m_port[1].m_in; } /*------------------------------------------------- porta_in_get - return port A output value -------------------------------------------------*/ uint8_t riot6532_device::porta_out_get() { return m_port[0].m_out; } /*------------------------------------------------- portb_in_get - return port B output value -------------------------------------------------*/ uint8_t riot6532_device::portb_out_get() { return m_port[1].m_out; } //------------------------------------------------- // paN_w - write Port A lines individually //------------------------------------------------- WRITE_LINE_MEMBER(riot6532_device::pa0_w) { porta_in_set(state ? 0x01 : 0x00, 0x01); } WRITE_LINE_MEMBER(riot6532_device::pa1_w) { porta_in_set(state ? 0x02 : 0x00, 0x02); } WRITE_LINE_MEMBER(riot6532_device::pa2_w) { porta_in_set(state ? 0x04 : 0x00, 0x04); } WRITE_LINE_MEMBER(riot6532_device::pa3_w) { porta_in_set(state ? 0x08 : 0x00, 0x08); } WRITE_LINE_MEMBER(riot6532_device::pa4_w) { porta_in_set(state ? 0x10 : 0x00, 0x10); } WRITE_LINE_MEMBER(riot6532_device::pa5_w) { porta_in_set(state ? 0x20 : 0x00, 0x20); } WRITE_LINE_MEMBER(riot6532_device::pa6_w) { porta_in_set(state ? 0x40 : 0x00, 0x40); } WRITE_LINE_MEMBER(riot6532_device::pa7_w) { porta_in_set(state ? 0x80 : 0x00, 0x80); } //------------------------------------------------- // pbN_w - write Port B lines individually //------------------------------------------------- WRITE_LINE_MEMBER(riot6532_device::pb0_w) { portb_in_set(state ? 0x01 : 0x00, 0x01); } WRITE_LINE_MEMBER(riot6532_device::pb1_w) { portb_in_set(state ? 0x02 : 0x00, 0x02); } WRITE_LINE_MEMBER(riot6532_device::pb2_w) { portb_in_set(state ? 0x04 : 0x00, 0x04); } WRITE_LINE_MEMBER(riot6532_device::pb3_w) { portb_in_set(state ? 0x08 : 0x00, 0x08); } WRITE_LINE_MEMBER(riot6532_device::pb4_w) { portb_in_set(state ? 0x10 : 0x00, 0x10); } WRITE_LINE_MEMBER(riot6532_device::pb5_w) { portb_in_set(state ? 0x20 : 0x00, 0x20); } WRITE_LINE_MEMBER(riot6532_device::pb6_w) { portb_in_set(state ? 0x40 : 0x00, 0x40); } WRITE_LINE_MEMBER(riot6532_device::pb7_w) { portb_in_set(state ? 0x80 : 0x00, 0x80); } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // riot6532_device - constructor //------------------------------------------------- riot6532_device::riot6532_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, RIOT6532, tag, owner, clock), m_in_pa_cb(*this), m_out_pa_cb(*this), m_in_pb_cb(*this), m_out_pb_cb(*this), m_irq_cb(*this), m_irqstate(0), m_irqenable(0), m_irq(CLEAR_LINE), m_pa7dir(0), m_pa7prev(0), m_timershift(0), m_timerstate(0), m_timer(nullptr) { memset(m_port, 0x00, sizeof(m_port)); } /*------------------------------------------------- device_start - device-specific startup -------------------------------------------------*/ void riot6532_device::device_start() { /* resolve callbacks */ m_in_pa_cb.resolve(); m_port[0].m_in_cb = &m_in_pa_cb; m_out_pa_cb.resolve_safe(); m_port[0].m_out_cb = &m_out_pa_cb; m_in_pb_cb.resolve(); m_port[1].m_in_cb = &m_in_pb_cb; m_out_pb_cb.resolve_safe(); m_port[1].m_out_cb = &m_out_pb_cb; m_irq_cb.resolve_safe(); /* allocate timers */ m_timer = timer_alloc(TIMER_END_CB); /* register for save states */ save_item(NAME(m_port[0].m_in)); save_item(NAME(m_port[0].m_out)); save_item(NAME(m_port[0].m_ddr)); save_item(NAME(m_port[1].m_in)); save_item(NAME(m_port[1].m_out)); save_item(NAME(m_port[1].m_ddr)); save_item(NAME(m_irqstate)); save_item(NAME(m_irqenable)); save_item(NAME(m_irq)); save_item(NAME(m_pa7dir)); save_item(NAME(m_pa7prev)); save_item(NAME(m_timershift)); save_item(NAME(m_timerstate)); } /*------------------------------------------------- device_reset - device-specific reset -------------------------------------------------*/ void riot6532_device::device_reset() { /* reset I/O states */ m_port[0].m_in = 0; m_port[0].m_out = 0; m_port[0].m_ddr = 0; m_port[1].m_in = 0; m_port[1].m_out = 0; m_port[1].m_ddr = 0; /* reset IRQ states */ m_irqenable = 0; m_irqstate = 0; update_irqstate(); /* reset PA7 states */ m_pa7dir = 0; m_pa7prev = 0; /* reset timer states */ m_timershift = 10; m_timerstate = TIMER_COUNTING; m_timer->adjust(attotime::from_ticks(256 << m_timershift, clock())); } void riot6532_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_END_CB: timer_end(); break; default: throw emu_fatalerror("Unknown id in riot6532_device::device_timer"); } }