// license: BSD-3-Clause // copyright-holders: Dirk Best /*************************************************************************** Popper © 1983 Omori Electric Co. (OEC) PCB connector pinout GND 1 2 GND GND 3 4 GND 5V 5 6 5V 5V 7 8 5V Speaker 9 10 Speaker 12V 11 12 12V 1-P Push 13 14 1-P Right 2-P Push 15 16 2-P Right 1-P Start 17 18 1-P Left 2-P Start 19 20 2-P Left Coin A 21 22 1-P Down Coin B 23 24 2-P Down Service 25 26 1-P Up 27 28 2-P Up 29 30 31 32 Synic 33 34 Red Green 35 36 Blue 5V 37 38 5V 5V 39 40 5V GND 41 42 GND TODO: - According to the schematics the sub CPU ROM should be 0x2000 - Verify screen raw parameters - Verify graphics with real hardware - Watchdog ***************************************************************************/ #include "emu.h" #include "cpu/z80/z80.h" #include "sound/ay8910.h" #include "video/resnet.h" #include "screen.h" #include "speaker.h" //************************************************************************** // TYPE DEFINITIONS //************************************************************************** class popper_state : public driver_device { public: popper_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag), m_maincpu(*this, "maincpu"), m_subcpu(*this, "subcpu"), m_screen(*this, "screen"), m_gfxdecode(*this, "gfxdecode"), m_ay{ {*this, "ay1"}, {*this, "ay2"} }, m_video_ram(*this, "video_ram"), m_attribute_ram(*this, "attribute_ram"), m_sprite_ram(*this, "sprite_ram"), m_inputs{ {*this, "in1"}, {*this, "in0"}, {*this, "dsw2"}, {*this, "dsw1"} }, m_scanline_timer(nullptr), m_layer0_tilemap(nullptr), m_layer1_tilemap(nullptr), m_nmi_enable(0), m_back_color(0), m_vram_page(0) { } void popper(machine_config &config); protected: virtual void machine_start() override; virtual void machine_reset() override; virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; private: required_device m_maincpu; required_device m_subcpu; required_device m_screen; required_device m_gfxdecode; required_device m_ay[2]; required_shared_ptr m_video_ram; required_shared_ptr m_attribute_ram; required_shared_ptr m_sprite_ram; required_ioport m_inputs[4]; emu_timer *m_scanline_timer; tilemap_t *m_layer0_tilemap; tilemap_t *m_layer1_tilemap; int m_nmi_enable; int m_back_color; int m_vram_page; void main_map(address_map &map); void sub_map(address_map &map); DECLARE_PALETTE_INIT(popper); uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); TILE_GET_INFO_MEMBER(layer0_tile_info); TILE_GET_INFO_MEMBER(layer1_tile_info); DECLARE_WRITE8_MEMBER(nmi_control_w); DECLARE_WRITE8_MEMBER(crt_direction_w); DECLARE_WRITE8_MEMBER(back_color_select_w); DECLARE_WRITE8_MEMBER(vram_page_select_w); DECLARE_WRITE8_MEMBER(intcycle_w); DECLARE_READ8_MEMBER(subcpu_nmi_r); DECLARE_READ8_MEMBER(subcpu_reset_r); DECLARE_WRITE8_MEMBER(ay1_w); DECLARE_READ8_MEMBER(watchdog_clear_r); DECLARE_READ8_MEMBER(inputs_r); }; //************************************************************************** // ADDRESS MAPS //************************************************************************** void popper_state::main_map(address_map &map) { map(0x0000, 0x7fff).rom(); map(0x8000, 0xbfff).noprw(); map(0xc000, 0xc0ff).ram(); map(0xc100, 0xc6ff).ram().share("video_ram"); map(0xc700, 0xc8ff).ram(); map(0xc900, 0xceff).ram().share("attribute_ram"); map(0xcf00, 0xcfff).ram(); map(0xd000, 0xd7ff).ram().share("sprite_ram"); map(0xd800, 0xdfff).ram().share("shared"); map(0xe000, 0xe003).mirror(0x03fc).r(this, FUNC(popper_state::inputs_r)); map(0xe000, 0xe000).mirror(0x1ff8).w(this, FUNC(popper_state::nmi_control_w)); map(0xe001, 0xe001).mirror(0x1ff8).w(this, FUNC(popper_state::crt_direction_w)); map(0xe002, 0xe002).mirror(0x1ff8).w(this, FUNC(popper_state::back_color_select_w)); map(0xe003, 0xe003).mirror(0x1ff8).w(this, FUNC(popper_state::vram_page_select_w)); map(0xe004, 0xe007).mirror(0x1ff8).w(this, FUNC(popper_state::intcycle_w)); map(0xe400, 0xe400).mirror(0x03ff).r(this, FUNC(popper_state::subcpu_nmi_r)); map(0xe800, 0xf7ff).noprw(); map(0xf800, 0xf800).mirror(0x03ff).r(this, FUNC(popper_state::subcpu_reset_r)); map(0xfc00, 0xfc00).mirror(0x03ff).r(this, FUNC(popper_state::watchdog_clear_r)); } void popper_state::sub_map(address_map &map) { map(0x0000, 0x1fff).rom(); map(0x2000, 0x7fff).noprw(); map(0x8000, 0x8003).mirror(0x1ffc).w(this, FUNC(popper_state::ay1_w)); map(0xa000, 0xa003).mirror(0x1ffc).w("ay2", FUNC(ay8910_device::write_bc1_bc2)); map(0xc000, 0xc7ff).mirror(0x1800).ram().share("shared"); map(0xe000, 0xffff).noprw(); } //************************************************************************** // INPUT PORT DEFINITIONS //************************************************************************** static INPUT_PORTS_START( popper ) PORT_START("in0") PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_COIN1) PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN) PORT_8WAY PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_COIN2) PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN) PORT_COCKTAIL PORT_8WAY PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_SERVICE1) PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP) PORT_8WAY PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_UNUSED) PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP) PORT_COCKTAIL PORT_8WAY PORT_START("in1") PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT) PORT_8WAY PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_COCKTAIL PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT) PORT_COCKTAIL PORT_8WAY PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_START1) PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT) PORT_8WAY PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_START2) PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT) PORT_COCKTAIL PORT_8WAY PORT_START("dsw1") PORT_DIPNAME(0x03, 0x00, DEF_STR( Coin_A )) PORT_DIPLOCATION("DSW1:1,2") PORT_DIPSETTING( 0x00, DEF_STR( 1C_1C )) PORT_DIPSETTING( 0x02, DEF_STR( 2C_1C )) PORT_DIPSETTING( 0x01, DEF_STR( 3C_1C )) PORT_DIPSETTING( 0x03, DEF_STR( 1C_2C )) PORT_DIPNAME(0x0c, 0x04, DEF_STR( Coin_B )) PORT_DIPLOCATION("DSW1:3,4") PORT_DIPSETTING( 0x00, DEF_STR( 1C_3C )) PORT_DIPSETTING( 0x08, DEF_STR( 1C_4C )) PORT_DIPSETTING( 0x04, DEF_STR( 1C_5C )) PORT_DIPSETTING( 0x0c, DEF_STR( 1C_6C )) PORT_DIPNAME(0x30, 0x20, DEF_STR( Lives )) PORT_DIPLOCATION("DSW1:5,6") PORT_DIPSETTING( 0x00, "2") PORT_DIPSETTING( 0x20, "3") PORT_DIPSETTING( 0x10, "4") PORT_DIPSETTING( 0x30, "5") PORT_DIPNAME(0xc0, 0x00, DEF_STR( Bonus_Life )) PORT_DIPLOCATION("DSW1:7,8") PORT_DIPSETTING( 0x00, "20,000 Points") PORT_DIPSETTING( 0x80, "30,000 Points") PORT_DIPSETTING( 0x40, "40,000 Points") PORT_DIPSETTING( 0xc0, "50,000 Points") PORT_START("dsw2") PORT_DIPNAME(0x01, 0x01, DEF_STR( Demo_Sounds )) PORT_DIPLOCATION("DSW2:1") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x01, DEF_STR( On )) PORT_DIPUNUSED_DIPLOC(0x02, IP_ACTIVE_LOW, "DSW2:2") PORT_DIPNAME(0x04, 0x00, DEF_STR( Free_Play )) PORT_DIPLOCATION("DSW2:3") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x04, DEF_STR( On )) PORT_DIPNAME(0x08, 0x00, "Invulnerability") PORT_DIPLOCATION("DSW2:4") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x08, DEF_STR( On )) PORT_DIPNAME(0x10, 0x00, DEF_STR( Flip_Screen )) PORT_DIPLOCATION("DSW2:5") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x10, DEF_STR( On )) PORT_DIPNAME(0x20, 0x20, DEF_STR( Cabinet )) PORT_DIPLOCATION("DSW2:6") PORT_DIPSETTING( 0x00, DEF_STR( Cocktail )) PORT_DIPSETTING( 0x20, DEF_STR( Upright )) PORT_DIPNAME(0x40, 0x00, "Game Repeating") PORT_DIPLOCATION("DSW2:7") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x40, DEF_STR( On )) PORT_DIPNAME(0x80, 0x00, DEF_STR( Pause )) PORT_DIPLOCATION("DSW2:8") PORT_DIPSETTING( 0x00, DEF_STR( Off )) PORT_DIPSETTING( 0x80, DEF_STR( On )) INPUT_PORTS_END //************************************************************************** // INPUT PORT HANDLING //************************************************************************** READ8_MEMBER( popper_state::inputs_r ) { uint8_t data = 0; data |= BIT(m_inputs[3]->read(), offset + 4) << 7; data |= BIT(m_inputs[3]->read(), offset + 0) << 6; data |= BIT(m_inputs[2]->read(), offset + 4) << 5; data |= BIT(m_inputs[2]->read(), offset + 0) << 4; data |= BIT(m_inputs[1]->read(), offset + 4) << 3; data |= BIT(m_inputs[1]->read(), offset + 0) << 2; data |= BIT(m_inputs[0]->read(), offset + 4) << 1; data |= BIT(m_inputs[0]->read(), offset + 0) << 0; return data; } //************************************************************************** // PALETTE //************************************************************************** static const res_net_decode_info popper_decode_info = { 1, 0, 63, // R G B { 0, 0, 0, }, // offsets { 0, 3, 6, }, // shifts { 0x07, 0x07, 0x03, } // masks }; static const res_net_info popper_net_info = { RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_TTL_OUT, { { RES_NET_AMP_NONE, 0, 0, 3, { 1000, 470, 220 } }, { RES_NET_AMP_NONE, 0, 0, 3, { 1000, 470, 220 } }, { RES_NET_AMP_NONE, 0, 0, 2, { 470, 220, 0 } } } }; PALETTE_INIT_MEMBER( popper_state, popper ) { const uint8_t *prom = memregion("colors")->base(); std::vector rgb; compute_res_net_all(rgb, prom, popper_decode_info, popper_net_info); palette.set_pen_colors(0, rgb); palette.palette()->normalize_range(0, 63); } //************************************************************************** // VIDEO EMULATION //************************************************************************** void popper_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { int y = m_screen->vpos(); // the maincpu gets an nmi when we enter vblank (when enabled) m_maincpu->set_input_line(INPUT_LINE_NMI, (m_nmi_enable && y == 240) ? ASSERT_LINE : CLEAR_LINE); // the subcpu gets an interrupt each 32 lines m_subcpu->set_input_line(INPUT_LINE_IRQ0, ((y & 31) == 0) ? ASSERT_LINE : CLEAR_LINE); m_scanline_timer->adjust(m_screen->time_until_pos(y + 1, 0)); } WRITE8_MEMBER( popper_state::crt_direction_w ) { flip_screen_set(data); } WRITE8_MEMBER( popper_state::back_color_select_w ) { m_back_color = data & 1; } WRITE8_MEMBER( popper_state::vram_page_select_w ) { m_vram_page = data & 1; } uint32_t popper_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { bitmap.fill(0, cliprect); // always draw all tiles m_layer0_tilemap->mark_all_dirty(); m_layer1_tilemap->mark_all_dirty(); // draw the layers with lower priority m_layer0_tilemap->draw(screen, bitmap, cliprect, 0, 0); m_layer1_tilemap->draw(screen, bitmap, cliprect, 0, 0); // draw the sprites for (int offs = 0; offs < 0x800; offs += 4) { // 0 76543210 Y coordinate // 1 76543210 Code // 2 7------- Flip Y // 2 -6------ Flip X // 2 --54---- Not used // 2 ----3210 Color // 3 76543210 X coordinate int sx = m_sprite_ram[offs + 3]; int sy = m_sprite_ram[offs + 0]; // only render sprite if it's in the current offset range if (((sy + (flip_screen() ? 2 : 0)) >> 4) != ((~(offs >> 7)) & 0x0f)) continue; sx += 64; sy = 240 - sy; int flipx = BIT(m_sprite_ram[offs + 2], 6); int flipy = BIT(m_sprite_ram[offs + 2], 7); if (flip_screen()) { flipx = !flipx; flipy = !flipy; sx = 232 - sx; sx += 128; sy = (240 + 2) - sy; } int code = m_sprite_ram[offs + 1]; int color = m_sprite_ram[offs + 2] & 0x0f; m_gfxdecode->gfx(2)->transpen(bitmap, cliprect, code, color, flipx, flipy, sx, sy, 0); } // draw the tiles with priority over the sprites m_layer0_tilemap->draw(screen, bitmap, cliprect, 1, 0); m_layer1_tilemap->draw(screen, bitmap, cliprect, 1, 0); return 0; } //************************************************************************** // DRAWGFX LAYOUTS //************************************************************************** static const gfx_layout layer0_charlayout = { 8,8, RGN_FRAC(2,2), 1, { 0, 4 }, { STEP4(8,1), STEP4(0,1) }, { STEP8(0,16) }, 16*8 }; static const gfx_layout layer1_charlayout = { 8,8, RGN_FRAC(2,2), 2, { 0, 4 }, { STEP4(8,1), STEP4(0,1) }, { STEP8(0,16) }, 16*8 }; static const gfx_layout spritelayout = { 16,16, RGN_FRAC(1,2), 2, { RGN_FRAC(0,2), RGN_FRAC(1,2) }, { STEP8(8,1), STEP8(0,1) }, { STEP16(0,16) }, 16*2*8 }; static GFXDECODE_START( gfx_popper ) GFXDECODE_ENTRY("tiles", 0, layer0_charlayout, 0, 32) GFXDECODE_ENTRY("tiles", 0, layer1_charlayout, 0, 16) GFXDECODE_ENTRY("sprites", 0, spritelayout, 0, 16) GFXDECODE_END // attribute ram layout // 7------- high priority // -654---- layer0 color // ----3210 layer1 color TILE_GET_INFO_MEMBER( popper_state::layer0_tile_info ) { int code = (m_vram_page << 8) | m_video_ram[tile_index]; int attr = m_attribute_ram[tile_index]; int color = (~m_back_color & 1) << 3 | ((attr >> 4) & 7); color <<= 1; // high priority only applies if a color is set tileinfo.category = BIT(attr, 7) && (attr & 0x70); SET_TILE_INFO_MEMBER(0, code, color, 0); } TILE_GET_INFO_MEMBER( popper_state::layer1_tile_info ) { int code = (m_vram_page << 8) | m_video_ram[tile_index]; int attr = m_attribute_ram[tile_index]; int color = attr & 0x0f; tileinfo.category = BIT(attr, 7); SET_TILE_INFO_MEMBER(1, code, color, 0); } //************************************************************************** // SUBCPU //************************************************************************** READ8_MEMBER( popper_state::subcpu_nmi_r ) { m_subcpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE); m_subcpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); return 0; } READ8_MEMBER( popper_state::subcpu_reset_r ) { m_subcpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE); m_subcpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE); return 0; } WRITE8_MEMBER( popper_state::ay1_w ) { if (offset == 3) { m_ay[0]->reset(); m_ay[1]->reset(); } m_ay[0]->write_bc1_bc2(space, offset, data); } //************************************************************************** // MACHINE EMULATION //************************************************************************** WRITE8_MEMBER( popper_state::nmi_control_w ) { m_nmi_enable = data & 1; } WRITE8_MEMBER( popper_state::intcycle_w ) { // set to 0 and apparently not used by the game logerror("intcycle_w: %d = %02x\n", offset, data); } READ8_MEMBER( popper_state::watchdog_clear_r ) { return 0; } void popper_state::machine_start() { // create tilemaps m_layer0_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(popper_state::layer0_tile_info), this), TILEMAP_SCAN_COLS, 8, 8, 48, 32); m_layer0_tilemap->set_transparent_pen(1); m_layer1_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(popper_state::layer1_tile_info), this), TILEMAP_SCAN_COLS, 8, 8, 48, 32); m_layer1_tilemap->set_transparent_pen(0); // allocate and start scanline timer m_scanline_timer = timer_alloc(0); m_scanline_timer->adjust(m_screen->time_until_pos(0, 0)); // register for save states save_item(NAME(m_nmi_enable)); save_item(NAME(m_back_color)); save_item(NAME(m_vram_page)); } void popper_state::machine_reset() { m_nmi_enable = 0; m_back_color = 0; m_vram_page = 0; } //************************************************************************** // MACHINE DEFINTIONS //************************************************************************** MACHINE_CONFIG_START(popper_state::popper) MCFG_DEVICE_ADD("maincpu", Z80, XTAL(18'432'000)/3/2) MCFG_DEVICE_PROGRAM_MAP(main_map) MCFG_DEVICE_ADD("subcpu", Z80, XTAL(18'432'000)/3/2) MCFG_DEVICE_PROGRAM_MAP(sub_map) MCFG_QUANTUM_PERFECT_CPU("maincpu") // video hardware MCFG_SCREEN_ADD("screen", RASTER) MCFG_SCREEN_RAW_PARAMS(XTAL(18'432'000)/3, 384, 48, 328, 264, 16, 240) MCFG_SCREEN_UPDATE_DRIVER(popper_state, screen_update) MCFG_SCREEN_PALETTE("palette") MCFG_DEVICE_ADD("gfxdecode", GFXDECODE, "palette", gfx_popper) MCFG_PALETTE_ADD("palette", 64) MCFG_PALETTE_INIT_OWNER(popper_state, popper) // audio hardware SPEAKER(config, "mono").front_center(); MCFG_DEVICE_ADD("ay1", AY8910, XTAL(18'432'000)/3/2/2) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.25) MCFG_DEVICE_ADD("ay2", AY8910, XTAL(18'432'000)/3/2/2) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.25) MACHINE_CONFIG_END //************************************************************************** // ROM DEFINITIONS //************************************************************************** ROM_START( popper ) ROM_REGION(0x8000, "maincpu", 0) ROM_LOAD("p1", 0x0000, 0x2000, CRC(56881b70) SHA1(d3ade7a54a6cb8a0babf0d667a6b27f492a739dc)) ROM_LOAD("p2", 0x2000, 0x2000, CRC(a054d9d2) SHA1(fcd86e7247b40cf07ea595a64c104b99b0e93ced)) ROM_LOAD("p3", 0x4000, 0x2000, CRC(6201928a) SHA1(53b571b9f2c0568f10cd974641863c2e00777b46)) ROM_REGION(0x2000, "subcpu", 0) ROM_LOAD("p0", 0x0000, 0x1000, CRC(ef5f7c5b) SHA1(c63a3d9ef2868ad7eaacddec810d62d2e124dc15)) ROM_REGION(0x2000, "tiles", 0) ROM_LOAD("p4", 0x0000, 0x2000, CRC(86203349) SHA1(cce2dd3fa786c2fb3ca80e7b93adf94db3b46b01)) ROM_REGION(0x4000, "sprites", 0) ROM_LOAD("p5", 0x0000, 0x2000, CRC(a21ac194) SHA1(2c0e3df8981a12d383b1c4619a0b95a7c2d176a7)) ROM_LOAD("p6", 0x2000, 0x2000, CRC(d99fa790) SHA1(201271ee4fb812236a38cb5f9070ac29e8186097)) ROM_REGION(0x40, "colors", 0) ROM_LOAD("p.m3", 0x00, 0x20, CRC(713217aa) SHA1(6083c3432bf94c9e983fcc79171529f519c86105)) ROM_LOAD("p.m4", 0x20, 0x20, CRC(384de5c1) SHA1(892c89a01c11671c5708113b4e4c27b84be37ea6)) ROM_END //************************************************************************** // SYSTEM DRIVERS //************************************************************************** // YEAR NAME PARENT MACHINE INPUT CLASS INIT ROTATION COMPANY FULLNAME FLAGS GAME( 1983, popper, 0, popper, popper, popper_state, empty_init, ROT90, "Omori", "Popper", MACHINE_SUPPORTS_SAVE )