zx: Start of an hardware-correct rewrite [O. Galibert]

Missing:
- hsync is not handled as it should yet, especially missing on the zx80
- the versions with an external charrom are not handled yet
- the zx97 is weird

On the other hand, the zx81 ain't bad and the hires modes work.
This commit is contained in:
Olivier Galibert 2015-10-28 09:54:26 +01:00
parent 47f56372ee
commit c4a787e527
6 changed files with 324 additions and 498 deletions

View File

@ -473,7 +473,11 @@ inline UINT8 z80_device::rop()
{
unsigned pc = PCD;
PC++;
return m_decrypted_opcodes_direct->read_byte(pc);
UINT8 res = m_decrypted_opcodes_direct->read_byte(pc);
m_icount -= 2;
m_refresh_cb((m_i << 8) | (m_r2 & 0x80) | ((m_r-1) & 0x7f));
m_icount += 2;
return res;
}
/****************************************************************
@ -3141,6 +3145,9 @@ void z80_device::take_interrupt()
else
irq_vector = m_irq_callback(*this, 0);
/* Say hi */
m_irqack_cb(true);
LOG(("Z80 '%s' single int. irq_vector $%02x\n", tag(), irq_vector));
/* Interrupt mode 2. Call [i:databyte] */
@ -3429,6 +3436,9 @@ void z80_device::device_start()
m_cc_xy = cc_xy;
m_cc_xycb = cc_xycb;
m_cc_ex = cc_ex;
m_irqack_cb.resolve_safe();
m_refresh_cb.resolve_safe();
}
void nsc800_device::device_start()
@ -3705,7 +3715,9 @@ z80_device::z80_device(const machine_config &mconfig, const char *tag, device_t
cpu_device(mconfig, Z80, "Z80", tag, owner, clock, "z80", __FILE__),
m_program_config("program", ENDIANNESS_LITTLE, 8, 16, 0),
m_decrypted_opcodes_config("decrypted_opcodes", ENDIANNESS_LITTLE, 8, 16, 0),
m_io_config("io", ENDIANNESS_LITTLE, 8, 16, 0)
m_io_config("io", ENDIANNESS_LITTLE, 8, 16, 0),
m_irqack_cb(*this),
m_refresh_cb(*this)
{
}
@ -3713,7 +3725,9 @@ z80_device::z80_device(const machine_config &mconfig, device_type type, const ch
cpu_device(mconfig, type, name, tag, owner, clock, shortname, source),
m_program_config("program", ENDIANNESS_LITTLE, 8, 16, 0),
m_decrypted_opcodes_config("decrypted_opcodes", ENDIANNESS_LITTLE, 8, 16, 0),
m_io_config("io", ENDIANNESS_LITTLE, 8, 16, 0)
m_io_config("io", ENDIANNESS_LITTLE, 8, 16, 0),
m_irqack_cb(*this),
m_refresh_cb(*this)
{
}

View File

@ -7,6 +7,12 @@
#include "z80daisy.h"
#define MCFG_Z80_SET_IRQACK_CALLBACK(_devcb) \
devcb = &z80_device::set_irqack_cb(*device, DEVCB_##_devcb);
#define MCFG_Z80_SET_REFRESH_CALLBACK(_devcb) \
devcb = &z80_device::set_refresh_cb(*device, DEVCB_##_devcb);
enum
{
NSC800_RSTA = INPUT_LINE_IRQ0 + 1,
@ -38,6 +44,8 @@ public:
DECLARE_WRITE_LINE_MEMBER( irq_line );
void z80_set_cycle_tables(const UINT8 *op, const UINT8 *cb, const UINT8 *ed, const UINT8 *xy, const UINT8 *xycb, const UINT8 *ex);
template<class _Object> static devcb_base &set_irqack_cb(device_t &device, _Object object) { return downcast<z80_device &>(device).m_irqack_cb.set_callback(object); }
template<class _Object> static devcb_base &set_refresh_cb(device_t &device, _Object object) { return downcast<z80_device &>(device).m_refresh_cb.set_callback(object); }
protected:
z80_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source);
@ -240,6 +248,8 @@ protected:
address_space *m_io;
direct_read_data *m_direct;
direct_read_data *m_decrypted_opcodes_direct;
devcb_write_line m_irqack_cb;
devcb_write16 m_refresh_cb;
PAIR m_prvpc;
PAIR m_pc;

View File

@ -1,5 +1,5 @@
// license:GPL-2.0+
// copyright-holders:Juergen Buchmueller, Krzysztof Strzecha, Robbbert
// copyright-holders: Olivier Galibert, Juergen Buchmueller, Krzysztof Strzecha, Robbbert
/***************************************************************************
zx.c
@ -9,7 +9,7 @@
Fixes and additions by Krzysztof Strzecha:
07.06.2004 Tape loading added. Some cleanups of debug code.
Fixed stupid bug in timings (vblank duration).
MACHINE_NOT_WORKING flag removed.
GAME_NOT_WORKING flag removed.
29.05.2004 CPU clock, number of scanlines, vblank duration corrected.
Some cleanups. Two non-working TESTDRIVERS added.
14.05.2004 Finally fixed and readded.
@ -31,8 +31,8 @@
- Modernised.
To do / problems:
- Halt-on-nmi emulation needs a cycle-exact z80
- Some memory areas are not mirrored as they should.
- Video hardware is not fully emulated, so it does not support pseudo hi-res and hi-res modes.
- The screen in pc8300/pow3000/lambda jumps when you type something.
- lambda/pow3000 32k memory pack is unemulated, because where is the upper 16k mirror going to be?
- h4th and tree4th need their address maps worked out (eg, the stack is set to FB80)
@ -47,8 +47,18 @@
/* Memory Maps */
static ADDRESS_MAP_START( zx80_map, AS_PROGRAM, 8, zx_state )
AM_RANGE(0x0000, 0x0fff) AM_ROM AM_MIRROR(0x3000)
AM_RANGE(0x4000, 0xffff) AM_RAM
ADDRESS_MAP_END
static ADDRESS_MAP_START( zx81_map, AS_PROGRAM, 8, zx_state )
AM_RANGE(0x0000, 0x1fff) AM_ROM AM_MIRROR(0x2000)
AM_RANGE(0xc000, 0xffff) AM_RAM_READ(zx_ram_r)
AM_RANGE(0x4000, 0xffff) AM_RAM
ADDRESS_MAP_END
static ADDRESS_MAP_START( ula_map, AS_DECRYPTED_OPCODES, 8, zx_state )
AM_RANGE(0x0000, 0x7fff) AM_READ(ula_low_r)
AM_RANGE(0x8000, 0xffff) AM_READ(ula_high_r)
ADDRESS_MAP_END
static ADDRESS_MAP_START( zx80_io_map, AS_IO, 8, zx_state )
@ -291,38 +301,6 @@ static INPUT_PORTS_START( pow3000 )
INPUT_PORTS_END
/* F4 character display */
static const gfx_layout zx_gfx_layout =
{
8, 8, /* 8x8 pixels */
64, /* 64 codes */
1, /* 1 bit per pixel */
{0}, /* no bitplanes */
/* x offsets */
{0, 1, 2, 3, 4, 5, 6, 7},
/* y offsets */
{0 * 8, 1 * 8, 2 * 8, 3 * 8, 4 * 8, 5 * 8, 6 * 8, 7 * 8},
8 * 8 /* eight bytes per code */
};
/* Graphics Decode Information */
static GFXDECODE_START( zx80 )
GFXDECODE_ENTRY( "maincpu", 0x0e00, zx_gfx_layout, 0, 2 )
GFXDECODE_END
static GFXDECODE_START( zx81 )
GFXDECODE_ENTRY( "maincpu", 0x1e00, zx_gfx_layout, 0, 2 )
GFXDECODE_END
static GFXDECODE_START( pc8300 )
GFXDECODE_ENTRY( "gfx1", 0, zx_gfx_layout, 0, 2 )
GFXDECODE_END
/* Palette Initialization */
@ -330,61 +308,34 @@ PALETTE_INIT_MEMBER(zx_state, zx)
{
palette.set_pen_color(0,rgb_t::white); /* white */
palette.set_pen_color(1,rgb_t::black); /* black */
palette.set_pen_color(2,rgb_t::black); /* black */
palette.set_pen_color(3,rgb_t::white); /* white */
}
PALETTE_INIT_MEMBER(zx_state,ts1000)
{
palette.set_pen_color(0,rgb_t(64, 244, 244)); /* cyan */
palette.set_pen_color(1,rgb_t::black); /* black */
palette.set_pen_color(2,rgb_t::black); /* black */
palette.set_pen_color(3,rgb_t(64, 244, 244)); /* cyan */
}
#define ZX81_CPU_CLOCK 3250000
#define ZX81_CYCLES_PER_SCANLINE 207
#define ZX81_PIXELS_PER_SCANLINE 256
#define ZX81_CYCLES_PER_VBLANK 1235
#define ZX81_VBLANK_DURATION (1.0*ZX81_CYCLES_PER_VBLANK/ZX81_CPU_CLOCK*1000*1000)
#define ZX81_PAL_SCANLINES 304
#define ZX81_PAL_FRAMES_PER_SECOND (1.0*ZX81_CPU_CLOCK/(ZX81_PAL_SCANLINES*ZX81_CYCLES_PER_SCANLINE+ZX81_CYCLES_PER_VBLANK))
#define ZX81_NTSC_SCANLINES 256
#define ZX81_NTSC_FRAMES_PER_SECOND (1.0*ZX81_CPU_CLOCK/(ZX81_NTSC_SCANLINES*ZX81_CYCLES_PER_SCANLINE+ZX81_CYCLES_PER_VBLANK))
/* Machine Drivers */
static MACHINE_CONFIG_START( zx80, zx_state )
/* basic machine hardware */
MCFG_CPU_ADD("maincpu", Z80, ZX81_CPU_CLOCK)
MCFG_CPU_ADD("maincpu", Z80, XTAL_6_5MHz/2)
MCFG_CPU_PROGRAM_MAP(zx80_map)
MCFG_CPU_IO_MAP(zx80_io_map)
MCFG_SCREEN_ADD("screen", RASTER)
MCFG_SCREEN_REFRESH_RATE(ZX81_PAL_FRAMES_PER_SECOND)
MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(ZX81_VBLANK_DURATION))
MCFG_CPU_DECRYPTED_OPCODES_MAP(ula_map)
MCFG_Z80_SET_REFRESH_CALLBACK(WRITE16(zx_state, refresh_w))
MCFG_SCREEN_ADD("screen", RASTER)
MCFG_SCREEN_REFRESH_RATE(XTAL_6_5MHz/2/64159.0) // 54223 for NTSC
/* video hardware */
MCFG_SCREEN_UPDATE_DRIVER(zx_state, screen_update)
MCFG_SCREEN_SIZE(ZX81_PIXELS_PER_SCANLINE, ZX81_PAL_SCANLINES)
MCFG_SCREEN_VISIBLE_AREA(0, ZX81_PIXELS_PER_SCANLINE-1, 0, ZX81_PAL_SCANLINES-1)
MCFG_SCREEN_VBLANK_DRIVER(zx_state, screen_eof_zx)
MCFG_SCREEN_SIZE(384, 311)
MCFG_SCREEN_VISIBLE_AREA(0, 383, 0, 310)
MCFG_SCREEN_PALETTE("palette")
MCFG_GFXDECODE_ADD("gfxdecode", "palette", zx80)
MCFG_PALETTE_ADD("palette", 4)
MCFG_PALETTE_ADD("palette", 2)
MCFG_PALETTE_INIT_OWNER(zx_state,zx)
/* sound hardware */
MCFG_SPEAKER_STANDARD_MONO("mono")
MCFG_SOUND_ADD("speaker", SPEAKER_SOUND, 0) /* Used by pc8300/lambda/pow3000 */
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.75)
MCFG_SOUND_WAVE_ADD(WAVE_TAG, "cassette")
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.25)
MCFG_CASSETTE_ADD( "cassette" )
MCFG_CASSETTE_FORMATS(zx80_o_format)
MCFG_CASSETTE_DEFAULT_STATE(CASSETTE_STOPPED | CASSETTE_SPEAKER_ENABLED)
@ -392,60 +343,52 @@ static MACHINE_CONFIG_START( zx80, zx_state )
/* internal ram */
MCFG_RAM_ADD(RAM_TAG)
MCFG_RAM_DEFAULT_SIZE("1K")
MCFG_RAM_EXTRA_OPTIONS("16K")
MCFG_RAM_EXTRA_OPTIONS("16K,32K,48K")
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( zx81, zx80 )
MCFG_CPU_MODIFY("maincpu")
MCFG_CPU_PROGRAM_MAP(zx81_map)
MCFG_CPU_IO_MAP(zx81_io_map)
MCFG_GFXDECODE_MODIFY("gfxdecode", zx81)
MCFG_CASSETTE_MODIFY( "cassette" )
MCFG_CASSETTE_FORMATS(zx81_p_format)
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( zx81_spk, zx81 )
/* sound hardware */
/* Used by pc8300/lambda/pow3000 */
MCFG_SPEAKER_STANDARD_MONO("mono")
MCFG_SOUND_ADD("speaker", SPEAKER_SOUND, 0)
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.75)
MCFG_SOUND_WAVE_ADD(WAVE_TAG, "cassette")
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.25)
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( ts1000, zx81 )
MCFG_PALETTE_MODIFY("palette")
MCFG_PALETTE_INIT_OWNER(zx_state,ts1000)
MCFG_PALETTE_INIT_OWNER(zx_state, ts1000)
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( ts1500, ts1000 )
/* internal ram */
MCFG_RAM_MODIFY(RAM_TAG)
MCFG_RAM_DEFAULT_SIZE("16K")
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( pc8300, zx81 )
static MACHINE_CONFIG_DERIVED( pc8300, zx81_spk )
MCFG_CPU_MODIFY("maincpu")
MCFG_CPU_IO_MAP(pc8300_io_map)
MCFG_MACHINE_RESET_OVERRIDE(zx_state,pc8300)
MCFG_SCREEN_MODIFY("screen")
MCFG_SCREEN_REFRESH_RATE(ZX81_NTSC_FRAMES_PER_SECOND)
MCFG_SCREEN_SIZE(ZX81_PIXELS_PER_SCANLINE, ZX81_NTSC_SCANLINES)
MCFG_SCREEN_VISIBLE_AREA(0, ZX81_PIXELS_PER_SCANLINE-1, 0, ZX81_NTSC_SCANLINES-1)
MCFG_GFXDECODE_MODIFY("gfxdecode", pc8300)
/* internal ram */
MCFG_RAM_MODIFY(RAM_TAG)
MCFG_RAM_DEFAULT_SIZE("16K")
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED( pow3000, zx81 )
static MACHINE_CONFIG_DERIVED( pow3000, zx81_spk )
MCFG_CPU_MODIFY("maincpu")
MCFG_CPU_IO_MAP(pow3000_io_map)
MCFG_MACHINE_RESET_OVERRIDE(zx_state,pow3000)
MCFG_GFXDECODE_MODIFY("gfxdecode", pc8300)
/* internal ram */
MCFG_RAM_MODIFY(RAM_TAG)
MCFG_RAM_DEFAULT_SIZE("2K")
@ -456,7 +399,7 @@ MACHINE_CONFIG_END
/* ROMs */
ROM_START(zx80)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x1000, "maincpu",0 )
ROM_SYSTEM_BIOS(0, "default", "BASIC")
ROMX_LOAD( "zx80.rom", 0x0000, 0x1000, CRC(4c7fc597) SHA1(b6769a3197c77009e0933e038c15b43cf4c98c7a), ROM_BIOS(1) )
ROM_SYSTEM_BIOS(1, "aszmic", "ASZMIC")
@ -464,7 +407,7 @@ ROM_START(zx80)
ROM_END
ROM_START(zx81)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_SYSTEM_BIOS(0, "3rd", "3rd rev.")
ROMX_LOAD( "zx81b.rom", 0x0000, 0x2000, CRC(522c37b8) SHA1(c6d8e06cb936989f6e1cc7a56d1f092da854a515), ROM_BIOS(1) )
ROM_SYSTEM_BIOS(1, "1st", "1st rev.")
@ -478,22 +421,22 @@ ROM_START(zx81)
ROM_END
ROM_START(ts1000)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_LOAD( "zx81a.rom", 0x0000, 0x2000, CRC(4b1dd6eb) SHA1(7b143ee964e9ada89d1f9e88f0bd48d919184cfc) )
ROM_END
ROM_START(ts1500)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_LOAD( "d2364c_649.u2", 0x0000, 0x2000, CRC(7dd19c48) SHA1(3eb437359221b4406d236085ec66fa02278e7495) )
ROM_END
ROM_START(ringo470)
ROM_REGION( 0x10000, "maincpu", ROMREGION_ERASEFF )
ROM_REGION( 0x2000, "maincpu", ROMREGION_ERASEFF )
ROM_LOAD( "ringo470.rom", 0x0000, 0x2000, CRC(b9c5abec) SHA1(191c4994adfffe4f83b98dc3959dde2724b1dbac) )
ROM_END
ROM_START(pc8300)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_LOAD( "8300_org.rom", 0x0000, 0x2000, CRC(a350f2b1) SHA1(6a9be484556cc27a9cd9d71085d2027c6243333f) )
ROM_REGION( 0x200, "gfx1", 0 )
@ -501,7 +444,7 @@ ROM_START(pc8300)
ROM_END
ROM_START(pow3000)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_LOAD( "pow3000.rom", 0x0000, 0x2000, CRC(8a49b2c3) SHA1(9b22daf2f3a991aa6a358ef95b091654c3ca1bdf) )
ROM_REGION( 0x200, "gfx1", 0 )
@ -509,7 +452,7 @@ ROM_START(pow3000)
ROM_END
ROM_START(lambda)
ROM_REGION( 0x10000, "maincpu",0 )
ROM_REGION( 0x2000, "maincpu",0 )
ROM_LOAD( "lambda.rom", 0x0000, 0x2000, CRC(8a49b2c3) SHA1(9b22daf2f3a991aa6a358ef95b091654c3ca1bdf) )
ROM_REGION( 0x200, "gfx1", 0 )
@ -517,28 +460,26 @@ ROM_START(lambda)
ROM_END
ROM_START( tk85 )
ROM_REGION( 0x10000, "maincpu", 0 )
ROM_REGION( 0x2800, "maincpu", 0 )
ROM_LOAD( "tk85.rom", 0x0000, 0x2800, CRC(8972d756) SHA1(7b961a1733fc047eb682150a32e17bca10a018d2) )
ROM_END
/* This homebrew has 192k of RAM and 32k of ROM via bankswitching. One of the primary bankswitching lines is /M1,
which is not emulated by MAME's z80. */
ROM_START( zx97 )
ROM_REGION( 0x10000, "maincpu", 0 )
ROM_LOAD( "zx97.rom", 0x0000, 0x2000, CRC(5cf49744) SHA1(b2a486efdc7b2bc3dc8e5a441ea5532bfa3207bd) )
ROM_IGNORE( 0x6000 ) /* Unemulated bankswitched part */
ROM_REGION( 0x8000, "maincpu", 0 )
ROM_LOAD( "zx97.rom", 0x0000, 0x8000, CRC(5cf49744) SHA1(b2a486efdc7b2bc3dc8e5a441ea5532bfa3207bd) )
ROM_END
/* Game Drivers */
/* YEAR NAME PARENT COMPAT MACHINE INPUT INIT COMPANY FULLNAME FLAGS */
COMP( 1980, zx80, 0, 0, zx80, zx80, zx_state, zx, "Sinclair Research Ltd", "ZX-80", 0 )
COMP( 1981, zx81, 0, 0, zx81, zx81, zx_state, zx, "Sinclair Research Ltd", "ZX-81", 0 )
COMP( 1982, ts1000, zx81, 0, ts1000, zx81, zx_state, zx, "Timex Sinclair", "Timex Sinclair 1000", 0 )
COMP( 1983, ts1500, zx81, 0, ts1500, zx81, zx_state, zx, "Timex Sinclair", "Timex Sinclair 1500", 0 )
COMP( 1983, tk85, zx81, 0, ts1000, zx81, zx_state, zx, "Microdigital", "TK85", 0 )
COMP( 1983, ringo470, zx81, 0, ts1000, zx81, zx_state, zx, "Ritas do Brasil Ltda", "Ringo 470", 0 )
COMP( 1984, pc8300, zx81, 0, pc8300, pc8300, zx_state, zx, "Your Computer", "PC8300", 0 )
COMP( 1980, zx80, 0, 0, zx80, zx80, zx_state, zx, "Sinclair Research Ltd", "ZX-80", MACHINE_NO_SOUND )
COMP( 1981, zx81, 0, 0, zx81, zx81, zx_state, zx, "Sinclair Research Ltd", "ZX-81", MACHINE_NO_SOUND )
COMP( 1982, ts1000, zx81, 0, ts1000, zx81, zx_state, zx, "Timex Sinclair", "Timex Sinclair 1000", MACHINE_NO_SOUND )
COMP( 1983, ts1500, zx81, 0, ts1500, zx81, zx_state, zx, "Timex Sinclair", "Timex Sinclair 1500", MACHINE_NO_SOUND )
COMP( 1983, tk85, zx81, 0, ts1000, zx81, zx_state, zx, "Microdigital", "TK85", MACHINE_NO_SOUND )
COMP( 1983, ringo470, zx81, 0, ts1000, zx81, zx_state, zx, "Ritas do Brasil Ltda", "Ringo 470", MACHINE_NO_SOUND )
COMP( 1984, pc8300, zx81, 0, pc8300, pc8300, zx_state, zx, "Your Computer", "PC8300", 0 )
COMP( 1983, pow3000, zx81, 0, pow3000, pow3000, zx_state, zx, "Creon Enterprises", "Power 3000", 0 )
COMP( 1982, lambda, zx81, 0, pow3000, pow3000, zx_state, zx, "Lambda Electronics Ltd", "Lambda 8300", 0 )
COMP( 1997, zx97, zx81, 0, zx81, zx81, zx_state, zx, "Wilf Rigter", "ZX97", MACHINE_NOT_WORKING | MACHINE_UNOFFICIAL )
COMP( 1997, zx97, zx81, 0, zx81, zx81, zx_state, zx, "Wilf Rigter", "ZX97", MACHINE_NOT_WORKING | MACHINE_NO_SOUND | MACHINE_UNOFFICIAL )

