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.
This commit is contained in:
Eric Anderson 2023-02-27 00:47:55 -08:00 committed by GitHub
parent daaf8645e0
commit 369ecb2f77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1131 additions and 0 deletions

View File

@ -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",
}

View File

@ -2052,6 +2052,18 @@ if opt_tool(FORMATS, "VG5K_CAS") then
}
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

View File

@ -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")

View File

@ -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

View File

@ -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 */

View File

@ -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

146
src/lib/formats/vgi_dsk.cpp Normal file
View File

@ -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;

34
src/lib/formats/vgi_dsk.h Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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 )