netronics/exp85.cpp: implemented rom mirroring, fixed interrupt handling timing (#11884)

* Fixed interrupt management (allows step execution in the monitor)
Implemented the ROM mirroring switch after boot and during interrupt handling.
* Removed logging, renamed and reordered member
variables and functions, removed defined,
introduced constexpr for constants.
* Corrected typos in instructions
on the top of the file.
* Replaced tags by object finders.
* Replaced the memory bank for a memory view for
switching between RAM and ROM mirroring.
This commit is contained in:
Federico 2024-01-23 16:11:23 +01:00 committed by GitHub
parent f26416a473
commit fa39b28906
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,6 +5,8 @@
Explorer 85
12/05/2009 Skeleton driver.
29/12/2023 Reworked driver to enable extended RAM memory,
MS BASIC and disable ROM mirror after boot/interrupt.
Setting Up
==========
@ -16,6 +18,17 @@
Once started, press Space. The system will start up.
All input must be in upper case.
Microsoft Basic is executed entering the following 2 commands
in the monitor prompt:
.XS nnnn-F87F mmmm-C000 <CR>
.G <CR>
where nnnn is the previous value of the stack pointer, and mmmm is the previous
value of the program counter.
Basic will request the amount of RAM memory available, 8192 (bytes) must be entered.
****************************************************************************/
/*
@ -23,8 +36,8 @@
TODO:
- dump of the hexadecimal keyboard monitor ROM
- disable ROM mirror after boot
- RAM expansions
- 64K RAM expansion
- Disk drive and CP/M OS support
*/
@ -41,24 +54,28 @@
#include "imagedev/cassette.h"
#include "sound/spkrdev.h"
namespace {
#define I8085A_TAG "u100"
#define I8155_TAG "u106"
#define I8355_TAG "u105"
static constexpr int LOW_MEMORY_ROM_MIRROR_ENTRY = 0;
static constexpr int LOW_MEMORY_RAM_ENTRY = 1;
class exp85_state : public driver_device
{
public:
exp85_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag)
, m_maincpu(*this, I8085A_TAG)
, m_maincpu(*this, "u100")
, m_i8355(*this, "u105")
, m_i8155(*this, "u106")
, m_rs232(*this, "rs232")
, m_cassette(*this, "cassette")
, m_speaker(*this, "speaker")
, m_rom(*this, I8085A_TAG)
, m_rom(*this, "u105") // the 8355 chip contains the monitor ROM
, m_low_memory_view(*this, "low_memory_view")
, m_is_preparing_interrupt_call(false)
, m_ignore_timer_out(true)
, m_tape_control(0)
, m_timer(nullptr)
{ }
void exp85(machine_config &config);
@ -67,12 +84,6 @@ public:
DECLARE_INPUT_CHANGED_MEMBER( trigger_rst75 );
private:
required_device<i8085a_cpu_device> m_maincpu;
required_device<rs232_port_device> m_rs232;
required_device<cassette_image_device> m_cassette;
required_device<speaker_sound_device> m_speaker;
required_memory_region m_rom;
virtual void machine_start() override;
uint8_t i8355_a_r();
@ -80,30 +91,57 @@ private:
int sid_r();
void sod_w(int state);
/* cassette state */
bool m_tape_control;
void status_out(u8 status);
void exp85_io(address_map &map);
void exp85_mem(address_map &map);
void to_change(int to);
TIMER_CALLBACK_MEMBER(trap_delay);
// Member variables
required_device<i8085a_cpu_device> m_maincpu;
required_device<i8355_device> m_i8355;
required_device<i8155_device> m_i8155;
required_device<rs232_port_device> m_rs232;
required_device<cassette_image_device> m_cassette;
required_device<speaker_sound_device> m_speaker;
required_memory_region m_rom;
memory_view m_low_memory_view;
bool m_is_preparing_interrupt_call;
bool m_ignore_timer_out;
bool m_tape_control;
emu_timer *m_timer;
};
/* Memory Maps */
/* Memory Maps */
void exp85_state::exp85_mem(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x07ff).bankr("bank1");
// Extended RAM or mapped monitor ROM (only during interrupt and reset)
map(0x0000, 0x1fff).view(m_low_memory_view);
// Microsoft Basic ROM
map(0xc000, 0xdfff).rom();
map(0xf000, 0xf7ff).rom();
map(0xf800, 0xf8ff).ram();
// Monitor ROM in the 8355 chip
map(0xf000, 0xf7ff).rom().region(m_rom, 0);
// 256 bytes of RAM of level A in the 8155 chip.
map(0xf800, 0xf8ff).rw(m_i8155, FUNC(i8155_device::memory_r), FUNC(i8155_device::memory_w));
// monitor ROM mirror
m_low_memory_view[LOW_MEMORY_ROM_MIRROR_ENTRY](0x0000, 0x07ff).rom().region(m_rom, 0);
// extended RAM
m_low_memory_view[LOW_MEMORY_RAM_ENTRY](0x0000, 0x1fff).ram();
}
void exp85_state::exp85_io(address_map &map)
{
map.unmap_value_high();
map.global_mask(0xff);
map(0xf0, 0xf3).rw(I8355_TAG, FUNC(i8355_device::io_r), FUNC(i8355_device::io_w));
map(0xf8, 0xfd).rw(I8155_TAG, FUNC(i8155_device::io_r), FUNC(i8155_device::io_w));
// map(0xfe, 0xff).rw(I8279_TAG, FUNC(i8279_device::read), FUNC(i8279_device::write));
map(0xf0, 0xf3).rw(m_i8355, FUNC(i8355_device::io_r), FUNC(i8355_device::io_w));
map(0xf8, 0xfd).rw(m_i8155, FUNC(i8155_device::io_r), FUNC(i8155_device::io_w));
}
/* Input Ports */
@ -111,6 +149,7 @@ void exp85_state::exp85_io(address_map &map)
INPUT_CHANGED_MEMBER( exp85_state::trigger_reset )
{
m_maincpu->set_input_line(INPUT_LINE_RESET, newval ? CLEAR_LINE : ASSERT_LINE);
m_low_memory_view.select(LOW_MEMORY_ROM_MIRROR_ENTRY);
}
INPUT_CHANGED_MEMBER( exp85_state::trigger_rst75 )
@ -119,7 +158,6 @@ INPUT_CHANGED_MEMBER( exp85_state::trigger_rst75 )
}
static INPUT_PORTS_START( exp85 )
PORT_START("SPECIAL")
PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYPAD ) PORT_NAME("R") PORT_CODE(KEYCODE_F1) PORT_CHANGED_MEMBER(DEVICE_SELF, exp85_state, trigger_reset, 0)
PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_KEYPAD ) PORT_NAME("I") PORT_CODE(KEYCODE_F2) PORT_CHANGED_MEMBER(DEVICE_SELF, exp85_state, trigger_rst75, 0)
@ -201,6 +239,41 @@ void exp85_state::sod_w(int state)
}
}
/* Memory mapping mgmt during reset and interrupt */
void exp85_state::status_out(u8 status)
{
// In the real hardware this is monitored via the IO/M, S0, S1
// and ALE output pins of the 8085.
// Since these pins are not emulated, then we must explicitly get the internal
// interrupt acknowledge state (0x23 or 0x26) and behave as the monitor expects.
auto current_pc = m_maincpu->pc();
if (status == 0x23 || status == 0x26)
{
// When an interrupt is triggered the low memory shall be set to the ROM mirror
m_is_preparing_interrupt_call = true;
m_low_memory_view.select(LOW_MEMORY_ROM_MIRROR_ENTRY);
}
else if (m_is_preparing_interrupt_call && (current_pc & 0xff00) == 0x0000)
{
// Avoids setting the lower memory back to RAM until
// it branches to the interrupt handler.
m_is_preparing_interrupt_call = false;
}
else if (!m_is_preparing_interrupt_call && (current_pc & 0xf000) == 0xf000)
{
// When the interrupt handler is executing and the address is >= 0xf000
// the low memory is mapped to RAM
m_low_memory_view.select(LOW_MEMORY_RAM_ENTRY);
}
}
//**************************************************************************
// INPUT PORTS
//**************************************************************************
/* Terminal Interface */
static DEVICE_INPUT_DEFAULTS_START( terminal )
@ -215,16 +288,47 @@ DEVICE_INPUT_DEFAULTS_END
void exp85_state::machine_start()
{
/* setup memory banking */
membank("bank1")->configure_entry(0, m_rom->base() + 0xf000);
membank("bank1")->configure_entry(1, m_rom->base());
membank("bank1")->set_entry(0);
/* clear the trap line that is asserted on start */
m_maincpu->set_input_line(I8085_TRAP_LINE, CLEAR_LINE);
/* setup memory mirror switch */
m_low_memory_view.select(LOW_MEMORY_ROM_MIRROR_ENTRY);
save_item(NAME(m_tape_control));
m_timer = timer_alloc(FUNC(exp85_state::trap_delay), this);
}
void exp85_state::to_change(int to)
{
// In the 8155 implementation the TIMER-OUT line (to) is
// asserted by default on initialization; this generates a spurious exception
// avoided here via an ignore flag.
if (m_ignore_timer_out)
{
m_ignore_timer_out = false;
return;
}
// The 8155 has a max TIMER-IN to TIMER-OUT delay of 400ns (datasheet page 3-256). In a real system
// this delay was measured in ~70ns. This is enough for the next instruction cycle to start
// and for the interrupt to be acknowledged one instruction later. This effect is fundamental
// for the ROM monitor stepping functionality, since it depends heavily on interrupting when the
// next instruction of the user program is being executed.
// MAME allows to set a timer of 70ns but that is not enough to get the CPU scheduled for at least 1 cycle
// before the interrupt is serviced, so we program a timer to expire beyond a full clock cycle.
m_timer->adjust(m_i8155->clocks_to_attotime(2), to);
}
TIMER_CALLBACK_MEMBER(exp85_state::trap_delay)
{
// The 8155 TIMER-OUT line is connected to the TRAP input line of the 8085;
// that line is set from the start high, and since there is no change in that line, the
// interrup is never requested (datasheet, page 6-13).
m_maincpu->set_input_line(I8085_TRAP_LINE, param == 1 ? ASSERT_LINE : CLEAR_LINE);
}
/* Machine Driver */
void exp85_state::exp85(machine_config &config)
{
/* basic machine hardware */
@ -233,17 +337,25 @@ void exp85_state::exp85(machine_config &config)
m_maincpu->set_addrmap(AS_IO, &exp85_state::exp85_io);
m_maincpu->in_sid_func().set(FUNC(exp85_state::sid_r));
m_maincpu->out_sod_func().set(FUNC(exp85_state::sod_w));
m_maincpu->out_status_func().set(FUNC(exp85_state::status_out));
/* sound hardware */
SPEAKER(config, "mono").front_center();
SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 0.25);
/* devices */
I8155(config, I8155_TAG, 6.144_MHz_XTAL/2);
i8355_device &i8355(I8355(config, I8355_TAG, 6.144_MHz_XTAL/2));
i8355.in_pa().set(FUNC(exp85_state::i8355_a_r));
i8355.out_pa().set(FUNC(exp85_state::i8355_a_w));
// The Explorer-85 uses a 6.144MHz clock, but the Intel 8085 divides by 2 this
// input frequency to give the processor internal operating frequency.
// The bus is connected to the CLK (out) of the 8085, running also
// at 6.144MHz divided by 2.
I8155(config, m_i8155, 6.144_MHz_XTAL/2);
m_i8155->out_to_callback().set(FUNC(exp85_state::to_change));
I8355(config, m_i8355, 6.144_MHz_XTAL/2);
m_i8355->in_pa().set(FUNC(exp85_state::i8355_a_r));
m_i8355->out_pa().set(FUNC(exp85_state::i8355_a_w));
CASSETTE(config, m_cassette);
m_cassette->set_default_state(CASSETTE_STOPPED | CASSETTE_MOTOR_ENABLED | CASSETTE_SPEAKER_ENABLED);
@ -251,31 +363,23 @@ void exp85_state::exp85(machine_config &config)
RS232_PORT(config, "rs232", default_rs232_devices, "terminal").set_option_device_input_defaults("terminal", DEVICE_INPUT_DEFAULTS_NAME(terminal));
/* internal ram */
RAM(config, RAM_TAG).set_default_size("256").set_extra_options("4K");
}
/* ROMs */
ROM_START( exp85 )
ROM_REGION( 0x10000, I8085A_TAG, 0 )
ROM_REGION( 0x10000, "u100", 0 ) // Microsoft Basic
ROM_DEFAULT_BIOS("eia")
ROM_LOAD( "c000.bin", 0xc000, 0x0800, CRC(73ce4aad) SHA1(2c69cd0b6c4bdc92f4640bce18467e4e99255bab) )
ROM_LOAD( "c800.bin", 0xc800, 0x0800, CRC(eb3fdedc) SHA1(af92d07f7cb7533841b16e1176401363176857e1) )
ROM_LOAD( "d000.bin", 0xd000, 0x0800, CRC(c10c4a22) SHA1(30588ba0b27a775d85f8c581ad54400c8521225d) )
ROM_LOAD( "d800.bin", 0xd800, 0x0800, CRC(dfa43ef4) SHA1(56a7e7a64928bdd1d5f0519023d1594cacef49b3) )
ROM_SYSTEM_BIOS( 0, "eia", "EIA Terminal" )
ROMX_LOAD( "ex 85.u105", 0xf000, 0x0800, CRC(1a99d0d9) SHA1(57b6d48e71257bc4ef2d3dddc9b30edf6c1db766), ROM_BIOS(0) )
ROM_SYSTEM_BIOS( 1, "hex", "Hex Keyboard" )
ROMX_LOAD( "1kbd.u105", 0xf000, 0x0800, NO_DUMP, ROM_BIOS(1) )
ROM_REGION( 0x800, I8355_TAG, ROMREGION_ERASE00 )
/* ROM_DEFAULT_BIOS("terminal")
ROM_SYSTEM_BIOS( 0, "terminal", "Terminal" )
ROMX_LOAD( "eia.u105", 0xf000, 0x0800, CRC(1a99d0d9) SHA1(57b6d48e71257bc4ef2d3dddc9b30edf6c1db766), ROM_BIOS(0) )
ROM_SYSTEM_BIOS( 1, "hexkbd", "Hex Keyboard" )
ROMX_LOAD( "hex.u105", 0xf000, 0x0800, NO_DUMP, ROM_BIOS(1) )*/
ROM_REGION( 0x800, "u105", 0 ) // Explorer 85 Monitor
ROM_SYSTEM_BIOS( 0, "eia", "EIA Terminal" ) // Serial terminal ROM
ROMX_LOAD( "ex 85.u105", 0x0000, 0x0800, CRC(1a99d0d9) SHA1(57b6d48e71257bc4ef2d3dddc9b30edf6c1db766), ROM_BIOS(0) )
ROM_SYSTEM_BIOS( 1, "hex", "Hex Keyboard" ) // Keypad ROM (not available)
ROMX_LOAD( "1kbd.u105", 0x0000, 0x0800, NO_DUMP, ROM_BIOS(1) )
ROM_END
} // anonymous namespace