summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/vt50/vt50.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/vt50/vt50.cpp')
-rw-r--r--src/devices/cpu/vt50/vt50.cpp155
1 files changed, 150 insertions, 5 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;