// license:BSD-3-Clause // copyright-holders:David Haywood /* Twins Electronic Devices, 1994 PCB Layout ---------- This is a very tiny PCB, only about 6 inches square. |-----------------------| | 6116 16MHz 62256| |TEST 6116 24C02 | | PAL 62256| |J 62256 |--------| | |A |TPC1020 | 2 | |M 62256 |AFN-084C| | |M | | 1 | |A |--------| | | AY3-8910 | | D70116| |-----------------------| Notes: V30 clock : 8.000MHz (16/2) AY3-8910 clock : 2.000MHz (16/8) VSync : 50Hz seems a similar board to Hot Blocks same TPC1020 AFN-084C chip same 24c02 eeprom V30 instead of I8088 AY3-8910 instead of YM2149 (compatible) video is not banked in this case instead palette data is sent to the ports strange palette format. todo: hook up eeprom (doesn't seem to work when hooked up??) Twins set 1 takes a long time to boot (eeprom?) Improve blitter / clear logic for Spider. Electronic Devices was printed on rom labels 1994 date string is in ROM Spider PCB appears almost identical but uses additional 'blitter' features. It is possible the Twins PCB has them too and doesn't use them. Twins (set 2) is significantly changed hardware, uses a regular RAMDAC hookup for plaette etc. To access Service Mode in Spider you must boot with P1 Left and P1 Right held down, this requires the -joystick_contradictory switch on the commandline. */ #include "emu.h" #include "cpu/nec/nec.h" #include "sound/ay8910.h" #include "machine/i2cmem.h" #include "video/ramdac.h" #include "screen.h" #include "speaker.h" class twins_state : public driver_device { public: twins_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_maincpu(*this, "maincpu") , m_paletteram(*this, "paletteram") , m_palette(*this, "palette") , m_i2cmem(*this, "i2cmem") , m_spritesinit(0) , m_videorambank(0) { } required_device m_maincpu; optional_shared_ptr m_paletteram; required_device m_palette; optional_device m_i2cmem; uint16_t m_paloff; DECLARE_READ16_MEMBER(twins_port4_r); DECLARE_WRITE16_MEMBER(twins_port4_w); DECLARE_WRITE16_MEMBER(twins_pal_w); DECLARE_WRITE16_MEMBER(spider_pal_w); DECLARE_WRITE16_MEMBER(porte_paloff0_w); DECLARE_WRITE16_MEMBER(spider_paloff0_w); DECLARE_WRITE16_MEMBER(spider_blitter_w); DECLARE_READ16_MEMBER(spider_blitter_r); DECLARE_READ16_MEMBER(spider_port_18_r); DECLARE_READ16_MEMBER(spider_port_1e_r); DECLARE_WRITE16_MEMBER(spider_port_1a_w); DECLARE_WRITE16_MEMBER(spider_port_1c_w); int m_spritesinit; int m_spriteswidth; int m_spritesaddr; uint16_t m_mainram[0x10000 / 2]; uint16_t m_videoram[0x10000 / 2]; uint16_t m_videoram2[0x10000 / 2]; uint16_t m_videorambank; uint32_t screen_update_twins(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); uint32_t screen_update_spider(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); virtual void machine_start() override; virtual void video_start() override; uint16_t* m_rom16; uint8_t* m_rom8; void spider(machine_config &config); void twins(machine_config &config); void twinsa(machine_config &config); void ramdac_map(address_map &map); void spider_io(address_map &map); void twins_io(address_map &map); void twins_map(address_map &map); void twinsa_io(address_map &map); }; void twins_state::machine_start() { m_rom16 = (uint16_t*)memregion("maincpu")->base(); m_rom8 = memregion("maincpu")->base(); } /* port 4 is eeprom */ READ16_MEMBER(twins_state::twins_port4_r) { // doesn't work?? // printf("%08x: twins_port4_r %04x\n", m_maincpu->pc(), mem_mask); // return m_i2cmem->read_sda();// | 0xfffe; return 0x0001; } WRITE16_MEMBER(twins_state::twins_port4_w) { // printf("%08x: twins_port4_w %04x %04x\n", m_maincpu->pc(), data, mem_mask); int i2c_clk = BIT(data, 1); int i2c_mem = BIT(data, 0); m_i2cmem->write_scl(i2c_clk); m_i2cmem->write_sda(i2c_mem); } WRITE16_MEMBER(twins_state::twins_pal_w) { COMBINE_DATA(&m_paletteram[m_paloff]); { int dat,r,g,b; dat = m_paletteram[m_paloff]; r = dat & 0x1f; r = bitswap<8>(r,7,6,5,0,1,2,3,4); g = (dat>>5) & 0x1f; g = bitswap<8>(g,7,6,5,0,1,2,3,4); b = (dat>>10) & 0x1f; b = bitswap<8>(b,7,6,5,0,1,2,3,4); m_palette->set_pen_color(m_paloff, pal5bit(r),pal5bit(g),pal5bit(b)); } m_paloff = (m_paloff + 1) & 0xff; } /* ??? weird ..*/ WRITE16_MEMBER(twins_state::porte_paloff0_w) { // printf("porte_paloff0_w %04x\n", data); m_paloff = 0; } READ16_MEMBER(twins_state::spider_blitter_r) { uint16_t* vram; if (m_videorambank & 1) vram = m_videoram2; else vram = m_videoram; if (offset < 0x10000 / 2) { return m_mainram[offset&0x7fff]; } else if (offset < 0x20000 / 2) { return vram[offset&0x7fff]; } else { uint16_t *src = m_rom16; return src[offset]; } } WRITE16_MEMBER(twins_state::spider_blitter_w) { // this is very strange, we use the offset (address bits) not data bits to set values.. // I get the impression this might actually overlay the entire address range, including RAM and regular VRAM? uint16_t* vram; if (m_videorambank & 1) vram = m_videoram2; else vram = m_videoram; if (m_spritesinit == 1) { // printf("spider_blitter_w %08x %04x %04x (init?) (base?)\n", offset * 2, data, mem_mask); m_spritesinit = 2; m_spritesaddr = offset; } else if (m_spritesinit == 2) { // printf("spider_blitter_w %08x %04x %04x (init2) (width?)\n", offset * 2, data, mem_mask); m_spriteswidth = offset & 0xff; if (m_spriteswidth == 0) m_spriteswidth = 80; m_spritesinit = 0; } else { if (offset < 0x10000 / 2) { COMBINE_DATA(&m_mainram[offset&0x7fff]); } else if (offset < 0x20000 / 2) { COMBINE_DATA(&vram[offset&0x7fff]); } else if (offset < 0x30000 / 2) { uint8_t *src = m_rom8; // printf("spider_blitter_w %08x %04x %04x (previous data width %d address %08x)\n", offset * 2, data, mem_mask, m_spriteswidth, m_spritesaddr); offset &= 0x7fff; for (int i = 0; i < m_spriteswidth; i++) { uint8_t data; data = (src[(m_spritesaddr * 2) + 1]); if (data) vram[offset] = (vram[offset] & 0x00ff) | data << 8; data = src[(m_spritesaddr*2)]; if (data) vram[offset] = (vram[offset] & 0xff00) | data; m_spritesaddr ++; offset++; offset &= 0x7fff; } } else { printf("spider_blitter_w unhandled RAM access %08x %04x %04x", offset * 2, data, mem_mask); } } } void twins_state::twins_map(address_map &map) { map(0x00000, 0xfffff).rw(this, FUNC(twins_state::spider_blitter_r), FUNC(twins_state::spider_blitter_w)); } void twins_state::twins_io(address_map &map) { map(0x0000, 0x0003).w("aysnd", FUNC(ay8910_device::address_data_w)).umask16(0x00ff); map(0x0002, 0x0002).r("aysnd", FUNC(ay8910_device::data_r)); map(0x0004, 0x0005).rw(this, FUNC(twins_state::twins_port4_r), FUNC(twins_state::twins_port4_w)); map(0x0006, 0x0007).w(this, FUNC(twins_state::twins_pal_w)).share("paletteram"); map(0x000e, 0x000f).w(this, FUNC(twins_state::porte_paloff0_w)); } void twins_state::video_start() { m_paloff = 0; save_item(NAME(m_paloff)); save_item(NAME(m_spritesinit)); save_item(NAME(m_spriteswidth)); save_item(NAME(m_spritesaddr)); save_item(NAME(m_mainram)); save_item(NAME(m_videoram)); save_item(NAME(m_videoram2)); save_item(NAME(m_videorambank)); } uint32_t twins_state::screen_update_twins(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { int y,x,count; static const int xxx=320,yyy=204; bitmap.fill(m_palette->black_pen()); count=0; uint8_t *videoram = (uint8_t*)m_videoram; for (y=0;yblack_pen()); count=0; uint8_t *videoram = (uint8_t*)m_videoram; for (y=0;y>5) & 0x1f; b = (dat>>10) & 0x1f; m_palette->set_pen_color(m_paloff-1, pal5bit(r),pal5bit(g),pal5bit(b)); } else { // printf("first palette write %04x\n", data); } m_paloff++; if (m_paloff == 0x101) m_paloff = 0; } WRITE16_MEMBER(twins_state::spider_paloff0_w) { // this seems to be video ram banking COMBINE_DATA(&m_videorambank); } WRITE16_MEMBER(twins_state::spider_port_1a_w) { // writes 1 } WRITE16_MEMBER(twins_state::spider_port_1c_w) { // done before the 'sprite' read / writes // might clear a buffer? // game is only animating sprites at 30fps, maybe there's some double buffering too? // data written is always 00, only seems to want the upper layer to be cleared // otherwise you get garbage sprites between rounds and the bg incorrectly wiped uint16_t* vram; // if (m_videorambank & 1) vram = m_videoram2; // else // vram = m_videoram; for (int i = 0; i < 0x8000; i++) { vram[i] = 0x0000; } } READ16_MEMBER(twins_state::spider_port_18_r) { // read before each blitter command // seems to put the bus in a state where the next 2 bus access offsets (anywhere) are the blitter params m_spritesinit = 1; return 0xff; } READ16_MEMBER(twins_state::spider_port_1e_r) { // done before each sprite pixel 'write' // the data read is the data written, but only reads one pixel?? return 0xff; } void twins_state::spider_io(address_map &map) { map(0x0000, 0x0003).w("aysnd", FUNC(ay8910_device::address_data_w)).umask16(0x00ff); map(0x0002, 0x0002).r("aysnd", FUNC(ay8910_device::data_r)); map(0x0004, 0x0005).rw(this, FUNC(twins_state::twins_port4_r), FUNC(twins_state::twins_port4_w)); map(0x0008, 0x0009).w(this, FUNC(twins_state::spider_pal_w)).share("paletteram"); map(0x0010, 0x0011).w(this, FUNC(twins_state::spider_paloff0_w)); map(0x0018, 0x0019).r(this, FUNC(twins_state::spider_port_18_r)); map(0x001a, 0x001b).w(this, FUNC(twins_state::spider_port_1a_w)); map(0x001c, 0x001d).w(this, FUNC(twins_state::spider_port_1c_w)); map(0x001e, 0x001f).r(this, FUNC(twins_state::spider_port_1e_r)); } MACHINE_CONFIG_START(twins_state::spider) /* basic machine hardware */ MCFG_DEVICE_ADD("maincpu", V30, 8000000) MCFG_DEVICE_PROGRAM_MAP(twins_map) MCFG_DEVICE_IO_MAP(spider_io) /* video hardware */ MCFG_SCREEN_ADD("screen", RASTER) MCFG_SCREEN_RAW_PARAMS(8000000, 512, 0, 320, 312, 0, 200) // 15.625 kHz horizontal??? MCFG_SCREEN_UPDATE_DRIVER(twins_state, screen_update_spider) MCFG_SCREEN_PALETTE("palette") MCFG_SCREEN_VBLANK_CALLBACK(INPUTLINE("maincpu", INPUT_LINE_NMI)) MCFG_PALETTE_ADD("palette", 0x100) MCFG_24C02_ADD("i2cmem") /* sound hardware */ SPEAKER(config, "mono").front_center(); MCFG_DEVICE_ADD("aysnd", AY8910, 2000000) MCFG_AY8910_PORT_A_READ_CB(IOPORT("P1")) MCFG_AY8910_PORT_B_READ_CB(IOPORT("P2")) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0) MACHINE_CONFIG_END ROM_START( twins ) ROM_REGION( 0x100000, "maincpu", 0 ) ROM_LOAD16_BYTE( "1.bin", 0x000000, 0x080000, CRC(d5ef7b0d) SHA1(7261dca5bb0aef755b4f2b85a159b356e7ac8219) ) ROM_LOAD16_BYTE( "2.bin", 0x000001, 0x080000, CRC(8a5392f4) SHA1(e6a2ecdb775138a87d27aa4ad267bdec33c26baa) ) ROM_END /* Shang Hay Twins Electronic Devices 1x nec9328n8-v30-d70116c-8 (main) 2x ay-3-8910a (sound) 1x blank (z80?) 1x oscillator 8.000 2x M27c4001 1x jamma edge connector 1x trimmer (volume) hmm, we're only emulating 1x ay-3-8910, is the other at port 0 on this? */ ROM_START( twinsa ) ROM_REGION( 0x100000, "maincpu", 0 ) ROM_LOAD16_BYTE( "lp.bin", 0x000000, 0x080000, CRC(4f07862e) SHA1(fbda1973f79c6938c7f026a4db706e78781c2df8) ) ROM_LOAD16_BYTE( "hp.bin", 0x000001, 0x080000, CRC(aaf74b83) SHA1(09bd76b9fc5cb7ba6ffe1a2581ffd5633fe440b3) ) ROM_END ROM_START( spider ) ROM_REGION( 0x100000, "maincpu", 0 ) ROM_LOAD16_BYTE( "20.bin", 0x000001, 0x080000, CRC(25e15f11) SHA1(b728f35c817f60a294e38d66559da8977b94a1f5) ) ROM_LOAD16_BYTE( "21.bin", 0x000000, 0x080000, CRC(ff224206) SHA1(d8d45850983542e811facc917d016841fc56a97f) ) ROM_END GAME( 1994, twins, 0, twins, twins, twins_state, empty_init, ROT0, "Electronic Devices", "Twins (set 1)", MACHINE_SUPPORTS_SAVE ) GAME( 1994, twinsa, twins, twinsa, twins, twins_state, empty_init, ROT0, "Electronic Devices", "Twins (set 2)", MACHINE_SUPPORTS_SAVE ) GAME( 1994, spider, 0, spider, twins, twins_state, empty_init, ROT0, "Buena Vision", "Spider", MACHINE_IMPERFECT_GRAPHICS | MACHINE_SUPPORTS_SAVE )