ti99: Reimplementation of the Horizon RAMdisk card with all config options of the real hardware.

This commit is contained in:
Michael Zapf 2020-11-14 14:01:05 +01:00
parent 8500209910
commit 45b19d2899
3 changed files with 492 additions and 373 deletions

View File

@ -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

View File

@ -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
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 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.
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.
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".
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 most peripheral cards,
the 4000-5fff area requires CRU bit 0 to be set.
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.
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()
{
int size = 2097152*(1 << ioport("HORIZONSIZE")->read());
memset(m_nvram->pointer(), 0, size);
memset(m_ros->pointer(), 0, ROSSIZE);
}
//-------------------------------------------------
// nvram_read - called to read NVRAM from the
// .nv file
//-------------------------------------------------
void horizon_ramdisk_device::nvram_read(emu_file &file)
{
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);
}
}
//-------------------------------------------------
// nvram_write - called to write NVRAM to the
// .nv file
//-------------------------------------------------
void horizon_ramdisk_device::nvram_write(emu_file &file)
{
int nvramsize = 2097152*(1 << ioport("HORIZONSIZE")->read());
auto buffer = make_unique_clear<uint8_t []>(nvramsize + ROSSIZE);
memcpy(&buffer[0], m_nvram->pointer(), nvramsize);
memcpy(&buffer[nvramsize], m_ros->pointer(), ROSSIZE);
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"
if (m_32k_installed)
{
switch((offset & 0xe000)>>13)
{
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];
return;
default:
break;
}
}
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_rambo_mode)
{
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);
}
}
}
else
{
if (in_dsr_space(offset, m_genmod_fix))
{
*value = m_ros->pointer()[offset & 0x1fff];
LOGMASKED(LOG_READ, "offset=%04x (Rambo) -> %02x\n", offset&0xffff, *value);
}
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);
}
}
read_write(offset, value, false);
}
void horizon_ramdisk_device::write(offs_t offset, uint8_t data)
{
read_write(offset, &data, true);
}
/*
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)
{
// 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.
// HRD4000(B) used this decoding (U13); earlier Horizon cards did not
// implement it
if ((offset & 0x70000) != 0x70000) return;
// 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
m_ram->pointer()[offset & 0x1fff] = data;
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;
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;
}
}
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))
// 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;
}
// Lower part of U7
if ((dsr && (b_a == 0x1800)) || ((sect == 3) && m_rambo_supported && m_crulatch_u3->q7_r()))
{
if ((offset & 0x1800) == 0x1800)
// 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())
{
// 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);
// 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)
{
LOGMASKED(LOG_PAGE, "Bank %02x, page %02x\n", m_bank, page);
m_current_bank = m_bank;
m_current_page = page;
}
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)
{
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
{
// ROS
m_ros->pointer()[offset & 0x1fff] = data;
LOGMASKED(LOG_WRITE, "offset=%04x (ROS) <- %02x\n", offset&0xffff, data);
}
}
}
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);
}
LOGMASKED(LOG_ORAM, "offset=%04x, page=%02x, bank=%02x %s outside of RAM space\n", offset&0xffff, page, m_bank, write? "write" : "read");
}
}
/*
CRU read operation. There is not such read feature on the Horizon.
*/
void horizon_ramdisk_device::crureadz(offs_t offset, uint8_t *value)
{
// There is no CRU read operation for the Horizon.
return;
}
void horizon_ramdisk_device::setbit(int& page, int pattern, bool set)
/*
CRU write operation.
*/
void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data)
{
if (set)
// 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)
{
page |= pattern;
if (bit < 8)
{
m_crulatch_u4->write_bit(bit & 7, data & 1);
}
else
{
page &= ~pattern;
m_crulatch_u3->write_bit(bit & 7, data & 1);
}
get_address_prefix();
}
}
void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data)
/*
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()
{
int size = ioport("HORIZONSIZE")->read();
int split_bit = size + 10;
int splitpagebit = 0x0200 << size;
u8 latch1 = m_crulatch_u4->output_state();
u8 latch2 = m_crulatch_u3->output_state();
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;
// Latch 2 Latch 1
// ..lbbbbp ppppppp. 512k
// ....lbbb bpppppp. 128k
// l=layer b=bank p=page
default: // bits 10-13
if (bit != split_bit || !m_split_mode)
if (m_128kx8)
{
if (bit <= split_bit) setbit(m_page, 0x0200<<(bit-10), data!=0);
}
break;
}
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));
m_page = (latch1 >> 1) & 0x3f;
m_bank = ((latch1 >> 7) & 0x01) | ((latch2 << 1) & 0x1e);
}
else
{
// In Geneve mode, switch between both RAMdisks by
// using the bit number of the last CRU access
setbit(m_page, splitpagebit, (bit>7));
}
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::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));
machine().save().register_postload(save_prepost_delegate(FUNC(horizon_ramdisk_device::get_address_prefix),this));
}
void horizon_ramdisk_device::device_reset(void)
{
m_cru_horizon = ioport("CRUHOR")->read();
m_cru_phoenix = ioport("CRUPHOE")->read();
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);
m_32k_installed = (ioport("HORIZON32")->read()!=0);
m_split_mode = (ioport("HORIZONDUAL")->read()!=0);
m_timode = (ioport("HORIZONDUAL")->read()==1);
m_rambo_mode = false;
m_hideswitch = (ioport("HORIZONACT")->read()!=0);
m_use_rambo = (ioport("RAMBO")->read()!=0);
m_genmod_fix = (ioport("GENMODFIX")->read()!=0);
m_page = 0;
m_selected = false;
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 )
{
LOGMASKED(LOG_CONFIG, "hideswitch changed %d\n", newval);
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);
}
}
/*
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 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::nvram_read(emu_file &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);
if (ramsize > 0) memset(m_ram->pointer(), 0, ramsize);
memset(m_dsrram->pointer(), 0, dsrsize);
// 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;
// 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);
// 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
file.write(buffer.get(), ramsize + dsrsize);
}
void horizon_ramdisk_device::get_mem_size(int& ramsize, int& dsrsize)
{
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_DIPSETTING( 0x00, DEF_STR( Off ) )
PORT_DIPSETTING( 0x01, "TI mode" )
PORT_DIPSETTING( 0x02, "Geneve mode" )
PORT_START( "MODE" )
PORT_DIPNAME( 0x01, 0x00, "JP4 Split mode" )
PORT_DIPSETTING( 0x00, "TI mode" )
PORT_DIPSETTING( 0x01, "Geneve mode" )
PORT_START( "HORIZONACT" )
PORT_DIPNAME( 0x01, 0x00, "Horizon hideswitch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, horizon_ramdisk_device, hs_changed, 0)
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, DEF_STR( On ) )
PORT_START( "HORIZON32" )
PORT_CONFNAME( 0x01, 0x00, "Horizon 32 KiB upgrade" )
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( "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

View File

@ -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<ram_device> m_ram;
required_device<ram_device> m_nvram;
required_device<ram_device> m_ros;
required_device<ram_device> m_dsrram;
required_device<ram_device> m_optram;
required_device<ls259_device> m_crulatch_u4;
required_device<ls259_device> m_crulatch_u3;
void get_mem_size(int& ramsize, int& dsrsize);
void get_address_prefix();
void read_write(offs_t offset, uint8_t *value, bool write);
bool m_32k_installed;
bool m_phoenix_accessed;
bool m_dsr32k;
bool m_128kx8;
bool m_geneve_mode;
bool m_phoenix_split;
bool m_hideswitch;
bool m_rambo_supported;
int m_page;
int m_bank;
int m_ramsize;
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_hideswitch;
bool m_use_rambo;
bool m_genmod_fix;
int m_cru_base_horizon;
int m_cru_base_phoenix;
// Debugging
int m_current_bank;
int m_current_page;
};
} } } // end namespace bus::ti99::peb