mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
510 lines
18 KiB
C++
510 lines
18 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders: VT1200 reverse-engineering project
|
|
/***************************************************************************
|
|
|
|
DEC VT1200 X terminal -- MAME execution harness
|
|
|
|
This is NOT an accurate hardware emulation. It exists to give us a way
|
|
to *execute* the VT1200 firmware under the TMS34010 interpreter with a
|
|
debugger (breakpoints, single step, memory watch, trace).
|
|
|
|
What is modelled:
|
|
* TMS34010 CPU (bit-addressed: MAME's tms34010 memory space uses a
|
|
right shift of 3, so address-map units are BITS).
|
|
* RAM at logical bit address 0, 4 MiB (byte 0 .. 0x3fffff).
|
|
* The 1.75 MiB firmware ROM (physical byte base 0x07e40000 = bit
|
|
0x3f200000), mirrored into the three upper 1 GiB quadrants so that
|
|
the reset-vector page at logical bit 0xffffffe0 resolves to the last
|
|
four bytes of the image (file offset 0x1bfffc).
|
|
* OPTIONALLY (see below) the three undecoded board windows.
|
|
|
|
What is NOT modelled: LANCE Ethernet, the DUARTs, and the board system
|
|
logic at bit 0x5800000 / 0x6200000 / 0x6400000. Because of that the
|
|
terminal does not boot to the X server; the firmware parks in early POST
|
|
waiting on a hardware-ready poll.
|
|
|
|
---------------------------------------------------------------------
|
|
Board-window experiments (all opt-in; with no env vars set the windows
|
|
stay unmapped and behave exactly as before):
|
|
|
|
VT1200_BOARD_LOG=<path> append a line for every *distinct*
|
|
(address, PC) board access, flushed
|
|
immediately. This is the evidence
|
|
gatherer: it shows every register the
|
|
firmware polls and the PC doing the
|
|
polling.
|
|
|
|
VT1200_BOARD_DEFAULT=<value> value returned for addresses without an
|
|
override (default 0).
|
|
|
|
VT1200_BOARD_MAP=<list> per-address overrides, e.g.
|
|
"0x06200010=1;0x0640000d0=0"
|
|
|
|
VT1200_BOARD_RAM=1 map the windows as plain read/write RAM
|
|
instead (writes persist and read back).
|
|
|
|
See FULL_BOOT_ESTIMATE.md section 4b for measured results.
|
|
|
|
***************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "cpu/tms34010/tms34010.h"
|
|
#include "emupal.h"
|
|
#include "screen.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
|
|
namespace {
|
|
|
|
// The undecoded windows, in bit-address units. 0x5900000 is paired with
|
|
// 0x5800000 as a command/data register interface (write a command to
|
|
// 0x5900000, then data to 0x5800000; the firmware converts a bit address to a
|
|
// byte address with >>3 before feeding it, which implies a DMA-style device
|
|
// that uses byte addressing).
|
|
static const u32 BOARD_BASE[4] = { 0x05800000, 0x05900000, 0x06200000, 0x06400000 };
|
|
|
|
class vt1200_state : public driver_device
|
|
{
|
|
public:
|
|
vt1200_state(const machine_config &mconfig, device_type type, const char *tag)
|
|
: driver_device(mconfig, type, tag)
|
|
, m_maincpu(*this, "maincpu")
|
|
, m_screen(*this, "screen")
|
|
, m_palette(*this, "palette")
|
|
, m_mainram(*this, "mainram")
|
|
{
|
|
}
|
|
|
|
void vt1200(machine_config &config);
|
|
|
|
virtual void machine_start() override ATTR_COLD;
|
|
virtual void machine_reset() override ATTR_COLD;
|
|
|
|
// scanline callback: renders (or blanks) the line, and can dump a frame
|
|
void scanline(screen_device &screen, bitmap_ind16 &bitmap, int scanline,
|
|
const tms340x0_device::display_params *params);
|
|
void dump_frame(screen_device &screen, bitmap_ind16 &bitmap);
|
|
void dump_ram();
|
|
|
|
// We drive the TMS34010 renderer ourselves so the frame dump does not
|
|
// depend on the device's scanline-callback plumbing.
|
|
uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
|
|
|
|
// Board window access, one pair per window so the absolute address is
|
|
// unambiguous no matter whether MAME hands us an entry-relative or an
|
|
// absolute offset.
|
|
u16 board0_r(offs_t off); void board0_w(offs_t off, u16 data);
|
|
u16 board1_r(offs_t off); void board1_w(offs_t off, u16 data);
|
|
u16 board2_r(offs_t off); void board2_w(offs_t off, u16 data);
|
|
u16 board3_r(offs_t off); void board3_w(offs_t off, u16 data);
|
|
|
|
private:
|
|
void program_map(address_map &map) ATTR_COLD;
|
|
|
|
u16 board_read(int win, offs_t off);
|
|
void board_write(int win, offs_t off, u16 data);
|
|
void log_pc_access(char kind, u32 addr, u16 value);
|
|
|
|
required_device<tms34010_device> m_maincpu;
|
|
required_device<screen_device> m_screen;
|
|
required_device<palette_device> m_palette;
|
|
required_shared_ptr<u16> m_mainram;
|
|
|
|
std::map<u32, u16> m_override; // absolute addr -> fixed value
|
|
std::map<u32, u16> m_store; // absolute addr -> last written value
|
|
std::map<std::pair<u32, u32>, u32> m_seen; // (addr, pc) -> count
|
|
u16 m_default = 0;
|
|
FILE *m_log = nullptr;
|
|
|
|
// Handshake model. The register at m_busy_addr carries a "busy" bit that
|
|
// the firmware both waits to be SET (command accepted) and waits to be
|
|
// CLEAR (operation finished), so it must change state, not hold a value.
|
|
// Any write to the window arms it; it clears after m_busy_ticks reads.
|
|
u32 m_busy_addr = 0x06200001;
|
|
u16 m_busy_bit = 1;
|
|
int m_busy_ticks = 2;
|
|
int m_busy_left = 0;
|
|
bool m_model = false;
|
|
|
|
// Video. Rendering is on by default now that the firmware reaches the
|
|
// display code; VT1200_NO_RENDER=1 restores the old blank-everything
|
|
// behaviour. VT1200_DUMP_FRAME=<n> writes that frame as a PPM.
|
|
bool m_render = true;
|
|
long m_dump_frame = -1;
|
|
std::string m_dump_dir = ".";
|
|
|
|
// VT1200_DUMP_RAM=<frame> writes main RAM at that frame to
|
|
// vt1200-ram-<frame>.bin so the framebuffer can be located offline.
|
|
long m_dump_ram = -1;
|
|
bool m_dumped_ram = false;
|
|
|
|
// VT1200_BOARD_SWITCH=<addr>:<mask>:<value>
|
|
// Bits in <mask> read as <value> (an input, e.g. the monitor-selection
|
|
// switch); all other bits behave as write-back output latches. 0x64000D0
|
|
// is exactly this: the self-test reads the monitor switch from bits 4-6
|
|
// (andi 0x70) while the firmware also writes the port.
|
|
bool m_switch = false;
|
|
u32 m_switch_addr = 0;
|
|
u16 m_switch_mask = 0;
|
|
u16 m_switch_val = 0;
|
|
};
|
|
|
|
void vt1200_state::machine_start()
|
|
{
|
|
if (const char *p = getenv("VT1200_BOARD_DEFAULT"))
|
|
m_default = u16(strtoul(p, nullptr, 0));
|
|
|
|
if (const char *list = getenv("VT1200_BOARD_MAP"))
|
|
{
|
|
std::string s(list), item;
|
|
size_t pos = 0;
|
|
while (pos <= s.size())
|
|
{
|
|
size_t semi = s.find(';', pos);
|
|
item = s.substr(pos, (semi == std::string::npos) ? std::string::npos : semi - pos);
|
|
pos = (semi == std::string::npos) ? s.size() + 1 : semi + 1;
|
|
if (item.empty())
|
|
continue;
|
|
size_t eq = item.find('=');
|
|
if (eq == std::string::npos)
|
|
continue;
|
|
u32 a = u32(strtoul(item.substr(0, eq).c_str(), nullptr, 0));
|
|
u16 v = u16(strtoul(item.substr(eq + 1).c_str(), nullptr, 0));
|
|
m_override[a] = v;
|
|
}
|
|
}
|
|
|
|
if (const char *path = getenv("VT1200_BOARD_LOG"))
|
|
{
|
|
m_log = fopen(path, "w");
|
|
if (m_log)
|
|
fprintf(m_log, "# kind addr pc value (bit addresses; distinct (addr,pc) only)\n");
|
|
}
|
|
|
|
// Handshake model: write-back registers + a self-clearing busy bit.
|
|
if (const char *model = getenv("VT1200_BOARD_MODEL"))
|
|
{
|
|
m_model = !strcmp(model, "handshake");
|
|
if (const char *a = getenv("VT1200_BOARD_BUSY"))
|
|
m_busy_addr = u32(strtoul(a, nullptr, 0));
|
|
if (const char *t = getenv("VT1200_BOARD_BUSY_TICKS"))
|
|
m_busy_ticks = (int)strtol(t, nullptr, 0);
|
|
if (const char *b = getenv("VT1200_BOARD_BUSY_BIT"))
|
|
m_busy_bit = u16(strtoul(b, nullptr, 0));
|
|
}
|
|
|
|
// Bit-mixed port: some bits are inputs (e.g. the monitor switch), the rest
|
|
// are write-back output latches.
|
|
if (const char *sw = getenv("VT1200_BOARD_SWITCH"))
|
|
{
|
|
unsigned a = 0, m = 0, v = 0;
|
|
if (sscanf(sw, "%x:%x:%x", &a, &m, &v) == 3)
|
|
{
|
|
m_switch = true;
|
|
m_switch_addr = a;
|
|
m_switch_mask = u16(m);
|
|
m_switch_val = u16(v);
|
|
}
|
|
}
|
|
|
|
// Video: render by default; optionally dump a frame as PPM.
|
|
if (getenv("VT1200_NO_RENDER"))
|
|
m_render = false;
|
|
if (const char *f = getenv("VT1200_DUMP_FRAME"))
|
|
m_dump_frame = strtol(f, nullptr, 0);
|
|
if (const char *f = getenv("VT1200_DUMP_RAM"))
|
|
m_dump_ram = strtol(f, nullptr, 0);
|
|
if (const char *d = getenv("VT1200_DUMP_DIR"))
|
|
m_dump_dir = d;
|
|
|
|
logerror("vt1200: board default=%04x overrides=%d model=%s busy=%08X/%d log=%s render=%d dump=%ld\n",
|
|
m_default, (int)m_override.size(), m_model ? "handshake" : "off",
|
|
m_busy_addr, m_busy_ticks, m_log ? "on" : "off", m_render ? 1 : 0, m_dump_frame);
|
|
}
|
|
|
|
void vt1200_state::machine_reset()
|
|
{
|
|
if (m_log)
|
|
fflush(m_log);
|
|
}
|
|
|
|
void vt1200_state::program_map(address_map &map)
|
|
{
|
|
// Main RAM: 4 MiB (1 MiB onboard + 3 x 1 MiB SIMMs on the real machine).
|
|
// The TMS34010 is bit-addressed, so 4 MiB of bytes == 0x2000000 address
|
|
// units. (The earlier 0x200000 range modelled only 256 KiB, which is
|
|
// almost certainly why POST mis-detected memory.)
|
|
map(0x00000000, 0x01ffffff).ram().share("mainram");
|
|
|
|
// ROM: physical byte base 0x07e40000 -> bit base 0x3f200000, 0x1c0000 bytes
|
|
// (0xe00000 bit addresses), plus the three upper logical aliases that the
|
|
// TMS34010 generates because its top two address bits are not emitted.
|
|
map(0x3f200000, 0x3fffffff).rom().region("maincpu", 0);
|
|
map(0x7f200000, 0x7fffffff).rom().region("maincpu", 0);
|
|
map(0xbf200000, 0xbfffffff).rom().region("maincpu", 0);
|
|
map(0xff200000, 0xffffffff).rom().region("maincpu", 0);
|
|
|
|
// Undecoded board windows. Left unmapped unless an experiment asks for
|
|
// them, so the default behaviour is unchanged.
|
|
if (getenv("VT1200_BOARD_RAM"))
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
map(BOARD_BASE[i], BOARD_BASE[i] + 0xffff).ram();
|
|
}
|
|
else if (getenv("VT1200_BOARD_LOG") || getenv("VT1200_BOARD_MAP")
|
|
|| getenv("VT1200_BOARD_DEFAULT") || getenv("VT1200_BOARD_MODEL"))
|
|
{
|
|
map(BOARD_BASE[0], BOARD_BASE[0] + 0xffff).rw(FUNC(vt1200_state::board0_r), FUNC(vt1200_state::board0_w));
|
|
map(BOARD_BASE[1], BOARD_BASE[1] + 0xffff).rw(FUNC(vt1200_state::board1_r), FUNC(vt1200_state::board1_w));
|
|
map(BOARD_BASE[2], BOARD_BASE[2] + 0xffff).rw(FUNC(vt1200_state::board2_r), FUNC(vt1200_state::board2_w));
|
|
map(BOARD_BASE[3], BOARD_BASE[3] + 0xffff).rw(FUNC(vt1200_state::board3_r), FUNC(vt1200_state::board3_w));
|
|
}
|
|
}
|
|
|
|
// MAME may hand us either an entry-relative offset (0..0xffff) or the absolute
|
|
// address; BOARD_BASE is 0x10000-aligned, so ORing the low 16 bits is correct
|
|
// in both cases.
|
|
u16 vt1200_state::board_read(int win, offs_t off)
|
|
{
|
|
const u32 addr = BOARD_BASE[win] | (u32(off) & 0xffff);
|
|
u16 value;
|
|
|
|
auto ov = m_override.find(addr);
|
|
if (ov != m_override.end())
|
|
{
|
|
value = ov->second;
|
|
}
|
|
else if (m_model)
|
|
{
|
|
// Write-back RAM, plus the self-clearing busy bit.
|
|
value = m_store.count(addr) ? m_store[addr] : m_default;
|
|
if (addr == m_busy_addr)
|
|
{
|
|
if (m_busy_left > 0)
|
|
{
|
|
value |= m_busy_bit; // busy: command accepted, not done
|
|
if (--m_busy_left == 0)
|
|
; // next read returns idle
|
|
}
|
|
else
|
|
{
|
|
value &= ~m_busy_bit; // idle: operation complete
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
value = m_default;
|
|
}
|
|
|
|
// Input bits override the write-back value: the monitor-selection switch
|
|
// lives in bits 4-6 of 0x64000d0 while the rest of the port is latched
|
|
// output. A constant value here would break the output bits, which is why
|
|
// a plain override never unblocked the boot.
|
|
if (m_switch && addr == m_switch_addr)
|
|
value = u16((value & ~m_switch_mask) | (m_switch_val & m_switch_mask));
|
|
|
|
log_pc_access('R', addr, value);
|
|
return value;
|
|
}
|
|
|
|
void vt1200_state::board_write(int win, offs_t off, u16 data)
|
|
{
|
|
const u32 addr = BOARD_BASE[win] | (u32(off) & 0xffff);
|
|
m_store[addr] = data;
|
|
if (m_model && m_busy_ticks > 0)
|
|
m_busy_left = m_busy_ticks; // arm the handshake
|
|
log_pc_access('W', addr, data);
|
|
}
|
|
|
|
void vt1200_state::log_pc_access(char kind, u32 addr, u16 value)
|
|
{
|
|
if (!m_log)
|
|
return;
|
|
const u32 pc = u32(m_maincpu->state_int(TMS34010_PC));
|
|
auto key = std::make_pair(addr, pc);
|
|
auto it = m_seen.find(key);
|
|
if (it == m_seen.end())
|
|
{
|
|
m_seen.emplace(key, 1);
|
|
fprintf(m_log, "%c %08X pc=%08X val=%04X\n", kind, addr, pc, value);
|
|
fflush(m_log);
|
|
}
|
|
else if (kind == 'R')
|
|
{
|
|
it->second++;
|
|
}
|
|
}
|
|
|
|
u16 vt1200_state::board0_r(offs_t off) { return board_read(0, off); }
|
|
u16 vt1200_state::board1_r(offs_t off) { return board_read(1, off); }
|
|
u16 vt1200_state::board2_r(offs_t off) { return board_read(2, off); }
|
|
u16 vt1200_state::board3_r(offs_t off) { return board_read(3, off); }
|
|
void vt1200_state::board0_w(offs_t off, u16 data) { board_write(0, off, data); }
|
|
void vt1200_state::board1_w(offs_t off, u16 data) { board_write(1, off, data); }
|
|
void vt1200_state::board2_w(offs_t off, u16 data) { board_write(2, off, data); }
|
|
void vt1200_state::board3_w(offs_t off, u16 data) { board_write(3, off, data); }
|
|
|
|
void vt1200_state::scanline(screen_device &screen, bitmap_ind16 &bitmap, int scanline,
|
|
const tms340x0_device::display_params *params)
|
|
{
|
|
{
|
|
static int calls = 0;
|
|
if (calls++ < 4)
|
|
osd_printf_info("vt1200: scanline#%d line=%d enabled=%d\n",
|
|
calls, scanline, params ? (params->enabled ? 1 : 0) : -1);
|
|
}
|
|
if (!m_render)
|
|
std::fill_n(&bitmap.pix(scanline, 0), bitmap.width(), u16(0));
|
|
|
|
// Dump on the last visible line of the target frame: by then the whole
|
|
// frame is in the bitmap, and unlike screen_update the scanline callback
|
|
// is reliably invoked once per line.
|
|
if (scanline == screen.visible_area().max_y)
|
|
{
|
|
if (m_dump_frame >= 0 && (long)screen.frame_number() == m_dump_frame)
|
|
dump_frame(screen, bitmap);
|
|
if (m_dump_ram >= 0 && !m_dumped_ram && (long)screen.frame_number() >= m_dump_ram)
|
|
{
|
|
m_dumped_ram = true;
|
|
dump_ram();
|
|
}
|
|
}
|
|
}
|
|
|
|
void vt1200_state::dump_frame(screen_device &screen, bitmap_ind16 &bitmap)
|
|
{
|
|
char path[1024];
|
|
snprintf(path, sizeof(path), "%s/vt1200-frame-%ld.ppm", m_dump_dir.c_str(), m_dump_frame);
|
|
FILE *fp = fopen(path, "wb");
|
|
if (!fp)
|
|
{
|
|
osd_printf_info("vt1200: cannot write %s\n", path);
|
|
return;
|
|
}
|
|
const int w = bitmap.width(), h = bitmap.height();
|
|
fprintf(fp, "P6\n%d %d\n255\n", w, h);
|
|
unsigned long lit = 0;
|
|
for (int y = 0; y < h; y++)
|
|
{
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
const u16 v = bitmap.pix(y, x);
|
|
if (v)
|
|
lit++;
|
|
const unsigned char c = v ? 255 : 0;
|
|
fputc(c, fp); fputc(c, fp); fputc(c, fp);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
osd_printf_info("vt1200: dumped frame %ld -> %s (%dx%d, %lu lit = %.4f%%)\n",
|
|
m_dump_frame, path, w, h, lit, 100.0 * lit / ((double)w * h));
|
|
}
|
|
|
|
uint32_t vt1200_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
|
|
{
|
|
{
|
|
static int calls = 0;
|
|
if (calls++ < 4)
|
|
osd_printf_info("vt1200: screen_update#%d frame=%u clip_y=%d..%d screen=%dx%d\n",
|
|
calls, (unsigned)screen.frame_number(), cliprect.min_y, cliprect.max_y,
|
|
screen.width(), screen.height());
|
|
}
|
|
|
|
// Let the TMS34010 display controller do the rendering.
|
|
const uint32_t changed = m_maincpu->tms340x0_ind16(screen, bitmap, cliprect);
|
|
|
|
// Dump once the whole frame has been rendered (the video manager calls us
|
|
// per region, so wait for the region that reaches the bottom line).
|
|
if (cliprect.max_y >= screen.height() - 1)
|
|
{
|
|
static int guards = 0;
|
|
if (guards++ < 5)
|
|
osd_printf_info("vt1200: guard#%d frame=%u dump_frame=%ld dump_ram=%ld\n",
|
|
guards, (unsigned)screen.frame_number(), m_dump_frame, m_dump_ram);
|
|
if (m_dump_frame >= 0 && (long)screen.frame_number() == m_dump_frame)
|
|
dump_frame(screen, bitmap);
|
|
if (m_dump_ram >= 0 && !m_dumped_ram && (long)screen.frame_number() >= m_dump_ram)
|
|
{
|
|
m_dumped_ram = true;
|
|
dump_ram();
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void vt1200_state::dump_ram()
|
|
{
|
|
char path[1024];
|
|
snprintf(path, sizeof(path), "%s/vt1200-ram-%ld.bin", m_dump_dir.c_str(), m_dump_ram);
|
|
FILE *fp = fopen(path, "wb");
|
|
if (!fp)
|
|
{
|
|
osd_printf_info("vt1200: cannot write %s\n", path);
|
|
return;
|
|
}
|
|
address_space &sp = m_maincpu->space(AS_PROGRAM);
|
|
(void)sp;
|
|
const u8 *base = reinterpret_cast<const u8 *>(m_mainram.target());
|
|
const size_t n = m_mainram.bytes();
|
|
fwrite(base, 1, n, fp);
|
|
fclose(fp);
|
|
osd_printf_info("vt1200: dumped %zu bytes of main RAM (frame %ld) -> %s\n",
|
|
n, m_dump_ram, path);
|
|
}
|
|
|
|
void vt1200_state::vt1200(machine_config &config)
|
|
{
|
|
// Clocks, derived from the real machine's 1280x1024 @ 72 Hz mono display and
|
|
// the firmware's own raster registers (HTOTAL=79, VTOTAL=900 observed):
|
|
//
|
|
// HTOTAL = 79 -> 80 pixel-clocks per line, and 80 * 16 = 1280 pixels,
|
|
// so the TMS34010 fetches SIXTEEN pixels per clock.
|
|
// refresh = pixel_clock / ((HTOTAL+1) * (VTOTAL+1))
|
|
// = pixel_clock / 72000 -> 72 Hz => pixel_clock = 5.184 MHz
|
|
//
|
|
// The CPU is a TMS34010FNL-50, i.e. 50 MHz (per the TMS34010 users guide and
|
|
// the wiki's part number).
|
|
TMS34010(config, m_maincpu, XTAL(50'000'000));
|
|
m_maincpu->set_addrmap(AS_PROGRAM, &vt1200_state::program_map);
|
|
m_maincpu->set_pixel_clock(5'184'000);
|
|
m_maincpu->set_pixels_per_clock(16);
|
|
m_maincpu->set_scanline_ind16_callback(FUNC(vt1200_state::scanline));
|
|
m_maincpu->set_screen("screen");
|
|
|
|
SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
|
|
// Must be consistent with pixels_per_clock=16, or the renderer (which
|
|
// computes heblnk = HEBLNK * 16) overruns the bitmap and corrupts the heap.
|
|
// The device reconfigures this from the firmware's registers anyway.
|
|
m_screen->set_raw(5'184'000, 1280, 0, 1280, 901, 0, 901);
|
|
m_screen->set_screen_update(FUNC(vt1200_state::screen_update));
|
|
m_screen->set_palette(m_palette);
|
|
|
|
// 256 entries so the TMS34010 renderer can encode multi-bit pixels; the
|
|
// terminal's real output stage is a later question.
|
|
PALETTE(config, m_palette).set_entries(256);
|
|
}
|
|
|
|
INPUT_PORTS_START(vt1200)
|
|
INPUT_PORTS_END
|
|
|
|
ROM_START(vt1200)
|
|
ROM_REGION(0x1c0000, "maincpu", 0)
|
|
ROM_LOAD("vt1200_v22.bin", 0x000000, 0x1c0000,
|
|
CRC(29448f23) SHA1(c2d65bce2c34701b53006f16ccba33e0adbbfd67))
|
|
ROM_END
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
// year, name, parent, compat, machine, input, class, init, company, fullname
|
|
CONS(1990, vt1200, 0, 0, vt1200, vt1200, vt1200_state, empty_init, "DEC",
|
|
"VT1200 X terminal (v2.2 firmware)", MACHINE_NOT_WORKING | MACHINE_NO_SOUND)
|