scn2674: Row buffering and timing improvements

- Add optional read callbacks for row buffering DMA
- Add MBC output
- Correct timing of BREQ and VBLANK outputs
- Improve character blink and cursor blink timings

wy50: Power-up tests pass now; very preliminary character display (nw)
This commit is contained in:
AJR 2019-02-09 17:49:28 -05:00
parent 83bd138bec
commit 98978eacf3
3 changed files with 269 additions and 146 deletions

View File

@ -44,12 +44,15 @@ scn2674_device::scn2674_device(const machine_config &mconfig, device_type type,
, device_memory_interface(mconfig, *this)
, m_intr_cb(*this)
, m_breq_cb(*this)
, m_mbc_cb(*this)
, m_mbc_char_cb(*this)
, m_mbc_attr_cb(*this)
, m_IR_pointer(0)
, m_screen1_address(0), m_screen2_address(0)
, m_cursor_address(0)
, m_irq_register(0), m_status_register(0), m_irq_mask(0)
, m_gfx_enabled(false)
, m_display_enabled(false), m_display_enabled_field(false), m_display_enabled_scanline(false)
, m_display_enabled(false), m_dadd_enabled(false), m_display_enabled_field(false), m_display_enabled_scanline(false)
, m_cursor_enabled(false)
, m_hpixels_per_column(0)
, m_double_ht_wd(false)
@ -83,6 +86,8 @@ scn2674_device::scn2674_device(const machine_config &mconfig, device_type type,
, m_char_buffer(0), m_attr_buffer(0)
, m_linecounter(0), m_address(0), m_start1change(0)
, m_scanline_timer(nullptr)
, m_breq_timer(nullptr)
, m_vblank_timer(nullptr)
, m_char_space(nullptr), m_attr_space(nullptr)
, m_char_space_config("charram", ENDIANNESS_LITTLE, 8, extend_addressing ? 16 : 14, 0, address_map_constructor(), address_map_constructor(FUNC(scn2674_device::scn2674_vram), this))
, m_attr_space_config("attrram", ENDIANNESS_LITTLE, 8, extend_addressing ? 16 : 14, 0, address_map_constructor(), address_map_constructor(FUNC(scn2674_device::scn2674_vram), this))
@ -105,7 +110,12 @@ void scn2674_device::device_start()
m_display_cb.bind_relative_to(*owner());
m_intr_cb.resolve_safe();
m_breq_cb.resolve_safe();
m_scanline_timer = timer_alloc(TIMER_SCANLINE);
m_mbc_cb.resolve_safe();
m_mbc_char_cb.resolve();
m_mbc_attr_cb.resolve();
m_scanline_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(scn2674_device::scanline_timer), this));
m_breq_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(scn2674_device::breq_timer), this));
m_vblank_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(scn2674_device::vblank_timer), this));
screen().register_screen_bitmap(m_bitmap);
m_char_space = &space(0);
@ -123,6 +133,7 @@ void scn2674_device::device_start()
save_item(NAME(m_irq_mask));
save_item(NAME(m_gfx_enabled));
save_item(NAME(m_display_enabled));
save_item(NAME(m_dadd_enabled));
save_item(NAME(m_display_enabled_field));
save_item(NAME(m_display_enabled_scanline));
save_item(NAME(m_cursor_enabled));
@ -174,6 +185,7 @@ void scn2674_device::device_reset()
m_irq_mask = 0;
m_gfx_enabled = false;
m_display_enabled = false;
m_dadd_enabled = false;
m_display_enabled_field = false;
m_display_enabled_scanline = false;
m_cursor_enabled = false;
@ -551,9 +563,15 @@ void scn2674_device::set_display_enabled(bool enabled, int n)
m_display_enabled = false;
if (n == 1)
{
LOGMASKED(LOG_COMMAND, "%s: Display off - float DADD bus\n", machine().describe_context());
m_dadd_enabled = false;
}
else
{
LOGMASKED(LOG_COMMAND, "%s: Display off - no float DADD bus\n", machine().describe_context());
m_dadd_enabled = true;
}
}
else
{
@ -766,6 +784,7 @@ void scn2674_device::write_command(uint8_t data)
m_irq_mask = 0x00;
m_gfx_enabled = false;
m_display_enabled = false;
m_dadd_enabled = false;
m_cursor_enabled = false;
m_use_row_table = false;
m_intr_cb(CLEAR_LINE);
@ -911,6 +930,8 @@ void scn2674_device::write(offs_t offset, uint8_t data)
m_screen1_address = (m_screen1_address & 0x3f00) | data;
if (!screen().vblank())
m_start1change = (m_linecounter / m_scanline_per_char_row) + 1;
else
m_start1change = 0;
break;
case 3:
@ -924,6 +945,8 @@ void scn2674_device::write(offs_t offset, uint8_t data)
}
if (!screen().vblank())
m_start1change = (m_linecounter / m_scanline_per_char_row) + 1;
else
m_start1change = 0;
break;
case 4:
@ -967,145 +990,191 @@ void scn2674_device::recompute_parameters()
rectangle visarea(0, max_visible_x, 0, max_visible_y);
screen().configure(horiz_pix_total, vert_pix_total, visarea, refresh.as_attoseconds());
m_scanline_timer->adjust(screen().time_until_pos(0, 0), 0, screen().scan_period());
m_linecounter = screen().vpos();
m_scanline_timer->adjust(screen().time_until_pos((m_linecounter + 1) % vert_pix_total, 0), 0, screen().scan_period());
}
void scn2674_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
TIMER_CALLBACK_MEMBER(scn2674_device::scanline_timer)
{
switch (id)
int dw = m_double_ht_wd ? m_double[0] : 0; // double width
if (((m_display_enabled_scanline) || (m_display_enabled_field && !m_interlace_enable)) && (!m_display_enabled))
{
case TIMER_SCANLINE:
m_display_enabled = true;
m_dadd_enabled = true;
m_display_enabled_scanline = false;
m_display_enabled_field = false;
}
m_linecounter++;
if (m_linecounter >= screen().height())
{
m_linecounter = 0;
m_address = m_screen1_address;
}
bool lastscan = m_linecounter == (m_rows_per_screen * m_scanline_per_char_row) - 1;
if (lastscan)
{
int horz_sync_begin = 2 * (m_equalizing_constant + 2 * m_horz_sync_width) - m_horz_sync_width - m_horz_back_porch;
m_vblank_timer->adjust(clocks_to_attotime(horz_sync_begin));
}
if (m_linecounter >= (m_rows_per_screen * m_scanline_per_char_row))
{
if (m_buffer_mode_select == 3 && m_linecounter == (screen().height() - 1))
m_breq_timer->adjust(clocks_to_attotime(m_character_per_row + 1), ASSERT_LINE);
return;
}
int charrow = m_linecounter % m_scanline_per_char_row;
int tilerow = charrow;
// should be triggered at the start of each ROW (line zero for that row)
if (charrow == 0)
{
m_status_register |= 0x08;
if (BIT(m_irq_mask, 3))
{
int dw = m_double_ht_wd ? m_double[0] : 0; // double width
if (((m_display_enabled_scanline) || (m_display_enabled_field && !m_interlace_enable)) && (!m_display_enabled))
{
m_display_enabled = true;
m_display_enabled_scanline = false;
m_display_enabled_field = false;
}
m_linecounter++;
if (m_linecounter >= screen().height())
{
m_linecounter = 0;
m_address = m_screen1_address;
}
if (m_linecounter == (m_rows_per_screen * m_scanline_per_char_row))
{
m_status_register |= 0x10;
if (BIT(m_irq_mask, 4))
{
LOGMASKED(LOG_INTR, "V-Blank interrupt at line %d\n", m_linecounter);
m_irq_register |= 0x10;
m_intr_cb(ASSERT_LINE);
}
}
if (m_linecounter >= (m_rows_per_screen * m_scanline_per_char_row))
break;
int charrow = m_linecounter % m_scanline_per_char_row;
int tilerow = charrow;
// should be triggered at the start of each ROW (line zero for that row)
if (charrow == 0)
{
m_status_register |= 0x08;
if (BIT(m_irq_mask, 3))
{
LOGMASKED(LOG_INTR, "Line Zero interrupt at line %d\n", m_linecounter);
m_irq_register |= 0x08;
m_intr_cb(ASSERT_LINE);
}
if (m_buffer_mode_select == 3)
m_breq_cb(ASSERT_LINE);
}
else if (m_buffer_mode_select == 3)
m_breq_cb(CLEAR_LINE);
// Handle screen splits
for (int s = 0; s < 2; s++)
{
if ((m_linecounter == ((m_split_register[s] + 1) * m_scanline_per_char_row)) && m_linecounter)
{
uint8_t flag = (s == 0) ? 0x04 : 0x01;
m_status_register |= flag;
if ((m_irq_mask & flag) != 0)
{
LOGMASKED(LOG_INTR, "Split Screen %d interrupt at line %d\n", s + 1, m_linecounter);
m_irq_register |= flag;
m_intr_cb(ASSERT_LINE);
}
if (m_spl[s])
m_address = m_screen2_address;
if (!m_double_ht_wd)
dw = m_double[s];
}
}
if (!m_display_enabled)
break;
if (m_use_row_table)
{
if (m_double_ht_wd)
dw = m_screen1_address >> 14;
if (!charrow)
{
uint16_t addr = m_screen2_address;
uint16_t line = m_char_space->read_word(addr);
m_screen1_address = line;
if (m_double_ht_wd)
{
dw = line >> 14;
line &= ~0xc000;
}
m_address = line;
addr += 2;
m_screen2_address = addr & 0x3fff;
}
}
else if (m_start1change && (m_start1change == (m_linecounter / m_scanline_per_char_row)))
{
m_address = m_screen1_address;
m_start1change = 0;
}
if (dw == 2)
tilerow >>= 1;
else if (dw == 3)
tilerow = (charrow + m_scanline_per_char_row) >> 1;
uint16_t address = m_address;
for (int i = 0; i < m_character_per_row; i++)
{
bool cursor_on = ((address & 0x3fff) == m_cursor_address);
if (!m_display_cb.isnull())
m_display_cb(m_bitmap,
i * m_hpixels_per_column,
m_linecounter,
tilerow,
m_char_space->read_byte(address),
m_attr_space != nullptr ? m_attr_space->read_byte(address) : 0,
address,
(charrow >= m_cursor_first_scanline) && (charrow <= m_cursor_last_scanline) && cursor_on,
dw != 0,
m_gfx_enabled,
charrow == m_cursor_underline_position,
m_cursor_blink && (screen().frame_number() & m_cursor_rate_divisor));
address = (address + 1) & 0xffff;
if (address > ((m_display_buffer_last_address << 10) | 0x3ff))
address = m_display_buffer_first_address;
}
if (m_gfx_enabled || (charrow == (m_scanline_per_char_row - 1)))
m_address = address;
LOGMASKED(LOG_INTR, "Line Zero interrupt at line %d\n", m_linecounter);
m_irq_register |= 0x08;
m_intr_cb(ASSERT_LINE);
}
if (m_buffer_mode_select == 3)
{
m_mbc_cb(1);
m_breq_timer->adjust(clocks_to_attotime(m_character_per_row + 1), CLEAR_LINE);
}
}
else if (m_buffer_mode_select == 3 && charrow == (m_scanline_per_char_row - 1) && !lastscan)
m_breq_timer->adjust(clocks_to_attotime(m_character_per_row + 1), ASSERT_LINE);
// Handle screen splits
for (int s = 0; s < 2; s++)
{
if ((m_linecounter == ((m_split_register[s] + 1) * m_scanline_per_char_row)) && m_linecounter)
{
uint8_t flag = (s == 0) ? 0x04 : 0x01;
m_status_register |= flag;
if ((m_irq_mask & flag) != 0)
{
LOGMASKED(LOG_INTR, "Split Screen %d interrupt at line %d\n", s + 1, m_linecounter);
m_irq_register |= flag;
m_intr_cb(ASSERT_LINE);
}
if (m_spl[s])
m_address = m_screen2_address;
if (!m_double_ht_wd)
dw = m_double[s];
}
}
// WY-50 requires that normal row buffering take place even after a "display off" command
if (!m_dadd_enabled)
return;
if (m_use_row_table)
{
if (m_double_ht_wd)
dw = m_screen1_address >> 14;
if (!charrow)
{
uint16_t addr = m_screen2_address;
uint16_t line = m_char_space->read_word(addr);
m_screen1_address = line;
if (m_double_ht_wd)
{
dw = line >> 14;
line &= ~0xc000;
}
m_address = line;
addr += 2;
m_screen2_address = addr & 0x3fff;
}
}
else if (m_start1change && (m_start1change == (m_linecounter / m_scanline_per_char_row)))
{
m_address = m_screen1_address;
m_start1change = 0;
}
if (dw == 2)
tilerow >>= 1;
else if (dw == 3)
tilerow = (charrow + m_scanline_per_char_row) >> 1;
uint16_t address = m_address;
const bool mbc = (charrow == 0) && (m_buffer_mode_select == 3);
const bool blink_on = (screen().frame_number() & (m_character_blink_rate_divisor >> 1)) != 0;
for (int i = 0; i < m_character_per_row; i++)
{
u8 charcode, attrcode = 0;
if (mbc && !m_mbc_char_cb.isnull())
{
// row buffering DMA
charcode = m_mbc_char_cb(address);
m_char_space->write_byte(address, charcode);
if (m_attr_space != nullptr && !m_mbc_attr_cb.isnull())
{
attrcode = m_mbc_attr_cb(address);
m_attr_space->write_byte(address, attrcode);
}
}
else
{
charcode = m_char_space->read_byte(address);
if (m_attr_space != nullptr)
attrcode = m_attr_space->read_byte(address);
}
if (m_display_enabled && !m_display_cb.isnull())
{
bool cursor_on = ((address & 0x3fff) == m_cursor_address)
&& m_cursor_enabled
&& (charrow >= m_cursor_first_scanline)
&& (charrow <= m_cursor_last_scanline)
&& (!m_cursor_blink || (screen().frame_number() & (m_cursor_rate_divisor >> 1)) != 0);
m_display_cb(m_bitmap,
i * m_hpixels_per_column,
m_linecounter,
tilerow,
charcode,
attrcode,
address,
cursor_on,
dw != 0,
m_gfx_enabled,
charrow == m_cursor_underline_position,
blink_on);
}
address = (address + 1) & 0xffff;
if (address > ((m_display_buffer_last_address << 10) | 0x3ff))
address = m_display_buffer_first_address;
}
if (m_gfx_enabled || (charrow == (m_scanline_per_char_row - 1)))
m_address = address;
}
TIMER_CALLBACK_MEMBER(scn2674_device::breq_timer)
{
LOGMASKED(LOG_INTR, "BREQ %sasserted at line %d\n", (param == ASSERT_LINE) ? "" : "de", m_linecounter);
m_breq_cb(param);
if (param == CLEAR_LINE)
m_mbc_cb(0);
}
TIMER_CALLBACK_MEMBER(scn2674_device::vblank_timer)
{
m_status_register |= 0x10;
if (BIT(m_irq_mask, 4))
{
LOGMASKED(LOG_INTR, "V-Blank interrupt at line %d\n", m_linecounter);
m_irq_register |= 0x10;
m_intr_cb(ASSERT_LINE);
}
}

