// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** Videa (predecessor to Sente) Gridlee hardware driver by Aaron Giles special thanks to Howard Delman, Roger Hector and Ed Rotberg for allowing distribution of the Gridlee ROMs Based on the Bally/Sente SAC system Games supported: * Gridlee Known bugs: * analog sound hardware is unemulated **************************************************************************** Memory map **************************************************************************** ======================================================================== CPU #1 ======================================================================== 0000-007F R/W xxxxxxxx Sprite RAM (32 entries x 4 bytes) R/W xxxxxxxx (0: image number) R/W -------- (1: unused?) R/W xxxxxxxx (2: Y position, offset by 17 pixels) R/W xxxxxxxx (3: X position) 0080-07FF R/W xxxxxxxx Program RAM 0800-7FFF R/W xxxxxxxx Video RAM (256x240 pixels) R/W xxxx---- (left pixel) R/W ----xxxx (right pixel) 9000 W -------x Player 1 LED 9010 W -------x Player 2 LED 9020 W -------x Coin counter 9060 W -------x Unknown (written once at startup) 9070 W -------x Cocktail flip 9200 W --xxxxxx Palette base select 9380 W -------- Watchdog reset 9500 R ---xxxxx Trackball Y position R ---x---- Sign of delta R ----xxxx Cumulative magnitude 9501 R ---xxxxx Trackball X position R ---x---- Sign of delta R ----xxxx Cumulative magnitude 9502 R ------x- Fire button 2 R -------x Fire button 1 9503 R --xx---- Coinage switches R ----x--- 2 player start R -----x-- 1 player start R ------x- Right coin R -------x Left coin 9600 R x------- Reset game data switch R -x------ Reset hall of fame switch R --x----- Cocktail/upright switch R ---x---- Free play switch R ----xx-- Lives switches R ------xx Bonus lives switches 9700 R x------- VBLANK R -x------ Service advance R --x----- Service switch 9820 R xxxxxxxx Random number generator 9828-982C W ???????? Unknown 9830-983F W ???????? Unknown (sound-related) 9C00-9CFF R/W -------- NVRAM A000-FFFF R xxxxxxxx Fixed program ROM ======================================================================== Interrupts: NMI not connected IRQ generated by 32L FIRQ generated by ??? (but should be around scanline 92) ======================================================================== ***************************************************************************/ #include "emu.h" #include "gridlee.h" #include "cpu/m6809/m6809.h" #include "sound/samples.h" #include "machine/nvram.h" #include "machine/watchdog.h" #include "speaker.h" /* constants */ #define FIRQ_SCANLINE 92 /************************************* * * Interrupt handling * *************************************/ TIMER_CALLBACK_MEMBER(gridlee_state::irq_off_tick) { m_maincpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE); } TIMER_CALLBACK_MEMBER(gridlee_state::irq_timer_tick) { /* next interrupt after scanline 256 is scanline 64 */ if (param == 256) m_irq_timer->adjust(m_screen->time_until_pos(64), 64); else m_irq_timer->adjust(m_screen->time_until_pos(param + 64), param + 64); /* IRQ starts on scanline 0, 64, 128, etc. */ m_maincpu->set_input_line(M6809_IRQ_LINE, ASSERT_LINE); /* it will turn off on the next HBLANK */ m_irq_off->adjust(m_screen->time_until_pos(param, GRIDLEE_HBSTART)); } TIMER_CALLBACK_MEMBER(gridlee_state::firq_off_tick) { m_maincpu->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE); } TIMER_CALLBACK_MEMBER(gridlee_state::firq_timer_tick) { /* same time next frame */ m_firq_timer->adjust(m_screen->time_until_pos(FIRQ_SCANLINE)); /* IRQ starts on scanline FIRQ_SCANLINE? */ m_maincpu->set_input_line(M6809_FIRQ_LINE, ASSERT_LINE); /* it will turn off on the next HBLANK */ m_firq_off->adjust(m_screen->time_until_pos(FIRQ_SCANLINE, GRIDLEE_HBSTART)); } void gridlee_state::machine_start() { /* create the polynomial tables */ poly17_init(); save_item(NAME(m_last_analog_input)); save_item(NAME(m_last_analog_output)); m_irq_off = timer_alloc(FUNC(gridlee_state::irq_off_tick), this); m_irq_timer = timer_alloc(FUNC(gridlee_state::irq_timer_tick), this); m_firq_off = timer_alloc(FUNC(gridlee_state::firq_off_tick), this); m_firq_timer = timer_alloc(FUNC(gridlee_state::firq_timer_tick), this); } void gridlee_state::machine_reset() { /* start timers to generate interrupts */ m_irq_timer->adjust(m_screen->time_until_pos(0)); m_firq_timer->adjust(m_screen->time_until_pos(FIRQ_SCANLINE)); } /************************************* * * ADC handlers * *************************************/ uint8_t gridlee_state::analog_port_r(offs_t offset) { int delta, sign, magnitude; uint8_t newval; static const char *const portnames[] = { "TRACK0_Y", "TRACK0_X", "TRACK1_Y", "TRACK1_X" }; /* first read the new trackball value and compute the signed delta */ newval = ioport(portnames[offset + 2 * m_cocktail_flip])->read(); delta = (int)newval - (int)m_last_analog_input[offset]; /* handle the case where we wrap around from 0x00 to 0xff, or vice versa */ if (delta >= 0x80) delta -= 0x100; if (delta <= -0x80) delta += 0x100; /* just return the previous value for deltas less than 2, which are ignored */ if (delta >= -1 && delta <= 1) return m_last_analog_output[offset]; m_last_analog_input[offset] = newval; /* compute the sign and the magnitude */ sign = (delta < 0) ? 0x10 : 0x00; magnitude = (delta < 0) ? -delta : delta; /* add the magnitude to the running total */ m_last_analog_output[offset] += magnitude; /* or in the sign bit and return that */ return (m_last_analog_output[offset] & 15) | sign; } /************************************* * * MM5837 noise generator * * NOTE: this is stolen straight from * POKEY.c * * NOTE: this is assumed to be the * same as balsente.c * *************************************/ #define POLY17_BITS 17 #define POLY17_SIZE ((1 << POLY17_BITS) - 1) #define POLY17_SHL 7 #define POLY17_SHR 10 #define POLY17_ADD 0x18000 void gridlee_state::poly17_init() { uint32_t i, x = 0; uint8_t *p, *r; /* allocate memory */ m_poly17 = std::make_unique(2 * (POLY17_SIZE + 1)); p = m_poly17.get(); r = m_rand17 = m_poly17.get() + POLY17_SIZE + 1; /* generate the polynomial */ for (i = 0; i < POLY17_SIZE; i++) { /* store new values */ *p++ = x & 1; *r++ = x >> 3; /* calculate next bit */ x = ((x << POLY17_SHL) + (x >> POLY17_SHR) + POLY17_ADD) & POLY17_SIZE; } } /************************************* * * Hardware random numbers * *************************************/ uint8_t gridlee_state::random_num_r() { uint32_t cc; /* CPU runs at 1.25MHz, noise source at 100kHz --> multiply by 12.5 */ cc = m_maincpu->total_cycles(); /* 12.5 = 8 + 4 + 0.5 */ cc = (cc << 3) + (cc << 2) + (cc >> 1); return m_rand17[cc & POLY17_SIZE]; } /************************************* * * Misc handlers * *************************************/ void gridlee_state::coin_counter_w(int state) { machine().bookkeeping().coin_counter_w(0, state); logerror("coin counter %s\n", state ? "on" : "off"); } /************************************* * * Main CPU memory handlers * *************************************/ /* CPU 1 read addresses */ void gridlee_state::cpu1_map(address_map &map) { map(0x0000, 0x07ff).ram().share("spriteram"); map(0x0800, 0x7fff).ram().w(FUNC(gridlee_state::gridlee_videoram_w)).share("videoram"); map(0x9000, 0x9000).select(0x0070).lw8(NAME([this] (offs_t offset, u8 data) { m_latch->write_d0(offset >> 4, data); })); map(0x9200, 0x9200).w(FUNC(gridlee_state::gridlee_palette_select_w)); map(0x9380, 0x9380).w("watchdog", FUNC(watchdog_timer_device::reset_w)); map(0x9500, 0x9501).r(FUNC(gridlee_state::analog_port_r)); map(0x9502, 0x9502).portr("IN0"); map(0x9503, 0x9503).portr("IN1"); map(0x9600, 0x9600).portr("DSW"); map(0x9700, 0x9700).portr("IN2").nopw(); map(0x9820, 0x9820).r(FUNC(gridlee_state::random_num_r)); map(0x9828, 0x993f).w("gridlee", FUNC(gridlee_sound_device::gridlee_sound_w)); map(0x9c00, 0x9cff).ram().share("nvram"); map(0xa000, 0xffff).rom(); } /************************************* * * Port definitions * *************************************/ static INPUT_PORTS_START( gridlee ) PORT_START("TRACK0_Y") /* 9500 (fake) */ PORT_BIT( 0xff, 0, IPT_TRACKBALL_Y ) PORT_SENSITIVITY(20) PORT_KEYDELTA(8) PORT_START("TRACK0_X") /* 9501 (fake) */ PORT_BIT( 0xff, 0, IPT_TRACKBALL_X ) PORT_SENSITIVITY(20) PORT_KEYDELTA(8) PORT_REVERSE PORT_START("TRACK1_Y") /* 9500 (fake) */ PORT_BIT( 0xff, 0, IPT_TRACKBALL_Y ) PORT_SENSITIVITY(20) PORT_KEYDELTA(8) PORT_COCKTAIL PORT_START("TRACK1_X") /* 9501 (fake) */ PORT_BIT( 0xff, 0, IPT_TRACKBALL_X ) PORT_SENSITIVITY(20) PORT_KEYDELTA(8) PORT_REVERSE PORT_COCKTAIL PORT_START("IN0") /* 9502 */ PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_COCKTAIL PORT_BIT( 0xfc, IP_ACTIVE_LOW, IPT_UNKNOWN ) PORT_START("IN1") /* 9503 */ PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_COIN1 ) PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_COIN2 ) PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_START1 ) PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_START2 ) PORT_DIPNAME( 0x30, 0x00, DEF_STR( Coinage )) PORT_DIPSETTING( 0x20, DEF_STR( 2C_1C )) PORT_DIPSETTING( 0x00, DEF_STR( 1C_1C )) PORT_DIPSETTING( 0x10, DEF_STR( 1C_2C )) PORT_BIT( 0xc0, IP_ACTIVE_LOW, IPT_UNKNOWN ) PORT_START("DSW") /* 9600 */ PORT_DIPNAME( 0x03, 0x01, DEF_STR( Bonus_Life )) PORT_DIPSETTING( 0x00, "8000 points" ) PORT_DIPSETTING( 0x01, "10000 points" ) PORT_DIPSETTING( 0x02, "12000 points" ) PORT_DIPSETTING( 0x03, "14000 points" ) PORT_DIPNAME( 0x0c, 0x04, DEF_STR( Lives )) PORT_DIPSETTING( 0x00, "2" ) PORT_DIPSETTING( 0x04, "3" ) PORT_DIPSETTING( 0x08, "4" ) PORT_DIPSETTING( 0x0c, "5" ) PORT_DIPNAME( 0x10, 0x00, DEF_STR( Free_Play )) PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x10, DEF_STR( On )) PORT_DIPNAME( 0x20, 0x00, DEF_STR( Cabinet ) ) PORT_DIPSETTING( 0x00, DEF_STR( Upright ) ) PORT_DIPSETTING( 0x20, DEF_STR( Cocktail ) ) PORT_DIPNAME( 0x40, 0x00, "Reset Hall of Fame" ) PORT_DIPSETTING( 0x00, DEF_STR( No )) PORT_DIPSETTING( 0x40, DEF_STR( Yes )) PORT_DIPNAME( 0x80, 0x00, "Reset Game Data" ) PORT_DIPSETTING( 0x00, DEF_STR( No )) PORT_DIPSETTING( 0x80, DEF_STR( Yes )) PORT_START("IN2") /* 9700 */ PORT_BIT( 0x1f, IP_ACTIVE_LOW, IPT_UNKNOWN ) PORT_SERVICE( 0x20, IP_ACTIVE_LOW ) PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_SERVICE1 ) PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_DEVICE_MEMBER("screen", FUNC(screen_device::vblank)) INPUT_PORTS_END /************************************* * * Sound definitions * *************************************/ static const char *const sample_names[] = { "*gridlee", "bounce1", "bounce2", nullptr /* end of array */ }; /************************************* * * Machine driver * *************************************/ void gridlee_state::gridlee(machine_config &config) { /* basic machine hardware */ M6809(config, m_maincpu, GRIDLEE_CPU_CLOCK); m_maincpu->set_addrmap(AS_PROGRAM, &gridlee_state::cpu1_map); NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_0); WATCHDOG_TIMER(config, "watchdog"); LS259(config, m_latch); // type can only be guessed m_latch->q_out_cb<0>().set_output("led0"); m_latch->q_out_cb<1>().set_output("led1"); m_latch->q_out_cb<2>().set(FUNC(gridlee_state::coin_counter_w)); // Q6 unknown - only written to at startup m_latch->q_out_cb<7>().set(FUNC(gridlee_state::cocktail_flip_w)); /* video hardware */ SCREEN(config, m_screen, SCREEN_TYPE_RASTER); m_screen->set_raw(GRIDLEE_PIXEL_CLOCK, GRIDLEE_HTOTAL, GRIDLEE_HBEND, GRIDLEE_HBSTART, GRIDLEE_VTOTAL, GRIDLEE_VBEND, GRIDLEE_VBSTART); m_screen->set_screen_update(FUNC(gridlee_state::screen_update_gridlee)); m_screen->set_palette(m_palette); PALETTE(config, m_palette, FUNC(gridlee_state::gridlee_palette), 2048); /* sound hardware */ SPEAKER(config, "mono").front_center(); GRIDLEE(config, "gridlee", 0).add_route(ALL_OUTPUTS, "mono", 1.0); SAMPLES(config, m_samples); m_samples->set_channels(8); m_samples->set_samples_names(sample_names); m_samples->add_route(ALL_OUTPUTS, "mono", 0.40); } /************************************* * * ROM definitions * *************************************/ ROM_START( gridlee ) ROM_REGION( 0x10000, "maincpu", 0 ) ROM_LOAD( "gridfnla.bin", 0xa000, 0x1000, CRC(1c43539e) SHA1(8b4a6f5c2c22bb021937157606d2129e2b01f718) ) ROM_LOAD( "gridfnlb.bin", 0xb000, 0x1000, CRC(c48b91b8) SHA1(651210470ddf7c14f16f6c3046a9b8e903824ab8) ) ROM_LOAD( "gridfnlc.bin", 0xc000, 0x1000, CRC(6ad436dd) SHA1(f393b63077f249d34a8e85649aea58b27a0425b1) ) ROM_LOAD( "gridfnld.bin", 0xd000, 0x1000, CRC(f7188ddb) SHA1(eeb3f7dd8c61689cdd9992280ee1b3b5dc79a54c) ) ROM_LOAD( "gridfnle.bin", 0xe000, 0x1000, CRC(d5330bee) SHA1(802bb5705d4cd22d556c1bcbcf730d688ca8e8ab) ) ROM_LOAD( "gridfnlf.bin", 0xf000, 0x1000, CRC(695d16a3) SHA1(53d22cbedbedad8c89a964b6a38b7075c43cf03b) ) ROM_REGION( 0x4000, "gfx1", 0 ) ROM_LOAD( "gridpix0.bin", 0x0000, 0x1000, CRC(e6ea15ae) SHA1(2c482e25ea44aafd63ca5533b5a2e2dd8bf89365) ) ROM_LOAD( "gridpix1.bin", 0x1000, 0x1000, CRC(d722f459) SHA1(8cad028eefbba387bdd57fb8bb3a855ae314fb32) ) ROM_LOAD( "gridpix2.bin", 0x2000, 0x1000, CRC(1e99143c) SHA1(89c2f772cd15f2c37c8167a03dc4c7d1c923e4c3) ) ROM_LOAD( "gridpix3.bin", 0x3000, 0x1000, CRC(274342a0) SHA1(818cfd4132183d922ff4585c73f2cd6e4546c75b) ) ROM_REGION( 0x1800, "proms", 0 ) ROM_LOAD( "grdrprom.bin", 0x0000, 0x800, CRC(f28f87ed) SHA1(736f38c3ec5455de1266aad348ba708d7201b21a) ) ROM_LOAD( "grdgprom.bin", 0x0800, 0x800, CRC(921b0328) SHA1(59d1a3d3a90bd680a75adca5dd1b4682236c673b) ) ROM_LOAD( "grdbprom.bin", 0x1000, 0x800, CRC(04350348) SHA1(098fec3073143e0b8746e728d7d321f2a353286f) ) ROM_END /************************************* * * Game drivers * *************************************/ GAME( 1983, gridlee, 0, gridlee, gridlee, gridlee_state, empty_init, ROT0, "Videa", "Gridlee", MACHINE_SUPPORTS_SAVE | MACHINE_IMPERFECT_SOUND )