hp_ipc: added configurable VRAM size, auto configuration & a few minor

commands to HP1LL3 GPU
This commit is contained in:
fulivi 2018-08-14 11:00:01 +02:00 committed by Vas Crabb
parent 1db10c9857
commit 717115443c
3 changed files with 184 additions and 82 deletions

View File

@ -19,13 +19,6 @@
- windows with blitter (copy, fill and scroll) and clipping
To do:
. proper cursor and mouse pointers [cursor can be offset from the pen location]
+ variable width fonts [?? placed relative to current window]
+ basic lines
- patterned lines
. bit blits & scroll
. meaning of WRRR bits
. meaning of CONF data [+ autoconfiguration]
- interrupt generation
- realistic timing?
- &c.
@ -145,9 +138,6 @@ constexpr uint16_t MSB_MASK = 0x8000; // Mask of MSB
} \
} while (0)
// This is the size for 9807. To be made configurable.
#define HPGPU_VRAM_SIZE 16384
// Index of words in configuration vector
enum : unsigned
{
@ -161,18 +151,18 @@ enum : unsigned
CONF_VER_3 = 7, // Vertical timing, 4th word
CONF_WPL = 8, // Words per line
CONF_LINES = 9, // Lines
CONF_RAM_SIZE = 10 // RAM size (?)
CONF_OPTS = 10 // RAM size or option bits (more likely)
};
// Fields of hor/ver timing words
enum : uint16_t
{
HV_CNT_MASK = 0x3ff, // Mask of counter part
HV_ZONE_MASK = 0xc000, // Mask of zone part
HV_ZONE_0 = 0x0000, // Value for zone 0
HV_ZONE_1 = 0x4000, // Value for zone 1
HV_ZONE_2 = 0x8000, // Value for zone 2
HV_ZONE_3 = 0xc000 // Value for zone 3
HV_ZONE_MASK = 0xc00, // Mask of zone part
HV_ZONE_0 = 0x000, // Value for zone 0
HV_ZONE_1 = 0x400, // Value for zone 1
HV_ZONE_2 = 0x800, // Value for zone 2
HV_ZONE_3 = 0xc00 // Value for zone 3
};
//**************************************************************************
@ -194,10 +184,10 @@ DEFINE_DEVICE_TYPE(HP1LL3, hp1ll3_device, "hp1ll3", "Hewlett-Packard 1LL3-0005 G
hp1ll3_device::hp1ll3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, HP1LL3, tag, owner, clock)
, device_video_interface(mconfig, *this)
, m_vram_size(16)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
@ -211,17 +201,17 @@ void hp1ll3_device::device_start()
screen().register_screen_bitmap(m_bitmap);
m_videoram = std::make_unique<uint16_t[]>(HPGPU_VRAM_SIZE);
m_videoram = std::make_unique<uint16_t[]>(m_vram_size);
save_pointer(NAME(m_videoram), HPGPU_VRAM_SIZE);
m_ram_addr_mask = 0x3fff;
save_pointer(NAME(m_videoram) , m_vram_size);
m_ram_addr_mask = uint16_t(m_vram_size - 1);
m_horiz_pix_total = visarea.max_x + 1;
m_vert_pix_total = visarea.max_y + 1;
}
void hp1ll3_device::device_reset()
{
m_io_ptr = m_command = 0;
m_rd_ptr = m_wr_ptr = m_command = 0;
m_sad = m_fad = m_dad = m_org = m_rr = m_udl = 0;
m_enable_video = m_enable_cursor = m_enable_sprite = m_busy = false;
}
@ -357,13 +347,23 @@ void hp1ll3_device::line(int x1, int y1, int x2, int y2)
}
}
void hp1ll3_device::get_font(uint16_t& font_data, uint16_t& font_height) const
{
font_data = m_fad + rd_video(m_fad + 1) + 2;
font_height = rd_video(m_fad);
}
void hp1ll3_device::label(uint8_t chr, int width)
{
draw_cursor_sprite();
uint16_t font_data;
uint16_t font_height;
get_font(font_data, font_height);
Rectangle clip = get_window();
Rectangle dst{{ m_cursor_x, m_cursor_y }, { uint16_t(width), m_fontheight }};
bitblt(m_fontdata + chr * 16, 16, 16, Point { 0, 0 }, clip, dst, m_rr);
Rectangle dst{{ m_cursor_x, m_cursor_y }, { uint16_t(width), font_height }};
bitblt(font_data + chr * 16, 16, 16, Point { 0, 0 }, clip, dst, m_rr);
m_cursor_x += width;
draw_cursor_sprite();
}
@ -639,6 +639,18 @@ void hp1ll3_device::set_sprite_pos(Point p)
}
}
void hp1ll3_device::disable_cursor()
{
draw_cursor();
m_enable_cursor = false;
}
void hp1ll3_device::disable_sprite()
{
draw_sprite();
m_enable_sprite = false;
}
hp1ll3_device::Rectangle hp1ll3_device::get_window() const
{
return Rectangle{{ m_window.org_x, m_window.org_y }, { m_window.width, m_window.height }};
@ -649,9 +661,49 @@ hp1ll3_device::Rectangle hp1ll3_device::get_screen() const
return Rectangle{{ 0, 0 }, { uint16_t(m_horiz_pix_total), uint16_t(m_vert_pix_total) }};
}
void hp1ll3_device::get_hv_timing(bool vertical, unsigned& total, unsigned& active) const
{
unsigned idx = vertical ? CONF_VER_0 : CONF_HOR_0;
// Look for active part (zone 1) & compute total time
total = 0;
active = 0;
for (unsigned i = 0; i < 4; ++i) {
unsigned cnt = (m_conf[idx + i] & HV_CNT_MASK) + 1;
total += cnt;
if ((m_conf[idx + i] & HV_ZONE_MASK) == HV_ZONE_1) {
active = cnt;
// It seems that if a zone 2 comes immediately before a zone 1, it's
// counted in the active part
unsigned idx_1 = (i + 3) % 4 + idx;
if ((m_conf[idx_1] & HV_ZONE_MASK) == HV_ZONE_2) {
active += (m_conf[idx_1] & HV_CNT_MASK) + 1;
}
}
}
DBG_LOG(2, 0, ("H/V=%d total=%u active=%u\n", vertical, total, active));
}
void hp1ll3_device::apply_conf()
{
// TODO:
unsigned h_total;
unsigned h_active;
unsigned v_total;
unsigned v_active;
get_hv_timing(false, h_total, h_active);
get_hv_timing(true, v_total, v_active);
// Dot clock is 4 times the GPU clock, H timings are in units of half the GPU clock
// I suspect that the GPU has a kind of "double clock" mode that is enabled by option bits in the last conf word.
// Without clock doubling, the HP-9808A video has a strange frame rate (~32Hz).
h_total *= 8;
h_active *= 8;
attotime frame_rate{clocks_to_attotime(h_total * v_total)};
screen().configure(h_total, v_total, rectangle{0, int32_t(h_active - 1), 0, int32_t(v_active - 1)}, frame_rate.attoseconds() / 4);
m_horiz_pix_total = h_active;
m_vert_pix_total = v_active;
}
uint32_t hp1ll3_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
@ -662,10 +714,9 @@ uint32_t hp1ll3_device::screen_update(screen_device &screen, bitmap_ind16 &bitma
return 0;
}
// XXX last line is not actually drawn on real hw
for (int y = 0; y < m_vert_pix_total; y++)
{
int const offset = m_sad + y * (m_horiz_pix_total / 16);
int const offset = m_sad + y*m_conf[CONF_WPL];
uint16_t *p = &m_bitmap.pix16(y);
for (int x = offset; x < offset + m_horiz_pix_total / 16; x++)
@ -723,7 +774,7 @@ READ8_MEMBER( hp1ll3_device::read )
}
else
{
if (!(m_io_ptr & 1))
if (!(m_rd_ptr & 1))
{
switch (m_command)
{
@ -762,19 +813,39 @@ READ8_MEMBER( hp1ll3_device::read )
break;
case RDP:
if (m_io_ptr == 0)
if (m_rd_ptr == 0)
m_io_word = m_cursor_x;
else if (m_io_ptr == 2)
else if (m_rd_ptr == 2)
m_io_word = m_cursor_y;
break;
case RDSP:
if (m_io_ptr == 0)
if (m_rd_ptr == 0)
m_io_word = m_sprite_x;
else if (m_io_ptr == 2)
else if (m_rd_ptr == 2)
m_io_word = m_sprite_y;
break;
case RDWINPARM:
switch (m_rd_ptr)
{
case 0:
m_io_word = m_window.org_x;
break;
case 2:
m_io_word = m_window.org_y;
break;
case 4:
m_io_word = m_window.width;
break;
case 6:
m_io_word = m_window.height;
break;
default:
break;
}
break;
case ID:
/*
* 'diagb' ROM accepts either of these ID values
@ -788,7 +859,7 @@ READ8_MEMBER( hp1ll3_device::read )
{
data = m_io_word & 0xff;
}
m_io_ptr++;
m_rd_ptr++;
}
DBG_LOG(1,"HPGPU", ("R @ %d == %02x\n", offset, data));
@ -811,7 +882,7 @@ WRITE8_MEMBER( hp1ll3_device::write )
}
else
{
if (!(m_io_ptr & 1))
if (!(m_wr_ptr & 1))
{
m_io_word = uint16_t(data) << 8;
}
@ -821,11 +892,11 @@ WRITE8_MEMBER( hp1ll3_device::write )
switch (m_command)
{
case CONF:
m_conf[m_io_ptr >> 1] = m_io_word;
DBG_LOG(1,"HPGPU",("CONF data word %d received: %04X\n", m_io_ptr >> 1, m_io_word));
if ((m_io_ptr >> 1) == 10) {
m_conf[m_wr_ptr >> 1] = m_io_word;
DBG_LOG(1,"HPGPU",("CONF data word %d received: %04X\n", m_wr_ptr >> 1, m_io_word));
if ((m_wr_ptr >> 1) == 10) {
apply_conf();
m_io_ptr = -1;
m_wr_ptr = -1;
m_command = NOP;
}
break;
@ -844,10 +915,7 @@ WRITE8_MEMBER( hp1ll3_device::write )
unsigned bit = m_rw_win_x % WS;
int width = m_window.width + m_window.org_x - m_rw_win_x;
if (width <= 0)
{
m_command = NOP;
draw_cursor_sprite();
}
else
{
unsigned w = std::min(WS, unsigned(width));
@ -871,22 +939,19 @@ WRITE8_MEMBER( hp1ll3_device::write )
m_rw_win_x = m_window.org_x;
m_rw_win_y++;
if (m_rw_win_y >= (m_window.height + m_window.org_y))
{
m_command = NOP;
draw_cursor_sprite();
}
}
}
}
break;
default:
if (m_io_ptr <= 4)
m_input[m_io_ptr / 2] = m_io_word;
DBG_LOG(2,"HPGPU",("wrote %02x at %d, input buffer is %04X %04X\n", data, m_io_ptr, m_input[0], m_input[1]));
if (m_wr_ptr <= 4)
m_input[m_wr_ptr / 2] = m_io_word;
DBG_LOG(2,"HPGPU",("wrote %02x at %d, input buffer is %04X %04X\n", data, m_wr_ptr, m_input[0], m_input[1]));
}
}
m_io_ptr++;
m_wr_ptr++;
}
}
@ -909,10 +974,6 @@ void hp1ll3_device::command(int command)
case WRMEM:
DBG_LOG(1,"HPGPU",("WRMEM of %d words to %04X complete\n", m_memory_ptr - m_input[0], m_input[0]));
break;
case WRWIN:
draw_cursor_sprite();
break;
}
break;
@ -930,8 +991,7 @@ void hp1ll3_device::command(int command)
case DISSP:
DBG_LOG(2,"HPGPU",("command: DISSP [%d, 0x%x]\n", command, command));
draw_sprite();
m_enable_sprite = false;
disable_sprite();
break;
case ENSP:
@ -945,8 +1005,7 @@ void hp1ll3_device::command(int command)
case DISCURS:
DBG_LOG(2,"HPGPU",("command: DISCURS [%d, 0x%x]\n", command, command));
draw_cursor();
m_enable_cursor = false;
disable_cursor();
break;
// type 1 commands -- 1 word of data expected in the buffer
@ -961,13 +1020,11 @@ void hp1ll3_device::command(int command)
case WRFAD:
DBG_LOG(2,"HPGPU",("command: WRFAD [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
m_fad = m_input[0];
break;
// ?? used by diagnostic ROM
case 6:
m_fontheight = rd_video(m_fad);
m_fontdata = m_fad + rd_video(m_fad + 1) + 2;
DBG_LOG(1,"HPGPU",("font data set: FAD %04X header %d bitmaps %04X height %d\n",
m_fad, rd_video(m_fad + 1), m_fontdata, m_fontheight));
DBG_LOG(1,"HPGPU",("command: 6\n"));
break;
// start of data area
@ -1018,19 +1075,15 @@ void hp1ll3_device::command(int command)
// Write window
case WRWIN:
DBG_LOG(2,"HPGPU",("command: WRWIN [%d, 0x%x] offset=%u size(%d,%d)\n",command, command, m_input[0], m_window.width, m_window.height));
// m_input[0] is offset (to be implemented)
m_rw_win_x = m_window.org_x;
m_rw_win_y = m_window.org_y;
draw_cursor_sprite();
break;
// Read window
case RDWIN:
DBG_LOG(2,"HPGPU",("command: RDWIN [%d, 0x%x] offset=%u\n",command, command, m_input[0]));
DBG_LOG(2,"HPGPU",("command: %s [%d, 0x%x] offset=%u size(%d,%d)\n",
command == RDWIN ? "RDWIN":"WRWIN", command, command, m_input[0], m_window.width, m_window.height));
// m_input[0] is offset (to be implemented)
m_rw_win_x = m_window.org_x;
m_rw_win_y = m_window.org_y;
disable_cursor();
disable_sprite();
break;
// type 2 commands -- 2 words of data expected in the buffer
@ -1079,6 +1132,13 @@ void hp1ll3_device::command(int command)
m_saved_x = m_cursor_x;
break;
// move pointer incremental
case IMOVEP:
DBG_LOG(2,"HPGPU",("command: IMOVEP [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
set_pen_pos(Point{ uint16_t(m_cursor_x + m_input[0]), uint16_t(m_cursor_y + m_input[1]) });
m_saved_x = m_cursor_x;
break;
// Scroll UP
case SCROLUP:
{
@ -1087,7 +1147,8 @@ void hp1ll3_device::command(int command)
Point src_p{ m_window.org_x, uint16_t(m_window.org_y + m_input[1]) };
Rectangle dst_rect = get_window();
dst_rect.size.y -= m_input[1];
bitblt(m_org, m_horiz_pix_total, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
unsigned rounded_width = m_conf[CONF_WPL] * WS;
bitblt(m_org, rounded_width, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
dst_rect.origin.y += dst_rect.size.y;
dst_rect.size.y = m_input[1];
fill(dst_rect, m_input[0]);
@ -1104,7 +1165,8 @@ void hp1ll3_device::command(int command)
Rectangle dst_rect = get_window();
dst_rect.origin.y += m_input[1];
dst_rect.size.y -= m_input[1];
bitblt(m_org, m_horiz_pix_total, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
unsigned rounded_width = m_conf[CONF_WPL] * WS;
bitblt(m_org, rounded_width, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
dst_rect.origin.y = m_window.org_y;
dst_rect.size.y = m_input[1];
fill(dst_rect, m_input[0]);
@ -1124,8 +1186,15 @@ void hp1ll3_device::command(int command)
// carriage return, line feed
case CRLFx:
DBG_LOG(2,"HPGPU",("command: CRLF [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
set_pen_pos(Point{ m_saved_x, uint16_t(m_cursor_y + m_fontheight) });
{
DBG_LOG(2,"HPGPU",("command: CRLF [%d, 0x%x]\n", command, command));
uint16_t font_data;
uint16_t font_height;
get_font(font_data, font_height);
set_pen_pos(Point{ m_saved_x, uint16_t(m_cursor_y + font_height) });
}
break;
// move sprite absolute
@ -1154,11 +1223,25 @@ void hp1ll3_device::command(int command)
DBG_LOG(2,"HPGPU",("command: %s [%d, 0x%x] (0x%04x)\n",
command == RDMEM?"RDMEM":"WRMEM", command, command, m_input[0]));
m_memory_ptr = m_input[0]; // memory is word-addressable
disable_cursor();
disable_sprite();
break;
// type 3 read commands
case RDP:
DBG_LOG(2,"HPGPU",("command: RDP [%d, 0x%x]\n", command, command));
break;
case RDSP:
DBG_LOG(2,"HPGPU",("command: RDSP [%d, 0x%x]\n", command, command));
break;
case RDWINPARM:
DBG_LOG(2,"HPGPU",("command: RDWINPARM [%d, 0x%x]\n", command, command));
break;
case ID:
DBG_LOG(2,"HPGPU",("command: ID [%d, 0x%x]\n", command, command));
break;
default:
@ -1166,6 +1249,7 @@ void hp1ll3_device::command(int command)
break;
}
m_io_ptr = 0;
m_rd_ptr = 0;
m_wr_ptr = 0;
m_command = command;
}

View File

@ -25,6 +25,10 @@ public:
// construction/destruction
hp1ll3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// Configuration
// Set VRAM size (in kw). Parameter must be <= 128 and it must be a power of 2.
void set_vram_size(unsigned kw) { m_vram_size = kw * 1024; }
DECLARE_READ8_MEMBER(read);
DECLARE_WRITE8_MEMBER(write);
@ -50,6 +54,7 @@ private:
uint16_t get_pix_addr(uint16_t x, uint16_t y) const;
inline void point(int x, int y, bool pix, const uint16_t masks[]);
void get_font(uint16_t& font_data, uint16_t& font_height) const;
void label(uint8_t chr, int width);
void line(int x_from, int y_from, int x_to, int y_to);
void wr_video(uint16_t addr, uint16_t v);
@ -69,19 +74,22 @@ private:
void draw_cursor_sprite();
void set_pen_pos(Point p);
void set_sprite_pos(Point p);
void disable_cursor();
void disable_sprite();
Rectangle get_window() const;
Rectangle get_screen() const;
void get_hv_timing(bool vertical, unsigned& total, unsigned& active) const;
void apply_conf();
uint16_t m_conf[11], m_input[2], m_io_word;
int m_io_ptr, m_memory_ptr;
int m_rd_ptr, m_wr_ptr, m_memory_ptr;
int m_command, m_horiz_pix_total, m_vert_pix_total;
uint16_t m_sad;
uint16_t m_org;
uint16_t m_dad;
uint16_t m_rr;
uint16_t m_fad, m_fontdata, m_fontheight;
uint16_t m_fad;
uint16_t m_udl;
bool m_enable_video, m_enable_cursor, m_enable_sprite;
@ -94,6 +102,7 @@ private:
uint16_t width, height, org_x, org_y;
} m_window;
std::unique_ptr<uint16_t[]> m_videoram;
unsigned m_vram_size;
uint16_t m_ram_addr_mask;
uint16_t m_rw_win_x, m_rw_win_y;

View File

@ -387,6 +387,7 @@ public:
, m_bankdev(*this, "bankdev")
, m_fdc(*this, "fdc")
, m_ram(*this, RAM_TAG)
, m_gpu(*this , "gpu")
, m_screen(*this, "screen")
{ }
@ -431,6 +432,7 @@ private:
required_device<address_map_bank_device> m_bankdev;
required_device<wd2797_device> m_fdc;
required_device<ram_device> m_ram;
required_device<hp1ll3_device> m_gpu;
required_device<screen_device> m_screen;
uint32_t m_mmu[4], m_lowest_ram_addr;
@ -485,7 +487,7 @@ void hp_ipc_state::hp_ipc_mem_inner_base(address_map &map)
map(0x1000000, 0x17FFFFF).rw(FUNC(hp_ipc_state::ram_r), FUNC(hp_ipc_state::ram_w));
map(0x1800000, 0x187FFFF).rom().region("maincpu", 0);
map(0x1E00000, 0x1E0FFFF).rw(FUNC(hp_ipc_state::mmu_r), FUNC(hp_ipc_state::mmu_w));
map(0x1E20000, 0x1E2000F).rw("gpu", FUNC(hp1ll3_device::read), FUNC(hp1ll3_device::write)).umask16(0x00ff);
map(0x1E20000, 0x1E2000F).rw(m_gpu, FUNC(hp1ll3_device::read), FUNC(hp1ll3_device::write)).umask16(0x00ff);
map(0x1E40000, 0x1E4002F).rw("rtc", FUNC(mm58167_device::read), FUNC(mm58167_device::write)).umask16(0x00ff);
// supervisor mode
@ -495,7 +497,7 @@ void hp_ipc_state::hp_ipc_mem_inner_base(address_map &map)
map(0x0600000, 0x060FFFF).rw(FUNC(hp_ipc_state::mmu_r), FUNC(hp_ipc_state::mmu_w));
map(0x0610000, 0x0610007).rw(FUNC(hp_ipc_state::floppy_id_r), FUNC(hp_ipc_state::floppy_id_w)).umask16(0x00ff);
map(0x0610008, 0x061000F).rw(m_fdc, FUNC(wd2797_device::read), FUNC(wd2797_device::write)).umask16(0x00ff);
map(0x0620000, 0x0620007).rw("gpu", FUNC(hp1ll3_device::read), FUNC(hp1ll3_device::write)).umask16(0x00ff);
map(0x0620000, 0x0620007).rw(m_gpu, FUNC(hp1ll3_device::read), FUNC(hp1ll3_device::write)).umask16(0x00ff);
map(0x0630000, 0x063FFFF).mask(0xf).rw("hpib" , FUNC(tms9914_device::reg8_r) , FUNC(tms9914_device::reg8_w)).umask16(0x00ff);
map(0x0640000, 0x064002F).rw("rtc", FUNC(mm58167_device::read), FUNC(mm58167_device::write)).umask16(0x00ff);
map(0x0660000, 0x06600FF).rw("mlc", FUNC(hp_hil_mlc_device::read), FUNC(hp_hil_mlc_device::write)).umask16(0x00ff); // 'caravan', scrn/caravan.h
@ -738,7 +740,7 @@ MACHINE_CONFIG_START(hp_ipc_state::hp_ipc_base)
MCFG_DEVICE_ADD("maincpu", M68000, 15.92_MHz_XTAL / 2)
MCFG_DEVICE_PROGRAM_MAP(hp_ipc_mem_outer)
HP1LL3(config , "gpu" , 24_MHz_XTAL / 8).set_screen("screen");
HP1LL3(config , m_gpu , 24_MHz_XTAL / 8).set_screen("screen");
// XXX actual clock is 1MHz; remove this workaround (and change 2000 to 100 in hp_ipc_dsk.cpp)
// XXX when floppy code correctly handles 600 rpm drives.
@ -794,6 +796,9 @@ MACHINE_CONFIG_START(hp_ipc_state::hp_ipc)
MCFG_ADDRESS_MAP_BANK_DATA_WIDTH(16)
MCFG_ADDRESS_MAP_BANK_STRIDE(0x1000000)
// 16kw/32kb video RAM
m_gpu->set_vram_size(16);
// horizontal time = 60 us (min)
// ver.refresh period = ~300 us
// ver.period = 16.7ms (~60 hz)
@ -817,12 +822,16 @@ MACHINE_CONFIG_START(hp_ipc_state::hp9808a)
MCFG_ADDRESS_MAP_BANK_DATA_WIDTH(16)
MCFG_ADDRESS_MAP_BANK_STRIDE(0x1000000)
MCFG_SCREEN_ADD_MONOCHROME("screen", LCD, rgb_t::amber()) // actually a kind of EL display
MCFG_SCREEN_UPDATE_DEVICE("gpu", hp1ll3_device, screen_update)
MCFG_SCREEN_RAW_PARAMS(6_MHz_XTAL * 2, 720, 0, 640, 480, 0, 400)
MCFG_SCREEN_VBLANK_CALLBACK(WRITELINE("mlc", hp_hil_mlc_device, ap_w))
// 64kw/128kb video RAM
m_gpu->set_vram_size(64);
SCREEN(config , m_screen , SCREEN_TYPE_LCD);
m_screen->set_color(rgb_t::amber()); // actually a kind of EL display
m_screen->set_screen_update("gpu" , FUNC(hp1ll3_device::screen_update));
m_screen->set_raw(6_MHz_XTAL * 2 , 880 , 0 , 640 , 425 , 0 , 400);
m_screen->screen_vblank().set("mlc", FUNC(hp_hil_mlc_device::ap_w)); // XXX actually it's driven by 555 (U59)
m_screen->set_palette("palette");
MCFG_SCREEN_PALETTE("palette")
MCFG_PALETTE_ADD_MONOCHROME("palette")
MACHINE_CONFIG_END