mirror of
https://github.com/holub/mame
synced 2025-07-05 09:57:47 +03:00
Merge pull request #931 from fulivi/hp9845_dev
Hp9845: hopefully fixed interrupt handling in hp hybrid processor & minor enhancements
This commit is contained in:
commit
baf3f115d2
@ -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)
|
||||
|
@ -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 )
|
||||
|
Loading…
Reference in New Issue
Block a user