summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/hp/hp48_m.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/hp/hp48_m.cpp')
-rw-r--r--src/mame/hp/hp48_m.cpp1045
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/mame/hp/hp48_m.cpp b/src/mame/hp/hp48_m.cpp
new file mode 100644
index 00000000000..aacd4da08ae
--- /dev/null
+++ b/src/mame/hp/hp48_m.cpp
@@ -0,0 +1,1045 @@
+// license:BSD-3-Clause
+// copyright-holders:Antoine Mine
+/**********************************************************************
+
+ Copyright (C) Antoine Mine' 2008
+
+ Hewlett Packard HP48 S/SX & G/GX/G+ and HP49 G
+
+**********************************************************************/
+
+#include "emu.h"
+#include "hp48.h"
+
+#include "cpu/saturn/saturn.h"
+#include "machine/nvram.h"
+
+#include "screen.h"
+
+#define LOG_SERIAL (1U << 1)
+#define LOG_INVALID (1U << 2)
+
+#define VERBOSE (LOG_INVALID)
+#include "logmacro.h"
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* state field in hp48_module */
+#define HP48_MODULE_UNCONFIGURED 0
+#define HP48_MODULE_MASK_KNOWN 1
+#define HP48_MODULE_CONFIGURED 2
+
+/***************************************************************************
+ GLOBAL VARIABLES & CONSTANTS
+***************************************************************************/
+
+/* current HP48 model */
+
+#define HP48_G_SERIES ((m_model==HP48_G) || (m_model==HP48_GX) || (m_model==HP48_GP))
+#define HP48_S_SERIES ((m_model==HP48_S) || (m_model==HP48_SX))
+#define HP48_X_SERIES ((m_model==HP48_SX) || (m_model==HP48_GX))
+#define HP48_GX_MODEL ((m_model==HP48_GX) || (m_model==HP48_GP))
+#define HP49_G_MODEL ((m_model==HP49_G))
+
+static const char *const hp48_module_names[6] =
+{ "HDW (I/O)", "NCE2 (RAM)", "CE1", "CE2", "NCE3", "NCE1 (ROM)" };
+
+/* values returned by C=ID */
+static const uint8_t hp48_module_mask_id[6] = { 0x00, 0x03, 0x05, 0x07, 0x01, 0x00 };
+static const uint8_t hp48_module_addr_id[6] = { 0x19, 0xf4, 0xf6, 0xf8, 0xf2, 0x00 };
+
+
+
+
+
+/***************************************************************************
+ FUNCTIONS
+***************************************************************************/
+
+void hp48_state::pulse_irq(int irq_line)
+{
+ m_maincpu->set_input_line(irq_line, ASSERT_LINE);
+ m_maincpu->set_input_line(irq_line, CLEAR_LINE);
+}
+
+
+/* ---------------- serial --------------------- */
+
+#define RS232_DELAY attotime::from_usec(300)
+
+/* end of receive event */
+TIMER_CALLBACK_MEMBER(hp48_state::rs232_byte_recv_cb)
+{
+ LOGMASKED(LOG_SERIAL, "%f hp48_state::rs232_byte_recv_cb: end of receive, data=%02x\n", machine().time().as_double(), param);
+
+ m_io[0x14] = param & 0xf; /* receive zone */
+ m_io[0x15] = param >> 4;
+ m_io[0x11] &= ~2; /* clear byte receiving */
+ m_io[0x11] |= 1; /* set byte received */
+
+ /* interrupt */
+ if (m_io[0x10] & 2)
+ {
+ pulse_irq(SATURN_IRQ_LINE);
+ }
+}
+
+/* outside world initiates a receive event */
+void hp48_state::rs232_start_recv_byte(uint8_t data)
+{
+ LOGMASKED(LOG_SERIAL, "%f hp48_state::rs232_start_recv_byte: start receiving, data=%02x\n", machine().time().as_double(), data);
+
+ m_io[0x11] |= 2; /* set byte receiving */
+
+ /* interrupt */
+ if (m_io[0x10] & 1)
+ {
+ pulse_irq(SATURN_IRQ_LINE);
+ }
+
+ /* schedule end of reception */
+ m_recv_done_timer->adjust(RS232_DELAY, data);
+}
+
+
+/* end of send event */
+TIMER_CALLBACK_MEMBER(hp48_state::rs232_byte_sent_cb)
+{
+ LOGMASKED(LOG_SERIAL, "%f hp48_state::rs232_byte_sent_cb: end of send, data=%02x\n", machine().time().as_double(), param);
+
+ m_io[0x12] &= ~3; /* clear byte sending and buffer full */
+
+ /* interrupt */
+ if (m_io[0x10] & 4)
+ {
+ pulse_irq(SATURN_IRQ_LINE);
+ }
+}
+
+/* CPU initiates a send event */
+void hp48_state::rs232_send_byte()
+{
+ uint8_t data = HP48_IO_8(0x16); /* byte to send */
+
+ LOGMASKED(LOG_SERIAL, "%s %f hp48_state::rs232_send_byte: start sending, data=%02x\n", machine().describe_context(), machine().time().as_double(), data);
+
+ /* set byte sending and send buffer full */
+ m_io[0x12] |= 3;
+
+ /* schedule transmission */
+ m_send_done_timer->adjust(RS232_DELAY, data);
+}
+
+
+
+
+/* ------ Saturn's IN / OUT registers ---------- */
+
+
+/* CPU sets OUT register (keyboard + beeper) */
+void hp48_state::reg_out(uint32_t data)
+{
+ LOG("%s %f hp48_state::reg_out: %03x\n", machine().describe_context(), machine().time().as_double(), data);
+
+ /* bits 0-8: keyboard lines */
+ m_out = data & 0x1ff;
+
+ /* bits 9-10: unused */
+
+ /* bit 11: beeper */
+ m_dac->write(BIT(data, 11));
+}
+
+int hp48_state::get_in()
+{
+ int in = 0;
+
+ /* regular keys */
+ if (BIT(m_out, 0)) in |= ioport("LINE0")->read();
+ if (BIT(m_out, 1)) in |= ioport("LINE1")->read();
+ if (BIT(m_out, 2)) in |= ioport("LINE2")->read();
+ if (BIT(m_out, 3)) in |= ioport("LINE3")->read();
+ if (BIT(m_out, 4)) in |= ioport("LINE4")->read();
+ if (BIT(m_out, 5)) in |= ioport("LINE5")->read();
+ if (BIT(m_out, 6)) in |= ioport("LINE6")->read();
+ if (BIT(m_out, 7)) in |= ioport("LINE7")->read();
+ if (BIT(m_out, 8)) in |= ioport("LINE8")->read();
+
+ /* on key */
+ in |= ioport("ON")->read();
+
+ return in;
+}
+
+/* CPU reads IN register (keyboard) */
+uint32_t hp48_state::reg_in()
+{
+ int in = get_in();
+ LOG("%s %f hp48_state::reg_in: %04x\n", machine().describe_context(), machine().time().as_double(), in);
+ return in;
+}
+
+/* key detect */
+void hp48_state::update_kdn()
+{
+ int in = get_in();
+
+ /* interrupt on raising edge */
+ if (in && !m_kdn)
+ {
+ LOG("%f hp48_state::update_kdn: interrupt\n", machine().time().as_double());
+ m_io[0x19] |= 8; // service request
+ pulse_irq(SATURN_WAKEUP_LINE);
+ pulse_irq(SATURN_IRQ_LINE);
+ }
+
+ m_kdn = (in != 0);
+}
+
+/* periodic keyboard polling, generates an interrupt */
+TIMER_CALLBACK_MEMBER(hp48_state::kbd_cb)
+{
+ /* NMI for ON key */
+ if (ioport( "ON" )->read())
+ {
+ LOG("%f hp48_state::kbd_cb: keyboard interrupt, on key\n", machine().time().as_double());
+ m_io[0x19] |= 8; // set service request
+ pulse_irq(SATURN_WAKEUP_LINE);
+ pulse_irq(SATURN_NMI_LINE);
+ return;
+ }
+
+ /* regular keys */
+ update_kdn();
+}
+
+/* RSI opcode */
+void hp48_state::rsi(int state)
+{
+ LOG("%s %f hp48_state::rsi\n", machine().describe_context(), machine().time().as_double());
+
+ // enables interrupts on key repeat (normally, there is only one interrupt, when the key is pressed)
+ m_kdn = 0;
+}
+
+
+/* ------------- annonciators ------------ */
+
+void hp48_state::update_annunciators()
+{
+ /* bit 0: left shift
+ bit 1: right shift
+ bit 2: alpha
+ bit 3: alert
+ bit 4: busy
+ bit 5: transmit
+ bit 7: master enable
+ */
+ int markers = HP48_IO_8(0xb);
+ m_lshift0 = (markers & 0x81) == 0x81;
+ m_rshift0 = (markers & 0x82) == 0x82;
+ m_alpha0 = (markers & 0x84) == 0x84;
+ m_alert0 = (markers & 0x88) == 0x88;
+ m_busy0 = (markers & 0x90) == 0x90;
+ m_transmit0 = (markers & 0xb0) == 0xb0;
+}
+
+
+/* ------------- I/O registers ----------- */
+
+/* Some part of the I/O registers are simple r/w registers. We store them in hp48_io.
+ Special cases are registers that:
+ - have a different meaning on read and write
+ - perform some action on read / write
+ */
+
+void hp48_state::io_w(offs_t offset, uint8_t data)
+{
+ LOG("%s %f hp48_state::io_w: off=%02x data=%x\n", machine().describe_context(), machine().time().as_double(), offset, data);
+
+ switch (offset)
+ {
+ /* CRC register */
+ case 0x04: m_crc = (m_crc & 0xfff0) | data; break;
+ case 0x05: m_crc = (m_crc & 0xff0f) | (data << 4); break;
+ case 0x06: m_crc = (m_crc & 0xf0ff) | (data << 8); break;
+ case 0x07: m_crc = (m_crc & 0x0fff) | (data << 12); break;
+
+ /* annunciators */
+ case 0x0b:
+ case 0x0c:
+ m_io[offset] = data;
+ update_annunciators();
+ break;
+
+ /* cntrl ROM */
+ case 0x29:
+ {
+ int old_cntrl = m_io[offset] & 8;
+ m_io[offset] = data;
+ if (old_cntrl != (data & 8))
+ {
+ apply_modules();
+ }
+ break;
+ }
+
+ /* timers */
+ case 0x37: m_timer1 = data; break;
+ case 0x38: m_timer2 = (m_timer2 & 0xfffffff0) | data; break;
+ case 0x39: m_timer2 = (m_timer2 & 0xffffff0f) | (data << 4); break;
+ case 0x3a: m_timer2 = (m_timer2 & 0xfffff0ff) | (data << 8); break;
+ case 0x3b: m_timer2 = (m_timer2 & 0xffff0fff) | (data << 12); break;
+ case 0x3c: m_timer2 = (m_timer2 & 0xfff0ffff) | (data << 16); break;
+ case 0x3d: m_timer2 = (m_timer2 & 0xff0fffff) | (data << 20); break;
+ case 0x3e: m_timer2 = (m_timer2 & 0xf0ffffff) | (data << 24); break;
+ case 0x3f: m_timer2 = (m_timer2 & 0x0fffffff) | (data << 28); break;
+
+ /* cards */
+ case 0x0e:
+ LOG("%s: card control write %02x\n", machine().describe_context(), data);
+
+ /* bit 0: software interrupt */
+ if (data & 1)
+ {
+ LOG("%f hp48_state::io_w: software interrupt requested\n", machine().time().as_double());
+ pulse_irq(SATURN_IRQ_LINE);
+ data &= ~1;
+ }
+
+ /* XXX not implemented
+ bit 1: card test?
+ */
+
+ m_io[0x0e] = data;
+ break;
+
+ case 0x0f:
+ LOG("%s: card info write %02x\n", machine().describe_context(), data);
+ m_io[0x0f] = data;
+ break;
+
+ /* serial */
+ case 0x13:
+ m_io[0x11] &= ~4; /* clear error status */
+ break;
+ case 0x16:
+ /* first nibble of sent data */
+ m_io[offset] = data;
+ break;
+ case 0x17:
+ /* second nibble of sent data */
+ m_io[offset] = data;
+ rs232_send_byte();
+ break;
+
+ /* XXX not implemented:
+
+ - 0x0d: RS232c speed:
+ bits 0-2: speed
+ 000 = 1200 bauds
+ 010 = 2400 bauds
+ 100 = 4800 bauds
+ 110 = 9600 bauds
+ bit 3: ?
+
+ - 0x1a: I/R input
+ bit 0: irq
+ bit 1: irq enable
+ bit 2: 1=RS232c mode 0=direct mode
+ bit 3: receiving
+
+ - 0x1c: I/R output control
+ bit 0: buffer full
+ bit 1: transmitting
+ bit 2: irq enable on buffer empty
+ bit 3: led on (direct mode)
+ on HP49 G, flash ROM write enable
+
+ - 0x1d: I/R output buffer
+ */
+
+ default: m_io[offset] = data;
+ }
+
+}
+
+
+uint8_t hp48_state::io_r(offs_t offset)
+{
+ uint8_t data = 0;
+
+ switch (offset)
+ {
+ /* CRC register */
+ case 0x04: data = m_crc & 0xf; break;
+ case 0x05: data = (m_crc >> 4) & 0xf; break;
+ case 0x06: data = (m_crc >> 8) & 0xf; break;
+ case 0x07: data = (m_crc >> 12) & 0xf; break;
+
+ /* battery test */
+ case 0x08:
+ data = 0;
+ if (m_io[0x9] & 8) /* test enable */
+ {
+ /* XXX not implemented:
+ bit 3: battery in port 2
+ bit 2: battery in port 1
+ */
+ switch (ioport("BATTERY")->read())
+ {
+ case 1: data = 2; break; /* low */
+ case 2: data = 3; break; /* low | critical */
+ }
+ }
+ break;
+
+ /* remaining lines in main bitmap */
+ case 0x28:
+ case 0x29:
+ {
+ int last_line = HP48_IO_8(0x28) & 0x3f; /* last line of main bitmap before menu */
+ int cur_line = m_screen->vpos();
+ if (last_line <= 1) last_line = 0x3f;
+ data = (cur_line >= 0 && cur_line <= last_line) ? last_line - cur_line : 0;
+ if (offset == 0x29)
+ {
+ data >>= 4;
+ data |= HP48_IO_4(0x29) & 0xc;
+ }
+ else
+ {
+ data &= 0xf;
+ }
+ break;
+ }
+
+ /* timers */
+ case 0x37: data = m_timer1; break;
+ case 0x38: data = m_timer2 & 0xf; break;
+ case 0x39: data = (m_timer2 >> 4) & 0xf; break;
+ case 0x3a: data = (m_timer2 >> 8) & 0xf; break;
+ case 0x3b: data = (m_timer2 >> 12) & 0xf; break;
+ case 0x3c: data = (m_timer2 >> 16) & 0xf; break;
+ case 0x3d: data = (m_timer2 >> 20) & 0xf; break;
+ case 0x3e: data = (m_timer2 >> 24) & 0xf; break;
+ case 0x3f: data = (m_timer2 >> 28) & 0xf; break;
+
+ /* serial */
+ case 0x15:
+ {
+ m_io[0x11] &= ~1; /* clear byte received */
+ data = m_io[offset];
+ break;
+ }
+
+ /* cards */
+ case 0x0e: /* detection */
+ data = m_io[0x0e];
+ LOG("%s: card control read %02x\n", machine().describe_context(), data);
+ break;
+ case 0x0f: /* card info */
+ data = 0;
+ if ( HP48_G_SERIES )
+ {
+ if (m_port[1].found() && m_port[1]->port_size() > 0)
+ {
+ data |= 1;
+ if (m_port[1]->port_write())
+ data |= 4;
+ }
+ if (m_port[0].found() && m_port[0]->port_size() > 0)
+ {
+ data |= 2;
+ if (m_port[0]->port_write())
+ data |= 8;
+ }
+ }
+ else
+ {
+ if (m_port[0].found() && m_port[0]->port_size() > 0)
+ {
+ data |= 1;
+ if (m_port[0]->port_write())
+ data |= 4;
+ }
+ if (m_port[1].found() && m_port[1]->port_size() > 0)
+ {
+ data |= 2;
+ if (m_port[1]->port_write())
+ data |= 8;
+ }
+ }
+ LOG("%s: card info read %02x\n", machine().describe_context(), data);
+ break;
+
+
+ default: data = m_io[offset];
+ }
+
+ LOG("%s %f hp48_state::io_r: off=%02x data=%x\n", machine().describe_context(), machine().time().as_double(), offset, data);
+ return data;
+}
+
+
+/* ---------- bank switcher --------- */
+
+uint8_t hp48_state::bank_r(offs_t offset)
+{
+ /* HP48 GX
+ bit 0: ignored
+ bits 2-5: bank number
+ bit 6: enable
+ */
+
+ /* HP49 G
+ bit 0: ignored
+ bits 1-2: select bank 0x00000-0x3ffff
+ bits 3-6: select bank 0x40000-0x7ffff
+ */
+ offset &= 0x7e;
+ if ( m_bank_switch != offset )
+ {
+ LOG("%s %f hp48_state::bank_r: off=%03x\n", machine().describe_context(), machine().time().as_double(), offset);
+ m_bank_switch = offset;
+ apply_modules();
+ }
+ return 0;
+}
+
+
+void hp48_state::hp49_bank_w(offs_t offset, uint8_t data)
+{
+ offset &= 0x7e;
+ if ( m_bank_switch != offset )
+ {
+ LOG("%s %f hp49_bank_w: off=%03x\n", machine().describe_context(), machine().time().as_double(), offset);
+ m_bank_switch = offset;
+ apply_modules();
+ }
+}
+
+
+
+/* ---------------- timers --------------- */
+
+TIMER_CALLBACK_MEMBER(hp48_state::timer1_cb)
+{
+ if (!(m_io[0x2f] & 1)) return; /* timer enable */
+
+ m_timer1 = (m_timer1 - 1) & 0xf;
+
+ /* wake-up on carry */
+ if ((m_io[0x2e] & 4) && (m_timer1 == 0xf))
+ {
+ LOG("wake-up on timer1\n");
+ m_io[0x2e] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ pulse_irq(SATURN_WAKEUP_LINE);
+ }
+ /* interrupt on carry */
+ if ((m_io[0x2e] & 2) && (m_timer1 == 0xf))
+ {
+ LOG("generate timer1 interrupt\n");
+ m_io[0x2e] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ pulse_irq(SATURN_NMI_LINE);
+ }
+}
+
+TIMER_CALLBACK_MEMBER(hp48_state::timer2_cb)
+{
+ if (!(m_io[0x2f] & 1)) return; /* timer enable */
+
+ m_timer2 = (m_timer2 - 1) & 0xffffffff;
+
+ /* wake-up on carry */
+ if ((m_io[0x2f] & 4) && (m_timer2 == 0xffffffff))
+ {
+ LOG("wake-up on timer2\n");
+ m_io[0x2f] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ pulse_irq(SATURN_WAKEUP_LINE);
+ }
+ /* interrupt on carry */
+ if ((m_io[0x2f] & 2) && (m_timer2 == 0xffffffff))
+ {
+ LOG("generate timer2 interrupt\n");
+ m_io[0x2f] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ pulse_irq(SATURN_NMI_LINE);
+ }
+}
+
+
+
+
+/* --------- memory controller ----------- */
+
+/*
+ Clark (S series) and York (G series) CPUs have 6 daisy-chained modules
+
+
+ <-- highest --------- priority ------------- lowest -->
+
+ CPU ---------------------------------------------------
+ | | | | | |
+ HDW NCE2 CE1 CE2 NCE3 NCE1
+
+
+ However, controller usage is different in both series:
+
+
+ controller 48 S series 48 G series 49 G series
+ (Clark CPU) (York CPU) (York CPU)
+
+ HDW 32B I/O RAM 32B I/O RAM 32B I/O RAM
+ NCE2 32KB RAM 32/128KB RAM 256KB RAM
+ CE1 port1 bank switcher bank switcher
+ CE2 port2 port1 128KB RAM
+ NCE3 unused port2 128KB RAM
+ NCE1 256KB ROM 512KB ROM 2MB flash ROM
+
+
+ - NCE1 (ROM) cannot be configured, it is always visible at addresses
+ 00000-7ffff not covered by higher priority modules.
+
+ - only the address of HDW (I/O) can be configured, its size is constant
+ (64 nibbles)
+
+ - other modules can have their address & size set
+
+ */
+
+
+/* remap all modules according to hp48_modules */
+void hp48_state::apply_modules()
+{
+ int nce3_enable = 1;
+ address_space& space = m_maincpu->space(AS_PROGRAM);
+
+ m_io_addr = 0x100000;
+
+ /* NCE1 (ROM) is a bit special, so treat it separately */
+ space.unmap_readwrite(0, 0xfffff);
+ if (HP49_G_MODEL)
+ {
+ int bank_lo = (m_bank_switch >> 5) & 3;
+ int bank_hi = (m_bank_switch >> 1) & 15;
+ LOG("hp48_state::apply_modules: low ROM bank is %i\n", bank_lo);
+ LOG("hp48_state::apply_modules: high ROM bank is %i\n", bank_hi);
+ if (m_rom)
+ {
+ space.install_rom(0x00000, 0x3ffff, 0x80000, m_rom + bank_lo * 0x40000);
+ space.install_rom(0x40000, 0x7ffff, 0x80000, m_rom + bank_hi * 0x40000);
+ }
+ }
+ else if (HP48_G_SERIES)
+ {
+ /* port 2 bank switch */
+ if (m_port[1].found() && m_port[1]->port_size() > 0)
+ {
+ int off = (m_bank_switch << 16) % m_port[1]->port_size();
+ LOG("hp48_state::apply_modules: port 2 offset is %i\n", off);
+ m_modules[HP48_NCE3].data = m_port[1]->port_data() + off;
+ }
+
+ /* ROM A19 (hi 256 KB) / NCE3 (port 2) control switch */
+ if (m_io[0x29] & 8)
+ {
+ /* A19 */
+ LOG("hp48_state::apply_modules: A19 enabled, NCE3 disabled\n");
+ nce3_enable = 0;
+ if (m_rom)
+ space.install_rom(0, 0xfffff, m_rom);
+ }
+ else
+ {
+ /* NCE3 */
+ nce3_enable = m_bank_switch >> 6;
+ LOG("hp48_apply_modules: A19 disabled, NCE3 %s\n", nce3_enable ? "enabled" : "disabled");
+ if (m_rom)
+ space.install_rom(0, 0x7ffff, 0x80000, m_rom);
+ }
+ }
+ else
+ {
+ if (m_rom)
+ space.install_rom(0, 0x7ffff, 0x80000, m_rom);
+ }
+
+
+ /* from lowest to highest priority */
+ for (int i = 4; i >= 0; i--)
+ {
+ uint32_t select_mask = m_modules[i].mask;
+ uint32_t nselect_mask = ~select_mask & 0xfffff;
+ uint32_t base = m_modules[i].base;
+ uint32_t off_mask = m_modules[i].off_mask;
+ uint32_t mirror = nselect_mask & ~off_mask;
+ uint32_t end = base + (off_mask & nselect_mask);
+
+ if (m_modules[i].state != HP48_MODULE_CONFIGURED) continue;
+ if ((i == 4) && !nce3_enable) continue;
+
+ /* our code assumes that the 20-bit select_mask is all 1s followed by all 0s */
+ if (nselect_mask & (nselect_mask + 1))
+ {
+ LOGMASKED(LOG_INVALID, "hp48_apply_modules: invalid mask %05x for module %s\n", select_mask, hp48_module_names[i]);
+ continue;
+ }
+
+ if (m_modules[i].data)
+ {
+ space.install_rom(base, end, mirror, m_modules[i].data);
+ }
+ else
+ {
+ if (!m_modules[i].read.isnull())
+ {
+ space.install_read_handler(base, end, 0, mirror, 0, m_modules[i].read);
+ }
+ }
+
+ if (m_modules[i].isnop)
+ {
+ space.nop_write(base, end | mirror);
+ }
+ else
+ {
+ if (m_modules[i].data)
+ {
+ space.install_writeonly(base, end, mirror, m_modules[i].data);
+ }
+ else
+ {
+ if (!m_modules[i].write.isnull())
+ {
+ space.install_write_handler(base, end, 0, mirror, 0, m_modules[i].write);
+ }
+ }
+ }
+
+ LOG("hp48_apply_modules: module %s configured at %05x-%05x, mirror %05x\n", hp48_module_names[i], base, end, mirror);
+
+ if (i == 0)
+ {
+ m_io_addr = base;
+ }
+ }
+}
+
+
+/* reset the configuration */
+void hp48_state::reset_modules()
+{
+ /* fixed size for HDW */
+ m_modules[HP48_HDW].state = HP48_MODULE_MASK_KNOWN;
+ m_modules[HP48_HDW].mask = 0xfffc0;
+ /* unconfigure NCE2, CE1, CE2, NCE3 */
+ for (int i = 1; i < 5; i++)
+ {
+ m_modules[i].state = HP48_MODULE_UNCONFIGURED;
+ }
+
+ /* fixed configuration for NCE1 */
+ m_modules[HP48_NCE1].state = HP48_MODULE_CONFIGURED;
+ m_modules[HP48_NCE1].base = 0;
+ m_modules[HP48_NCE1].mask = 0;
+
+ apply_modules();
+}
+
+
+/* RESET opcode */
+void hp48_state::mem_reset(int state)
+{
+ LOG("%s %f hp48_state::mem_reset\n", machine().describe_context(), machine().time().as_double());
+ reset_modules();
+}
+
+
+/* CONFIG opcode */
+void hp48_state::mem_config(uint32_t data)
+{
+ LOG("%s %f hp48_state::mem_config: %05x\n", machine().describe_context(), machine().time().as_double(), data);
+
+ /* find the highest priority unconfigured module (except non-configurable NCE1)... */
+ for (int i = 0; i < 5; i++)
+ {
+ /* ... first call sets the address mask */
+ if (m_modules[i].state == HP48_MODULE_UNCONFIGURED)
+ {
+ m_modules[i].mask = data & 0xff000;
+ m_modules[i].state = HP48_MODULE_MASK_KNOWN;
+ break;
+ }
+
+ /* ... second call sets the base address */
+ if (m_modules[i].state == HP48_MODULE_MASK_KNOWN)
+ {
+ m_modules[i].base = data & m_modules[i].mask;
+ m_modules[i].state = HP48_MODULE_CONFIGURED;
+ LOG("hp48_mem_config: module %s configured base=%05x, mask=%05x\n", hp48_module_names[i], m_modules[i].base, m_modules[i].mask);
+ apply_modules();
+ break;
+ }
+ }
+}
+
+
+/* UNCFG opcode */
+void hp48_state::mem_unconfig(uint32_t data)
+{
+ LOG("%s %f hp48_state::mem_unconfig: %05x\n", machine().describe_context(), machine().time().as_double(), data);
+
+ /* find the highest priority fully configured module at address v (except NCE1)... */
+ for (int i = 0; i < 5; i++)
+ {
+ /* ... and unconfigure it */
+ if (m_modules[i].state == HP48_MODULE_CONFIGURED && (m_modules[i].base == (data & m_modules[i].mask)))
+ {
+ m_modules[i].state = i > 0 ? HP48_MODULE_UNCONFIGURED : HP48_MODULE_MASK_KNOWN;
+ LOG("hp48_mem_unconfig: module %s\n", hp48_module_names[i]);
+ apply_modules();
+ break;
+ }
+ }
+}
+
+
+/* C=ID opcode */
+uint32_t hp48_state::mem_id()
+{
+ int data = 0; /* 0 = everything is configured */
+
+ /* find the highest priority unconfigured module (except NCE1)... */
+ for (int i = 0; i < 5; i++)
+ {
+ /* ... mask need to be configured */
+ if (m_modules[i].state == HP48_MODULE_UNCONFIGURED)
+ {
+ data = hp48_module_mask_id[i] | (m_modules[i].mask & ~0xff);
+ break;
+ }
+
+ /* ... address need to be configured */
+ if (m_modules[i].state == HP48_MODULE_MASK_KNOWN)
+ {
+ data = hp48_module_addr_id[i] | (m_modules[i].base & ~0x3f);
+ break;
+ }
+ }
+
+ LOG("%s %f mem_id = %02x\n", machine().describe_context(), machine().time().as_double(), data);
+
+ return data; /* everything is configured */
+}
+
+
+
+/* --------- CRC ---------- */
+
+/* each memory read by the CPU updates the internal CRC state */
+void hp48_state::mem_crc(offs_t offset, uint32_t data)
+{
+ /* no CRC for I/O RAM */
+ if (offset >= m_io_addr && offset < m_io_addr + 0x40) return;
+
+ m_crc = (m_crc >> 4) ^ (((m_crc ^ data) & 0xf) * 0x1081);
+}
+
+
+
+/* ------ utilities ------- */
+
+
+/* decodes size bytes into 2*size nibbles (least significant first) */
+void hp48_state::decode_nibble(uint8_t* dst, uint8_t* src, int size)
+{
+ for (int i = size - 1; i >= 0; i--)
+ {
+ dst[2 * i + 1] = src[i] >> 4;
+ dst[2 * i] = src[i] & 0xf;
+ }
+}
+
+/* inverse of decode_nibble */
+void hp48_state::encode_nibble(uint8_t* dst, uint8_t* src, int size)
+{
+ for (int i = 0; i < size; i++)
+ {
+ dst[i] = (src[2 * i] & 0xf) | (src[2 * i + 1] << 4);
+ }
+}
+
+/***************************************************************************
+ MACHINES
+***************************************************************************/
+
+void hp48_state::init_hp48()
+{
+ LOG("hp48: driver init called\n");
+ for (int i = 0; i < 6; i++)
+ {
+ m_modules[i].off_mask = 0x00fff; // 2 KB
+ m_modules[i].read = read8sm_delegate(*this);
+ m_modules[i].write = write8sm_delegate(*this);
+ m_modules[i].data = nullptr;
+ m_modules[i].isnop = 0;
+ m_modules[i].state = 0;
+ m_modules[i].base = 0;
+ m_modules[i].mask = 0;
+ }
+ m_rom = nullptr;
+}
+
+void hp48_state::machine_reset()
+{
+ LOG("hp48: machine reset called\n");
+ m_bank_switch = 0;
+ m_cur_screen = 0;
+ reset_modules();
+ update_annunciators();
+}
+
+void hp48_state::base_machine_start(hp48_models model)
+{
+ LOG("hp48_state::machine_start: model %i\n", model);
+
+ m_model = model;
+
+ /* internal RAM */
+ uint32_t ram_size =
+ HP49_G_MODEL ? (512 * 1024) :
+ HP48_GX_MODEL ? (128 * 1024) : (32 * 1024);
+
+ m_allocated_ram = std::make_unique<uint8_t[]>(2 * ram_size);
+ subdevice<nvram_device>("nvram")->set_base(m_allocated_ram.get(), 2 * ram_size);
+
+
+ /* ROM load */
+ uint32_t rom_size =
+ HP49_G_MODEL ? (2048 * 1024) :
+ HP48_S_SERIES ? (256 * 1024) : (512 * 1024);
+ m_allocated_rom = std::make_unique<uint8_t[]>(2 * rom_size);
+ decode_nibble(m_allocated_rom.get(), memregion("maincpu")->base(), rom_size);
+ m_rom = m_allocated_rom.get();
+
+ /* init state */
+ std::fill_n(&m_allocated_ram[0], 2 * ram_size, 0);
+ memset(m_io, 0, sizeof(m_io));
+ m_out = 0;
+ m_kdn = 0;
+ m_crc = 0;
+ m_timer1 = 0;
+ m_timer2 = 0;
+ m_bank_switch = 0;
+
+ /* I/O RAM */
+ m_modules[HP48_HDW].off_mask = 0x0003f; /* 32 B */
+ m_modules[HP48_HDW].read = read8sm_delegate(*this, FUNC(hp48_state::io_r));
+ m_modules[HP48_HDW].write = write8sm_delegate(*this, FUNC(hp48_state::io_w));
+
+ /* internal RAM */
+ if (HP49_G_MODEL)
+ {
+ m_modules[HP48_NCE2].off_mask = 2 * 256 * 1024 - 1;
+ m_modules[HP48_NCE2].data = &m_allocated_ram[0];
+ m_modules[HP48_CE2].off_mask = 2 * 128 * 1024 - 1;
+ m_modules[HP48_CE2].data = &m_allocated_ram[2 * 256 * 1024];
+ m_modules[HP48_NCE3].off_mask = 2 * 128 * 1024 - 1;
+ m_modules[HP48_NCE3].data = &m_allocated_ram[2 * (128+256) * 1024];
+ }
+ else
+ {
+ m_modules[HP48_NCE2].off_mask = 2 * ram_size - 1;
+ m_modules[HP48_NCE2].data = &m_allocated_ram[0];
+ }
+
+ /* bank switcher */
+ if (HP48_G_SERIES)
+ {
+ m_modules[HP48_CE1].off_mask = 0x00fff; /* 2 KB */
+ m_modules[HP48_CE1].read = read8sm_delegate(*this, FUNC(hp48_state::bank_r));
+ m_modules[HP48_CE1].write = HP49_G_MODEL ? write8sm_delegate(*this, FUNC(hp48_state::hp49_bank_w)) : write8sm_delegate(*this);
+ }
+
+ /* timers */
+ m_1st_timer = timer_alloc(FUNC(hp48_state::timer1_cb), this);
+ m_1st_timer->adjust(attotime::from_hz(16), 0, attotime::from_hz(16));
+
+ m_2nd_timer = timer_alloc(FUNC(hp48_state::timer2_cb), this);
+ m_2nd_timer->adjust(attotime::from_hz(8192), 0, attotime::from_hz(8192));
+
+ /* 1ms keyboard polling */
+ m_kbd_timer = timer_alloc(FUNC(hp48_state::kbd_cb), this);
+ m_kbd_timer->adjust(attotime::from_msec(1), 0, attotime::from_msec(1));
+
+ /* serial receive done */
+ m_recv_done_timer = timer_alloc(FUNC(hp48_state::rs232_byte_recv_cb), this);
+ m_recv_done_timer->adjust(attotime::never);
+
+ /* serial send done */
+ m_send_done_timer = timer_alloc(FUNC(hp48_state::rs232_byte_sent_cb), this);
+ m_send_done_timer->adjust(attotime::never);
+
+ m_lshift0.resolve();
+ m_rshift0.resolve();
+ m_alpha0.resolve();
+ m_alert0.resolve();
+ m_busy0.resolve();
+ m_transmit0.resolve();
+
+ /* save state */
+ save_item(NAME(m_out));
+ save_item(NAME(m_kdn));
+ save_item(NAME(m_io_addr));
+ save_item(NAME(m_crc));
+ save_item(NAME(m_timer1));
+ save_item(NAME(m_timer2));
+ save_item(NAME(m_bank_switch));
+ for (int i = 0; i < 6; i++)
+ {
+ save_item(m_modules[i].state, "globals/m_modules[i].state", i);
+ save_item(m_modules[i].base, "globals/m_modules[i].base", i);
+ save_item(m_modules[i].mask, "globals/m_modules[i].mask", i);
+ }
+ save_item(NAME(m_io) );
+ machine().save().register_postload(save_prepost_delegate(FUNC(hp48_state::update_annunciators), this));
+ machine().save().register_postload(save_prepost_delegate(FUNC(hp48_state::apply_modules), this));
+
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48s)
+{
+ base_machine_start(HP48_S);
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48sx)
+{
+ base_machine_start(HP48_SX);
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48g)
+{
+ base_machine_start(HP48_G);
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48gx)
+{
+ base_machine_start(HP48_GX);
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48gp)
+{
+ base_machine_start(HP48_GP);
+}
+
+MACHINE_START_MEMBER(hp48_state,hp49g)
+{
+ base_machine_start(HP49_G);
+}