sinclair/chloe.cpp: Chloe 280SE (Timex TS2068 successor)- New WORKING (#12337)

This commit is contained in:
holub 2024-06-06 20:22:08 -04:00 committed by GitHub
parent 1f0791d6d6
commit dc96d365b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1343 additions and 1 deletions

View File

@ -178,6 +178,7 @@ void spi_sdcard_device::latch_in()
m_image->read(m_blknext++, &m_data[1]);
util::crc16_t crc16 = util::crc16_creator::simple(&m_data[1], m_blksize);
put_u16be(&m_data[m_blksize + 1], crc16);
LOG("reading LBA %x: [0] %02x %02x .. [%d] %02x %02x [crc16] %04x\n", m_blknext - 1, m_data[1], m_data[2], m_blksize - 2, m_data[m_blksize - 1], m_data[m_blksize], crc16);
send_data(1 + m_blksize + 2, SD_STATE_DATA_MULTI);
}
}
@ -356,11 +357,11 @@ void spi_sdcard_device::do_command()
{
blk /= m_blksize;
}
LOG("reading LBA %x\n", blk);
m_image->read(blk, &m_data[3]);
{
util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], m_blksize);
put_u16be(&m_data[m_blksize + 3], crc16);
LOG("reading LBA %x: [0] %02x %02x .. [%d] %02x %02x [crc16] %04x\n", blk, m_data[3], m_data[4], m_blksize - 2, m_data[m_blksize + 1], m_data[m_blksize + 2], crc16);
}
send_data(3 + m_blksize + 2, SD_STATE_DATA);
}

View File

@ -41542,6 +41542,9 @@ atm //
atmtb2 //
atmtb2plus //
@source:sinclair/chloe.cpp
chloe // 1999 Chloe Corporation
@source:sinclair/elwro800.cpp
elwro800 //

968
src/mame/sinclair/chloe.cpp Normal file
View File

