Skip to content

sinclair/spectrum_v.cpp: Added ULA snow effect support [holub, spectramine] #13963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/devices/cpu/z80/z80.lst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ macro wm16_sp %dd

macro rop
m_m1_cycles-2 !! TDAT8 = opcode_read();
// TODO based on ULA contention analyzes refresh must send already incremented R.
if (refresh_en)
m_refresh_cb((I << 8) | (R2 & 0x80) | (R & 0x7f), 0x00, 0xff);
+ 2
Expand Down
1 change: 1 addition & 0 deletions src/mame/sinclair/atm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ void atm_state::atm(machine_config &config)
m_maincpu->set_addrmap(AS_IO, &atm_state::atm_io);
m_maincpu->set_addrmap(AS_OPCODES, &atm_state::atm_switch);
m_maincpu->set_vblank_int("screen", FUNC(atm_state::atm_interrupt));
m_maincpu->refresh_cb().remove();
m_maincpu->nomreq_cb().remove();

m_screen->set_raw(X1_128_SINCLAIR / 5, 448, 312, {get_screen_area().left() - 40, get_screen_area().right() + 40, get_screen_area().top() - 40, get_screen_area().bottom() + 40});
Expand Down
7 changes: 5 additions & 2 deletions src/mame/sinclair/byte.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ void byte_state::map_io(address_map &map)

