summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author fulivi <fulivi@users.noreply.github.com>2017-02-28 17:43:00 +0100
committer Vas Crabb <cuavas@users.noreply.github.com>2017-03-08 10:50:36 +1100
commit7557a9250366346283c44937150634e8da28cb09 (patch)
tree16cce0b9bcc5a627b12b19f4d4be0f713637241f
parent93db554bfa7fc86ba9bac36c339e4d7407d38fa5 (diff)
hp9845: Implemented 9845C driver together with A.Kueckes!
-rw-r--r--src/mame/drivers/hp9845.cpp1557
-rw-r--r--src/mame/includes/hp9845.h15
2 files changed, 1440 insertions, 132 deletions
diff --git a/src/mame/drivers/hp9845.cpp b/src/mame/drivers/hp9845.cpp
index f632b375bbc..710b01f0f35 100644
--- a/src/mame/drivers/hp9845.cpp
+++ b/src/mame/drivers/hp9845.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Curt Coder, F. Ulivi
+// copyright-holders:Curt Coder, F. Ulivi, Ansgar Kueckes
/*
HP 9845
@@ -12,6 +12,7 @@
// *******************************
//
// What's in:
+// - Emulation of 45B and 45C systems
// - Emulation of both 5061-3001 CPUs
// - LPU & PPU ROMs
// - LPU & PPU RAMs
@@ -25,8 +26,16 @@
// What's not yet in:
// - Better naming of tape drive image (it's now "magt", should be "t15")
// - Better documentation of this file
+// - Emulation of 45T system
+// - Display softkeys on 45C & 45T
+// - Better keyboard mapping
+// - German keyboard
// What's wrong:
// - Speed, as usual
+// - Light pen sometimes behaves erratically in 45C
+// What will probably never be in:
+// - Integral printer (firmware and character generator ROMs are very difficult to dump)
+// - Fast LPU processor (dump of microcode PROMs is not available)
#include "emu.h"
#include "includes/hp9845.h"
@@ -40,13 +49,66 @@
#include "hp9845b.lh"
+// Debugging
+#define VERBOSE 0
+#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+
#define BIT_MASK(n) (1U << (n))
// Macros to clear/set single bits
#define BIT_CLR(w , n) ((w) &= ~BIT_MASK(n))
#define BIT_SET(w , n) ((w) |= BIT_MASK(n))
+/*
+
+ The 9845 has three possible display options:
+
+ 98750A: Standard monochrome (alpha with graphics option)
+ 98780A: Enhanced monochrome (alpha with hardware accelerated monochrome graphics)
+ 98770A: Color (color alpha with hardware accelerated color graphics with three planes)
+
+ All displays use a 560x455 graphics raster. Alpha and graphics share the
+ same dots within a 720x455 super matrix. All pixels have a 1:1 ratio (square
+ pixels).
+
+ The 98750A uses a 25x80 alpha area, either alpha or graphics can be enabled,
+ but not both at the same time. In fact, both modes use different video circuits.
+
+ Timing and pixel size for real 98750A are slightly different between
+ alpha and graphics raster (dual raster):
+
+ alpha graphics
+ ------------------------------------------------------
+ Matrix: 720x375 560x455
+ Clock frequency: 20.85 MHz 20.85 MHz
+ Horizontal scan frequency: 23.4 kHz 28.7 kHz
+ Horizontal retrace time: 8.2 us 8.0 us
+ Frame frequency: 60 Hz 60 Hz
+ Vertical retrace time: 641 us 800 us
+ Size on screen: 9.3"x4.84" 7.9"x6.4"
+
+
+ The 98770A and 98780A both use a 720x455 raster, implemented with a single video
+ circuit, which again is shared by the alpha and graphics logic, with alpha
+ dominant over graphics. So, nominally the alpha area for those systems can
+ hold up to 30 rows with full size characters plus some lines for one row with
+ cropped characters:
+
+ 98770A 98780A
+ ------------------------------------------------------
+ Matrix: 720x455 720x455
+ Clock frequency: 29.7984 MHz 28.224 MHz
+ Horizontal scan frequency: 29.1 kHz 31.5 kHz
+ Horizontal retrace time: 10.02 us 4.145 us
+ Frame frequency: 60 Hz 60 Hz
+ Vertical retrace time: 1.03 ms 2.22 ms
+ Size on screen: 247x154 mm 236x149 mm
+ Dot size: 0.343 mm 0.33 mm
+
+*/
+
// Base address of video buffer
+#define VIDEO_BUFFER_BASE_LOW 0x16000 // for 98770A and 98780A
#define VIDEO_BUFFER_BASE_HIGH 0x17000 // for 98750A
// For test "B" of alpha video to succeed this must be < 234
@@ -66,6 +128,7 @@
#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)
+#define VIDEO_TOT_HPIXELS (VIDEO_CHAR_WIDTH * VIDEO_CHAR_COLUMNS)
// Constants of graphic video
// Pixel clock is 20.8494 MHz (the same as alpha video)
@@ -85,6 +148,18 @@
#define GVIDEO_ADDR_MASK (GVIDEO_MEM_SIZE - 1)
#define GVIDEO_PA 13
+// Constants of 98770A video
+// HBEND & VBEND probably are not really 0
+#define VIDEO_770_PIXEL_CLOCK 29798400
+#define VIDEO_770_HTOTAL 1024
+#define VIDEO_770_HBEND 0
+#define VIDEO_770_HBSTART (VIDEO_CHAR_COLUMNS * VIDEO_CHAR_WIDTH)
+#define VIDEO_770_VTOTAL 485
+#define VIDEO_770_VBEND 0
+#define VIDEO_770_VBSTART (VIDEO_770_VBEND + GVIDEO_VPIXELS)
+#define VIDEO_770_ALPHA_L_LIM 80 // Left-side limit of alpha-only horizontal part
+#define VIDEO_770_ALPHA_R_LIM 640 // Right-side limit of alpha-only horizontal part
+
#define I_GR 0xb0 // graphics intensity
#define I_AL 0xd0 // alpha intensity
#define I_CU 0xf0 // graphics cursor intensity
@@ -350,7 +425,6 @@ void hp9845_base_state::machine_reset()
m_pa = 0;
sts_w(GVIDEO_PA , true);
- update_graphic_bits();
memset(&m_kb_state[ 0 ] , 0 , sizeof(m_kb_state));
m_kb_scancode = 0x7f;
@@ -366,74 +440,6 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9845_base_state::gv_timer)
advance_gv_fsm(false , false);
}
-READ16_MEMBER(hp9845_base_state::graphic_r)
-{
- uint16_t 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
- res = graphic_r5_r();
- 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(hp9845_base_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_t)(data & 0xf);
- m_gv_dma_en = BIT(data , 6) != 0;
- m_gv_int_en = BIT(data , 7) != 0;
- graphic_r5_w(data);
- 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 hp9845_base_state::time_to_gv_mem_availability(void) const
{
if (m_graphic_sel) {
@@ -451,24 +457,6 @@ attotime hp9845_base_state::time_to_gv_mem_availability(void) const
}
}
-
-void hp9845_base_state::update_graphic_bits(void)
-{
- bool 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;
-
- flg_w(GVIDEO_PA , gv_ready);
-
- bool irq = m_gv_int_en && !m_gv_dma_en && gv_ready;
-
- irq_w(GVIDEO_PA , irq);
-
- bool dmar = gv_ready && m_gv_dma_en;
-
- m_ppu->dmar_w(dmar);
-}
-
IRQ_CALLBACK_MEMBER(hp9845_base_state::irq_callback)
{
if (irqline == HPHYBRID_IRL) {
@@ -677,6 +665,9 @@ public:
virtual void machine_start() override;
virtual void machine_reset() override;
+ virtual DECLARE_READ16_MEMBER(graphic_r) override;
+ virtual DECLARE_WRITE16_MEMBER(graphic_w) override;
+
TIMER_DEVICE_CALLBACK_MEMBER(scanline_timer);
void vblank_w(screen_device &screen, bool state);
@@ -688,16 +679,11 @@ protected:
void video_render_buff(unsigned video_scanline , unsigned line_in_row, bool buff_idx);
void graphic_video_render(unsigned video_scanline);
- virtual uint16_t graphic_r5_r(void) override;
- virtual void graphic_r5_w(uint16_t data) override;
virtual void advance_gv_fsm(bool ds , bool trigger) override;
+ void update_graphic_bits(void);
uint8_t m_video_attr;
- uint16_t m_gv_io_counter; // U1, U2, U14 & U15 (GC)
- uint16_t m_gv_cursor_x; // U31 & U23 (GS)
- uint16_t m_gv_cursor_y; // U15 & U8 (GS)
- bool m_gv_cursor_gc; // U8 (GS)
- bool m_gv_cursor_fs; // U8 (GS)
+ uint16_t m_gv_cursor_w; // U38 & U39 (GS)
std::vector<uint16_t> m_graphic_mem;
};
@@ -738,6 +724,84 @@ void hp9845b_state::machine_reset()
set_video_mar(0);
m_video_attr = 0;
+ update_graphic_bits();
+}
+
+READ16_MEMBER(hp9845b_state::graphic_r)
+{
+ uint16_t 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); // ID
+ 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_t)(data & 0xf);
+ m_gv_dma_en = BIT(data , 6) != 0;
+ m_gv_int_en = BIT(data , 7) != 0;
+ if (BIT(data , 5)) {
+ m_gv_fsm_state = GV_STAT_RESET;
+ }
+ 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;
+ }
}
TIMER_DEVICE_CALLBACK_MEMBER(hp9845b_state::scanline_timer)
@@ -953,33 +1017,6 @@ void hp9845b_state::graphic_video_render(unsigned video_scanline)
}
}
-uint16_t hp9845b_state::graphic_r5_r(void)
-{
- uint16_t res = 0;
-
- if (m_gv_int_en) {
- BIT_SET(res, 7);
- }
- if (m_gv_dma_en) {
- BIT_SET(res, 6);
- }
- BIT_SET(res, 5); // ID
-
- return res;
-}
-
-void hp9845b_state::graphic_r5_w(uint16_t data)
-{
- // Already set in hp9845_base_state::graphic_w:
- // m_gv_cmd
- // m_gv_dma_en
- // m_gv_int_en
- if (BIT(data , 5)) {
- m_gv_fsm_state = GV_STAT_RESET;
- }
- advance_gv_fsm(false , false);
-}
-
void hp9845b_state::advance_gv_fsm(bool ds , bool trigger)
{
bool get_out = false;
@@ -1124,20 +1161,1263 @@ void hp9845b_state::advance_gv_fsm(bool ds , bool trigger)
update_graphic_bits();
}
+void hp9845b_state::update_graphic_bits(void)
+{
+ bool 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;
+
+ flg_w(GVIDEO_PA , gv_ready);
+
+ bool irq = m_gv_int_en && !m_gv_dma_en && gv_ready;
+
+ irq_w(GVIDEO_PA , irq);
+
+ bool dmar = gv_ready && m_gv_dma_en;
+
+ m_ppu->dmar_w(dmar);
+}
+
// ***************
// hp9845ct_state
// ***************
+
+/*
+ For 9845C and 9845T we just add the light pen support via MAME's lightgun device.
+
+ Note that the LIGHTGUN device needs '-lightgun' and '-lightgun_device mouse' for light gun emulation if no real light gun device is installed.
+ */
+static INPUT_PORTS_START(hp9845ct)
+ PORT_INCLUDE(hp9845_base)
+ PORT_START("LIGHTPENX")
+ PORT_BIT( 0x3ff, 0x000, IPT_LIGHTGUN_X ) PORT_SENSITIVITY(20) PORT_MINMAX(0, VIDEO_TOT_HPIXELS - 1) PORT_CROSSHAIR(X, 1.0, 0.0, 0)
+
+ PORT_START("LIGHTPENY")
+ PORT_BIT( 0x3ff, 0x000, IPT_LIGHTGUN_Y ) PORT_SENSITIVITY(20) PORT_MINMAX(0, GVIDEO_VPIXELS - 1) PORT_CROSSHAIR(Y, 1.0, 0.0, 0)
+
+ PORT_START("GKEY")
+ PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_CODE(MOUSECODE_BUTTON1) PORT_NAME("Gkey")
+INPUT_PORTS_END
+
class hp9845ct_state : public hp9845_base_state
{
public:
hp9845ct_state(const machine_config &mconfig, device_type type, const char *tag);
+ virtual void machine_start() override;
+ virtual void machine_reset() override;
+
+ uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
+
+ void vblank_w(screen_device &screen, bool state);
+
protected:
+ required_ioport m_lightpen_x;
+ required_ioport m_lightpen_y;
+ required_ioport m_lightpen_sw;
+
+ virtual void set_graphic_mode(bool graphic , bool alpha) = 0;
+ void set_video_mar(uint16_t mar);
+ void video_fill_buff(bool buff_idx);
+ virtual void plot(uint16_t x, uint16_t y, bool draw_erase) = 0;
+ void draw_line(unsigned x0 , unsigned y0 , unsigned x1 , unsigned y1);
+ void update_line_pattern(void);
+ static uint16_t get_gv_mem_addr(unsigned x , unsigned y);
+ void update_graphic_bits(void);
+
+ void lp_r4_w(uint16_t data);
+ uint16_t lp_r4_r(void);
+ void lp_r5_w(uint16_t data);
+ void compute_lp_data(void);
+
+ bool m_alpha_sel;
+ bool m_gv_sk_en;
+ bool m_gv_gr_en;
+ bool m_gv_opt_en;
+ bool m_gv_dsa_en;
+ bool m_gv_lp_status;
+ bool m_gv_sk_status;
+ uint16_t m_gv_lp_cursor_x;
+ uint16_t m_gv_lp_cursor_y;
+ bool m_gv_lp_cursor_fs;
+ bool m_gv_lp_en;
+ uint8_t m_gv_last_cmd;
+ uint16_t m_gv_word_x_position;
+ uint16_t m_gv_word_y_position;
+ uint16_t m_gv_memory_control;
+ uint16_t m_gv_line_type_area_fill;
+ uint16_t m_gv_line_type_mask;
+ uint8_t m_gv_repeat_count;
+ uint16_t m_gv_xpt;
+ uint16_t m_gv_ypt;
+ uint16_t m_gv_last_xpt;
+ uint16_t m_gv_last_ypt;
+ uint16_t m_gv_lp_data[ 3 ];
+ bool m_gv_lp_selftest;
+ bool m_gv_lp_xwindow;
+ bool m_gv_lp_ywindow;
+ bool m_gv_lp_interlace;
+ bool m_gv_lp_vblank;
+ bool m_gv_lp_1sthit;
+ bool m_gv_lp_vbint;
+ bool m_gv_lp_fullbright;
+ bool m_gv_lp_threshold;
+ uint16_t m_gv_lp_x;
+ uint16_t m_gv_lp_y;
+ bool m_gv_lp_sw;
+ uint8_t m_gv_lp_reg_cnt;
+ bool m_gv_lp_int_en;
+
+ static const uint16_t m_line_type[];
+ static const uint16_t m_area_fill[];
};
hp9845ct_state::hp9845ct_state(const machine_config &mconfig, device_type type, const char *tag)
- : hp9845_base_state(mconfig , type , tag)
+ : hp9845_base_state(mconfig , type , tag),
+ m_lightpen_x(*this, "LIGHTPENX"),
+ m_lightpen_y(*this, "LIGHTPENY"),
+ m_lightpen_sw(*this, "GKEY")
+{
+}
+
+void hp9845ct_state::machine_start()
+{
+ // Common part first
+ hp9845_base_state::machine_start();
+}
+
+void hp9845ct_state::machine_reset()
+{
+ // Common part first
+ hp9845_base_state::machine_reset();
+
+ m_alpha_sel = true;
+ m_gv_sk_en = false;
+ m_gv_gr_en = false;
+ m_gv_opt_en = false;
+ m_gv_dsa_en = false;
+ // TODO: check
+ //m_gv_lp_status = true; // required by Test ROM
+ m_gv_lp_status = false;
+ m_gv_sk_status = false;
+ m_gv_lp_cursor_x = 944;
+ m_gv_lp_cursor_y = 50;
+ m_gv_lp_cursor_fs = false;
+ m_gv_lp_en = false;
+ m_gv_last_cmd = 0;
+ m_gv_word_x_position = 0;
+ m_gv_word_y_position = 0;
+ m_gv_memory_control = 0;
+ m_gv_line_type_area_fill = 0;
+ m_gv_line_type_mask = 0xffff;
+ m_gv_repeat_count = 0;
+ m_gv_xpt = 0;
+ m_gv_ypt = 0;
+ m_gv_last_xpt = 0;
+ m_gv_last_ypt = 0;
+ m_gv_lp_selftest = false;
+ m_gv_lp_xwindow = false;
+ m_gv_lp_ywindow = false;
+ m_gv_lp_interlace = false;
+ m_gv_lp_vblank = false;
+ m_gv_lp_1sthit = false;
+ m_gv_lp_vbint = false;
+ m_gv_lp_fullbright = false;
+ m_gv_lp_threshold = false;
+ m_gv_lp_x = 0;
+ m_gv_lp_y = 0;
+ m_gv_lp_sw = false;
+ m_gv_lp_int_en = false;
+
+ update_graphic_bits();
+}
+
+uint32_t hp9845ct_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
+{
+ copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
+
+ return 0;
+}
+
+void hp9845ct_state::vblank_w(screen_device &screen, bool state)
+{
+ // 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_blanked = false;
+ m_video_buff_idx = !m_video_buff_idx;
+ video_fill_buff(!m_video_buff_idx);
+
+ // lightpen
+ m_gv_lp_vblank = true;
+ m_gv_lp_xwindow = false;
+ m_gv_lp_ywindow = false;
+ m_gv_lp_sw = m_lightpen_sw->read();
+ m_gv_lp_x = m_lightpen_x->read();
+ if (m_gv_lp_x > (VIDEO_TOT_HPIXELS - 1)) {
+ m_gv_lp_x = VIDEO_TOT_HPIXELS - 1;
+ }
+ m_gv_lp_y = m_lightpen_y->read();
+ if (m_gv_lp_y > (GVIDEO_VPIXELS - 1)) {
+ m_gv_lp_y = GVIDEO_VPIXELS - 1;
+ }
+
+ // TODO: right place to call it?
+ compute_lp_data();
+ // VB interrupt
+ if (m_gv_lp_vbint) {
+ m_gv_lp_status = true;
+ }
+ update_graphic_bits();
+ } else {
+ m_gv_lp_vblank = false;
+ }
+}
+
+void hp9845ct_state::set_video_mar(uint16_t mar)
+{
+ m_video_mar = (mar & 0x1fff) | VIDEO_BUFFER_BASE_LOW;
+}
+
+void hp9845ct_state::video_fill_buff(bool buff_idx)
+{
+ unsigned char_idx = 0;
+ unsigned iters = 0;
+ address_space& prog_space = m_ppu->space(AS_PROGRAM);
+
+ m_video_buff[ buff_idx ].full = false;
+
+ while (1) {
+ if ((m_video_mar & 0x1fff) > 0x1dff) {
+ // CRT buffer ends at 0x7dff
+ break;
+ }
+ // Get video word
+ if (iters++ >= MAX_WORD_PER_ROW) {
+ // Limit on accesses per row reached
+ break;
+ }
+ 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
+ if (m_video_first_mar) {
+ set_graphic_mode(BIT(m_video_word , 15), BIT(m_video_word , 14));
+ m_video_first_mar = false;
+ }
+ set_video_mar(~m_video_word);
+ m_video_load_mar = false;
+ continue;
+ } else {
+ // Update counter for next word fetch
+ set_video_mar(m_video_mar + 1);
+ }
+ // Parse video word
+ if (m_video_word == 0x8020) {
+ // End-of-line (EOL)
+ // Fill rest of buffer with spaces
+ memset(&m_video_buff[ buff_idx ].chars[ char_idx ] , 0x20 , 80 - char_idx);
+ memset(&m_video_buff[ buff_idx ].attrs[ char_idx ] , 0 , 80 - char_idx);
+ m_video_buff[ buff_idx ].full = true;
+ break;
+ } else if ((m_video_word & 0xc020) == 0x8000) {
+ // New word address (NWA)
+ m_video_load_mar = true;
+ } else if ((m_video_word & 0xc000) == 0xc000) {
+ // NOP
+ } else {
+ // fill line buffer
+ m_video_buff[ buff_idx ].chars[ char_idx ] = (uint8_t)(m_video_word & 0xff);
+ m_video_buff[ buff_idx ].attrs[ char_idx ] = (uint8_t)(m_video_word >> 8);
+ char_idx++;
+ if (char_idx == 80) {
+ m_video_buff[ buff_idx ].full = true;
+ break;
+ }
+ }
+ }
+}
+
+void hp9845ct_state::draw_line(unsigned x0 , unsigned y0 , unsigned x1 , unsigned y1)
+{
+ int dx, dy, sx, sy, x, y, err, e2;
+
+ // draw line, vector generator uses Bresenham's algorithm
+ x = x0;
+ y = y0;
+ dx = abs(x1 - x);
+ sx = x < x1 ? 1 : -1; // actually always 1 because of normalization
+ dy = abs(y1 - y);
+ sy = y < y1 ? 1 : -1;
+ err = (dx > dy ? dx : -dy) / 2;
+
+ for(;;){
+ plot(x, y, BIT(m_gv_line_type_mask, 15));
+ update_line_pattern();
+
+ if (x == x1 && y == y1) break;
+
+ e2 = err;
+ if (e2 > -dx) {
+ err -= dy;
+ x += sx;
+ }
+ if (e2 < dy) {
+ err += dx;
+ y += sy;
+ }
+ }
+}
+
+void hp9845ct_state::update_line_pattern(void)
+{
+ // update line pattern
+ m_gv_repeat_count++;
+ if (m_gv_repeat_count > ((m_gv_line_type_area_fill >> 5) & 0xf)) {
+ // Rotate m_gv_line_type_mask 1 bit to the left
+ bool save_bit = BIT(m_gv_line_type_mask , 15);
+ m_gv_line_type_mask = save_bit | (m_gv_line_type_mask << 1);
+ m_gv_repeat_count = 0;
+ }
+}
+
+uint16_t hp9845ct_state::get_gv_mem_addr(unsigned x , unsigned y)
+{
+ return (uint16_t)((x + y * 35) & GVIDEO_ADDR_MASK);
+}
+
+void hp9845ct_state::update_graphic_bits(void)
+{
+ bool gv_ready = m_gv_lp_int_en && m_gv_lp_status;
+
+ if (m_gv_gr_en && !gv_ready) {
+ gv_ready = m_gv_fsm_state == GV_STAT_WAIT_DS_0 ||
+ m_gv_fsm_state == GV_STAT_WAIT_TRIG_0 ||
+ m_gv_fsm_state == GV_STAT_WAIT_DS_1 ||
+ m_gv_fsm_state == GV_STAT_WAIT_DS_2 ||
+ m_gv_fsm_state == GV_STAT_WAIT_TRIG_1;
+ }
+
+ flg_w(GVIDEO_PA , gv_ready);
+
+ bool irq = m_gv_int_en && !m_gv_dma_en && gv_ready;
+
+#if 0
+ // DEBUG DEBUG DEBUG
+ static bool last_irq = false;
+ if (!last_irq && irq) {
+ logerror("GV IRQ 0->1\n");
+ }
+ last_irq = irq;
+#endif
+
+ irq_w(GVIDEO_PA , irq);
+
+ bool dmar = gv_ready && m_gv_dma_en;
+
+ m_ppu->dmar_w(dmar);
+}
+
+#if 0
+bool hp9845ct_state::extra_gv_irqs(void)
+{
+ return m_gv_lp_status || m_gv_sk_status;
+}
+#endif
+
+void hp9845ct_state::lp_r4_w(uint16_t data)
+{
+ if (m_gv_lp_en) {
+ switch (m_gv_lp_reg_cnt) {
+ case 2:
+ // LP Y cursor + threshold + interlace + vertical blank interrupt
+ m_gv_lp_cursor_y = ((~data >> 6) & 0x1ff);
+#if 0
+ if (m_gv_lp_cursor_y < 454)
+ m_gv_lp_cursor_y += 24;
+ else
+ m_gv_lp_cursor_y -= 461;
+#endif
+ m_gv_lp_fullbright = BIT(data, 1);
+ m_gv_lp_threshold = BIT(data, 3);
+ m_gv_lp_interlace = !BIT(data, 4);
+ m_gv_lp_vbint = !BIT(data, 5);
+ LOG(("LP Y cursor y = %d, threshold = %d, interlace = %d, vbint = %d\n",
+ m_gv_lp_cursor_y, m_gv_lp_threshold, m_gv_lp_interlace, m_gv_lp_vbint));
+ m_gv_lp_reg_cnt--;
+ break;
+
+ case 3:
+ // LP X cursor + cursor type
+ m_gv_lp_cursor_x = ((data >> 6) & 0x3ff) + 1;
+ m_gv_lp_cursor_fs = !BIT(data, 0);
+ LOG(("LP X cursor x = %d, fs = %d\n", m_gv_lp_cursor_x, m_gv_lp_cursor_fs));
+ m_gv_lp_reg_cnt--;
+ break;
+
+ default:
+ logerror("Writing to unmapped LP register %u\n" , m_gv_lp_reg_cnt);
+ }
+ }
+}
+
+uint16_t hp9845ct_state::lp_r4_r(void)
+{
+ uint16_t res = 0;
+
+ if (m_gv_lp_en) {
+ switch (m_gv_lp_reg_cnt) {
+ case 4:
+ // YLO
+ res = m_gv_lp_data[ 2 ];
+ m_gv_lp_reg_cnt--;
+ m_gv_lp_status = false;
+ m_gv_lp_1sthit = false;
+ update_graphic_bits();
+ break;
+
+ case 5:
+ // XLEFT
+ res = m_gv_lp_data[ 1 ];
+ m_gv_lp_reg_cnt--;
+ break;
+
+ case 6:
+ // YHI
+ res = m_gv_lp_data[ 0 ];
+ if (!m_gv_lp_vblank) {
+ BIT_SET(res, 12);
+ }
+ if (m_gv_lp_sw) {
+ BIT_SET(res, 14);
+ }
+ // TODO: correct?
+ if (m_gv_lp_1sthit) {
+ BIT_SET(res, 15);
+ }
+ m_gv_lp_reg_cnt--;
+ break;
+
+ default:
+ logerror("Reading from unmapped LP register %u\n" , m_gv_lp_reg_cnt);
+ }
+ }
+ return res;
+}
+
+void hp9845ct_state::lp_r5_w(uint16_t data)
{
+ m_gv_lp_reg_cnt = data & 7;
+ m_gv_lp_en = (data & 0x700) == 0x400; // enables writes on R4 to set LP data (actually FB bit), also enables LP command processing and LP IRQs
+ m_gv_lp_int_en = (data & 0x500) == 0x400;
+ m_gv_lp_selftest = m_gv_lp_en && m_gv_lp_reg_cnt == 7;
+ update_graphic_bits();
+}
+
+void hp9845ct_state::compute_lp_data(void)
+{
+ // get LP hit data, returns three words for cmd=6 and one word for cmd=4
+ // actually simulating the 9845 lightpen is a bit more complex, since YHI, XLEFT and YLO
+ // depend on an circular field of view, moving on the screen
+ // bit 0..10 x bzw y
+ // bit 11 = IRQ (YHI + XLEFT + YLO)
+ // bit 12 = vblank (YHI)
+ // bit 13 = xwindow (YHI + XLEFT + YLO) = X is in [xcursor-24, xcursor+24] and Y in [ycursor-8,ycursor+8]
+ // bit 14 = sw (YHI) bzw. ywindow (XLEFT + YLO)
+ // bit 15 = 1st hit (YHI) = valid hit
+ // TODO: check
+ m_gv_lp_status = true;
+ if (m_gv_lp_selftest) {
+ int offset = 57 - VIDEO_770_ALPHA_L_LIM;
+ m_gv_lp_xwindow = true;
+ m_gv_lp_ywindow = true;
+ m_gv_lp_data[0] = (~(m_gv_lp_cursor_y + 16)) & 0x1ff; // YHI
+ m_gv_lp_data[1] = (~(m_gv_lp_cursor_x + offset)) & 0x3ff; // XLEFT
+ m_gv_lp_data[2] = (~(m_gv_lp_cursor_y + 32)) & 0x1ff; // YLO
+ } else {
+ uint8_t fov = 9; // field of view = [cursor - fov, cursor + fov]
+ uint16_t xp = m_gv_lp_x; // light gun pointer
+ uint16_t yp = m_gv_lp_y;
+ uint16_t xc = m_gv_lp_cursor_x + 1; // 9845 light pen crosshair cursor
+ uint16_t yc = m_gv_lp_cursor_y + 24;
+ uint16_t yhi, xleft, ylo; // hit coordinates
+ uint16_t xoffset; // delay for hit detection on horizontal line
+ // try to calculate YHI, XLEFT, YLO hit coordinates with respect to LP cursor
+ // should give a better match with the prediction algorithm in the firmware
+ uint16_t dx = 0, dy = fov;
+ xoffset = 14; // longer delay due to bright line
+ // if vertical line of the cursor is within field of view, get y delta to intersection
+ if (abs(xc - xp) <= fov)
+ dy = (uint16_t)sqrt((fov * fov) - ((xc - xp) * (xc - xp)));
+ // if horizontal line of the cursor is within field of view, get x delta to intersection
+ if (abs(yc - yp) <= fov)
+ dx = (uint16_t)sqrt((fov * fov) - ((yc - yp) * (yc - yp)));
+ // check whether intersection with vertical line of the cursor is within window
+ if ((yp + dy >= yc - 24) && (yp - dy <= yc - 24)) {
+ // return the first hit in the window
+ yhi = (yp - dy > yc - 24) || !m_gv_lp_interlace ? yp - dy : yc - 24;
+ // return the last hit in the window
+ ylo = (yp + dy < yc + 24) || !m_gv_lp_interlace ? yp + dy : yc + 24;
+ } else {
+ // otherwise return (simulated) first hit in view of field
+ yhi = yp - fov;
+ ylo = yp + fov;
+ }
+ // check whether intersection with horizontal line of the cursor is within window
+ if ((xp + dx >= xc - 24) && (xp - dx <= xc + 24))
+ // return the first hit on the horizontal bar of the cursor
+ xleft = (xp - dx > xc - 24) ? xp - dx - fov + xoffset : xp + dx - fov + xoffset;
+ else
+ // otherwise return (simulated) first hit in view of field
+ xleft = xp - fov + xoffset;
+ m_gv_lp_data[0] = ~yhi & 0x1ff; // YHI
+ m_gv_lp_data[1] = ~xleft & 0x3ff; // XLEFT
+ m_gv_lp_data[2] = ~ylo & 0x1ff; // YLO
+
+ if (m_gv_lp_interlace) {
+ m_gv_lp_xwindow = ((xp > (xc - 24)) && (xp < (xc + 24)));
+ m_gv_lp_ywindow = ((yp > (yc - 24)) && (yp < (yc + 24)));
+ } else {
+ m_gv_lp_xwindow = false;
+ m_gv_lp_ywindow = false;
+ }
+ }
+ // TODO: debug
+ //m_gv_lp_data[0] |= 0x8000;
+ if (!m_gv_lp_xwindow) {
+ BIT_SET(m_gv_lp_data[ 0 ], 13);
+ BIT_SET(m_gv_lp_data[ 1 ], 13);
+ BIT_SET(m_gv_lp_data[ 2 ], 13);
+ }
+ if (!m_gv_lp_ywindow) {
+ BIT_SET(m_gv_lp_data[ 1 ], 14);
+ BIT_SET(m_gv_lp_data[ 2 ], 14);
+ }
+ if (!m_gv_lp_status) {
+ BIT_SET(m_gv_lp_data[ 0 ], 11);
+ BIT_SET(m_gv_lp_data[ 1 ], 11);
+ BIT_SET(m_gv_lp_data[ 2 ], 11);
+ }
+ m_gv_lp_1sthit = true;
+ LOG(("LP data %d %04x %04x %04x\n" , m_gv_lp_selftest , m_gv_lp_data[ 0 ] , m_gv_lp_data[ 1 ] , m_gv_lp_data[ 2 ]));
+}
+
+const uint16_t hp9845ct_state::m_line_type[] = {
+ 0xffff, 0xaaaa, 0xff00, 0xfff0, 0xfffa, 0xfff6, 0xffb6, 0x0000
+};
+
+const uint16_t hp9845ct_state::m_area_fill[] = {
+ 0xffff, 0xefff, 0xefbf, 0xefaf, 0xafaf, 0xadaf, 0xada7, 0xada5,
+ 0xa5a5, 0xa4a5, 0xa4a1, 0xa4a0, 0xa0a0, 0x80a0, 0x8020, 0x8000
+};
+
+// ***************
+// hp9845c_state
+// ***************
+class hp9845c_state : public hp9845ct_state
+{
+public:
+ hp9845c_state(const machine_config &mconfig, device_type type, const char *tag);
+
+ virtual void machine_start() override;
+ virtual void machine_reset() override;
+
+ virtual DECLARE_READ16_MEMBER(graphic_r) override;
+ virtual DECLARE_WRITE16_MEMBER(graphic_w) override;
+
+ TIMER_DEVICE_CALLBACK_MEMBER(scanline_timer);
+
+protected:
+ virtual void set_graphic_mode(bool graphic , bool alpha) override;
+ void video_render_buff(unsigned video_scanline , unsigned line_in_row, bool buff_idx);
+ void graphic_video_render(unsigned video_scanline);
+ virtual void plot(uint16_t x, uint16_t y, bool draw_erase) override;
+ void pattern_fill(uint16_t x0 , uint16_t y0 , uint16_t x1 , uint16_t y1);
+
+ void check_io_counter_restore(void);
+ void advance_io_counter(void);
+ virtual void advance_gv_fsm(bool ds , bool trigger) override;
+
+ // Palette indexes
+ static constexpr unsigned pen_graphic(unsigned rgb) { return rgb; }
+ static constexpr unsigned pen_alpha(unsigned rgb) { return 8 + rgb; }
+ static constexpr unsigned pen_cursor(unsigned rgb) { return 16 + rgb; }
+
+ std::vector<uint16_t> m_graphic_mem[ 3 ];
+ uint16_t m_gv_music_memory;
+ uint8_t m_gv_cursor_color;
+ uint8_t m_gv_plane;
+ bool m_gv_plane_wrap;
+};
+
+hp9845c_state::hp9845c_state(const machine_config &mconfig, device_type type, const char *tag)
+ : hp9845ct_state(mconfig , type , tag)
+{
+}
+
+void hp9845c_state::machine_start()
+{
+ // Common part first
+ hp9845ct_state::machine_start();
+
+ m_graphic_mem[ 0 ].resize(GVIDEO_MEM_SIZE);
+ m_graphic_mem[ 1 ].resize(GVIDEO_MEM_SIZE);
+ m_graphic_mem[ 2 ].resize(GVIDEO_MEM_SIZE);
+
+ // initialize palette
+ // graphics colors
+ m_palette->set_pen_color(0, 0x00, 0x00, 0x00); // black
+ m_palette->set_pen_color(1, I_GR, 0x00, 0x00); // red
+ m_palette->set_pen_color(2, 0x00, I_GR, 0x00); // green
+ m_palette->set_pen_color(3, I_GR, I_GR, 0x00); // yellow
+ m_palette->set_pen_color(4, 0x00, 0x00, I_GR); // blue
+ m_palette->set_pen_color(5, I_GR, 0x00, I_GR); // magenta
+ m_palette->set_pen_color(6, 0x00, I_GR, I_GR); // cyan
+ m_palette->set_pen_color(7, I_GR, I_GR, I_GR); // white
+
+ // alpha colors
+ m_palette->set_pen_color(8, 0x00, 0x00, 0x00); // black
+ m_palette->set_pen_color(9, I_AL, 0x00, 0x00); // red
+ m_palette->set_pen_color(10, 0x00, I_AL, 0x00); // green
+ m_palette->set_pen_color(11, I_AL, I_AL, 0x00); // yellow
+ m_palette->set_pen_color(12, 0x00, 0x00, I_AL); // blue
+ m_palette->set_pen_color(13, I_AL, 0x00, I_AL); // magenta
+ m_palette->set_pen_color(14, 0x00, I_AL, I_AL); // cyan
+ m_palette->set_pen_color(15, I_AL, I_AL, I_AL); // white
+
+ // cursor colors
+ m_palette->set_pen_color(16, 0x80, 0x80, 0x80); // grey
+ m_palette->set_pen_color(17, I_CU, 0x00, 0x00); // red
+ m_palette->set_pen_color(18, 0x00, I_CU, 0x00); // green
+ m_palette->set_pen_color(19, I_CU, I_CU, 0x00); // yellow
+ m_palette->set_pen_color(20, 0x00, 0x00, I_CU); // blue
+ m_palette->set_pen_color(21, I_CU, 0x00, I_CU); // magenta
+ m_palette->set_pen_color(22, 0x00, I_CU, I_CU); // cyan
+ m_palette->set_pen_color(23, I_CU, I_CU, I_CU); // white
+}
+
+void hp9845c_state::machine_reset()
+{
+ // Common part first
+ hp9845ct_state::machine_reset();
+
+ set_video_mar(0);
+
+ // red -> plane #1, green -> plane #2, blue -> plane #3
+ m_gv_music_memory = 0x1 | (0x2 << 3) | (0x4 << 6);
+ // TODO: correct?
+ m_gv_cursor_color = 7;
+ m_gv_plane = 0;
+ m_gv_plane_wrap = false;
+}
+
+READ16_MEMBER(hp9845c_state::graphic_r)
+{
+ uint16_t res = 0;
+
+ switch (offset) {
+ case 0:
+ // R4: data register
+ if (m_gv_lp_en) {
+ res = lp_r4_r();
+ } else {
+ 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);
+ }
+ if (m_gv_lp_status && m_gv_lp_int_en) {
+ BIT_SET(res, 0); // Lightpen service request (also automatically set after system reset)
+ }
+ if (m_gv_sk_status) {
+ BIT_SET(res, 1); // Softkey service request
+ m_gv_sk_status = false;
+ }
+ BIT_SET(res, 11); // ID
+
+ update_graphic_bits();
+ break;
+
+ case 2:
+ // R6: data register with DMA TC
+ m_gv_dma_en = false;
+ if (m_gv_lp_en) {
+ res = lp_r4_r();
+ } else {
+ res = m_gv_data_r;
+ }
+ advance_gv_fsm(true , false);
+ break;
+
+ case 3:
+ // R7: not mapped
+ break;
+ }
+
+ LOG(("rd gv R%u = %04x\n", 4 + offset , res));
+
+ return res;
+}
+
+WRITE16_MEMBER(hp9845c_state::graphic_w)
+{
+ LOG(("wr gv R%u = %04x\n", 4 + offset , data));
+
+ switch (offset) {
+ case 0:
+ // R4: data register
+ m_gv_data_w = data;
+ advance_gv_fsm(true , false);
+ lp_r4_w(data);
+ break;
+
+ case 1:
+ // R5: command register
+ m_gv_cmd = (uint8_t)(data & 0xf);
+ m_gv_dma_en = BIT(data , 6) != 0;
+ m_gv_int_en = BIT(data , 7) != 0;
+ m_gv_gr_en = BIT(data , 8); // enables graphics controller & vector generator command processing and IRQs
+ m_gv_sk_en = BIT(data , 9); // enables reads on R4 to return SK keycode, also enables SK IRQs
+ m_gv_opt_en = BIT(data , 11); // not really used
+ m_gv_dsa_en = BIT(data , 12); // for factory use only (unknown)
+ if (BIT(data, 5)) {
+ m_gv_fsm_state = GV_STAT_RESET; // command/reset state machine
+ }
+ advance_gv_fsm(false , false);
+ lp_r5_w(data);
+ break;
+
+ case 2:
+ // R6: data register with DMA TC
+ m_gv_dma_en = false;
+ m_gv_data_w = data;
+ advance_gv_fsm(true , false);
+ lp_r4_w(data);
+ break;
+
+ case 3:
+ // R7: trigger
+ advance_gv_fsm(false , true);
+ break;
+ }
+}
+
+TIMER_DEVICE_CALLBACK_MEMBER(hp9845c_state::scanline_timer)
+{
+ unsigned video_scanline = param;
+
+ if (m_graphic_sel) {
+ if (video_scanline >= VIDEO_770_VBEND && video_scanline < VIDEO_770_VBSTART) {
+ graphic_video_render(video_scanline - VIDEO_770_VBEND);
+ }
+ }
+ if (video_scanline >= VIDEO_770_VBEND && video_scanline < VIDEO_770_VBSTART) {
+ unsigned row = (video_scanline - VIDEO_770_VBEND) / VIDEO_CHAR_HEIGHT;
+ unsigned line_in_row = (video_scanline - VIDEO_770_VBEND) - row * VIDEO_CHAR_HEIGHT;
+
+ 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(video_scanline , line_in_row , m_video_buff_idx);
+ }
+}
+
+void hp9845c_state::set_graphic_mode(bool graphic , bool alpha)
+{
+ m_graphic_sel = graphic;
+ m_alpha_sel = alpha;
+}
+
+void hp9845c_state::video_render_buff(unsigned video_scanline , unsigned line_in_row, bool buff_idx)
+{
+ if (!m_video_buff[ buff_idx ].full) {
+ m_video_blanked = true;
+ }
+
+ const pen_t *pen = m_palette->pens();
+
+ if (m_video_blanked || !m_alpha_sel) {
+ // Blank scanline
+ for (unsigned i = 0; i < VIDEO_770_ALPHA_L_LIM; i++) {
+ m_bitmap.pix32(video_scanline , i) = pen[ pen_alpha(0) ];
+ }
+ if (!m_graphic_sel) {
+ for (unsigned i = VIDEO_770_ALPHA_L_LIM; i < VIDEO_770_ALPHA_R_LIM; i++) {
+ m_bitmap.pix32(video_scanline , i) = pen[ pen_alpha(0) ];
+ }
+ }
+ for (unsigned i = VIDEO_770_ALPHA_R_LIM; i < VIDEO_TOT_HPIXELS; i++) {
+ m_bitmap.pix32(video_scanline , i) = pen[ pen_alpha(0) ];
+ }
+ } else {
+ bool cursor_line = line_in_row == 12;
+ bool ul_line = line_in_row == 14;
+ unsigned video_frame = (unsigned)m_screen->frame_number();
+ bool cursor_blink = BIT(video_frame , 3);
+ bool char_blink = BIT(video_frame , 4);
+
+ for (unsigned i = 0; i < 80; i++) {
+ uint8_t charcode = m_video_buff[ buff_idx ].chars[ i ] & 0x7f;
+ uint8_t attrs = m_video_buff[ buff_idx ].attrs[ i ];
+ uint16_t chrgen_addr = ((uint16_t)(charcode ^ 0x7f) << 4) | line_in_row;
+ uint16_t pixels;
+
+ if ((ul_line && BIT(attrs , 3)) ||
+ (cursor_line && cursor_blink && BIT(attrs , 0))) {
+ pixels = ~0;
+ } else if (char_blink && BIT(attrs , 2)) {
+ pixels = 0;
+ } else if (BIT(m_video_buff[ buff_idx ].chars[ i ] , 7)) {
+ pixels = (uint16_t)(m_optional_chargen[ chrgen_addr ] & 0x7f) << 1;
+ } else {
+ pixels = (uint16_t)(m_chargen[ chrgen_addr ] & 0x7f) << 1;
+ }
+
+ if (BIT(attrs , 1)) {
+ pixels = ~pixels;
+ }
+
+ for (unsigned j = 0; j < 9; j++) {
+ bool pixel = (pixels & (1U << j)) != 0;
+ unsigned x = i * 9 + j;
+
+ if (m_graphic_sel && x >= VIDEO_770_ALPHA_L_LIM && x < VIDEO_770_ALPHA_R_LIM) {
+ // alpha overlays graphics (non-dominating)
+ if (pixel) {
+ m_bitmap.pix32(video_scanline , x) = pen[ pen_alpha((attrs >> 4) & 7) ];
+ }
+ } else {
+ // Graphics disabled or alpha-only zone
+ m_bitmap.pix32(video_scanline , x) = pen[ pixel ? pen_alpha((attrs >> 4) & 7) : pen_alpha(0) ];
+ }
+ }
+ }
+ }
+}
+
+void hp9845c_state::graphic_video_render(unsigned video_scanline)
+{
+ // video_scanline is 0-based, i.e. the topmost visible line of graphic screen is 0
+ const pen_t *pen = m_palette->pens();
+ bool yc, yw, blink, lp_cursor;
+ uint16_t word0, word1, word2;
+ uint8_t pen0, pen1, pen2;
+
+ yc = (video_scanline + 42) == m_gv_cursor_y;
+
+ // apply music memory
+ pen0 = (m_gv_music_memory & 0x001) | ((m_gv_music_memory & 0x008) >> 2) | ((m_gv_music_memory & 0x040) >> 4);
+ pen1 = ((m_gv_music_memory & 0x002) >> 1) | ((m_gv_music_memory & 0x010) >> 3) | ((m_gv_music_memory & 0x080) >> 5);
+ pen2 = ((m_gv_music_memory & 0x004) >> 2) | ((m_gv_music_memory & 0x020) >> 4) | ((m_gv_music_memory & 0x100) >> 6);
+
+ // 49 pixel lightpen cross hair cursor
+ lp_cursor = (m_gv_lp_cursor_x < VIDEO_TOT_HPIXELS) && (m_gv_lp_cursor_y < GVIDEO_VPIXELS);
+ if (lp_cursor) {
+ yc = video_scanline == (m_gv_lp_cursor_y + 24);
+ if (m_gv_lp_cursor_fs)
+ yw = true;
+ else
+ yw = video_scanline >= m_gv_lp_cursor_y &&
+ video_scanline <= (m_gv_lp_cursor_y + 49);
+ blink = true;
+ } else if (m_gv_cursor_fs) {
+ yw = true;
+ // Steady cursor
+ blink = true;
+ } else {
+ // 15 pixel
+ yw = ((video_scanline + 50) > m_gv_cursor_y &&
+ (video_scanline + 50) < (m_gv_cursor_y + 7)) ||
+ ((video_scanline + 50) > (m_gv_cursor_y + 9) &&
+ (video_scanline + 50) < (m_gv_cursor_y + 16));
+ if (m_gv_cursor_gc) {
+ blink = true;
+ } else {
+ // Blinking cursor (frame freq. / 16)
+ blink = BIT(m_screen->frame_number() , 3) != 0;
+ }
+ }
+
+ unsigned mem_idx = get_gv_mem_addr(0 , video_scanline);
+ for (unsigned i = 0; i < GVIDEO_HPIXELS; i += 16) {
+ word0 = m_graphic_mem[ 0 ][ mem_idx ];
+ word1 = m_graphic_mem[ 1 ][ mem_idx ];
+ word2 = m_graphic_mem[ 2 ][ mem_idx ];
+ mem_idx++;
+ unsigned x = i;
+ for (uint16_t mask = 0x8000; mask != 0; mask >>= 1) {
+ bool xc = false;
+ bool xw = false;
+ unsigned pixel;
+
+ if (lp_cursor) {
+ // lightpen cursor
+ xc = (x + VIDEO_770_ALPHA_L_LIM) == m_gv_lp_cursor_x;
+ xw = m_gv_lp_cursor_fs || ((x + 24 + VIDEO_770_ALPHA_L_LIM) >= m_gv_lp_cursor_x && (x + VIDEO_770_ALPHA_L_LIM - 25) <= m_gv_lp_cursor_x);
+ } else if (m_gv_cursor_gc) {
+ xc = (x + 61) == m_gv_cursor_x;
+ xw = m_gv_cursor_fs || ((x + 69) > m_gv_cursor_x && (x + 53) < m_gv_cursor_x && ((x + 62) < m_gv_cursor_x || (x + 60) > m_gv_cursor_x));
+ }
+ if (blink && ((xw && yc) || (yw && xc && (m_gv_cursor_gc || lp_cursor)))) {
+ // Cursor (LP cursor is white)
+ pixel = lp_cursor ? pen_cursor(7) : pen_cursor(m_gv_cursor_color);
+ } else {
+ // Normal pixel
+ pixel = pen_graphic(((word0 & mask) ? pen0 : 0) | ((word1 & mask) ? pen1 : 0) | ((word2 & mask) ? pen2 : 0));
+ }
+ m_bitmap.pix32(video_scanline , VIDEO_770_ALPHA_L_LIM + x++) = pen[ pixel ];
+ }
+ }
+}
+
+void hp9845c_state::plot(uint16_t x, uint16_t y, bool draw_erase)
+{
+ uint16_t addr, pixel_mask;
+ bool do_draw, do_erase, dominance;
+
+ pixel_mask = 0x8000 >> (x & 0xf);
+
+ addr = get_gv_mem_addr(x >> 4 , y);
+ dominance = BIT(m_gv_memory_control, 6);
+ if (BIT(m_gv_memory_control, 0)) {
+ do_erase = dominance;
+ do_draw = draw_erase;
+ if (!BIT(m_gv_memory_control, 3) && draw_erase) {
+ do_draw = false;
+ do_erase = true;
+ }
+ if (do_draw)
+ m_graphic_mem[0][ addr ] |= pixel_mask;
+ else if (do_erase)
+ m_graphic_mem[0][ addr ] &= ~pixel_mask;
+ }
+ if (BIT(m_gv_memory_control, 1)) {
+ do_erase = dominance;
+ do_draw = draw_erase;
+ if (!BIT(m_gv_memory_control, 4) && draw_erase) {
+ do_draw = false;
+ do_erase = true;
+ }
+ if (do_draw)
+ m_graphic_mem[1][ addr ] |= pixel_mask;
+ else if (do_erase)
+ m_graphic_mem[1][ addr ] &= ~pixel_mask;
+ }
+ if (BIT(m_gv_memory_control, 2)) {
+ do_erase = dominance;
+ do_draw = draw_erase;
+ if (!BIT(m_gv_memory_control, 5) && draw_erase) {
+ do_draw = false;
+ do_erase = true;
+ }
+ if (do_draw)
+ m_graphic_mem[2][ addr ] |= pixel_mask;
+ else if (do_erase)
+ m_graphic_mem[2][ addr ] &= ~pixel_mask;
+ }
+}
+
+void hp9845c_state::pattern_fill(uint16_t x0 , uint16_t y0 , uint16_t x1 , uint16_t y1)
+{
+ uint16_t x,y,xmax,ymax;
+ uint16_t pixel_mask, fill_mask;
+
+ x = std::min(x0 , x1);
+ xmax = std::max(x0 , x1);
+ y = std::min(y0 , y1);
+ ymax = std::max(y0 , y1);
+
+ for (;y <= ymax; y++) {
+ fill_mask = (m_area_fill[ m_gv_line_type_area_fill & 0xf ] << (y % 4) * 4) & 0xf000;
+ fill_mask |= (fill_mask >> 4) | (fill_mask >> 8) | (fill_mask >> 12);
+ for (;x <= xmax; x++) {
+ pixel_mask = (0x8000 >> (x % 16));
+ plot(x , y , (pixel_mask & fill_mask) != 0);
+ }
+ }
+}
+
+void hp9845c_state::check_io_counter_restore(void)
+{
+ if (m_gv_last_cmd != m_gv_cmd) {
+ // restore memory counter
+ m_gv_io_counter = get_gv_mem_addr(m_gv_word_x_position , m_gv_word_y_position);
+ // no auto-increment when switching commands
+ if (m_gv_plane_wrap) {
+ m_gv_plane = 2;
+ } else if (m_gv_plane > 0) {
+ m_gv_plane--;
+ }
+ m_gv_last_cmd = m_gv_cmd;
+ }
+}
+
+void hp9845c_state::advance_io_counter(void)
+{
+ m_gv_plane++;
+ if (m_gv_plane > 2) {
+ if (m_gv_io_counter < GVIDEO_ADDR_MASK) {
+ m_gv_plane = 0;
+ m_gv_io_counter++;
+ } else {
+ m_gv_plane = 2;
+ }
+ m_gv_plane_wrap = true;
+ }
+}
+
+void hp9845c_state::advance_gv_fsm(bool ds , bool trigger)
+{
+ if (!m_gv_gr_en) {
+ return;
+ }
+
+ bool get_out = false;
+
+ attotime time_mem_av;
+
+ do {
+ // U73 on vector generator board
+ bool act_trig = trigger || m_gv_int_en || !BIT(m_gv_cmd , 0);
+
+ switch (m_gv_fsm_state) {
+ case GV_STAT_WAIT_DS_0:
+ // inital state (same as GV_STAT_RESET), command received
+ if (m_gv_cmd == 0x1) {
+ // read words command
+ check_io_counter_restore();
+ LOG(("read words, last = %x\n", m_gv_last_cmd));
+ m_gv_fsm_state = GV_STAT_WAIT_MEM_0; // -> read stream
+ m_gv_last_cmd = m_gv_cmd;
+ } else if (ds) {
+ if ((m_gv_cmd == 0x0) || (m_gv_cmd == 0x2)) {
+ // write words & clear/set words commands
+ check_io_counter_restore();
+ if (m_gv_cmd == 0x2) LOG(("clear/set words, last = %x\n", m_gv_last_cmd));
+ else LOG(("write words, last = %x\n", m_gv_last_cmd));
+ m_gv_fsm_state = GV_STAT_WAIT_TRIG_1; // -> write stream
+ } else {
+ // any other command
+ m_gv_fsm_state = GV_STAT_WAIT_TRIG_0; // -> wait for trigger
+ }
+ m_gv_last_cmd = m_gv_cmd;
+ } else {
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_TRIG_0:
+ // process data on R4 or R6
+ if (act_trig) {
+ switch (m_gv_cmd) {
+ case 0x8: // load X I/O address
+ m_gv_word_x_position = ~m_gv_data_w & 0x3f; // 0..34
+ LOG(("load X I/O adress = %04x\n", m_gv_word_x_position));
+ m_gv_io_counter = get_gv_mem_addr(m_gv_word_x_position , m_gv_word_y_position);
+ m_gv_plane = 0;
+ m_gv_plane_wrap = false;
+ break;
+ case 0x9: // load Y I/O address
+ m_gv_word_y_position = ~m_gv_data_w & 0x1ff; // 0..454
+ LOG(("load Y I/O adress = %04x\n", m_gv_word_y_position));
+ m_gv_io_counter = get_gv_mem_addr(m_gv_word_x_position , m_gv_word_y_position);
+ m_gv_plane = 0;
+ m_gv_plane_wrap = false;
+ break;
+ case 0xa: // load memory control
+ m_gv_memory_control = m_gv_data_w & 0x7f;
+ LOG(("load memory control = %04x\n", m_gv_memory_control));
+ break;
+ case 0xb: // set line type/area fill
+ m_gv_line_type_area_fill = m_gv_data_w & 0x1ff;
+ if (BIT(m_gv_line_type_area_fill, 4)) {
+ m_gv_line_type_mask = m_line_type[ m_gv_line_type_area_fill & 0x7 ];
+ m_gv_repeat_count = 0;
+ }
+ LOG(("set line type = %04x\n", m_gv_line_type_area_fill));
+ break;
+ case 0xc: // load color mask
+ m_gv_music_memory = m_gv_data_w & 0x1ff;
+ LOG(("load color mask = %04x\n", m_gv_music_memory));
+ break;
+ case 0xd: // load end points
+ m_gv_ypt = ~m_gv_data_w & 0x1ff;
+ LOG(("load end points y = %d\n", m_gv_ypt));
+ break;
+ case 0xe: // Y cursor position & color
+ m_gv_cursor_color = ~m_gv_data_w & 0x7;
+ m_gv_cursor_y = 1073 - (m_gv_data_w >> 6);
+ if (m_gv_cursor_fs) m_gv_cursor_y -= 8;
+ LOG(("Y cursor position = %d, color = %d\n", m_gv_cursor_y, m_gv_cursor_color));
+ break;
+ case 0xf: // X cursor position & type
+ m_gv_cursor_fs = BIT(m_gv_data_w, 0);
+ m_gv_cursor_gc = BIT(m_gv_data_w, 1) || m_gv_cursor_fs;
+ m_gv_cursor_x = ((m_gv_data_w >> 6) & 0x3ff) - 42;
+ if (m_gv_cursor_fs) m_gv_cursor_x -= 8;
+
+ LOG(("X cursor position = %d, fs = %d, gc = %d\n", m_gv_cursor_x, m_gv_cursor_fs, m_gv_cursor_gc));
+ break;
+ default:
+ logerror("unknown 98770A command = %d, parm = 0x%04x\n", m_gv_cmd, m_gv_data_w);
+ }
+ if (m_gv_cmd == 0xd) {
+ m_gv_fsm_state = GV_STAT_WAIT_DS_2; // -> get second data word
+ } else {
+ get_out = true;
+ m_gv_fsm_state = GV_STAT_WAIT_DS_0; // -> done
+ }
+ } else {
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_MEM_0:
+ // process data during read transfer
+ 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_plane ][ m_gv_io_counter ];
+ LOG(("read words @%04x = %04x, plane #%d\n" , m_gv_io_counter , m_gv_data_r, m_gv_plane + 1));
+ advance_io_counter();
+ m_gv_fsm_state = GV_STAT_WAIT_DS_1; // -> proceed with read stream
+ } else {
+ m_gv_timer->adjust(time_mem_av);
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_DS_1:
+ // wait for data word to be read
+ if (ds) {
+ // -- next word
+ m_gv_fsm_state = GV_STAT_WAIT_MEM_0; // -> process data word
+ } else {
+ // -- done
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_DS_2:
+ // wait for data word to be written
+ if (ds) {
+ // -- next word
+ m_gv_fsm_state = GV_STAT_WAIT_TRIG_1; // -> process data word
+ } else {
+ // done
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_TRIG_1:
+ // process multi-word parameters & data during write transfer
+ if (act_trig) {
+ if (m_gv_cmd == 0xd) {
+ // load endpoints command
+ m_gv_xpt = ~m_gv_data_w & 0x3ff;
+ if (BIT(m_gv_data_w, 10)) {
+ // draw vector
+ LOG(("load end points x = %d (draw)\n", m_gv_xpt));
+ m_gv_fsm_state = GV_STAT_WAIT_MEM_2; // -> proceed with draw vector
+ } else {
+ LOG(("load end points x = %d (move)\n", m_gv_xpt));
+ m_gv_last_xpt = m_gv_xpt;
+ m_gv_last_ypt = m_gv_ypt;
+ m_gv_fsm_state = GV_STAT_WAIT_DS_0; // -> proceed with next word pair
+ }
+ } else if (m_gv_cmd == 0x2) {
+ // clear/set words command
+ m_gv_data_w = BIT(m_gv_memory_control, m_gv_plane + 3) ? 0xffff : 0;
+ m_gv_fsm_state = GV_STAT_WAIT_MEM_1; // -> proceed with next word
+ } else if (m_gv_cmd == 0x0) {
+ // write words command
+ m_gv_fsm_state = GV_STAT_WAIT_MEM_1; // -> proceed with next word
+ }
+ } else {
+ // done
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_MEM_1:
+ // -- transfer from bus to graphics memory to bus within write transfer
+ time_mem_av = time_to_gv_mem_availability();
+ if (time_mem_av.is_zero()) {
+ // Write a full word to graphic memory
+ LOG(("write words @%04x = %04x, plane #%d\n" , m_gv_io_counter , m_gv_data_w, m_gv_plane + 1));
+ if ((m_gv_cmd == 0x0) || BIT(m_gv_memory_control, m_gv_plane)) {
+ m_graphic_mem[ m_gv_plane ][ m_gv_io_counter ] = m_gv_data_w;
+ }
+ advance_io_counter();
+ m_gv_fsm_state = GV_STAT_WAIT_DS_2; // -> proceed with write stream
+ } else {
+ m_gv_timer->adjust(time_mem_av);
+ get_out = true;
+ }
+ break;
+
+ case GV_STAT_WAIT_MEM_2:
+ // vector generator
+ time_mem_av = time_to_gv_mem_availability();
+ if (time_mem_av.is_zero()) {
+ if (BIT (m_gv_line_type_area_fill, 4)) {
+ unsigned x0;
+ unsigned x1;
+ unsigned y0;
+ unsigned y1;
+
+ // vector generator uses normalization
+ if (m_gv_xpt > m_gv_last_xpt) {
+ x0 = m_gv_last_xpt;
+ y0 = m_gv_last_ypt;
+ x1 = m_gv_xpt;
+ y1 = m_gv_ypt;
+ } else {
+ x0 = m_gv_xpt;
+ y0 = m_gv_ypt;
+ x1 = m_gv_last_xpt;
+ y1 = m_gv_last_ypt;
+ }
+ draw_line(x0 , y0 , x1 , y1);
+ } else {
+ // fill area with pattern
+ LOG(("area fill (%d,%d) -> (%d,%d) pattern=%04x\n", m_gv_last_xpt, m_gv_last_ypt, m_gv_xpt, m_gv_ypt, m_gv_line_type_area_fill));
+
+ pattern_fill(m_gv_xpt , m_gv_ypt , m_gv_last_xpt , m_gv_last_ypt);
+ }
+ m_gv_last_xpt = m_gv_xpt;
+ m_gv_last_ypt = m_gv_ypt;
+ m_gv_fsm_state = GV_STAT_WAIT_DS_0; // -> proceed with next word pair
+ } else {
+ m_gv_timer->adjust(time_mem_av);
+ 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();
}
static MACHINE_CONFIG_START( hp9845a, hp9845_state )
@@ -1306,6 +2586,20 @@ static MACHINE_CONFIG_START(hp9845b, hp9845b_state)
MACHINE_CONFIG_END
+static MACHINE_CONFIG_START(hp9845c, hp9845c_state)
+ MCFG_FRAGMENT_ADD(hp9845_base)
+ // video hardware
+ MCFG_SCREEN_MODIFY("screen")
+ MCFG_SCREEN_UPDATE_DRIVER(hp9845c_state, screen_update)
+ MCFG_SCREEN_VBLANK_DRIVER(hp9845c_state, vblank_w)
+ MCFG_SCREEN_RAW_PARAMS(VIDEO_770_PIXEL_CLOCK , VIDEO_770_HTOTAL , VIDEO_770_HBEND , VIDEO_770_HBSTART , VIDEO_770_VTOTAL , VIDEO_770_VBEND , VIDEO_770_VBSTART)
+ MCFG_PALETTE_ADD("palette", 24)
+ MCFG_TIMER_DRIVER_ADD_SCANLINE("scantimer", hp9845c_state, scanline_timer, "screen", 0, 1)
+
+ MCFG_SOFTWARE_LIST_ADD("optrom_list", "hp9845b_rom")
+
+MACHINE_CONFIG_END
+
ROM_START( hp9845a )
ROM_REGION( 0200000, "lpu", ROMREGION_16BIT | ROMREGION_BE )
ROM_LOAD( "09845-65544-65547-03-system_lpu.bin", 0000000, 0200000, CRC(47beb87f) SHA1(456caefacafcf19435e1e7e68b1c1e4010841664) )
@@ -1407,7 +2701,20 @@ ROM_START( hp9845b )
ROM_END
#define rom_hp9845t rom_hp9845b
-#define rom_hp9845c rom_hp9845b
+
+ROM_START( hp9845c )
+ ROM_REGION(0x800 , "chargen" , 0)
+ ROM_LOAD("chrgen.bin" , 0 , 0x800 , CRC(fe9e844f) SHA1(0c45ae00766ceba94a19bd5e154bd6d23e208cca))
+
+ ROM_REGION(0x800 , "optional_chargen" , 0)
+ ROM_LOAD("optional_chrgen.bin" , 0 , 0x800 , CRC(0ecfa63b) SHA1(c295e6393d1503d903c1d2ce576fa597df9746bf))
+
+ ROM_REGION(0x10000, "lpu", ROMREGION_16BIT | ROMREGION_BE)
+ ROM_LOAD("9845-LPU-Standard-Processor.bin", 0, 0x10000, CRC(dc266c1b) SHA1(1cf3267f13872fbbfc035b70f8b4ec6b5923f182))
+
+ ROM_REGION(0x10000, "ppu", ROMREGION_16BIT | ROMREGION_BE)
+ ROM_LOAD("9845-PPU-Color-Enhanced-Graphics.bin", 0, 0x10000, CRC(96e11edc) SHA1(3f1da50edb35dfc57ec2ecfd816a8c8230e110bd))
+ROM_END
COMP( 1978, hp9845a, 0, 0, hp9845a, hp9845, driver_device, 0, "Hewlett-Packard", "9845A", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1978, hp9845s, hp9845a, 0, hp9845a, hp9845, driver_device, 0, "Hewlett-Packard", "9845S", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
@@ -1415,4 +2722,4 @@ COMP( 1979, hp9835a, 0, 0, hp9835a, hp9845, driver_device, 0,
COMP( 1979, hp9835b, hp9835a, 0, hp9835a, hp9845, driver_device, 0, "Hewlett-Packard", "9835B", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1980, hp9845b, 0, 0, hp9845b, hp9845_base,driver_device, 0, "Hewlett-Packard", "9845B", 0 )
COMP( 1980, hp9845t, hp9845b, 0, hp9845b, hp9845_base,driver_device, 0, "Hewlett-Packard", "9845T", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
-COMP( 1981, hp9845c, hp9845b, 0, hp9845b, hp9845_base,driver_device, 0, "Hewlett-Packard", "9845C", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
+COMP( 1981, hp9845c, 0, 0, hp9845c, hp9845ct,driver_device, 0, "Hewlett-Packard", "9845C", 0 )
diff --git a/src/mame/includes/hp9845.h b/src/mame/includes/hp9845.h
index 82b221a976c..ee9d3c3fbee 100644
--- a/src/mame/includes/hp9845.h
+++ b/src/mame/includes/hp9845.h
@@ -24,10 +24,9 @@ public:
TIMER_DEVICE_CALLBACK_MEMBER(gv_timer);
- DECLARE_READ16_MEMBER(graphic_r);
- DECLARE_WRITE16_MEMBER(graphic_w);
+ virtual DECLARE_READ16_MEMBER(graphic_r) = 0;
+ virtual DECLARE_WRITE16_MEMBER(graphic_w) = 0;
attotime time_to_gv_mem_availability(void) const;
- void update_graphic_bits(void);
IRQ_CALLBACK_MEMBER(irq_callback);
void update_irq(void);
@@ -70,15 +69,13 @@ protected:
void setup_ram_block(unsigned block , unsigned offset);
- virtual uint16_t graphic_r5_r(void) = 0;
- virtual void graphic_r5_w(uint16_t data) = 0;
virtual void advance_gv_fsm(bool ds , bool trigger) = 0;
// Character generator
required_region_ptr<uint8_t> m_chargen;
// Optional character generator
- optional_region_ptr<uint8_t> m_optional_chargen;
+ required_region_ptr<uint8_t> m_optional_chargen;
// Text mode video I/F
typedef struct {
@@ -117,7 +114,11 @@ protected:
uint8_t m_gv_cmd; // U65 (GC)
uint16_t m_gv_data_w; // U29, U45, U28 & U44 (GC)
uint16_t m_gv_data_r; // U59 & U60 (GC)
- uint16_t m_gv_cursor_w; // U38 & U39 (GS)
+ uint16_t m_gv_io_counter; // U1, U2, U14 & U15 (GC)
+ uint16_t m_gv_cursor_x; // U31 & U23 (GS)
+ uint16_t m_gv_cursor_y; // U15 & U8 (GS)
+ bool m_gv_cursor_gc; // U8 (GS)
+ bool m_gv_cursor_fs; // U8 (GS)
// Interrupt handling
uint8_t m_irl_pending;