@ -0,0 +1,968 @@
// license:BSD-3-Clause
// copyright-holders:Andrei I. Holub
/**********************************************************************
Chloe 280SE
**********************************************************************/
#include "emu.h"
#include "screen_ula.h"
#include "spec128.h"
#include "machine/spi_sdcard.h"
#include "sound/ay8910.h"
#include "sound/dac.h"
#include "screen.h"
#include "speaker.h"
#define LOG_IO (1U << 1)
#define LOG_MEM (1U << 2)
#define LOG_WARN (1U << 3)
#define VERBOSE ( /*LOG_GENERAL | LOG_IO | LOG_MEM |*/ LOG_WARN )
#include "logmacro.h"
#define LOGIO(...) LOGMASKED(LOG_IO, __VA_ARGS__)
#define LOGMEM(...) LOGMASKED(LOG_MEM, __VA_ARGS__)
#define LOGWARN(...) LOGMASKED(LOG_WARN, __VA_ARGS__)
namespace {
#define TIMINGS_PERFECT 0
// Must be 800x525 to match VGA. With below puts odd scanlines to the right in order to avoid the same line redrawing.
static const u16 CYCLES_HORIZ = 800 << 1;
static const u16 CYCLES_VERT = 525 >> 1;
static const rectangle SCR_FULL = { 0, 640 - 1, 0, 240 - 1 };
static const rectangle SCR_256x192 = { 64, 64 + (256 << 1) - 1, 24, 24 + 192 - 1 };
class chloe_state : public spectrum_128_state
{
public:
chloe_state(const machine_config &mconfig, device_type type, const char *tag)
: spectrum_128_state(mconfig, type, tag)
, m_bank_ram(*this, "bank_ram%u", 0U)
, m_bank0_view(*this, "bank0_view")
, m_bank1_view(*this, "bank1_view")
, m_regs_map(*this, "regs_map")
, m_palette(*this, "palette")
, m_ula(*this, "ula")
, m_sdcard(*this, "sdcard")
, m_io_line(*this, "IO_LINE%u", 0U)
, m_io_mouse(*this, "mouse_input%u", 1U)
, m_ay(*this, "ay%u", 0U)
, m_covox(*this, "covox")
{}
void chloe(machine_config &config);
INPUT_CHANGED_MEMBER(on_divmmc_nmi);
protected:
static const u8 BASIC48_ROM = 0x01;
virtual void machine_start() override ATTR_COLD;
virtual void machine_reset() override ATTR_COLD;
virtual void video_start() override;
void map_regs(address_map &map);
void map_fetch(address_map &map);
void map_mem(address_map &map);
void map_io(address_map &map);
u8 kbd_fe_r(offs_t offset);
u8 divmmc_neutral_r(offs_t offset);
u8 divmmc_enable_r(offs_t offset);
u8 divmmc_disable_r(offs_t offset);
void dma_reg_w(offs_t offset, u8 data);
void port_7ffd_w(u8 data);
void port_f4_w(u8 data);
void port_ff_w(u8 data);
void port_e3_w(u8 data);
void ay_address_w(u8 data);
u8 spi_data_r();
void spi_data_w(u8 data);
void spi_miso_w(u8 data);
void update_memory();
u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
void raster_irq_adjust();
private:
TIMER_CALLBACK_MEMBER(spi_clock);
TIMER_CALLBACK_MEMBER(raster_irq_on);
TIMER_CALLBACK_MEMBER(raster_irq_off);
INTERRUPT_GEN_MEMBER(chloe_interrupt);
memory_access<8, 0, 0, ENDIANNESS_LITTLE>::specific m_uno_regs;
memory_access<16, 0, 0, ENDIANNESS_LITTLE>::specific m_program;
memory_access<16, 0, 0, ENDIANNESS_LITTLE>::specific m_io;
memory_bank_array_creator<8> m_bank_ram;
memory_view m_bank0_view, m_bank1_view;
required_device<address_map_bank_device> m_regs_map;
required_device<device_palette_interface> m_palette;
required_device<screen_ula_plus_device> m_ula;
required_device<spi_sdcard_sdhc_device> m_sdcard;
required_ioport_array<8> m_io_line;
required_ioport_array<3> m_io_mouse;
required_device_array<ay8912_device, 2> m_ay;
required_device<dac_byte_interface> m_covox;
u8 m_timex_mmu;
u8 m_port_ff_data;
bool m_core_boot;
u8 m_reg_selected;
bool m_divmmc_paged;
u8 m_divmmc_ctrl;
u8 m_uno_regs_data[256];
u8 m_palpen_selected;
u8 m_ay_selected;
bool m_dma_hilo;
u8 m_dma_src_latch;
u8 m_dma_dst_latch;
u8 m_dma_pre_latch;
u8 m_dma_len_latch;
u8 m_dma_prob_latch;
emu_timer *m_irq_raster_on_timer;
emu_timer *m_irq_raster_off_timer;
emu_timer *m_spi_clock;
int m_spi_clock_cycles;
bool m_spi_clock_state;
u8 m_spi_mosi_dat;
u8 m_spi_miso_dat;
};
void chloe_state::update_memory()
{
m_screen->update_now();
m_ula->ula_shadow_en_w(BIT(m_port_7ffd_data, 3));
const bool ext = BIT(m_port_ff_data, 7); // 0 - DOC 7xxxx=28+; 1 - EXT 6xxxx=24+
m_bank0_view.disable();
m_bank1_view.disable();
const u8 divmmc_sram_page = BIT(m_divmmc_ctrl, 0, 6);
const bool divmmc_sram_page_is_valid = BIT(divmmc_sram_page, 4, 2) == 0;
const bool mapram_mode = BIT(m_divmmc_ctrl, 6);
const bool conmem = BIT(m_divmmc_ctrl, 7);
const bool divmmc_rom_active = m_divmmc_paged || conmem;
for (auto i = 0; i < 8; ++i)
{
const bool paged = BIT(m_timex_mmu, i);
u8 pg;
if (i == 0 && (divmmc_rom_active || !paged))
{
if (divmmc_rom_active)
{
pg = (!mapram_mode || conmem) ? 24 : 35;
}
else
{
pg = (8 + BIT(m_port_7ffd_data, 4)) << 1;
}
m_bank0_view.select(0);
m_bank_ram[0]->set_entry(pg);
}
else if (i == 1 && (divmmc_rom_active || !paged))
{
if (divmmc_rom_active)
{
pg = 32 + (divmmc_sram_page & 0x0f);
if (!mapram_mode || conmem)
{
if (!divmmc_sram_page_is_valid)
{
m_bank1_view.select(0);
}
}
else
{
if ((mapram_mode && (divmmc_sram_page == 3)) || !divmmc_sram_page_is_valid)
{
m_bank1_view.select(0);
}
}
}
else
{
pg = ((8 + BIT(m_port_7ffd_data, 4)) << 1) + 1;
m_bank1_view.select(0);
}
m_bank_ram[1]->set_entry(pg);
}
else if (paged)
{
pg = ((28 - 4 * ext) << 1) + i;
m_bank_ram[i]->set_entry(pg);
}
else
{
if (i < 4)
{
pg = 5;
}
else if (i < 6)
{
pg = 2;
}
else
{
pg = m_port_7ffd_data & 0x07;
}
m_bank_ram[i]->set_entry((pg << 1) + (i & 1));
}
}
}
u32 chloe_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
rectangle clip256x192 = SCR_256x192;
clip256x192 &= cliprect;
screen.priority().fill(0, cliprect);
m_ula->draw_border(bitmap, cliprect, m_port_fe_data & 0x07);
const bool flash = u64(screen.frame_number() / m_frame_invert_count) & 1;
m_ula->draw(screen, bitmap, clip256x192, flash, 0);
return 0;
}
void chloe_state::port_7ffd_w(u8 data)
{
if (m_port_7ffd_data & 0x20)
{
return;
}
m_port_7ffd_data = data;
update_memory();
}
void chloe_state::port_ff_w(u8 data)
{
m_ula->port_ff_reg_w(data);
m_port_ff_data = data;
update_memory();
}
void chloe_state::port_f4_w(u8 data)
{
m_timex_mmu = data;
update_memory();
}
void chloe_state::port_e3_w(u8 data)
{
if (m_divmmc_ctrl & 0x40)
{
m_divmmc_ctrl = data | 0x40;
}
else
{
m_divmmc_ctrl = data;
}
update_memory();
}
void chloe_state::ay_address_w(u8 data)
{
if ((data & 0xfe) == 0xfe)
{
m_ay_selected = data & 1;
}
else
{
m_ay[m_ay_selected]->address_w(data);
}
}
u8 chloe_state::spi_data_r()
{
u8 din = m_spi_miso_dat;
if (!machine().side_effects_disabled())
{
spi_data_w(0xff);
}
return din;
}
void chloe_state::spi_data_w(u8 data)
{
m_spi_mosi_dat = data;
#if TIMINGS_PERFECT
m_spi_clock_cycles = 8;
m_spi_clock->adjust(m_maincpu->clocks_to_attotime(1) / 4, 0, m_maincpu->clocks_to_attotime(1) / 4);
#else
for (u8 m = 0x80; m; m >>= 1)
{
m_sdcard->spi_mosi_w(m_spi_mosi_dat & m ? 1 : 0);
m_sdcard->spi_clock_w(CLEAR_LINE);
m_sdcard->spi_clock_w(ASSERT_LINE);
}
#endif
}
void chloe_state::spi_miso_w(u8 data)
{
m_spi_miso_dat <<= 1;
m_spi_miso_dat |= data;
}
TIMER_CALLBACK_MEMBER(chloe_state::spi_clock)
{
if (m_spi_clock_cycles > 0)
{
if (m_spi_clock_state)
{
m_sdcard->spi_clock_w(ASSERT_LINE);
m_spi_clock_cycles--;
}
else
{
m_sdcard->spi_mosi_w(BIT(m_spi_mosi_dat, m_spi_clock_cycles - 1));
m_sdcard->spi_clock_w(CLEAR_LINE);
}
m_spi_clock_state = !m_spi_clock_state;
}
else
{
m_spi_clock_state = false;
m_spi_clock->reset();
}
}
u8 chloe_state::divmmc_neutral_r(offs_t offset)
{
return m_program.read_byte(offset);
}
u8 chloe_state::divmmc_enable_r(offs_t offset)
{
// M1
if (!machine().side_effects_disabled() && !m_divmmc_paged && (offset >= 0x3d00))
{
m_divmmc_paged = 1;
update_memory();
}
const u8 op = m_program.read_byte(offset);
// after M1
if (!machine().side_effects_disabled() && !m_divmmc_paged)
{
m_divmmc_paged = 1;
update_memory();
}
return op;
}
u8 chloe_state::divmmc_disable_r(offs_t offset)
{
if (!machine().side_effects_disabled() && m_divmmc_paged && (offset >= 0x4000))
{
m_divmmc_paged = 0;
update_memory();
}
const u8 op = m_program.read_byte(offset);
if (!machine().side_effects_disabled() && m_divmmc_paged)
{
m_divmmc_paged = 0;
update_memory();
}
return op;
}
void chloe_state::dma_reg_w(offs_t offset, u8 data)
{
// TODO dma stub
m_uno_regs_data[0xa0 + offset] = data;
m_dma_hilo = !m_dma_hilo;
switch (offset + 10 * m_dma_hilo)
{
case 0: case 10: // DMACTRL
// 07;
break;
case 1: // DMASRC
//m_dma->((m_dma_src_latch << 8) | data);
break;
case 11:
m_dma_src_latch = data;
break;
case 2: // DMADST
//m_dma->((m_dma_dst_latch << 8) | data);
break;
case 12:
m_dma_dst_latch = data;
break;
case 3: // DMAPRE
//m_dma->((m_dma_pre_latch << 8) | data);
break;
case 13:
m_dma_pre_latch = data;
break;
case 4: // DMALEN
//m_dma->((m_dma_len_latch << 8) | data);
break;
case 14:
m_dma_len_latch = data;
break;
case 5: // DMAPROB
//m_dma->((m_dma_prob_latch << 8) | data);
break;
case 15:
m_dma_prob_latch = data;
break;
case 6: case 16: // DMASTAT
break;
}
}
void chloe_state::raster_irq_adjust()
{
if (BIT(m_uno_regs_data[0x0d], 1))
{
u16 line = (BIT(m_uno_regs_data[0x0d], 0) << 8) | m_uno_regs_data[0x0c];
m_irq_raster_on_timer->adjust(m_screen->time_until_pos((SCR_256x192.top() + line) % m_screen->height()));
}
else
m_irq_raster_on_timer->reset();
}
INPUT_CHANGED_MEMBER(chloe_state::on_divmmc_nmi)
{
if ((newval & 1) && (~m_io_line[0]->read() & 0x8000))
{
m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero);
}
}
TIMER_CALLBACK_MEMBER(chloe_state::raster_irq_on)
{
m_screen->update_now();
m_maincpu->set_input_line(INPUT_LINE_IRQ0, ASSERT_LINE);
m_irq_raster_off_timer->adjust(m_maincpu->clocks_to_attotime(32));
}
TIMER_CALLBACK_MEMBER(chloe_state::raster_irq_off)
{
m_maincpu->set_input_line(INPUT_LINE_IRQ0, CLEAR_LINE);
raster_irq_adjust();
}
INTERRUPT_GEN_MEMBER(chloe_state::chloe_interrupt)
{
if (BIT(~m_uno_regs_data[0x0d], 2))
{
m_irq_on_timer->adjust(m_screen->time_until_pos(SCR_256x192.top(), SCR_256x192.left())
- attotime::from_ticks(14365, m_maincpu->unscaled_clock())); // TODO confirm
}
}
void chloe_state::map_fetch(address_map &map)
{
map(0x0000, 0x3fff).r(FUNC(chloe_state::divmmc_neutral_r));
map(0x0000, 0x0000).lr8(NAME([this]() { return divmmc_enable_r(0x0000); }));
map(0x0008, 0x0008).lr8(NAME([this]() { return divmmc_enable_r(0x0008); }));
map(0x0038, 0x0038).lr8(NAME([this]() { return divmmc_enable_r(0x0038); }));
map(0x0066, 0x0066).lr8(NAME([this]() { return divmmc_enable_r(0x0066); }));
map(0x04c6, 0x04c6).lr8(NAME([this]() { return divmmc_enable_r(0x04c6); }));
map(0x0562, 0x0562).lr8(NAME([this]() { return divmmc_enable_r(0x0562); }));
map(0x1ff8, 0x1fff).lr8(NAME([this](offs_t offset) { return divmmc_disable_r(0x1ff8 + offset); }));
map(0x3d00, 0x3dff).lr8(NAME([this](offs_t offset) { return divmmc_enable_r(0x3d00 + offset); }));
map(0x4000, 0xffff).lr8(NAME([this](offs_t offset) { return divmmc_disable_r(0x4000 + offset); }));
}
void chloe_state::map_mem(address_map &map)
{
for (auto i = 0; i < 8; i++)
map(0x0000 + i * 0x2000, 0x1fff + i * 0x2000).bankrw(m_bank_ram[i]);
map(0x0000, 0x1fff).view(m_bank0_view);
m_bank0_view[0](0x0000, 0x1fff).nopw();
map(0x2000, 0x3fff).view(m_bank1_view);
m_bank1_view[0](0x2000, 0x3fff).nopw();
}
void chloe_state::map_io(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x0000).select(0xfffe).rw(FUNC(chloe_state::kbd_fe_r), FUNC(chloe_state::spectrum_ula_w));
map(0x00ff, 0x00ff).mirror(0xff00).lr8(NAME([this]() { return m_port_ff_data; })).w(FUNC(chloe_state::port_ff_w));
map(0x7ffd, 0x7ffd).w(FUNC(chloe_state::port_7ffd_w));
map(0x1ffd, 0x1ffd).w(FUNC(chloe_state::port_7ffd_w));
map(0x00f4, 0x00f4).mirror(0xff00).w(FUNC(chloe_state::port_f4_w));
map(0x00e3, 0x00e3).mirror(0xff00).w(FUNC(chloe_state::port_e3_w));
map(0x00e7, 0x00e7).mirror(0xff00).lw8(NAME([this](u8 data) { m_sdcard->spi_ss_w(data & 1); }));
map(0x00eb, 0x00eb).mirror(0xff00).rw(FUNC(chloe_state::spi_data_r), FUNC(chloe_state::spi_data_w));
map(0x00fd, 0x00fd).lw8(NAME([this](u8 data)
{
if (data & 1)
{
LOGMEM("Core Boot Off\n");
m_core_boot = 0;
reset();
}
}));
map(0xbffd, 0xbffd).lw8(NAME([this](u8 data) { return m_ay[m_ay_selected]->data_w(data); }));
map(0xfffd, 0xfffd).lr8(NAME([this]()
{
return m_ay[m_ay_selected]->data_r();
})).w(FUNC(chloe_state::ay_address_w));
map(0xbf3b, 0xbf3b).lw8(NAME([this](u8 data)
{
m_palpen_selected = data;
if ((data & 0xc0) == 0x40)
{
port_ff_w(data);
}
}));
map(0xff3b, 0xff3b).lrw8(NAME([this]()
{
rgb_t rgb = m_palette->pen_color(0xc0 | m_palpen_selected);
return ((rgb.g() >> 5) << 5) | ((rgb.r() >> 5) << 2) | ((rgb.b() >> 6) << 0);
}), NAME([this](u8 data)
{
if (m_palpen_selected < 64)
{
m_palette->set_pen_color(0xc0 | m_palpen_selected, rgbexpand<3,3,3>((data << 1) | (((data >> 1) | data) & 1), 3, 6, 0));
}
else if ((m_palpen_selected & 0xc0) == 0x40)
{
m_ula->ulap_en_w(data & 1);
}
}));
map(0xfc3b, 0xfc3b).lrw8(NAME([this]() { return m_reg_selected; })
, NAME([this](u8 data) { m_dma_hilo = 0; m_reg_selected = data; }));
map(0xfd3b, 0xfd3b).lrw8(NAME([this]() { return m_uno_regs.read_byte(m_reg_selected); })
, NAME([this](u8 data) { m_uno_regs.write_byte(m_reg_selected, data); }));
//map(0x007f, 0x007f).mirror(0xff00).lr8(NAME([]() -> u8 { return 0x00; })); // Fuller
map(0x001f, 0x001f).mirror(0xff00).lr8(NAME([this]() -> u8 { return m_io_joy1->read() & 0x1f; })); // Kempston 1
map(0x00df, 0x00df).mirror(0xff00).lrw8(NAME([this]() -> u8 { return m_io_joy2->read() & 0x1f; }) // Kempston 2
, NAME([this](u8 data) { m_covox->data_w(data); }));
map(0x00b3, 0x00b3).mirror(0xff00).lw8(NAME([this](u8 data) { m_covox->data_w(data); }));
map(0xfadf, 0xfadf).lr8(NAME([this]() -> u8 { return 0x80 | (m_io_mouse[2]->read() & 0x07); }));
map(0xfbdf, 0xfbdf).lr8(NAME([this]() -> u8 { return m_io_mouse[0]->read(); }));
map(0xffdf, 0xffdf).lr8(NAME([this]() -> u8 { return ~m_io_mouse[1]->read(); }));
map(0x00f7, 0x00f7).mirror(0xff00).nopw(); // Audio Mixer. No support for now, using default ACB
map(0x8e3b, 0x8e3b).nopw(); // PRISMSPEEDCTRL used by software compatible with Prism
}
void chloe_state::map_regs(address_map &map)
{
map(0x00, 0xff).lrw8(NAME([this](offs_t offset)
{
if (!machine().side_effects_disabled())
{
LOGIO("rREG %02x\n", offset);
}
return m_uno_regs_data[offset];
}), NAME([this](offs_t offset, u8 data)
{
LOGIO("wREG %02x = %02x\n", offset, data);
m_uno_regs_data[offset] = data;
}));
map(0x01, 0x01).lw8(NAME([this](u8 data)
{
m_uno_regs_data[0x01] = data;
LOGMEM("UnoMapper %d\n", data);
}));
map(0x0b, 0x0b).lw8(NAME([this](u8 data)
{
m_uno_regs_data[0x0b] = data;
m_maincpu->set_clock_scale(1 << BIT(data, 6, 2));
}));
map(0x0c, 0x0d).lw8(NAME([this](offs_t offset, u8 data)
{
m_uno_regs_data[0x0c + offset] = data;
raster_irq_adjust();
}));
map(0xa0, 0xa6).w(FUNC(chloe_state::dma_reg_w));
}
u8 chloe_state::kbd_fe_r(offs_t offset)
{
u16 data = 0xffff;
u16 shifts = 0xffff;
u8 oi = offset >> 8;
for (u8 i = 0; i < 8; i++, oi >>= 1)
{
u16 line_data = m_io_line[i]->read();
shifts &= line_data;
if ((oi & 1) == 0)
{
data &= line_data;
}
}
bool shift_hold = ~m_io_line[0]->read() & 0x8000;
if (shift_hold && ((shifts & 0x1f00) != 0x1f00))
{
data >>= 8;
shifts >>= 8;
}
if (((offset & 0x0100) == 0) && BIT(~shifts, 6))
{
data &= ~0x01; // CS
}
if (((offset & 0x8000) == 0) && BIT(~shifts, 7))
{
data &= ~0x02; // SS
}
data |= 0xe0;
/* cassette input from wav */
if (m_cassette->input() > 0.0038 )
{
data &= ~0x40;
}
return data;
}
INPUT_PORTS_START(chloe)
/* PORT_NAME = KEY Mode CAPS Mode SYMBOL Mode */
PORT_START("IO_LINE0") /* 0xFEFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Caps Shift") PORT_CODE(KEYCODE_LSHIFT) PORT_CODE(KEYCODE_RSHIFT) PORT_CHAR(UCHAR_SHIFT_1) PORT_CHAR(UCHAR_SHIFT_2)
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("z Z :") PORT_CODE(KEYCODE_Z) PORT_CHAR('z') PORT_CHAR('Z') PORT_CHAR(':')
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("x X `") PORT_CODE(KEYCODE_X) PORT_CHAR('x') PORT_CHAR('X') PORT_CHAR('`')
PORT_CODE(KEYCODE_TILDE)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("c C ?") PORT_CODE(KEYCODE_C) PORT_CHAR('c') PORT_CHAR('C') PORT_CHAR('?')
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("v V /") PORT_CODE(KEYCODE_V) PORT_CHAR('v') PORT_CHAR('V') PORT_CHAR('/')
PORT_CODE(KEYCODE_SLASH) PORT_CODE(KEYCODE_SLASH_PAD)
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line0")
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line0") PORT_CODE(KEYCODE_TILDE) PORT_CODE(KEYCODE_SLASH) PORT_CODE(KEYCODE_SLASH_PAD)
PORT_BIT(0x0200, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(": (SS+KEY)") PORT_CODE(KEYCODE_COLON)
PORT_BIT(0x0800, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("? (SS+KEY)") PORT_CODE(KEYCODE_SLASH)
PORT_BIT(0x8000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS") PORT_CODE(KEYCODE_LSHIFT) PORT_CODE(KEYCODE_RSHIFT)
PORT_BIT(0x7520, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE1") /* 0xFDFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("a A ~") PORT_CODE(KEYCODE_A) PORT_CHAR('a') PORT_CHAR('A') PORT_CHAR('~')
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("s S |") PORT_CODE(KEYCODE_S) PORT_CHAR('s') PORT_CHAR('S') PORT_CHAR('|')
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("d D \\") PORT_CODE(KEYCODE_D) PORT_CHAR('d') PORT_CHAR('D') PORT_CHAR('\\')
PORT_CODE(KEYCODE_BACKSLASH)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("f F {") PORT_CODE(KEYCODE_F) PORT_CHAR('f') PORT_CHAR('F') PORT_CHAR('{')
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("g G }") PORT_CODE(KEYCODE_G) PORT_CHAR('g') PORT_CHAR('G') PORT_CHAR('}')
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line1")
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line1") PORT_CODE(KEYCODE_BACKSLASH)
PORT_BIT(0x0100, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("~ (SS+KEY)") PORT_CODE(KEYCODE_TILDE)
PORT_BIT(0x0200, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("| (SS+KEY)") PORT_CODE(KEYCODE_BACKSLASH)
PORT_BIT(0x0800, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("{ (SS+KEY)") PORT_CODE(KEYCODE_OPENBRACE)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("} (SS+KEY)") PORT_CODE(KEYCODE_CLOSEBRACE)
PORT_BIT(0xe420, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE2") /* 0xFBFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("q Q Hom") PORT_CODE(KEYCODE_Q) PORT_CODE(KEYCODE_HOME) PORT_CHAR('q') PORT_CHAR('Q')
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("w W Del") PORT_CODE(KEYCODE_W) PORT_CODE(KEYCODE_DEL) PORT_CHAR('w') PORT_CHAR('W')
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("e E End") PORT_CODE(KEYCODE_E) PORT_CODE(KEYCODE_END) PORT_CHAR('e') PORT_CHAR('E')
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("r R <") PORT_CODE(KEYCODE_R) PORT_CHAR('r') PORT_CHAR('R') PORT_CHAR('<')
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("t T >") PORT_CODE(KEYCODE_T) PORT_CHAR('t') PORT_CHAR('T') PORT_CHAR('>')
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line2")
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line2") PORT_CODE(KEYCODE_HOME) PORT_CODE(KEYCODE_DEL) PORT_CODE(KEYCODE_END)
PORT_BIT(0x0800, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("< (SS+KEY)") PORT_CODE(KEYCODE_COMMA)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("> (SS+KEY)") PORT_CODE(KEYCODE_STOP)
PORT_BIT(0xe720, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE3") /* 0xF7FE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("1 Tab !") PORT_CODE(KEYCODE_1) PORT_CHAR(UCHAR_MAMEKEY(F1)) PORT_CHAR('1') PORT_CHAR('!')
PORT_CODE(KEYCODE_F1) PORT_CODE(KEYCODE_TAB) PORT_CODE(KEYCODE_1_PAD)
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("2 CLk @") PORT_CODE(KEYCODE_2) PORT_CHAR(UCHAR_MAMEKEY(F2)) PORT_CHAR('2') PORT_CHAR('@')
PORT_CODE(KEYCODE_F2) PORT_CODE(KEYCODE_CAPSLOCK) PORT_CODE(KEYCODE_2_PAD)
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("3 PgU #") PORT_CODE(KEYCODE_3) PORT_CHAR(UCHAR_MAMEKEY(F3)) PORT_CHAR('3') PORT_CHAR('#')
PORT_CODE(KEYCODE_F3) PORT_CODE(KEYCODE_PGUP) PORT_CODE(KEYCODE_3_PAD)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("4 PgD $") PORT_CODE(KEYCODE_4) PORT_CHAR(UCHAR_MAMEKEY(F4)) PORT_CHAR('4') PORT_CHAR('$')
PORT_CODE(KEYCODE_F4) PORT_CODE(KEYCODE_PGDN) PORT_CODE(KEYCODE_4_PAD)
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("5 Lft %") PORT_CODE(KEYCODE_5) PORT_CHAR(UCHAR_MAMEKEY(F5)) PORT_CHAR('5') PORT_CHAR('%')
PORT_CODE(KEYCODE_F5) PORT_CODE(KEYCODE_LEFT) PORT_CODE(KEYCODE_5_PAD)
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line3") PORT_CODE(KEYCODE_TAB) PORT_CODE(KEYCODE_CAPSLOCK) PORT_CODE(KEYCODE_PGUP) PORT_CODE(KEYCODE_PGDN) PORT_CODE(KEYCODE_LEFT)
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line3")
PORT_BIT(0x0100, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("! (SS+KEY)") PORT_CODE(KEYCODE_1)
PORT_BIT(0x0200, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("@ (SS+KEY)") PORT_CODE(KEYCODE_2)
PORT_BIT(0x0400, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("# (SS+KEY)") PORT_CODE(KEYCODE_3)
PORT_BIT(0x0800, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("$ (SS+KEY)") PORT_CODE(KEYCODE_4)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("% (SS+KEY)") PORT_CODE(KEYCODE_5)
PORT_BIT(0xe020, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE4") /* 0xEFFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("0 BSp _") PORT_CODE(KEYCODE_0) PORT_CHAR(UCHAR_MAMEKEY(F10)) PORT_CHAR('0') PORT_CHAR('_')
PORT_CODE(KEYCODE_F10) PORT_CODE(KEYCODE_BACKSPACE) PORT_CODE(KEYCODE_0_PAD)
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("9 Ctr )") PORT_CODE(KEYCODE_9) PORT_CHAR(UCHAR_MAMEKEY(F9)) PORT_CHAR('9') PORT_CHAR(')')
PORT_CODE(KEYCODE_F9) PORT_CODE(KEYCODE_9_PAD) PORT_CODE(KEYCODE_LCONTROL)
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("8 Rgt (") PORT_CODE(KEYCODE_8) PORT_CHAR(UCHAR_MAMEKEY(F8)) PORT_CHAR('8') PORT_CHAR('(')
PORT_CODE(KEYCODE_F8) PORT_CODE(KEYCODE_RIGHT) PORT_CODE(KEYCODE_8_PAD)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("7 Up '") PORT_CODE(KEYCODE_7) PORT_CHAR(UCHAR_MAMEKEY(F7)) PORT_CHAR('7') PORT_CHAR('\'')
PORT_CODE(KEYCODE_F7) PORT_CODE(KEYCODE_UP) PORT_CODE(KEYCODE_QUOTE) PORT_CODE(KEYCODE_7_PAD)
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("6 Dwn &") PORT_CODE(KEYCODE_6) PORT_CHAR(UCHAR_MAMEKEY(F6)) PORT_CHAR('6') PORT_CHAR('&')
PORT_CODE(KEYCODE_F6) PORT_CODE(KEYCODE_DOWN) PORT_CODE(KEYCODE_6_PAD)
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line4") PORT_CODE(KEYCODE_BACKSPACE) PORT_CODE(KEYCODE_RIGHT) PORT_CODE(KEYCODE_UP) PORT_CODE(KEYCODE_DOWN) PORT_CODE(KEYCODE_LCONTROL)
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line4") PORT_CODE(KEYCODE_QUOTE)
PORT_BIT(0x0100, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("_ (SS+KEY)") PORT_CODE(KEYCODE_MINUS)
PORT_BIT(0x0200, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(") (SS+KEY)") PORT_CODE(KEYCODE_0)
PORT_BIT(0x0400, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("( (SS+KEY)") PORT_CODE(KEYCODE_9)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("& (SS+KEY)") PORT_CODE(KEYCODE_7)
PORT_BIT(0xe820, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE5") /* 0xDFFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("p P \"") PORT_CODE(KEYCODE_P) PORT_CHAR('p') PORT_CHAR('P') PORT_CHAR('"')
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("o O ;") PORT_CODE(KEYCODE_O) PORT_CHAR('o') PORT_CHAR('O') PORT_CHAR(';')
PORT_CODE(KEYCODE_COLON)
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("i I Ins") PORT_CODE(KEYCODE_I) PORT_CHAR('i') PORT_CHAR('I')
PORT_CODE(KEYCODE_INSERT)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("u U ]") PORT_CODE(KEYCODE_U) PORT_CHAR('u') PORT_CHAR('U') PORT_CHAR(']')
PORT_CODE(KEYCODE_CLOSEBRACE)
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("y Y [") PORT_CODE(KEYCODE_Y) PORT_CHAR('y') PORT_CHAR('Y') PORT_CHAR('[')
PORT_CODE(KEYCODE_OPENBRACE)
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line5")
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line5") PORT_CODE(KEYCODE_COLON) PORT_CODE(KEYCODE_INSERT) PORT_CODE(KEYCODE_CLOSEBRACE) PORT_CODE(KEYCODE_OPENBRACE)
PORT_BIT(0x0100, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("\" (SS+KEY)") PORT_CODE(KEYCODE_QUOTE)
PORT_BIT(0xfe20, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE6") /* 0xBFFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Ret Cmp Clr") PORT_CODE(KEYCODE_ENTER) PORT_CHAR(13)
PORT_CODE(KEYCODE_ENTER_PAD) PORT_CODE(KEYCODE_RALT)
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("l L =") PORT_CODE(KEYCODE_L) PORT_CHAR('l') PORT_CHAR('L') PORT_CHAR('=')
PORT_CODE(KEYCODE_EQUALS)
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("k K +") PORT_CODE(KEYCODE_K) PORT_CHAR('k') PORT_CHAR('K') PORT_CHAR('+')
PORT_CODE(KEYCODE_PLUS_PAD)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("j J -") PORT_CODE(KEYCODE_J) PORT_CHAR('j') PORT_CHAR('J') PORT_CHAR('-')
PORT_CODE(KEYCODE_MINUS) PORT_CODE(KEYCODE_MINUS_PAD)
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("h H ^") PORT_CODE(KEYCODE_H) PORT_CHAR('h') PORT_CHAR('H') PORT_CHAR('^')
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line6") PORT_CODE(KEYCODE_RALT)
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line6") PORT_CODE(KEYCODE_EQUALS) PORT_CODE(KEYCODE_MINUS) PORT_CODE(KEYCODE_MINUS_PAD) PORT_CODE(KEYCODE_PLUS_PAD)
PORT_BIT(0x0400, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("+ (SS+KEY)") PORT_CODE(KEYCODE_EQUALS)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("^ (SS+KEY)") PORT_CODE(KEYCODE_6)
PORT_BIT(0xeb20, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("IO_LINE7") /* 0x7FFE */
PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Spc Esc Hlp") PORT_CODE(KEYCODE_SPACE) PORT_CHAR(' ') PORT_CODE(KEYCODE_ESC)
PORT_CODE(KEYCODE_RCONTROL)
PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Koru") PORT_CODE(KEYCODE_LALT)
PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("m M .") PORT_CODE(KEYCODE_M) PORT_CHAR('m') PORT_CHAR('M') PORT_CHAR('.')
PORT_CODE(KEYCODE_STOP)
PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("n N ,") PORT_CODE(KEYCODE_N) PORT_CHAR('n') PORT_CHAR('N') PORT_CHAR(',')
PORT_CODE(KEYCODE_COMMA)
PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("b B *") PORT_CODE(KEYCODE_B) PORT_CHAR('b') PORT_CHAR('B') PORT_CHAR('*')
PORT_CODE(KEYCODE_ASTERISK)
PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CS Line7") PORT_CODE(KEYCODE_ESC) PORT_CODE(KEYCODE_LALT)
PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SS Line7") PORT_CODE(KEYCODE_STOP) PORT_CODE(KEYCODE_COMMA) PORT_CODE(KEYCODE_ASTERISK) PORT_CODE(KEYCODE_RCONTROL)
PORT_BIT(0x1000, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("* (SS+KEY)") PORT_CODE(KEYCODE_8)
PORT_BIT(0xef20, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("NMI")
PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("NMI (SS+KEY)") PORT_CODE(KEYCODE_ESC) PORT_CHANGED_MEMBER(DEVICE_SELF, chloe_state, on_divmmc_nmi, 0)
PORT_START("JOY1")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_PLAYER(1) PORT_CODE(JOYCODE_BUTTON1)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_UP) PORT_8WAY PORT_PLAYER(1) PORT_CODE(JOYCODE_Y_UP_SWITCH)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN) PORT_8WAY PORT_PLAYER(1) PORT_CODE(JOYCODE_Y_DOWN_SWITCH)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT) PORT_8WAY PORT_PLAYER(1) PORT_CODE(JOYCODE_X_RIGHT_SWITCH)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT) PORT_8WAY PORT_PLAYER(1) PORT_CODE(JOYCODE_X_LEFT_SWITCH)
PORT_START("JOY2")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT) PORT_8WAY PORT_PLAYER(2) PORT_CODE(JOYCODE_X_LEFT_SWITCH)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT) PORT_8WAY PORT_PLAYER(2) PORT_CODE(JOYCODE_X_RIGHT_SWITCH)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN) PORT_8WAY PORT_PLAYER(2) PORT_CODE(JOYCODE_Y_DOWN_SWITCH)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_UP) PORT_8WAY PORT_PLAYER(2) PORT_CODE(JOYCODE_Y_UP_SWITCH)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_PLAYER(2) PORT_CODE(JOYCODE_BUTTON1)
PORT_START("mouse_input1")
PORT_BIT(0xff, 0, IPT_MOUSE_X) PORT_SENSITIVITY(30)
PORT_START("mouse_input2")
PORT_BIT(0xff, 0, IPT_MOUSE_Y) PORT_SENSITIVITY(30)
PORT_START("mouse_input3")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_BUTTON4) PORT_NAME("Left mouse button") PORT_CODE(MOUSECODE_BUTTON1)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_BUTTON5) PORT_NAME("Right mouse button") PORT_CODE(MOUSECODE_BUTTON2)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_BUTTON6) PORT_NAME("Middle mouse button") PORT_CODE(MOUSECODE_BUTTON3)
INPUT_PORTS_END
void chloe_state::machine_start()
{
spectrum_128_state::machine_start();
m_spi_clock = timer_alloc(FUNC(chloe_state::spi_clock), this);
m_irq_raster_on_timer = timer_alloc(FUNC(chloe_state::raster_irq_on), this);
m_irq_raster_off_timer = timer_alloc(FUNC(chloe_state::raster_irq_off), this);
m_regs_map->space(AS_PROGRAM).specific(m_uno_regs);
m_maincpu->space(AS_PROGRAM).specific(m_program);
m_maincpu->space(AS_IO).specific(m_io);
for (auto i = 0; i < 8; i++)
{
m_bank_ram[i]->configure_entries(0, m_ram->size() / 0x2000, m_ram->pointer(), 0x2000);
m_bank_ram[i]->configure_entries( 8 << 1, 4, memregion("maincpu")->base(), 0x2000);
m_bank_ram[i]->configure_entries(12 << 1, 2, memregion("maincpu")->base() + 0x8000, 0x2000);
}
memset(m_uno_regs_data, 0 , 256);
m_core_boot = 1;
m_divmmc_ctrl = 0;
// Save
save_item(NAME(m_timex_mmu));
save_item(NAME(m_port_ff_data));
save_item(NAME(m_core_boot));
save_item(NAME(m_reg_selected));
save_item(NAME(m_divmmc_paged));
save_item(NAME(m_divmmc_ctrl));
save_pointer(NAME(m_uno_regs_data), 256);
save_item(NAME(m_palpen_selected));
save_item(NAME(m_ay_selected));
save_item(NAME(m_dma_hilo));
save_item(NAME(m_dma_src_latch));
save_item(NAME(m_dma_dst_latch));
save_item(NAME(m_dma_pre_latch));
save_item(NAME(m_dma_len_latch));
save_item(NAME(m_dma_prob_latch));
save_item(NAME(m_spi_clock_cycles));
save_item(NAME(m_spi_clock_state));
save_item(NAME(m_spi_mosi_dat));
save_item(NAME(m_spi_miso_dat));
}
void chloe_state::machine_reset()
{
spectrum_128_state::machine_reset();
m_irq_raster_on_timer->reset();
m_irq_raster_off_timer->reset();
m_spi_clock->reset();
m_spi_clock_cycles = 0;
m_spi_clock_state = false;
m_dma_hilo = 0;
m_timex_mmu = 0;
m_port_ff_data = 0;
m_port_7ffd_data = 0;
m_divmmc_paged = 1;
m_divmmc_ctrl &= 0x40;
m_ay_selected = 0;
update_memory();
}
static const gfx_layout chloe_charlayout =
{
8, 8, // 8 x 8 characters
256, // 128 characters
1, // 1 bits per pixel
{ 0 }, // no bitplanes
{ STEP8(0, 1) }, // x offsets
{ STEP8(0, 8) }, // y offsets
8 * 8 // every char takes 8 bytes
};
static GFXDECODE_START(gfx_chloe)
GFXDECODE_ENTRY("maincpu", 0x2a00, chloe_charlayout, 7, 1)
GFXDECODE_END
void chloe_state::video_start()
{
spectrum_128_state::video_start();
m_contention_pattern = {}; // Has no contention
const u8 *ram = m_ram->pointer();
m_ula->set_host_ram_ptr(ram);
}
void chloe_state::chloe(machine_config &config)
{
spectrum_128(config);
config.device_remove("exp");
config.device_remove("palette");
m_ram->set_default_size("512K").set_default_value(0xff);
Z80(config.replace(), m_maincpu, 28_MHz_XTAL / 8);
m_maincpu->set_m1_map(&chloe_state::map_fetch);
m_maincpu->set_memory_map(&chloe_state::map_mem);
m_maincpu->set_io_map(&chloe_state::map_io);
m_maincpu->set_vblank_int("screen", FUNC(chloe_state::chloe_interrupt));
m_maincpu->nomreq_cb().set_nop();
ADDRESS_MAP_BANK(config, m_regs_map).set_map(&chloe_state::map_regs).set_options(ENDIANNESS_LITTLE, 8, 8, 0);
/*
???DMA(config, m_dma, 28_MHz_XTAL / 8);
m_dma->out_busreq_callback().set_inputline(m_maincpu, Z80_INPUT_LINE_BUSRQ);
m_dma->in_mreq_callback().set([this](offs_t offset) { return m_program.read_byte(offset); });
m_dma->out_mreq_callback().set([this](offs_t offset, u8 data) { m_program.write_byte(offset, data); });
m_dma->in_iorq_callback().set([this](offs_t offset) { return m_io.read_byte(offset); });
m_dma->out_iorq_callback().set([this](offs_t offset, u8 data) { m_io.write_byte(offset, data); });
*/
SPI_SDCARD(config, m_sdcard, 0);
m_sdcard->spi_miso_callback().set(FUNC(chloe_state::spi_miso_w));
subdevice<gfxdecode_device>("gfxdecode")->set_info(gfx_chloe);
m_screen->set_raw(25.175_MHz_XTAL, CYCLES_HORIZ, CYCLES_VERT, SCR_FULL); // VGA
m_screen->set_screen_update(FUNC(chloe_state::screen_update));
m_screen->set_no_palette();
PALETTE(config, m_palette, FUNC(chloe_state::spectrum_palette), 256);
SCREEN_ULA_PLUS(config, m_ula, 0).set_raster_offset(SCR_256x192.left(), SCR_256x192.top()).set_palette(m_palette->device().tag(), 0x000, 0x000);
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
config.device_remove("ay8912");
AY8912(config, m_ay[0], 28_MHz_XTAL / 16)
.add_route(0, "lspeaker", 0.50)
.add_route(2, "lspeaker", 0.25)
.add_route(2, "rspeaker", 0.25)
.add_route(1, "rspeaker", 0.50);
AY8912(config, m_ay[1], 28_MHz_XTAL / 16)
.add_route(0, "lspeaker", 0.50)
.add_route(2, "lspeaker", 0.25)
.add_route(2, "rspeaker", 0.25)
.add_route(1, "rspeaker", 0.50);
DAC_8BIT_R2R(config, m_covox, 0)
.add_route(ALL_OUTPUTS, "lspeaker", 0.75)
.add_route(ALL_OUTPUTS, "rspeaker", 0.75);
SOFTWARE_LIST(config, "cass_list_t").set_original("timex_cass");
}
ROM_START(chloe)
ROM_REGION(0xc000, "maincpu", ROMREGION_ERASEFF)
// SE/OS 1.0
ROM_LOAD( "10_boot.rom", 0x0000, 0x4000, CRC(efbfe46e) SHA1(f5a86b56955661f72fa416e7e644de0b3afe6509))
ROM_LOAD( "10_basic_42.rom", 0x4000, 0x4000, CRC(c6273eaa) SHA1(f09a26c50f5cfe454e4d56c920cdcc62bc4f90cb))
ROM_LOAD( "10_dos_31.rom", 0x8000, 0x2000, CRC(67dfef09) SHA1(ba9616494071dfe65834d7db657e0d3bcce0b732))
ROM_END
} // Anonymous namespace
/* YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS */
COMP( 1999, chloe, spec128, 0, chloe, chloe, chloe_state, empty_init, "Chloe Corporation", "Chloe 280SE", MACHINE_SUPPORTS_SAVE | MACHINE_IMPERFECT_SOUND | MACHINE_IMPERFECT_CONTROLS )

