ti99_2: Various fixes, added cassette and Hexbus, still WIP

This commit is contained in:
Michael Zapf 2018-05-21 18:08:40 +02:00
parent e796d56660
commit fd4fe2c91e
4 changed files with 446 additions and 218 deletions

View File

@ -7,12 +7,26 @@
This component implements the custom video controller and interface chip
from the TI-99/2 console.
May 2018
***************************************************************************/
#include "emu.h"
#include "998board.h"
#define LOG_WARN (1U<<1) // Warnings
#define LOG_CRU (1U<<2) // CRU logging
#define LOG_CASSETTE (1U<<3) // Cassette logging
#define LOG_HEXBUS (1U<<4) // Hexbus logging
#define LOG_BANK (1U<<5) // Change ROM banks
#define LOG_KEYBOARD (1U<<6) // Keyboard operation
#define VERBOSE ( LOG_GENERAL | LOG_WARN )
#include "logmacro.h"
#include "992board.h"
/**
/*
Emulation of the CRT Gate Array of the TI-99/2
Video display controller
@ -41,7 +55,6 @@
CRU Bit VIDENA: disables the scan line generation; blank white screen
Clock: 10.7 MHz
Divided by 2 for 9995 CLKIN
Scanline refresh:
- Pull down HOLD
@ -52,7 +65,7 @@
- Push the byte to the shift register
- Get the bits for the scanline from the register
EF00: Control byte
EF00: Control byte
+--+--+--+--+--+--+--+--+
|- |- |- |- |- |T |B |S |
+--+--+--+--+--+--+--+--+
@ -94,7 +107,9 @@
279..304: Hor sync
------------------------------
Later versions define a "bitmap mode" [2]
Later versions define a "bitmap mode". [2]
There are no known consoles with this capability, and it would require at
least 6 KiB of RAM.
EF00: Control byte
+--+--+--+--+--+--+--+--+
@ -109,11 +124,11 @@
[2] VDC Controller CF40052
*/
#include "emu.h"
#include "992board.h"
DEFINE_DEVICE_TYPE_NS(VIDEO99224, bus::ti99::internal, video992_24_device, "video992_24", "TI-99/2 CRT Controller 24K version")
DEFINE_DEVICE_TYPE_NS(VIDEO99232, bus::ti99::internal, video992_32_device, "video992_32", "TI-99/2 CRT Controller 32K version")
DEFINE_DEVICE_TYPE_NS(IO99224, bus::ti99::internal, io992_24_device, "io992_24", "TI-99/2 I/O controller 24K version")
DEFINE_DEVICE_TYPE_NS(IO99232, bus::ti99::internal, io992_32_device, "io992_32", "TI-99/2 I/O controller 32K version")
namespace bus { namespace ti99 { namespace internal {
@ -170,6 +185,11 @@ void video992_device::device_timer(emu_timer &timer, device_timer_id id, int par
if (m_videna)
{
// Hold the CPU
// We should expect the HOLDA (HOLD acknowledge) line to go to the
// same circuit that issued the HOLD, but this is not the case.
// The HOLDA line goes to the I/O circuit, which (obviously) allows
// access to the RAM at that point. There is no
// indication of any other effect on the video controller.
m_hold_time = machine().time();
m_hold_cb(ASSERT_LINE);
}
@ -262,7 +282,7 @@ uint32_t video992_device::screen_update( screen_device &screen, bitmap_rgb32 &bi
}
/*
VIDENA pin, negative logic
VIDENA pin, positive logic
*/
WRITE_LINE_MEMBER( video992_device::videna )
{
@ -292,6 +312,302 @@ void video992_device::device_reset()
m_free_timer->adjust(screen().time_until_pos(0, HORZ_DISPLAY_START));
}
/*
Emulation of the I/O Gate Array of the TI-99/2 [3]
The I/O controller is a TAL004 Low Power Schottky-TTL bipolar Gate Array
that provides the interface between the CPU, the keyboard, the Hexbus,
and the cassette interface. It delivers memory select lines for use by the
CPU and by the VDC. It also offers a synchronized RESET signal and a
divider for the CPU clock (which is seemingly not used in the real
machines).
It is mapped into the CRU I/O address space at addresses E000 and E800:
I/O map (CRU map)
-----------------
0000 - 1DFE: unmapped
1E00 - 1EFE: TMS9995-internal CRU addresses
1F00 - 1FD8: unmapped
1FDA: TMS9995 MID flag
1FDC - 1FFF: unmapped
2000 - DFFE: unmapped
E000 - E00E: Read: Keyboard column input
E000 - E00A: Write: Keyboard row selection
E00C: Write: unmapped
E00E: Write: Video enable (VIDENA)
E010 - E7FE: Mirrors of the above
E800 - E80C: Hexbus
E800 - E806: Data lines
E808: HSK line
E80A: BAV line
E80C: Inhibit (Write only)
E80E: Cassette
[3] I/O Controller CF40051, Preliminary specification, Texas Instruments
*/
io992_device::io992_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
: bus::hexbus::hexbus_chained_device(mconfig, type, tag, owner, clock),
m_set_rom_bank(*this),
m_key_row(0),
m_latch_out(0xd7),
m_latch_in(0xd7),
m_communication_disable(true)
{
}
io992_24_device::io992_24_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: io992_device(mconfig, IO99224, tag, owner, clock)
{
m_have_banked_rom = false;
}
io992_32_device::io992_32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: io992_device(mconfig, IO99232, tag, owner, clock)
{
m_have_banked_rom = true;
}
/*
54-key keyboard
*/
static INPUT_PORTS_START( keys992 )
PORT_START("LINE0") /* col 0 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("1 ! DEL") PORT_CODE(KEYCODE_1)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("2 @ INS") PORT_CODE(KEYCODE_2)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("3 #") PORT_CODE(KEYCODE_3)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("4 $ CLEAR") PORT_CODE(KEYCODE_4)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("5 % BEGIN") PORT_CODE(KEYCODE_5)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("6 ^ PROC'D") PORT_CODE(KEYCODE_6)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("7 & AID") PORT_CODE(KEYCODE_7)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("8 * REDO") PORT_CODE(KEYCODE_8)
PORT_START("LINE1") /* col 1 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("q Q") PORT_CODE(KEYCODE_Q)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("w W ~") PORT_CODE(KEYCODE_W)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("e E (UP)") PORT_CODE(KEYCODE_E)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("r R [") PORT_CODE(KEYCODE_R)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("t T ]") PORT_CODE(KEYCODE_T)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("y Y") PORT_CODE(KEYCODE_Y)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("i I ?") PORT_CODE(KEYCODE_I)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("9 ( BACK") PORT_CODE(KEYCODE_9)
PORT_START("LINE2") /* col 2 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("a A") PORT_CODE(KEYCODE_A)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("s S (LEFT)") PORT_CODE(KEYCODE_S)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("d D (RIGHT)") PORT_CODE(KEYCODE_D)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("f F {") PORT_CODE(KEYCODE_F)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("h H") PORT_CODE(KEYCODE_H)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("u U _") PORT_CODE(KEYCODE_U)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("o O '") PORT_CODE(KEYCODE_O)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("0 )") PORT_CODE(KEYCODE_0)
PORT_START("LINE3") /* col 3 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("z Z \\") PORT_CODE(KEYCODE_Z)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("x X (DOWN)") PORT_CODE(KEYCODE_X)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("c C `") PORT_CODE(KEYCODE_C)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("g G }") PORT_CODE(KEYCODE_G)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("j J") PORT_CODE(KEYCODE_J)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("k K") PORT_CODE(KEYCODE_K)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("p P \"") PORT_CODE(KEYCODE_P)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("= + QUIT") PORT_CODE(KEYCODE_EQUALS)
PORT_START("LINE4") /* col 4 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SHIFT") PORT_CODE(KEYCODE_LSHIFT)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CTRL") PORT_CODE(KEYCODE_LCONTROL)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("v V") PORT_CODE(KEYCODE_V)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("n N") PORT_CODE(KEYCODE_N)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(", <") PORT_CODE(KEYCODE_COMMA)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("l L") PORT_CODE(KEYCODE_L)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("; :") PORT_CODE(KEYCODE_COLON)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("/ -") PORT_CODE(KEYCODE_SLASH)
PORT_START("LINE5") /* col 5 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("BREAK") PORT_CODE(KEYCODE_ESC)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("(SPACE)") PORT_CODE(KEYCODE_SPACE)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("b B") PORT_CODE(KEYCODE_B)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("m M") PORT_CODE(KEYCODE_M)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(". >") PORT_CODE(KEYCODE_STOP)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("FCTN") PORT_CODE(KEYCODE_LALT)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SHIFT") PORT_CODE(KEYCODE_RSHIFT)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("ENTER") PORT_CODE(KEYCODE_ENTER)
PORT_START("LINE6") /* col 6 */
PORT_BIT(0xFF, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("LINE7") /* col 7 */
PORT_BIT(0xFF, IP_ACTIVE_LOW, IPT_UNUSED)
INPUT_PORTS_END
void io992_device::device_start()
{
m_hexbus_outbound = dynamic_cast<bus::hexbus::hexbus_device*>(machine().device(TI_HEXBUS_TAG));
m_cassette = dynamic_cast<cassette_image_device*>(machine().device(TI_CASSETTE));
m_videoctrl = dynamic_cast<video992_device*>(machine().device(TI992_VDC_TAG));
// Establish callback for inbound propagations
m_hexbus_outbound->set_chain_element(this);
m_set_rom_bank.resolve();
}
READ8_MEMBER(io992_device::cruread)
{
static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7" };
int address = offset << 4;
uint8_t value = 0x7f; // All Hexbus lines high
double inp = 0;
int i;
uint8_t bit = 1;
switch (address)
{
case 0xe000:
// CRU E000-E7fE: Keyboard
// Read: 0000 1110 0*** **** (mirror 007f)
value = ioport(keynames[m_key_row])->read();
break;
case 0xe800:
// CRU E800-EFFE: Hexbus and other functions
// Read: 0000 1110 1*** **** (mirror 007f)
for (i=0; i < 6; i++)
{
if ((m_latch_in & m_hexbval[i])==0) value &= ~bit;
bit <<= 1;
}
inp = m_cassette->input();
if (inp > 0)
value |= 0x80;
LOGMASKED(LOG_CASSETTE, "value=%f\n", inp);
break;
default:
LOGMASKED(LOG_CRU, "Unknown access to %04x\n", address);
}
LOGMASKED(LOG_CRU, "CRU %04x -> %02x\n", address, value);
return value;
}
WRITE8_MEMBER(io992_device::cruwrite)
{
int address = (offset << 1) & 0xf80e;
LOGMASKED(LOG_CRU, "CRU %04x <- %1x\n", address, data);
uint8_t olddata = m_latch_out;
switch (address)
{
// Select the current keyboard row. Also, bit 0 is used to switch the
// ROM bank. I guess that means we won't be able to read the keyboard
// when processing that particular ROM area.
// CRU E000-E7fE: Keyboard
// Write: 1110 0*** **** XXX0 (mirror 07f0)
case 0xe000:
if (m_have_banked_rom)
{
LOGMASKED(LOG_BANK, "set bank = %d\n", data);
m_set_rom_bank(data==1);
}
// no break
case 0xe002:
case 0xe004:
case 0xe006:
case 0xe008:
case 0xe00a:
if (data == 0) m_key_row = offset&7;
break;
case 0xe00c:
LOGMASKED(LOG_WARN, "Unmapped CRU write to address e00c\n");
break;
case 0xe00e:
LOGMASKED(LOG_CRU, "VIDENA = %d\n", data);
m_videoctrl->videna(data);
break;
// Write: 1110 1*** **** XXX0 (mirror 07f0)
case 0xe800: // ID0
case 0xe802: // ID1
case 0xe804: // ID2
case 0xe806: // ID3
if (data != 0) m_latch_out |= m_hexbval[offset & 0x07];
else m_latch_out &= ~m_hexbval[offset & 0x07];
LOGMASKED(LOG_HEXBUS, "Hexbus latch out = %02x\n", m_latch_out);
break;
case 0xe808: // HSK
case 0xe80a: // BAV
if (data != 0) m_latch_out |= m_hexbval[offset & 0x07];
else m_latch_out &= ~m_hexbval[offset & 0x07];
if (m_latch_out != olddata)
{
LOGMASKED(LOG_HEXBUS, "%s %s\n", (address==0xe808)? "HSK*" : "BAV*", (data==0)? "assert" : "clear");
if (m_communication_disable)
{
// This is not explicitly stated in the specs, but since they
// also claim that communication is disabled on power-up,
// we must turn it on here; the ROM fails to do it.
LOGMASKED(LOG_HEXBUS, "Enabling Hexbus\n");
m_communication_disable = false;
}
hexbus_write(m_latch_out);
// Check how the bus has changed. This depends on the states of all
// connected peripherals
m_latch_in = hexbus_read();
}
break;
case 0xe80c:
LOGMASKED(LOG_HEXBUS, "Hexbus inhibit = %d\n", data);
if (data == 1)
{
m_latch_in = 0xd7;
m_communication_disable = true;
}
else m_communication_disable = false;
break;
case 0xe80e:
LOGMASKED(LOG_CRU, "Cassette output = %d\n", data);
// Tape output. See also ti99_4x.cpp.
m_cassette->output((data==1)? +1 : -1);
break;
}
}
/*
Input from the Hexbus. Since it cannot trigger any interrupt on the
CPU, it must be polled via CRU.
Line state received via the Hexbus
+------+------+------+------+------+------+------+------+
| ID3 | ID2 | - | HSK* | 0 | BAV* | ID1 | ID0 |
+------+------+------+------+------+------+------+------+
*/
void io992_device::hexbus_value_changed(uint8_t data)
{
// Only latch the incoming data when BAV* is asserted and the Hexbus
// is not inhibited
bool bav_asserted = ((m_latch_out & bus::hexbus::HEXBUS_LINE_BAV)==0);
if (!m_communication_disable && bav_asserted)
{
LOGMASKED(LOG_HEXBUS, "Hexbus changed and latched: %02x\n", data);
m_latch_in = data;
}
else
LOGMASKED(LOG_HEXBUS, "Ignoring Hexbus change (to %02x), latch=%s, BAV*=%d\n", data, m_communication_disable? "inhibit":"enabled", bav_asserted? 0:1);
}
ioport_constructor io992_device::device_input_ports() const
{
return INPUT_PORTS_NAME( keys992 );
}
} } }

View File

@ -19,6 +19,9 @@
#pragma once
#include "screen.h"
#include "bus/ti99/ti99defs.h"
#include "bus/hexbus/hexbus.h"
#include "imagedev/cassette.h"
namespace bus { namespace ti99 { namespace internal {
@ -91,6 +94,64 @@ public:
video992_32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
/*
I/O custom chip
*/
class io992_device : public bus::hexbus::hexbus_chained_device
{
public:
io992_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
DECLARE_READ8_MEMBER( cruread );
DECLARE_WRITE8_MEMBER( cruwrite );
void device_start() override;
template <class Object> devcb_base &set_rombank_callback(Object &&cb) { return m_set_rom_bank.set_callback(std::forward<Object>(cb)); }
ioport_constructor device_input_ports() const override;
protected:
io992_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
bool m_have_banked_rom;
void hexbus_value_changed(uint8_t data) override;
private:
const uint8_t m_hexbval[6] = { 0x01, 0x02, 0x40, 0x80, 0x10, 0x04 };
cassette_image_device* m_cassette;
video992_device* m_videoctrl;
// Set ROM bank
devcb_write_line m_set_rom_bank;
// Keyboard row
int m_key_row;
// Hexbus outgoing signal latch. Should be set to D7 when idle.
// (see hexbus.cpp)
uint8_t m_latch_out;
// Hexbus incoming signal latch. Should be set to D7 when idle.
uint8_t m_latch_in;
// Hexbus inhibit. This prevents the incoming latches to store the data.
bool m_communication_disable;
};
/* Variant for TI-99/2 24K */
class io992_24_device : public io992_device
{
public:
io992_24_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
/* Variant for TI-99/2 32K */
class io992_32_device : public io992_device
{
public:
io992_32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
} } } // end namespace bus::ti99::internal
#define MCFG_VIDEO992_SCREEN_ADD(_screen_tag) \
@ -108,7 +169,12 @@ public:
#define MCFG_VIDEO992_INT_CB(_devcb) \
devcb = &downcast<bus::ti99::internal::video992_device &>(*device).set_int_callback(DEVCB_##_devcb);
#define MCFG_SET_ROMBANK_HANDLER( _devcb ) \
devcb = &downcast<bus::ti99::internal::io992_device &>(*device).set_rombank_callback(DEVCB_##_devcb);
DECLARE_DEVICE_TYPE_NS(VIDEO99224, bus::ti99::internal, video992_24_device)
DECLARE_DEVICE_TYPE_NS(VIDEO99232, bus::ti99::internal, video992_32_device)
DECLARE_DEVICE_TYPE_NS(IO99224, bus::ti99::internal, io992_24_device)
DECLARE_DEVICE_TYPE_NS(IO99232, bus::ti99::internal, io992_32_device)
#endif // MAME_BUS_TI99_INTERNAL_992BOARD_H

View File

@ -36,6 +36,9 @@
#define TI_VDP_TAG "vdp"
#define TI_SCREEN_TAG "screen"
#define TI_HEXBUS_TAG "hexbus"
#define TI_CASSETTE "cassette"
#define TI992_VDC_TAG "vdc"
#define TI_VDPFREQ XTAL(10'738'635)
// v9938

View File

@ -13,7 +13,7 @@
The 99/2 was equipped with a TMS9995, the same CPU as used in the envisioned
flag ship 99/8 and later in the Geneve 9640. In the 99/2 it is clocked
at 5.35 MHz. Also, the CPU has on-chip RAM, unlike the version in the 99/8.
at 10.7 MHz. Also, the CPU has on-chip RAM, unlike the version in the 99/8.
At many places, the tight price constraint shows up.
- 2 or 4 KiB of RAM
@ -25,8 +25,8 @@
Main board
----------
1 CPU @ 5.35 MHz
2 RAM circuits
1 CPU @ 10.7 MHz (contrary to specs which state 5.35 MHz)
2 RAM circuits (4 KiB instead of 2 KiB in specs)
3 or 4 EPROMs
1 TAL-004 custom gate array as the video controller
1 TAL-004 custom gate array as the I/O controller
@ -35,7 +35,7 @@
Connectors
----------
- Built-in RF modulator, switch allows for setting the channel to VHF 3 or 4
- Cassette connector (compatible to 99/4A), one unit only
- Two jacks for cassette input and output, no motor control
- Hexbus connector
- System expansion slot for cartridges (ROM or RAM), 60 pins, on the back
@ -52,7 +52,9 @@
design, only 2 KiB RAM were planned, and the included ROM was 24 KiB.
Later, the 2 KiB were obviously expanded to 4 KiB, while the ROM remained
the same in size. This can be proved here, since the console crashes with
less than 4 KiB by an unmapped memory access.
less than 4 KiB by an unmapped memory access. Also, the CPU is not clocked
by 5.35 MHz as specified, but by the undivided 10.7 MHz; this was proved
by running test programs on the real consoles.
The next version showed an additional 8 KiB of ROM. Possibly in order
to avoid taking away too much address space, two EPROMs are mapped to the
@ -67,7 +69,8 @@
systems like the CC-40 and the TI-74. The TI-99/2 also offers a Hexbus
interface so that any kind of Hexbus device can be connected, like, for
example, the HX5102 floppy drive, a Wafertape drive, or the RS232 serial
interface.
interface. The 24K version seems to have no proper Hexbus support for
floppy drives; it always starts the cassette I/O instead.
The address space layout looks like this:
@ -110,6 +113,8 @@
- HOLD is asserted in every scanline when characters are drawn that are
not the "Blank End of line" (BEOL). Once encountered, the remaining
scanline remains blank.
- During cassette transfer, the screen is blanked using the VIDENA line.
This is expected and not a bug.
- When a frame has been completed, the INT4 interrupt of the 9995 is
triggered as a vblank interrupt.
- All CRU operations are handled by the second gate array. Unfortunately,
@ -162,23 +167,21 @@
*/
#include "emu.h"
#include "bus/ti99/ti99defs.h"
#include "machine/tms9901.h"
#include "cpu/tms9900/tms9995.h"
#include "bus/ti99/internal/992board.h"
#include "machine/ram.h"
#include "imagedev/cassette.h"
#define TI_VDC_TAG "vdc"
#define TI_SCREEN_TAG "screen"
#define TI992_SCREEN_TAG "screen"
#define TI992_ROM "rom_region"
#define TI992_RAM_TAG "ram_region"
#define TI992_IO_TAG "io"
#define LOG_WARN (1U<<1) // Warnings
#define LOG_HEXBUS (1U<<2) // Hexbus operation
#define LOG_KEYBOARD (1U<<3) // Keyboard operation
#define LOG_SIGNALS (1U<<4) // Signals like HOLD/HOLDA
#define LOG_CRU (1U<<5) // CRU activities
#define LOG_BANK (1U<<6) // Change ROM banks
#define LOG_CRU (1U<<2) // CRU activities
#define LOG_SIGNALS (1U<<3) // Signals like HOLD/HOLDA
// Minimum log should be config and warnings
#define VERBOSE ( LOG_GENERAL | LOG_WARN )
@ -191,29 +194,22 @@ public:
ti99_2_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_maincpu(*this, "maincpu"),
m_videoctrl(*this, "vdc"),
m_cassette(*this, "cassette"),
m_videoctrl(*this, TI992_VDC_TAG),
m_io992(*this, TI992_IO_TAG),
m_cassette(*this, TI_CASSETTE),
m_ram(*this, TI992_RAM_TAG),
m_have_banked_ROM(true),
m_otherbank(false),
m_keyRow(0),
m_rom(nullptr),
m_ram_start(0xf000),
m_first_ram_page(0)
{ }
DECLARE_MACHINE_START( ti99_224 );
DECLARE_MACHINE_START( ti99_232 );
DECLARE_MACHINE_RESET( ti99_2 );
DECLARE_WRITE8_MEMBER(intflag_write);
DECLARE_READ8_MEMBER(read_e00x);
DECLARE_READ8_MEMBER(read_e80x);
DECLARE_WRITE8_MEMBER(write_e00x);
DECLARE_WRITE8_MEMBER(write_e80x);
DECLARE_READ8_MEMBER(mem_read);
DECLARE_WRITE8_MEMBER(mem_write);
@ -222,6 +218,8 @@ public:
DECLARE_WRITE_LINE_MEMBER(interrupt);
DECLARE_WRITE_LINE_MEMBER(cassette_output);
DECLARE_WRITE_LINE_MEMBER( rombank_set );
void crumap(address_map &map);
void memmap(address_map &map);
@ -232,12 +230,12 @@ public:
private:
required_device<tms9995_device> m_maincpu;
required_device<bus::ti99::internal::video992_device> m_videoctrl;
required_device<bus::ti99::internal::io992_device> m_io992;
required_device<cassette_image_device> m_cassette;
required_device<ram_device> m_ram;
bool m_have_banked_ROM;
bool m_otherbank;
int m_keyRow;
uint8_t* m_rom;
int m_ram_start;
@ -249,7 +247,6 @@ MACHINE_START_MEMBER(ti99_2_state, ti99_224)
m_rom = memregion(TI992_ROM)->base();
m_ram_start = 0xf000 - m_ram->default_size();
m_first_ram_page = m_ram_start >> 12;
m_have_banked_ROM = false;
}
MACHINE_START_MEMBER(ti99_2_state, ti99_232)
@ -257,7 +254,6 @@ MACHINE_START_MEMBER(ti99_2_state, ti99_232)
m_rom = memregion(TI992_ROM)->base();
m_ram_start = 0xf000 - m_ram->default_size();
m_first_ram_page = m_ram_start >> 12;
m_have_banked_ROM = true;
}
MACHINE_RESET_MEMBER(ti99_2_state, ti99_2)
@ -266,11 +262,16 @@ MACHINE_RESET_MEMBER(ti99_2_state, ti99_2)
// Configure CPU to insert 1 wait state for each external memory access
// by lowering the READY line on reset
// TODO: Check with specs
// This has been verified with the real machine, running test loops.
m_maincpu->ready_line(CLEAR_LINE);
m_maincpu->reset_line(ASSERT_LINE);
}
WRITE_LINE_MEMBER( ti99_2_state::rombank_set )
{
m_otherbank = (state==ASSERT_LINE);
}
/*
Memory map - see description above
*/
@ -291,107 +292,12 @@ void ti99_2_state::memmap(address_map &map)
*/
void ti99_2_state::crumap(address_map &map)
{
map(0x0000, 0x0fff).r(m_io992, FUNC(bus::ti99::internal::io992_device::cruread));
map(0x0000, 0x7fff).w(m_io992, FUNC(bus::ti99::internal::io992_device::cruwrite));
// Mirror of CPU-internal flags (1ee0-1efe). Don't read. Write is OK.
map(0x01ee, 0x01ef).nopr();
map(0x0f70, 0x0F7F).w(this, FUNC(ti99_2_state::intflag_write));
// CRU E000-E7fE: Keyboard
// Read: 0000 1110 0*** **** (mirror 007f)
// Write: 0111 00** **** *XXX (mirror 03f8)
map(0x0e00, 0x0e00).mirror(0x007f).r(this, FUNC(ti99_2_state::read_e00x));
map(0x7000, 0x7007).mirror(0x03f8).w(this, FUNC(ti99_2_state::write_e00x));
// CRU E800-EFFE: Hexbus and other functions
// Read: 0000 1110 1*** **** (mirror 007f)
// Write: 0111 01** **** *XXX (mirror 03f8)
map(0x0e80, 0x0e80).mirror(0x007f).r(this, FUNC(ti99_2_state::read_e80x));
map(0x7400, 0x7407).mirror(0x03f8).w(this, FUNC(ti99_2_state::write_e80x));
}
/*
Select the current keyboard row. Also, bit 0 is used to switch the
ROM bank. Suppose that means we won't be able to read the keyboard
when processing that ROM area.
*/
WRITE8_MEMBER(ti99_2_state::write_e00x)
{
// logerror("offset=%d, data=%d\n", offset, data);
switch (offset)
{
case 0:
if (m_have_banked_ROM)
{
LOGMASKED(LOG_BANK, "set bank = %d\n", data);
m_otherbank = (data==1);
}
// no break
case 1:
case 2:
case 3:
case 4:
case 5:
if (data == 0) m_keyRow = offset;
break;
case 6:
LOGMASKED(LOG_WARN, "Unmapped CRU write to address e00c\n");
break;
case 7:
LOGMASKED(LOG_CRU, "VIDENA = %d\n", data);
m_videoctrl->videna(data);
break;
}
}
WRITE8_MEMBER(ti99_2_state::write_e80x)
{
switch (offset)
{
case 0:
case 1:
case 2:
case 3:
LOGMASKED(LOG_CRU, "Hexbus I/O (%d) = %d\n", offset, data);
break;
case 4:
LOGMASKED(LOG_CRU, "Hexbus HSK = %d\n", data);
break;
case 5:
LOGMASKED(LOG_CRU, "Hexbus BAV = %d\n", data);
break;
case 6:
LOGMASKED(LOG_CRU, "Hexbus inhibit = %d\n", data);
break;
case 7:
LOGMASKED(LOG_CRU, "Cassette output = %d\n", data);
cassette_output((data==1)? ASSERT_LINE : CLEAR_LINE);
break;
}
}
/* read keys in the current row */
READ8_MEMBER(ti99_2_state::read_e00x)
{
static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7" };
return ioport(keynames[m_keyRow])->read();
}
READ8_MEMBER(ti99_2_state::read_e80x)
{
uint8_t value = 0;
if (m_cassette->input() > 0)
{
value |= 0x80;
}
return value;
}
/*
Tape output. See also ti99_4x.cpp where this is taken from.
*/
WRITE_LINE_MEMBER( ti99_2_state::cassette_output )
{
m_cassette->output(state==ASSERT_LINE? +1 : -1);
map(0x0f70, 0x0f7f).w(this, FUNC(ti99_2_state::intflag_write));
}
/*
@ -508,88 +414,17 @@ WRITE_LINE_MEMBER(ti99_2_state::holda)
LOGMASKED(LOG_SIGNALS, "HOLDA: %d\n", state);
}
/*
54-key keyboard
*/
static INPUT_PORTS_START(ti99_2)
PORT_START("LINE0") /* col 0 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("1 ! DEL") PORT_CODE(KEYCODE_1)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("2 @ INS") PORT_CODE(KEYCODE_2)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("3 #") PORT_CODE(KEYCODE_3)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("4 $ CLEAR") PORT_CODE(KEYCODE_4)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("5 % BEGIN") PORT_CODE(KEYCODE_5)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("6 ^ PROC'D") PORT_CODE(KEYCODE_6)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("7 & AID") PORT_CODE(KEYCODE_7)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("8 * REDO") PORT_CODE(KEYCODE_8)
PORT_START("LINE1") /* col 1 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("q Q") PORT_CODE(KEYCODE_Q)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("w W ~") PORT_CODE(KEYCODE_W)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("e E (UP)") PORT_CODE(KEYCODE_E)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("r R [") PORT_CODE(KEYCODE_R)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("t T ]") PORT_CODE(KEYCODE_T)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("y Y") PORT_CODE(KEYCODE_Y)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("i I ?") PORT_CODE(KEYCODE_I)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("9 ( BACK") PORT_CODE(KEYCODE_9)
PORT_START("LINE2") /* col 2 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("a A") PORT_CODE(KEYCODE_A)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("s S (LEFT)") PORT_CODE(KEYCODE_S)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("d D (RIGHT)") PORT_CODE(KEYCODE_D)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("f F {") PORT_CODE(KEYCODE_F)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("h H") PORT_CODE(KEYCODE_H)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("u U _") PORT_CODE(KEYCODE_U)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("o O '") PORT_CODE(KEYCODE_O)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("0 )") PORT_CODE(KEYCODE_0)
PORT_START("LINE3") /* col 3 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("z Z \\") PORT_CODE(KEYCODE_Z)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("x X (DOWN)") PORT_CODE(KEYCODE_X)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("c C `") PORT_CODE(KEYCODE_C)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("g G }") PORT_CODE(KEYCODE_G)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("j J") PORT_CODE(KEYCODE_J)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("k K") PORT_CODE(KEYCODE_K)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("p P \"") PORT_CODE(KEYCODE_P)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("= + QUIT") PORT_CODE(KEYCODE_EQUALS)
PORT_START("LINE4") /* col 4 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SHIFT") PORT_CODE(KEYCODE_LSHIFT/*KEYCODE_CAPSLOCK*/)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CTRL") PORT_CODE(KEYCODE_LCONTROL)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("v V") PORT_CODE(KEYCODE_V)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("n N") PORT_CODE(KEYCODE_N)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(", <") PORT_CODE(KEYCODE_COMMA)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("l L") PORT_CODE(KEYCODE_L)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("; :") PORT_CODE(KEYCODE_COLON)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("/ -") PORT_CODE(KEYCODE_SLASH)
PORT_START("LINE5") /* col 5 */
PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("BREAK") PORT_CODE(KEYCODE_ESC)
PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("(SPACE)") PORT_CODE(KEYCODE_SPACE)
PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("b B") PORT_CODE(KEYCODE_B)
PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("m M") PORT_CODE(KEYCODE_M)
PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(". >") PORT_CODE(KEYCODE_STOP)
PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("FCTN") PORT_CODE(KEYCODE_LALT)
PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("SHIFT") PORT_CODE(KEYCODE_RSHIFT)
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("ENTER") PORT_CODE(KEYCODE_ENTER)
PORT_START("LINE6") /* col 6 */
PORT_BIT(0xFF, IP_ACTIVE_LOW, IPT_UNUSED)
PORT_START("LINE7") /* col 7 */
PORT_BIT(0xFF, IP_ACTIVE_LOW, IPT_UNUSED)
INPUT_PORTS_END
MACHINE_CONFIG_START(ti99_2_state::ti99_224)
ti99_2(config);
// Video hardware
MCFG_DEVICE_ADD( TI_VDC_TAG, VIDEO99224, XTAL(10'738'635) )
MCFG_DEVICE_ADD( TI992_VDC_TAG, VIDEO99224, XTAL(10'738'635) )
MCFG_VIDEO992_MEM_ACCESS_CB( READ8( *this, ti99_2_state, mem_read ) )
MCFG_VIDEO992_HOLD_CB( WRITELINE( *this, ti99_2_state, hold ) )
MCFG_VIDEO992_INT_CB( WRITELINE( *this, ti99_2_state, interrupt ) )
MCFG_VIDEO992_SCREEN_ADD( TI_SCREEN_TAG )
MCFG_SCREEN_UPDATE_DEVICE( TI_VDC_TAG, bus::ti99::internal::video992_device, screen_update )
MCFG_VIDEO992_SCREEN_ADD( TI992_SCREEN_TAG )
MCFG_SCREEN_UPDATE_DEVICE( TI992_VDC_TAG, bus::ti99::internal::video992_device, screen_update )
// I/O interface circuit
MCFG_DEVICE_ADD(TI992_IO_TAG, IO99224, 0)
MCFG_MACHINE_START_OVERRIDE(ti99_2_state, ti99_224 )
MACHINE_CONFIG_END
@ -597,12 +432,16 @@ MACHINE_CONFIG_END
MACHINE_CONFIG_START(ti99_2_state::ti99_232)
ti99_2(config);
// Video hardware
MCFG_DEVICE_ADD( TI_VDC_TAG, VIDEO99232, XTAL(10'738'635) )
MCFG_DEVICE_ADD( TI992_VDC_TAG, VIDEO99232, XTAL(10'738'635) )
MCFG_VIDEO992_MEM_ACCESS_CB( READ8( *this, ti99_2_state, mem_read ) )
MCFG_VIDEO992_HOLD_CB( WRITELINE( *this, ti99_2_state, hold ) )
MCFG_VIDEO992_INT_CB( WRITELINE( *this, ti99_2_state, interrupt ) )
MCFG_VIDEO992_SCREEN_ADD( TI_SCREEN_TAG )
MCFG_SCREEN_UPDATE_DEVICE( TI_VDC_TAG, bus::ti99::internal::video992_device, screen_update )
MCFG_VIDEO992_SCREEN_ADD( TI992_SCREEN_TAG )
MCFG_SCREEN_UPDATE_DEVICE( TI992_VDC_TAG, bus::ti99::internal::video992_device, screen_update )
// I/O interface circuit
MCFG_DEVICE_ADD(TI992_IO_TAG, IO99232, 0)
MCFG_SET_ROMBANK_HANDLER(WRITELINE(*this, ti99_2_state, rombank_set) )
MCFG_MACHINE_START_OVERRIDE(ti99_2_state, ti99_232 )
@ -610,8 +449,9 @@ MACHINE_CONFIG_END
MACHINE_CONFIG_START(ti99_2_state::ti99_2)
// TMS9995, standard variant
// There is a divider by 2 for the clock rate
MCFG_TMS99xx_ADD("maincpu", TMS9995, XTAL(10'738'635) / 2, memmap, crumap)
// Documents state that there is a divider by 2 for the clock rate
// Experiments with real consoles proved them wrong.
MCFG_TMS99xx_ADD("maincpu", TMS9995, XTAL(10'738'635), memmap, crumap)
MCFG_TMS9995_HOLDA_HANDLER( WRITELINE(*this, ti99_2_state, holda) )
// RAM 4 KiB
@ -628,6 +468,9 @@ MACHINE_CONFIG_START(ti99_2_state::ti99_2)
// There is no route from the cassette to some audio output, so we don't hear it.
MCFG_CASSETTE_ADD( "cassette" )
// Hexbus
MCFG_HEXBUS_ADD( TI_HEXBUS_TAG )
MACHINE_CONFIG_END
/*
@ -652,5 +495,5 @@ ROM_START(ti99_232)
ROM_END
// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS
COMP( 1983, ti99_224, 0, 0, ti99_224, ti99_2, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (24 KiB ROM)" , MACHINE_NO_SOUND_HW )
COMP( 1983, ti99_232, ti99_224, 0, ti99_232, ti99_2, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (32 KiB ROM)" , MACHINE_NO_SOUND_HW )
COMP( 1983, ti99_224, 0, 0, ti99_224, 0, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (24 KiB ROM)" , MACHINE_NO_SOUND_HW )
COMP( 1983, ti99_232, 0, 0, ti99_232, 0, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (32 KiB ROM)" , MACHINE_NO_SOUND_HW )