// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
Sega 16-bit common hardware
***************************************************************************/
#include "emu.h"
#include "segaic16.h"
#include <algorithm>
//**************************************************************************
// DEBUGGING
//**************************************************************************
#define LOG_MULTIPLY (0)
#define LOG_DIVIDE (0)
#define LOG_COMPARE (0)
//**************************************************************************
// CONSTANTS
//**************************************************************************
// device type definition
DEFINE_DEVICE_TYPE(SEGA_315_5248_MULTIPLIER, sega_315_5248_multiplier_device, "sega_315_5248", "Sega 315-5248 Multiplier")
DEFINE_DEVICE_TYPE(SEGA_315_5249_DIVIDER, sega_315_5249_divider_device, "sega_315_5249", "Sega 315-5249 Divider")
DEFINE_DEVICE_TYPE(SEGA_315_5250_COMPARE_TIMER, sega_315_5250_compare_timer_device, "sega_315_5250", "Sega 315-5250 Compare/Timer")
//**************************************************************************
// 315-5248 MULTIPLIER
//**************************************************************************
//-------------------------------------------------
// sega_315_5248_multiplier_device - constructor
//-------------------------------------------------
sega_315_5248_multiplier_device::sega_315_5248_multiplier_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, SEGA_315_5248_MULTIPLIER, tag, owner, clock)
{
}
//-------------------------------------------------
// read - read the registers
//-------------------------------------------------
u16 sega_315_5248_multiplier_device::read(offs_t offset)
{
switch (offset & 3)
{
// if bit 1 is 0, just return register values
case 0: return m_regs[0];
case 1: return m_regs[1];
// if bit 1 is 1, return ther results
case 2: return (s16(m_regs[0]) * s16(m_regs[1])) >> 16;
case 3: return (s16(m_regs[0]) * s16(m_regs[1])) & 0xffff;
}
// should never get here
return 0xffff;
}
//-------------------------------------------------
// write - write to the registers
//-------------------------------------------------
void sega_315_5248_multiplier_device::write(offs_t offset, u16 data, u16 mem_mask)
{
// only low bit matters
COMBINE_DATA(&m_regs[offset & 1]);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void sega_315_5248_multiplier_device::device_start()
{
save_item(NAME(m_regs));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void sega_315_5248_multiplier_device::device_reset()
{
std::fill(std::begin(m_regs), std::end(m_regs), 0);
}
//**************************************************************************
// 315-5249 DIVIDER
//**************************************************************************
//-------------------------------------------------
// sega_315_5249_divider_device - constructor
//-------------------------------------------------
sega_315_5249_divider_device::sega_315_5249_divider_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, SEGA_315_5249_DIVIDER, tag, owner, clock)
{
}
//-------------------------------------------------
// read - read the registers
//-------------------------------------------------
u16 sega_315_5249_divider_device::read(offs_t offset)
{
// 8 effective read registers
switch (offset & 7)
{
case 0: return m_regs[0]; // dividend high
case 1: return m_regs[1]; // dividend low
case 2: return m_regs[2]; // divisor
case 4: return m_regs[4]; // quotient (mode 0) or quotient high (mode 1)
case 5: return m_regs[5]; // remainder (mode 0) or quotient low (mode 1)
case 6: return m_regs[6]; // flags
}
return 0xffff;
}
//-------------------------------------------------
// write - write to the registers
//-------------------------------------------------
void sega_315_5249_divider_device::write(offs_t offset, u16 data, u16 mem_mask)
{
if (LOG_DIVIDE) logerror("divide_w(%X) = %04X\n", offset, data);
// only 4 effective write registers
switch (offset & 3)
{
case 0: COMBINE_DATA(&m_regs[0]); break; // dividend high
case 1: COMBINE_DATA(&m_regs[1]); break; // dividend low
case 2: COMBINE_DATA(&m_regs[2]); break; // divisor/trigger
case 3: break;
}
// if A4 line is high, divide, using A3 as the mode
if (offset & 8)
execute(offset & 4);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void sega_315_5249_divider_device::device_start()
{
save_item(NAME(m_regs));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void sega_315_5249_divider_device::device_reset()
{
std::fill(std::begin(m_regs), std::end(m_regs), 0);
}
//-------------------------------------------------
// execute - execute the divide
//-------------------------------------------------
void sega_315_5249_divider_device::execute(int mode)
{
// clear the flags by default
m_regs[6] = 0;
// mode 0: signed divide, return 16-bit quotient/remainder
if (mode == 0)
{
// perform signed divide
const s32 dividend = s32((m_regs[0] << 16) | m_regs[1]);
const s32 divisor = s16(m_regs[2]);
s32 quotient;
// check for divide by 0, signal if we did
if (divisor == 0)
{
quotient = dividend;//((s32)(dividend ^ divisor) < 0) ? 0x8000 : 0x7fff;
m_regs[6] |= 0x4000;
}
else
quotient = dividend / divisor;
// clamp to 16-bit signed, signal overflow if we did
if (quotient < -32768)
{
quotient = -32768;
m_regs[6] |= 0x8000;
}
else if (quotient > 32767)
{
quotient = 32767;
m_regs[6] |= 0x8000;
}
// store quotient and remainder
m_regs[4] = s16(quotient);
m_regs[5] = s16(dividend - quotient * divisor);
}
// mode 1: unsigned divide, 32-bit quotient only
else
{
// perform unsigned divide
const u32 dividend = u32((m_regs[0] << 16) | m_regs[1]);
const u32 divisor = u16(m_regs[2]);
u32 quotient;
// check for divide by 0, signal if we did
if (divisor == 0)
{
quotient = dividend;//0x7fffffff;
m_regs[6] |= 0x4000;
}
else
quotient = dividend / divisor;
// store 32-bit quotient
m_regs[4] = quotient >> 16;
m_regs[5] = quotient & 0xffff;
}
}
//**************************************************************************
// 315-5250 COMPARE/TIMER
//**************************************************************************
//-------------------------------------------------
// sega_315_5250_compare_timer_device -
// constructor
//-------------------------------------------------
sega_315_5250_compare_timer_device::sega_315_5250_compare_timer_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, SEGA_315_5250_COMPARE_TIMER, tag, owner, clock)
, m_68kint_callback(*this)
, m_zint_callback(*this)
, m_exck(false)
{
}
//-------------------------------------------------
// exck_w - clock the timer
//-------------------------------------------------
WRITE_LINE_MEMBER(sega_315_5250_compare_timer_device::exck_w)
{
if (m_exck == bool(state))
return;
// update on rising edge
m_exck = bool(state);
if (!m_exck)
return;
// if we're enabled, clock the upcounter
const int old_counter = m_counter;
if (m_regs[10] & 1)
m_counter++;
// regardless of the enable, a value of 0xfff will generate the IRQ
if (old_counter == 0xfff)
{
if (!m_68kint_callback.isnull())
m_68kint_callback(ASSERT_LINE);
m_counter = m_regs[8] & 0xfff;
}
}
//-------------------------------------------------
// interrupt_ack - acknowledge timer interrupt
//-------------------------------------------------
void sega_315_5250_compare_timer_device::interrupt_ack()
{
if (!m_68kint_callback.isnull())
m_68kint_callback(CLEAR_LINE);
}
//-------------------------------------------------
// read - read the registers
//-------------------------------------------------
u16 sega_315_5250_compare_timer_device::read(offs_t offset)
{
if (LOG_COMPARE) logerror("compare_r(%X) = %04X\n", offset, m_regs[offset]);
switch (offset & 15)
{
case 0x0: return m_regs[0];
case 0x1: return m_regs[1];
case 0x2: return m_regs[2];
case 0x3: return m_regs[3];
case 0x4: return m_regs[4];
case 0x5: return m_regs[1];
case 0x6: return m_regs[2];
case 0x7: return m_regs[7];
case 0x9:
case 0xd: if (!machine().side_effects_disabled()) interrupt_ack(); break;
}
return 0xffff;
}
//-------------------------------------------------
// write - write to the registers
//-------------------------------------------------
void sega_315_5250_compare_timer_device::write(offs_t offset, u16 data, u16 mem_mask)
{
if (LOG_COMPARE) logerror("compare_w(%X) = %04X\n", offset, data);
switch (offset & 15)
{
case 0x0: COMBINE_DATA(&m_regs[0]); execute(); break;
case 0x1: COMBINE_DATA(&m_regs[1]); execute(); break;
case 0x2: COMBINE_DATA(&m_regs[2]); execute(true); break;
case 0x4: m_regs[4] = 0; m_bit = 0; break;
case 0x6: COMBINE_DATA(&m_regs[2]); execute(); break;
case 0x8:
case 0xc: COMBINE_DATA(&m_regs[8]); break;
case 0x9:
case 0xd: interrupt_ack(); break;
case 0xa:
case 0xe: COMBINE_DATA(&m_regs[10]); break;
case 0xb:
case 0xf:
machine().scheduler().synchronize(timer_expired_delegate(FUNC(sega_315_5250_compare_timer_device::write_to_sound), this), data & 0xff);
break;
}
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void sega_315_5250_compare_timer_device::device_start()
{
// bind our handlers
m_68kint_callback.resolve();
m_zint_callback.resolve();
// save states
save_item(NAME(m_regs));
save_item(NAME(m_counter));
save_item(NAME(m_bit));
save_item(NAME(m_exck));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void sega_315_5250_compare_timer_device::device_reset()
{
std::fill(std::begin(m_regs), std::end(m_regs), 0);
m_counter = 0;
m_bit = 0;
interrupt_ack();
if (!m_zint_callback.isnull())
m_zint_callback(CLEAR_LINE);
}
//-------------------------------------------------
// write_to_sound - write data for the sound CPU
//-------------------------------------------------
TIMER_CALLBACK_MEMBER(sega_315_5250_compare_timer_device::write_to_sound)
{
m_regs[11] = param;
if (!m_zint_callback.isnull())
m_zint_callback(ASSERT_LINE);
}
//-------------------------------------------------
// zread - read data from sound CPU bus
//-------------------------------------------------
u8 sega_315_5250_compare_timer_device::zread()
{
if (!m_zint_callback.isnull() && !machine().side_effects_disabled())
m_zint_callback(CLEAR_LINE);
return m_regs[11];
}
//-------------------------------------------------
// execute - execute the compare
//-------------------------------------------------
void sega_315_5250_compare_timer_device::execute(bool update_history)
{
const s16 bound1 = s16(m_regs[0]);
const s16 bound2 = s16(m_regs[1]);
const s16 value = s16(m_regs[2]);
const s16 min = (bound1 < bound2) ? bound1 : bound2;
const s16 max = (bound1 > bound2) ? bound1 : bound2;
if (value < min)
{
m_regs[7] = min;
m_regs[3] = 0x8000;
}
else if (value > max)
{
m_regs[7] = max;
m_regs[3] = 0x4000;
}
else
{
m_regs[7] = value;
m_regs[3] = 0x0000;
}
if (update_history)
m_regs[4] |= (m_regs[3] == 0) << m_bit++;
}