View File

@ -23,6 +23,9 @@ public:
// static configuration
auto intr_callback() { return m_intr_cb.bind(); }
auto breq_callback() { return m_breq_cb.bind(); }
auto mbc_callback() { return m_mbc_cb.bind(); }
auto mbc_char_callback() { return m_mbc_char_cb.bind(); }
auto mbc_attr_callback() { return m_mbc_attr_cb.bind(); }
void set_character_width(int value) { m_hpixels_per_column = value; }
template <class FunctionClass>
@ -54,14 +57,20 @@ protected:
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
virtual space_config_vector memory_space_config() const override;
TIMER_CALLBACK_MEMBER(scanline_timer);
TIMER_CALLBACK_MEMBER(breq_timer);
TIMER_CALLBACK_MEMBER(vblank_timer);
//protected:
bitmap_rgb32 m_bitmap;
devcb_write_line m_intr_cb;
devcb_write_line m_breq_cb;
devcb_write_line m_mbc_cb;
devcb_read8 m_mbc_char_cb;
devcb_read8 m_mbc_attr_cb;
uint8_t m_IR_pointer;
uint16_t m_screen1_address;
@ -72,6 +81,7 @@ protected:
uint8_t m_irq_mask;
bool m_gfx_enabled;
bool m_display_enabled;
bool m_dadd_enabled;
bool m_display_enabled_field;
bool m_display_enabled_scanline;
bool m_cursor_enabled;
@ -129,14 +139,12 @@ protected:
draw_character_delegate m_display_cb;
emu_timer *m_scanline_timer;
emu_timer *m_breq_timer;
emu_timer *m_vblank_timer;
address_space *m_char_space;
address_space *m_attr_space;
const address_space_config m_char_space_config;
const address_space_config m_attr_space_config;
enum
{
TIMER_SCANLINE
};
};
class scn2672_device : public scn2674_device