View File

@ -0,0 +1,272 @@
// license:BSD-3-Clause
// copyright-holders:Andrei I. Holub
/**********************************************************************
Spectrum Screen ULA
* ULA+
* ULA+ The Next extension
ULA+ implementation can used used instead classic ULA. Doesn't
implement contention yet.
Assumes ULA+ palette is installed at 0xc0.
**********************************************************************/
#include "emu.h"
#include "screen_ula.h"
#include "screen.h"
screen_ula_device::screen_ula_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, type, tag, owner, clock)
, device_gfx_interface(mconfig, *this)
{
}
screen_ula_plus_device::screen_ula_plus_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
screen_ula_device(mconfig, SCREEN_ULA_PLUS, tag, owner, clock)
{
m_ula_type = ULA_TYPE_PLUS;
}
screen_ula_next_device::screen_ula_next_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
screen_ula_device(mconfig, SCREEN_ULA_NEXT, tag, owner, clock)
{
m_ula_type = ULA_TYPE_NEXT;
}
screen_ula_device &screen_ula_device::set_palette(const char *tag, u16 base_offset, u16 alt_offset)
{
device_gfx_interface::set_palette(tag);
m_palette_base_offset = base_offset,
m_palette_alt_offset = alt_offset;
return *this;
}
u8 screen_ula_device::screen_mode()
{
// Next doesn't support hires in screen1
return ((m_ula_type == ULA_TYPE_NEXT) && m_ula_shadow_en) ? 0b000 : (m_port_ff_reg & 7);
}
void screen_ula_device::draw_border(bitmap_rgb32 &bitmap, const rectangle &cliprect, u8 border_color)
{
u8 pap_attr = border_color << 3;
if ((m_ula_type == ULA_TYPE_NEXT) && m_ulanext_en)
pap_attr = border_color << m_pap_shift;
else if (m_ulap_en && (screen_mode() == 6))
pap_attr = 0x40 | (~m_port_ff_reg & 0x38);
const rgb_t pap = parse_attribute(pap_attr).first;
const rgb_t gt0 = rgbexpand<3,3,3>((m_global_transparent << 1) | 0, 6, 3, 0);
const rgb_t gt1 = rgbexpand<3,3,3>((m_global_transparent << 1) | 1, 6, 3, 0);
if (pap != gt0 && pap != gt1)
{
bitmap.fill(pap, cliprect);
rectangle clip = { SCREEN_AREA.left() << 1, (SCREEN_AREA.right() << 1) | 1, SCREEN_AREA.top(), SCREEN_AREA.bottom() };
clip.offset(m_offset_h, m_offset_v);
clip &= cliprect;
clip.setx(clip.left() & ~1, clip.right() | 1);
if (!clip.empty())
bitmap.fill(palette().pen_color(UTM_FALLBACK_PEN), clip);
}
else
bitmap.fill(palette().pen_color(UTM_FALLBACK_PEN), cliprect);
}
void screen_ula_device::draw(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect, bool flash, u8 pcode)
{
rectangle clip = { m_ula_clip_x1 << 1, (m_ula_clip_x2 << 1) | 1, m_ula_clip_y1, m_ula_clip_y2 };
clip.offset(m_offset_h, m_offset_v);
clip &= cliprect;
clip.setx(clip.left() & ~1, clip.right() | 1);
if (!clip.empty())
{
if (screen_mode() == 6)
draw_hires(bitmap, clip, screen.priority(), pcode);
else
draw_ula(bitmap, clip, flash, screen.priority(), pcode);
}
}
void screen_ula_device::draw_ula(bitmap_rgb32 &bitmap, const rectangle &clip, bool flash, bitmap_ind8 &priority_bitmap, u8 pcode)
{
const bool hicolor = screen_mode() == 2; // timex hicolor
flash &= !m_ulanext_en && !m_ulap_en;
const rgb_t gt0 = rgbexpand<3,3,3>((m_global_transparent << 1) | 0, 6, 3, 0);
const rgb_t gt1 = rgbexpand<3,3,3>((m_global_transparent << 1) | 1, 6, 3, 0);
const u8 *screen_location = m_host_ram_ptr + ((m_ula_shadow_en ? 7 : 5) << 14);
const u16 x_min = (((clip.left() - m_offset_h) >> 1) + m_ula_scroll_x) % SCREEN_AREA.width();
for (u16 vpos = clip.top(); vpos <= clip.bottom(); vpos++)
{
u16 hpos = clip.left();
u16 y = (vpos - m_offset_v + m_ula_scroll_y) % SCREEN_AREA.height();
u16 x = x_min;
const u8 *scr = &screen_location[((y & 7) << 8) | ((y & 0x38) << 2) | ((y & 0xc0) << 5) | (x >> 3)];
const u8 *attr = hicolor ? &scr[0x2000] : &screen_location[0x1800 + (((y & 0xf8) << 2) | (x >> 3))];
u32 *pix = &(bitmap.pix(vpos, hpos));
u8 *prio = &(priority_bitmap.pix(vpos, hpos));
while (hpos <= clip.right())
{
const std::pair<rgb_t, rgb_t> pi = parse_attribute(*attr);
const rgb_t pap = pi.first;
const rgb_t ink = pi.second;
const u8 pix8 = (flash && (*attr & 0x80)) ? ~*scr : *scr;
for (u8 b = (0x80 >> (x & 7)); b && (hpos <= clip.right()); b >>= 1, ++x, hpos += 2, pix += 2, prio += 2)
{
const rgb_t pen = (pix8 & b) ? ink : pap;
if ((pen != gt0) && (pen != gt1))
{
*pix = pen;
*(pix + 1) = pen;
*(prio) |= pcode;
*(prio + 1) |= pcode;
}
}
x %= SCREEN_AREA.width();
if (x == 0)
{
scr = &screen_location[((y & 7) << 8) | ((y & 0x38) << 2) | ((y & 0xc0) << 5)];
attr = hicolor ? &scr[0x2000] : &screen_location[0x1800 + (((y & 0xf8) << 2))];
}
else
{
++scr;
++attr;
}
}
}
}
void screen_ula_device::draw_hires(bitmap_rgb32 &bitmap, const rectangle &clip, bitmap_ind8 &priority_bitmap, u8 pcode)
{
const rgb_t gt0 = rgbexpand<3,3,3>((m_global_transparent << 1) | 0, 6, 3, 0);
const rgb_t gt1 = rgbexpand<3,3,3>((m_global_transparent << 1) | 1, 6, 3, 0);
const u8 *screen_location = m_host_ram_ptr + ((m_ula_shadow_en ? 7 : 5) << 14);
const u8 attr = 0x40 | (~m_port_ff_reg & 0x38) | BIT(m_port_ff_reg, 3, 3);
const std::pair<rgb_t, rgb_t> pi = parse_attribute(attr);
const rgb_t pap = pi.first;
const rgb_t ink = pi.second;
const u16 x_min = ((clip.left() - m_offset_h) + (m_ula_scroll_x << 1)) % (SCREEN_AREA.width() << 1);
for (u16 vpos = clip.top(); vpos <= clip.bottom(); vpos++)
{
u16 hpos = clip.left();
u16 y = (vpos - m_offset_v + m_ula_scroll_y) % SCREEN_AREA.height();
u16 x = x_min;
const u8 *scr = &screen_location[((y & 7) << 8) | ((y & 0x38) << 2) | ((y & 0xc0) << 5) | ((x >> 3) >> 1)];
u32 *pix = &(bitmap.pix(vpos, hpos));
u8 *prio = &(priority_bitmap.pix(vpos, hpos));
while (hpos <= clip.right())
{
const u8 pix8 = *(scr + 0x2000 * BIT(x, 3));
for (u8 b = (0x80 >> (x & 7)); b && (hpos <= clip.right()); b >>= 1, ++x, ++hpos, ++pix, ++prio)
{
const rgb_t pen = (pix8 & b) ? ink : pap;
if (pen != gt0 && pen != gt1)
{
*pix = pen;
*prio |= pcode;
}
}
x %= SCREEN_AREA.width() << 1;
if (x == 0)
scr = &screen_location[((y & 7) << 8) | ((y & 0x38) << 2) | ((y & 0xc0) << 5)];
else if (BIT(~x, 3))
++scr;
}
}
}
void screen_ula_device::ulanext_format_w(u8 ulanext_format)
{
m_ulanext_format = ulanext_format;
m_ink_mask = m_ulanext_format;
switch (m_ulanext_format)
{
case 0x01: m_pap_shift = 1; break;
case 0x03: m_pap_shift = 2; break;
case 0x07: m_pap_shift = 3; break;
case 0x0f: m_pap_shift = 4; break;
case 0x1f: m_pap_shift = 5; break;
case 0x3f: m_pap_shift = 6; break;
case 0x7f: m_pap_shift = 7; break;
default:
m_ink_mask = 0xff;
m_pap_shift = 8;
}
}
std::pair<rgb_t, rgb_t> screen_ula_device::parse_attribute(u8 attr)
{
const u16 pal_base = m_ula_palette_select ? m_palette_alt_offset : m_palette_base_offset;
u16 pap, ink;
if (m_ulanext_en)
{
ink = attr & m_ink_mask;
pap = (m_ulanext_format == 0xff) ? (UTM_FALLBACK_PEN - pal_base) : ((attr >> m_pap_shift) | 0x80);
}
else if (m_ulap_en)
{
ink = 0xc0 | ((attr & 0xc0) >> 2) | BIT(attr, 0, 3);
pap = 0xc8 | ((attr & 0xc0) >> 2) | BIT(attr, 3, 3);
}
else
{
ink = ((attr & 0x40) >> 3) | BIT(attr, 0, 3);
pap = ((attr & 0x40) >> 3) | BIT(attr, 3, 3);
if (m_ula_type == ULA_TYPE_NEXT)
pap |= 0x10;
}
return { palette().pen_color(pal_base + pap), palette().pen_color(pal_base + ink) };
}
void screen_ula_device::device_add_mconfig(machine_config &config)
{
m_offset_h = 0;
m_offset_v = 0;
m_global_transparent = 0xaa; // TODO feature toggle
m_ula_palette_select = 0;
m_ulanext_en = 0;
m_ulap_en = 0;
m_ula_shadow_en = 0;
m_ula_clip_x1 = 0;
m_ula_clip_x2 = 255;
m_ula_clip_y1 = 0;
m_ula_clip_y2 = 192;
m_ula_scroll_x = 0;
m_ula_scroll_y = 0;
}
void screen_ula_device::device_start()
{
save_item(NAME(m_global_transparent));
save_item(NAME(m_ula_palette_select));
save_item(NAME(m_ulanext_en));
save_item(NAME(m_ulanext_format));
save_item(NAME(m_ink_mask));
save_item(NAME(m_pap_shift));
save_item(NAME(m_ulap_en));
save_item(NAME(m_port_ff_reg));
save_item(NAME(m_ula_shadow_en));
save_item(NAME(m_ula_clip_x1));
save_item(NAME(m_ula_clip_x2));
save_item(NAME(m_ula_clip_y1));
save_item(NAME(m_ula_clip_y2));
save_item(NAME(m_ula_scroll_x));
save_item(NAME(m_ula_scroll_y));
save_item(NAME(m_ula_fine_scroll_x));
}
// device type definition
DEFINE_DEVICE_TYPE(SCREEN_ULA_PLUS, screen_ula_plus_device, "ula_plus", "Spectrum ULA+")
DEFINE_DEVICE_TYPE(SCREEN_ULA_NEXT, screen_ula_next_device, "ula_next", "Spectrum ULA Next")

