summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99/peb/pgram.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/ti99/peb/pgram.cpp')
-rw-r--r--src/devices/bus/ti99/peb/pgram.cpp564
1 files changed, 564 insertions, 0 deletions
diff --git a/src/devices/bus/ti99/peb/pgram.cpp b/src/devices/bus/ti99/peb/pgram.cpp
new file mode 100644
index 00000000000..007f168ab63
--- /dev/null
+++ b/src/devices/bus/ti99/peb/pgram.cpp
@@ -0,0 +1,564 @@
+// license:LGPL-2.1+
+// copyright-holders:Michael Zapf
+/*******************************************************************************
+ PGRAM(+) Memory expansion and GROM simulator
+
+ The PGRAM card is a battery-buffered RAM card. It also contains a circuitry
+ to simulate GROMs (TMC0430), which can also be programmed, thus better
+ called GRAMs.
+
+ Memory chips of the board:
+
+ u21: 43256LP (SRAM 32Kx8) -- GROMs 4-7
+ u22: 6264LP (SRAM 8Kx8) -- GROM 3
+ u23: 43256LP (SRAM 32Kx8) -- 16 KiB DSR (driver) / 16 KiB RAM
+
+ The PGRAM+ is an extension of this card, offering 192 KiB memory:
+
+ u21: e.g. BS62LV1027 (SRAM 128Kx8) -- GROMs 4-7 (4 banks)
+ u22: 43256LP (SRAM 32Kx8) -- GROM 3 (4 banks)
+ u23: 43256LP (SRAM 32Kx8) -- 16 KiB DSR (driver) / 16 KiB RAM
+
+ The socket for u21 is already prepared for the larger package of the
+ 128Kx8 SRAM chip (32 pins). No modifications are needed; the smaller chip
+ can simply be plugged into the larger socket. For u22, a jumper is included
+ on the board for connecting the address line to the bus (PGRAM+)
+ or to high level (PGRAM).
+
+ A real-time clock is also included on the board:
+ u1: MM58167A
+
+ The GROM address counter is implemented by four 74LS161 chips.
+
+ A 3V battery is used to buffer the SRAMs.
+
+ The 74LS259 latch (u14) stores CRU settings and controls select lines.
+
+ Memory/port mapping
+ -------------------
+ The card can be configured to respond to CRU addresses 1000-1700 with the
+ exception of 1100, which is used by most disk controller cards. The
+ address is set by one of seven switches (SW1).
+
+ CRU bits (relative to start address):
+
+ 0 - Map DSR into CPU space 4000-5FFF
+ 1 - Enable GRAM and RAM
+ 2 - Write protect GRAM/RAM
+ 3 - DSR/RAM bank selection
+ 4 - DSR/RAM bank selection (see below)
+
+ The GRAM simulation responds to the usual GROM address base 9800. However,
+ only GROMs 3-7 are simulated; GROMs 0-2 are located in the console. Setting
+ the GROM address is done by writing both bytes of the 16-bit address to
+ address 9C02 (high/low). Reading the address is not possible, but this is
+ provided by the console GROMs. Reading the contents of the GRAM simulation
+ is done by reading a byte from address 9800, which also advances the address
+ counter. Writing to the GRAM is achieved by writing to address 9C00.
+
+ For the PGRAM+ card, four GROM banks are available:
+ Bank 0: 98x0/9Cx0/9Cx2
+ Bank 1: 98x4/9Cx4/9Cx4
+ Bank 2: 98x8/9Cx8/9Cx8
+ Bank 3: 98xC/9CxC/9CxC
+
+ Due to incomplete decoding, the second digit from the right may take any
+ value.
+
+ The real-time clock is mapped to addresses 8640-865E (even addresses only).
+ 8640 = Register 0
+ 865e = Register 15
+
+ The 32K RAM circuit (u23) is used for the DSR (device service routine, i.e.
+ the driver) and 16 KiB RAM. The DSR area is located in the lower half of the
+ address space, while the RAM area is located in the upper half.
+
+ The DSR area is mapped as two selectable 8K blocks into 4000-5FFF.
+ The RAM area is mapped as two selectable 8K blocks into 6000-7FFF.
+
+ +------------+ 0000
+ | DSR bank 0 |
+ +------------+ 2000
+ | DSR bank 1 |
+ +------------+ 4000
+ | RAM bank 0 |
+ +------------+ 6000
+ | RAM bank 1 |
+ +------------+ 7FFF
+
+ The selection of either bank of the DSR space is done by writing a value
+ to an address of 4000/4004/4008... for selecting bank 0, and to 4002/4006/...
+ for selecting bank 1. For the RAM area, the same scheme is applied to
+ 6000/6004/... and 6002/6006/... The selection is buffered by the flipflop
+ u3 for both RAM and DSR, hence it should make no difference whether to
+ write to 6000 or to 4000.
+
+ CRU bits 3 and 4 are fed into the PRE* and CLR* inputs of the flipflop.
+
+ Bits 3 4
+ 0 0 -> not recommended (typically, bit 4 wins)
+ 0 1 -> set lower 8K
+ 1 0 -> set higher 8K
+ 1 1 -> allow switching by write access
+
+ The PGRAM+ card does not allow for expanding this area. This is indeed a
+ limitation, as it does not allow us to store several cartridges with
+ a GROM and ROM part into the PGRAM+.
+
+ Switch SW2 deactivates the card in case the contents prevent a proper
+ startup of the TI console.
+
+ Michael Zapf
+ March 2020
+
+*******************************************************************************/
+
+#include "emu.h"
+#include "pgram.h"
+
+#define LOG_WARN (1U << 1)
+#define LOG_WP (1U << 2)
+#define LOG_DSR (1U << 3)
+#define LOG_RAM (1U << 4)
+#define LOG_GRAM (1U << 5)
+#define LOG_GRAMADDR (1U << 6)
+#define LOG_CRU (1U << 7)
+#define LOG_BANK (1U << 8)
+
+#define VERBOSE (LOG_GENERAL | LOG_WARN)
+
+#include "logmacro.h"
+
+#define GRAM4567_TAG "u21_gram4567"
+#define GRAM3_TAG "u22_gram3"
+#define DSRRAM_TAG "u23_dsrram"
+#define CLOCK_TAG "u1_rtc"
+#define BANKFF_TAG "u3_bankff"
+#define CRULATCH_TAG "u14_latch"
+
+#define COUNT0_TAG "u13_counter"
+#define COUNT1_TAG "u11_counter"
+#define COUNT2_TAG "u12_counter"
+#define COUNT3_TAG "u10_counter"
+
+DEFINE_DEVICE_TYPE(TI99_PGRAM, bus::ti99::peb::pgram_device, "ti99_pgram", "PGRAM(+) memory card")
+
+namespace bus::ti99::peb {
+
+pgram_device::pgram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock):
+ device_t(mconfig, TI99_PGRAM, tag, owner, clock),
+ device_ti99_peribox_card_interface(mconfig, *this),
+ m_gram3(*this, GRAM3_TAG),
+ m_gram4567(*this, GRAM4567_TAG),
+ m_dsrram(*this, DSRRAM_TAG),
+ m_clock(*this, CLOCK_TAG),
+ m_crulatch(*this, CRULATCH_TAG),
+ m_bankff(*this, BANKFF_TAG),
+ m_count0(*this, COUNT0_TAG),
+ m_count1(*this, COUNT1_TAG),
+ m_count2(*this, COUNT2_TAG),
+ m_count3(*this, COUNT3_TAG),
+ m_lowbyte(false)
+{
+}
+
+/*
+ Read access to the card. This comprises reading from the RAM circuit for
+ DSR and free space, for GRAM reading, and the real-time clock.
+
+ Decoding is done by the 74ls138 and 74ls139 circuits on the board.
+*/
+void pgram_device::readz(offs_t offset, uint8_t *value)
+{
+ if (!m_active) return;
+
+ // GROMs: 1001 1w.. .... ..00
+ if ((offset & 0xfc03)==0x9800)
+ {
+ gram_read((offset>>2)&0x03, value);
+ }
+ else
+ {
+ // DSR/RAM: 01.
+ if ((offset & 0xc000)==0x4000)
+ {
+ dsr_ram_read(offset & 0x3fff, value);
+ }
+ else
+ {
+ // RTC: 1000 0110 01.. ...0
+ if ((offset & 0xffc1)==0x8640)
+ {
+ uint8_t val = m_clock->read((offset >> 1) & 0x001f);
+ *value = val;
+ }
+ }
+ }
+}
+
+/*
+ Write access to the card. Same comments apply as for the read access.
+*/
+void pgram_device::write(offs_t offset, uint8_t data)
+{
+ if (!m_active) return;
+
+ // GROMs: 1001 11.. .... ..a0
+ if ((offset & 0xfc01)==0x9c00)
+ {
+ if ((offset & 0x0002)!=0)
+ set_gram_address(data);
+ else
+ gram_write((offset>>2)&0x03, data);
+ }
+ else
+ {
+ // DSR/RAM: 01.
+ if ((offset & 0xc000)==0x4000)
+ dsr_ram_write(offset & 0x3fff, data);
+ else
+ {
+ // RTC: 1000 0110 01.. ...0
+ if ((offset & 0xffc1)==0x8640)
+ m_clock->write((offset >> 1) & 0x001f, data);
+ }
+ }
+}
+
+/*
+ Read access to the 32K RAM circuit (u23). This one is used to provide the
+ DSR (driver) in the memory area 4000-5fff, and to provide free space in the
+ area 6000-7fff. A second bank can be selected for both DSR and RAM using
+ CRU bits 3 and 4.
+*/
+void pgram_device::dsr_ram_read(offs_t offset, uint8_t *value)
+{
+ // ..0. .... .... .... = DSR
+ // ..1. .... .... .... = RAM
+ bool dsr = ((offset & 0x2000)==0);
+
+ // Note: On power-up, the ls259 latch is reset, and bits 4 and 3 lead 0
+ // to both PRE* and CLR* of the 7474. This would normally cause the bank
+ // select line to be 1 (unstable).
+
+ // Reset the clock
+ m_bankff->clock_w(1);
+
+ // Latch may have changed; update FF asynchronously
+ m_bankff->clear_w(m_crulatch->q3_r());
+ m_bankff->preset_w(m_crulatch->q4_r());
+
+ offs_t base = (offset & 0x2000)<<1; // DSR:0000, RAM:4000
+ offs_t address = base | (offset & 0x1fff) | (m_bankff->output_r()? 0x2000 : 0);
+
+ if ((dsr && m_crulatch->q0_r()) || (!dsr && m_crulatch->q1_r()))
+ {
+ *value = m_dsrram->read(address);
+
+ if (address&1)
+ {
+ uint16_t b0 = m_dsrram->read(address&0xfffe) << 8 | *value;
+
+ if (dsr)
+ LOGMASKED(LOG_DSR, "%04x (bank %d) -> %04x\n",(offset&0xfffe)|0x4000, m_bankff->output_r()? 1:0, b0);
+ else
+ LOGMASKED(LOG_RAM, "%04x (bank %d) -> %04x\n",(offset&0xfffe)|0x4000, m_bankff->output_r()? 1:0, b0);
+ }
+ }
+}
+
+/*
+ Write access to the 32K RAM circuit (u23). See above for information about
+ the DSR and RAM space.
+ A flipflop (7474) is used to control access to the banks of both DSR and
+ RAM. The state of the flipflop is controlled by CRU bits 3 and 4. When both
+ bits are set, the flipflop can be set or reset by address line 14 (2^1).
+*/
+void pgram_device::dsr_ram_write(offs_t offset, uint8_t data)
+{
+ // ..0. .... .... .... = DSR
+ // ..1. .... .... .... = RAM
+ bool dsr = ((offset & 0x2000)==0);
+
+ if ((dsr && m_crulatch->q0_r()) || (!dsr && m_crulatch->q1_r()))
+ {
+ // Get the current settings of the flipflop from the latch
+ m_bankff->clear_w(m_crulatch->q3_r());
+ m_bankff->preset_w(m_crulatch->q4_r());
+
+ int oldff = m_bankff->output_r();
+
+ // A14 is led to the flipflop D input
+ m_bankff->d_w((offset & 2)>>1); // A14
+ m_bankff->clock_w(0);
+
+ if (m_crulatch->q2_r()==0) // not write-protected
+ {
+ offs_t base = (offset & 0x2000)<<1;
+ offs_t address = base | (offset & 0x1fff) | (m_bankff->output_r()? 0x2000 : 0);
+
+ m_dsrram->write(address, data);
+ if (base==0)
+ LOGMASKED(LOG_DSR, "%04x (bank %d) <- %02x\n", offset|0x4000, m_bankff->output_r()? 1:0, data);
+ else
+ LOGMASKED(LOG_RAM, "%04x (bank %d) <- %02x\n", offset|0x4000, m_bankff->output_r()? 1:0, data);
+ }
+
+ m_bankff->clock_w(1);
+ if (m_bankff->output_r() != oldff)
+ LOGMASKED(LOG_BANK, "Switch to bank %d (%04x)\n", m_bankff->output_r(), offset|0x4000);
+ }
+}
+
+/*
+ Read access to the GRAM emulation. The GRAM space is split between
+ two memory circuits, one for G3 (8K) and one for the other four GRAMs (32K).
+ For PGRAM+, we habe 32K + 128K (four banks).
+ When the GROM address is outside the area for G3-G7, the access is ignored.
+ GRAM access requires setting CRU bit 1 before.
+*/
+void pgram_device::gram_read(offs_t offset, uint8_t *value)
+{
+ // Don't let the debugger mess with the GRAM emulation
+ if (machine().side_effects_disabled())
+ {
+ *value = 0;
+ return;
+ }
+
+ if (m_crulatch->q1_r())
+ {
+ // Reset the clock
+ m_bankff->clock_w(1);
+
+ clock_gram_counter(0);
+ uint16_t gaddress = get_gram_address();
+
+ // GROM3: 011. .... .... ....
+ // Checked by u9
+ if ((gaddress & 0xe000)==0x6000)
+ {
+ offs_t gfull = (gaddress & 0x1fff) | (m_pgramplus? (offset << 13) : 0);
+ *value = m_gram3->read(gfull);
+ LOGMASKED(LOG_GRAM, "gram(%04x,%04x) -> %02x\n", gaddress, gfull, *value);
+ }
+
+ // GROM4-7: 1... .... .... ....
+ // Checked by u9 + u8
+ if ((gaddress & 0x8000)!=0)
+ {
+ offs_t gfull = (gaddress & 0x7fff) | (m_pgramplus? (offset << 15) : 0);
+ *value = m_gram4567->read(gfull);
+ LOGMASKED(LOG_GRAM, "gram(%04x,%05x) -> %02x\n", gaddress, gfull, *value);
+ }
+ clock_gram_counter(1);
+ }
+}
+
+/*
+ Write access to the GRAM emulation. Same comments apply as for gram_read.
+*/
+void pgram_device::gram_write(offs_t offset, uint8_t data)
+{
+ // Don't let the debugger mess with the GRAM emulation
+ if (machine().side_effects_disabled())
+ {
+ return;
+ }
+
+ if (m_crulatch->q1_r())
+ {
+ clock_gram_counter(0);
+ uint16_t gaddress = get_gram_address();
+
+ // GROM3: 011. .... .... ....
+ if ((gaddress & 0xe000)==0x6000)
+ {
+ offs_t gfull = (gaddress & 0x1fff) | (m_pgramplus? (offset << 13) : 0);
+ m_gram3->write(gfull, data);
+ LOGMASKED(LOG_GRAM, "gram(%04x,%04x) <- %02x\n", gaddress, gfull, data);
+ }
+
+ // GROM4-7: 1... .... .... ....
+ if ((gaddress & 0x8000)!=0)
+ {
+ offs_t gfull = (gaddress & 0x7fff) | (m_pgramplus? (offset << 15) : 0);
+ m_gram4567->write(gfull, data);
+ LOGMASKED(LOG_GRAM, "gram(%04x,%05x) <- %02x\n", gaddress, gfull, data);
+ }
+ clock_gram_counter(1);
+ }
+}
+
+/*
+ Set the address digits in the counters. Address setting is done by two
+ consecutive byte writes, where the currently stored digits in u13 and u11
+ are transferred to u12 and u10, and u13 and u11 get their new values
+ from the data bus.
+
+ D0-D3 -> u11 -> u10
+ D4-D7 -> u13 -> u12
+*/
+void pgram_device::set_gram_address(uint8_t data)
+{
+ if (m_crulatch->q1_r())
+ {
+ set_load_gram_counter(0);
+ clock_gram_counter(0);
+ m_count0->p_w(data&0x0f);
+ m_count1->p_w((data>>4)&0x0f);
+ m_count2->p_w(m_count0->output_r());
+ m_count3->p_w(m_count1->output_r());
+ clock_gram_counter(1);
+ set_load_gram_counter(1);
+ m_lowbyte = !m_lowbyte;
+ }
+}
+
+/*
+ Set the clock signal for all four counter chips. Propagate carry bits.
+*/
+void pgram_device::clock_gram_counter(int state)
+{
+ if (m_crulatch->q1_r())
+ {
+ m_count1->cet_w(m_count0->tc_r());
+ m_count2->cet_w(m_count1->tc_r());
+ m_count3->cet_w(m_count2->tc_r());
+
+ m_count0->clock_w(state);
+ m_count1->clock_w(state);
+ m_count2->clock_w(state);
+ m_count3->clock_w(state);
+
+ // Debugging
+ if (state==1 && m_lowbyte)
+ {
+ uint16_t gaddr = get_gram_address();
+ if (gaddr >= 0x6000) LOGMASKED(LOG_GRAMADDR, "gaddr=%04x\n", gaddr);
+ }
+ }
+}
+
+/*
+ Convenience method to retrieve the current GRAM address.
+*/
+uint16_t pgram_device::get_gram_address()
+{
+ return (m_count3->output_r()<<12) | (m_count2->output_r()<<8) | (m_count1->output_r()<<4) | m_count0->output_r();
+}
+
+/*
+ Set/reset the PE* bit of all 74LS161 curcuits.
+*/
+void pgram_device::set_load_gram_counter(int state)
+{
+ m_count0->pe_w(state);
+ m_count1->pe_w(state);
+ m_count2->pe_w(state);
+ m_count3->pe_w(state);
+}
+
+/*
+ CRU write access to the LS259 latch.
+*/
+void pgram_device::cruwrite(offs_t offset, uint8_t data)
+{
+ // ...1 0xxx .... ...0
+ bool selected = ((offset & 0xff01)==m_crubase);
+ if (selected)
+ {
+ uint8_t bit = (offset & 0xfe)>>1;
+ LOGMASKED(LOG_CRU, "cru %04x (bit %d) <- %d\n", offset, bit , data);
+ if (bit==3 || bit==4) LOGMASKED(LOG_BANK, "cru bit %d <- %d\n", bit, data);
+ }
+
+ m_crulatch->enable_w(selected? 0 : 1);
+ m_crulatch->write_abcd((offset>>1)&0x07, data);
+}
+
+/*
+ Device construction.
+*/
+void pgram_device::device_add_mconfig(machine_config& config)
+{
+ // 4-bit counters
+ TTL74161(config, m_count0); // u13
+ TTL74161(config, m_count1); // u11
+ TTL74161(config, m_count2); // u12
+ TTL74161(config, m_count3); // u10
+
+ // Hardwired connections
+ m_count0->set_cet_pin_value(1);
+
+ m_count0->set_cep_pin_value(1);
+ m_count1->set_cep_pin_value(1);
+ m_count2->set_cep_pin_value(1);
+ m_count3->set_cep_pin_value(1);
+
+ // CRU latch
+ LS259(config, m_crulatch); // u14
+
+ // Bank switch
+ TTL7474(config, m_bankff, 0);
+
+ // We allocate the space for PGRAM+
+ BUFF_RAM(config, GRAM4567_TAG, 0).set_size(128*1024);
+ BUFF_RAM(config, GRAM3_TAG, 0).set_size(32*1024);
+ BUFF_RAM(config, DSRRAM_TAG, 0).set_size(32*1024);
+
+ // Real-time clock
+ MM58167(config, CLOCK_TAG, 32.768_kHz_XTAL);
+}
+
+void pgram_device::device_start()
+{
+}
+
+void pgram_device::device_reset()
+{
+ m_active = (ioport("SW2")->read()==1);
+ m_crubase = (ioport("SW1")->read()<<8) | 0x1000;
+ m_pgramplus = (ioport("SIZE")->read()==1);
+}
+
+INPUT_CHANGED_MEMBER( pgram_device::sw1_changed )
+{
+ // CRU base changed
+ m_crubase = (newval << 8) | 0x1000;
+}
+
+INPUT_CHANGED_MEMBER( pgram_device::sw2_changed )
+{
+ // Activation switch changed
+ m_active = (newval==1);
+}
+
+INPUT_PORTS_START( pgram_switches )
+ PORT_START( "SIZE" )
+ PORT_CONFNAME( 0x01, 0x00, "Memory size") // cannot be changed during runtime
+ PORT_CONFSETTING(0x00, "PGRAM 72K")
+ PORT_CONFSETTING(0x01, "PGRAM+ 192K")
+
+ PORT_START( "SW2" )
+ PORT_DIPNAME( 0x01, 0x01, "Activate switch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, pgram_device, sw2_changed, 0)
+ PORT_DIPSETTING(0x00, DEF_STR( Off ))
+ PORT_DIPSETTING(0x01, DEF_STR( On ))
+
+ PORT_START( "SW1" )
+ PORT_DIPNAME( 0x07, 0x00, "CRU base" ) PORT_CHANGED_MEMBER(DEVICE_SELF, pgram_device, sw1_changed, 0)
+ PORT_DIPSETTING( 0x00, "1000")
+ PORT_DIPSETTING( 0x02, "1200")
+ PORT_DIPSETTING( 0x03, "1300")
+ PORT_DIPSETTING( 0x04, "1400")
+ PORT_DIPSETTING( 0x05, "1500")
+ PORT_DIPSETTING( 0x06, "1600")
+ PORT_DIPSETTING( 0x07, "1700")
+INPUT_PORTS_END
+
+ioport_constructor pgram_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME( pgram_switches );
+}
+
+} // end namespace bus::ti99::peb