From acb80456ef9f93c03776794e332a5ef400ffbef0 Mon Sep 17 00:00:00 2001 From: fulivi Date: Mon, 17 Jul 2017 14:51:28 +0200 Subject: hp85: improved CRT access timing, implemented hw timers and beeper --- src/mame/drivers/hp80.cpp | 337 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 308 insertions(+), 29 deletions(-) diff --git a/src/mame/drivers/hp80.cpp b/src/mame/drivers/hp80.cpp index 8ee1fbcdcaa..5302bc52ab1 100644 --- a/src/mame/drivers/hp80.cpp +++ b/src/mame/drivers/hp80.cpp @@ -10,6 +10,10 @@ #include "emu.h" #include "screen.h" #include "cpu/capricorn/capricorn.h" +#include "speaker.h" +#include "sound/beep.h" +#include "sound/dac.h" +#include "sound/volt_reg.h" // Debugging #define VERBOSE 1 @@ -48,20 +52,24 @@ static constexpr unsigned MASTER_CLOCK = 9808000; static constexpr unsigned VIDEO_MEM_SIZE= 8192; static constexpr unsigned ALPHA_MEM_SIZE= 4096; static constexpr unsigned GRAPH_MEM_SIZE= 16384; -static constexpr unsigned CRT_STS_READY_BIT = 0; -static constexpr unsigned CRT_STS_DISPLAY_BIT = 1; -//static constexpr unsigned CRT_STS_BUSY_BIT = 7; -static constexpr unsigned CRT_CTL_RD_RQ_BIT = 0; -static constexpr unsigned CRT_CTL_WIPEOUT_BIT = 1; -static constexpr unsigned CRT_CTL_POWERDN_BIT = 2; -static constexpr unsigned CRT_CTL_GRAPHICS_BIT = 7; -static constexpr unsigned IRQ_KEYBOARD_BIT = 0; -static constexpr unsigned IRQ_TIMER0_BIT = 1; -static constexpr unsigned TIMER_COUNT = 4; -static constexpr unsigned IRQ_IOP0_BIT = IRQ_TIMER0_BIT + TIMER_COUNT; -static constexpr unsigned IOP_COUNT = 0; -static constexpr unsigned IRQ_BIT_COUNT = IRQ_IOP0_BIT + IOP_COUNT; -static constexpr unsigned NO_IRQ = IRQ_BIT_COUNT; +static constexpr unsigned CRT_STS_READY_BIT = 0; +static constexpr unsigned CRT_STS_DISPLAY_BIT = 1; +static constexpr unsigned CRT_STS_BUSY_BIT = 7; +static constexpr unsigned CRT_CTL_RD_RQ_BIT = 0; +static constexpr unsigned CRT_CTL_WIPEOUT_BIT = 1; +static constexpr unsigned CRT_CTL_POWERDN_BIT = 2; +static constexpr unsigned CRT_CTL_GRAPHICS_BIT = 7; +// Time to read/write a byte in video memory (in master clock cycles) +static constexpr unsigned CRT_RW_TIME = 96; +// Time taken by hw timer updating (semi-made up) (in usec) +static constexpr unsigned TIMER_BUSY_USEC = 128; +static constexpr unsigned IRQ_KEYBOARD_BIT = 0; +static constexpr unsigned IRQ_TIMER0_BIT = 1; +static constexpr unsigned TIMER_COUNT = 4; +static constexpr unsigned IRQ_IOP0_BIT = IRQ_TIMER0_BIT + TIMER_COUNT; +static constexpr unsigned IOP_COUNT = 0; +static constexpr unsigned IRQ_BIT_COUNT = IRQ_IOP0_BIT + IOP_COUNT; +static constexpr unsigned NO_IRQ = IRQ_BIT_COUNT; // ************ // hp85_state @@ -87,13 +95,24 @@ public: DECLARE_WRITE8_MEMBER(keycod_w); DECLARE_READ8_MEMBER(crtc_r); DECLARE_WRITE8_MEMBER(crtc_w); + DECLARE_READ8_MEMBER(clksts_r); + DECLARE_WRITE8_MEMBER(clksts_w); + DECLARE_READ8_MEMBER(clkdat_r); + DECLARE_WRITE8_MEMBER(clkdat_w); DECLARE_WRITE8_MEMBER(rselec_w); TIMER_DEVICE_CALLBACK_MEMBER(kb_scan); + TIMER_DEVICE_CALLBACK_MEMBER(vm_timer); + TIMER_DEVICE_CALLBACK_MEMBER(timer_update); + TIMER_DEVICE_CALLBACK_MEMBER(clk_busy_timer); protected: required_device m_cpu; required_device m_screen; required_device m_palette; + required_device m_vm_timer; + required_device m_clk_busy_timer; + required_device m_beep; + required_device m_dac; required_region_ptr m_rom00; required_memory_bank m_rombank; required_ioport m_io_key0; @@ -111,6 +130,7 @@ protected: uint8_t m_crt_sts; uint8_t m_crt_ctl; uint8_t m_crt_read_byte; + uint8_t m_crt_write_byte; uint8_t m_empty_bank[ 0x2000 ]; bool m_global_int_en; uint16_t m_int_req; @@ -126,11 +146,24 @@ protected: bool m_kb_flipped; uint8_t m_kb_keycode; + // Timers + typedef struct { + uint8_t m_timer_cnt[ 4 ]; + uint8_t m_timer_reg[ 4 ]; + bool m_timer_en; + bool m_timer_clr; + uint8_t m_digit_to_match; + } hw_timer_t; + hw_timer_t m_hw_timer[ TIMER_COUNT ]; + uint8_t m_timer_idx; + bool m_clk_busy; + + attotime time_to_video_mem_availability() const; static void get_video_addr(uint16_t addr , uint16_t& byte_addr , bool& lsb_nibble); uint8_t video_mem_r(uint16_t addr , uint16_t addr_mask) const; void video_mem_w(uint16_t addr , uint16_t addr_mask , uint8_t data); void video_mem_read(); - void video_mem_write(uint8_t data); + void video_mem_write(); bool kb_scan_ioport(ioport_value pressed , unsigned idx_base , uint8_t& keycode); @@ -145,6 +178,10 @@ hp85_state::hp85_state(const machine_config &mconfig, device_type type, const ch m_cpu(*this , "cpu"), m_screen(*this , "screen"), m_palette(*this , "palette"), + m_vm_timer(*this , "vm_timer"), + m_clk_busy_timer(*this , "clk_busy_timer"), + m_beep(*this , "beeper"), + m_dac(*this , "dac"), m_rom00(*this , "rom00"), m_rombank(*this , "rombank"), m_io_key0(*this , "KEY0"), @@ -176,6 +213,7 @@ void hp85_state::machine_reset() m_crt_sts = 0x7c; m_crt_ctl = BIT_MASK(CRT_CTL_POWERDN_BIT) | BIT_MASK(CRT_CTL_WIPEOUT_BIT); m_crt_read_byte = 0; + m_crt_write_byte = 0; // Clear RSELEC m_rombank->set_entry(0xff); @@ -192,6 +230,17 @@ void hp85_state::machine_reset() m_kb_enable = true; m_kb_pressed = false; m_kb_flipped = false; + for (auto& timer : m_hw_timer) { + for (unsigned i = 0; i < 4; i++) { + timer.m_timer_cnt[ i ] = 0; + timer.m_timer_reg[ i ] = 0; + } + timer.m_timer_en = false; + timer.m_timer_clr = false; + timer.m_digit_to_match = 0; + } + m_timer_idx = 0; + m_clk_busy = false; update_irl(); } @@ -260,6 +309,7 @@ static const uint8_t vector_table[] = { IRQ_CALLBACK_MEMBER(hp85_state::irq_callback) { + logerror("IRQ ACK %u\n" , m_top_pending); BIT_SET(m_int_acked , m_top_pending); update_irl(); return vector_table[ m_top_pending ]; @@ -297,13 +347,13 @@ READ8_MEMBER(hp85_state::keysts_r) WRITE8_MEMBER(hp85_state::keysts_w) { - logerror("KEYSTS W=%02x\n" , data); if (BIT(data , 0)) { irq_en_w(IRQ_KEYBOARD_BIT , true); } else if (BIT(data , 1)) { irq_en_w(IRQ_KEYBOARD_BIT , false); } - // TODO: beeper + m_dac->write(BIT(data , 5)); + m_beep->set_state(BIT(data , 6)); if (BIT(data , 7)) { m_kb_flipped = !m_kb_flipped; } @@ -316,7 +366,6 @@ READ8_MEMBER(hp85_state::keycod_r) WRITE8_MEMBER(hp85_state::keycod_w) { - logerror("KEYCOD W=%02x\n" , data); if (data == 1) { irq_w(IRQ_KEYBOARD_BIT , false); m_kb_enable = true; @@ -347,14 +396,12 @@ READ8_MEMBER(hp85_state::crtc_r) res = m_crt_read_byte; break; } - logerror("RD @%u = %02x\n" , offset , res); return res; } WRITE8_MEMBER(hp85_state::crtc_w) { // Write to CRT controller (1MA5) - logerror("WR @%u = %02x\n" , offset , data); uint8_t burst_idx = m_cpu->flatten_burst(); switch (offset) { case 0: @@ -364,7 +411,6 @@ WRITE8_MEMBER(hp85_state::crtc_w) } else if (burst_idx == 0) { m_crt_sad = (m_crt_sad & 0xff00) | data; } - logerror("SAD=%04x m=%u\n" , m_crt_sad , burst_idx); break; case 1: @@ -374,24 +420,117 @@ WRITE8_MEMBER(hp85_state::crtc_w) } else if (burst_idx == 0) { m_crt_bad = (m_crt_bad & 0xff00) | data; } - logerror("BAD=%04x m=%u\n" , m_crt_bad , burst_idx); break; case 2: // CRTCTL m_crt_ctl = data; if (BIT(m_crt_ctl , CRT_CTL_RD_RQ_BIT)) { - video_mem_read(); + BIT_CLR(m_crt_sts , CRT_STS_READY_BIT); + BIT_SET(m_crt_sts , CRT_STS_BUSY_BIT); + attotime vm_av = time_to_video_mem_availability(); + m_vm_timer->adjust(vm_av + attotime::from_ticks(CRT_RW_TIME , MASTER_CLOCK)); } break; case 3: // CRTDAT - video_mem_write(data); + { + m_crt_write_byte = data; + BIT_CLR(m_crt_sts , CRT_STS_READY_BIT); + BIT_SET(m_crt_sts , CRT_STS_BUSY_BIT); + attotime vm_av = time_to_video_mem_availability(); + m_vm_timer->adjust(vm_av + attotime::from_ticks(CRT_RW_TIME , MASTER_CLOCK)); + } break; } } +READ8_MEMBER(hp85_state::clksts_r) +{ + uint8_t res = 0; + for (unsigned i = 0; i < TIMER_COUNT; i++) { + if (BIT(m_int_en , IRQ_TIMER0_BIT + i)) { + BIT_SET(res , i); + } + } + if (!m_clk_busy) { + BIT_SET(res , 7); + } + //logerror("CLKSTS R=%02x\n" , res); + return res; +} + +WRITE8_MEMBER(hp85_state::clksts_w) +{ + // logerror("CLKSTS W=%02x\n" , data); + if (data == 0x0c) { + // Set test mode (see timer_update) + auto& timer = m_hw_timer[ m_timer_idx ]; + timer.m_digit_to_match = 1; + timer.m_timer_cnt[ 0 ] = timer.m_timer_reg[ 0 ]; + timer.m_timer_cnt[ 1 ] = timer.m_timer_reg[ 1 ]; + timer.m_timer_cnt[ 2 ] = timer.m_timer_reg[ 2 ]; + timer.m_timer_cnt[ 3 ] = timer.m_timer_reg[ 3 ]; + logerror("Test mode enabled for timer %u\n" , m_timer_idx); + } else { + m_timer_idx = (data >> 6) & 3; + auto& timer = m_hw_timer[ m_timer_idx ]; + if (BIT(data , 0)) { + // Disable timer irq + irq_en_w(IRQ_TIMER0_BIT + m_timer_idx , false); + } else if (BIT(data , 1)) { + // Enable timer irq + irq_en_w(IRQ_TIMER0_BIT + m_timer_idx , true); + } + if (BIT(data , 2)) { + // Stop timer + timer.m_timer_en = false; + } else if (BIT(data , 3)) { + // Start timer + timer.m_timer_en = true; + } + if (BIT(data , 4) || (BIT(data , 3) && timer.m_digit_to_match)) { + // Clear timer + timer.m_timer_clr = true; + // Disable test mode + timer.m_digit_to_match = 0; + } + if (BIT(data , 5)) { + // Clear timer irq + irq_w(IRQ_TIMER0_BIT + m_timer_idx , false); + } + update_int_bits(); + } +} + +READ8_MEMBER(hp85_state::clkdat_r) +{ + uint8_t res; + unsigned burst_idx = m_cpu->flatten_burst(); + if (burst_idx < 4) { + res = m_hw_timer[ m_timer_idx ].m_timer_cnt[ burst_idx ]; + } else { + // What happens when loading more than 4 bytes from timers? + logerror("Reading more than 4 bytes from timer %u\n" , m_timer_idx); + res = 0; + } + //logerror("CLKDAT R %u=%02x\n" , burst_idx , res); + return res; +} + +WRITE8_MEMBER(hp85_state::clkdat_w) +{ + unsigned burst_idx = m_cpu->flatten_burst(); + //logerror("CLKDAT W %u=%02x\n" , burst_idx , data); + if (burst_idx < 4) { + m_hw_timer[ m_timer_idx ].m_timer_reg[ burst_idx ] = data; + } else { + // What happens when storing more than 4 bytes into timers? + logerror("Writing more than 4 bytes into timer %u\n" , m_timer_idx); + } +} + WRITE8_MEMBER(hp85_state::rselec_w) { m_rombank->set_entry(data); @@ -525,7 +664,6 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp85_state::kb_scan) kb_scan_ioport(input[ 2 ] & ~m_kb_state[ 2 ] , 64 , keycode); if (got_key) { - logerror("key %02x\n" , keycode); m_kb_keycode = keycode; irq_w(IRQ_KEYBOARD_BIT , true); m_kb_enable = false; @@ -540,6 +678,129 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp85_state::kb_scan) m_kb_state[ 2 ] = input[ 2 ]; } +TIMER_DEVICE_CALLBACK_MEMBER(hp85_state::vm_timer) +{ + if (BIT(m_crt_ctl , CRT_CTL_RD_RQ_BIT)) { + video_mem_read(); + } else { + video_mem_write(); + } + BIT_CLR(m_crt_sts , CRT_STS_BUSY_BIT); +} + +TIMER_DEVICE_CALLBACK_MEMBER(hp85_state::timer_update) +{ + for (unsigned i = 0; i < TIMER_COUNT; i++) { + auto& timer = m_hw_timer[ i ]; + if (timer.m_timer_clr) { + timer.m_timer_clr = false; + timer.m_timer_cnt[ 0 ] = 0; + timer.m_timer_cnt[ 1 ] = 0; + timer.m_timer_cnt[ 2 ] = 0; + timer.m_timer_cnt[ 3 ] = 0; + } else if (timer.m_timer_en) { + if (timer.m_digit_to_match) { + // Timers have an undocumented mode (used by test "J" of service ROM) + // where the counter has to match in sequence all digits of register + // in order to raise an interrupt. In other words interrupt is generated + // after a number of updates that's equal to the sum of all digits in + // register + 1. My opinion is that people at HP designed this mode to + // allow all digits in a timer to be tested quickly. Without this special + // mode it takes more than 27 hours to check that all digits increment + // correctly and that there are no stuck bits. + // From an operative point of view, we copy register into counter when + // this special mode is activated (see clksts_w). Then, at each update, + // we decrement the digit of counter pointed to by m_digit_to_match (1 = + // least significant digit). Each time a digit "borrows" (i.e. it decrements + // from 0 to 9), we move on to digit at left. When m_digit_to_match reaches + // 9, interrupt is raised and the timer stops. + // At this point counter is always "99999999". + if (timer.m_digit_to_match < 9) { + while (true) { + bool borrow = false; + uint8_t b = timer.m_timer_cnt[ (timer.m_digit_to_match - 1) / 2 ]; + if (BIT(timer.m_digit_to_match , 0)) { + // Least significant digit in b + if (b & 0x0f) { + b--; + } else { + b = (b & 0xf0) | 9; + borrow = true; + } + } else { + // Most significant digit in b + if (b & 0xf0) { + b -= 0x10; + } else { + b = 0x99; + borrow = true; + } + } + timer.m_timer_cnt[ (timer.m_digit_to_match - 1) / 2 ] = b; + if (borrow) { + timer.m_digit_to_match++; + if (timer.m_digit_to_match == 9) { + irq_w(IRQ_TIMER0_BIT + i , true); + break; + } + } else { + break; + } + } + } + } else { + // Standard timer mode + // Increment all active timers by 1 + bool carry = true; + for (unsigned idx = 0; idx < 4 && carry; idx++) { + carry = false; + uint8_t b = timer.m_timer_cnt[ idx ]; + b++; + if ((b & 0xf) > 9) { + b += 6; + if (b >= 0xa0) { + b += 0x60; + carry = true; + } + } + timer.m_timer_cnt[ idx ] = b; + } + if (timer.m_timer_cnt[ 0 ] == timer.m_timer_reg[ 0 ] && + timer.m_timer_cnt[ 1 ] == timer.m_timer_reg[ 1 ] && + timer.m_timer_cnt[ 2 ] == timer.m_timer_reg[ 2 ] && + timer.m_timer_cnt[ 3 ] == timer.m_timer_reg[ 3 ]) { + timer.m_timer_cnt[ 0 ] = 0; + timer.m_timer_cnt[ 1 ] = 0; + timer.m_timer_cnt[ 2 ] = 0; + timer.m_timer_cnt[ 3 ] = 0; + irq_w(IRQ_TIMER0_BIT + i , true); + } + } + } + } + m_clk_busy = true; + m_clk_busy_timer->adjust(attotime::from_usec(TIMER_BUSY_USEC)); +} + +TIMER_DEVICE_CALLBACK_MEMBER(hp85_state::clk_busy_timer) +{ + m_clk_busy = false; +} + +attotime hp85_state::time_to_video_mem_availability() const +{ + if (BIT(m_crt_ctl , CRT_CTL_WIPEOUT_BIT) || BIT(m_crt_ctl , CRT_CTL_POWERDN_BIT)) { + // Blank video, immediate access + return attotime::zero; + } else if (m_screen->vblank()) { + // Vertical blanking, immediate access + return attotime::zero; + } else { + // In the active part, wait until vertical blanking + return m_screen->time_until_vblank_start(); + } +} + void hp85_state::get_video_addr(uint16_t addr , uint16_t& byte_addr , bool& lsb_nibble) { byte_addr = (addr / 2) & (VIDEO_MEM_SIZE - 1); @@ -597,20 +858,20 @@ void hp85_state::video_mem_read() } m_crt_read_byte = video_mem_r(m_crt_bad , mask); m_crt_bad += 2; + BIT_CLR(m_crt_ctl , CRT_CTL_RD_RQ_BIT); BIT_SET(m_crt_sts , CRT_STS_READY_BIT); } -void hp85_state::video_mem_write(uint8_t data) +void hp85_state::video_mem_write() { uint16_t mask; - logerror("VM @%04x=%02x\n" , m_crt_bad , data); if (BIT(m_crt_ctl , CRT_CTL_GRAPHICS_BIT)) { mask = GRAPH_MEM_SIZE / 2 - 1; } else { mask = ALPHA_MEM_SIZE / 2 - 1; } - video_mem_w(m_crt_bad , mask , data); + video_mem_w(m_crt_bad , mask , m_crt_write_byte); m_crt_bad += 2; } @@ -756,6 +1017,8 @@ static ADDRESS_MAP_START(cpu_mem_map , AS_PROGRAM , 8 , hp85_state) AM_RANGE(0xff02 , 0xff02) AM_READWRITE(keysts_r , keysts_w) AM_RANGE(0xff03 , 0xff03) AM_READWRITE(keycod_r , keycod_w) AM_RANGE(0xff04 , 0xff07) AM_READWRITE(crtc_r , crtc_w) + AM_RANGE(0xff0a , 0xff0a) AM_READWRITE(clksts_r , clksts_w) + AM_RANGE(0xff0b , 0xff0b) AM_READWRITE(clkdat_r , clkdat_w) AM_RANGE(0xff18 , 0xff18) AM_WRITE(rselec_w) ADDRESS_MAP_END @@ -769,9 +1032,25 @@ static MACHINE_CONFIG_START(hp85) MCFG_SCREEN_UPDATE_DRIVER(hp85_state , screen_update) MCFG_SCREEN_VBLANK_CALLBACK(WRITELINE(hp85_state, vblank_w)) MCFG_PALETTE_ADD_MONOCHROME("palette") + MCFG_TIMER_DRIVER_ADD("vm_timer", hp85_state, vm_timer) // No idea at all about the actual keyboard scan frequency MCFG_TIMER_DRIVER_ADD_PERIODIC("kb_timer" , hp85_state , kb_scan , attotime::from_hz(100)) + + // Hw timers are updated at 1 kHz rate + MCFG_TIMER_DRIVER_ADD_PERIODIC("hw_timer" , hp85_state , timer_update , attotime::from_hz(1000)) + MCFG_TIMER_DRIVER_ADD("clk_busy_timer", hp85_state, clk_busy_timer) + + // Beeper + MCFG_SPEAKER_STANDARD_MONO("mono") + MCFG_SOUND_ADD("dac" , DAC_1BIT , 0) + MCFG_MIXER_ROUTE(ALL_OUTPUTS , "mono" , 0.5 , 0) + MCFG_DEVICE_ADD("vref", VOLTAGE_REGULATOR, 0) + MCFG_VOLTAGE_REGULATOR_OUTPUT(5.0) + MCFG_SOUND_ROUTE_EX(0, "dac", 1.0, DAC_VREF_POS_INPUT) + MCFG_SOUND_ADD("beeper" , BEEP , MASTER_CLOCK / 8192) + MCFG_MIXER_ROUTE(ALL_OUTPUTS , "mono" , 0.5 , 0) + MACHINE_CONFIG_END ROM_START(hp85) @@ -787,4 +1066,4 @@ ROM_START(hp85) ROM_LOAD("chrgen.bin" , 0 , 0x400 , CRC(9c402544) SHA1(32634fc73c1544aeeefda62ebb10349c5b40729f)) ROM_END -COMP(1980 , hp85 , 0 , 0 , hp85 , hp85 , hp85_state , 0 , "HP" , "HP 85" , MACHINE_NO_SOUND) +COMP(1980 , hp85 , 0 , 0 , hp85 , hp85 , hp85_state , 0 , "HP" , "HP 85" , 0) -- cgit v1.2.3