Merge pull request #931 from fulivi/hp9845_dev

Hp9845: hopefully fixed interrupt handling in hp hybrid processor & minor enhancements
This commit is contained in:
R. Belmont 2016-06-05 13:53:51 -04:00
commit baf3f115d2
2 changed files with 134 additions and 83 deletions

View File

@ -7,25 +7,34 @@
// I searched for a while for any kind of documentation about them but found nothing at all.
// Some time later I found the mnemonics in the binary dump of assembly development option ROM:
// CIM & SIM, respectively. From the mnemonic I deduced their function: Clear & Set Interrupt Mask.
// I think they are basically used to temporarily disable/enable interrupt recognition inside
// ISRs. This is consistent with their usage in PPU firmware and test ROM. The official EIR &
// DIR instructions cannot be used while servicing an interrupt because they probably reset
// the "in ISR" condition of the processor.
// Using CIM&SIM only makes sense in low-level ISRs because high-level ones can't be interrupted
// by anyone.
// Now, I still have some doubts about the "polarity" of interrupt mask. Is interrupt
// recognition disabled when the mask is cleared or is it the opposite?
// I'm leaning towards the "no interrupts with mask cleared" interpretation, but I'm not 100%
// convinced. CIM & SIM at the moment are implemented with this interpretation (see also
// NO_ISR_WITH_IM_CLEARED macro below).
// After a few experiments, crashes, etc. here's my opinion on their purpose.
// When the CPU receives an interrupt, its AEC registers can be in any state so it could
// be impossible to properly save state, fetch the interrupt vector and start executing the ISR.
// The solution is having an hidden "interrupt mask" flag that gets set when an interrupt (either
// low or high priority) is acknowledged and is cleared when the "ret 0,p" instruction that ends
// the ISR is executed. The effects of having the interrupt mask set are:
// * No interrupts are recognized
// * A few essential AEC registers are overridden to establish a "safe" environment to save state
// and execute ISR (see hp_5061_3001_cpu_device::add_mae).
// Inside the ISR, CIM & SIM instructions can be used to change the interrupt mask and switch
// between normal & overridden settings of AEC.
// As an example of CIM&SIM usage, we can have a look at the keyboard ISR in 9845B PPU processor:
// * A key is pressed and IRQ 0 is set
// * Interrupt 0 is recognized, IM is set
// * R register is used to save program counter in block = 1 (overriding any R36 value)
// * Vector is fetched and execution begins in block 5 (overriding R33 value)
// * Registers are saved to RAM (again in overridden block 1)
// * AEC registers are set to correct value for ISR execution
// * CIM is used to exit the special behaviour of AEC and to allow high-priority interrupts
// * Useful ISR processing is done
// * SIM is used to re-enter special behaviour of AEC and to block any interrupt
// * State is restored (including all AEC registers)
// * RET 0,P is executed to end ISR: return program counter is popped off the stack and IM is cleared
#include "emu.h"
#include "debugger.h"
#include "hphybrid.h"
// Define this to have "IM cleared" == "No interrupt recognition"
#define NO_ISR_WITH_IM_CLEARED
enum {
HPHYBRID_A,
HPHYBRID_B,
@ -539,9 +548,11 @@ UINT16 hp_hybrid_cpu_device::execute_one_sub(UINT16 opcode)
memmove(&m_reg_PA[ 0 ] , &m_reg_PA[ 1 ] , HPHYBRID_INT_LVLS);
m_pa_changed_func((UINT8)CURRENT_PA);
}
BIT_CLR(m_flags, HPHYBRID_IM_BIT);
tmp = RM(AEC_CASE_C , m_reg_R--) + (opcode & 0x1f);
BIT_CLR(m_flags, HPHYBRID_IM_BIT);
} else {
tmp = RM(AEC_CASE_C , m_reg_R--) + (opcode & 0x1f);
}
tmp = RM(AEC_CASE_C , m_reg_R--) + (opcode & 0x1f);
return BIT(opcode , 5) ? tmp - 0x20 : tmp;
} else {
switch (opcode) {
@ -1483,11 +1494,7 @@ UINT16 hp_5061_3001_cpu_device::execute_no_bpc_ioc(UINT16 opcode)
// Probably "Clear Interrupt Mask"
// No idea at all about exec. time: make it 9 cycles
m_icount -= 9;
#ifndef NO_ISR_WITH_IM_CLEARED
BIT_CLR(m_flags, HPHYBRID_IM_BIT);
#else
BIT_SET(m_flags, HPHYBRID_IM_BIT);
#endif
logerror("hp-5061-3001: CIM, P = %06x flags = %05x\n" , m_genpc , m_flags);
break;
@ -1497,11 +1504,7 @@ UINT16 hp_5061_3001_cpu_device::execute_no_bpc_ioc(UINT16 opcode)
// Probably "Set Interrupt Mask"
// No idea at all about exec. time: make it 9 cycles
m_icount -= 9;
#ifndef NO_ISR_WITH_IM_CLEARED
BIT_SET(m_flags, HPHYBRID_IM_BIT);
#else
BIT_CLR(m_flags, HPHYBRID_IM_BIT);
#endif
logerror("hp-5061-3001: SIM, P = %06x flags = %05x\n" , m_genpc , m_flags);
break;
@ -1532,51 +1535,63 @@ offs_t hp_5061_3001_cpu_device::disasm_disassemble(char *buffer, offs_t pc, cons
UINT32 hp_5061_3001_cpu_device::add_mae(aec_cases_t aec_case , UINT16 addr)
{
UINT16 bsc_reg;
bool top_half = BIT(addr , 15) != 0;
UINT16 bsc_reg;
bool top_half = BIT(addr , 15) != 0;
// Detect accesses to top half of base page
if ((aec_case == AEC_CASE_C || aec_case == AEC_CASE_I) && (addr & 0xfe00) == 0xfe00) {
aec_case = AEC_CASE_B;
// Detect accesses to top half of base page
if ((aec_case == AEC_CASE_C || aec_case == AEC_CASE_I) && (addr & 0xfe00) == 0xfe00) {
aec_case = AEC_CASE_B;
}
switch (aec_case) {
case AEC_CASE_A:
if (top_half) {
bsc_reg = m_reg_aec[ HP_REG_R34_ADDR - HP_REG_R32_ADDR ];
} else {
// Block 5 is used when IM bit overrides R33 value
bsc_reg = BIT(m_flags , HPHYBRID_IM_BIT) ? 5 : m_reg_aec[ HP_REG_R33_ADDR - HP_REG_R32_ADDR ];
}
break;
switch (aec_case) {
case AEC_CASE_A:
bsc_reg = top_half ? HP_REG_R34_ADDR : HP_REG_R33_ADDR;
break;
case AEC_CASE_B:
bsc_reg = top_half ? HP_REG_R36_ADDR : HP_REG_R33_ADDR;
break;
case AEC_CASE_C:
bsc_reg = top_half ? HP_REG_R32_ADDR : HP_REG_R35_ADDR;
break;
case AEC_CASE_D:
bsc_reg = top_half ? HP_REG_R32_ADDR : HP_REG_R37_ADDR;
break;
case AEC_CASE_I:
// Behaviour of AEC during interrupt vector fetch is undocumented but it can be guessed from 9845B firmware.
// Basically in this case the integrated AEC seems to do what the discrete implementation in 9845A does:
// top half of memory is mapped to block 0 (fixed) and bottom half is mapped according to content of R35
// (see pg 334 of patent).
bsc_reg = top_half ? 0 : HP_REG_R35_ADDR;
break;
default:
logerror("hphybrid: aec_case=%d\n" , aec_case);
return 0;
}
UINT16 aec_reg = (bsc_reg != 0) ? (m_reg_aec[ bsc_reg - HP_REG_R32_ADDR ] & BSC_REG_MASK) : 0;
if (m_forced_bsc_25) {
aec_reg = (aec_reg & 0xf) | 0x20;
case AEC_CASE_B:
if (top_half) {
// Block 1 is used when IM bit overrides R36 value
bsc_reg = BIT(m_flags , HPHYBRID_IM_BIT) ? 1 : m_reg_aec[ HP_REG_R36_ADDR - HP_REG_R32_ADDR ];
} else {
// Block 5 is used when IM bit overrides R33 value
bsc_reg = BIT(m_flags , HPHYBRID_IM_BIT) ? 5 : m_reg_aec[ HP_REG_R33_ADDR - HP_REG_R32_ADDR ];
}
break;
return (UINT32)addr | ((UINT32)aec_reg << 16);
case AEC_CASE_C:
bsc_reg = top_half ? m_reg_aec[ HP_REG_R32_ADDR - HP_REG_R32_ADDR ] : m_reg_aec[ HP_REG_R35_ADDR - HP_REG_R32_ADDR ];
break;
case AEC_CASE_D:
bsc_reg = top_half ? m_reg_aec[ HP_REG_R32_ADDR - HP_REG_R32_ADDR ] : m_reg_aec[ HP_REG_R37_ADDR - HP_REG_R32_ADDR ];
break;
case AEC_CASE_I:
// Behaviour of AEC during interrupt vector fetch is undocumented but it can be guessed from 9845B firmware.
// Basically in this case the integrated AEC seems to do what the discrete implementation in 9845A does:
// top half of memory is mapped to block 0 (fixed) and bottom half is mapped according to content of R35
// (see pg 334 of patent).
// I'm beginning to suspect that these values actually come from IM overriding case "C"
bsc_reg = top_half ? 0 : m_reg_aec[ HP_REG_R35_ADDR - HP_REG_R32_ADDR ];
break;
default:
logerror("hphybrid: aec_case=%d\n" , aec_case);
return 0;
}
UINT16 aec_reg = bsc_reg & BSC_REG_MASK;
if (m_forced_bsc_25) {
aec_reg = (aec_reg & 0xf) | 0x20;
}
return (UINT32)addr | ((UINT32)aec_reg << 16);
}
UINT16 hp_5061_3001_cpu_device::read_non_common_reg(UINT16 addr)
@ -1655,10 +1670,8 @@ void hp_5061_3001_cpu_device::write_non_common_reg(UINT16 addr , UINT16 v)
void hp_5061_3001_cpu_device::enter_isr(void)
{
// Set interrupt mask when entering an ISR
#ifndef NO_ISR_WITH_IM_CLEARED
BIT_SET(m_flags, HPHYBRID_IM_BIT);
#endif
// Set interrupt mask when entering an ISR
BIT_SET(m_flags, HPHYBRID_IM_BIT);
}
hp_5061_3011_cpu_device::hp_5061_3011_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)

View File

@ -20,8 +20,8 @@
// - Keyboard
// - T15 tape drive
// - Software list to load optional ROMs
// What's not yet in:
// - Beeper
// What's not yet in:
// - Better naming of tape drive image (it's now "magt", should be "t15")
// - Better documentation of this file
// What's wrong:
@ -34,6 +34,7 @@
#include "cpu/hphybrid/hphybrid.h"
#include "machine/hp_taco.h"
#include "bus/hp_optroms/hp_optrom.h"
#include "sound/beep.h"
#define BIT_MASK(n) (1U << (n))
@ -111,7 +112,9 @@ public:
m_io_key1(*this , "KEY1"),
m_io_key2(*this , "KEY2"),
m_io_key3(*this , "KEY3"),
m_t15(*this , "t15")
m_t15(*this , "t15"),
m_beeper(*this , "beeper"),
m_beep_timer(*this , "beep_timer")
{ }
UINT32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
@ -138,6 +141,7 @@ public:
DECLARE_READ16_MEMBER(kb_scancode_r);
DECLARE_READ16_MEMBER(kb_status_r);
DECLARE_WRITE16_MEMBER(kb_irq_clear_w);
TIMER_DEVICE_CALLBACK_MEMBER(beeper_off);
DECLARE_WRITE8_MEMBER(pa_w);
@ -156,6 +160,8 @@ private:
required_ioport m_io_key2;
required_ioport m_io_key3;
required_device<hp_taco_device> m_t15;
required_device<beep_device> m_beeper;
required_device<timer_device> m_beep_timer;
void set_video_mar(UINT16 mar);
void video_fill_buff(bool buff_idx);
@ -416,6 +422,8 @@ void hp9845b_state::machine_reset()
memset(&m_kb_state[ 0 ] , 0 , sizeof(m_kb_state));
m_kb_scancode = 0x7f;
m_kb_status = 0;
m_beeper->set_state(0);
}
void hp9845b_state::set_video_mar(UINT16 mar)
@ -512,7 +520,6 @@ void hp9845b_state::video_render_buff(unsigned video_scanline , unsigned line_in
UINT8 chargen_byte = m_chargen[ line_in_row | ((unsigned)charcode << 4) ];
UINT16 pixels;
// TODO: Handle selection of 2nd chargen
// TODO: Check if order of bits in "pixels" is ok
if ((ul_line && BIT(attrs , 3)) ||
@ -520,8 +527,11 @@ void hp9845b_state::video_render_buff(unsigned video_scanline , unsigned line_in
pixels = ~0;
} else if (char_blink && BIT(attrs , 2)) {
pixels = 0;
} else if (BIT(attrs , 4)) {
// Optional character generator ROM not installed, it reads as 1 everywhere
pixels = 0x7f << 1;
} else {
pixels = (UINT16)(chargen_byte & 0x7f) << 2;
pixels = (UINT16)(chargen_byte & 0x7f) << 1;
}
if (BIT(attrs , 1)) {
@ -1004,7 +1014,17 @@ WRITE16_MEMBER(hp9845b_state::kb_irq_clear_w)
BIT_CLR(m_kb_status, 0);
update_irq();
m_lpu->status_w(0);
// TODO: beeper start
if (BIT(data , 15)) {
// Start beeper
m_beep_timer->adjust(attotime::from_ticks(64, KEY_SCAN_OSCILLATOR / 512));
m_beeper->set_state(1);
}
}
TIMER_DEVICE_CALLBACK_MEMBER(hp9845b_state::beeper_off)
{
m_beeper->set_state(0);
}
WRITE8_MEMBER(hp9845b_state::pa_w)
@ -1129,21 +1149,39 @@ static MACHINE_CONFIG_START( hp9845b, hp9845b_state )
// Actual keyboard refresh rate should be KEY_SCAN_OSCILLATOR / 128 (2560 Hz)
MCFG_TIMER_DRIVER_ADD_PERIODIC("kb_timer" , hp9845b_state , kb_scan , attotime::from_hz(100))
// Beeper
MCFG_SPEAKER_STANDARD_MONO("mono")
MCFG_SOUND_ADD("beeper" , BEEP , KEY_SCAN_OSCILLATOR / 512)
MCFG_SOUND_ROUTE(ALL_OUTPUTS , "mono" , 1.00)
MCFG_TIMER_DRIVER_ADD("beep_timer" , hp9845b_state , beeper_off);
// Tape controller
MCFG_DEVICE_ADD("t15" , HP_TACO , 4000000)
MCFG_TACO_IRQ_HANDLER(WRITELINE(hp9845b_state , t15_irq_w))
MCFG_TACO_FLG_HANDLER(WRITELINE(hp9845b_state , t15_flg_w))
MCFG_TACO_STS_HANDLER(WRITELINE(hp9845b_state , t15_sts_w))
// In real machine there were 8 slots for LPU ROMs and 8 slots for PPU ROMs in
// right-hand side and left-hand side drawers, respectively.
// Here we do away with the distinction between LPU & PPU ROMs: in the end they
// are visible to both CPUs at the same addresses.
// For now we define just a couple of slots..
MCFG_DEVICE_ADD("drawer1", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer2", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
// In real machine there were 8 slots for LPU ROMs and 8 slots for PPU ROMs in
// right-hand side and left-hand side drawers, respectively.
// Here we do away with the distinction between LPU & PPU ROMs: in the end they
// are visible to both CPUs at the same addresses.
MCFG_DEVICE_ADD("drawer1", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer2", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer3", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer4", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer5", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer6", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer7", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_DEVICE_ADD("drawer8", HP_OPTROM_SLOT, 0)
MCFG_DEVICE_SLOT_INTERFACE(hp_optrom_slot_device, NULL, false)
MCFG_SOFTWARE_LIST_ADD("optrom_list", "hp9845b_rom")
MACHINE_CONFIG_END
@ -1253,6 +1291,6 @@ COMP( 1978, hp9845a, 0, 0, hp9845a, hp9845, driver_device, 0,
COMP( 1978, hp9845s, hp9845a, 0, hp9845a, hp9845, driver_device, 0, "Hewlett-Packard", "9845S", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1979, hp9835a, 0, 0, hp9835a, hp9845, driver_device, 0, "Hewlett-Packard", "9835A", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1979, hp9835b, hp9835a, 0, hp9835a, hp9845, driver_device, 0, "Hewlett-Packard", "9835B", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1980, hp9845b, 0, 0, hp9845b, hp9845b,driver_device, 0, "Hewlett-Packard", "9845B", MACHINE_NO_SOUND )
COMP( 1980, hp9845b, 0, 0, hp9845b, hp9845b,driver_device, 0, "Hewlett-Packard", "9845B", 0 )
COMP( 1980, hp9845t, hp9845b, 0, hp9845b, hp9845b,driver_device, 0, "Hewlett-Packard", "9845T", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1981, hp9845c, hp9845b, 0, hp9845b, hp9845b,driver_device, 0, "Hewlett-Packard", "9845C", MACHINE_IS_SKELETON | MACHINE_NOT_WORKING | MACHINE_NO_SOUND )