summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Patrick Mackinlay <pmackinlay@hotmail.com>2025-08-29 14:28:45 +0700
committer Patrick Mackinlay <pmackinlay@hotmail.com>2025-08-29 14:28:45 +0700
commitd35c3ae53c28198a0c315f79cdf46a843464afe5 (patch)
treefb3e3590a499bc705a55bcc16754d58e1cccf17c
parentf2b7da343cbed33d62d8180b20fb8f0b5369cb96 (diff)
ibm/rtpc.cpp: ibm advanced monochrome graphics display adapter (aka apa8)
-rw-r--r--scripts/src/bus.lua2
-rw-r--r--src/devices/bus/isa/amgda.cpp464
-rw-r--r--src/devices/bus/isa/amgda.h13
-rw-r--r--src/devices/bus/isa/isa_cards.cpp2
-rw-r--r--src/mame/ibm/rtpc.cpp9
5 files changed, 488 insertions, 2 deletions
diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua
index 8b2297835e1..d96d031cc1f 100644
--- a/scripts/src/bus.lua
+++ b/scripts/src/bus.lua
@@ -1796,6 +1796,8 @@ if (BUSES["ISA"]~=null) then
MAME_DIR .. "src/devices/bus/isa/aha1542c.h",
MAME_DIR .. "src/devices/bus/isa/aha174x.cpp",
MAME_DIR .. "src/devices/bus/isa/aha174x.h",
+ MAME_DIR .. "src/devices/bus/isa/amgda.cpp",
+ MAME_DIR .. "src/devices/bus/isa/amgda.h",
MAME_DIR .. "src/devices/bus/isa/asc88.cpp",
MAME_DIR .. "src/devices/bus/isa/asc88.h",
MAME_DIR .. "src/devices/bus/isa/bblue2.cpp",
diff --git a/src/devices/bus/isa/amgda.cpp b/src/devices/bus/isa/amgda.cpp
new file mode 100644
index 00000000000..bac9e5b6b4c
--- /dev/null
+++ b/src/devices/bus/isa/amgda.cpp
@@ -0,0 +1,464 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+/*
+ * IBM Advanced Monochrome Graphics Display Adapter (also known as All-Points-Addressable-8, or APA8).
+ *
+ * This adapter is an entry level, bitmap-addressable, monochrome graphics
+ * adapter designed for use with the IBM RT PC. It is intended to be paired
+ * with an IBM 6153 Advanced Monochrome Graphics Display, a 12", monochrome,
+ * white phosphor CRT. The combination produces an interlaced 720x512 pixel
+ * image with a 92Hz field rate and 46Hz frame rate.
+ *
+ * The adapter features 64KiB of video RAM, which can be accessed in several
+ * modes applying a variety of masks, shifts and logic functions to data from
+ * the host or read from video RAM.
+ *
+ * The 16 bit ISA data bus is connected byte-swapped to the adapter video RAM
+ * and registers. When accessed by the host RT PC, the byte-swapping performed
+ * by the IOCC makes these registers appear to be in host byte order (i.e.,
+ * big-endian).
+ *
+ * Sources:
+ * - IBM RT PC Hardware Technical Reference, Volume III (84X0873), Second Edition (September 1986), International Business Machines Corporation 1986.
+ *
+ * TODO:
+ * - fix failing interrupt diagnostic
+ * - vblank/hblank timing, interlace
+ * - interrupt clear
+ * - undefined functions
+ * - D3 content
+ */
+#include "emu.h"
+
+#include "amgda.h"
+#include "screen.h"
+
+#define LOG_REGR (1U << 1)
+#define LOG_REGW (1U << 2)
+#define LOG_MODE (1U << 3)
+#define LOG_VRAM (1U << 4)
+
+//#define VERBOSE (LOG_GENERAL|LOG_REGR|LOG_REGW|LOG_MODE|LOG_VRAM)
+#include "logmacro.h"
+
+class isa16_amgda_device
+ : public device_t
+ , public device_isa16_card_interface
+{
+public:
+ isa16_amgda_device(machine_config const &mconfig, char const *const tag, device_t *owner, u32 clock)
+ : device_t(mconfig, ISA16_AMGDA, tag, owner, clock)
+ , device_isa16_card_interface(mconfig, *this)
+ , m_screen(*this, "screen")
+{
+}
+
+protected:
+ virtual void device_add_mconfig(machine_config &config) override;
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
+
+ void pio_map(address_map &map);
+ void mem_map(address_map &map);
+
+ u16 vram_r(offs_t offset);
+ void vram_w(offs_t offset, u16 data, u16 mem_mask);
+
+ u16 ras_r();
+ u16 btr_r();
+ void dcr_w(u16 data);
+ void dmr_w(u16 data);
+
+ void irq_w(int state);
+ void irq_arm(u8 data) { irq_w(0); }
+
+ u16 address_step(u16 offset) const;
+ u8 alu(unsigned const byte) const;
+
+private:
+ required_device<screen_device> m_screen;
+
+ std::unique_ptr<u8[]> m_vram;
+
+ enum ras_mask : u8
+ {
+ RAS_EF = 0x01, // even field
+ RAS_HS = 0x02, // horizontal sync
+ RAS_VS = 0x04, // vertical sync
+ RAS_SV = 0x08, // serialized video
+ RAS_X = 0x10, // x stepping
+ RAS_IP = 0x20, // interrupt pending
+ RAS_DEC = 0x40, // decrement
+ RAS_LD = 0x80, // load address
+ };
+ u8 m_ras; // ras status register
+ enum dcr_mask : u16
+ {
+ DCR_ROT = 0x0007, // rotate count
+ DCR_FUNC = 0x0038, // logic function unit control
+ DCR_MODE = 0x0300, // memory mode
+ DCR_DEC = 0x0400, // decrement
+ DCR_X = 0x0800, // x stepping
+ DCR_BLK = 0x1000, // block transfer
+ DCR_IE = 0x2000, // interrupt enable
+ DCR_SE = 0x4000, // sync enable
+ DCR_VE = 0x8000, // video enable
+ };
+ enum dcr_mode : u16
+ {
+ MODE_SYSTEM = 0x0000,
+ MODE_OVERLAY = 0x0100,
+ MODE_ADAPTER = 0x0200,
+ MODE_AUTO = 0x0300,
+ };
+ u16 m_dcr; // data control register
+ u8 m_dm[2]; // data mask registers
+ u8 m_wm[2]; // write mask registers
+
+ // internal state
+ u8 m_sy[2]; // system data latches
+ u8 m_d[3]; // on-card data latches
+ u16 m_ba; // block address
+ bool m_aw; // automatic write
+};
+
+DEFINE_DEVICE_TYPE_PRIVATE(ISA16_AMGDA, device_isa16_card_interface, isa16_amgda_device, "isa16_amgda_device", "IBM Advanced Monochrome Graphics Display Adapter")
+
+void isa16_amgda_device::device_add_mconfig(machine_config &config)
+{
+ // monitor has 25.7MHz nominal bandwidth
+ // OSC: 44.22MHz
+ // H: 24.68KHz, retrace: 8.0us
+ // V: 92Hz field, 46Hz frame
+ // 720x512, 64Kx8 video ram
+ SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
+ m_screen->set_raw(44'220'000 / 2, 800, 0, 720, 600, 0, 512);
+ m_screen->set_screen_update(FUNC(isa16_amgda_device::screen_update));
+ m_screen->screen_vblank().set(
+ [this](int state)
+ {
+ if (state)
+ {
+ m_ras ^= RAS_EF;
+
+ if (m_dcr & DCR_IE)
+ irq_w(1);
+ }
+ });
+}
+
+void isa16_amgda_device::device_start()
+{
+ save_item(NAME(m_ras));
+ save_item(NAME(m_dcr));
+ save_item(NAME(m_dm));
+ save_item(NAME(m_wm));
+
+ save_item(NAME(m_sy));
+ save_item(NAME(m_d));
+ save_item(NAME(m_ba));
+ save_item(NAME(m_aw));
+
+ m_vram = std::make_unique<u8[]>(0x1'0000);
+ save_pointer(NAME(m_vram), 0x1'0000);
+
+ set_isa_device();
+
+ m_isa->install_device(0x0160, 0x016f, *this, &isa16_amgda_device::pio_map);
+ m_isa->install_memory(0xd0'0000, 0xd1'ffff, *this, &isa16_amgda_device::mem_map);
+
+ // TODO: ISA bus shared interrupt 11
+ m_isa->space(isa16_device::AS_ISA_IO).install_write_handler(0x06f3, 0x06f3, emu::rw_delegate(*this, NAME(&isa16_amgda_device::irq_arm)));
+}
+
+void isa16_amgda_device::device_reset()
+{
+ irq_w(0);
+
+ m_ras = RAS_LD;
+ m_dcr = 0;
+ m_dm[0] = 0;
+ m_dm[1] = 0;
+ m_wm[0] = 0;
+ m_wm[1] = 0;
+
+ m_sy[0] = 0;
+ m_sy[1] = 0;
+ m_d[0] = 0;
+ m_d[1] = 0;
+ m_d[2] = 0;
+ m_ba = 0;
+ m_aw = false;
+}
+
+u32 isa16_amgda_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
+{
+ if (m_dcr & DCR_VE)
+ {
+ u8 const *vram = m_vram.get();
+
+ for (s32 y = screen.visible_area().min_y; y <= screen.visible_area().max_y; y++)
+ {
+ for (s32 x = screen.visible_area().min_x; x <= screen.visible_area().max_x; x += 8)
+ {
+ u8 const data = *vram++;
+
+ bitmap.pix(y, x + 0) = BIT(data, 7) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 1) = BIT(data, 6) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 2) = BIT(data, 5) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 3) = BIT(data, 4) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 4) = BIT(data, 3) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 5) = BIT(data, 2) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 6) = BIT(data, 1) ? rgb_t::white() : rgb_t::black();
+ bitmap.pix(y, x + 7) = BIT(data, 0) ? rgb_t::white() : rgb_t::black();
+ }
+
+ vram += 38;
+ }
+ }
+ else
+ bitmap.fill(rgb_t::black());
+
+ return 0;
+}
+
+void isa16_amgda_device::pio_map(address_map &map)
+{
+ map(0x0, 0x1).rw(FUNC(isa16_amgda_device::ras_r), FUNC(isa16_amgda_device::dcr_w)).flags(1);
+ map(0x2, 0x3).rw(FUNC(isa16_amgda_device::btr_r), FUNC(isa16_amgda_device::dmr_w)).flags(1);
+}
+
+void isa16_amgda_device::mem_map(address_map &map)
+{
+ map(0x00000, 0x1ffff).rw(FUNC(isa16_amgda_device::vram_r), FUNC(isa16_amgda_device::vram_w));
+}
+
+u16 isa16_amgda_device::vram_r(offs_t offset)
+{
+ // treat debugger read as x++ system read without side effects
+ if (machine().side_effects_disabled())
+ return u16(m_vram[offset + 1]) << 8 | m_vram[offset + 0];
+
+ if (m_dcr & DCR_BLK)
+ {
+ if (m_ras & RAS_LD)
+ {
+ m_ba = offset;
+ m_ras &= ~RAS_LD;
+ }
+ else
+ offset = m_ba;
+ }
+
+ u16 address = offset;
+
+ for (unsigned byte = 0; byte < 2; byte++)
+ {
+ // latch data
+ if ((m_dcr & DCR_MODE) != MODE_ADAPTER)
+ m_d[byte] = m_vram[address];
+
+ address = address_step(address);
+ }
+
+ if (m_dcr & DCR_BLK)
+ m_ba = address;
+
+ m_aw = true;
+
+ LOGMASKED(LOG_VRAM, "%s: vram_r 0x%04x data 0x%02x%02x\n", machine().describe_context(), offset, m_d[0], m_d[1]);
+
+ // external data bus is byte-swapped
+ return (u16(m_d[1]) << 8) | m_d[0];
+}
+
+void isa16_amgda_device::vram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ // external data bus is byte-swapped
+ data = swapendian_int16(data);
+
+ if (m_dcr & DCR_BLK)
+ {
+ if (m_ras & RAS_LD)
+ {
+ m_ba = offset;
+ m_ras &= ~RAS_LD;
+ }
+ else
+ offset = m_ba;
+ }
+
+ if (!machine().side_effects_disabled())
+ LOGMASKED(LOG_VRAM, "%s: vram_w 0x%04x data 0x%04x\n", machine().describe_context(), offset, data);
+
+ u16 address = offset;
+
+ for (unsigned byte = 0; byte < 2; byte++)
+ {
+ switch (m_dcr & DCR_MODE)
+ {
+ case MODE_SYSTEM:
+ m_sy[byte] = BIT(data, 8 - byte * 8, 8);
+ break;
+ case MODE_OVERLAY:
+ m_wm[byte] = BIT(data, 8 - byte * 8, 8);
+ break;
+ }
+
+ if ((m_dcr & DCR_MODE) != MODE_AUTO || m_aw)
+ m_vram[address] = (m_vram[address] & m_wm[byte]) | (alu(byte) & ~m_wm[byte]);
+ else
+ m_d[byte] = m_vram[address];
+
+ address = address_step(address);
+ }
+
+ if (m_dcr & DCR_BLK)
+ m_ba = address;
+
+ m_aw = !m_aw;
+}
+
+u16 isa16_amgda_device::ras_r()
+{
+ u16 data = m_ras;
+
+ if (m_dcr & DCR_DEC)
+ data |= RAS_DEC;
+
+ if (m_dcr & DCR_X)
+ data |= RAS_X;
+
+ if (!machine().side_effects_disabled())
+ {
+ rectangle const &visible = m_screen->visible_area();
+ int const hpos = m_screen->hpos();
+ int const vpos = m_screen->vpos();
+
+ if (visible.contains(hpos, vpos))
+ {
+ unsigned const bit = (vpos - visible.min_y) * 1024 + (hpos - visible.min_x);
+
+ if ((m_dcr & DCR_VE) && BIT(m_vram[bit >> 3], 7 - (bit & 7)))
+ data |= RAS_SV;
+ }
+ else if (vpos < visible.min_y || vpos > visible.max_y)
+ data |= RAS_VS;
+ else if (hpos < visible.min_x || hpos > visible.max_x)
+ data |= RAS_HS;
+
+ LOGMASKED(LOG_REGR, "%s: ras_r 0x%02x\n", machine().describe_context(), data);
+ }
+
+ // external data bus is byte-swapped
+ return swapendian_int16(data);
+}
+
+u16 isa16_amgda_device::btr_r()
+{
+ if (!machine().side_effects_disabled())
+ {
+ LOGMASKED(LOG_REGR, "%s: btr_r\n", machine().describe_context());
+
+ m_ras |= RAS_LD;
+ }
+
+ return 0;
+}
+
+void isa16_amgda_device::dcr_w(u16 data)
+{
+ // external data bus is byte-swapped
+ data = swapendian_int16(data);
+
+ if (!machine().side_effects_disabled())
+ {
+ LOGMASKED(LOG_REGW, "%s: dcr_w 0x%04x\n", machine().describe_context(), data);
+
+ static char const *const func[] = { "B", "1", "A", "3", "~B", "A|B", "~A", "~A|B" };
+ static char const *const mode[] = { "system", "overlay", "adapter", "automatic" };
+
+ LOGMASKED(LOG_MODE, "ve=%u, se=%u, ie=%u, blk=%u, %s%s, mode=%s, function=%s, rotate=%u\n",
+ BIT(data, 15), BIT(data, 14), BIT(data, 13), BIT(data, 12),
+ (data & DCR_X) ? "x" : "y", (data & DCR_DEC) ? "--" : "++",
+ mode[BIT(data, 8, 2)], func[BIT(data, 3, 3)], (data & DCR_ROT));
+ }
+
+ if ((data ^ m_dcr) & DCR_BLK)
+ m_ras |= RAS_LD;
+
+ if (!(data & DCR_IE))
+ irq_w(0);
+
+ m_dcr = data;
+}
+
+void isa16_amgda_device::dmr_w(u16 data)
+{
+ // external data bus is byte-swapped
+ data = swapendian_int16(data);
+
+ if (!machine().side_effects_disabled())
+ LOGMASKED(LOG_REGW, "%s: dmr_w 0x%04x\n", machine().describe_context(), data);
+
+ m_dm[0] = BIT(data, 8, 8);
+ m_dm[1] = BIT(data, 0, 8);
+}
+
+void isa16_amgda_device::irq_w(int state)
+{
+ if (state)
+ {
+ if (!(m_ras & RAS_IP))
+ {
+ m_ras |= RAS_IP;
+ m_isa->irq11_w(state);
+ }
+ }
+ else
+ {
+ if (m_ras & RAS_IP)
+ {
+ m_ras &= ~RAS_IP;
+ m_isa->irq11_w(state);
+ }
+ }
+}
+
+u16 isa16_amgda_device::address_step(u16 offset) const
+{
+ switch (BIT(m_dcr, 10, 2))
+ {
+ case 0: return offset + 128; // y++
+ case 1: return offset - 128; // y--
+ case 2: return (offset & 0xff80) | ((offset + 1) & 0x7f); // x++
+ case 3: return (offset & 0xff80) | ((offset - 1) & 0x7f); // x--
+ }
+
+ // can't happen
+ abort();
+}
+
+u8 isa16_amgda_device::alu(unsigned const byte) const
+{
+ // apply data masks and shift
+ unsigned const shift = m_dcr & DCR_ROT;
+ u8 const a = m_dm[0] & m_sy[byte];
+ u8 const b = m_dm[1] & ((m_d[byte + 0] << shift) | (m_d[byte + 1] >> (8 - shift)));
+
+ switch (BIT(m_dcr, 3, 3))
+ {
+ case 0: return b;
+ case 2: return a;
+ case 4: return ~b;
+ case 5: return a | b;
+ case 6: return ~a;
+ case 7: return ~(a | b);
+ }
+
+ // FIXME: undocumented functions 1 and 3
+ return 0;
+}
diff --git a/src/devices/bus/isa/amgda.h b/src/devices/bus/isa/amgda.h
new file mode 100644
index 00000000000..a6c9d7369ab
--- /dev/null
+++ b/src/devices/bus/isa/amgda.h
@@ -0,0 +1,13 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+#ifndef MAME_BUS_ISA_AMGDA_H
+#define MAME_BUS_ISA_AMGDA_H
+
+#pragma once
+
+#include "isa.h"
+
+DECLARE_DEVICE_TYPE(ISA16_AMGDA, device_isa16_card_interface)
+
+#endif // MAME_BUS_ISA_AMGDA_H
diff --git a/src/devices/bus/isa/isa_cards.cpp b/src/devices/bus/isa/isa_cards.cpp
index 442c4d816b0..c634d85e6e4 100644
--- a/src/devices/bus/isa/isa_cards.cpp
+++ b/src/devices/bus/isa/isa_cards.cpp
@@ -11,6 +11,7 @@
// video
#include "aga.h"
+#include "amgda.h"
#include "cga.h"
#include "ega.h"
#include "eis_hgb107x.h"
@@ -177,6 +178,7 @@ void pc_isa16_cards(device_slot_interface &device)
device.option_add("aha1542c", AHA1542C);
device.option_add("aha1542cf", AHA1542CF);
device.option_add("aha1542cp", AHA1542CP);
+ device.option_add("amgda", ISA16_AMGDA);
device.option_add("gus", ISA16_GUS);
device.option_add("sblaster_16", ISA16_SOUND_BLASTER_16);
device.option_add("svga_s3", ISA16_SVGA_S3);
diff --git a/src/mame/ibm/rtpc.cpp b/src/mame/ibm/rtpc.cpp
index d652cf9e0b5..803f9ea85fc 100644
--- a/src/mame/ibm/rtpc.cpp
+++ b/src/mame/ibm/rtpc.cpp
@@ -98,6 +98,8 @@
// busses and connectors
#include "bus/isa/isa.h"
#include "bus/isa/isa_cards.h"
+#include "bus/isa/amgda.h"
+#include "bus/isa/ega.h"
#include "bus/isa/fdc.h"
#include "bus/isa/ide.h"
#include "bus/isa/mda.h"
@@ -806,13 +808,16 @@ void rtpc_state::common(machine_config &config)
void rtpc_isa8_cards(device_slot_interface &device)
{
device.option_add("mda", ISA8_MDA);
+ device.option_add("ega", ISA8_EGA);
+ device.option_add("fdc", ISA8_FDC_AT);
}
void rtpc_isa16_cards(device_slot_interface &device)
{
- // FIXME: need 16-bit combined hdc/fdc card
- device.option_add("fdc", ISA8_FDC_AT);
+ device.option_add("amgda", ISA16_AMGDA);
device.option_add("ide", ISA16_IDE);
+
+ rtpc_isa8_cards(device);
}
void rtpc_state::ibm6150(machine_config &config)