View File

@ -41,6 +41,7 @@ public:
, m_pvtc(*this, "pvtc")
, m_sio(*this, "sio")
, m_chargen(*this, "chargen")
, m_videoram(*this, "videoram%u", 0U)
{
}
@ -51,6 +52,7 @@ protected:
virtual void machine_reset() override;
private:
u8 pvtc_videoram_r(offs_t offset);
SCN2672_DRAW_CHARACTER_MEMBER(draw_character);
u8 pvtc_r(offs_t offset);
@ -65,6 +67,7 @@ private:
void prg_map(address_map &map);
void io_map(address_map &map);
void row_buffer_map(address_map &map);
required_device<mcs51_cpu_device> m_maincpu;
required_device<er1400_device> m_earom;
@ -72,10 +75,19 @@ private:
required_device<mc2661_device> m_sio;
required_region_ptr<u8> m_chargen;
required_shared_ptr_array<u8, 2> m_videoram;
u8 m_row_buffer_char;
bool m_is_132;
};
void wy50_state::machine_start()
{
m_row_buffer_char = 0;
m_is_132 = false;
save_item(NAME(m_row_buffer_char));
save_item(NAME(m_is_132));
}
void wy50_state::machine_reset()
@ -84,8 +96,27 @@ void wy50_state::machine_reset()
earom_w(0);
}
u8 wy50_state::pvtc_videoram_r(offs_t offset)
{
m_row_buffer_char = m_videoram[BIT(offset, 13)][offset & 0x07ff];
return m_row_buffer_char;
}
SCN2672_DRAW_CHARACTER_MEMBER(wy50_state::draw_character)
{
bool is_attr = (charcode & 0xe0) == 0x80;
u16 dots = is_attr ? 0 : m_chargen[charcode << 4 | linecount] << 1;
if (cursor)
dots = ~dots;
for (int i = 0; i < 9; i++)
{
bitmap.pix32(y, x++) = BIT(dots, 8) ? rgb_t::white() : rgb_t::black();
dots <<= 1;
}
if (!m_is_132)
bitmap.pix32(y, x++) = BIT(dots, 8) ? rgb_t::white() : rgb_t::black();
}
u8 wy50_state::pvtc_r(offs_t offset)
@ -111,7 +142,7 @@ void wy50_state::sio_w(offs_t offset, u8 data)
u8 wy50_state::rbreg_r()
{
// LS374 row buffer diagnostic register
return 0;
return m_row_buffer_char;
}
void wy50_state::keyboard_w(u8 data)
@ -132,8 +163,8 @@ u8 wy50_state::p1_r()
{
// P1.0 = AUX RDY
// P1.1 = NVD OUT
// P1.4 = KEY (inverted)
return 0xfd | (m_earom->data_r() << 1);
// P1.4 = KEY (inverted, active high)
return 0xed | (m_earom->data_r() << 1);
}
void wy50_state::p1_w(u8 data)
@ -143,6 +174,13 @@ void wy50_state::p1_w(u8 data)
// P1.5 = BEEPER
// P1.6 = REV/DIM PROT
// P1.7 (inverted) = 80/132
if (m_is_132 != BIT(data, 7))
{
m_is_132 = BIT(data, 7);
m_pvtc->set_character_width(m_is_132 ? 9 : 10);
m_pvtc->set_unscaled_clock(68.85_MHz_XTAL / (m_is_132 ? 20 : 30));
}
}
void wy50_state::prg_map(address_map &map)
@ -152,8 +190,8 @@ void wy50_state::prg_map(address_map &map)
void wy50_state::io_map(address_map &map)
{
map(0x0000, 0x07ff).mirror(0x1800).ram();
map(0x2000, 0x27ff).mirror(0x1800).ram();
map(0x0000, 0x07ff).mirror(0x1800).ram().share("videoram0");
map(0x2000, 0x27ff).mirror(0x1800).ram().share("videoram1");
map(0x4000, 0x47ff).mirror(0x1800).rw(FUNC(wy50_state::pvtc_r), FUNC(wy50_state::pvtc_w));
map(0x6000, 0x63ff).mirror(0x1c00).rw(FUNC(wy50_state::sio_r), FUNC(wy50_state::sio_w));
map(0x8000, 0x8000).mirror(0x1fff).r(FUNC(wy50_state::rbreg_r));
@ -161,6 +199,12 @@ void wy50_state::io_map(address_map &map)
map(0xc000, 0xc000).mirror(0x1fff).w(FUNC(wy50_state::earom_w));
}
void wy50_state::row_buffer_map(address_map &map)
{
map.global_mask(0x0ff);
map(0x000, 0x0ff).ram();
}
static INPUT_PORTS_START(wy50)
INPUT_PORTS_END
@ -183,12 +227,14 @@ void wy50_state::wy50(machine_config &config)
SCN2672(config, m_pvtc, 68.85_MHz_XTAL / 30); // SCN2672A or SCN2672B
m_pvtc->set_screen("screen");
m_pvtc->set_character_width(10); // 9 in 132-column mode
m_pvtc->set_addrmap(0, &wy50_state::row_buffer_map);
m_pvtc->set_display_callback(FUNC(wy50_state::draw_character));
m_pvtc->intr_callback().set_inputline(m_maincpu, MCS51_T0_LINE);
m_pvtc->breq_callback().set_inputline(m_maincpu, MCS51_INT0_LINE);
m_pvtc->mbc_char_callback().set(FUNC(wy50_state::pvtc_videoram_r));
MC2661(config, m_sio, 4.9152_MHz_XTAL); // SCN2661B
m_sio->txrdy_handler().set_inputline(m_maincpu, MCS51_INT1_LINE);
m_sio->rxrdy_handler().set_inputline(m_maincpu, MCS51_INT1_LINE);
}
ROM_START(wy50)