hp9845: first draft of graphic video support: no cursor yet, reading

from graphic RAM is not working
This commit is contained in:
fulivi 2016-05-09 16:15:23 +02:00
parent cc6d5a643d
commit 4175e66e34
3 changed files with 420 additions and 45 deletions

View File

@ -131,6 +131,11 @@ WRITE_LINE_MEMBER(hp_hybrid_cpu_device::flag_w)
}
}
UINT8 hp_hybrid_cpu_device::pa_r(void) const
{
return CURRENT_PA;
}
hp_hybrid_cpu_device::hp_hybrid_cpu_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname , UINT8 addrwidth)
: cpu_device(mconfig, type, name, tag, owner, clock, shortname, __FILE__),
m_pa_changed_func(*this),

View File

@ -87,6 +87,8 @@ public:
DECLARE_WRITE_LINE_MEMBER(status_w);
DECLARE_WRITE_LINE_MEMBER(flag_w);
UINT8 pa_r(void) const;
template<class _Object> static devcb_base &set_pa_changed_func(device_t &device, _Object object) { return downcast<hp_hybrid_cpu_device &>(device).m_pa_changed_func.set_callback(object); }
protected:

View File

@ -49,12 +49,37 @@
// from 2nd row on. This in turn prevents "BAD" text to be visible on screen.
#define MAX_WORD_PER_ROW 220
#define VIDEO_PIXEL_CLOCK 20849400
#define VIDEO_CHAR_WIDTH 9
#define VIDEO_CHAR_HEIGHT 15
#define VIDEO_CHAR_COLUMNS 80
#define VIDEO_CHAR_TOTAL 99
#define VIDEO_CHAR_ROWS 25
#define VIDEO_ROWS_TOTAL 26
#define VIDEO_HTOTAL (VIDEO_CHAR_WIDTH * VIDEO_CHAR_TOTAL)
#define VIDEO_VTOTAL (VIDEO_CHAR_HEIGHT * VIDEO_ROWS_TOTAL)
#define VIDEO_ACTIVE_SCANLINES (VIDEO_CHAR_HEIGHT * VIDEO_CHAR_ROWS)
// Constants of graphic video
// Pixel clock is 20.8494 MHz (the same as alpha video)
// Horizontal counter counts in [1..727] range
// Vertical counter counts in [34..511] range
#define GVIDEO_HTOTAL 727
#define GVIDEO_HCNT_OFF 1 // Actual start value of h counter
#define GVIDEO_HBEND (69 - GVIDEO_HCNT_OFF)
#define GVIDEO_HPIXELS 560
#define GVIDEO_HBSTART (GVIDEO_HBEND + GVIDEO_HPIXELS)
#define GVIDEO_VTOTAL 478
#define GVIDEO_VCNT_OFF 34 // Actual start value of v counter
#define GVIDEO_VBEND (50 - GVIDEO_VCNT_OFF)
#define GVIDEO_VPIXELS 455
#define GVIDEO_VBSTART (GVIDEO_VBEND + GVIDEO_VPIXELS)
#define GVIDEO_MEM_SIZE 16384
#define GVIDEO_ADDR_MASK (GVIDEO_MEM_SIZE - 1)
#define GVIDEO_PA 13
#define T15_PA 15
#define KEY_SCAN_OSCILLATOR 327680
class hp9845_state : public driver_device
@ -77,6 +102,7 @@ public:
driver_device(mconfig, type, tag),
m_lpu(*this , "lpu"),
m_ppu(*this , "ppu"),
m_screen(*this , "screen"),
m_palette(*this , "palette"),
m_io_key0(*this , "KEY0"),
m_io_key1(*this , "KEY1"),
@ -94,6 +120,13 @@ public:
void vblank_w(screen_device &screen, bool state);
void set_graphic_mode(bool graphic);
DECLARE_READ16_MEMBER(graphic_r);
DECLARE_WRITE16_MEMBER(graphic_w);
attotime time_to_gv_mem_availability(void) const;
void advance_gv_fsm(bool ds , bool trigger);
void update_graphic_bits(void);
IRQ_CALLBACK_MEMBER(irq_callback);
void update_irq(void);
@ -111,6 +144,7 @@ public:
private:
required_device<hp_5061_3001_cpu_device> m_lpu;
required_device<hp_5061_3001_cpu_device> m_ppu;
required_device<screen_device> m_screen;
required_device<palette_device> m_palette;
required_ioport m_io_key0;
required_ioport m_io_key1;
@ -137,6 +171,7 @@ private:
offs_t m_video_mar;
UINT16 m_video_word;
bool m_video_load_mar;
bool m_video_first_mar;
bool m_video_byte_idx;
UINT8 m_video_attr;
bool m_video_buff_idx;
@ -144,6 +179,35 @@ private:
UINT8 m_video_frame;
video_buffer_t m_video_buff[ 2 ];
// Graphic video
typedef enum {
GV_STAT_RESET,
GV_STAT_WAIT_DS_0 = GV_STAT_RESET,
GV_STAT_WAIT_TRIG_0,
GV_STAT_WAIT_MEM_0,
GV_STAT_WAIT_DS_1,
GV_STAT_WAIT_DS_2,
GV_STAT_WAIT_TRIG_1,
GV_STAT_WAIT_MEM_1,
GV_STAT_WAIT_MEM_2
} gv_fsm_state_t;
bool m_graphic_sel;
gv_fsm_state_t m_gv_fsm_state;
bool m_gv_int_en;
bool m_gv_dma_en;
bool m_gv_ready;
UINT8 m_gv_cmd; // U65 (GC)
UINT16 m_gv_data_w; // U29, U45, U28 & U44 (GC)
UINT16 m_gv_data_r; // U59 & U60 (GC)
UINT16 m_gv_io_counter; // U1, U2, U14 & U15 (GC)
UINT16 m_gv_cursor_w; // U38 & U39 (GS)
UINT16 m_gv_cursor_x; // U31 & U23 (GS)
UINT16 m_gv_cursor_y; // U15 & U8 (GS)
bool m_gv_cursor_gc; // U8 (GS)
bool m_gv_cursor_fs; // U8 (GS)
std::vector<UINT16> m_graphic_mem;
// Interrupt handling
UINT8 m_irl_pending;
UINT8 m_irh_pending;
@ -152,9 +216,6 @@ private:
ioport_value m_kb_state[ 4 ];
UINT8 m_kb_scancode;
UINT16 m_kb_status;
// State of PPU I/O
UINT8 m_ppu_pa;
};
static INPUT_PORTS_START(hp9845b)
@ -308,7 +369,11 @@ UINT32 hp9845_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
UINT32 hp9845b_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
if (m_graphic_sel) {
copybitmap(bitmap, m_bitmap, 0, 0, GVIDEO_HBEND, GVIDEO_VBEND, cliprect);
} else {
copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
}
return 0;
}
@ -318,6 +383,8 @@ void hp9845b_state::machine_start()
machine().first_screen()->register_screen_bitmap(m_bitmap);
m_chargen = memregion("chargen")->base();
m_graphic_mem.resize(GVIDEO_MEM_SIZE);
}
void hp9845b_state::machine_reset()
@ -328,11 +395,17 @@ void hp9845b_state::machine_reset()
// Some sensible defaults
m_video_mar = VIDEO_BUFFER_BASE;
m_video_load_mar = false;
m_video_first_mar = false;
m_video_byte_idx = false;
m_video_attr = 0;
m_video_buff_idx = false;
m_video_blanked = false;
m_video_frame = 0;
m_graphic_sel = false;
m_gv_fsm_state = GV_STAT_RESET;
m_gv_int_en = false;
m_gv_dma_en = false;
m_gv_ready = true;
m_irl_pending = 0;
m_irh_pending = 0;
@ -340,8 +413,6 @@ void hp9845b_state::machine_reset()
memset(&m_kb_state[ 0 ] , 0 , sizeof(m_kb_state));
m_kb_scancode = 0x7f;
m_kb_status = 0;
m_ppu_pa = 0;
}
void hp9845b_state::set_video_mar(UINT16 mar)
@ -367,7 +438,10 @@ void hp9845b_state::video_fill_buff(bool buff_idx)
m_video_word = prog_space.read_word(m_video_mar << 1);
if (m_video_load_mar) {
// Load new address into MAR after start of a new frame or NWA instruction
// TODO: decode graphic/alpha mode bit
if (m_video_first_mar) {
set_graphic_mode(!BIT(m_video_word , 15));
m_video_first_mar = false;
}
set_video_mar(~m_video_word);
m_video_load_mar = false;
continue;
@ -415,13 +489,13 @@ void hp9845b_state::video_render_buff(unsigned line_in_row, bool buff_idx)
m_video_blanked = true;
}
const rgb_t *palette = m_palette->palette()->entry_list_raw();
const pen_t *pen = m_palette->pens();
if (m_video_blanked) {
// Blank scanline
for (unsigned i = 0; i < (80 * 9); i++) {
m_bitmap.pix32(m_video_scanline , i) = palette[ 0 ];
}
// Blank scanline
for (unsigned i = 0; i < (80 * 9); i++) {
m_bitmap.pix32(m_video_scanline , i) = pen[ 0 ];
}
} else {
bool cursor_line = line_in_row == 12;
bool ul_line = line_in_row == 14;
@ -453,7 +527,7 @@ void hp9845b_state::video_render_buff(unsigned line_in_row, bool buff_idx)
for (unsigned j = 0; j < 9; j++) {
bool pixel = (pixels & (1U << (8 - j))) != 0;
m_bitmap.pix32(m_video_scanline , i * 9 + j) = palette[ pixel ? 1 : 0 ];
m_bitmap.pix32(m_video_scanline , i * 9 + j) = pen[ pixel ? 1 : 0 ];
}
}
}
@ -461,44 +535,332 @@ void hp9845b_state::video_render_buff(unsigned line_in_row, bool buff_idx)
TIMER_DEVICE_CALLBACK_MEMBER(hp9845b_state::scanline_timer)
{
m_video_scanline = param;
m_video_scanline = param;
//logerror("sl=%d\n" , param);
if (m_video_scanline < VIDEO_ACTIVE_SCANLINES) {
unsigned row = m_video_scanline / 15;
unsigned line_in_row = m_video_scanline - row * 15;
if (m_graphic_sel) {
if (m_video_scanline >= GVIDEO_VBEND && m_video_scanline < GVIDEO_VBSTART) {
const pen_t *pen = m_palette->pens();
if (line_in_row == 0) {
// Start of new row, swap buffers
m_video_buff_idx = !m_video_buff_idx;
video_fill_buff(!m_video_buff_idx);
}
unsigned mem_idx = 36 * (m_video_scanline - GVIDEO_VBEND);
for (unsigned i = 0; i < GVIDEO_HPIXELS; i += 16) {
UINT16 word = m_graphic_mem[ mem_idx++ ];
unsigned x = i;
for (UINT16 mask = 0x8000; mask != 0; mask >>= 1) {
m_bitmap.pix32(m_video_scanline - GVIDEO_VBEND , x++) = pen[ (word & mask) != 0 ];
}
}
}
} else if (m_video_scanline < VIDEO_ACTIVE_SCANLINES) {
unsigned row = m_video_scanline / 15;
unsigned line_in_row = m_video_scanline - row * 15;
video_render_buff(line_in_row , m_video_buff_idx);
}
if (line_in_row == 0) {
// Start of new row, swap buffers
m_video_buff_idx = !m_video_buff_idx;
video_fill_buff(!m_video_buff_idx);
}
video_render_buff(line_in_row , m_video_buff_idx);
}
}
void hp9845b_state::vblank_w(screen_device &screen, bool state)
{
// VBlank signal is fed into HALT flag of PPU
m_ppu->halt_w(state);
//logerror("vb=%d\n" , state);
if (state) {
// Start of V blank
set_video_mar(0);
m_video_load_mar = true;
m_video_byte_idx = false;
m_video_blanked = false;
m_video_frame++;
m_video_buff_idx = !m_video_buff_idx;
video_fill_buff(!m_video_buff_idx);
}
// VBlank signal is fed into HALT flag of PPU
m_ppu->halt_w(state);
if (state) {
// Start of V blank
set_video_mar(0);
m_video_load_mar = true;
m_video_first_mar = true;
m_video_byte_idx = false;
m_video_blanked = false;
m_video_frame++;
m_video_buff_idx = !m_video_buff_idx;
video_fill_buff(!m_video_buff_idx);
}
}
void hp9845b_state::set_graphic_mode(bool graphic)
{
if (graphic != m_graphic_sel) {
m_graphic_sel = graphic;
logerror("GS=%d\n" , graphic);
if (m_graphic_sel) {
m_screen->configure(GVIDEO_HTOTAL , GVIDEO_VTOTAL , rectangle(GVIDEO_HBEND , GVIDEO_HBSTART - 1 , GVIDEO_VBEND , GVIDEO_VBSTART - 1) , HZ_TO_ATTOSECONDS(VIDEO_PIXEL_CLOCK) * GVIDEO_HTOTAL * GVIDEO_VTOTAL);
} else {
m_screen->configure(VIDEO_HTOTAL , VIDEO_VTOTAL , rectangle(0 , (VIDEO_CHAR_WIDTH * VIDEO_CHAR_COLUMNS) - 1 , 0 , VIDEO_ACTIVE_SCANLINES - 1) , HZ_TO_ATTOSECONDS(VIDEO_PIXEL_CLOCK) * VIDEO_HTOTAL * VIDEO_VTOTAL);
}
}
}
READ16_MEMBER(hp9845b_state::graphic_r)
{
UINT16 res = 0;
switch (offset) {
case 0:
// R4: data register
res = m_gv_data_r;
advance_gv_fsm(true , false);
break;
case 1:
// R5: status register
if (m_gv_int_en) {
BIT_SET(res, 7);
}
if (m_gv_dma_en) {
BIT_SET(res, 6);
}
BIT_SET(res, 5);
break;
case 2:
// R6: data register with DMA TC
m_gv_dma_en = false;
res = m_gv_data_r;
advance_gv_fsm(true , false);
break;
case 3:
// R7: not mapped
break;
}
logerror("rd gv R%u = %04x\n", 4 + offset , res);
return res;
}
WRITE16_MEMBER(hp9845b_state::graphic_w)
{
logerror("wr gv R%u = %04x\n", 4 + offset , data);
switch (offset) {
case 0:
// R4: data register
m_gv_data_w = data;
m_gv_cursor_w = data;
advance_gv_fsm(true , false);
break;
case 1:
// R5: command register
m_gv_cmd = (UINT8)(data & 0xf);
if (BIT(data , 5)) {
m_gv_fsm_state = GV_STAT_RESET;
}
m_gv_dma_en = BIT(data , 6) != 0;
m_gv_int_en = BIT(data , 7) != 0;
advance_gv_fsm(false , false);
break;
case 2:
// R6: data register with DMA TC
m_gv_dma_en = false;
m_gv_data_w = data;
m_gv_cursor_w = data;
advance_gv_fsm(true , false);
break;
case 3:
// R7: trigger
advance_gv_fsm(false , true);
break;
}
}
attotime hp9845b_state::time_to_gv_mem_availability(void) const
{
// TODO:
return attotime::zero;
}
void hp9845b_state::advance_gv_fsm(bool ds , bool trigger)
{
bool get_out = false;
attotime time_mem_av;
do {
bool act_trig = trigger || m_gv_dma_en || !BIT(m_gv_cmd , 2);
switch (m_gv_fsm_state) {
case GV_STAT_WAIT_DS_0:
// Wait for data strobe (r/w on r4 or r6)
if (ds) {
m_gv_fsm_state = GV_STAT_WAIT_TRIG_0;
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_TRIG_0:
// Wait for trigger
if (act_trig) {
if (BIT(m_gv_cmd , 3)) {
// Not a cursor command
// Load memory address
m_gv_io_counter = ~m_gv_data_w & GVIDEO_ADDR_MASK;
if (BIT(m_gv_cmd , 2)) {
// Read command (11xx)
m_gv_fsm_state = GV_STAT_WAIT_MEM_0;
} else {
// Write commands (10xx)
m_gv_fsm_state = GV_STAT_WAIT_DS_2;
}
} else {
// Cursor command (0xxx)
if (BIT(m_gv_cmd , 2)) {
// Write X cursor position (01xx)
m_gv_cursor_x = (~m_gv_cursor_w >> 6) & 0x3ff;
logerror("gv x curs pos = %u\n" , m_gv_cursor_x);
} else {
// Write Y cursor position and type (00xx)
m_gv_cursor_y = (~m_gv_cursor_w >> 6) & 0x1ff;
m_gv_cursor_gc = BIT(m_gv_cmd , 1) == 0;
m_gv_cursor_fs = BIT(m_gv_cmd , 0) != 0;
logerror("gv y curs pos = %u gc = %d fs = %d\n" , m_gv_cursor_y , m_gv_cursor_gc , m_gv_cursor_fs);
}
m_gv_fsm_state = GV_STAT_WAIT_DS_0;
}
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_MEM_0:
time_mem_av = time_to_gv_mem_availability();
if (time_mem_av.is_zero()) {
// Read a word from graphic memory
m_gv_data_r = m_graphic_mem[ m_gv_io_counter ];
logerror("rd gv mem @%04x = %04x\n" , m_gv_io_counter , m_gv_data_r);
m_gv_io_counter = (m_gv_io_counter + 1) & GVIDEO_ADDR_MASK;
m_gv_fsm_state = GV_STAT_WAIT_DS_1;
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_DS_1:
if (ds) {
m_gv_fsm_state = GV_STAT_WAIT_MEM_0;
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_DS_2:
// Wait for data word to be written
if (ds) {
m_gv_fsm_state = GV_STAT_WAIT_TRIG_1;
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_TRIG_1:
// Wait for trigger
if (act_trig) {
if (BIT(m_gv_cmd , 1)) {
// Clear words (101x)
m_gv_data_w = 0;
m_gv_fsm_state = GV_STAT_WAIT_MEM_1;
} else if (BIT(m_gv_cmd , 0)) {
// Write a single pixel (1001)
m_gv_fsm_state = GV_STAT_WAIT_MEM_2;
} else {
// Write words (1000)
m_gv_fsm_state = GV_STAT_WAIT_MEM_1;
}
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_MEM_1:
time_mem_av = time_to_gv_mem_availability();
if (time_mem_av.is_zero()) {
// Write a full word to graphic memory
logerror("wr gv mem @%04x = %04x\n" , m_gv_io_counter , m_gv_data_w);
m_graphic_mem[ m_gv_io_counter ] = m_gv_data_w;
m_gv_io_counter = (m_gv_io_counter + 1) & GVIDEO_ADDR_MASK;
m_gv_fsm_state = GV_STAT_WAIT_DS_2;
} else {
get_out = true;
}
break;
case GV_STAT_WAIT_MEM_2:
time_mem_av = time_to_gv_mem_availability();
if (time_mem_av.is_zero()) {
// Write a single pixel to graphic memory
logerror("wr gv pixel @%04x:%x = %d\n" , m_gv_io_counter , m_gv_data_w & 0xf , BIT(m_gv_data_w , 15));
UINT16 mask = 0x8000 >> (m_gv_data_w & 0xf);
if (BIT(m_gv_data_w , 15)) {
// Set pixel
m_graphic_mem[ m_gv_io_counter ] |= mask;
} else {
// Clear pixel
m_graphic_mem[ m_gv_io_counter ] &= ~mask;
}
// Not really needed
m_gv_io_counter = (m_gv_io_counter + 1) & GVIDEO_ADDR_MASK;
m_gv_fsm_state = GV_STAT_WAIT_DS_0;
} else {
get_out = true;
}
break;
default:
logerror("Invalid state reached %d\n" , m_gv_fsm_state);
m_gv_fsm_state = GV_STAT_RESET;
}
ds = false;
trigger = false;
} while (!get_out);
update_graphic_bits();
}
void hp9845b_state::update_graphic_bits(void)
{
m_gv_ready = m_gv_fsm_state == GV_STAT_WAIT_DS_0 ||
m_gv_fsm_state == GV_STAT_WAIT_DS_1 ||
m_gv_fsm_state == GV_STAT_WAIT_DS_2;
bool irq = m_gv_int_en && !m_gv_dma_en && m_gv_ready;
if (irq) {
BIT_SET(m_irh_pending, GVIDEO_PA - 8);
} else {
BIT_CLR(m_irh_pending, GVIDEO_PA - 8);
}
update_irq();
bool dmar = m_gv_ready && m_gv_dma_en;
m_ppu->dmar_w(dmar);
if (m_ppu->pa_r() == GVIDEO_PA) {
m_ppu->flag_w(m_gv_ready);
}
}
IRQ_CALLBACK_MEMBER(hp9845b_state::irq_callback)
{
if (irqline == HPHYBRID_IRL) {
logerror("irq ack L %02x\n" , m_irl_pending);
return m_irl_pending;
} else {
logerror("irq ack H %02x\n" , m_irh_pending);
return m_irh_pending;
}
}
@ -600,13 +962,15 @@ WRITE16_MEMBER(hp9845b_state::kb_irq_clear_w)
WRITE8_MEMBER(hp9845b_state::pa_w)
{
m_ppu_pa = data;
// TODO: handle sts & flg
if (data == 15) {
if (data == T15_PA) {
// RHS tape drive (T15)
m_ppu->status_w(m_t15->sts_r());
m_ppu->flag_w(m_t15->flg_r());
} else if (data == GVIDEO_PA) {
// Graphic video
m_ppu->status_w(1);
m_ppu->flag_w(m_gv_ready);
} else {
m_ppu->status_w(0);
m_ppu->flag_w(0);
@ -616,23 +980,23 @@ WRITE8_MEMBER(hp9845b_state::pa_w)
WRITE_LINE_MEMBER(hp9845b_state::t15_irq_w)
{
if (state) {
BIT_SET(m_irh_pending , 7);
BIT_SET(m_irh_pending , T15_PA - 8);
} else {
BIT_CLR(m_irh_pending , 7);
BIT_CLR(m_irh_pending , T15_PA - 8);
}
update_irq();
}
WRITE_LINE_MEMBER(hp9845b_state::t15_flg_w)
{
if (m_ppu_pa == 15) {
if (m_ppu->pa_r() == T15_PA) {
m_ppu->flag_w(state);
}
}
WRITE_LINE_MEMBER(hp9845b_state::t15_sts_w)
{
if (m_ppu_pa == 15) {
if (m_ppu->pa_r() == T15_PA) {
m_ppu->status_w(state);
}
}
@ -684,9 +1048,12 @@ static ADDRESS_MAP_START(ppu_io_map , AS_IO , 16 , hp9845b_state)
// PA = 0, IC = 3
// Keyboard status input & keyboard interrupt clear
AM_RANGE(HP_MAKE_IOADDR(0 , 3) , HP_MAKE_IOADDR(0 , 3)) AM_READWRITE(kb_status_r , kb_irq_clear_w)
// PA = 13, IC = 0..3
// Graphic video
AM_RANGE(HP_MAKE_IOADDR(GVIDEO_PA , 0) , HP_MAKE_IOADDR(GVIDEO_PA , 3)) AM_READWRITE(graphic_r , graphic_w)
// PA = 15, IC = 0..3
// Right-hand side tape drive (T15)
AM_RANGE(HP_MAKE_IOADDR(15 , 0) , HP_MAKE_IOADDR(15 , 3)) AM_DEVREADWRITE("t15" , hp_taco_device , reg_r , reg_w)
AM_RANGE(HP_MAKE_IOADDR(T15_PA , 0) , HP_MAKE_IOADDR(T15_PA , 3)) AM_DEVREADWRITE("t15" , hp_taco_device , reg_r , reg_w)
ADDRESS_MAP_END
static MACHINE_CONFIG_START( hp9845b, hp9845b_state )
@ -703,7 +1070,8 @@ static MACHINE_CONFIG_START( hp9845b, hp9845b_state )
// video hardware
MCFG_SCREEN_ADD_MONOCHROME("screen", RASTER, rgb_t::green)
MCFG_SCREEN_UPDATE_DRIVER(hp9845b_state, screen_update)
MCFG_SCREEN_RAW_PARAMS(20849400 , 99 * 9 , 0 , 80 * 9 , 26 * 15 , 0 , 25 * 15)
// These parameters are for alpha video
MCFG_SCREEN_RAW_PARAMS(VIDEO_PIXEL_CLOCK , VIDEO_HTOTAL , 0 , VIDEO_CHAR_WIDTH * VIDEO_CHAR_COLUMNS , VIDEO_VTOTAL , 0 , VIDEO_ACTIVE_SCANLINES)
MCFG_SCREEN_VBLANK_DRIVER(hp9845b_state, vblank_w)
MCFG_PALETTE_ADD_MONOCHROME("palette")