View File

@ -21,13 +21,6 @@
class zx_state : public driver_device
{
public:
enum
{
TIMER_TAPE_PULSE,
TIMER_ULA_NMI,
TIMER_ULA_IRQ
};
zx_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_maincpu(*this, "maincpu"),
@ -49,49 +42,36 @@ public:
UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
bitmap_ind16 m_bitmap;
DECLARE_READ8_MEMBER(zx_ram_r);
DECLARE_READ8_MEMBER(ula_high_r);
DECLARE_READ8_MEMBER(ula_low_r);
DECLARE_WRITE16_MEMBER(refresh_w);
DECLARE_READ8_MEMBER(zx80_io_r);
DECLARE_READ8_MEMBER(zx81_io_r);
DECLARE_READ8_MEMBER(pc8300_io_r);
DECLARE_READ8_MEMBER(pow3000_io_r);
DECLARE_WRITE8_MEMBER(zx80_io_w);
DECLARE_WRITE8_MEMBER(zx81_io_w);
emu_timer *m_ula_nmi;
int m_ula_irq_active;
int m_ula_frame_vsync;
int m_ula_scanline_count;
UINT8 m_tape_bit;
UINT8 m_speaker_state;
int m_old_x;
int m_old_y;
UINT8 m_old_c;
UINT8 m_charline[32];
UINT8 m_charline_ptr;
int m_offs1;
void zx_ula_bkgnd(UINT8 color);
DECLARE_WRITE8_MEMBER(zx_ram_w);
DECLARE_DIRECT_UPDATE_MEMBER(zx_setdirect);
DECLARE_DIRECT_UPDATE_MEMBER(pc8300_setdirect);
DECLARE_DIRECT_UPDATE_MEMBER(pow3000_setdirect);
DECLARE_DRIVER_INIT(zx);
virtual void machine_reset();
virtual void video_start();
DECLARE_PALETTE_INIT(zx);
DECLARE_PALETTE_INIT(ts1000);
DECLARE_MACHINE_RESET(pc8300);
DECLARE_MACHINE_RESET(pow3000);
void screen_eof_zx(screen_device &screen, bool state);
TIMER_CALLBACK_MEMBER(zx_tape_pulse);
TIMER_CALLBACK_MEMBER(zx_ula_nmi);
TIMER_CALLBACK_MEMBER(zx_ula_irq);
void zx_tape_input();
void zx_ula_hsync();
UINT32 get_ram_size();
protected:
enum
{
TIMER_TAPE_INPUT,
TIMER_ULA_HSYNC
};
required_device<cpu_device> m_maincpu;
required_device<ram_device> m_ram;
required_device<cassette_image_device> m_cassette;
required_device<speaker_sound_device> m_speaker;
optional_device<speaker_sound_device> m_speaker;
required_memory_region m_region_maincpu;
optional_memory_region m_region_gfx1;
required_ioport m_io_row0;
@ -105,8 +85,25 @@ protected:
optional_ioport m_io_config;
required_device<screen_device> m_screen;
void zx_ula_r(int offs, memory_region *region, const UINT8 param);
address_space *m_program;
emu_timer *m_tape_input, *m_ula_hsync;
bool m_vsync_active, m_hsync_active, m_nmi_on, m_nmi_generator_active;
UINT64 m_base_vsync_clock, m_vsync_start_time;
UINT32 m_ypos;
UINT8 m_prev_refresh;
UINT8 m_speaker_state;
bitmap_ind16 *m_bitmap_render, *m_bitmap_buffer;
UINT16 m_ula_char_buffer;
double m_cassette_cur_level;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);
void drop_sync();
void recalc_hsync();
};
#endif /* ZX_H_ */

