From 6ab7c6949f95c5a70a97cde3415c4228c939665f Mon Sep 17 00:00:00 2001 From: fulivi Date: Tue, 5 Dec 2017 04:34:30 +0100 Subject: [PATCH] HLE of HP9845 internal printer (#2884) * hp9845 & hp64k: fixed breakage caused by c46e1007a8 * hp9845: HLE of internal printer added * hp9845: fix to printer interrupt line * hp9845: moved src/devices/machine/hp9845_printer.* to src/mame/machine --- scripts/target/mame/mess.lua | 2 + src/devices/cpu/hphybrid/hphybrid.cpp | 8 +- src/mame/drivers/hp9845.cpp | 41 +- src/mame/includes/hp9845.h | 8 +- src/mame/machine/hp9845_printer.cpp | 971 ++++++++++++++++++++++++++ src/mame/machine/hp9845_printer.h | 134 ++++ 6 files changed, 1156 insertions(+), 8 deletions(-) create mode 100644 src/mame/machine/hp9845_printer.cpp create mode 100644 src/mame/machine/hp9845_printer.h diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index 93fd799bfb1..3814eb5d019 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -2132,6 +2132,8 @@ files { MAME_DIR .. "src/mame/drivers/hp48.cpp", MAME_DIR .. "src/mame/includes/hp48.h", MAME_DIR .. "src/mame/machine/hp48.cpp", + MAME_DIR .. "src/mame/machine/hp9845_printer.cpp", + MAME_DIR .. "src/mame/machine/hp9845_printer.h", MAME_DIR .. "src/mame/video/hp48.cpp", MAME_DIR .. "src/mame/drivers/hp49gp.cpp", MAME_DIR .. "src/mame/drivers/hp9845.cpp", diff --git a/src/devices/cpu/hphybrid/hphybrid.cpp b/src/devices/cpu/hphybrid/hphybrid.cpp index 57bc1411186..f85fd4b9a06 100644 --- a/src/devices/cpu/hphybrid/hphybrid.cpp +++ b/src/devices/cpu/hphybrid/hphybrid.cpp @@ -1025,9 +1025,9 @@ void hp_hybrid_cpu_device::do_pw(uint16_t opcode) } WM(tmp_addr >> 1 , tmp); } else { - // Extend address - uint16_t val = (tmp_addr & 1) ? uint8_t(tmp) << 8 : uint8_t(tmp); - uint16_t mask = (tmp_addr & 1) ? 0xff00 : 0x00ff; + // Extend address, form byte address + uint16_t val = (tmp_addr & 1) ? uint8_t(tmp) : (tmp << 8); + uint16_t mask = (tmp_addr & 1) ? 0x00ff : 0xff00; tmp_addr = add_mae(AEC_CASE_C , tmp_addr >> 1); m_program->write_word(tmp_addr , val, mask); } @@ -1130,7 +1130,7 @@ uint16_t hp_hybrid_cpu_device::RIO(uint8_t pa , uint8_t ic) void hp_hybrid_cpu_device::WIO(uint8_t pa , uint8_t ic , uint16_t v) { - m_io->write_word(HP_MAKE_IOADDR(pa, ic), v); + m_io->write_word(HP_MAKE_IOADDR(pa, ic) , v); } hp_5061_3001_cpu_device::hp_5061_3001_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) diff --git a/src/mame/drivers/hp9845.cpp b/src/mame/drivers/hp9845.cpp index d66adef8eef..b104ef1e7b7 100644 --- a/src/mame/drivers/hp9845.cpp +++ b/src/mame/drivers/hp9845.cpp @@ -25,6 +25,7 @@ // - Correct character generator ROMs (a huge "thank you" to Ansgar Kueckes for the dumps!) // - 98775 light pen controller // - Display softkeys on 45C & 45T +// - HLE of integral printer // What's not yet in: // - Better naming of tape drive image (it's now "magt1" and "magt2", should be "t15" and "t14") // - Better documentation of this file @@ -32,7 +33,6 @@ // - Speed, as usual // - Light pen tracing sometimes behaves erratically in 45C and 45T // What will probably never be in: -// - Integral printer (firmware and character generator ROMs are very difficult to dump) // - Fast LPU processor (dump of microcode PROMs is not available) #include "emu.h" @@ -48,6 +48,7 @@ #include "hp9845b.lh" +#include "machine/hp9845_printer.h" // Debugging #define VERBOSE 0 @@ -187,6 +188,7 @@ constexpr unsigned LP_FOV = 9; // Field of view constexpr unsigned LP_XOFFSET = 5; // x-offset of LP (due to delay in hit recognition) // Peripheral Addresses (PA) +#define PRINTER_PA 0 #define IO_SLOT_FIRST_PA 1 #define IO_SLOT_LAST_PA 12 #define GVIDEO_PA 13 @@ -523,6 +525,8 @@ void hp9845_base_state::machine_reset() m_beeper->set_state(0); + m_prt_irl = false; + logerror("STS=%04x FLG=%04x\n" , m_sts_status , m_flg_status); } @@ -708,8 +712,8 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9845_base_state::kb_scan) // Key pressed, store scancode & generate IRL //logerror("idx=%u msl=%d\n" , max_seq_idx , max_seq_len); m_kb_scancode = max_seq_idx; - irq_w(0 , 1); BIT_SET(m_kb_status, 0); + update_kb_prt_irq(); // Special case: pressing stop key sets LPU "status" flag if (max_seq_idx == 0x47) { @@ -732,8 +736,8 @@ READ16_MEMBER(hp9845_base_state::kb_status_r) WRITE16_MEMBER(hp9845_base_state::kb_irq_clear_w) { - irq_w(0 , 0); BIT_CLR(m_kb_status, 0); + update_kb_prt_irq(); m_lpu->status_w(0); if (BIT(data , 15)) { @@ -743,6 +747,12 @@ WRITE16_MEMBER(hp9845_base_state::kb_irq_clear_w) } } +void hp9845_base_state::update_kb_prt_irq() +{ + bool state = BIT(m_kb_status , 0) || m_prt_irl; + irq_w(0 , state); +} + TIMER_DEVICE_CALLBACK_MEMBER(hp9845_base_state::beeper_off) { m_beeper->set_state(0); @@ -756,6 +766,22 @@ WRITE8_MEMBER(hp9845_base_state::pa_w) } } +WRITE_LINE_MEMBER(hp9845_base_state::prt_irl_w) +{ + m_prt_irl = state; + update_kb_prt_irq(); +} + +WRITE_LINE_MEMBER(hp9845_base_state::prt_flg_w) +{ + flg_w(PRINTER_PA , state); +} + +WRITE_LINE_MEMBER(hp9845_base_state::prt_sts_w) +{ + sts_w(PRINTER_PA , state); +} + WRITE_LINE_MEMBER(hp9845_base_state::t14_irq_w) { irq_w(T14_PA , state); @@ -3695,6 +3721,9 @@ ADDRESS_MAP_END static ADDRESS_MAP_START(ppu_io_map , AS_IO , 16 , hp9845_base_state) ADDRESS_MAP_UNMAP_LOW + // PA = 0, IC = 0..1 + // Internal printer + AM_RANGE(HP_MAKE_IOADDR(PRINTER_PA , 0) , HP_MAKE_IOADDR(PRINTER_PA , 1)) AM_DEVREADWRITE("printer" , hp9845_printer_device , printer_r , printer_w) // PA = 0, IC = 2 // Keyboard scancode input AM_RANGE(HP_MAKE_IOADDR(0 , 2) , HP_MAKE_IOADDR(0 , 2)) AM_READ(kb_scancode_r) @@ -3791,6 +3820,12 @@ static MACHINE_CONFIG_START(hp9845_base) MCFG_RAM_ADD(RAM_TAG) MCFG_RAM_DEFAULT_SIZE("192K") MCFG_RAM_EXTRA_OPTIONS("64K, 320K, 448K") + + // Internal printer + MCFG_DEVICE_ADD("printer" , HP9845_PRINTER , 0) + MCFG_9845PRT_IRL_HANDLER(WRITELINE(hp9845_base_state , prt_irl_w)) + MCFG_9845PRT_FLG_HANDLER(WRITELINE(hp9845_base_state , prt_flg_w)) + MCFG_9845PRT_STS_HANDLER(WRITELINE(hp9845_base_state , prt_sts_w)) MACHINE_CONFIG_END static MACHINE_CONFIG_START(hp9845b) diff --git a/src/mame/includes/hp9845.h b/src/mame/includes/hp9845.h index 52663f376f0..60f09aa5e7d 100644 --- a/src/mame/includes/hp9845.h +++ b/src/mame/includes/hp9845.h @@ -47,6 +47,9 @@ public: DECLARE_WRITE8_MEMBER(pa_w); + DECLARE_WRITE_LINE_MEMBER(prt_irl_w); + DECLARE_WRITE_LINE_MEMBER(prt_flg_w); + DECLARE_WRITE_LINE_MEMBER(prt_sts_w); DECLARE_WRITE_LINE_MEMBER(t14_irq_w); DECLARE_WRITE_LINE_MEMBER(t14_flg_w); DECLARE_WRITE_LINE_MEMBER(t14_sts_w); @@ -55,7 +58,6 @@ public: DECLARE_WRITE_LINE_MEMBER(t15_sts_w); DECLARE_INPUT_CHANGED_MEMBER(togglekey_changed); - protected: required_device m_lpu; required_device m_ppu; @@ -81,6 +83,7 @@ protected: virtual void advance_gv_fsm(bool ds , bool trigger) = 0; void kb_scan_ioport(ioport_value pressed , ioport_port *port , unsigned idx_base , int& max_seq_len , unsigned& max_seq_idx); + void update_kb_prt_irq(); // Character generator required_region_ptr m_chargen; @@ -141,6 +144,9 @@ protected: ioport_value m_kb_state[ 4 ]; uint8_t m_kb_scancode; uint16_t m_kb_status; + + // Printer + bool m_prt_irl; }; #endif /* _HP9845_H_ */ diff --git a/src/mame/machine/hp9845_printer.cpp b/src/mame/machine/hp9845_printer.cpp new file mode 100644 index 00000000000..3741b737552 --- /dev/null +++ b/src/mame/machine/hp9845_printer.cpp @@ -0,0 +1,971 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp9845_printer.cpp + + HP9845 internal printer HLE + + The internal printer of HP9845-series machines is a thermal + printer. It prints by heating the resistors in a fixed line + of 560 elements. A stepper motor advances the paper (and in some + cases backs it up). + A HP nanoprocessor controls the operation of the printer. + The NP runs firmware from a 2 kB ROM and it has 256 bytes of + RAM. A second 2 kB ROM acts as character generator. + Character cells are 7 pixels wide and 12 pixels high. + Shape of characters in the character generator is "compressed" + in a peculiar way (see patent) so that the 7x12 cell fits in 8 + bytes. These 8 bytes are addressed according to a Gray code, not + a binary code, to speed up access. + Each step of the motor moves the paper by 1/154 of inch. Each + line in a normal sized character is 2 steps high. Lines of big + sized characters are 3 steps high. The horizontal size of + pixels is equal to 2 steps. + HLE is necessary because it's very difficult to dump ROMs (they're + non-standard HP parts and they're soldered on the PCB). + Content of char.gen. was reconstructed by looking at the shape + of each character on paper. + Thanks to Ansgar Kueckes for letting me run tests "remotely" on + his printer. + + Output of printer: + - bitbanger 1: raw dump of all bytes from PPU to printer + - bitbanger 2: output of printer pixels. Each line has this form: + :, where line_no is the line number (counted in + motor steps, see above) and is the 560-pixel line. Each + character of is either a space (no dot) or '*'. This + stream can be post-processed to produce a bitmapped graphic. + Each '*' is to be translated to a 2x2 black square to preserve + the 1:1 aspect-ratio of pixels. + + What's not implemented: + - A realistic simulation of printer speed + - Form Feed + - Handling of page length & top/bottom margins + - ESC & l [ST] commands (set vertical size of lines & + top margin of paper) + + References: + - US Patent 4,180,854 + - HP 09845-93000, sep 81, HP9845 BASIC programming (appendix A) + +*********************************************************************/ + +#include "emu.h" +#include "hp9845_printer.h" + +// Debugging +#define VERBOSE 0 +#include "logmacro.h" + +// Bit manipulation +namespace { + template constexpr T BIT_MASK(unsigned n) + { + return (T)1U << n; + } + + template void BIT_CLR(T& w , unsigned n) + { + w &= ~BIT_MASK(n); + } + + template void BIT_SET(T& w , unsigned n) + { + w |= BIT_MASK(n); + } + + template void COPY_BIT(bool bit , T& w , unsigned n) + { + if (bit) { + BIT_SET(w , n); + } else { + BIT_CLR(w , n); + } + } +} + +// Character constants +constexpr uint8_t CH_BS = 0x08; +constexpr uint8_t CH_TAB = 0x09; +constexpr uint8_t CH_LF = 0x0a; +constexpr uint8_t CH_CR = 0x0d; +constexpr uint8_t CH_SH_OUT = 0x0e; +constexpr uint8_t CH_SH_IN = 0x0f; +constexpr uint8_t CH_ESC = 0x1b; + +// Bits in m_attrs +constexpr unsigned ATTRS_NEW_BIT = 7; // Redefined character +constexpr unsigned ATTRS_TAB_BIT = 6; // Tab position +constexpr unsigned ATTRS_U_L_BIT = 5; // Underlined +constexpr unsigned ATTRS_BIG_BIT = 4; // Big character +constexpr uint8_t ATTRS_REDEF_NO_MASK = 0x0f; // Redefined character number +constexpr uint8_t ATTRS_NEW_MASK = BIT_MASK(ATTRS_NEW_BIT); +constexpr uint8_t ATTRS_BIG_MASK = BIT_MASK(ATTRS_BIG_BIT); +constexpr uint8_t ATTRS_U_L_MASK = BIT_MASK(ATTRS_U_L_BIT); + +// Various timing constants +constexpr unsigned MAX_PIXELS_BURNED = 62; // Maximum number of pixels burned in parallel +constexpr unsigned USEC_LT_16_PIXELS = 4960; // microseconds to burn < 16 pixels +constexpr unsigned USEC_LT_32_PIXELS = 5984; // microseconds to burn < 32 pixels +constexpr unsigned USEC_LT_48_PIXELS = 6464; // microseconds to burn < 48 pixels +constexpr unsigned USEC_GE_48_PIXELS = 6976; // microseconds to burn >= 48 pixels +constexpr unsigned USEC_1_STEP = 3456; // microseconds for 1 motor step + +// Device type definition +DEFINE_DEVICE_TYPE(HP9845_PRINTER, hp9845_printer_device, "hp9845_prt", "HP9845 internal printer") + +hp9845_printer_device::hp9845_printer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) +: device_t(mconfig , HP9845_PRINTER , tag , owner , clock), + m_irl_handler(*this), + m_flg_handler(*this), + m_sts_handler(*this), + m_prt_graph_out(*this , "prt_graphic"), + m_prt_alpha_out(*this , "prt_alpha"), + m_prt_chargen(*this , "prt_chargen") +{ +} + +ROM_START(hp9845_printer) + ROM_REGION(0x800, "prt_chargen", 0) + ROM_LOAD("1818-2687.bin", 0, 0x800, CRC(cce64de8) SHA1(1dfabd32f4bdef85f88a514bd1978b80b25a6e80)) +ROM_END + +const tiny_rom_entry *hp9845_printer_device::device_rom_region() const +{ + return ROM_NAME(hp9845_printer); +} + +MACHINE_CONFIG_MEMBER(hp9845_printer_device::device_add_mconfig) + MCFG_DEVICE_ADD("prt_alpha", BITBANGER, 0) + MCFG_DEVICE_ADD("prt_graphic", BITBANGER, 0) +MACHINE_CONFIG_END + +void hp9845_printer_device::device_start() +{ + m_irl_handler.resolve_safe(); + m_flg_handler.resolve_safe(); + m_sts_handler.resolve_safe(); + + save_item(NAME(m_display_mode)); + save_item(NAME(m_shifted)); + save_item(NAME(m_current_u_l)); + save_item(NAME(m_current_big)); + save_item(NAME(m_ibf)); + save_item(NAME(m_inten)); + save_item(NAME(m_busy)); + save_item(NAME(m_ib)); + save_item(NAME(m_pos)); + save_item(NAME(m_line)); + save_item(NAME(m_attrs)); + save_item(NAME(m_redef_count)); + save_item(NAME(m_redef_idx)); + save_item(NAME(m_redef_chars)); + save_item(NAME(m_replace_count)); + save_item(NAME(m_redef_buff)); + save_item(NAME(m_next_replace)); + save_item(NAME(m_rep_str_len)); + save_item(NAME(m_rep_str_ptr)); + save_item(NAME(m_octal_accum)); + save_item(NAME(m_fsm_state)); + save_item(NAME(m_cur_line)); + + m_timer = timer_alloc(0); +} + +void hp9845_printer_device::device_reset() +{ + state_reset(); + m_cur_line = 0; + m_inten = false; + m_ibf = false; + m_busy = false; + update_flg(); + m_sts_handler(true); +} + +void hp9845_printer_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + m_busy = false; + update_fsm(); + update_flg(); +} + +READ16_MEMBER(hp9845_printer_device::printer_r) +{ + uint16_t res = 0; + + switch (offset) { + case 0: + case 1: + // Bit 7: Interrupt enabled + // Bit 5: 1 + // Bit 1: Printer powered (1) + // Bit 0: Interrupt pending + if (m_inten) { + BIT_SET(res , 7); + } + BIT_SET(res , 5); + BIT_SET(res , 1); + if (m_inten && !m_ibf && !m_busy) { + BIT_SET(res , 0); + } + break; + + default: + break; + } + + return res; +} + +WRITE16_MEMBER(hp9845_printer_device::printer_w) +{ + switch (offset) { + case 0: + // New character + m_ib = (uint8_t)data; + m_ibf = true; + update_fsm(); + update_flg(); + break; + + case 1: + // Control word + m_inten = BIT(data , 7); + update_flg(); + break; + + default: + break; + } +} + +void hp9845_printer_device::state_reset() +{ + m_display_mode = false; + m_shifted = false; + m_current_u_l = false; + m_current_big = false; + memset(m_attrs , 0 , sizeof(m_attrs)); + memset(m_redef_buff, 0, sizeof(m_redef_buff)); + m_redef_count = 0; + m_replace_count = 0; + m_next_replace = REDEF_BUFF_LEN - 1; + m_rep_str_len = 0; + m_rep_str_ptr = 0; + start_new_line(); + m_fsm_state = FSM_NORMAL_TEXT; +} + +void hp9845_printer_device::insert_char(uint8_t ch) +{ + if (m_pos == 80) { + crlf(); + } + COPY_BIT(m_current_big , m_attrs[ m_pos ] , ATTRS_BIG_BIT); + COPY_BIT(m_current_u_l , m_attrs[ m_pos ] , ATTRS_U_L_BIT); + if (ch == '_') { + BIT_SET(m_attrs[ m_pos ] , ATTRS_U_L_BIT); + } else { + m_line[ m_pos ] = ch; + // Check for redefined characters + unsigned redef_idx; + if (is_ch_redef(ch , redef_idx)) { + m_attrs[ m_pos ] = (m_attrs[ m_pos ] & ~ATTRS_REDEF_NO_MASK) | (uint8_t)redef_idx | ATTRS_NEW_MASK; + } else { + BIT_CLR(m_attrs[ m_pos ] , ATTRS_NEW_BIT); + } + } + m_pos++; +} + +void hp9845_printer_device::start_new_line() +{ + m_pos = 0; + memset(m_line, ' ', sizeof(m_line)); + for (auto& x : m_attrs) { + x &= ~(ATTRS_NEW_MASK | ATTRS_BIG_MASK | ATTRS_U_L_MASK); + } +} + +uint8_t hp9845_printer_device::get_ch_matrix_line(const uint8_t *matrix_base , unsigned line_no , const uint8_t *seq) +{ + uint8_t res = 0; + + switch (line_no) { + case 0: + { + uint8_t b0 = matrix_base[ seq[ 0 ] ]; + if (BIT(b0 , 0)) { + res = b0 >> 1; + } + } + break; + + case 1: + { + uint8_t b3 = matrix_base[ seq[ 3 ] ]; + if (BIT(b3 , 0)) { + res = matrix_base[ seq[ 0 ] ] >> 1; + } + } + break; + + case 2: + { + uint8_t b1 = matrix_base[ seq[ 1 ] ]; + if (!BIT(b1 , 0)) { + res = b1 >> 1; + } + } + break; + + case 3: + { + uint8_t b2 = matrix_base[ seq[ 2 ] ]; + if (!BIT(b2 , 0)) { + res = b2 >> 1; + } + } + break; + + case 4: + case 5: + case 6: + case 7: + case 8: + res = matrix_base[ seq[ line_no - 1 ] ] >> 1; + break; + + case 9: + { + uint8_t b1 = matrix_base[ seq[ 1 ] ]; + if (BIT(b1 , 0)) { + res = b1 >> 1; + } else { + uint8_t b5 = matrix_base[ seq[ 5 ] ]; + if (BIT(b5 , 0)) { + res = matrix_base[ seq[ 0 ] ] >> 1; + } + } + } + break; + + case 10: + { + uint8_t b2 = matrix_base[ seq[ 2 ] ]; + if (BIT(b2 , 0)) { + res = b2 >> 1; + } else { + uint8_t b6 = matrix_base[ seq[ 6 ] ]; + if (BIT(b6 , 0)) { + res = matrix_base[ seq[ 0 ] ] >> 1; + } + } + } + break; + + case 11: + { + uint8_t b7 = matrix_base[ seq[ 7 ] ]; + if (BIT(b7 , 0)) { + res = matrix_base[ seq[ 0 ] ] >> 1; + } + } + break; + } + + return res; +} + +unsigned hp9845_printer_device::print_560_pixels(unsigned line_no , const uint8_t *pixels) +{ + std::ostringstream buff; + buff << line_no; + buff << ':'; + unsigned pixel_count = 0; + for (unsigned i = 0; i < 70; i++) { + uint8_t pop_count = pixels[ i ]; + pop_count = (pop_count & 0x55) + ((pop_count >> 1) & 0x55); + pop_count = (pop_count & 0x33) + ((pop_count >> 2) & 0x33); + pop_count = (pop_count & 0x0f) + ((pop_count >> 4) & 0x0f); + pixel_count += pop_count; + for (uint8_t mask = 0x80; mask; mask >>= 1) { + buff << ((mask & pixels[ i ]) ? '*' : ' '); + } + } + if (pixel_count) { + buff << '\n'; + std::string out{buff.str()}; + for (auto c : out) { + m_prt_graph_out->output(c); + } + } + LOG("pc=%u\n" , pixel_count); + return pixel_count; +} + +static const uint8_t linear_seq[] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }; +static const uint8_t gray_seq[] = { 0 , 1 , 3 , 2 , 6 , 7 , 5 , 4 }; + +uint8_t hp9845_printer_device::get_ch_pixels(uint8_t ch , uint8_t attrs , unsigned matrix_line) const +{ + uint8_t pixels; + bool new_ch = BIT(attrs , ATTRS_NEW_BIT); + if (new_ch) { + const uint8_t *matrix = &m_redef_buff[ (attrs & ATTRS_REDEF_NO_MASK) * 8 ]; + pixels = get_ch_matrix_line(matrix, matrix_line, linear_seq); + } else { + const uint8_t *matrix = &m_prt_chargen[ ch * 8 ]; + pixels = get_ch_matrix_line(matrix, matrix_line, gray_seq); + } + return pixels; +} + +attotime hp9845_printer_device::burn_time(unsigned pixel_count) +{ + if (pixel_count == 0) { + return attotime::zero; + } + unsigned full_burns = pixel_count / MAX_PIXELS_BURNED; + unsigned rem_pixels = pixel_count - full_burns * MAX_PIXELS_BURNED; + unsigned usec = full_burns * USEC_GE_48_PIXELS; + if (rem_pixels < 16) { + usec += USEC_LT_16_PIXELS; + } else if (rem_pixels < 32) { + usec += USEC_LT_32_PIXELS; + } else if (rem_pixels < 48) { + usec += USEC_LT_48_PIXELS; + } else { + usec += USEC_GE_48_PIXELS; + } + return attotime::from_usec(usec); +} + +void hp9845_printer_device::print_line() +{ + attotime print_time; + bool any_big = false; + // Normal-sized character range is [0..22], every other line + // Big-sized characters range is [-8..22], one line every 3 + for (int line_off = -8; line_off < 23; line_off++) { + if ((line_off + int(m_cur_line)) < 0) { + continue; + } + bool norm_line = line_off >= 0 && line_off % 2 == 0; + bool big_line = (line_off + 8) % 3 == 0; + + if (norm_line || big_line) { + uint8_t pixel_line[ 70 ]; + memset(pixel_line, 0, sizeof(pixel_line)); + + unsigned pixel_line_idx = 0; + uint8_t pixel_line_mask = 0x80; + + for (unsigned pos = 0; pos < m_pos; pos++) { + uint8_t ch = m_line[ pos ]; + uint8_t attrs = m_attrs[ pos ]; + uint8_t pixels = 0; + + if (line_off == 22 && BIT(attrs , ATTRS_U_L_BIT)) { + // Underline + pixels = 0x7f; + } else if (BIT(attrs , ATTRS_BIG_BIT) && big_line) { + any_big = true; + unsigned matrix_line = (line_off + 8) / 3; + pixels = get_ch_pixels(ch, attrs, matrix_line); + } else if (!BIT(attrs , ATTRS_BIG_BIT) && norm_line) { + unsigned matrix_line = line_off / 2; + pixels = get_ch_pixels(ch, attrs, matrix_line); + } + for (uint8_t mask = 0x40; mask; mask >>= 1) { + if (pixels & mask) { + pixel_line[ pixel_line_idx ] |= pixel_line_mask; + } + pixel_line_mask >>= 1; + if (pixel_line_mask == 0) { + pixel_line_mask = 0x80; + pixel_line_idx++; + } + } + } + unsigned pixel_count = print_560_pixels(unsigned(line_off + int(m_cur_line)) , pixel_line); + print_time += burn_time(pixel_count); + } + } + m_cur_line += 24; + // Computing an accurate printing time is a lot more complex than this. + // This should be a reasonable estimate. + if (any_big) { + print_time += attotime::from_usec(USEC_1_STEP * 32); + } else { + print_time += attotime::from_usec(USEC_1_STEP * 24); + } + LOG("A time=%.6f\n" , print_time.as_double()); + m_timer->adjust(print_time); + m_busy = true; +} + +void hp9845_printer_device::print_graphic_line() +{ + unsigned pixel_count = print_560_pixels(m_cur_line , m_line); + attotime print_time{burn_time(pixel_count)}; + print_time += attotime::from_usec(USEC_1_STEP * 2); + m_cur_line += 2; + LOG("G time=%.6f\n" , print_time.as_double()); + m_timer->adjust(print_time); + m_busy = true; +} + +void hp9845_printer_device::crlf() +{ + print_line(); + start_new_line(); +} + +void hp9845_printer_device::set_tab() +{ + if (m_pos > 0) { + BIT_SET(m_attrs[ m_pos - 1 ] , ATTRS_TAB_BIT); + } +} + +void hp9845_printer_device::clear_tabs() +{ + for (auto& x : m_attrs) { + BIT_CLR(x , ATTRS_TAB_BIT); + } +} + +void hp9845_printer_device::move_to_next_tab() +{ + for (unsigned i = m_pos; i < 80; i++) { + if (BIT(m_attrs[ i ] , ATTRS_TAB_BIT)) { + m_pos = i + 1; + return; + } + } + crlf(); +} + +void hp9845_printer_device::update_flg() +{ + m_flg_handler(!m_ibf && !m_busy); + m_irl_handler(!m_ibf && !m_busy && m_inten); +} + +bool hp9845_printer_device::is_ch_redef(uint8_t ch , unsigned& redef_number) const +{ + for (unsigned i = 0; i < m_redef_count; i++) { + if (m_redef_chars[ i ] == ch) { + redef_number = i; + return true; + } + } + return false; +} + +uint8_t hp9845_printer_device::allocate_ch_redef(uint8_t& idx) +{ + if (m_redef_count >= REDEF_CH_COUNT) { + idx = REDEF_CH_COUNT - 1; + return true; + } else if (m_next_replace < 8) { + return false; + } else { + uint8_t max_idx = (m_next_replace - 8) / 8; + if (max_idx < m_redef_count) { + idx = max_idx; + } else { + idx = m_redef_count++; + } + return true; + } +} + +bool hp9845_printer_device::is_ch_replaced(uint8_t ch , uint8_t& len , uint8_t& ptr) const +{ + uint8_t idx = REDEF_BUFF_LEN - 1; + for (unsigned i = 0; i < m_replace_count; i++) { + if (m_redef_buff[ idx-- ] == ch) { + len = m_redef_buff[ idx-- ]; + ptr = idx; + return true; + } else { + idx -= (m_redef_buff[ idx ] + 1); + } + } + return false; +} + +uint8_t hp9845_printer_device::free_redef_space() const +{ + return m_next_replace - m_redef_count * 8; +} + +uint8_t hp9845_printer_device::apply_shifting(uint8_t ch) const +{ + if ((ch & 0x60) == 0 || !m_shifted) { + return ch; + } else { + return ch | 0x80; + } +} + +bool hp9845_printer_device::parse_octal(uint8_t ch) +{ + if (ch >= '0' && ch <= '7') { + m_octal_accum = (m_octal_accum * 8) + (ch - '0'); + return true; + } else { + return false; + } +} + +bool hp9845_printer_device::parse_ch(uint8_t ch) +{ + bool consume = true; + + switch (m_fsm_state) { + case FSM_NORMAL_TEXT: + if (m_display_mode) { + insert_char(apply_shifting(ch)); + if (ch == CH_CR) { + crlf(); + } else if (ch == CH_ESC) { + m_fsm_state = FSM_WAIT_ESC_Z; + } + } else { + switch (ch) { + case CH_BS: + if (m_pos > 0) { + m_pos--; + } + break; + + case CH_TAB: + move_to_next_tab(); + break; + + case CH_LF: + { + auto save_pos = m_pos; + crlf(); + m_pos = save_pos; + } + break; + + case CH_CR: + m_fsm_state = FSM_AFTER_CR; + break; + + case CH_SH_OUT: + m_shifted = true; + break; + + case CH_SH_IN: + m_shifted = false; + break; + + case CH_ESC: + m_fsm_state = FSM_AFTER_ESC; + break; + + default: + if ((ch & 0xe4) == 0x80) { + m_current_u_l = false; + } else if ((ch & 0xe4) == 0x84) { + m_current_u_l = true; + } else if ((ch & 0x60) != 0) { + insert_char(apply_shifting(ch)); + } + break; + } + } + break; + + case FSM_AFTER_CR: + if (ch == CH_LF) { + // CR + LF sequence + crlf(); + } else { + // CR + something not LF + auto save_cur_line = m_cur_line; + crlf(); + m_cur_line = save_cur_line; + // Ch is pushed back for next iteration of FSM + consume = false; + } + m_fsm_state = FSM_NORMAL_TEXT; + break; + + case FSM_AFTER_ESC: + m_fsm_state = FSM_NORMAL_TEXT; + switch (ch) { + case '1': + set_tab(); + break; + + case '3': + clear_tabs(); + break; + + case 'E': + LOG("State reset\n"); + state_reset(); + break; + + case 'Y': + m_display_mode = true; + break; + + case '&': + m_fsm_state = FSM_AFTER_ESC_AMP; + break; + + case '?': + if (m_pos > 0) { + crlf(); + } + m_pos = 0; + m_fsm_state = FSM_COLLECT_ESC_QMARK; + break; + + default: + // Unknown ESC sequence + break; + } + break; + + case FSM_AFTER_ESC_AMP: + switch (ch) { + case 'd': + m_fsm_state = FSM_AFTER_ESC_AMP_D; + break; + + case 'k': + m_fsm_state = FSM_AFTER_ESC_AMP_K; + break; + + case 'l': + m_fsm_state = FSM_AFTER_ESC_AMP_L; + m_octal_accum = 0; + break; + + case 'n': + m_fsm_state = FSM_AFTER_ESC_AMP_N; + m_octal_accum = 0; + break; + + case 'o': + m_fsm_state = FSM_AFTER_ESC_AMP_O; + m_octal_accum = 0; + break; + + default: + // Unknown ESC & sequence + m_fsm_state = FSM_NORMAL_TEXT; + break; + } + break; + + case FSM_COLLECT_ESC_QMARK: + m_line[ m_pos++ ] = ch; + if (m_pos == 70) { + print_graphic_line(); + start_new_line(); + m_fsm_state = FSM_NORMAL_TEXT; + } + break; + + case FSM_AFTER_ESC_AMP_K: + switch (ch) { + case '0': + case '1': + m_octal_accum = ch != '0'; + m_fsm_state = FSM_AFTER_ESC_AMP_K_01; + break; + + default: + m_fsm_state = FSM_NORMAL_TEXT; + break; + } + break; + + case FSM_AFTER_ESC_AMP_K_01: + if (ch == 'S') { + m_current_big = m_octal_accum; + } + m_fsm_state = FSM_NORMAL_TEXT; + break; + + case FSM_AFTER_ESC_AMP_D: + switch (ch) { + case 'D': + case 'E': + case 'F': + case 'G': + case 'L': + case 'M': + case 'N': + case 'O': + m_current_u_l = true; + break; + + case '@': + case 'A': + case 'B': + case 'C': + case 'H': + case 'I': + case 'J': + case 'K': + m_current_u_l = false; + break; + + default: + break; + } + m_fsm_state = FSM_NORMAL_TEXT; + break; + + case FSM_AFTER_ESC_AMP_N: + if (!parse_octal(ch)) { + if (ch == 'c') { + unsigned dummy; + if (is_ch_redef(m_octal_accum, dummy) || !allocate_ch_redef(m_redef_idx)) { + // Character redefined twice or space exhausted + m_redef_idx = 0xff; + } + LOG("Redef ch %02x @%u\n" , m_octal_accum , m_redef_idx); + if (m_redef_idx < REDEF_CH_COUNT) { + m_redef_chars[ m_redef_idx ] = m_octal_accum; + } + m_octal_accum = 0; + m_fsm_state = FSM_AFTER_ESC_AMP_N_C; + } else { + m_fsm_state = FSM_NORMAL_TEXT; + } + } + break; + + case FSM_AFTER_ESC_AMP_O: + if (!parse_octal(ch)) { + if (ch == 'c') { + uint8_t dummy1; + uint8_t dummy2; + if (is_ch_replaced(m_octal_accum, dummy1, dummy2) || free_redef_space() < 2) { + // Character replaced twice or space exhausted + m_redef_idx = 0xff; + } else { + m_redef_idx = m_next_replace; + m_redef_buff[ m_redef_idx-- ] = m_octal_accum; + m_redef_buff[ m_redef_idx ] = 0; + m_next_replace -= 2; + m_replace_count++; + } + m_octal_accum = 0; + m_fsm_state = FSM_AFTER_ESC_AMP_O_C; + } else { + m_fsm_state = FSM_NORMAL_TEXT; + } + } + break; + + case FSM_AFTER_ESC_AMP_O_C: + if (!parse_octal(ch)) { + if (ch == 'L') { + if (m_redef_idx < REDEF_BUFF_LEN) { + uint8_t max_len = std::min(m_octal_accum , free_redef_space()); + m_next_replace -= max_len; + m_redef_buff[ m_redef_idx-- ] = max_len; + LOG("Repl ch %02x with str @%u, len=%u\n" , m_redef_buff[ m_redef_idx + 2 ] , m_redef_idx , max_len); + } + if (m_octal_accum) { + m_fsm_state = FSM_AFTER_ESC_AMP_O_C_L; + } else { + m_fsm_state = FSM_NORMAL_TEXT; + } + } else { + m_fsm_state = FSM_NORMAL_TEXT; + } + } + break; + + case FSM_AFTER_ESC_AMP_O_C_L: + if (m_redef_idx < REDEF_BUFF_LEN && m_redef_idx >= m_next_replace) { + m_redef_buff[ m_redef_idx-- ] = ch; + } + if (--m_octal_accum == 0) { + m_fsm_state = FSM_NORMAL_TEXT; + } + break; + + case FSM_AFTER_ESC_AMP_L: + if (!parse_octal(ch)) { + // TODO: ESC & l [ST] + m_fsm_state = FSM_NORMAL_TEXT; + } + break; + + case FSM_AFTER_ESC_AMP_N_C: + if (!parse_octal(ch)) { + if (ch >= 'p' && ch <= 'w') { + if (m_redef_idx < REDEF_CH_COUNT) { + m_redef_buff[ m_redef_idx * 8 + ch - 'p' ] = m_octal_accum; + } + m_octal_accum = 0; + } else if (ch >= 'P' && ch <= 'W') { + if (m_redef_idx < REDEF_CH_COUNT) { + m_redef_buff[ m_redef_idx * 8 + ch - 'P' ] = m_octal_accum; + } + m_fsm_state = FSM_NORMAL_TEXT; + } else { + m_fsm_state = FSM_NORMAL_TEXT; + } + } + break; + + case FSM_WAIT_ESC_Z: + insert_char(ch); + if (ch == 'Z') { + m_display_mode = false; + m_fsm_state = FSM_NORMAL_TEXT; + } else if (ch != CH_ESC) { + m_fsm_state = FSM_NORMAL_TEXT; + } + break; + } + return consume; +} + +void hp9845_printer_device::update_fsm() +{ + while (!m_busy && m_ibf) { + bool consume = false; + if (m_rep_str_len != 0) { + consume = parse_ch(m_redef_buff[ m_rep_str_ptr ]); + } else if ((m_fsm_state == FSM_NORMAL_TEXT || m_fsm_state == FSM_AFTER_CR || m_fsm_state == FSM_COLLECT_ESC_QMARK) && + is_ch_replaced(apply_shifting(m_ib), m_rep_str_len, m_rep_str_ptr)) { + LOG("Subst ch %02x with str @%u (len = %u)\n" , m_ib , m_rep_str_ptr , m_rep_str_len); + if (m_rep_str_len == 0) { + consume = true; + } + } else { + consume = parse_ch(m_ib); + } + if (consume) { + if (m_rep_str_len) { + m_rep_str_len--; + m_rep_str_ptr--; + } + if (m_rep_str_len == 0) { + m_prt_alpha_out->output(m_ib); + m_ibf = false; + update_flg(); + } + } + } +} diff --git a/src/mame/machine/hp9845_printer.h b/src/mame/machine/hp9845_printer.h new file mode 100644 index 00000000000..db88d87f2c7 --- /dev/null +++ b/src/mame/machine/hp9845_printer.h @@ -0,0 +1,134 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp9845_printer.h + + HP9845 internal printer HLE + +*********************************************************************/ + +#ifndef MAME_MACHINE_HP9845_PRINTER_H +#define MAME_MACHINE_HP9845_PRINTER_H + +#pragma once + +#include "imagedev/bitbngr.h" + +#define MCFG_9845PRT_IRL_HANDLER(_devcb) \ + devcb = &hp9845_printer_device::set_irl_handler(*device , DEVCB_##_devcb); + +#define MCFG_9845PRT_FLG_HANDLER(_devcb) \ + devcb = &hp9845_printer_device::set_flg_handler(*device , DEVCB_##_devcb); + +#define MCFG_9845PRT_STS_HANDLER(_devcb) \ + devcb = &hp9845_printer_device::set_sts_handler(*device , DEVCB_##_devcb); + +class hp9845_printer_device : public device_t +{ +public: + // construction/destruction + hp9845_printer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + // static configuration helpers + template static devcb_base &set_irl_handler(device_t &device, Object &&cb) { return downcast(device).m_irl_handler.set_callback(std::forward(cb)); } + template static devcb_base &set_flg_handler(device_t &device, Object &&cb) { return downcast(device).m_flg_handler.set_callback(std::forward(cb)); } + template static devcb_base &set_sts_handler(device_t &device, Object &&cb) { return downcast(device).m_sts_handler.set_callback(std::forward(cb)); } + + // device-level overrides + virtual const tiny_rom_entry *device_rom_region() const override; + virtual void device_add_mconfig(machine_config &config) override; + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + + // PPU access + DECLARE_READ16_MEMBER(printer_r); + DECLARE_WRITE16_MEMBER(printer_w); + +private: + devcb_write_line m_irl_handler; + devcb_write_line m_flg_handler; + devcb_write_line m_sts_handler; + + required_device m_prt_graph_out; + required_device m_prt_alpha_out; + required_region_ptr m_prt_chargen; + + emu_timer *m_timer; + + // Size of various buffers + static constexpr unsigned REDEF_CH_COUNT = 9; + static constexpr unsigned REDEF_BUFF_LEN = 77; + + // State + bool m_display_mode; + bool m_shifted; + bool m_current_u_l; + bool m_current_big; + bool m_ibf; + bool m_inten; + bool m_busy; + uint8_t m_ib; + uint8_t m_pos; + uint8_t m_line[ 80 ]; + uint8_t m_attrs[ 80 ]; + uint8_t m_redef_count; + uint8_t m_redef_idx; + uint8_t m_redef_chars[ REDEF_CH_COUNT ]; + uint8_t m_replace_count; + uint8_t m_redef_buff[ REDEF_BUFF_LEN ]; + uint8_t m_next_replace; + uint8_t m_rep_str_len; + uint8_t m_rep_str_ptr; + uint8_t m_octal_accum; + int m_fsm_state; + unsigned m_cur_line; + + // FSM states + enum { + FSM_NORMAL_TEXT, + FSM_AFTER_CR, + FSM_AFTER_ESC, + FSM_AFTER_ESC_AMP, + FSM_COLLECT_ESC_QMARK, + FSM_AFTER_ESC_AMP_K, + FSM_AFTER_ESC_AMP_K_01, + FSM_AFTER_ESC_AMP_D, + FSM_AFTER_ESC_AMP_N, + FSM_AFTER_ESC_AMP_O, + FSM_AFTER_ESC_AMP_O_C, + FSM_AFTER_ESC_AMP_O_C_L, + FSM_AFTER_ESC_AMP_L, + FSM_AFTER_ESC_AMP_N_C, + FSM_WAIT_ESC_Z + }; + + void state_reset(); + void insert_char(uint8_t ch); + void start_new_line(); + static uint8_t get_ch_matrix_line(const uint8_t *matrix_base , unsigned line_no , const uint8_t *seq); + unsigned print_560_pixels(unsigned line_no , const uint8_t *pixels); + uint8_t get_ch_pixels(uint8_t ch , uint8_t attrs , unsigned matrix_line) const; + static attotime burn_time(unsigned pixel_count); + void print_line(); + void print_graphic_line(); + void crlf(); + void set_tab(); + void clear_tabs(); + void move_to_next_tab(); + void update_flg(); + bool is_ch_redef(uint8_t ch , unsigned& redef_number) const; + uint8_t allocate_ch_redef(uint8_t& idx); + bool is_ch_replaced(uint8_t ch , uint8_t& len , uint8_t& ptr) const; + uint8_t free_redef_space() const; + uint8_t apply_shifting(uint8_t ch) const; + bool parse_octal(uint8_t ch); + bool parse_ch(uint8_t ch); + void update_fsm(); +}; + +// device type definition +DECLARE_DEVICE_TYPE(HP9845_PRINTER, hp9845_printer_device) + +#endif // MAME_MACHINE_HP9845_PRINTER_H