u8 byte_state::kbd_fe_r(offs_t offset)
{
if (is_contended(offset)) content_early();
content_early(1);
if (!machine().side_effects_disabled())
{
if (is_contended(offset)) content_early();
content_early(1);
}

u8 lines = offset >> 8;
u8 data = 0xff;
Expand Down
1 change: 1 addition & 0 deletions src/mame/sinclair/pentagon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ void pentagon_state::pentagon(machine_config &config)
m_maincpu->set_addrmap(AS_IO, &pentagon_state::pentagon_io);
m_maincpu->set_addrmap(AS_OPCODES, &pentagon_state::pentagon_switch);
m_maincpu->set_vblank_int("screen", FUNC(pentagon_state::pentagon_interrupt));
m_maincpu->refresh_cb().remove();
m_maincpu->nomreq_cb().remove();

m_screen->set_raw(14_MHz_XTAL / 2, 448, 320, {get_screen_area().left() - 48, get_screen_area().right() + 48, get_screen_area().top() - 48, get_screen_area().bottom() + 48});
Expand Down
1 change: 1 addition & 0 deletions src/mame/sinclair/scorpion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ void scorpion_state::scorpion(machine_config &config)
m_maincpu->set_m1_map(&scorpion_state::scorpion_switch);
m_maincpu->set_io_map(&scorpion_state::scorpion_io);
m_maincpu->set_vblank_int("screen", FUNC(scorpion_state::scorpion_interrupt));
m_maincpu->refresh_cb().remove();
m_maincpu->nomreq_cb().remove();

subdevice<gfxdecode_device>("gfxdecode")->set_info(gfx_scorpion);
Expand Down
25 changes: 18 additions & 7 deletions src/mame/sinclair/spec128.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ void spectrum_128_state::video_start()

uint8_t spectrum_128_state::spectrum_128_pre_opcode_fetch_r(offs_t offset)
{
if (is_contended(offset)) content_early();
m_is_m1_rd_contended = false;
if (!machine().side_effects_disabled() && is_contended(offset)) content_early();

/* this allows expansion devices to act upon opcode fetches from MEM addresses
for example, interface1 detection fetches requires fetches at 0008 / 0708 to
Expand Down Expand Up @@ -213,7 +214,7 @@ template void spectrum_128_state::spectrum_128_ram_w<0>(offs_t offset, u8 data);
template <u8 Bank> u8 spectrum_128_state::spectrum_128_ram_r(offs_t offset)
{
u16 addr = 0x4000 * Bank + offset;
if (is_contended(addr)) content_early();
if (!machine().side_effects_disabled() && is_contended(addr)) content_early();

return ((u8*)m_bank_ram[Bank]->base())[offset];
}
Expand Down Expand Up @@ -247,14 +248,14 @@ void spectrum_128_state::spectrum_128_update_memory()

m_screen->update_now();
if (BIT(m_port_7ffd_data, 3))
m_screen_location = m_ram->pointer() + (7<<14);
m_screen_location = m_ram->pointer() + (7 << 14);
else
m_screen_location = m_ram->pointer() + (5<<14);
m_screen_location = m_ram->pointer() + (5 << 14);
}

uint8_t spectrum_128_state::spectrum_port_r(offs_t offset)
{
if (is_contended(offset))
if (!machine().side_effects_disabled() && is_contended(offset))
{
content_early();
content_late();
Expand Down Expand Up @@ -381,9 +382,18 @@ bool spectrum_128_state::is_vram_write(offs_t offset) {
}

bool spectrum_128_state::is_contended(offs_t offset) {
u8 bank = m_bank_ram[3]->entry();
u8 pg = m_bank_ram[3]->entry();
return spectrum_state::is_contended(offset)
|| ((offset >= 0xc000 && offset <= 0xffff) && (bank & 1)); // Memory banks 1,3,5 and 7 are contended
|| ((offset >= 0xc000 && offset <= 0xffff) && (pg & 1)); // Memory pages 1,3,5 and 7 are contended
}

u8* spectrum_128_state::snow_pattern1_base(u8 i_reg)
{
const bool is_alt_scr_selected = BIT(m_port_7ffd_data, 3);
const bool is_alt_scr = i_reg & 0x80;
const u8 i_pg = is_alt_scr ? (m_bank_ram[3]->entry() & 0b101) : 5;

return m_ram->pointer() + ((i_pg | (is_alt_scr_selected << 1)) << 14);
}

static const gfx_layout spectrum_charlayout =
Expand Down Expand Up @@ -416,6 +426,7 @@ void spectrum_128_state::spectrum_128(machine_config &config)
m_maincpu->set_io_map(&spectrum_128_state::spectrum_128_io);
m_maincpu->set_m1_map(&spectrum_128_state::spectrum_128_fetch);
m_maincpu->set_vblank_int("screen", FUNC(spectrum_128_state::spec_interrupt));
m_maincpu->refresh_cb().set(FUNC(spectrum_128_state::spectrum_refresh_w));
m_maincpu->nomreq_cb().set(FUNC(spectrum_128_state::spectrum_nomreq));
m_maincpu->busack_cb().set("dma", FUNC(dma_slot_device::bai_w));

Expand Down
1 change: 1 addition & 0 deletions src/mame/sinclair/spec128.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class spectrum_128_state : public spectrum_state

virtual bool is_contended(offs_t offset) override;
virtual bool is_vram_write(offs_t offset) override;
virtual u8* snow_pattern1_base(u8 i_reg) override;

template <u8 Bank>
void spectrum_128_ram_w(offs_t offset, u8 data);
Expand Down
1 change: 1 addition & 0 deletions src/mame/sinclair/specpls3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ void specpls3_state::spectrum_plus2(machine_config &config)

m_maincpu->set_addrmap(AS_PROGRAM, &specpls3_state::plus3_mem);
m_maincpu->set_addrmap(AS_IO, &specpls3_state::plus3_io);
m_maincpu->refresh_cb().remove();
m_maincpu->nomreq_cb().remove();

subdevice<gfxdecode_device>("gfxdecode")->set_info(specpls3);
Expand Down
17 changes: 12 additions & 5 deletions src/mame/sinclair/spectrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ SamRam

uint8_t spectrum_state::pre_opcode_fetch_r(offs_t offset)
{
if (is_contended(offset)) content_early();
m_is_m1_rd_contended = false;
if (!machine().side_effects_disabled() && is_contended(offset)) content_early();

/* this allows expansion devices to act upon opcode fetches from MEM addresses
for example, interface1 detection fetches requires fetches at 0008 / 0708 to
Expand All @@ -308,7 +309,7 @@ uint8_t spectrum_state::pre_opcode_fetch_r(offs_t offset)

uint8_t spectrum_state::spectrum_data_r(offs_t offset)
{
if (is_contended(offset)) content_early();
if (!machine().side_effects_disabled() && is_contended(offset)) content_early();

m_exp->pre_data_fetch(offset);
uint8_t retval = m_specmem->read8(offset);
Expand Down Expand Up @@ -372,8 +373,11 @@ void spectrum_state::spectrum_ula_w(offs_t offset, uint8_t data)
/* DJR: Spectrum+ keys added */
uint8_t spectrum_state::spectrum_ula_r(offs_t offset)
{
if (is_contended(offset)) content_early();
content_early(1);
if (!machine().side_effects_disabled())
{
if (is_contended(offset)) content_early();
content_early(1);
}

int lines = offset >> 8;
int data = 0xff;
Expand Down Expand Up @@ -462,7 +466,7 @@ void spectrum_state::spectrum_port_w(offs_t offset, uint8_t data)

uint8_t spectrum_state::spectrum_port_r(offs_t offset)
{
if (is_contended(offset))
if (!machine().side_effects_disabled() && is_contended(offset))
{
content_early();
content_late();
Expand Down Expand Up @@ -686,6 +690,7 @@ void spectrum_state::machine_start()
{
save_item(NAME(m_port_fe_data));
save_item(NAME(m_int_at));
save_item(NAME(m_is_m1_rd_contended));

m_maincpu->space(AS_PROGRAM).specific(m_program);
m_maincpu->space(AS_IO).specific(m_io);
Expand All @@ -698,6 +703,7 @@ void spectrum_state::machine_reset()
m_port_fe_data = -1;
m_port_7ffd_data = -1;
m_port_1ffd_data = -1;
m_is_m1_rd_contended = false;
m_irq_on_timer->adjust(attotime::never);
m_irq_off_timer->adjust(attotime::never);
}
Expand Down Expand Up @@ -744,6 +750,7 @@ void spectrum_state::spectrum_common(machine_config &config)
m_maincpu->set_memory_map(&spectrum_state::spectrum_data);
m_maincpu->set_io_map(&spectrum_state::spectrum_io);
m_maincpu->set_vblank_int("screen", FUNC(spectrum_state::spec_interrupt));
m_maincpu->refresh_cb().set(FUNC(spectrum_state::spectrum_refresh_w));
m_maincpu->nomreq_cb().set(FUNC(spectrum_state::spectrum_nomreq));
m_maincpu->busack_cb().set("dma", FUNC(dma_slot_device::bai_w));

Expand Down
3 changes: 3 additions & 0 deletions src/mame/sinclair/spectrum.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class spectrum_state : public driver_device
u8 m_border4t_render_at = 0;
/* Defines offset in CPU cycles from screen left side. Early model (48/128/+2) typically use -1, later (+2A/+3) +1 */
s8 m_contention_offset = -1;
bool m_is_m1_rd_contended = false;
u64 m_int_at;

uint8_t pre_opcode_fetch_r(offs_t offset);
Expand All @@ -133,7 +134,9 @@ class spectrum_state : public driver_device
virtual bool is_vram_write(offs_t offset);
void content_early(s8 shift = 0);
void content_late();
virtual u8* snow_pattern1_base(u8 i_reg);

void spectrum_refresh_w(offs_t offset, uint8_t data);
void spectrum_nomreq(offs_t offset, uint8_t data);
void spectrum_ula_w(offs_t offset, uint8_t data);
uint8_t spectrum_ula_r(offs_t offset);
Expand Down
82 changes: 77 additions & 5 deletions src/mame/sinclair/spectrum_v.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,12 @@ void spectrum_state::spectrum_update_border(screen_device &screen, bitmap_ind16
}

/* ULA reads screen data in 16px (8T) chunks as following:
T: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
px: | 0 | 1 | 2 | 3 |*4*| 5 | 6 | 7 |*0*| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| << !right << | char1 | attr1 | char2 | attr2 | >> right >> |
T: | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
ULA contention: | | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 0 |
opcode read T: | 0 | 1 | 2 RSH | 3 RSH | | | | | | ULA reads lower address for char1/attr1 from R6:0
opcode read T: | | | 0 | 1 | 2 RSH | 3 RSH | | | | ULA skip reading char2/attr2 and keep them from char1/attr1
px: | | 0 | 1 | 2 | 3 |*4*| 5 | 6 | 7 |*0*| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| | << !right << | char1 | attr1 | char2 | attr2 | >> right >> |

TODO Curren implementation only tracks char switch position. In order to track both (char and attr) we need to share
some state between screen->update() events.
Expand All @@ -194,8 +197,8 @@ void spectrum_state::spectrum_update_screen(screen_device &screen, bitmap_ind16
chunk_right = !chunk_right;
}
u16 y = vpos - get_screen_area().top();
u8 *scr = &m_screen_location[((y & 7) << 8) | ((y & 0x38) << 2) | ((y & 0xc0) << 5) | (x >> 3)];
u8 *attr = &m_screen_location[0x1800 + (((y & 0xf8) << 2) | (x >> 3))];
u8 *scr = &m_screen_location[((y & 0xc0) << 5) | ((y & 7) << 8) | ((y & 0x38) << 2) | (x >> 3)];
u8 *attr = &m_screen_location[0x1800 | (((y & 0xf8) << 2) | (x >> 3))];
u16 *pix = &(bitmap.pix(vpos, hpos));

while ((hpos + (chunk_right ? 0 : 4)) <= cliprect.right())
Expand Down Expand Up @@ -233,6 +236,7 @@ void spectrum_state::content_early(s8 shift)

if(cf <= now && now < ct)
{
m_is_m1_rd_contended = true; // make sure M1 sets it to false before
u64 clocks = now - cf;
u8 c = m_contention_pattern[clocks % m_contention_pattern.size()];
m_maincpu->adjust_icount(-c);
Expand Down Expand Up @@ -265,3 +269,71 @@ void spectrum_state::spectrum_nomreq(offs_t offset, uint8_t data)
{
if (is_contended(offset)) content_early();
}

// see: spectrum_state::spectrum_update_screen()
void spectrum_state::spectrum_refresh_w(offs_t offset, uint8_t data)
{
const u8 i = offset >> 8;
bool is_snow_possible = !m_contention_pattern.empty() && is_contended(i << 8);
if (!is_snow_possible || m_is_m1_rd_contended)
return;

const u64 hpos = m_screen->hpos();
const u64 vpos = m_screen->vpos();
if (hpos < get_screen_area().left() || hpos > get_screen_area().right() || vpos < get_screen_area().top() || vpos > get_screen_area().bottom())
return;

u8 x = hpos - get_screen_area().left();
const u16 y = vpos - get_screen_area().top();

// icount is not adjusted yet, everything below is happening during
// the last REFRESH cycle: +2px(1t of 3.5MHz)
u8 snow_pattern = 0;
u8 t_to_chunk_end = 0;
if (x % 16 == 0x00)
{
snow_pattern = 1;
t_to_chunk_end = 4;
}
else if (x % 16 == 0x04)
{
snow_pattern = 2;
x += 8;
t_to_chunk_end = 6;
}
else
{
return;
}

const u16 px_addr_hi = ((y & 0xc0) << 5) | ((y & 7) << 8) | ((y & 0x20) << 2);
const u16 attr_addr_hi = 0x1800 | ((y & 0xe0) << 2);
const u8 addr_lo = ((y & 0x18) << 2) | (x >> 3);

const u8 px_tmp = m_screen_location[px_addr_hi | addr_lo];
const u8 attr_tmp = m_screen_location[attr_addr_hi | addr_lo];

if (snow_pattern == 1)
{
const u8 r = (offset + 1) & 0x7f; // R must be already incremented during refresh. Consider z80 update.
const u8* base = snow_pattern1_base(i);
m_screen_location[px_addr_hi | addr_lo] = base[px_addr_hi | r];
m_screen_location[attr_addr_hi | addr_lo] = base[attr_addr_hi | r];
}
else if (snow_pattern == 2)
{
m_screen_location[px_addr_hi | addr_lo] = m_screen_location[(px_addr_hi | addr_lo) - 1];
m_screen_location[attr_addr_hi | addr_lo] = m_screen_location[(attr_addr_hi | addr_lo) - 1];
}

m_maincpu->adjust_icount(-t_to_chunk_end);
m_screen->update_now();
m_maincpu->adjust_icount(t_to_chunk_end);
m_screen_location[px_addr_hi | addr_lo] = px_tmp;
m_screen_location[attr_addr_hi | addr_lo] = attr_tmp;
}

u8* spectrum_state::snow_pattern1_base(u8 i_reg)
{
return m_screen_location;
}
Loading