View File

@ -1,5 +1,5 @@
// license:GPL-2.0+
// copyright-holders:Juergen Buchmueller, Krzysztof Strzecha, Robbbert
// copyright Olivier Galibert, Buchmueller, Krzysztof Strzecha, Robbbert
/***************************************************************************
zx.c
@ -10,102 +10,112 @@
#include "includes/zx.h"
#define video_screen_get_refresh(screen) (((screen_config *)(screen)->inline_config)->refresh)
#define DEBUG_ZX81_PORTS 1
#define DEBUG_ZX81_VSYNC 1
#define LOG_ZX81_IOR(_comment) do { if (DEBUG_ZX81_PORTS) logerror("ZX81 IOR: %04x, Data: %02x, Scanline: %d (%s)\n", offset, data, space.machine().first_screen()->vpos(), _comment); } while (0)
#define LOG_ZX81_IOW(_comment) do { if (DEBUG_ZX81_PORTS) logerror("ZX81 IOW: %04x, Data: %02x, Scanline: %d (%s)\n", offset, data, space.machine().first_screen()->vpos(), _comment); } while (0)
#define LOG_ZX81_VSYNC do { if (DEBUG_ZX81_VSYNC) logerror("VSYNC starts in scanline: %d\n", space.machine().first_screen()->vpos()); } while (0)
WRITE8_MEMBER(zx_state::zx_ram_w)
{
UINT8 *RAM = m_region_maincpu->base();
RAM[offset + 0x4000] = data;
if (data & 0x40)
{
space.write_byte(offset | 0xc000, data);
RAM[offset | 0xc000] = data;
}
else
{
space.write_byte(offset | 0xc000, 0);
RAM[offset | 0xc000] = 0;
}
}
/* I know this looks really pointless... but it has to be here */
READ8_MEMBER( zx_state::zx_ram_r )
{
UINT8 *RAM = m_region_maincpu->base();
return RAM[offset | 0xc000];
}
DRIVER_INIT_MEMBER(zx_state,zx)
{
address_space &space = m_maincpu->space(AS_PROGRAM);
m_program = &m_maincpu->space(AS_PROGRAM);
m_tape_input = timer_alloc(TIMER_TAPE_INPUT);
space.install_read_bank(0x4000, 0x4000 + m_ram->size() - 1, "bank1");
space.install_write_handler(0x4000, 0x4000 + m_ram->size() - 1, write8_delegate(FUNC(zx_state::zx_ram_w),this));
membank("bank1")->set_base(m_region_maincpu->base() + 0x4000);
}
DIRECT_UPDATE_MEMBER(zx_state::zx_setdirect)
{
if (address & 0xc000)
zx_ula_r(address, m_region_maincpu, 0);
return address;
}
DIRECT_UPDATE_MEMBER(zx_state::pc8300_setdirect)
{
if (address & 0xc000)
zx_ula_r(address, m_region_gfx1, 0);
return address;
}
DIRECT_UPDATE_MEMBER(zx_state::pow3000_setdirect)
{
if (address & 0xc000)
zx_ula_r(address, m_region_gfx1, 1);
return address;
if(m_ram->size() == 32768)
m_program->unmap_readwrite(0x8000, 0xbfff);
else if(m_ram->size() == 16384)
m_program->unmap_readwrite(0x8000, 0xffff);
else if(m_ram->size() < 16384)
m_program->unmap_readwrite(0x4000 + m_ram->size(), 0xffff);
}
void zx_state::machine_reset()
{
m_maincpu->space(AS_PROGRAM).set_direct_update_handler(direct_update_delegate(FUNC(zx_state::zx_setdirect), this));
m_tape_bit = 0x80;
m_prev_refresh = 0xff;
m_vsync_active = false;
m_base_vsync_clock = 0;
m_ypos = 0;
m_nmi_on = false;
m_nmi_generator_active = false;
m_cassette_cur_level = 0;
m_tape_input->adjust(attotime::from_hz(44100), 0, attotime::from_hz(44100));
}
MACHINE_RESET_MEMBER(zx_state,pow3000)
void zx_state::zx_tape_input()
{
m_maincpu->space(AS_PROGRAM).set_direct_update_handler(direct_update_delegate(FUNC(zx_state::pow3000_setdirect), this));
m_tape_bit = 0x80;
m_cassette_cur_level = m_cassette->input();
}
MACHINE_RESET_MEMBER(zx_state,pc8300)
void zx_state::drop_sync()
{
m_maincpu->space(AS_PROGRAM).set_direct_update_handler(direct_update_delegate(FUNC(zx_state::pc8300_setdirect), this));
m_tape_bit = 0x80;
}
if (m_vsync_active) {
UINT64 time = m_maincpu->total_cycles();
m_vsync_active = false;
m_cassette->output(-1.0);
TIMER_CALLBACK_MEMBER(zx_state::zx_tape_pulse)
{
m_tape_bit = 0x80;
int xs = 2*((m_vsync_start_time - m_base_vsync_clock) % 207);
int ys = (m_vsync_start_time - m_base_vsync_clock) / 207;
int xe = 2*((time - m_base_vsync_clock) % 207);
int ye = (time - m_base_vsync_clock) / 207;
if(xs >= 384) {
xs = 0;
ys++;
}
if(xe >= 384) {
xe = 0;
ye++;
}
if(ys < 311) {
if(ye > 310) {
ye = 311;
xe = 0;
}
if(ys == ye) {
UINT16 *dest = &m_bitmap_render->pix16(ys, xs);
for(int x = xs; x < xe; x++)
*dest++ = 1;
} else {
UINT16 *dest = &m_bitmap_render->pix16(ys, xs);
for(int x = xs; x < 384; x++)
*dest++ = 1;
for(int y = ys+1; y < ye; y++) {
dest = &m_bitmap_render->pix16(y, 0);
for(int x = 0; x<384; x++)
*dest++ = 1;
}
dest = &m_bitmap_render->pix16(ye, 0);
for(int x = 0; x < xe; x++)
*dest++ = 1;
}
}
// Short is hsync
if(time - m_vsync_start_time > 1000) {
// Ignore too short frame times, they're cassette output
if(time - m_base_vsync_clock > 52000) {
logerror("frame time %d\n", int(time - m_base_vsync_clock));
rectangle rect(0, 383, 0, 310);
copybitmap(*m_bitmap_buffer, *m_bitmap_render, 0, 0, 0, 0, rect);
m_bitmap_render->fill(0);
m_base_vsync_clock = time;
m_ypos = 0;
}
if(m_nmi_on)
m_maincpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
m_nmi_on = m_hsync_active = false;
recalc_hsync();
} else
m_ypos = ((time-m_base_vsync_clock)%207) < 192 ? 0 : -1;
}
}
READ8_MEMBER( zx_state::zx80_io_r )
{
/* port FE = read keyboard, NTSC/PAL diode, and cass bit; turn off HSYNC-generator/cass-out
The upper 8 bits are used to select a keyboard scan line */
/* port FE = read keyboard, NTSC/PAL diode, and cass bit; turn off HSYNC-generator/cass-out
The upper 8 bits are used to select a keyboard scan line */
UINT8 data = 0xff;
UINT8 offs = offset & 0xff;
if (offs == 0xfe)
if (!(offset & 0x01))
{
if ((offset & 0x0100) == 0)
data &= m_io_row0->read();
@ -129,25 +139,13 @@ READ8_MEMBER( zx_state::zx80_io_r )
m_cassette->output(+1.0);
if (m_ula_irq_active)
{
zx_ula_bkgnd(0);
m_ula_irq_active = 0;
if (!m_vsync_active && !m_nmi_generator_active) {
m_vsync_active = true;
m_vsync_start_time = m_maincpu->total_cycles();
}
// else
// {
if ((m_cassette->input() < -0.75) && m_tape_bit)
{
m_tape_bit = 0x00;
timer_set(attotime::from_usec(362), TIMER_TAPE_PULSE);
}
data &= ~m_tape_bit;
// }
if (m_ula_frame_vsync == 3)
{
m_ula_frame_vsync = 2;
}
if(m_cassette_cur_level <= 0)
data &= 0x7f;
}
return data;
@ -160,9 +158,8 @@ READ8_MEMBER( zx_state::zx81_io_r )
The upper 8 bits are used to select a keyboard scan line */
UINT8 data = 0xff;
UINT8 offs = offset & 0xff;
if (offs == 0xfe)
if (!(offset & 0x01))
{
if ((offset & 0x0100) == 0)
data &= m_io_row0->read();
@ -186,25 +183,13 @@ READ8_MEMBER( zx_state::zx81_io_r )
m_cassette->output(+1.0);
if (m_ula_irq_active)
{
zx_ula_bkgnd(0);
m_ula_irq_active = 0;
if (!m_vsync_active && !m_nmi_generator_active) {
m_vsync_active = true;
m_vsync_start_time = m_maincpu->total_cycles();
}
else
{
if ((m_cassette->input() < -0.75) && m_tape_bit)
{
m_tape_bit = 0x00;
timer_set(attotime::from_usec(362), TIMER_TAPE_PULSE);
}
data &= ~m_tape_bit;
}
if (m_ula_frame_vsync == 3)
{
m_ula_frame_vsync = 2;
}
if(m_cassette_cur_level <= 0)
data &= 0x7f;
}
return data;
@ -248,26 +233,8 @@ READ8_MEMBER( zx_state::pc8300_io_r )
data &= m_io_row7->read();
m_cassette->output(+1.0);
if (m_ula_irq_active)
{
zx_ula_bkgnd(0);
m_ula_irq_active = 0;
}
else
{
if ((m_cassette->input() < -0.75) && m_tape_bit)
{
m_tape_bit = 0x00;
timer_set(attotime::from_usec(362), TIMER_TAPE_PULSE);
}
data &= ~m_tape_bit;
}
if (m_ula_frame_vsync == 3)
{
m_ula_frame_vsync = 2;
}
if(m_cassette_cur_level <= 0)
data &= 0x7f;
}
return data;
@ -316,26 +283,8 @@ READ8_MEMBER( zx_state::pow3000_io_r )
data &= m_io_row7->read();
m_cassette->output(+1.0);
if (m_ula_irq_active)
{
zx_ula_bkgnd(0);
m_ula_irq_active = 0;
}
else
{
if ((m_cassette->input() < -0.75) && m_tape_bit)
{
m_tape_bit = 0x00;
timer_set(attotime::from_usec(362), TIMER_TAPE_PULSE);
}
data &= ~m_tape_bit;
}
if (m_ula_frame_vsync == 3)
{
m_ula_frame_vsync = 2;
}
if(m_cassette_cur_level <= 0)
data &= 0x7f;
}
return data;
@ -360,31 +309,20 @@ WRITE8_MEMBER( zx_state::zx81_io_w )
FE = turn on NMI generator
FF = write HSYNC and cass data */
int height = m_screen->height();
UINT8 offs = offset & 0xff;
if (offs == 0xfd)
{
m_ula_nmi->reset();
if (!(offset & 0x01) && !m_nmi_generator_active) {
m_nmi_generator_active = true;
m_nmi_on = m_hsync_active;
m_maincpu->set_input_line(INPUT_LINE_NMI, m_nmi_on ? ASSERT_LINE : CLEAR_LINE);
recalc_hsync();
}
else
if (offs == 0xfe)
{
m_ula_nmi->adjust(attotime::zero, 0, m_maincpu->cycles_to_attotime(207));
/* remove the IRQ */
m_ula_irq_active = 0;
}
else
if (offs == 0xff)
{
m_cassette->output(-1.0);
zx_ula_bkgnd(1);
if (m_ula_frame_vsync == 2)
{
m_maincpu->spin_until_time(m_screen->time_until_pos(height - 1, 0));
m_ula_scanline_count = height - 1;
logerror ("S: %d B: %d\n", m_screen->vpos(), m_screen->hpos());
if (!(offset & 0x02) && m_nmi_generator_active) {
m_nmi_generator_active = false;
if(m_nmi_on) {
m_maincpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
m_nmi_on = false;
}
}
drop_sync();
}

View File

@ -1,5 +1,5 @@
// license:GPL-2.0+
// copyright-holders:Juergen Buchmueller, Krzysztof Strzecha, Robbbert
// copyright-holders: Olivier Galibert, Juergen Buchmueller, Krzysztof Strzecha, Robbbert
/***************************************************************************
zx.c
@ -25,14 +25,11 @@ void zx_state::device_timer(emu_timer &timer, device_timer_id id, int param, voi
{
switch (id)
{
case TIMER_TAPE_PULSE:
zx_tape_pulse(ptr, param);
case TIMER_TAPE_INPUT:
zx_tape_input();
break;
case TIMER_ULA_NMI:
zx_ula_nmi(ptr, param);
break;
case TIMER_ULA_IRQ:
zx_ula_irq(ptr, param);
case TIMER_ULA_HSYNC:
zx_ula_hsync();
break;
default:
assert_always(FALSE, "Unknown id in zx_state::device_timer");
@ -40,184 +37,113 @@ void zx_state::device_timer(emu_timer &timer, device_timer_id id, int param, voi
}
/*
* Toggle the video output between black and white.
* This happens whenever the ULA scanline IRQs are enabled/disabled.
* Normally this is done during the synchronized zx_ula_r() function,
* which outputs 8 pixels per code, but if the video sync is off
* (during tape IO or sound output) zx_ula_bkgnd() is used to
* simulate the display of a ZX80/ZX81.
*/
void zx_state::zx_ula_bkgnd(UINT8 color)
void zx_state::zx_ula_hsync()
{
int width = m_screen->width();
int height = m_screen->height();
const rectangle &visarea = m_screen->visible_area();
if (m_ula_frame_vsync == 0 && color != m_old_c)
{
int y, new_x, new_y;
rectangle r;
bitmap_ind16 &bitmap = m_bitmap;
new_y = machine().first_screen()->vpos();
new_x = machine().first_screen()->hpos();
/* logerror("zx_ula_bkgnd: %3d,%3d - %3d,%3d\n", state->m_old_x, state->m_old_y, new_x, new_y);*/
y = m_old_y;
for (;;)
{
if (y == new_y)
{
r.set(m_old_x, new_x, y, y);
bitmap.fill(color, r);
break;
}
else
{
r.set(m_old_x, visarea.max_x, y, y);
bitmap.fill(color, r);
m_old_x = 0;
}
if (++y == height)
y = 0;
}
m_old_x = (new_x + 1) % width;
m_old_y = new_y;
m_old_c = color;
m_hsync_active = !m_hsync_active;
if(m_hsync_active)
m_ypos++;
if(m_nmi_generator_active) {
m_nmi_on = m_hsync_active;
m_maincpu->set_input_line(INPUT_LINE_NMI, m_nmi_on ? ASSERT_LINE : CLEAR_LINE);
}
recalc_hsync();
}
/*
* PAL: 310 total lines,
* 0.. 55 vblank
* 56..247 192 visible lines
* 248..303 vblank
* 304... vsync
* NTSC: 262 total lines
* 0.. 31 vblank
* 32..223 192 visible lines
* 224..233 vblank
*/
TIMER_CALLBACK_MEMBER(zx_state::zx_ula_nmi)
WRITE16_MEMBER(zx_state::refresh_w)
{
/*
* An NMI is issued on the ZX81 every 64us for the blanked
* scanlines at the top and bottom of the display.
*/
int height = m_screen->height();
const rectangle& r1 = m_screen->visible_area();
rectangle r;
bitmap_ind16 &bitmap = m_bitmap;
r.set(r1.min_x, r1.max_x, m_ula_scanline_count, m_ula_scanline_count);
bitmap.fill(1, r);
// logerror("ULA %3d[%d] NMI, R:$%02X, $%04x\n", machine().first_screen()->vpos(), ula_scancode_count, (unsigned) m_maincpu->state_int(Z80_R), (unsigned) m_maincpu->state_int(Z80_PC));
m_maincpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE);
if (++m_ula_scanline_count == height)
m_ula_scanline_count = 0;
}
TIMER_CALLBACK_MEMBER(zx_state::zx_ula_irq)
{
/*
* An IRQ is issued on the ZX80/81 whenever the R registers
* bit 6 goes low. In MESS this IRQ timed from the first read
* from the copy of the DFILE in the upper 32K in zx_ula_r().
*/
if (m_ula_irq_active)
{
// logerror("ULA %3d[%d] IRQ, R:$%02X, $%04x\n", machine().first_screen()->vpos(), ula_scancode_count, (unsigned) m_maincpu->state_int(Z80_R), (unsigned) m_maincpu->state_int(Z80_PC));
m_ula_irq_active = 0;
m_maincpu->set_input_line(0, HOLD_LINE);
}
}
void zx_state::zx_ula_r(int offs, memory_region *region, const UINT8 param)
{
int offs0 = offs & 0x7fff;
UINT8 *rom = m_region_maincpu->base();
UINT8 chr = rom[offs0];
if ((!m_ula_irq_active) && (chr == 0x76))
{
bitmap_ind16 &bitmap = m_bitmap;
UINT16 y, *scanline;
UINT16 ireg = m_maincpu->state_int(Z80_I) << 8;
UINT8 data, *chrgen, creg;
if (param)
creg = m_maincpu->state_int(Z80_B);
if((data ^ m_prev_refresh) & 0x40)
m_maincpu->set_input_line(INPUT_LINE_IRQ0, data & 0x40 ? CLEAR_LINE : ASSERT_LINE);
m_prev_refresh = data;
if(m_ula_char_buffer != 0xffff) {
UINT64 time = m_maincpu->total_cycles();
int x = 2*((time-m_base_vsync_clock) % 207);
int y = (time-m_base_vsync_clock) / 207;
UINT8 pixels;
if(m_region_gfx1)
pixels = m_region_gfx1->base()[((m_ula_char_buffer & 0x3f) << 3) | (m_ypos & 7)];
else
creg = m_maincpu->state_int(Z80_C);
chrgen = region->base();
if ((++m_ula_scanline_count == m_screen->height()) || (creg == 32))
{
m_ula_scanline_count = 0;
m_offs1 = offs0;
pixels = m_program->read_byte((data & 0xfe00) | ((m_ula_char_buffer & 0x3f) << 3) | (m_ypos & 7));
if(m_ula_char_buffer & 0x80)
pixels = ~pixels;
if(x < 384-8 && y < 311) {
UINT16 *dest = &m_bitmap_render->pix16(y, x);
for(int i=0; i<8; i++)
*dest++ |= pixels & (0x80 >> i) ? 1 : 0;
}
m_ula_frame_vsync = 3;
m_charline_ptr = 0;
for (y = m_offs1+1; ((y < offs0) && (m_charline_ptr < ARRAY_LENGTH(m_charline))); y++)
{
m_charline[m_charline_ptr] = rom[y];
m_charline_ptr++;
}
for (y = m_charline_ptr; y < ARRAY_LENGTH(m_charline); y++)
m_charline[y] = 0;
timer_set(m_maincpu->cycles_to_attotime(((32 - m_charline_ptr) << 2)), TIMER_ULA_IRQ);
m_ula_irq_active++;
scanline = &bitmap.pix16(m_ula_scanline_count);
y = 0;
for (m_charline_ptr = 0; m_charline_ptr < ARRAY_LENGTH(m_charline); m_charline_ptr++)
{
chr = m_charline[m_charline_ptr];
data = chrgen[ireg | ((chr & 0x3f) << 3) | ((8 - creg)&7) ];
if (chr & 0x80) data ^= 0xff;
scanline[y++] = (data >> 7) & 1;
scanline[y++] = (data >> 6) & 1;
scanline[y++] = (data >> 5) & 1;
scanline[y++] = (data >> 4) & 1;
scanline[y++] = (data >> 3) & 1;
scanline[y++] = (data >> 2) & 1;
scanline[y++] = (data >> 1) & 1;
scanline[y++] = (data >> 0) & 1;
m_charline[m_charline_ptr] = 0;
}
if (creg == 1) m_offs1 = offs0;
m_ula_char_buffer = 0xffff;
}
}
void zx_state::recalc_hsync()
{
UINT64 time = machine().time().as_ticks(m_maincpu->clock());
UINT32 step = (time - m_base_vsync_clock) % 207;
UINT32 delta;
if (m_hsync_active)
delta = 207 - step;
else {
if(step < 192)
delta = 192 - step;
else
delta = 399 - step;
}
m_ula_hsync->adjust(m_maincpu->cycles_to_attotime(delta));
}
READ8_MEMBER(zx_state::ula_low_r)
{
UINT8 cdata = m_program->read_byte(offset);
if(space.debugger_access())
return cdata;
if(m_maincpu->state_int(Z80_HALT))
return cdata;
if(m_nmi_on) {
UINT64 time = m_maincpu->total_cycles();
int pos = (time-m_base_vsync_clock) % 207;
if(pos >= 192)
m_maincpu->adjust_icount(pos - 207);
}
return cdata;
}
READ8_MEMBER(zx_state::ula_high_r)
{
UINT8 cdata = m_program->read_byte(offset);
if(space.debugger_access())
return cdata;
if(m_maincpu->state_int(Z80_HALT))
return cdata;
if(m_nmi_on) {
UINT64 time = m_maincpu->total_cycles();
int pos = (time-m_base_vsync_clock) % 207;
if(pos >= 192)
m_maincpu->adjust_icount(pos - 207);
}
if(cdata & 0x40)
return cdata;
m_ula_char_buffer = cdata;
return 0x00; // nop
}
void zx_state::video_start()
{
m_ula_nmi = timer_alloc(TIMER_ULA_NMI);
m_ula_irq_active = 0;
m_screen->register_screen_bitmap(m_bitmap);
}
m_ula_hsync = timer_alloc(TIMER_ULA_HSYNC);
m_ula_char_buffer = 0xffff;
void zx_state::screen_eof_zx(screen_device &screen, bool state)
{
// rising edge
if (state)
{
/* decrement video synchronization counter */
if (m_ula_frame_vsync)
--m_ula_frame_vsync;
}
m_bitmap_render = auto_bitmap_ind16_alloc(machine(), 384, 311);
m_bitmap_buffer = auto_bitmap_ind16_alloc(machine(), 384, 311);
}
UINT32 zx_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
copybitmap(bitmap, *m_bitmap_buffer, 0, 0, 0, 0, cliprect);
return 0;
}