summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
author AJR <ajrhacker@users.noreply.github.com>2020-01-10 09:00:28 -0500
committer AJR <ajrhacker@users.noreply.github.com>2020-01-10 09:00:28 -0500
commit2b4c203d88e672e7c117d6266c5bb6ed7ef5bd00 (patch)
tree4731d5183f0e3ca357864511d3a531131750cffd /src
parenta7e88ab526f435061d15bd822a638eabe5a83fc7 (diff)
vt52: Draw characters on screen; add notes about processor architecture
vt50dasm: Slightly smarter jump decoding; note another (unemulated) VT52 difference (nw)
Diffstat (limited to 'src')
-rw-r--r--src/devices/cpu/vt50/vt50.cpp155
-rw-r--r--src/devices/cpu/vt50/vt50.h16
-rw-r--r--src/devices/cpu/vt50/vt50dasm.cpp26
-rw-r--r--src/devices/cpu/vt50/vt50dasm.h3
-rw-r--r--src/mame/drivers/vt52.cpp24
5 files changed, 203 insertions, 21 deletions
diff --git a/src/devices/cpu/vt50/vt50.cpp b/src/devices/cpu/vt50/vt50.cpp
index d2ed58dde79..b12b75e79da 100644
--- a/src/devices/cpu/vt50/vt50.cpp
+++ b/src/devices/cpu/vt50/vt50.cpp
@@ -2,13 +2,69 @@
// copyright-holders:AJR
/***************************************************************************
- DEC VT50/VT52 CPU skeleton
+ DEC VT50/VT52 microprocessor emulation
+
+ The principal components of this custom TTL-based processor are
+ inelegantly divided between two PCBs: ROM, UART & Timing (RUT) and
+ Data Paths, Memory & Decoders. The UART present on the former board
+ is not included in this CPU emulation, which uses callbacks instead
+ (as for the keyboard, which is a separate component in the same case).
+ Opcodes may contain up to four instructions each, which are executed
+ sequentially during defined phases of the instruction cycle.
+
+ The machine cycle time (each instruction takes two cycles) is also the
+ time it takes to display one character. RAM addresses are determined
+ by the contents of the X and Y registers (plus one XOR gate applied to
+ bit 3 of the X output) for both displayed characters and programmed
+ data transfers. During non-blanked portions of of horizontal lines, X
+ (but not Y) is automatically incremented as each character is latched,
+ with the lowest 3 bits of the accumulator defining the character scan
+ line. The firmware uses the tail end of RAM as its scratchpad area.
+
+ The accumulator, X and Y registers are mostly implemented as 74193
+ up/down counters. There is no proper ALU, only a 7485 magnitude
+ comparator and an 8242 equality checker whose output is also used to
+ establish the position of the underline cursor.
+
+ RAM is 7 bits wide, even though the VT50's character generator can
+ only accept 6 bits. Most of the registers are also effectively 7 bits
+ wide, though unused eighth bits are physically present. PC is also
+ physically 12 bits wide, though only up to 10 bits are usable. Y is
+ only 4 bits wide on the VT50, which has a 12-line display; in order
+ to double the quantity of addressable RAM to allow for 24 lines, the
+ VT52 adds an extra flip-flop stage to Y and rejumpers the address
+ encoding circuit.
+
+ The mode flip-flop changes the meanings of the jump conditions and the
+ function of the constant load instruction, whose execution in mode 0
+ is conditioned on equality with the preincremented accumulator. Jumps,
+ if taken, load the highest two bits of the destination (which define
+ the ROM page) from a ripple counter whose value may be incremented by
+ the IROM instruction.
+
+ The done flip-flop is set any time data is committed to memory. Its
+ purpose is to ensure that only one in a sequence of consecutive
+ load instructions in the firmware's keyboard lookup routine is
+ actually executed.
+
+ While horizontal blanking is defined in hardware as 20 characters out
+ of every 100, vertical blanking periods are arbitrarily determined by
+ when the firmware decides to deactivate the video flip-flop, which
+ necessitates an awkward workaround since MAME's screen emulation
+ expects a definite value. The vertical and horizontal synchronization
+ pulses are also generated without regard to each other, which causes
+ the screen refresh period to be 256 lines in 60 Hz mode and 307.2
+ lines in 50 Hz mode. The unorthodox split structure of the timing
+ chain permits it to double as a baud rate generator.
***************************************************************************/
#include "emu.h"
#include "vt50.h"
#include "vt50dasm.h"
+#include "screen.h"
+
+#define FIND_FIRST_LINE 0
// device type definitions
DEFINE_DEVICE_TYPE(VT50_CPU, vt50_cpu_device, "vt50_cpu", "DEC VT50 CPU")
@@ -16,6 +72,7 @@ DEFINE_DEVICE_TYPE(VT52_CPU, vt52_cpu_device, "vt52_cpu", "DEC VT52 CPU")
vt5x_cpu_device::vt5x_cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int bbits, int ybits)
: cpu_device(mconfig, type, tag, owner, clock)
+ , device_video_interface(mconfig, *this)
, m_rom_config("program", ENDIANNESS_LITTLE, 8, 10, 0)
, m_ram_config("data", ENDIANNESS_LITTLE, 8, 6 + ybits, 0) // actually 7 bits wide
, m_rom_cache(nullptr)
@@ -34,6 +91,7 @@ vt5x_cpu_device::vt5x_cpu_device(const machine_config &mconfig, device_type type
, m_cen_callback(*this)
, m_csf_callback(*this)
, m_ccf_callback(*this)
+ , m_char_data_callback(*this)
, m_bbits(bbits)
, m_ybits(ybits)
, m_pc(0)
@@ -59,6 +117,8 @@ vt5x_cpu_device::vt5x_cpu_device(const machine_config &mconfig, device_type type
, m_horiz_count(0)
, m_vert_count(0)
, m_top_of_screen(false)
+ , m_current_line(0)
+ , m_first_line(~0)
{
m_rom_config.m_is_octal = true;
m_ram_config.m_is_octal = true;
@@ -93,6 +153,18 @@ device_memory_interface::space_config_vector vt5x_cpu_device::memory_space_confi
};
}
+void vt5x_cpu_device::device_config_complete()
+{
+ if (!has_screen())
+ return;
+
+ if (!screen().has_screen_update())
+ screen().set_screen_update(*this, FUNC(vt5x_cpu_device::screen_update));
+
+ if (!screen().refresh_attoseconds())
+ screen().set_raw(clock(), 900, 128, 848, 256, 4, 244); // 60 Hz default parameters
+}
+
void vt5x_cpu_device::device_resolve_objects()
{
// resolve callbacks
@@ -110,6 +182,7 @@ void vt5x_cpu_device::device_resolve_objects()
m_cen_callback.resolve_safe();
m_csf_callback.resolve_safe(1);
m_ccf_callback.resolve_safe(1);
+ m_char_data_callback.resolve_safe(0177);
}
void vt52_cpu_device::device_resolve_objects()
@@ -125,6 +198,7 @@ void vt5x_cpu_device::device_start()
m_rom_cache = space(AS_PROGRAM).cache<0, 0, ENDIANNESS_LITTLE>();
m_ram_cache = space(AS_DATA).cache<0, 0, ENDIANNESS_LITTLE>();
+ screen().register_screen_bitmap(m_bitmap);
set_icountptr(m_icount);
state_add(VT5X_PC, "PC", m_pc).formatstr("%04O").mask(01777);
@@ -168,6 +242,12 @@ void vt5x_cpu_device::device_start()
save_item(NAME(m_horiz_count));
save_item(NAME(m_vert_count));
save_item(NAME(m_top_of_screen));
+ save_item(NAME(m_current_line));
+#if FIND_FIRST_LINE
+ save_item(NAME(m_first_line));
+#else
+ (void)m_first_line;
+#endif
}
void vt5x_cpu_device::device_reset()
@@ -184,11 +264,45 @@ void vt5x_cpu_device::device_reset()
m_horiz_count = 0;
m_vert_count = 0;
m_top_of_screen = true;
+ m_current_line = 0;
m_baud_9600_callback(0);
m_vert_count_callback(0);
}
+u32 vt5x_cpu_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
+{
+ copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
+ return 0;
+}
+
+void vt5x_cpu_device::draw_char_line()
+{
+ if (m_current_line < screen().visible_area().top() || m_current_line > screen().visible_area().bottom())
+ return;
+
+ u8 hc = (u8(m_horiz_count) >> 4) * 10 + (m_horiz_count & 15);
+ unsigned xc = ((hc >= 22 ? hc : hc + 100) - 22) * 9 + screen().visible_area().left();
+ if (xc > screen().visible_area().right() - 8)
+ return;
+
+ u32 *pix = &m_bitmap.pix32(m_current_line, xc);
+ if (m_video_process && m_cursor_ff && m_cursor_active)
+ std::fill_n(pix, 9, rgb_t::white());
+ else if (!m_video_process || m_cursor_ff)
+ std::fill_n(pix, 9, rgb_t::black());
+ else
+ {
+ // CD6 is first shifted out; CD0 is last out
+ u8 vsr = m_char_data_callback(u16(m_ram_do) << 3 | (m_ac & 7)) | 0200;
+ for (int i = 0; i < 9; i++)
+ {
+ *pix++ = BIT(vsr, 7) ? rgb_t::black() : rgb_t::white();
+ vsr = (vsr << 1) | 1;
+ }
+ }
+}
+
offs_t vt5x_cpu_device::translate_xy() const
{
// A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
@@ -525,7 +639,8 @@ void vt5x_cpu_device::execute_th(u8 inst)
break;
case 0120:
- // TRUJ
+ // M0: COPJ (TODO?)
+ // M1: TRUJ
m_load_pc = true;
break;
@@ -599,7 +714,27 @@ void vt5x_cpu_device::clock_video_counters()
m_horiz_count++;
if (m_horiz_count == 8)
{
- m_top_of_screen = false;
+ if (m_top_of_screen)
+ {
+ m_top_of_screen = false;
+
+ // This calculates the number of visible lines, which is actually firmware-defined.
+ bool is_60hz = BIT(m_vert_count, 9);
+ unsigned first_line = is_60hz ? 4 : 32;
+ screen().configure(
+ 900,
+ (010000 - m_vert_count) / 10,
+ rectangle(128, 847, first_line, 24 * (is_60hz ? 10 : 11) + first_line - 1),
+ clocks_to_attotime((010000 - m_vert_count) * 90).as_attoseconds()
+ );
+ screen().reset_origin();
+ m_current_line = 0;
+#if FIND_FIRST_LINE
+ m_first_line = ~0;
+#endif
+ }
+ else
+ m_current_line++;
m_baud_9600_callback(0);
}
else if (m_horiz_count == 4)
@@ -632,8 +767,18 @@ void vt5x_cpu_device::execute_run()
if (!m_write_ff)
m_ram_do = m_ram_cache->read_byte(translate_xy()) & 0177;
m_cursor_active = m_ac == (m_x ^ (m_x8 ? 8 : 0));
- if (m_video_process && u8(m_horiz_count - 2) >= 2 * 16)
- m_x = (m_x + 1) & 0177;
+ if (u8(m_horiz_count - 2) >= 2 * 16)
+ {
+ if (m_video_process)
+ {
+#if FIND_FIRST_LINE
+ if (m_first_line > m_current_line)
+ m_first_line = m_current_line;
+#endif
+ m_x = (m_x + 1) & 0177;
+ }
+ draw_char_line();
+ }
m_t = 3;
break;
diff --git a/src/devices/cpu/vt50/vt50.h b/src/devices/cpu/vt50/vt50.h
index 206dfe86488..fd73d1fd83e 100644
--- a/src/devices/cpu/vt50/vt50.h
+++ b/src/devices/cpu/vt50/vt50.h
@@ -6,7 +6,7 @@
#pragma once
-class vt5x_cpu_device : public cpu_device
+class vt5x_cpu_device : public cpu_device, public device_video_interface
{
public:
enum {
@@ -31,12 +31,17 @@ public:
auto cen_callback() { return m_cen_callback.bind(); }
auto ccf_callback() { return m_ccf_callback.bind(); }
auto csf_callback() { return m_csf_callback.bind(); }
+ auto char_data_callback() { return m_char_data_callback.bind(); }
+
+ // screen update method
+ u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
protected:
// construction/destruction
vt5x_cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int bbits, int ybits);
// device-level overrides
+ virtual void device_config_complete() override;
virtual void device_resolve_objects() override;
virtual void device_start() override;
virtual void device_reset() override;
@@ -50,6 +55,9 @@ protected:
// device_state_interface overrides
virtual void state_string_export(const device_state_entry &entry, std::string &str) const override;
+ // video helpers
+ void draw_char_line();
+
// address translation
offs_t translate_xy() const;
@@ -83,6 +91,7 @@ protected:
devcb_write_line m_cen_callback;
devcb_read_line m_csf_callback;
devcb_read_line m_ccf_callback;
+ devcb_read8 m_char_data_callback;
// register dimensions
const u8 m_bbits;
@@ -116,6 +125,11 @@ protected:
u8 m_horiz_count;
u16 m_vert_count;
bool m_top_of_screen;
+ u16 m_current_line;
+ u16 m_first_line;
+
+ // display bitmap
+ bitmap_rgb32 m_bitmap;
};
class vt50_cpu_device : public vt5x_cpu_device
diff --git a/src/devices/cpu/vt50/vt50dasm.cpp b/src/devices/cpu/vt50/vt50dasm.cpp
index 84e9f231fb3..5f97d1eb1a9 100644
--- a/src/devices/cpu/vt50/vt50dasm.cpp
+++ b/src/devices/cpu/vt50/vt50dasm.cpp
@@ -34,7 +34,7 @@ const char *const vt5x_disassembler::s_opcodes_e[8] = {
};
const char *const vt5x_disassembler::s_opcodes_f[8] = {
- "DXDY", "IA", "IA1", "IY", "DY", "IROM", "DX", "DA"
+ "DXDY", "IA", "IA1", "IY", "DY", "IROM", "DX", "DA" // IA is functionally duplicated
};
const char *const vt50_disassembler::s_opcodes_g[8] = {
@@ -45,11 +45,16 @@ const char *const vt52_disassembler::s_opcodes_g[8] = {
"M2A", "A2M", "M2U", "B2M", "M2X", "U2M", "M2B", "GRPH"
};
-const char *const vt5x_disassembler::s_jumps_h[2][8] = {
+const char *const vt50_disassembler::s_jumps_h[2][8] = {
{ "PSC", "TAB", "KCL", "FRQ", "PRQ", "TRU", "UT", "TOS" }, // mode 0
{ "UR", "AEM", "ALM", "ADX", "AEM2", nullptr, "VSC", "KEY" } // mode 1
};
+const char *const vt52_disassembler::s_jumps_h[2][8] = {
+ { "PSC", "TAB", "KCL", "FRQ", "PRQ", "COP", "UT", "TOS" }, // mode 0
+ { "UR", "AEM", "ALM", "ADX", "AEM2", "TRU", "VSC", "KEY" } // mode 1
+};
+
const char *const vt5x_disassembler::s_opcodes_w[8] = {
"SCFF", "SVID", "B2Y", "CBFF", "ZCAV", "LPB", "EPR", "HPR!ZY"
};
@@ -99,12 +104,21 @@ offs_t vt5x_disassembler::disassemble(std::ostream &stream, offs_t pc, const vt5
{
if (!first)
stream << "!";
- util::stream_format(stream, "%sJ", m_jumps_h[0][(opcode & 0160) >> 4]);
- if (m_jumps_h[1][(opcode & 0160) >> 4] != nullptr)
- util::stream_format(stream, "/%sJ", m_jumps_h[1][(opcode & 0160) >> 4]);
+ bool m0 = (opcode & 0170) != 0130;
+ bool m1 = (opcode & 0170) != 0170;
+ if (m_jumps_h[1][(opcode & 0160) >> 4] == nullptr)
+ m0 = std::exchange(m1, false);
+ if (m0)
+ {
+ util::stream_format(stream, "%sJ", m_jumps_h[0][(opcode & 0160) >> 4]);
+ if (m1)
+ stream << "/";
+ }
+ if (m1)
+ util::stream_format(stream, "%sJ", m_jumps_h[1][(opcode & 0160) >> 4]);
u16 nextpc = pc + 1;
- if ((opcode & 0164) == 0124) // IROM!TRUJ adjustment
+ if ((opcode & 0164) == 0124) // IROM adjustment
nextpc += 0400;
util::stream_format(stream, " %04o", (nextpc & 01400) | opcodes.r8(pc + 1));
return 2;
diff --git a/src/devices/cpu/vt50/vt50dasm.h b/src/devices/cpu/vt50/vt50dasm.h
index ce9b16a3d06..163598c133c 100644
--- a/src/devices/cpu/vt50/vt50dasm.h
+++ b/src/devices/cpu/vt50/vt50dasm.h
@@ -18,7 +18,6 @@ protected:
// tables
static const char *const s_opcodes_e[8];
static const char *const s_opcodes_f[8];
- static const char *const s_jumps_h[2][8];
static const char *const s_opcodes_w[8];
private:
@@ -38,6 +37,7 @@ public:
private:
// tables
static const char *const s_opcodes_g[8];
+ static const char *const s_jumps_h[2][8];
};
class vt52_disassembler : public vt5x_disassembler
@@ -49,6 +49,7 @@ public:
private:
// tables
static const char *const s_opcodes_g[8];
+ static const char *const s_jumps_h[2][8];
};
#endif // MAME_CPU_VT50_VT50DASM_H
diff --git a/src/mame/drivers/vt52.cpp b/src/mame/drivers/vt52.cpp
index 110c7ebb03e..254fb36c932 100644
--- a/src/mame/drivers/vt52.cpp
+++ b/src/mame/drivers/vt52.cpp
@@ -14,8 +14,8 @@
character generator (on a board of its own). VT50 and VT52 each had
minor variants differing in keyboard function and printer availability.
- The VT55 was a graphical terminal ostensibly in the same family as the
- VT50 and VT52. Its hardware commonalities and differences are unknown.
+ The VT55 DECgraphic Scope was a graphical terminal based on the same
+ main boards as the VT50 and VT52.
****************************************************************************/
@@ -37,6 +37,7 @@ public:
, m_keys(*this, "KEY%d", 0U)
, m_baud_sw(*this, "BAUD")
, m_data_sw(*this, "DATABITS")
+ , m_chargen(*this, "chargen")
{
}
@@ -56,6 +57,7 @@ private:
void uart_xd_w(u8 data);
DECLARE_WRITE_LINE_MEMBER(serial_out_w);
DECLARE_READ_LINE_MEMBER(xrdy_eoc_r);
+ u8 chargen_r(offs_t offset);
void rom_1k(address_map &map);
void ram_2k(address_map &map);
@@ -65,6 +67,7 @@ private:
required_ioport_array<8> m_keys;
required_ioport m_baud_sw;
required_ioport m_data_sw;
+ required_region_ptr<u8> m_chargen;
};
void vt52_state::machine_reset()
@@ -150,6 +153,12 @@ READ_LINE_MEMBER(vt52_state::xrdy_eoc_r)
return m_uart->tbmt_r() || m_uart->eoc_r();
}
+u8 vt52_state::chargen_r(offs_t offset)
+{
+ // ROM is on its own board, shared only with 7404 inverters
+ return ~m_chargen[offset];
+}
+
void vt52_state::rom_1k(address_map &map)
{
map(00000, 01777).rom().region("program", 0);
@@ -306,6 +315,7 @@ void vt52_state::vt52(machine_config &mconfig)
VT52_CPU(mconfig, m_maincpu, 13.824_MHz_XTAL);
m_maincpu->set_addrmap(AS_PROGRAM, &vt52_state::rom_1k);
m_maincpu->set_addrmap(AS_DATA, &vt52_state::ram_2k);
+ m_maincpu->set_screen("screen");
m_maincpu->baud_9600_callback().set(FUNC(vt52_state::baud_9600_w));
m_maincpu->vert_count_callback().set(FUNC(vt52_state::vert_count_w));
m_maincpu->uart_rd_callback().set(m_uart, FUNC(ay51013_device::receive));
@@ -317,14 +327,12 @@ void vt52_state::vt52(machine_config &mconfig)
m_maincpu->kclk_callback().set_ioport("KEYCLICK");
m_maincpu->frq_callback().set_ioport("60HJ");
m_maincpu->bell_callback().set("bell", FUNC(speaker_sound_device::level_w));
+ m_maincpu->char_data_callback().set(FUNC(vt52_state::chargen_r));
AY51013(mconfig, m_uart); // TR1402 or equivalent
m_uart->write_so_callback().set(FUNC(vt52_state::serial_out_w));
- screen_device &screen(SCREEN(mconfig, "screen", SCREEN_TYPE_RASTER));
- screen.set_raw(13.824_MHz_XTAL, 900, 0, 720, 256, 0, 240);
- //screen.set_raw(13.824_MHz_XTAL, 900, 0, 720, 307.2, 0, 264); // not a whole number of scans
- screen.set_screen_update(FUNC(vt52_state::screen_update));
+ SCREEN(mconfig, "screen", SCREEN_TYPE_RASTER);
SPEAKER(mconfig, "mono").front_center();
SPEAKER_SOUND(mconfig, "bell").add_route(ALL_OUTPUTS, "mono", 1.0); // FIXME: uses a flyback diode circuit
@@ -337,8 +345,8 @@ ROM_START(vt52)
ROM_LOAD_NIB_LOW( "23-126a9.e37", 0x200, 0x200, CRC(4883a600) SHA1(c5d9b0c21493065c75b4a7d52d5bd47f9851dfe7))
ROM_LOAD_NIB_HIGH("23-127a9.e21", 0x200, 0x200, CRC(56c1c0d6) SHA1(ab0eb6e7bbafcc3d28481b62de3d3490f01c0174))
- ROM_REGION(0x400, "chargen", 0) // 2608 character generator
+ ROM_REGION(0x400, "chargen", 0) // 2608 (non-JEDEC) character generator
ROM_LOAD("23-002b4.e1", 0x000, 0x400, CRC(b486500c) SHA1(029f07424d6c23ee083db42d9f9c252ac728ccd0))
ROM_END
-COMP(1975, vt52, 0, 0, vt52, vt52, vt52_state, empty_init, "Digital Equipment Corporation", "VT52 Video Display Terminal", MACHINE_NOT_WORKING | MACHINE_IMPERFECT_SOUND)
+COMP(1975, vt52, 0, 0, vt52, vt52, vt52_state, empty_init, "Digital Equipment Corporation", "VT52 Video Display Terminal", MACHINE_NOT_WORKING | MACHINE_IMPERFECT_SOUND | MACHINE_NODEVICE_PRINTER)