// license:BSD-3-Clause
// copyright-holders:Steve Baines, Frank Palazzolo
/***************************************************************************
Atari Star Wars hardware
This file is Copyright Steve Baines.
Modified by Frank Palazzolo for sound support
***************************************************************************/
#include "emu.h"
#include "includes/starwars.h"
/* Constants for matrix processor operations */
#define NOP 0x00
#define LAC 0x01
#define READ_ACC 0x02
#define M_HALT 0x04
#define INC_BIC 0x08
#define CLEAR_ACC 0x10
#define LDC 0x20
#define LDB 0x40
#define LDA 0x80
/* Debugging flag */
#define MATHDEBUG 0
#define MASTER_CLOCK (12096000)
TIMER_CALLBACK_MEMBER(starwars_state::math_run_clear)
{
m_math_run = 0;
}
/*************************************
*
* X2212 nvram store
*
*************************************/
WRITE8_MEMBER(starwars_state::starwars_nstore_w)
{
m_novram->store(0);
m_novram->store(1);
m_novram->store(0);
}
WRITE_LINE_MEMBER(starwars_state::recall_w)
{
m_novram->recall(!state);
}
/*************************************
*
* Coin counters and LEDs
*
*************************************/
WRITE_LINE_MEMBER(starwars_state::coin1_counter_w)
{
machine().bookkeeping().coin_counter_w(0, state);
}
WRITE_LINE_MEMBER(starwars_state::coin2_counter_w)
{
machine().bookkeeping().coin_counter_w(1, state);
}
/*************************************
*
* Input port 1
*
*************************************/
READ_LINE_MEMBER(starwars_state::matrix_flag_r)
{
/* set the matrix processor flag */
return m_math_run ? 1 : 0;
}
/*************************************
*
* Matrix Processor initialization
*
*************************************/
void starwars_state::starwars_mproc_init()
{
uint8_t *src = memregion("user2")->base();
int cnt, val;
m_PROM_STR = std::make_unique<uint8_t[]>(1024);
m_PROM_MAS = std::make_unique<uint8_t[]>(1024);
m_PROM_AM = std::make_unique<uint8_t[]>(1024);
for (cnt = 0; cnt < 1024; cnt++)
{
/* translate PROMS into 16 bit code */
val = (src[0x0c00 + cnt] ) & 0x000f; /* Set LS nibble */
val |= (src[0x0800 + cnt] << 4) & 0x00f0;
val |= (src[0x0400 + cnt] << 8) & 0x0f00;
val |= (src[0x0000 + cnt] << 12) & 0xf000; /* Set MS nibble */
/* perform pre-decoding */
m_PROM_STR[cnt] = (val >> 8) & 0x00ff;
m_PROM_MAS[cnt] = val & 0x007f;
m_PROM_AM[cnt] = (val >> 7) & 0x0001;
}
m_math_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(starwars_state::math_run_clear),this));
}
/*************************************
*
* Matrix Processor reset
*
*************************************/
void starwars_state::starwars_mproc_reset()
{
m_MPA = m_BIC = 0;
m_math_run = 0;
}
/*************************************
*
* Matrix Processor execution
*
*************************************/
void starwars_state::run_mproc()
{
int RAMWORD = 0;
int MA_byte;
int tmp;
int M_STOP = 100000; /* Limit on number of instructions allowed before halt */
int MA;
int IP15_8, IP7, IP6_0; /* Instruction PROM values */
int mptime;
mptime = 0;
m_math_run = 1;
/* loop until finished */
while (M_STOP > 0)
{
/* each step of the matrix processor takes five clock cycles */
mptime += 5;
/* fetch the current instruction data */
IP15_8 = m_PROM_STR[m_MPA];
IP7 = m_PROM_AM[m_MPA];
IP6_0 = m_PROM_MAS[m_MPA];
#if (MATHDEBUG)
osd_printf_debug("\n(MPA:%x), Strobe: %x, IP7: %d, IP6_0:%x\n",m_MPA, IP15_8, IP7, IP6_0);
osd_printf_debug("(BIC: %x), A: %x, B: %x, C: %x, ACC: %x\n",m_BIC,m_A,m_B,m_C,m_ACC);
#endif
/* construct the current RAM address */
if (IP7 == 0)
MA = (IP6_0 & 3) | ((m_BIC & 0x01ff) << 2); /* MA10-2 set to BIC8-0 */
else
MA = IP6_0;
/* convert RAM offset to eight bit addressing (2kx8 rather than 1k*16)
and apply base address offset */
MA_byte = MA << 1;
RAMWORD = (m_mathram[MA_byte + 1] & 0x00ff) | ((m_mathram[MA_byte] & 0x00ff) << 8);
// logerror("MATH ADDR: %x, CPU ADDR: %x, RAMWORD: %x\n", MA, MA_byte, RAMWORD);
/*
* RAMWORD is the sixteen bit Math RAM value for the selected address
* MA_byte is the base address of this location as seen by the main CPU
* IP is the 16 bit instruction word from the PROM. IP7_0 have already
* been used in the address selection stage
* IP15_8 provide the instruction strobes
*/
/* The accumulator is built from two ls299 (msb) and two ls164
* (lsb). You can only read/write the 16 msb. The lsb are
* used while adding up multiplication results giving better
* accuracy.
*/
/* 0x10 - CLEAR_ACC */
if (IP15_8 & CLEAR_ACC)
{
m_ACC = 0;
}
/* 0x01 - LAC (also clears lsb)*/
if (IP15_8 & LAC)
m_ACC = (RAMWORD << 16);
/* 0x02 - READ_ACC */
if (IP15_8 & READ_ACC)
{
m_mathram[MA_byte+1] = ((m_ACC >> 16) & 0xff);
m_mathram[MA_byte ] = ((m_ACC >> 24) & 0xff);
}
/* 0x04 - M_HALT */
if (IP15_8 & M_HALT)
M_STOP = 0;
/* 0x08 - INC_BIC */
if (IP15_8 & INC_BIC)
m_BIC = (m_BIC + 1) & 0x1ff; /* Restrict to 9 bits */
/* 0x20 - LDC*/
if (IP15_8 & LDC)
{
m_C = RAMWORD;
/* This is a serial subtractor - multiplier (74ls384) -
* accumulator. For the full calculation 33 GMCLK pulses
* are generated. The calculation performed is:
*
* ACC = ACC + (A - B) * C
*
* 1. pulse: Bit 0 of A and B are subtracted. Bit 0 of the
* multiplication between multiplicand C and 0 is
* calculated (bit 0 of A-B is not yet at the multiplier
* input). Bit 0 of ACC is added to 0 (again, 'real' results
* from the previous operations are no yet there).
*
* 2. pulse: Bit 1 of A-B is calculated. Bit 1 of
* mutliplication is calculated based on bit 0 of A-B and
* bit 1 of C. Bit 1 of ACC is added to the multiplication
* result from first pulse.
*
* 3. pulse: Bit 2 of A-B is calculated. Bit 2 of
* mutliplication is calculated based on bit 1 of A-B and
* bit 2 of C. Bit 2 of ACC is added to the multiplication
* between bit 1 of C and bit 0 of A-B.
*
* etc.
*
* This pipeline causes the shifts between A-B, C and ACC.
* The 32 bit ACC and one bit adder form a ring so it
* takes 33 clock pulses to do a full rotation.
*/
m_ACC += (((int32_t)(m_A - m_B) << 1) * m_C) << 1;
/* A and B are sign extended (requred by the ls384). After
* multiplication they just contain the sign.
*/
m_A = (m_A & 0x8000)? 0xffff: 0;
m_B = (m_B & 0x8000)? 0xffff: 0;
/* The multiply-add holds the main matrix processor counter
* for 33 cycles
*/
mptime += 33;
}
/* 0x40 - LDB */
if (IP15_8 & LDB)
m_B = RAMWORD;
/* 0x80 - LDA */
if (IP15_8 & LDA)
m_A = RAMWORD;
/*
* Now update the PROM address counter
* Done like this because the top two bits are not part of the counter
* This means that each of the four pages should wrap around rather than
* leaking from one to another. It may not matter, but I've put it in anyway
*/
tmp = m_MPA + 1;
m_MPA = (m_MPA & 0x0300) | (tmp & 0x00ff); /* New MPA value */
M_STOP--; /* Decrease count */
}
m_math_timer->adjust(attotime::from_hz(MASTER_CLOCK) * mptime, 1);
}
/*************************************
*
* Pseudo-RNG read
*
*************************************/
READ8_MEMBER(starwars_state::starwars_prng_r)
{
/*
* The PRNG is a modified 23 bit LFSR. Taps are at 4 and 22 so the
* resulting LFSR polynomial is,
*
* x^5 + x^{23} + 1
*
* which is prime. It has a loop length of 8388607. The feedback
* bit is inverted so the PRNG can start with 0. Only 8 bits from
* bit 8 to 15 can be read by the CPU. The PRNG runs constantly at
* a clock speed of 3 MHz.
*/
/* Use MAME's PRNG for now */
return machine().rand();
}
WRITE_LINE_MEMBER(starwars_state::prng_reset_w)
{
}
/*************************************
*
* Starwars divider
*
*************************************/
READ8_MEMBER(starwars_state::starwars_div_reh_r)
{
return (m_quotient_shift & 0xff00) >> 8;
}
READ8_MEMBER(starwars_state::starwars_div_rel_r)
{
return m_quotient_shift & 0x00ff;
}
WRITE8_MEMBER(starwars_state::starwars_math_w)
{
int i;
data &= 0xff; /* ASG 971002 -- make sure we only get bytes here */
switch (offset)
{
case 0: /* mw0 */
m_MPA = data << 2; /* Set starting PROM address */
run_mproc(); /* and run the Matrix Processor */
break;
case 1: /* mw1 */
m_BIC = (m_BIC & 0x00ff) | ((data & 0x01) << 8);
break;
case 2: /* mw2 */
m_BIC = (m_BIC & 0x0100) | data;
break;
case 4: /* dvsrh */
m_divisor = (m_divisor & 0x00ff) | (data << 8);
m_dvd_shift = m_dividend;
m_quotient_shift = 0;
break;
case 5: /* dvsrl */
/* Note: Divide is triggered by write to low byte. This is */
/* dependant on the proper 16 bit write order in the */
/* 6809 emulation (high bytes, then low byte). */
/* If the Tie fighters look corrupt, he byte order of */
/* the 16 bit writes in the 6809 are backwards */
m_divisor = (m_divisor & 0xff00) | data;
/*
* Simple restoring division as shown in the
* schematics. The algorithm produces the same "wrong"
* results as the hardware if m_divisor < 2*m_dividend or
* m_divisor > 0x8000.
*/
for (i = 1; i < 16; i++)
{
m_quotient_shift <<= 1;
if (((int32_t)m_dvd_shift + (m_divisor ^ 0xffff) + 1) & 0x10000)
{
m_quotient_shift |= 1;
m_dvd_shift = (m_dvd_shift + (m_divisor ^ 0xffff) + 1) << 1;
}
else
{
m_dvd_shift <<= 1;
}
}
break;
case 6: /* dvddh */
m_dividend = (m_dividend & 0x00ff) | (data << 8);
break;
case 7: /* dvddl */
m_dividend = (m_dividend & 0xff00) | (data);
break;
default:
break;
}
}