From 45b19d2899f303447ec2d83c52fe9ca770f11c6f Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Sat, 14 Nov 2020 14:01:05 +0100 Subject: ti99: Reimplementation of the Horizon RAMdisk card with all config options of the real hardware. --- src/devices/bus/ti99/gromport/cartridges.cpp | 5 +- src/devices/bus/ti99/peb/horizon.cpp | 775 +++++++++++++++------------ src/devices/bus/ti99/peb/horizon.h | 37 +- 3 files changed, 468 insertions(+), 349 deletions(-) diff --git a/src/devices/bus/ti99/gromport/cartridges.cpp b/src/devices/bus/ti99/gromport/cartridges.cpp index c703d331bf9..3735d850ec4 100644 --- a/src/devices/bus/ti99/gromport/cartridges.cpp +++ b/src/devices/bus/ti99/gromport/cartridges.cpp @@ -24,6 +24,7 @@ #define LOG_WRITE (1U<<7) // Write operation #define LOG_GROM (1U<<8) // GROM access #define LOG_RPK (1U<<9) // RPK handler +#define LOG_WARNW (1U<<10) // Warn when writing to cartridge space #define VERBOSE ( LOG_GENERAL | LOG_WARN ) #include "logmacro.h" @@ -538,7 +539,9 @@ void ti99_cartridge_pcb::write(offs_t offset, uint8_t data) { if (m_romspace_selected) { - if (m_ram_ptr == nullptr) LOGMASKED(LOG_WARN, "Cannot write to cartridge ROM space at %04x\n", offset | 0x6000); + // Do not warn by default; devices like Horizon will create a lot of + // meaningless warnings at this point + if (m_ram_ptr == nullptr) LOGMASKED(LOG_WARNW, "Cannot write to cartridge ROM space at %04x\n", offset | 0x6000); else { // Check if we have RAM in the ROM socket diff --git a/src/devices/bus/ti99/peb/horizon.cpp b/src/devices/bus/ti99/peb/horizon.cpp index 8d9aa8dd20a..5cf60c8574f 100644 --- a/src/devices/bus/ti99/peb/horizon.cpp +++ b/src/devices/bus/ti99/peb/horizon.cpp @@ -4,54 +4,122 @@ 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: - 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". + 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. + + 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 *****************************************************************************/ @@ -60,360 +128,369 @@ #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_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_NS(TI99_HORIZON, bus::ti99::peb, horizon_ramdisk_device, "ti99_horizon", "Horizon 4000B RAMdisk") namespace bus { namespace ti99 { namespace 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_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(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(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()) + { + // 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; + } + + // Just debugging stuff + if (page != m_current_page || m_bank != m_current_bank) { - *value = m_ros->pointer()[offset & 0x1fff]; - LOGMASKED(LOG_READ, "offset=%04x (Rambo) -> %02x\n", offset&0xffff, *value); + LOGMASKED(LOG_PAGE, "Bank %02x, page %02x\n", m_bank, page); + m_current_bank = m_bank; + m_current_page = page; } - if (in_cart_space(offset, m_genmod_fix)) + + 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 (ramaddress < m_ramsize) { - // 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); + if (write) + m_ram->write(ramaddress, *value); + else + *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"); } } -void horizon_ramdisk_device::write(offs_t offset, uint8_t data) +/* + CRU read operation. There is not such read feature on the Horizon. +*/ +void horizon_ramdisk_device::crureadz(offs_t offset, uint8_t *value) { - // 32K expansion - // According to the manual, "this memory is not affected by the HIDE switch" - if (m_32k_installed) + return; +} + +/* + 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) { - switch((offset & 0xe000)>>13) + if (bit < 8) { - 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; + m_crulatch_u4->write_bit(bit & 7, data & 1); } + else + { + m_crulatch_u3->write_bit(bit & 7, data & 1); + } + get_address_prefix(); } +} - if (m_hideswitch) return; +/* + 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() +{ + u8 latch1 = m_crulatch_u4->output_state(); + u8 latch2 = m_crulatch_u3->output_state(); - // I think RAMBO mode does not need the card to be selected - if (!m_selected && !m_rambo_mode) return; + // Latch 2 Latch 1 + // ..lbbbbp ppppppp. 512k + // ....lbbb bpppppp. 128k + // l=layer b=bank p=page - if (!m_rambo_mode) + if (m_128kx8) { - if (in_dsr_space(offset, m_genmod_fix)) - { - if ((offset & 0x1800) == 0x1800) - { - // 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); - } - else - { - // ROS - m_ros->pointer()[offset & 0x1fff] = data; - LOGMASKED(LOG_WRITE, "offset=%04x (ROS) <- %02x\n", offset&0xffff, data); - } - } + m_page = (latch1 >> 1) & 0x3f; + m_bank = ((latch1 >> 7) & 0x01) | ((latch2 << 1) & 0x1e); } else { - if (in_dsr_space(offset, m_genmod_fix)) - { - m_ros->pointer()[offset & 0x1fff] = data; - LOGMASKED(LOG_WRITE, "offset=%04x (Rambo) <- %02x\n", offset&0xffff, data); - } - 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 - 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_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::crureadz(offs_t offset, uint8_t *value) +void horizon_ramdisk_device::device_start(void) { - // There is no CRU read operation for the Horizon. - return; + machine().save().register_postload(save_prepost_delegate(FUNC(horizon_ramdisk_device::get_address_prefix),this)); } -void horizon_ramdisk_device::setbit(int& page, int pattern, bool set) +void horizon_ramdisk_device::device_reset(void) { - if (set) + 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); +} + +INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed ) +{ + if (param == 0) { - page |= pattern; + LOGMASKED(LOG_CONFIG, "Hideswitch changed to %d\n", newval); + m_hideswitch = (newval!=0); } else { - page &= ~pattern; + LOGMASKED(LOG_CONFIG, "Phoenix split setting changed to %d\n", newval); + m_phoenix_split = (newval!=0); } } -void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data) +/* + 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() { - 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; - - default: // bits 10-13 - if (bit != split_bit || !m_split_mode) - { - if (bit <= split_bit) setbit(m_page, 0x0200<<(bit-10), data!=0); - } - break; - } + int ramsize, dsrsize; + get_mem_size(ramsize, dsrsize); - 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)); - } - } - } + if (ramsize > 0) memset(m_ram->pointer(), 0, ramsize); + memset(m_dsrram->pointer(), 0, dsrsize); } -void horizon_ramdisk_device::device_start(void) -{ - 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)); -} -void horizon_ramdisk_device::device_reset(void) +void horizon_ramdisk_device::nvram_read(emu_file &file) { - m_cru_horizon = ioport("CRUHOR")->read(); - m_cru_phoenix = ioport("CRUPHOE")->read(); + int ramsize, dsrsize; + get_mem_size(ramsize, dsrsize); + + // NVRAM plus ROS, according to the current configuration + auto buffer = make_unique_clear(ramsize + dsrsize); - m_32k_installed = (ioport("HORIZON32")->read()!=0); + if (ramsize > 0) memset(m_ram->pointer(), 0, ramsize); + memset(m_dsrram->pointer(), 0, dsrsize); - m_split_mode = (ioport("HORIZONDUAL")->read()!=0); - m_timode = (ioport("HORIZONDUAL")->read()==1); + // Read complete file, at most ramsize+dsrsize + // Mind that the configuration may have changed + int filesize = file.read(&buffer[0], ramsize + dsrsize); + int nvramsize = filesize - dsrsize; - m_rambo_mode = false; - m_hideswitch = (ioport("HORIZONACT")->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); + } +} + +void horizon_ramdisk_device::nvram_write(emu_file &file) +{ + int ramsize, dsrsize; + get_mem_size(ramsize, dsrsize); - m_use_rambo = (ioport("RAMBO")->read()!=0); + // NVRAM plus ROS, according to the current configuration + auto buffer = make_unique_clear(ramsize + dsrsize); - m_genmod_fix = (ioport("GENMODFIX")->read()!=0); + memcpy(&buffer[0], m_ram->pointer(), ramsize); + memcpy(&buffer[ramsize], m_dsrram->pointer(), dsrsize); - m_page = 0; - m_selected = false; + // Store both parts in one file + file.write(buffer.get(), ramsize + dsrsize); } -INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed ) +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 +498,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 +508,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 +566,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 diff --git a/src/devices/bus/ti99/peb/horizon.h b/src/devices/bus/ti99/peb/horizon.h index d284e1ffbcb..0d9372d9109 100644 --- a/src/devices/bus/ti99/peb/horizon.h +++ b/src/devices/bus/ti99/peb/horizon.h @@ -18,6 +18,7 @@ #include "peribox.h" #include "machine/ram.h" +#include "machine/74259.h" namespace bus { namespace ti99 { namespace peb { @@ -45,23 +46,35 @@ protected: void nvram_write(emu_file &file) override; private: - void setbit(int& page, int pattern, bool set); - required_device m_ram; - required_device m_nvram; - required_device m_ros; + required_device m_dsrram; + required_device m_optram; + required_device m_crulatch_u4; + required_device m_crulatch_u3; - int m_page; + void get_mem_size(int& ramsize, int& dsrsize); + void get_address_prefix(); + void read_write(offs_t offset, uint8_t *value, bool write); - int m_cru_horizon; - int m_cru_phoenix; - bool m_timode; bool m_32k_installed; - bool m_split_mode; - bool m_rambo_mode; + bool m_phoenix_accessed; + bool m_dsr32k; + bool m_128kx8; + bool m_geneve_mode; + bool m_phoenix_split; bool m_hideswitch; - bool m_use_rambo; - bool m_genmod_fix; + bool m_rambo_supported; + + int m_page; + int m_bank; + int m_ramsize; + + int m_cru_base_horizon; + int m_cru_base_phoenix; + + // Debugging + int m_current_bank; + int m_current_page; }; } } } // end namespace bus::ti99::peb -- cgit v1.2.3