summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Eric Anderson <ejona86@gmail.com>2023-02-27 00:47:55 -0800
committer GitHub <noreply@github.com>2023-02-27 19:47:55 +1100
commit369ecb2f77eecfa71fe22988f8837ce16d7ab7f2 (patch)
tree24014eee52ab36cc631cbe6601b888482982a3be
parentdaaf8645e01f7ec1a3e5248954af054666573dff (diff)
vectorgraphic/vector4.cpp: Added Vector 4 driver. (#10710)
* bus/s100: Added Vector Dual-Mode Disk Controller (only floppy supported for now). * formats/vgi_dsk.cpp: Addec Micropolis VGI floppy disk image format.
-rw-r--r--scripts/src/bus.lua2
-rw-r--r--scripts/src/formats.lua12
-rw-r--r--src/devices/bus/s100/vectordualmode.cpp327
-rw-r--r--src/devices/bus/s100/vectordualmode.h55
-rw-r--r--src/emu/xtal.cpp1
-rw-r--r--src/lib/formats/all.cpp9
-rw-r--r--src/lib/formats/vgi_dsk.cpp146
-rw-r--r--src/lib/formats/vgi_dsk.h34
-rw-r--r--src/mame/mame.lst3
-rw-r--r--src/mame/mess.flt1
-rw-r--r--src/mame/vectorgraphic/sbcvideo.cpp183
-rw-r--r--src/mame/vectorgraphic/sbcvideo.h43
-rw-r--r--src/mame/vectorgraphic/vector4.cpp315
13 files changed, 1131 insertions, 0 deletions
diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua
index 154370c0759..315b14927ca 100644
--- a/scripts/src/bus.lua
+++ b/scripts/src/bus.lua
@@ -2270,6 +2270,8 @@ if (BUSES["S100"]~=null) then
MAME_DIR .. "src/devices/bus/s100/polyvti.h",
MAME_DIR .. "src/devices/bus/s100/seals8k.cpp",
MAME_DIR .. "src/devices/bus/s100/seals8k.h",
+ MAME_DIR .. "src/devices/bus/s100/vectordualmode.cpp",
+ MAME_DIR .. "src/devices/bus/s100/vectordualmode.h",
MAME_DIR .. "src/devices/bus/s100/wunderbus.cpp",
MAME_DIR .. "src/devices/bus/s100/wunderbus.h",
}
diff --git a/scripts/src/formats.lua b/scripts/src/formats.lua
index 0fc21bb1941..92055468830 100644
--- a/scripts/src/formats.lua
+++ b/scripts/src/formats.lua
@@ -2054,6 +2054,18 @@ end
--------------------------------------------------
--
+--@src/lib/formats/vgi_dsk.h,FORMATS["VGI_DSK"] = true
+--------------------------------------------------
+
+if opt_tool(FORMATS, "VGI_DSK") then
+ files {
+ MAME_DIR.. "src/lib/formats/vgi_dsk.cpp",
+ MAME_DIR.. "src/lib/formats/vgi_dsk.h",
+ }
+end
+
+--------------------------------------------------
+--
--@src/lib/formats/victor9k_dsk.h,FORMATS["VICTOR9K_DSK"] = true
--------------------------------------------------
diff --git a/src/devices/bus/s100/vectordualmode.cpp b/src/devices/bus/s100/vectordualmode.cpp
new file mode 100644
index 00000000000..00e80cadef5
--- /dev/null
+++ b/src/devices/bus/s100/vectordualmode.cpp
@@ -0,0 +1,327 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+/***************************************************************************
+
+Vector Graphic had two related disk controllers for the Vector 4. There was
+the "dual-mode" ST506-interface HDD/5.25" FDD controller and a stripped-down
+5.25" FDD-only controller. Both can handle four FDD. The dual-mode version
+supports a HDD as drive 0, replacing a FDD when used.
+
+The floppy and hard drive formatting is not IBM compatible. Instead they are
+based on the Micropolis MFM hard-sectored format which starts and ends the
+sector with 0x00 preamble and postable bytes and starts sector data with a
+0xFF sync byte. The FDD has 16 hard sectors, but the HDD uses a normal
+soft-sectored drive with a PLL on the controller to emulate 32 hard sectors.
+No abnormal MFM clock bits are used.
+
+https://www.bitsavers.org/pdf/vectorGraphic/hardware/7200-1200-02-1_Dual-Mode_Disk_Controller_Board_Engineering_Documentation_Feb81.pdf
+https://archive.org/details/7200-0001-vector-4-technical-information-sep-82
+
+TODO:
+- HDD support
+- ECC
+
+****************************************************************************/
+
+#include "emu.h"
+#include "vectordualmode.h"
+
+#include "formats/vgi_dsk.h"
+
+static const attotime half_bitcell_size = attotime::from_usec(2);
+
+/* Interleave 8 bits with zeros. abcdefgh -> 0a0b0c0d0e0f0g0h */
+static int deposit8(int data)
+{
+ int d = data;
+ d = ((d & 0xf0) << 4) | (d & 0x0f);
+ d = ((d << 2) | d) & 0x3333;
+ d = ((d << 1) | d) & 0x5555;
+ return d;
+}
+
+static uint16_t mfm_byte(uint8_t data, unsigned int prev_data)
+{
+ const unsigned int ext_data = data | (prev_data << 8);
+ const unsigned int clock = ~(ext_data | (ext_data >> 1));
+ return (deposit8(clock) << 1) | deposit8(ext_data);
+}
+
+static uint8_t unmfm_byte(uint16_t mfm)
+{
+ unsigned int d = mfm;
+ d &= 0x5555;
+ d = ((d >> 1) | d) & 0x3333;
+ d = ((d >> 2) | d) & 0x0f0f;
+ d = ((d >> 4) | d) & 0x00ff;
+ return d;
+}
+
+s100_vector_dualmode_device::s100_vector_dualmode_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, S100_VECTOR_DUALMODE, tag, owner, clock)
+ , device_s100_card_interface(mconfig, *this)
+ , m_floppy(*this, "floppy%u", 0U)
+ , m_ram{0}
+ , m_cmar(0)
+ , m_drive(0)
+ , m_sector(0)
+ , m_read(false)
+ , m_pll()
+ , m_byte_timer(nullptr)
+ , m_sector_timer(nullptr)
+ , m_pending_byte(0)
+ , m_pending_size(0)
+{
+}
+
+TIMER_CALLBACK_MEMBER(s100_vector_dualmode_device::motor_off)
+{
+ for (int i = 0; i < m_floppy.size(); i++) {
+ floppy_image_device* flop = m_floppy[m_drive]->get_device();
+ if (flop)
+ flop->mon_w(1);
+ }
+}
+
+bool s100_vector_dualmode_device::hdd_selected()
+{
+ // TODO: HDD support
+ return m_drive == 0 && false;
+}
+
+uint8_t s100_vector_dualmode_device::s100_sinp_r(offs_t offset)
+{
+ // 7200-1200-02-1 page 16 (1-10)
+ uint8_t data;
+ if (offset == 0xc0) { // status (0) port
+ bool write_protect; // FDD
+ bool ready; // HDD
+ bool track0;
+ bool write_fault = false; // HDD
+ bool seek_complete; // HDD
+ bool loss_of_sync; // HDD
+ if (hdd_selected()) {
+ write_protect = false;
+ ready = true;
+ track0 = false;
+ seek_complete = true;
+ loss_of_sync = true;
+ } else {
+ floppy_image_device* flop = m_floppy[m_drive]->get_device();
+ write_protect = flop && flop->wpt_r();
+ ready = false;
+ track0 = flop && !flop->trk00_r();
+ seek_complete = false;
+ loss_of_sync = false;
+ }
+
+ data = (write_protect ? 0x01 : 0)
+ | (ready ? 0x02 : 0)
+ | (track0 ? 0x04 : 0)
+ | (write_fault ? 0x08 : 0)
+ | (seek_complete ? 0x10 : 0)
+ | (loss_of_sync ? 0x20 : 0)
+ | 0xc0;
+ } else if (offset == 0xc1) { // status (1) port
+ bool floppy_disk_selected;
+ bool controller_busy = m_sector_timer->enabled();
+ bool motor_on; // FDD
+ bool type_of_hard_disk = true;
+ if (hdd_selected()) {
+ floppy_disk_selected = false;
+ motor_on = false;
+ } else {
+ floppy_disk_selected = true;
+ motor_on = m_motor_on_timer->enabled();
+ }
+ data = (floppy_disk_selected ? 0x01 : 0)
+ | (controller_busy ? 0x02 : 0)
+ | (motor_on ? 0x04 : 0)
+ | (type_of_hard_disk ? 0x08 : 0)
+ | 0xf0;
+ } else if (offset == 0xc2) { // data port
+ data = m_ram[m_cmar];
+ if (!machine().side_effects_disabled()) {
+ m_cmar++;
+ m_cmar &= 0x1ff;
+ }
+ } else if (offset == 0xc3) { // reset port
+ if (!machine().side_effects_disabled())
+ m_cmar = 0;
+ data = 0xff;
+ } else {
+ data = 0xff;
+ }
+ return data;
+}
+
+void s100_vector_dualmode_device::s100_sout_w(offs_t offset, uint8_t data)
+{
+ // TODO: check actual behavior when controller is busy
+ if (m_sector_timer->enabled()) {
+ return;
+ }
+ // 7200-1200-02-1 page 14 (1-8)
+ if (offset == 0xc0) { // control (0) port
+ m_drive = BIT(data, 0, 2);
+ const uint8_t head = BIT(data, 2, 3);
+ const bool step = BIT(data, 5);
+ const bool step_in = BIT(data, 6);
+ //uint8_t low_current = BIT(data, 7);
+
+ for (int i = 0; i < m_floppy.size(); i++) {
+ floppy_image_device* flop = m_floppy[m_drive]->get_device();
+ if (flop)
+ flop->mon_w(0);
+ }
+ // WR0| triggers U60, a 74LS123 with 100uF cap and 100k res
+ m_motor_on_timer->adjust(attotime::from_usec(2819600));
+
+ floppy_image_device* flop = m_floppy[m_drive]->get_device();
+ if (flop) {
+ flop->ss_w(head & 1);
+ // Software should not change other bits when pulsing step
+ flop->stp_w(!step);
+ flop->dir_w(!step_in);
+ }
+ } else if (offset == 0xc1) { // control (1) port
+ m_sector = BIT(data, 0, 5);
+ m_read = BIT(data, 5);
+ } else if (offset == 0xc2) { // data port
+ m_ram[m_cmar++] = data;
+ m_cmar &= 0x1ff;
+ } else if (offset == 0xc3) { // start port
+ floppy_image_device* flop = m_floppy[m_drive]->get_device();
+ if (!flop || flop->time_next_index().is_never())
+ return;
+ const attotime rot_time = attotime::from_msec(200);
+ attotime sector_time = flop->time_next_index() - machine().time() + (rot_time / 16) * m_sector;
+ if (sector_time > rot_time)
+ sector_time -= rot_time;
+ m_sector_timer->adjust(sector_time, SECTOR_START);
+ }
+}
+
+bool s100_vector_dualmode_device::get_next_bit(attotime &tm, const attotime &limit)
+{
+ int bit = m_pll.get_next_bit(tm, m_floppy[m_drive]->get_device(), limit);
+ if (bit < 0)
+ return false;
+ m_pending_byte <<= 1;
+ m_pending_byte |= bit;
+ m_pending_size++;
+ return true;
+}
+
+TIMER_CALLBACK_MEMBER(s100_vector_dualmode_device::sector_cb)
+{
+ switch (param) {
+ case SECTOR_START:
+ if (m_read) {
+ m_pll.set_clock(half_bitcell_size);
+ m_pll.read_reset(machine().time());
+ attotime tm;
+ attotime limit = machine().time() + half_bitcell_size*512;
+ while (get_next_bit(tm, limit)) {} // init PLL
+ limit += half_bitcell_size*16*30;
+ while (get_next_bit(tm, limit) && m_pending_byte != 0x5554) {}
+ if (m_pending_byte == 0x5554) {
+ m_pending_size = 1;
+ m_byte_timer->adjust(tm - machine().time());
+ }
+ } else {
+ m_pending_size = 0;
+ m_byte_timer->adjust(attotime::zero);
+ }
+ m_sector_timer->adjust(attotime::from_msec(200)/16, SECTOR_END);
+ break;
+
+ case SECTOR_END:
+ m_byte_timer->enable(false);
+ if (m_read)
+ m_ram[274] = 0; // Ignore ECC
+ break;
+ }
+}
+
+TIMER_CALLBACK_MEMBER(s100_vector_dualmode_device::byte_cb)
+{
+ if (m_read) {
+ if (m_pending_size == 16) {
+ m_pending_size = 0;
+ m_ram[m_cmar++] = unmfm_byte(m_pending_byte);
+ m_cmar &= 0x1ff;
+ }
+ attotime tm;
+ while (m_pending_size != 16 && get_next_bit(tm, attotime::never)) {}
+ m_byte_timer->adjust(tm - machine().time());
+ } else {
+ if (m_pending_size == 16) {
+ attotime start_time = machine().time() - half_bitcell_size*m_pending_size;
+ attotime tm = start_time + attotime::from_usec(1);
+ attotime buf[8];
+ int pos = 0;
+ while (m_pending_size) {
+ if (m_pending_byte & (1 << --m_pending_size))
+ buf[pos++] = tm;
+ tm += half_bitcell_size;
+ }
+ floppy_image_device *floppy = m_floppy[m_drive]->get_device();
+ if (floppy)
+ floppy->write_flux(start_time, machine().time(), pos, buf);
+ }
+ uint8_t last = m_cmar ? m_ram[m_cmar-1] : 0;
+ m_pending_byte = mfm_byte(m_ram[m_cmar++], last);
+ m_pending_size = 16;
+ m_cmar &= 0x1ff;
+ m_byte_timer->adjust(half_bitcell_size*16);
+ }
+}
+
+void s100_vector_dualmode_device::device_start()
+{
+ m_motor_on_timer = timer_alloc(FUNC(s100_vector_dualmode_device::motor_off), this);
+ m_byte_timer = timer_alloc(FUNC(s100_vector_dualmode_device::byte_cb), this);
+ m_sector_timer = timer_alloc(FUNC(s100_vector_dualmode_device::sector_cb), this);
+
+ save_item(NAME(m_ram));
+ save_item(NAME(m_cmar));
+ save_item(NAME(m_drive));
+ save_item(NAME(m_sector));
+ save_item(NAME(m_read));
+ save_item(NAME(m_pending_byte));
+ save_item(NAME(m_pending_size));
+}
+
+void s100_vector_dualmode_device::device_reset()
+{
+ // POC| resets
+ // U9
+ m_drive = 0;
+ // U18
+ m_sector = 0;
+ m_read = false;
+ // U60
+ m_motor_on_timer->enable(false);
+}
+
+static void vector4_floppies(device_slot_interface &device)
+{
+ device.option_add("525", FLOPPY_525_QD);
+}
+
+static void vector4_formats(format_registration &fr)
+{
+ fr.add_mfm_containers();
+ fr.add(FLOPPY_VGI_FORMAT);
+}
+
+void s100_vector_dualmode_device::device_add_mconfig(machine_config &config)
+{
+ FLOPPY_CONNECTOR(config, m_floppy[0], vector4_floppies, "525", vector4_formats).enable_sound(true);
+ FLOPPY_CONNECTOR(config, m_floppy[1], vector4_floppies, "525", vector4_formats).enable_sound(true);
+ FLOPPY_CONNECTOR(config, m_floppy[2], vector4_floppies, "525", vector4_formats).enable_sound(true);
+ FLOPPY_CONNECTOR(config, m_floppy[3], vector4_floppies, "525", vector4_formats).enable_sound(true);
+}
+
+DEFINE_DEVICE_TYPE(S100_VECTOR_DUALMODE, s100_vector_dualmode_device, "vectordualmode", "Vector Dual-Mode Disk Controller")
diff --git a/src/devices/bus/s100/vectordualmode.h b/src/devices/bus/s100/vectordualmode.h
new file mode 100644
index 00000000000..e3a924a94c3
--- /dev/null
+++ b/src/devices/bus/s100/vectordualmode.h
@@ -0,0 +1,55 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+#ifndef MAME_BUS_S100_VECTORDUALMODE_H
+#define MAME_BUS_S100_VECTORDUALMODE_H
+
+#pragma once
+
+#include "bus/s100/s100.h"
+#include "imagedev/floppy.h"
+#include "machine/fdc_pll.h"
+
+class s100_vector_dualmode_device :
+ public device_t,
+ public device_s100_card_interface
+{
+public:
+ s100_vector_dualmode_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+protected:
+ void device_start() override;
+ void device_reset() override;
+ void device_add_mconfig(machine_config &config) override;
+
+ uint8_t s100_sinp_r(offs_t offset) override;
+ void s100_sout_w(offs_t offset, uint8_t data) override;
+
+private:
+ TIMER_CALLBACK_MEMBER(motor_off);
+ TIMER_CALLBACK_MEMBER(sector_cb);
+ TIMER_CALLBACK_MEMBER(byte_cb);
+ bool hdd_selected();
+ bool get_next_bit(attotime &tm, const attotime &limit);
+
+ required_device_array<floppy_connector, 4> m_floppy;
+ uint8_t m_ram[512];
+ uint16_t m_cmar;
+ uint8_t m_drive;
+ uint8_t m_sector;
+ bool m_read;
+ emu_timer *m_motor_on_timer;
+
+ enum sector_timer_state {
+ SECTOR_START,
+ SECTOR_END,
+ };
+ fdc_pll_t m_pll;
+ emu_timer *m_byte_timer;
+ emu_timer *m_sector_timer;
+ uint16_t m_pending_byte;
+ uint8_t m_pending_size;
+};
+
+DECLARE_DEVICE_TYPE(S100_VECTOR_DUALMODE, s100_vector_dualmode_device)
+
+#endif // MAME_BUS_S100_VECTORDUALMODE_H
diff --git a/src/emu/xtal.cpp b/src/emu/xtal.cpp
index 9505cad64c5..8367a55cfb4 100644
--- a/src/emu/xtal.cpp
+++ b/src/emu/xtal.cpp
@@ -402,6 +402,7 @@ const double XTAL::known_xtals[] = {
32'256'000, /* 32.256_MHz_XTAL Hitachi MB-6890 */
32'317'400, /* 32.3174_MHz_XTAL DEC VT330, VT340 */
32'530'470, /* 32.53047_MHz_XTAL Seta 2 */
+ 32'640'000, /* 32.64_MHz_XTAL Vector 4 */
32'768'000, /* 32.768_MHz_XTAL Roland D-50 audio clock */
33'000'000, /* 33_MHz_XTAL Sega Model 3 video board */
33'264'000, /* 33.264_MHz_XTAL Hazeltine 1500 terminal */
diff --git a/src/lib/formats/all.cpp b/src/lib/formats/all.cpp
index b8d0089f3f9..017c81b3ce9 100644
--- a/src/lib/formats/all.cpp
+++ b/src/lib/formats/all.cpp
@@ -680,6 +680,10 @@
#include "vg5k_cas.h"
#endif
+#ifdef HAS_FORMATS_VGI_DSK
+#include "vgi_dsk.h"
+#endif
+
#ifdef HAS_FORMATS_VICTOR9K_DSK
#include "victor9k_dsk.h"
#endif
@@ -1396,6 +1400,11 @@ void mame_formats_full_list(mame_formats_enumerator &en)
en.add(mbee_cassette_formats); // mbee_cas.h
#endif
+ en.category("Micropolis");
+#ifdef HAS_FORMATS_VGI_DSK
+ en.add(FLOPPY_VGI_FORMAT); // vgi_dsk.h
+#endif
+
en.category("Orao");
#ifdef HAS_FORMATS_ORAO_CAS
en.add(orao_cassette_formats); // orao_cas.h
diff --git a/src/lib/formats/vgi_dsk.cpp b/src/lib/formats/vgi_dsk.cpp
new file mode 100644
index 00000000000..c15bba33d86
--- /dev/null
+++ b/src/lib/formats/vgi_dsk.cpp
@@ -0,0 +1,146 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+/*********************************************************************
+
+Micropolis VGI disk image format
+
+The format is essentially an HCS-ordered sector image, but with raw
+275 byte sectors. The sector format is: SYNC (0xFF), track, sector,
+10 byte "user data", 256 byte data, checksum, 4 byte ECC, ECC present
+flag.
+
+http://www.bitsavers.org/pdf/micropolis/metafloppy/1084-01_1040_1050_Users_Manual_Apr79.pdf
+
+*********************************************************************/
+
+#include "vgi_dsk.h"
+#include "ioprocs.h"
+
+#include <cstring>
+
+static const int track_size = 100'000;
+static const int half_bitcell_size = 2000;
+
+micropolis_vgi_format::micropolis_vgi_format() : floppy_image_format_t()
+{
+}
+
+struct format {
+ int head_count;
+ int track_count;
+ uint32_t variant;
+};
+
+static const format formats[] = {
+ {1, 35, floppy_image::SSDD}, // MOD-I
+ {2, 35, floppy_image::DSDD},
+ {1, 77, floppy_image::SSQD}, // MOD-II
+ {2, 77, floppy_image::DSQD},
+ {}
+};
+
+static format find_format(int file_size)
+{
+ for (int i = 0; formats[i].head_count; i++)
+ if (file_size == formats[i].head_count * formats[i].track_count * 16 * 275)
+ return formats[i];
+ return {};
+}
+
+int micropolis_vgi_format::identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const
+{
+ uint64_t file_size;
+ if (io.length(file_size))
+ return 0;
+ format fmt = find_format(file_size);
+ if (!fmt.head_count)
+ return 0;
+ if (!has_variant(variants, fmt.variant))
+ return 0;
+ return FIFID_SIZE;
+}
+
+bool micropolis_vgi_format::load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants, floppy_image *image) const
+{
+ uint64_t file_size;
+ if (io.length(file_size))
+ return false;
+ const format fmt = find_format(file_size);
+ if (!fmt.head_count)
+ return false;
+ image->set_variant(fmt.variant);
+
+ std::vector<uint32_t> buf;
+ uint8_t sector_bytes[275];
+ for (int head = 0; head < fmt.head_count; head++) {
+ for (int track = 0; track < fmt.track_count; track++) {
+ for (int sector = 0; sector < 16; sector++) {
+ for (int i = 0; i < 40; i++)
+ mfm_w(buf, 8, 0, half_bitcell_size);
+ std::size_t actual;
+ if (io.read(sector_bytes, std::size(sector_bytes), actual))
+ return false;
+ for (int i = 0; i < std::size(sector_bytes); i++)
+ mfm_w(buf, 8, sector_bytes[i], half_bitcell_size);
+ while (buf.size() < track_size/16 * (sector+1))
+ mfm_w(buf, 8, 0, half_bitcell_size);
+ }
+ generate_track_from_levels(track, head, buf, 0, image);
+ buf.clear();
+ }
+ }
+ return true;
+}
+
+bool micropolis_vgi_format::save(util::random_read_write &io, const std::vector<uint32_t> &variants, floppy_image *image) const
+{
+ uint32_t variant = image->get_variant();
+ format fmt = {};
+ for (int i = 0; formats[i].head_count; i++)
+ if (variant == formats[i].variant)
+ fmt = formats[i];
+ if (!fmt.head_count) {
+ int heads, tracks;
+ image->get_actual_geometry(tracks, heads);
+ if (heads == 0 && tracks == 0)
+ return false; // Brand-new image; we don't know the size yet
+ for (int i = 0; formats[i].head_count; i++)
+ if (heads <= formats[i].head_count && tracks <= formats[i].track_count)
+ fmt = formats[i];
+ }
+ if (!fmt.head_count)
+ return false;
+
+ if (io.seek(0, SEEK_SET))
+ return false;
+ uint8_t sector_bytes[275];
+ for (int head = 0; head < fmt.head_count; head++) {
+ for (int track = 0; track < fmt.track_count; track++) {
+ std::vector<bool> bitstream = generate_bitstream_from_track(track, head, half_bitcell_size, image);
+ for (int sector = 0; sector < 16; sector++) {
+ int sector_start = track_size/16 * sector;
+ uint32_t pos = sector_start + 512 - 16;
+ uint16_t shift_reg = 0;
+ while (pos < sector_start + 60*16 && pos < bitstream.size()) {
+ shift_reg = (shift_reg << 1) | bitstream[pos++];
+ if (shift_reg == 0x5554)
+ break;
+ }
+ if (shift_reg == 0x5554) {
+ pos--;
+ for (int i = 0; i < std::size(sector_bytes); i++)
+ sector_bytes[i] = sbyte_mfm_r(bitstream, pos);
+ } else {
+ memset(sector_bytes, 0, std::size(sector_bytes));
+ }
+
+ std::size_t actual;
+ if (io.write(sector_bytes, std::size(sector_bytes), actual))
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+const micropolis_vgi_format FLOPPY_VGI_FORMAT;
diff --git a/src/lib/formats/vgi_dsk.h b/src/lib/formats/vgi_dsk.h
new file mode 100644
index 00000000000..876481f10f0
--- /dev/null
+++ b/src/lib/formats/vgi_dsk.h
@@ -0,0 +1,34 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+/*********************************************************************
+
+Micropolis VGI disk image format
+
+The Micropolis disk format was used in Vector Graphic machines.
+
+*********************************************************************/
+#ifndef MAME_FORMATS_VGI_DSK_H
+#define MAME_FORMATS_VGI_DSK_H
+
+#pragma once
+
+#include "flopimg.h"
+
+class micropolis_vgi_format : public floppy_image_format_t
+{
+public:
+ micropolis_vgi_format();
+
+ int identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const override;
+ bool load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants, floppy_image *image) const override;
+ bool save(util::random_read_write &io, const std::vector<uint32_t> &variants, floppy_image *image) const override;
+
+ const char *name() const override { return "vgi"; }
+ const char *description() const override { return "Micropolis VGI disk image"; }
+ const char *extensions() const override { return "vgi"; }
+ bool supports_save() const override { return true; }
+};
+
+extern const micropolis_vgi_format FLOPPY_VGI_FORMAT;
+
+#endif // MAME_FORMATS_VGI_DSK_H
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index 9fff4d896a9..86318e114e9 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -44640,6 +44640,9 @@ squaitsa // (c) 1985
@source:valadon/tankbust.cpp
tankbust // (c) 1985
+@source:vectorgraphic/vector4.cpp
+vector4 //
+
@source:venture/looping.cpp
looping // (c) 1982 Video Games GmbH
loopingv // (c) 1982 Video Games GmbH (Venture Line license)
diff --git a/src/mame/mess.flt b/src/mame/mess.flt
index c53fd35580a..374e899a402 100644
--- a/src/mame/mess.flt
+++ b/src/mame/mess.flt
@@ -1221,6 +1221,7 @@ ussr/unior.cpp
ussr/ut88.cpp
ussr/vector06.cpp
ussr/vta2000.cpp
+vectorgraphic/vector4.cpp
verifone/tranz330.cpp
vidbrain/vidbrain.cpp
videoton/tvc.cpp
diff --git a/src/mame/vectorgraphic/sbcvideo.cpp b/src/mame/vectorgraphic/sbcvideo.cpp
new file mode 100644
index 00000000000..eb9a7918f66
--- /dev/null
+++ b/src/mame/vectorgraphic/sbcvideo.cpp
@@ -0,0 +1,183 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+#include "emu.h"
+
+#include "sbcvideo.h"
+
+#include "video/cgapal.h"
+
+#include "screen.h"
+
+vector_sbc_video_device::vector_sbc_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SBC_VIDEO, tag, owner, clock)
+ , m_io_sbc_video_conf(*this, "SBC_VIDEO_CONF")
+ , m_buffer(*this, finder_base::DUMMY_TAG)
+ , m_chrroml(*this, finder_base::DUMMY_TAG)
+ , m_chrromr(*this, finder_base::DUMMY_TAG)
+ , m_res320_ram{0}
+{
+}
+
+void vector_sbc_video_device::spr_w(uint8_t data)
+{
+ m_spr = data;
+}
+
+void vector_sbc_video_device::res320_mapping_ram_w(offs_t offset, uint8_t data)
+{
+ // 7200-0001 page 209 (VI A-6) C6
+ m_res320_ram[offset & 0x3] = data & 0x0F;
+}
+
+static inline rgb_t raw_4bit_to_rgb(uint8_t enc, bool color)
+{
+ if (color) {
+ return rgb_t(cga_palette[enc][0], cga_palette[enc][1], cga_palette[enc][2]);
+ } else {
+ // 7200-0001 page 209 C1. Flip R and G
+ enc = (enc & 0xc) | (BIT(enc, 0, 1) << 1) | BIT(enc, 1, 1);
+ uint8_t px = enc | (enc << 4);
+ return rgb_t(0, px, 0);
+ }
+}
+
+MC6845_UPDATE_ROW(vector_sbc_video_device::update_row)
+{
+ uint32_t monochrome_color = rgb_t(0x00, 0xff, 0x00);
+ bool graph = BIT(m_spr, 1); // ALPHA|/GRAPH
+ bool gry = BIT(m_spr, 2); // DIG|/GRY
+ bool res320 = BIT(m_spr, 3); // 320/160|
+ bool chr1 = BIT(m_spr, 4); // CHR0|/CHR1
+ // Color is available only on an external/second monitor, via a CGA
+ // connector. But the color monitor is not provided output for
+ // monochrome modes (only if GRAPH and GRY). The same pixel value is used
+ // by the builtin monitor and RGBI monitor, but they interpret the value
+ // differently.
+ bool color = (m_io_sbc_video_conf->read() & 0x1) == 0x1;
+ // video never uses MA17. 7200-0001 209 B5
+ uint32_t addr = BIT(ma, 0, 10); // U82 U100
+ uint8_t jumper_c = (m_io_sbc_video_conf->read() >> 1) & 0x7;
+ if (graph)
+ addr |= (ra | (BIT(ma, 11, 2) << 4)) << 10; // U81
+ else
+ addr |= (BIT(ma, 10, 4) | (jumper_c << 4)) << 10; // U99
+ addr <<= 1;
+
+ // The display is always 312 scanlines.
+ // The horizonal resolution is 1280 in alpha mode, 640 in high res mode
+ // (monochrome), and 320 or 160 in gray/color scale mode. 320 and 160 modes
+ // have the same color space, but the 320 mode uses a palette.
+ // Buffer is 1920 bytes for Alpha mode, and 24960 bytes for Graphic mode.
+
+ uint32_t *p = &bitmap.pix(y);
+
+ if (graph) {
+ if (gry) {
+ // 7200-0001 page 83 (II 3-20)
+ if (res320) {
+ for (uint16_t x = 0; x < x_count*2; x++) {
+ uint8_t cell = m_buffer->read(x+addr);
+ rgb_t px1 = raw_4bit_to_rgb(m_res320_ram[BIT(cell, 0, 2)], color);
+ for (int i = 0; i < 4; i++)
+ *p++ = px1;
+ rgb_t px2 = raw_4bit_to_rgb(m_res320_ram[BIT(cell, 2, 2)], color);
+ for (int i = 0; i < 4; i++)
+ *p++ = px2;
+ rgb_t px3 = raw_4bit_to_rgb(m_res320_ram[BIT(cell, 4, 2)], color);
+ for (int i = 0; i < 4; i++)
+ *p++ = px3;
+ rgb_t px4 = raw_4bit_to_rgb(m_res320_ram[BIT(cell, 6, 2)], color);
+ for (int i = 0; i < 4; i++)
+ *p++ = px4;
+ }
+ } else { // 160
+ for (uint16_t x = 0; x < x_count*2; x++) {
+ uint8_t cell = m_buffer->read(x+addr);
+ rgb_t px1 = raw_4bit_to_rgb(BIT(cell, 0, 4), color);
+ for (int i = 0; i < 8; i++)
+ *p++ = px1;
+ rgb_t px2 = raw_4bit_to_rgb(BIT(cell, 4, 4), color);
+ for (int i = 0; i < 8; i++)
+ *p++ = px2;
+ }
+ }
+ } else { // DIG
+ for (uint16_t x = 0; x < x_count*2; x++) {
+ uint8_t cell = m_buffer->read(x+addr);
+ for (int i = 0; i < 8; i++) {
+ *p++ = BIT(cell, i) ? monochrome_color : 0;
+ *p++ = BIT(cell, i) ? monochrome_color : 0;
+ }
+ }
+ }
+ } else { // ALPHA
+ for (uint16_t x = 0; x < x_count*2; x++) {
+ // Character Generators. 7200-0001 page 69 (II 3-6) C4
+ uint8_t cell = m_buffer->read(x+addr);
+ uint8_t chr = cell & 0x7F;
+ uint8_t invert = BIT(cell, 7);
+ uint8_t gfxl = m_chrroml[chr | (ra << 7) | (chr1 << 11)];
+ uint8_t gfxr = m_chrromr[chr | (ra << 7) | (chr1 << 11)];
+
+ if (!invert) {
+ gfxl ^= 0xff;
+ gfxr ^= 0xff;
+ }
+
+ // Alpha Mode Shift Registers. C3
+ for (int i = 0; i < 8; i++)
+ *p++ = BIT(gfxr, i) ? monochrome_color : 0;
+ for (int i = 0; i < 8; i++)
+ *p++ = BIT(gfxl, i) ? monochrome_color : 0;
+ }
+ }
+}
+
+void vector_sbc_video_device::device_add_mconfig(machine_config &config)
+{
+ // CPU|/VID used as a clock
+ c6545_1_device &crtc(C6545_1(config, "crtc", DERIVED_CLOCK(1, 32)));
+ crtc.set_screen("screen");
+ crtc.set_show_border_area(false);
+ crtc.set_char_width(16*2);
+ crtc.set_update_row_callback(FUNC(vector_sbc_video_device::update_row));
+
+ screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER));
+ screen.set_raw(32'640'000, 1600, 0, 1280, 340, 0, 312);
+ screen.set_screen_update(crtc, FUNC(c6545_1_device::screen_update));
+}
+
+void vector_sbc_video_device::device_start()
+{
+ save_item(NAME(m_spr));
+ save_item(NAME(m_res320_ram));
+}
+
+void vector_sbc_video_device::device_reset()
+{
+ m_spr = 0;
+}
+
+INPUT_PORTS_START( sbc_video )
+ PORT_START("SBC_VIDEO_CONF")
+ PORT_CONFNAME(0x001, 0x000, "Color Monitor")
+ PORT_CONFSETTING(0x000, DEF_STR(No))
+ PORT_CONFSETTING(0x001, DEF_STR(Yes))
+
+ PORT_CONFNAME(0x00e, 0x002, "Alpha Graphic Memory Block")
+ PORT_CONFSETTING(0x000, "0x00000-0x07FFFF")
+ PORT_CONFSETTING(0x002, "0x08000-0x0FFFFF")
+ PORT_CONFSETTING(0x004, "0x10000-0x17FFFF")
+ PORT_CONFSETTING(0x006, "0x18000-0x1FFFFF")
+ PORT_CONFSETTING(0x008, "0x20000-0x27FFFF")
+ PORT_CONFSETTING(0x00a, "0x28000-0x2FFFFF")
+ PORT_CONFSETTING(0x00c, "0x30000-0x37FFFF")
+ PORT_CONFSETTING(0x00e, "0x38000-0x3FFFFF")
+INPUT_PORTS_END
+
+ioport_constructor vector_sbc_video_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME(sbc_video);
+}
+
+DEFINE_DEVICE_TYPE(SBC_VIDEO, vector_sbc_video_device, "vectorsbcvideo", "Vector SBC Video Output")
diff --git a/src/mame/vectorgraphic/sbcvideo.h b/src/mame/vectorgraphic/sbcvideo.h
new file mode 100644
index 00000000000..490ecb9fa75
--- /dev/null
+++ b/src/mame/vectorgraphic/sbcvideo.h
@@ -0,0 +1,43 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+#ifndef MAME_VECTORGRAPHIC_SBCVIDEO_H
+#define MAME_VECTORGRAPHIC_SBCVIDEO_H
+
+#pragma once
+
+#include "machine/ram.h"
+#include "video/mc6845.h"
+
+
+class vector_sbc_video_device : public device_t
+{
+public:
+ vector_sbc_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template <typename T> void set_buffer(T &&tag) { m_buffer.set_tag(std::forward<T>(tag)); }
+ template <typename T> void set_chrroml(T &&tag) { m_chrroml.set_tag(std::forward<T>(tag)); }
+ template <typename T> void set_chrromr(T &&tag) { m_chrromr.set_tag(std::forward<T>(tag)); }
+
+ void spr_w(uint8_t data);
+ void res320_mapping_ram_w(offs_t offset, uint8_t data);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual ioport_constructor device_input_ports() const override;
+ virtual void device_add_mconfig(machine_config &config) override;
+
+private:
+ MC6845_UPDATE_ROW(update_row);
+
+ required_ioport m_io_sbc_video_conf;
+ required_device<ram_device> m_buffer;
+ required_region_ptr<uint8_t> m_chrroml;
+ required_region_ptr<uint8_t> m_chrromr;
+ uint8_t m_spr;
+ uint8_t m_res320_ram[4];
+};
+
+DECLARE_DEVICE_TYPE(SBC_VIDEO, vector_sbc_video_device)
+
+#endif // MAME_VECTORGRAPHIC_SBCVIDEO_H
diff --git a/src/mame/vectorgraphic/vector4.cpp b/src/mame/vectorgraphic/vector4.cpp
new file mode 100644
index 00000000000..0abd2d6586e
--- /dev/null
+++ b/src/mame/vectorgraphic/vector4.cpp
@@ -0,0 +1,315 @@
+// license:BSD-3-Clause
+// copyright-holders:Eric Anderson
+/***************************************************************************
+
+Vector Graphic Vector 4. Vector Graphic, Inc was a Californian company that
+made a personal computers before the IBM PC disrupted the industry. The
+Vector 4 was an office computer with word processing, spreadsheeting,
+database, and eventually networked drives.
+
+The ROM boots from the second floppy. "Winchester" boots from the hard drive
+(currently unsupported), which is the first device when in use.
+
+On power on the system uses a Z80B, but it also contains an 8088-2 for limited
+IBM compatibility. Only one processor is running at a time and reading from a
+port can toggle the active processor. They share memory with each other and the
+video subsystem. The active processor and the video subsystem alternate access to
+the RAM. The BDOS for CP/M makes use of the 8088, so both processors are
+necessary for any of the OSes to boot: CP/M, CP/M 86, MS-DOS.
+
+The system had three S-100 slots, with the floppy controller using one. It was a
+modified bus since it provided regulated power. The system had 128K RAM and
+could be extended to 256K. The keyboard plugged in to a 6P6C jack. There were
+female ports for two DB-25 RS-232, a 36-pin micro ribbon (aka, telco,
+centronics) parallel, a 50-pin micro ribbon parallel, and a DE-9 RGBI/CGA
+monitor.
+
+The 7100-0001 User's Manual provides some overview specifications starting on
+page 171 (X A-1) including ports and devices. But it provides few details. The
+7200-0001 Technical Information provides thorough descriptions and schematics.
+
+"Executive" is the name of the boot ROM, not to be confused with a model name.
+The motherboard is called the Single Board Computer (SBC) and includes all
+functionality except for the disk controller.
+
+https://archive.org/details/7200-0001-vector-4-technical-information-sep-82
+https://www.bitsavers.org/pdf/vectorGraphic/vector_4/7100-0001_Vector_4_Users_Manual_Feb83.pdf
+
+TODO:
+- keyboard mcu
+- S-100 interrupts and ready
+- parallel port
+- WAIT CPU states
+- CPM-86 and MS-DOS 2.0 don't boot
+
+****************************************************************************/
+
+#include "emu.h"
+
+#include "sbcvideo.h"
+
+#include "bus/rs232/rs232.h"
+#include "bus/s100/s100.h"
+#include "bus/s100/vectordualmode.h"
+#include "cpu/i86/i86.h"
+#include "cpu/z80/z80.h"
+#include "machine/bankdev.h"
+#include "machine/clock.h"
+#include "machine/i8251.h"
+#include "machine/i8255.h"
+#include "machine/ram.h"
+#include "machine/pit8253.h"
+#include "sound/sn76496.h"
+
+#include "speaker.h"
+
+namespace {
+
+class vector4_state : public driver_device
+{
+public:
+ vector4_state(const machine_config &mconfig, device_type type, const char *tag)
+ : driver_device(mconfig, type, tag)
+ , m_maincpu(*this, "maincpu")
+ , m_8088cpu(*this, "8088cpu")
+ , m_rambanks(*this, "rambank%u", 0U)
+ , m_256k(false)
+ , m_ram(*this, RAM_TAG)
+ , m_romenbl(*this, "romenbl")
+ , m_sbc_video(*this, "video")
+ , m_s100(*this, "s100")
+ { }
+
+ void vector4(machine_config &config);
+
+private:
+ void vector4_io(address_map &map);
+ void vector4_z80mem(address_map &map);
+ void vector4_8088mem(address_map &map);
+ void spr_w(uint8_t data);
+ uint8_t msc_r();
+ void msc_w(uint8_t data) { machine_reset(); }
+ void addrmap_w(offs_t offset, uint8_t data);
+ uint8_t s100_r(offs_t offset) { return m_s100->sinp_r(offset+0x20); }
+ void s100_w(offs_t offset, uint8_t data) { m_s100->sout_w(offset+0x20, data); }
+ void machine_start() override;
+ void machine_reset() override;
+
+ required_device<cpu_device> m_maincpu;
+ required_device<cpu_device> m_8088cpu;
+ required_memory_bank_array<32> m_rambanks;
+ bool m_256k;
+ required_device<ram_device> m_ram;
+ memory_view m_romenbl;
+ required_device<vector_sbc_video_device> m_sbc_video;
+ required_device<s100_bus_device> m_s100;
+};
+
+
+void vector4_state::vector4_z80mem(address_map &map)
+{
+ map.unmap_value_high();
+ for (int bank = 0; bank < (1<<5); bank++)
+ map(bank << 11, ((bank+1) << 11)-1).bankrw(m_rambanks[bank]);
+ // 7200-0001 page 209 (VI A-6) B6
+ // ROM mapping only applies to the z80 and does not use address mapping.
+ map(0x0000, 0x0fff).view(m_romenbl);
+ m_romenbl[0](0x0000, 0x0fff).rom().region("maincpu", 0);
+}
+
+void vector4_state::vector4_8088mem(address_map &map)
+{
+ map.unmap_value_high();
+ map.global_mask(0x3ffff);
+}
+
+void vector4_state::vector4_io(address_map &map)
+{
+ map.unmap_value_high();
+ map(0x00, 0x01).mirror(0xff00).rw("uart0", FUNC(i8251_device::read), FUNC(i8251_device::write)); // keyboard
+ map(0x02, 0x03).mirror(0xff00).w(FUNC(vector4_state::spr_w)); // subsystem port register
+ map(0x04, 0x05).mirror(0xff00).rw("uart1", FUNC(i8251_device::read), FUNC(i8251_device::write)); // modem
+ map(0x06, 0x07).mirror(0xff00).rw("uart2", FUNC(i8251_device::read), FUNC(i8251_device::write)); // serial printer
+ map(0x08, 0x0b).mirror(0xff00).rw("ppi", FUNC(i8255_device::read), FUNC(i8255_device::write)); // parallel printer
+ map(0x0c, 0x0d).mirror(0xff00).rw(FUNC(vector4_state::msc_r), FUNC(vector4_state::msc_w)); // select Z80/8088-2
+ map(0x0e, 0x0e).mirror(0xff00).rw("video:crtc", FUNC(c6545_1_device::status_r), FUNC(c6545_1_device::address_w)); // video controller
+ map(0x0f, 0x0f).mirror(0xff00).rw("video:crtc", FUNC(c6545_1_device::register_r), FUNC(c6545_1_device::register_w));
+ map(0x10, 0x13).mirror(0xff00).rw("pit", FUNC(pit8253_device::read), FUNC(pit8253_device::write)); // baud generator and timer
+ map(0x16, 0x17).select(0xff00).w(FUNC(vector4_state::addrmap_w)); // RAM address map
+ map(0x18, 0x19).mirror(0xff00).w("sn", FUNC(sn76489_device::write)); // tone generator
+ map(0x1c, 0x1f).mirror(0xff00).w(m_sbc_video, FUNC(vector_sbc_video_device::res320_mapping_ram_w)); // resolution 320 mapping RAM
+ map(0x20, 0xff).mirror(0xff00).rw(FUNC(vector4_state::s100_r), FUNC(vector4_state::s100_w));
+}
+
+static void vector4_s100_devices(device_slot_interface &device)
+{
+ device.option_add("dualmodedisk", S100_VECTOR_DUALMODE);
+}
+
+static INPUT_PORTS_START( vector4 )
+INPUT_PORTS_END
+
+// 7200-0001 page 102 (II 5-11)
+DEVICE_INPUT_DEFAULTS_START(keyboard)
+ DEVICE_INPUT_DEFAULTS("RS232_TXBAUD", 0x00ff, RS232_BAUD_300)
+ DEVICE_INPUT_DEFAULTS("RS232_RXBAUD", 0x00ff, RS232_BAUD_300)
+ DEVICE_INPUT_DEFAULTS("RS232_DATABITS", 0x00ff, RS232_DATABITS_8)
+ DEVICE_INPUT_DEFAULTS("RS232_PARITY", 0x00ff, RS232_PARITY_NONE)
+ DEVICE_INPUT_DEFAULTS("RS232_STOPBITS", 0x00ff, RS232_STOPBITS_2)
+DEVICE_INPUT_DEFAULTS_END
+
+void vector4_state::vector4(machine_config &config)
+{
+ const XTAL _32m(32'640'000);
+ const XTAL _2m = _32m/16;
+
+ /* processors */
+ // Manual says 5.1 MHz. To do so, schematic shows (A1) it is driven by the
+ // 32.64 MHz xtal, 1/16 of the cycles are skipped, and it is divided by 6.
+ Z80(config, m_maincpu, 5'100'000);
+ m_maincpu->set_addrmap(AS_PROGRAM, &vector4_state::vector4_z80mem);
+ m_maincpu->set_addrmap(AS_IO, &vector4_state::vector4_io);
+
+ I8088(config, m_8088cpu, 5'100'000);
+ m_8088cpu->set_addrmap(AS_PROGRAM, &vector4_state::vector4_8088mem);
+ m_8088cpu->set_addrmap(AS_IO, &vector4_state::vector4_io);
+
+ RAM(config, m_ram).set_default_size("128K").set_extra_options("256K");
+
+ /* video hardware */
+ SBC_VIDEO(config, m_sbc_video, _32m);
+ m_sbc_video->set_buffer(m_ram);
+ m_sbc_video->set_chrroml("chargenl");
+ m_sbc_video->set_chrromr("chargenr");
+
+ /* i/o */
+ XTAL _2mclk(2'000'000); // 7200-0001 page 210 (VI A-7) D13
+
+ S100_BUS(config, m_s100, _2mclk);
+ S100_SLOT(config, "s100:1", vector4_s100_devices, "dualmodedisk");
+ S100_SLOT(config, "s100:2", vector4_s100_devices, nullptr);
+ S100_SLOT(config, "s100:3", vector4_s100_devices, nullptr);
+
+ pit8253_device &pit(PIT8253(config, "pit", 0));
+ // 7200-0001 page 210 D7, 208 (VI A-5) A1
+ pit.set_clk<0>(_2mclk);
+ pit.set_clk<1>(_2mclk);
+ pit.set_clk<2>(_2mclk);
+ // 7200-0001 page 107 (II 5-16)
+ pit.out_handler<0>().set_inputline(m_maincpu, INPUT_LINE_IRQ0);
+ pit.out_handler<0>().append_inputline(m_8088cpu, INPUT_LINE_IRQ0);
+
+ // 7200-0001 page 210 D13, D1
+ clock_device &keyboard_clock(CLOCK(config, "keyboard_clock", _2mclk/26/16));
+ i8251_device &uart0(I8251(config, "uart0", 0));
+ rs232_port_device &rs232keyboard(RS232_PORT(config, "rs232keyboard", default_rs232_devices, "keyboard"));
+ keyboard_clock.signal_handler().set(uart0, FUNC(i8251_device::write_txc));
+ keyboard_clock.signal_handler().append(uart0, FUNC(i8251_device::write_rxc));
+ uart0.txd_handler().set(rs232keyboard, FUNC(rs232_port_device::write_txd));
+ rs232keyboard.rxd_handler().set(uart0, FUNC(i8251_device::write_rxd));
+ rs232keyboard.set_option_device_input_defaults("keyboard", DEVICE_INPUT_DEFAULTS_NAME(keyboard));
+
+ // D3
+ i8251_device &uart1(I8251(config, "uart1", 0));
+ rs232_port_device &rs232com(RS232_PORT(config, "rs232com", default_rs232_devices, nullptr));
+ pit.out_handler<1>().set(uart1, FUNC(i8251_device::write_txc));
+ pit.out_handler<1>().append(uart1, FUNC(i8251_device::write_rxc));
+ uart1.txd_handler().set(rs232com, FUNC(rs232_port_device::write_txd));
+ uart1.dtr_handler().set(rs232com, FUNC(rs232_port_device::write_dtr));
+ uart1.rts_handler().set(rs232com, FUNC(rs232_port_device::write_rts));
+ rs232com.rxd_handler().set(uart1, FUNC(i8251_device::write_rxd));
+ rs232com.dsr_handler().set(uart1, FUNC(i8251_device::write_dsr));
+ rs232com.cts_handler().set(uart1, FUNC(i8251_device::write_cts));
+
+ // D2
+ i8251_device &uart2(I8251(config, "uart2", 0));
+ rs232_port_device &rs232prtr(RS232_PORT(config, "rs232prtr", default_rs232_devices, nullptr));
+ pit.out_handler<2>().set(uart2, FUNC(i8251_device::write_txc));
+ pit.out_handler<2>().append(uart2, FUNC(i8251_device::write_rxc));
+ uart2.txd_handler().set(rs232prtr, FUNC(rs232_port_device::write_txd));
+ uart2.dtr_handler().set(rs232prtr, FUNC(rs232_port_device::write_dtr));
+ uart2.rts_handler().set(rs232prtr, FUNC(rs232_port_device::write_rts));
+ rs232prtr.rxd_handler().set(uart2, FUNC(i8251_device::write_rxd));
+ rs232prtr.dsr_handler().set(uart2, FUNC(i8251_device::write_dsr));
+ rs232prtr.cts_handler().set(uart2, FUNC(i8251_device::write_cts));
+
+ // 7200-0001 page 110 (II 5-19), 210 D8
+ // TODO: Qume (50 pin) and Centronics (36 pin)
+ I8255A(config, "ppi");
+
+ SPEAKER(config, "mono").front_center();
+ sn76489_device &sn(SN76489(config, "sn", _2m));
+ sn.add_route(ALL_OUTPUTS, "mono", 1.0);
+}
+
+void vector4_state::machine_start()
+{
+ m_256k = (m_ram->size() == 256 * 1024);
+ m_8088cpu->space(AS_PROGRAM).install_ram(0, m_ram->mask(), m_ram->size() & 0x20000, m_ram->pointer());
+ for (int bank = 0; bank < (1<<5); bank++)
+ m_rambanks[bank]->configure_entries(0, 1<<7, m_ram->pointer(), 1<<11);
+}
+
+void vector4_state::machine_reset()
+{
+ // 7200-0001 page 39 (II 1-10), page 210 D11
+ spr_w(0);
+ m_8088cpu->suspend(SUSPEND_REASON_HALT, true);
+ m_maincpu->resume(SUSPEND_REASON_HALT);
+}
+
+/* Subsystem Port Register */
+void vector4_state::spr_w(uint8_t data)
+{
+ // 7200-0001 page 81 (II 3-18)
+ if (BIT(data, 0))
+ m_romenbl.disable();
+ else
+ m_romenbl.select(0);
+ m_sbc_video->spr_w(data);
+}
+
+/* Microprocessor Switching Control */
+uint8_t vector4_state::msc_r()
+{
+ // 7200-0001 page 40 (II 1-11), 208 A3
+ if (m_maincpu->suspended(SUSPEND_REASON_HALT)) {
+ m_8088cpu->suspend(SUSPEND_REASON_HALT, false);
+ m_maincpu->resume(SUSPEND_REASON_HALT);
+ } else {
+ m_maincpu->suspend(SUSPEND_REASON_HALT, false);
+ m_8088cpu->resume(SUSPEND_REASON_HALT);
+ }
+ return 0xff;
+}
+
+/* Address mapping RAM Subsystem */
+void vector4_state::addrmap_w(offs_t offset, uint8_t data)
+{
+ // 7200-0001 page 50 (II 2-7), page 208 B1
+ // m_256k is for jumper area B. 7200-0001 page 170 (IV 2-1), page 209 coord A8
+ m_rambanks[BIT(offset, 11, 5)]->set_entry(data & (m_256k ? 0x7f : 0x3f));
+}
+
+/* ROM definition */
+ROM_START( vector4 )
+ ROM_REGION( 0x1000, "maincpu", ROMREGION_ERASEFF )
+ ROM_SYSTEM_BIOS( 0, "v11", "SBC ver 1.1" ) // VECTOR 4 SBC EXECUTIVE 1.1
+ ROMX_LOAD( "sbcv11.bin", 0x0000, 0x1000, CRC(56c80656) SHA1(a381bdbb6cdee0ac1dc8b1c1359361a19ba6fe46), ROM_BIOS(0))
+ ROM_SYSTEM_BIOS( 1, "v111", "SBC ver 1.11" ) // VECTOR 4 SBC EXECUTIVE 1.11
+ ROMX_LOAD( "u35_sbc_v111_4f11.bin", 0x0000, 0x1000, CRC(fcfd42c6) SHA1(11cd5cf0c3f2d2a30864b8a4b4be51ade03d02ea), ROM_BIOS(1))
+ ROM_SYSTEM_BIOS( 2, "v200", "SBC ver 2.00" ) // VECTOR GRAPHIC V4/40 [F] SBC EXECUTIVE - REVISION 2.00 (AA)
+ ROMX_LOAD( "sbcexecf.bin", 0x0000, 0x1000, CRC(738e10b5) SHA1(abce22abe9bac6241b1996d078647fe36b638769), ROM_BIOS(2))
+
+ ROM_REGION( 0x1000, "chargenl", ROMREGION_ERASEFF )
+ ROM_LOAD( "cgst60l.bin", 0x0000, 0x1000, CRC(e55a404c) SHA1(4dafd6f588c42081212928d3c4448f10dd461a7a))
+ ROM_REGION( 0x1000, "chargenr", ROMREGION_ERASEFF )
+ ROM_LOAD( "cgst60r.bin", 0x0000, 0x1000, CRC(201d783c) SHA1(7c2b8988c27b5fa435d31c9b7d4d9ed176c9b8a3))
+ROM_END
+
+} // anonymous namespace
+
+/* Driver */
+
+// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS
+COMP( 1982, vector4, 0, 0, vector4, vector4, vector4_state, empty_init, "Vector Graphic", "Vector 4", MACHINE_NODEVICE_PRINTER | MACHINE_IMPERFECT_TIMING | MACHINE_SUPPORTS_SAVE )