summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99/peb/horizon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/ti99/peb/horizon.cpp')
-rw-r--r--src/devices/bus/ti99/peb/horizon.cpp800
1 files changed, 461 insertions, 339 deletions
diff --git a/src/devices/bus/ti99/peb/horizon.cpp b/src/devices/bus/ti99/peb/horizon.cpp
index 8d9aa8dd20a..d594f445e5b 100644
--- a/src/devices/bus/ti99/peb/horizon.cpp
+++ b/src/devices/bus/ti99/peb/horizon.cpp
@@ -4,416 +4,512 @@
Horizon Ramdisk
- This emulation realizes the latest development, the HRD 4000, which could
- host up to 16 MiB of SRAM. Real cards rarely had more than 1.5 MiB since
- the SRAM used on the card is rather expensive.
+ This emulation realizes the latest development, the HRD 4000B, which can
+ host up to 16 MiB of SRAM. The SRAM is buffered with a battery pack. Also,
+ there is an option for an additional 32 KiB of unbuffered memory.
- The SRAM is buffered with a battery pack. Also, there is an option for
- an additional 32 KiB of unbuffered memory.
-
- The driver (ROS) of the ramdisk is stored in another buffered 8 KiB SRAM.
+ The driver (ROS) of the ramdisk is stored in another buffered SRAM.
+ Originally set to be 8 KiB, the 4000B offers four banks of 8 KiB storage
+ for the driver.
The Horizon RAMdisk comes with a disk containing the ROS and a configuration
- program (CFG). The latest version is ROS 8.14.
+ program (CFG).
Technical details:
- In the tradition (Horizon) mode, memory is organized as 2 KiB pages. The
+ The card is able to handle 128K*8 and 512K*8 SRAM chips, allowing a total
+ of 16 MiB memory space. The card may be equipped with 0 to 32 SRAM chips;
+ in this implementation, a multiple of 4 can be selected in that range.
+
+ The Horizon 4000(B) implements the AMA decoding, unlike the the earlier
+ models.
+
+ Sockets must be populated contiguously from 0 to 15. Only when all sockets
+ are populated, further SRAM circuits may be soldered on top of the bottom
+ layer chips, again contiguously from 0 to 15.
+
+ The respective CS* lines of the SRAM chips on the top layer must be
+ connected with separate wires to the selector chip.
+
+ === Normal mode ===
+
+ In the normal (Horizon) mode, memory is organized as 2 KiB pages. The
pages are selected via CRU bits and visible in the address area 5800 - 5fff.
- The area 4000-57ff is occupied by the ROS. As with all peripheral cards,
- the 4000-5fff area requires a CRU bit to be set (usually bit 0 of this
- card's CRU base).
-
- Next releases of the HRD included new modes. The RAMBO (RAM Block operator)
- mode gathers four pages to a single 8 KiB page that is visible in the
- area 6000-7fff (cartridge space). Note that due to a possible design glitch,
- each RAMBO page n covers Horizon pages 4n, 4n+2, 4n+1, 4n+3 in this sequence.
- We emulate this by swapping two CRU lines.
-
- The RAMDisk may be split in two separate drives, which is called the
- Phoenix extension. This is particularly important for use in the Geneve.
- As a bootable drive, the RAMdisk must not
- exceed 256 KiB; consequently, the RAM area is split, and one part realizes
- the boot drive while the other is still available for data. Also, there
- is a mechanism for selecting the parts of the card: The TI setting allows
- to select two CRU addresses, one for each part. In the Geneve mode, only
- one CRU address is used (1400 or 1600), and the part is selected by the
- fact that one disk uses CRU bits higher than 8, while the other uses the
- bits lower than 8.
+ The area 4000-57ff is occupied by the ROS. As with most peripheral cards,
+ the 4000-5fff area requires CRU bit 0 to be set.
- The card is able to handle 128K*8 and 512K*8 SRAM chips, allowing a total
- of 16 MiB memory space. Unfortunately, a bug causes the configuration
- program to crash when used with more than 2 MiB. Although the card was
- quite popular, this bug was not found because most cards were sold with
- less than 2 MiB onboard. As the community is still alive we can hope for
- a fix for this problem; so we make the size configurable.
+ CRU bits:
+
+ 15 14 13 12 11 10 9 8 7 6 1 0
+ +-----------------------------------------------------------------+
+ | RAMBO | DSR | SRAM | B | B | B | B | P | P | P ... | P | Ena | 512Kx8
+ | | bank | Layer | Bank (0-15) | Page (0-255) | ble | RAM
+ +-----------------------------------------------------------------+
+
+ 15 14 13 12 11 10 9 8 7 6 1 0
+ +-----------------------------------------------------------------+
+ | RAMBO | DSR | - | - | SRAM | B | B | B | B | P | ... | P | Ena | 128Kx8
+ | | bank | | | Layer| Bank (0-15) | Page(0-63) | ble | RAM
+ +-----------------------------------------------------------------+
+
+ The difference between layer, banks, and pages results from the board
+ design. Logically, the layer, bank, and page information can be seen as a
+ 13-bit page number for 512Kx8 RAMs or a 11-bit page number for 128Kx8 SRAMs.
+
+ The 8-bit page number results from the capacity of a 512Kx8 chip (256 pages
+ of 2 KiB), while the 6-bit number applies to 128Kx8 chips (64 pages of
+ 2 KiB). The bank selects the chip socket. The layer allows for selecting
+ the second layer of chips, piggy-backed on the bottom layer.
+
+ === RAMBO ===
+
+ The RAMBO (RAM Block operator) mode gathers four pages to a single 8 KiB
+ page in the area 6000-7fff (cartridge space). Due to an arguable design
+ glitch in the real hardware (swapped A3/A4 lines), the four pages do not
+ appear in their original order but as the base number plus 0 / 2 / 1 / 3.
+ This can only be noticed when working in both modes (writing to pages in the
+ normal mode, then mapping the group of pages via RAMBO).
+
+ The above CRU bit chart also applies here, but bits 1 and 2 (least signi-
+ ficant page number bits) are ignored.
+
+ === Phoenix ===
+
+ Using jumper JP2 and a DIP switch setting, the card can be configured to the
+ Phoenix mode. This mode was originally intended to allow the Geneve to boot
+ from the Horizon card, because the early boot ROMs did not support a boot
+ drive larger than 256 KiB. Consequently, the RAM area is split, and one part
+ realizes the boot drive while the other is still available for data.
+
+ The Phoenix configuration splits the card in the middle, assigning the
+ lower 8 sockets (bottom M0-M7, top M16-M23) to the Phoenix part, and the
+ upper 8 sockets (bottom M8-M15, top M24-M31) to the normal part.
+
+ The most significant bank selection bit (12 or 10) is ignored, because this
+ bit determines the choice of upper and lower part. The selection is done
+ in this way instead:
+
+ In "TI mode", two separate CRU base addresses must be selected by the DIP
+ switches. When an access occurs on the normal CRU address, the bit is set,
+ while an access to the Phoenix CRU address resets the bit. Accesses occur
+ when the page is set.
+
+ In the "Geneve mode", only one CRU address is used. A CRU write access
+ to bits 8-15 sets the bit to 0, and a CRU write access to bits 0-7 sets
+ the bit to 1. Since the bits keep their semantics, care must be taken in
+ which order the bits are written to. For this reason, the Phoenix mode is
+ rarely used.
- According to the Genmod setup instructions, the Horizon Ramdisks do not
- decode the AMA/AMB/AMC lines, so it is important to modify them when
- running with the Genmod system. This can be done with the configuration
- setting "Genmod fix".
+ Later investigations proved that the GeneveOS could be loaded normally from
+ the large ramdisk, and the Phoenix mode became obsolete. It is included for
+ test purposes and for the sake of completeness.
+
+ The "Split mode" for TI and Geneve can be used without splitting the RAM
+ space, i.e. a Phoenix address may be set without JP2 set to split. In that
+ case, only the DSR RAM is split in two halves (if its size is 32KiB).
+
+ References
+
+ [1] Software Developer's Guide to the HRD4000B, Sept. 2020
+ [2] Schematics for the HRD4000B
+ [3] Horizon 4000 Ramdisk Construction Guide, Bud Mills Services, 1992
+
+ Original design of the hardware (2000) by Horizon Computer, Ltd., 1986
+ Extensions (3000, 4000) by Bud Mills Services, 1989, 1992
+ HRD 4000B by Jim Fetzner et al., 2019
+
+ Michael Zapf
+ November 2020
*****************************************************************************/
#include "emu.h"
#include "horizon.h"
-#define LOG_WARN (1U<<1) // Warnings
-#define LOG_CONFIG (1U<<2) // Configuration
-#define LOG_READ (1U<<3)
-#define LOG_WRITE (1U<<4)
-#define LOG_CRU (1U<<5)
+#define LOG_WARN (1U << 1) // Warnings
+#define LOG_CONFIG (1U << 2) // Configuration
+#define LOG_32K (1U << 3) // 32K optional RAM r/w
+#define LOG_DSR (1U << 4) // DSR
+#define LOG_RAM (1U << 5) // RAM chips
+#define LOG_ORAM (1U << 6) // outside of RAM chips
+#define LOG_CRU (1U << 7) // CRU
+#define LOG_PAGE (1U << 8) // Page access
-#define VERBOSE ( LOG_CONFIG | LOG_WARN )
+#define VERBOSE (LOG_GENERAL | LOG_CONFIG | LOG_WARN)
#include "logmacro.h"
-DEFINE_DEVICE_TYPE_NS(TI99_HORIZON, bus::ti99::peb, horizon_ramdisk_device, "ti99_horizon", "Horizon 4000 Ramdisk")
+DEFINE_DEVICE_TYPE(TI99_HORIZON, bus::ti99::peb::horizon_ramdisk_device, "ti99_horizon", "Horizon 4000B RAMdisk")
-namespace bus { namespace ti99 { namespace peb {
+namespace bus::ti99::peb {
-#define RAMREGION "ram32k"
-#define ROSREGION "ros8k"
-#define NVRAMREGION "nvram"
+#define CRULATCH1_TAG "u4_latch"
+#define CRULATCH2_TAG "u3_latch"
-#define MAXSIZE 16777216
-#define ROSSIZE 8192
+#define DSRRAM_TAG "u9_dsrram"
+#define OPT32K_TAG "m32_ram"
+#define RAM16M_TAG "m31_m0_ram"
horizon_ramdisk_device::horizon_ramdisk_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock):
device_t(mconfig, TI99_HORIZON, tag, owner, clock),
device_ti99_peribox_card_interface(mconfig, *this),
device_nvram_interface(mconfig, *this),
- m_ram(*this, RAMREGION),
- m_nvram(*this, NVRAMREGION),
- m_ros(*this, ROSREGION),
- m_page(0),
- m_cru_horizon(0),
- m_cru_phoenix(0),
- m_timode(false),
- m_32k_installed(false),
- m_split_mode(false),
- m_rambo_mode(false),
+ m_ram(*this, RAM16M_TAG),
+ m_dsrram(*this, DSRRAM_TAG),
+ m_optram(*this, OPT32K_TAG),
+ m_crulatch_u4(*this, CRULATCH1_TAG),
+ m_crulatch_u3(*this, CRULATCH2_TAG),
+ m_32k_installed(true),
+ m_phoenix_accessed(false),
+ m_dsr32k(true),
+ m_128kx8(true),
+ m_geneve_mode(false),
+ m_phoenix_split(false),
m_hideswitch(false),
- m_use_rambo(false),
- m_genmod_fix(false)
+ m_rambo_supported(false),
+ m_modified(false),
+ m_page(0),
+ m_bank(0),
+ m_ramsize(0),
+ m_cru_base_horizon(0),
+ m_cru_base_phoenix(0)
{
}
-//-------------------------------------------------
-// nvram_default - called to initialize NVRAM to
-// its default state
-//-------------------------------------------------
-
-void horizon_ramdisk_device::nvram_default()
+void horizon_ramdisk_device::readz(offs_t offset, uint8_t *value)
{
- int size = 2097152*(1 << ioport("HORIZONSIZE")->read());
- memset(m_nvram->pointer(), 0, size);
- memset(m_ros->pointer(), 0, ROSSIZE);
+ read_write(offset, value, false);
}
-//-------------------------------------------------
-// nvram_read - called to read NVRAM from the
-// .nv file
-//-------------------------------------------------
-
-void horizon_ramdisk_device::nvram_read(emu_file &file)
+void horizon_ramdisk_device::write(offs_t offset, uint8_t data)
{
- int size = 2097152*(1 << ioport("HORIZONSIZE")->read());
-
- // NVRAM plus ROS
- auto buffer = make_unique_clear<uint8_t []>(MAXSIZE + ROSSIZE);
-
- memset(m_nvram->pointer(), 0, size);
- memset(m_ros->pointer(), 0, ROSSIZE);
-
- // We assume the last 8K is ROS
- int filesize = file.read(&buffer[0], MAXSIZE+ROSSIZE);
- int nvramsize = filesize - ROSSIZE;
-
- // If there is a reasonable size
- if (nvramsize >= 0)
- {
- // Copy from buffer to NVRAM and ROS
- memcpy(m_nvram->pointer(), &buffer[0], nvramsize);
- memcpy(m_ros->pointer(), &buffer[nvramsize], ROSSIZE);
- }
+ read_write(offset, &data, true);
}
-//-------------------------------------------------
-// nvram_write - called to write NVRAM to the
-// .nv file
-//-------------------------------------------------
-
-void horizon_ramdisk_device::nvram_write(emu_file &file)
+/*
+ Read from or write to the card. This can involve the
+ a) optional 32K RAM (M32)
+ b) DSR RAM (U9)
+ c) set of RAM chips (M0-M31)
+*/
+void horizon_ramdisk_device::read_write(offs_t offset, uint8_t *value, bool write)
{
- int nvramsize = 2097152*(1 << ioport("HORIZONSIZE")->read());
+ // If AMA/B/C != 111, do not allow access. This "AMA decoding" was done
+ // by default by TI cards, but third-party cards did not comply in all cases.
- auto buffer = make_unique_clear<uint8_t []>(nvramsize + ROSSIZE);
- memcpy(&buffer[0], m_nvram->pointer(), nvramsize);
- memcpy(&buffer[nvramsize], m_ros->pointer(), ROSSIZE);
+ // HRD4000(B) used this decoding (U13); earlier Horizon cards did not
+ // implement it
+ if ((offset & 0x70000) != 0x70000) return;
- file.write(buffer.get(), nvramsize + ROSSIZE);
-}
-
-void horizon_ramdisk_device::readz(offs_t offset, uint8_t *value)
-{
// 32K expansion
- // According to the manual, "this memory is not affected by the HIDE switch"
+ // According to [3], this memory is not affected by the HIDE switch
+ // Also, it cannot be mapped out by a CRU bit
+ //
+ // offset a14 a13
+ // ----------------
+ // | E000 | 0 0
+ // ----------------
+ // | C000 | 0 1
+ // ----------------
+ // | A000 | 1 0
+ // ----------------
+ // | 2000 | 1 1
+ // ----------------
+ //
+ // a14=/c*/e, a13=/a*/e (pins of the memory chip, not the address bus)
+
+ int sect = (offset>>13) & 0x07; // 8K sections in the 64K address space
+ int offset8k = (offset & 0x1fff);
+ int offset2k = (offset & 0x07ff);
+
if (m_32k_installed)
{
- switch((offset & 0xe000)>>13)
+ // Decoding by U12 and U11
+ int a14 = ((sect != 6) && (sect != 7))? 0x4000 : 0; // a14: 11x
+ int a13 = ((sect != 5) && (sect != 7))? 0x2000 : 0; // a13: 1x1
+
+ if ((sect == 1) || (sect == 5) || (sect == 6) || (sect == 7))
{
- case 1: // 2000-3fff
- *value = m_ram->pointer()[offset & 0x1fff];
- return;
- case 5: // a000-bfff
- *value = m_ram->pointer()[(offset & 0x1fff) | 0x2000];
- return;
- case 6: // c000-dfff
- *value = m_ram->pointer()[(offset & 0x1fff) | 0x4000];
- return;
- case 7: // e000-ffff
- *value = m_ram->pointer()[(offset & 0x1fff) | 0x6000];
+ if (write)
+ m_optram->write(offset8k | a14 | a13, *value);
+ else
+ *value = m_optram->read(offset8k | a14 | a13);
+
+ LOGMASKED(LOG_32K, "offset=%04x (32K) %s %02x\n", offset&0xffff, write? "<-" : "->", *value);
return;
- default:
- break;
}
}
- if (m_hideswitch) return;
+ // DSR RAM is not affected by SW2 either.
+ // CRU bit 0 must be set to 1 to activate it.
+ // Decoding by U12 and U7
+ int b_a = offset & 0x1800;
+ if (m_rambo_supported && m_crulatch_u3->q7_r()) b_a &= 0x0f00; // clear A3 (RAMBO mode)
- // I think RAMBO mode does not need the card to be selected
- if (!m_selected && !m_rambo_mode) return;
+ bool dsr = m_crulatch_u4->q0_r() && (sect == 2);
- if (!m_rambo_mode)
+ if (dsr && (b_a != 0x1800))
{
- if (in_dsr_space(offset, m_genmod_fix))
- {
- if ((offset & 0x1800) == 0x1800)
- {
- // NVRAM page of size 2 KiB
- *value = m_nvram->pointer()[(m_page << 11)|(offset & 0x07ff)];
- LOGMASKED(LOG_READ, "offset=%04x, page=%04x -> %02x\n", offset&0xffff, m_page, *value);
- }
- else
- {
- // ROS
- *value = m_ros->pointer()[offset & 0x1fff];
- LOGMASKED(LOG_READ, "offset=%04x (ROS) -> %02x\n", offset&0xffff, *value);
- }
- }
+ // DSR access
+ // All cards 2000-4000 have 8K RAM here
+ // The 4000B supports up to 32K; 4 banks can be selected
+ int a13 = (m_crulatch_u3->q6_r())? 0x2000 : 0;
+ int a14 = (!m_phoenix_accessed)? 0x4000 : 0;
+
+ if (write)
+ m_dsrram->write(offset8k | (m_dsr32k? (a13 | a14) : 0), *value);
+ else
+ *value = m_dsrram->read(offset8k | (m_dsr32k? (a13 | a14) : 0));
+
+ LOGMASKED(LOG_DSR, "offset=%04x (DSR) %s %02x\n", offset&0xffff, write? "<-" : "->", *value);
+ return;
}
- else
+
+ // Lower part of U7
+ if ((dsr && (b_a == 0x1800)) || ((sect == 3) && m_rambo_supported && m_crulatch_u3->q7_r()))
{
- if (in_dsr_space(offset, m_genmod_fix))
+ // Access to a selected RAM
+ // Either in normal mode (on 5800) or in RAMBO mode (6000-7fff)
+ int page = m_page;
+ int ramaddress = offset2k;
+
+ // RAMBO mode
+ // If we are here in RAMBO mode, we are accessing 6000-7fff,
+ // because for sect 2 (4000-5fff), b_a was changed to be != 1800
+ if (m_rambo_supported && m_crulatch_u3->q7_r())
{
- *value = m_ros->pointer()[offset & 0x1fff];
- LOGMASKED(LOG_READ, "offset=%04x (Rambo) -> %02x\n", offset&0xffff, *value);
+ // Replace the last two bits of the page number by address bits A3 and A4
+ // ...xx...........
+ // According to the specs, the lines must be swapped
+ // Hence, the 2k page number sequence in the 6000 space is
+ // base + 0 2 1 3
+ int a3_4 = (((offset & 0x0800)>>10) | ((offset & 0x1000)>>12));
+ page = (page & 0xfc) | a3_4;
}
- if (in_cart_space(offset, m_genmod_fix))
- {
- // In RAMBO mode the page numbers are multiples of 4
- // (encompassing 4 Horizon pages)
- // We clear away the rightmost two bits
- *value = m_nvram->pointer()[((m_page&0xfffc)<<11) | (offset & 0x1fff)];
- LOGMASKED(LOG_READ, "offset=%04x, page=%04x (Rambo) -> %02x\n", offset&0xffff, m_page, *value);
- }
- }
-}
-void horizon_ramdisk_device::write(offs_t offset, uint8_t data)
-{
- // 32K expansion
- // According to the manual, "this memory is not affected by the HIDE switch"
- if (m_32k_installed)
- {
- switch((offset & 0xe000)>>13)
+ // Just debugging stuff
+ if (page != m_current_page || m_bank != m_current_bank)
{
- case 1: // 2000-3fff
- m_ram->pointer()[offset & 0x1fff] = data;
- return;
- case 5: // a000-bfff
- m_ram->pointer()[(offset & 0x1fff) | 0x2000] = data;
- return;
- case 6: // c000-dfff
- m_ram->pointer()[(offset & 0x1fff) | 0x4000] = data;
- return;
- case 7: // e000-ffff
- m_ram->pointer()[(offset & 0x1fff) | 0x6000] = data;
- return;
- default:
- break;
+ LOGMASKED(LOG_PAGE, "Bank %02x, page %02x\n", m_bank, page);
+ m_current_bank = m_bank;
+ m_current_page = page;
}
- }
-
- if (m_hideswitch) return;
- // I think RAMBO mode does not need the card to be selected
- if (!m_selected && !m_rambo_mode) return;
+ if (m_128kx8)
+ // bb bbbp pppp p... .... ....
+ ramaddress |= ((page << 11) | (m_bank << 17));
+ else
+ // bbbb bppp pppp p... .... ....
+ ramaddress |= ((page << 11) | (m_bank << 19));
- if (!m_rambo_mode)
- {
- if (in_dsr_space(offset, m_genmod_fix))
+ if (ramaddress < m_ramsize)
{
- if ((offset & 0x1800) == 0x1800)
+ if (write)
{
- // NVRAM page of size 2 KiB
- m_nvram->pointer()[(m_page << 11)|(offset & 0x07ff)] = data;
- LOGMASKED(LOG_WRITE, "offset=%04x, page=%04x <- %02x\n", offset&0xffff, m_page, data);
+ m_modified = true;
+ m_ram->write(ramaddress, *value);
}
else
- {
- // ROS
- m_ros->pointer()[offset & 0x1fff] = data;
- LOGMASKED(LOG_WRITE, "offset=%04x (ROS) <- %02x\n", offset&0xffff, data);
- }
+ *value = m_ram->read(ramaddress);
+
+ LOGMASKED(LOG_RAM, "offset=%04x, page=%02x, bank=%02x %s %02x\n", offset&0xffff, page, m_bank, write? "<-" : "->", *value);
}
+ else
+ LOGMASKED(LOG_ORAM, "offset=%04x, page=%02x, bank=%02x %s outside of RAM space\n", offset&0xffff, page, m_bank, write? "write" : "read");
}
- else
+}
+
+/*
+ CRU read operation. There is not such read feature on the Horizon.
+*/
+void horizon_ramdisk_device::crureadz(offs_t offset, uint8_t *value)
+{
+}
+
+/*
+ CRU write operation.
+*/
+void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data)
+{
+ // Horizon and Phoenix set to off = ff00 will never match
+ if (((offset & 0x1f00) != m_cru_base_horizon) && ((offset & 0x1f00) != m_cru_base_phoenix))
+ return;
+
+ int bit = (offset >> 1) & 0x0f;
+
+ // Set the latch U5
+ // For Geneve mode, it is set when a bit 8-15 is accessed; for the TI mode
+ // it is set for an access to the Phoenix CRU area
+ m_phoenix_accessed = m_geneve_mode? (bit >= 8) : ((offset & 0x1f00) == m_cru_base_phoenix);
+
+ // Set the latches U4 and U3 (unless the SW2 hideswitch is turned on)
+ if (m_hideswitch==false)
{
- if (in_dsr_space(offset, m_genmod_fix))
+ if (bit < 8)
{
- m_ros->pointer()[offset & 0x1fff] = data;
- LOGMASKED(LOG_WRITE, "offset=%04x (Rambo) <- %02x\n", offset&0xffff, data);
+ m_crulatch_u4->write_bit(bit & 7, data & 1);
}
- if (in_cart_space(offset, m_genmod_fix))
+ else
{
- // In RAMBO mode the page numbers are multiples of 4
- // (encompassing 4 Horizon pages)
- // We clear away the rightmost two bits
- m_nvram->pointer()[((m_page&0xfffc)<<11) | (offset & 0x1fff)] = data;
- LOGMASKED(LOG_WRITE, "offset=%04x, page=%04x (Rambo) <- %02x\n", offset&0xffff, m_page, data);
+ m_crulatch_u3->write_bit(bit & 7, data & 1);
}
+ get_address_prefix();
}
}
-void horizon_ramdisk_device::crureadz(offs_t offset, uint8_t *value)
+/*
+ Translate latch settings to page and bank.
+ Also called after savestate load.
+ This method is used to avoid reading all settings every time that a memory
+ access occurs.
+*/
+void horizon_ramdisk_device::get_address_prefix()
{
- // There is no CRU read operation for the Horizon.
- return;
-}
+ u8 latch1 = m_crulatch_u4->output_state();
+ u8 latch2 = m_crulatch_u3->output_state();
-void horizon_ramdisk_device::setbit(int& page, int pattern, bool set)
-{
- if (set)
+ // Latch 2 Latch 1
+ // ..lbbbbp ppppppp. 512k
+ // ....lbbb bpppppp. 128k
+ // l=layer b=bank p=page
+
+ if (m_128kx8)
{
- page |= pattern;
+ m_page = (latch1 >> 1) & 0x3f;
+ m_bank = ((latch1 >> 7) & 0x01) | ((latch2 << 1) & 0x1e);
}
else
{
- page &= ~pattern;
+ m_page = ((latch1 >> 1) & 0x7f) | ((latch2 << 7) & 0x80);
+ m_bank = (latch2 >> 1) & 0x1f;
+ }
+
+ // Phoenix jumper JP2
+ if (m_phoenix_split)
+ {
+ m_bank &= 0x08; // Clear the D bit
+ if (!m_phoenix_accessed) m_bank |= 0x08;
}
}
-void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data)
+void horizon_ramdisk_device::device_start(void)
{
- int size = ioport("HORIZONSIZE")->read();
- int split_bit = size + 10;
- int splitpagebit = 0x0200 << size;
-
- if (((offset & 0xff00)==m_cru_horizon)||((offset & 0xff00)==m_cru_phoenix))
- {
- int bit = (offset >> 1) & 0x0f;
- LOGMASKED(LOG_CRU, "CRU write bit %d <- %d\n", bit, data);
- switch (bit)
- {
- case 0:
- m_selected = (data!=0);
- LOGMASKED(LOG_CRU, "Activate ROS = %d\n", m_selected);
- break;
- case 1:
- // Swap the lines so that the access with RAMBO is consistent
- if (!m_rambo_mode) setbit(m_page, 0x0002, data!=0);
- break;
- case 2:
- // Swap the lines so that the access with RAMBO is consistent
- if (!m_rambo_mode) setbit(m_page, 0x0001, data!=0);
- break;
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- setbit(m_page, 0x0001 << (bit-1), data!=0);
- break;
- case 14:
- break;
- case 15:
- if (m_use_rambo)
- {
- m_rambo_mode = (data != 0);
- LOGMASKED(LOG_CRU, "RAMBO = %d\n", m_rambo_mode);
- }
- break;
+ machine().save().register_postload(save_prepost_delegate(FUNC(horizon_ramdisk_device::get_address_prefix),this));
+}
- default: // bits 10-13
- if (bit != split_bit || !m_split_mode)
- {
- if (bit <= split_bit) setbit(m_page, 0x0200<<(bit-10), data!=0);
- }
- break;
- }
+void horizon_ramdisk_device::device_reset(void)
+{
+ m_cru_base_horizon = ioport("CRUHOR")->read();
+ m_cru_base_phoenix = ioport("CRUPHOE")->read();
+ m_32k_installed = (ioport("OPT32K")->read()!=0);
+ m_phoenix_split = (ioport("PHOENIX")->read()!=0);
+ m_hideswitch = (ioport("HIDESW2")->read()!=0);
+ m_dsr32k = (ioport("DSRSIZE")->read()!=0);
+ m_128kx8 = (ioport("CHIPTYPE")->read()==0);
+ m_geneve_mode = (ioport("MODE")->read()!=0);
+ m_rambo_supported = (ioport("RAMBO")->read()!=0);
+
+ int dsrsize = 0;
+ get_mem_size(m_ramsize, dsrsize);
+ LOGMASKED(LOG_CONFIG, "Horizon card memory: %d bytes\n", m_ramsize);
+}
- if (m_split_mode)
- {
- if (m_timode)
- {
- // In TI mode, switch between both RAMDisks using the CRU address
- setbit(m_page, splitpagebit, ((offset & 0xff00)==m_cru_phoenix));
- }
- else
- {
- // In Geneve mode, switch between both RAMdisks by
- // using the bit number of the last CRU access
- setbit(m_page, splitpagebit, (bit>7));
- }
- }
+INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed )
+{
+ if (param == 0)
+ {
+ LOGMASKED(LOG_CONFIG, "Hideswitch changed to %d\n", newval);
+ m_hideswitch = (newval!=0);
+ }
+ else
+ {
+ LOGMASKED(LOG_CONFIG, "Phoenix split setting changed to %d\n", newval);
+ m_phoenix_split = (newval!=0);
}
}
-void horizon_ramdisk_device::device_start(void)
+/*
+ NVRAM support
+ The size of the file is num_chips * mem_per_chip + dsr_size
+ The contents of the RAM banks are stored first, then the
+ DSR chip contents are appended
+
+ 0 end
+ ____________________
+ | RAM | DSR |
+ --------------------
+*/
+void horizon_ramdisk_device::nvram_default()
{
- m_cru_horizon = 0;
- m_cru_phoenix = 0;
-
- save_item(NAME(m_page));
- save_item(NAME(m_cru_horizon));
- save_item(NAME(m_cru_phoenix));
- save_item(NAME(m_timode));
- save_item(NAME(m_32k_installed));
- save_item(NAME(m_split_mode));
- save_item(NAME(m_rambo_mode));
- save_item(NAME(m_hideswitch));
- save_item(NAME(m_use_rambo));
+ int ramsize, dsrsize;
+ get_mem_size(ramsize, dsrsize);
+
+ if (ramsize > 0) memset(m_ram->pointer(), 0, ramsize);
+ memset(m_dsrram->pointer(), 0, dsrsize);
}
-void horizon_ramdisk_device::device_reset(void)
+
+bool horizon_ramdisk_device::nvram_read(util::read_stream &file)
{
- m_cru_horizon = ioport("CRUHOR")->read();
- m_cru_phoenix = ioport("CRUPHOE")->read();
+ int ramsize, dsrsize;
+ get_mem_size(ramsize, dsrsize);
- m_32k_installed = (ioport("HORIZON32")->read()!=0);
+ // NVRAM plus ROS, according to the current configuration
+ auto buffer = make_unique_clear<uint8_t []>(ramsize + dsrsize);
- m_split_mode = (ioport("HORIZONDUAL")->read()!=0);
- m_timode = (ioport("HORIZONDUAL")->read()==1);
+ if (ramsize > 0) memset(m_ram->pointer(), 0, ramsize);
+ memset(m_dsrram->pointer(), 0, dsrsize);
- m_rambo_mode = false;
- m_hideswitch = (ioport("HORIZONACT")->read()!=0);
+ // Read complete file, at most ramsize+dsrsize
+ // Mind that the configuration may have changed
+ auto const [err, filesize] = util::read(file, &buffer[0], ramsize + dsrsize);
+ if (err)
+ return false;
+ int nvramsize = int(filesize) - dsrsize;
- m_use_rambo = (ioport("RAMBO")->read()!=0);
+ // At least the DSR must be complete
+ if (nvramsize >= 0)
+ {
+ // Copy from buffer to NVRAM and ROS
+ if (nvramsize > 0) memcpy(m_ram->pointer(), &buffer[0], nvramsize);
+ memcpy(m_dsrram->pointer(), &buffer[nvramsize], dsrsize);
- m_genmod_fix = (ioport("GENMODFIX")->read()!=0);
+ return true;
+ }
- m_page = 0;
- m_selected = false;
+ return false;
}
-INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed )
+bool horizon_ramdisk_device::nvram_write(util::write_stream &file)
+{
+ int ramsize, dsrsize;
+ get_mem_size(ramsize, dsrsize);
+
+ // NVRAM plus ROS, according to the current configuration
+ auto buffer = make_unique_clear<uint8_t []>(ramsize + dsrsize);
+
+ memcpy(&buffer[0], m_ram->pointer(), ramsize);
+ memcpy(&buffer[ramsize], m_dsrram->pointer(), dsrsize);
+
+ // Store both parts in one file
+ auto const [err, filesize] = util::write(file, buffer.get(), ramsize + dsrsize);
+ return !err;
+}
+
+bool horizon_ramdisk_device::nvram_can_write() const
+{
+ // Do not save if nothing was written. This is helpful to avoid loss of the
+ // contents when the settings were found to be different, and the emulation
+ // has to be restarted after restoring the settings.
+ return m_modified;
+}
+
+
+void horizon_ramdisk_device::get_mem_size(int& ramsize, int& dsrsize)
{
- LOGMASKED(LOG_CONFIG, "hideswitch changed %d\n", newval);
- m_hideswitch = (newval!=0);
+ int chipsize = 128*1024*((ioport("CHIPTYPE")->read()*3)+1);
+ ramsize = (ioport("CHIPCOUNT")->read()*4) * chipsize;
+ dsrsize = ((ioport("DSRSIZE")->read()*3)+1)*8192;
}
/*
@@ -421,9 +517,9 @@ INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed )
*/
INPUT_PORTS_START( horizon )
PORT_START( "CRUHOR" )
- PORT_DIPNAME( 0x1f00, 0x1200, "Horizon CRU base" )
- PORT_DIPSETTING( 0x0000, DEF_STR( Off ) )
- PORT_DIPSETTING( 0x1000, "1000" )
+ PORT_DIPNAME( 0xff00, 0x1200, "SW1 Horizon CRU base" )
+ PORT_DIPSETTING( 0xff00, DEF_STR( Off ) )
+ PORT_DIPSETTING( 0x1000, "1000" ) // 1100 and 1300 not available
PORT_DIPSETTING( 0x1200, "1200" )
PORT_DIPSETTING( 0x1400, "1400" )
PORT_DIPSETTING( 0x1500, "1500" )
@@ -431,41 +527,57 @@ INPUT_PORTS_START( horizon )
PORT_DIPSETTING( 0x1700, "1700" )
PORT_START( "CRUPHOE" )
- PORT_DIPNAME( 0x1f00, 0x0000, "Phoenix CRU base" )
- PORT_DIPSETTING( 0x0000, DEF_STR( Off ) )
- PORT_DIPSETTING( 0x1400, "1400" )
+ PORT_DIPNAME( 0xff00, 0xff00, "SW1 Phoenix CRU base" )
+ PORT_DIPSETTING( 0xff00, DEF_STR( Off ) )
+ PORT_DIPSETTING( 0x1400, "1400" ) // must not be used when selected for Horizon
PORT_DIPSETTING( 0x1600, "1600" )
- PORT_START( "HORIZONDUAL" )
- PORT_DIPNAME( 0x03, 0x00, "Horizon ramdisk split" )
+ PORT_START( "MODE" )
+ PORT_DIPNAME( 0x01, 0x00, "JP4 Split mode" )
+ PORT_DIPSETTING( 0x00, "TI mode" )
+ PORT_DIPSETTING( 0x01, "Geneve mode" )
+
+ PORT_START( "HIDESW2" )
+ PORT_DIPNAME( 0x01, 0x00, "SW2 Hideswitch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, horizon_ramdisk_device, hs_changed, 0)
PORT_DIPSETTING( 0x00, DEF_STR( Off ) )
- PORT_DIPSETTING( 0x01, "TI mode" )
- PORT_DIPSETTING( 0x02, "Geneve mode" )
+ PORT_DIPSETTING( 0x01, DEF_STR( On ) )
- PORT_START( "HORIZONACT" )
- PORT_DIPNAME( 0x01, 0x00, "Horizon hideswitch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, horizon_ramdisk_device, hs_changed, 0)
+ PORT_START( "PHOENIX" )
+ PORT_DIPNAME( 0x01, 0x00, "JP2 Phoenix split" ) PORT_CHANGED_MEMBER(DEVICE_SELF, horizon_ramdisk_device, hs_changed, 1)
PORT_DIPSETTING( 0x00, DEF_STR( Off ) )
PORT_DIPSETTING( 0x01, DEF_STR( On ) )
- PORT_START( "HORIZON32" )
- PORT_CONFNAME( 0x01, 0x00, "Horizon 32 KiB upgrade" )
+// --------------------------------------------------------------------
+
+ PORT_START( "CHIPTYPE" )
+ PORT_CONFNAME( 0x01, 0x00, "Memory circuit type" )
+ PORT_CONFSETTING( 0x00, "128Kx8")
+ PORT_CONFSETTING( 0x01, "512Kx8")
+
+ PORT_START( "CHIPCOUNT" )
+ PORT_CONFNAME( 0x0f, 0x04, "Memory circuit count" )
+ PORT_CONFSETTING( 0x00, "0")
+ PORT_CONFSETTING( 0x01, "4")
+ PORT_CONFSETTING( 0x02, "8")
+ PORT_CONFSETTING( 0x03, "12")
+ PORT_CONFSETTING( 0x04, "16")
+ PORT_CONFSETTING( 0x05, "20")
+ PORT_CONFSETTING( 0x06, "24")
+ PORT_CONFSETTING( 0x07, "28")
+ PORT_CONFSETTING( 0x08, "32")
+
+ PORT_START( "DSRSIZE" )
+ PORT_CONFNAME( 0x01, 0x00, "DSR memory size" )
+ PORT_CONFSETTING( 0x00, "8 KiB" )
+ PORT_CONFSETTING( 0x01, "32 KiB" )
+
+ PORT_START( "OPT32K" )
+ PORT_CONFNAME( 0x01, 0x00, "Optional 32 KiB memory" )
PORT_CONFSETTING( 0x00, DEF_STR( Off ))
PORT_CONFSETTING( 0x01, DEF_STR( On ))
PORT_START( "RAMBO" )
- PORT_CONFNAME( 0x01, 0x01, "Horizon RAMBO" )
- PORT_CONFSETTING( 0x00, DEF_STR( Off ))
- PORT_CONFSETTING( 0x01, DEF_STR( On ))
-
- PORT_START( "HORIZONSIZE" )
- PORT_CONFNAME( 0x03, 0x00, "Horizon size" )
- PORT_CONFSETTING( 0x00, "2 MiB")
- PORT_CONFSETTING( 0x01, "4 MiB")
- PORT_CONFSETTING( 0x02, "8 MiB")
- PORT_CONFSETTING( 0x03, "16 MiB")
-
- PORT_START( "GENMODFIX" )
- PORT_CONFNAME( 0x01, 0x00, "Horizon Genmod fix" )
+ PORT_CONFNAME( 0x01, 0x00, "RAMBO support" )
PORT_CONFSETTING( 0x00, DEF_STR( Off ))
PORT_CONFSETTING( 0x01, DEF_STR( On ))
@@ -473,9 +585,19 @@ INPUT_PORTS_END
void horizon_ramdisk_device::device_add_mconfig(machine_config &config)
{
- RAM(config, NVRAMREGION).set_default_size("16M");
- RAM(config, ROSREGION).set_default_size("8K");
- RAM(config, RAMREGION).set_default_size("32K").set_default_value(0);
+ // It could make sense to use buffered_ram (BUFF_RAM) for the 16M RAM and
+ // the DSR RAM which has its own NVRAM handler. However, we want to have
+ // a configurable emulation, and the config switches are not available
+ // before the NVRAM handlers of the subdevices kick in. It could be done
+ // if the NVRAM files always use the same (maximum) size.
+
+ RAM(config, RAM16M_TAG).set_default_size("16M");
+ RAM(config, DSRRAM_TAG).set_default_size("32K");
+ RAM(config, OPT32K_TAG).set_default_size("32K").set_default_value(0);
+
+ // CRU latches
+ LS259(config, m_crulatch_u4);
+ LS259(config, m_crulatch_u3);
}
ioport_constructor horizon_ramdisk_device::device_input_ports() const
@@ -483,4 +605,4 @@ ioport_constructor horizon_ramdisk_device::device_input_ports() const
return INPUT_PORTS_NAME(horizon);
}
-} } } // end namespace bus::ti99::peb
+} // end namespace bus::ti99::peb