summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/drivers/hp80.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/drivers/hp80.cpp')
-rw-r--r--src/mame/drivers/hp80.cpp337
1 files 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<capricorn_cpu_device> m_cpu;
required_device<screen_device> m_screen;
required_device<palette_device> m_palette;
+ required_device<timer_device> m_vm_timer;
+ required_device<timer_device> m_clk_busy_timer;
+ required_device<beep_device> m_beep;
+ required_device<dac_1bit_device> m_dac;
required_region_ptr<uint8_t> 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)