View File

@ -0,0 +1,98 @@
// license:BSD-3-Clause
// copyright-holders:Andrei I. Holub
#ifndef MAME_SINCLAIR_SCREEN_ULA_H
#define MAME_SINCLAIR_SCREEN_ULA_H
#pragma once
class screen_ula_device : public device_t, public device_gfx_interface
{
public:
screen_ula_device &set_raster_offset(u16 offset_h, u16 offset_v) { m_offset_h = offset_h; m_offset_v = offset_v; return *this; }
screen_ula_device &set_host_ram_ptr(const u8 *host_ram_ptr) { m_host_ram_ptr = host_ram_ptr; return *this; }
screen_ula_device &set_palette(const char *tag, u16 base_offset, u16 alt_offset);
void set_global_transparent(u8 global_transparent) { m_global_transparent = global_transparent; }
void ula_palette_select_w(bool ula_palette_select) { m_ula_palette_select = ula_palette_select; }
void ula_shadow_en_w(bool ula_shadow_en) { m_ula_shadow_en = ula_shadow_en; }
void ulanext_en_w(bool ulanext_en) { m_ulanext_en = ulanext_en; }
void ulanext_format_w(u8 ulanext_format);
void ulap_en_w(bool ulap_en) { m_ulap_en = ulap_en; }
void port_ff_reg_w(u8 port_ff_reg) { m_port_ff_reg = port_ff_reg; }
void ula_clip_x1_w(u8 ula_clip_x1) { m_ula_clip_x1 = ula_clip_x1; }
void ula_clip_x2_w(u8 ula_clip_x2) { m_ula_clip_x2 = ula_clip_x2; }
void ula_clip_y1_w(u8 ula_clip_y1) { m_ula_clip_y1 = ula_clip_y1; }
void ula_clip_y2_w(u8 ula_clip_y2) { m_ula_clip_y2 = ula_clip_y2; }
void ula_scroll_x_w(u8 ula_scroll_x) { m_ula_scroll_x = ula_scroll_x; }
void ula_scroll_y_w(u8 ula_scroll_y) { m_ula_scroll_y = ula_scroll_y; }
void ula_fine_scroll_x_w (bool ula_fine_scroll_x) { m_ula_fine_scroll_x = ula_fine_scroll_x; }
void draw_border(bitmap_rgb32 &bitmap, const rectangle &cliprect, u8 border_color);
void draw(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect, bool flash = 0, u8 pcode = 0);
protected:
screen_ula_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock);
enum ula_type : u8
{
ULA_TYPE_PLUS = 0,
ULA_TYPE_NEXT
};
static constexpr rectangle SCREEN_AREA = { 0, 255, 0, 191 };
virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
virtual void device_start() override ATTR_COLD;
void draw_ula(bitmap_rgb32 &bitmap, const rectangle &clip, bool flash, bitmap_ind8 &priority_bitmap, u8 pcode);
void draw_hires(bitmap_rgb32 &bitmap, const rectangle &clip, bitmap_ind8 &priority_bitmap, u8 pcode);
ula_type m_ula_type;
private:
static const u16 UTM_FALLBACK_PEN = 0x800;
u16 m_offset_h, m_offset_v;
const u8 *m_host_ram_ptr;
u8 m_global_transparent;
u16 m_palette_base_offset;
u16 m_palette_alt_offset;
bool m_ula_palette_select;
bool m_ulanext_en;
u8 m_ulanext_format;
u8 m_ink_mask;
u8 m_pap_shift;
bool m_ulap_en;
u8 m_port_ff_reg; // u6
bool m_ula_shadow_en;
u8 m_ula_clip_x1;
u8 m_ula_clip_x2;
u8 m_ula_clip_y1;
u8 m_ula_clip_y2;
u8 m_ula_scroll_x;
u8 m_ula_scroll_y;
bool m_ula_fine_scroll_x;
u8 screen_mode();
std::pair<rgb_t, rgb_t> parse_attribute(u8 attr);
};
class screen_ula_plus_device : public screen_ula_device
{
public:
screen_ula_plus_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
class screen_ula_next_device : public screen_ula_device
{
public:
screen_ula_next_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(SCREEN_ULA_PLUS, screen_ula_plus_device)
DECLARE_DEVICE_TYPE(SCREEN_ULA_NEXT, screen_ula_next_device)
#endif // MAME_SINCLAIR_SCREEN_ULA_H