ti99_2: New WORKING driver. New and verified ROM dumps, re-implementation, fixes.

This commit is contained in:
Michael Zapf 2018-05-05 22:04:21 +02:00
parent 9e5971241e
commit 7adec69c08
4 changed files with 865 additions and 255 deletions

View File

@ -2561,6 +2561,8 @@ end
if (BUSES["TI99"]~=null) then
files {
MAME_DIR .. "src/devices/bus/ti99/ti99defs.h",
MAME_DIR .. "src/devices/bus/ti99/internal/992board.cpp",
MAME_DIR .. "src/devices/bus/ti99/internal/992board.h",
MAME_DIR .. "src/devices/bus/ti99/internal/998board.cpp",
MAME_DIR .. "src/devices/bus/ti99/internal/998board.h",
MAME_DIR .. "src/devices/bus/ti99/internal/datamux.cpp",

View File

@ -0,0 +1,298 @@
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/***************************************************************************
TI-99/2 main board custom circuits
This component implements the custom video controller and interface chip
from the TI-99/2 console.
***************************************************************************/
#include "emu.h"
#include "992board.h"
/**
Emulation of the CRT Gate Array of the TI-99/2
Video display controller
RF-modulated, composite output
for standard black/white television
Selectable channel 3 or 4 VHF
625 lines for US markets
525 lines for European markets
Display: 24 rows, 32 columns
The controller accesses ROM and RAM space of the 9995 CPU. It makes use
of the HOLD line to gain access. Thus, the controller has DMA control
while producing each scan line. The CPU has a chance to execute instructions
in border time, horizontal retrace, vertical retrace.
In order to get more computing time, a special character (BEOL - bank end
of line) is used to indicate the last drawable character on the line. After
this character, the buses are released.
24K version: BEOL = any character from 0x70 to 0xff
32K version: BEOL = any character from 0x80 to 0xff
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
- Wait for a short time (some dots)
- Use row, column, dot_line counters
- Get the value c at 0xEC00 + row*32+col
- Get the byte b from 0x1C00 + c*8 + (dot_line%8)
- Push the byte to the shift register
- Get the bits for the scanline from the register
EF00: Control byte
+--+--+--+--+--+--+--+--+
|- |- |- |- |- |T |B |S |
+--+--+--+--+--+--+--+--+
Fabrice's 99/2:
T: Text color (1=white)
B: border color (1=white)
S: Background color (1=text color, 0=inverted)
text border back
0 0 0 b b w
0 0 1 b b b
0 1 0 b w w
0 1 1 b w b
1 0 0 w b b
1 0 1 w b w
1 1 0 w w b
1 1 1 w w w
Counters:
dotline 9 bit
after reaching 261, resets to 0
224..236: Top blanking
237..261: Top border
000..191: Display
192..217: Bottom border
218..220: Bottom blanking
221..223: Vert sync
dotcolumn 9 bit
increments every clock tick until reaching 341, resets to 0, and incr dotline
305..328: Left blanking
329..341: Left border
000..255: Display
256..270: Right border
271..278: Right blanking
279..304: Hor sync
------------------------------
Later versions define a "bitmap mode" [2]
EF00: Control byte
+--+--+--+--+--+--+--+--+
|- |- |- |- |- |C |B |M |
+--+--+--+--+--+--+--+--+
C: character color (1=white)
B: border color (1=white)
M: bitmap mode (1=bitmap)
[1] Ground Squirrel Personal Computer Product Specification
[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")
namespace bus { namespace ti99 { namespace internal {
video992_device::video992_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, type, tag, owner, clock),
device_video_interface(mconfig, *this),
m_mem_read_cb(*this),
m_hold_cb(*this),
m_int_cb(*this),
m_holdcpu(false),
m_videna(true)
{
}
video992_24_device::video992_24_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock )
: video992_device(mconfig, VIDEO99224, tag, owner, clock)
{
m_beol = 0x70;
}
video992_32_device::video992_32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock )
: video992_device(mconfig, VIDEO99232, tag, owner, clock)
{
m_beol = 0x7f;
}
std::string video992_device::tts(attotime t)
{
char buf[256];
const char *sign = "";
if(t.seconds() < 0) {
t = attotime::zero-t;
sign = "-";
}
int nsec = t.attoseconds() / ATTOSECONDS_PER_NANOSECOND;
sprintf(buf, "%s%04d.%03d,%03d,%03d", sign, int(t.seconds()), nsec/1000000, (nsec/1000)%1000, nsec % 1000);
return buf;
}
void video992_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
int raw_vpos = screen().vpos();
if (id == HOLD_TIME)
{
// logerror("release time: %s, diff: %s\n", tts(machine().time()), tts(machine().time()-m_hold_time));
// We're holding the CPU; release it until the next start
m_hold_cb(CLEAR_LINE);
m_free_timer->adjust(screen().time_until_pos((raw_vpos+1) % screen().height(), HORZ_DISPLAY_START));
return;
}
// logerror("hold time: %s\n", tts(machine().time()));
if (m_videna)
{
// Hold the CPU
m_hold_time = machine().time();
m_hold_cb(ASSERT_LINE);
}
int vpos = raw_vpos * m_vertical_size / screen().height();
uint32_t *p = &m_tmpbmp.pix32(vpos);
bool endofline = false;
int linelength = 0;
// logerror("draw line %d\n", vpos);
// Get control byte
uint8_t control = m_mem_read_cb(0xef00);
bool text_white = ((control & 0x04)!=0);
bool border_white = ((control & 0x02)!=0);
bool background_white = ((control & 0x01)!=0)? text_white : !text_white;
int y = vpos - m_top_border;
if (y < 0 || y >= 192)
{
// Draw border colour
for (int i = 0; i < TOTAL_HORZ; i++)
p[i] = border_white? rgb_t::white() : rgb_t::black();
// vblank is set at the last cycle of the first inactive line
// not confirmed by the specs, just doing like 9928A.
if ( y == 193 )
{
m_int_cb( ASSERT_LINE );
m_int_cb( CLEAR_LINE );
}
}
else
{
// Draw regular line
// Left border
for (int i = 0; i < HORZ_DISPLAY_START; i++)
p[i] = border_white? rgb_t::white() : rgb_t::black();
int addr = ((y << 2) & 0x3e0) | 0xec00;
// Active display
for (int x = HORZ_DISPLAY_START; x<HORZ_DISPLAY_START+256; x+=8)
{
uint8_t charcode = 0;
uint8_t pattern = 0;
if (!endofline && m_videna)
{
// Get character code at the location
charcode = m_mem_read_cb(addr) & 0x7f;
// Is it the BEOL (blank end-of-line)?
if (charcode >= m_beol)
endofline = true;
}
if (!endofline && m_videna)
{
// Get the pattern
int addrp = 0x1c00 | (charcode << 3) | (y%8);
pattern = m_mem_read_cb(addrp);
linelength++;
}
for (int i = 0; i < 8; i++)
{
if ((pattern & 0x80)!=0)
p[x+i] = text_white? rgb_t::white() : rgb_t::black();
else
p[x+i] = background_white? rgb_t::white() : rgb_t::black();
pattern <<= 1;
}
addr++;
}
// Right border
for (int i = HORZ_DISPLAY_START + 256; i < TOTAL_HORZ; i++)
p[i] = border_white? rgb_t::white() : rgb_t::black();
}
// +1 for the minimum hold time
// logerror("line length: %d\n", linelength);
m_hold_timer->adjust(screen().time_until_pos(raw_vpos, HORZ_DISPLAY_START + linelength*8 + 1));
}
uint32_t video992_device::screen_update( screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect )
{
copybitmap( bitmap, m_tmpbmp, 0, 0, 0, 0, cliprect );
return 0;
}
/*
VIDENA pin, negative logic
*/
WRITE_LINE_MEMBER( video992_device::videna )
{
m_videna = (state==CLEAR_LINE);
}
void video992_device::device_start()
{
m_top_border = VERT_DISPLAY_START_NTSC;
m_vertical_size = TOTAL_VERT_NTSC;
m_tmpbmp.allocate(TOTAL_HORZ, TOTAL_VERT_NTSC);
m_hold_timer = timer_alloc(HOLD_TIME);
m_free_timer = timer_alloc(FREE_TIME);
m_border_color = rgb_t::black();
m_background_color = rgb_t::white();
m_text_color = rgb_t::black();
m_mem_read_cb.resolve();
m_hold_cb.resolve();
m_int_cb.resolve();
}
void video992_device::device_reset()
{
m_free_timer->adjust(screen().time_until_pos(0, HORZ_DISPLAY_START));
}
} } }

View File

@ -0,0 +1,115 @@
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/****************************************************************************
TI-99/2 main board logic
This component implements the custom video controller and interface chip
from the TI-99/2 console.
See 992board.cpp for documentation
Michael Zapf
*****************************************************************************/
#ifndef MAME_BUS_TI99_INTERNAL_992BOARD_H
#define MAME_BUS_TI99_INTERNAL_992BOARD_H
#pragma once
#include "screen.h"
namespace bus { namespace ti99 { namespace internal {
class video992_device : public device_t,
public device_video_interface
{
public:
// Complete line (31.848 us)
static constexpr unsigned TOTAL_HORZ = 342;
static constexpr unsigned TOTAL_VERT_NTSC = 262;
template <class Object> devcb_base &set_readmem_callback(Object &&cb) { return m_mem_read_cb.set_callback(std::forward<Object>(cb)); }
template <class Object> devcb_base &set_hold_callback(Object &&cb) { return m_hold_cb.set_callback(std::forward<Object>(cb)); }
template <class Object> devcb_base &set_int_callback(Object &&cb) { return m_int_cb.set_callback(std::forward<Object>(cb)); }
// Delay(2) + ColorBurst(14) + Delay(8) + LeftBorder(13)
static constexpr unsigned HORZ_DISPLAY_START = 2 + 14 + 8 + 13;
// RightBorder(15) + Delay(8) + HorizSync(26)
static constexpr unsigned VERT_DISPLAY_START_NTSC = 13 + 27;
// update the screen
uint32_t screen_update( screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect );
// Video enable
DECLARE_WRITE_LINE_MEMBER( videna );
protected:
video992_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
int m_beol;
private:
static const device_timer_id HOLD_TIME = 0;
static const device_timer_id FREE_TIME = 1;
void device_start() override;
void device_reset() override;
void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
std::string tts(attotime t);
devcb_read8 m_mem_read_cb; // Callback to read memory
devcb_write_line m_hold_cb;
devcb_write_line m_int_cb;
bitmap_rgb32 m_tmpbmp;
emu_timer *m_free_timer;
emu_timer *m_hold_timer;
int m_top_border;
int m_vertical_size;
rgb_t m_border_color;
rgb_t m_background_color;
rgb_t m_text_color;
bool m_holdcpu;
bool m_videna;
attotime m_hold_time;
};
/* Variant for TI-99/2 24K */
class video992_24_device : public video992_device
{
public:
video992_24_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
};
/* Variant for TI-99/2 32K */
class video992_32_device : public video992_device
{
public:
video992_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) \
MCFG_VIDEO_SET_SCREEN(_screen_tag) \
MCFG_SCREEN_ADD( _screen_tag, RASTER ) \
MCFG_SCREEN_RAW_PARAMS( XTAL(10'738'635) / 2, bus::ti99::internal::video992_device::TOTAL_HORZ, bus::ti99::internal::video992_device::HORZ_DISPLAY_START-12, bus::ti99::internal::video992_device::HORZ_DISPLAY_START + 256 + 12, \
bus::ti99::internal::video992_device::TOTAL_VERT_NTSC, bus::ti99::internal::video992_device::VERT_DISPLAY_START_NTSC - 12, bus::ti99::internal::video992_device::VERT_DISPLAY_START_NTSC + 192 + 12 )
#define MCFG_VIDEO992_MEM_ACCESS_CB(_devcb) \
devcb = &downcast<bus::ti99::internal::video992_device &>(*device).set_readmem_callback(DEVCB_##_devcb);
#define MCFG_VIDEO992_HOLD_CB(_devcb) \
devcb = &downcast<bus::ti99::internal::video992_device &>(*device).set_hold_callback(DEVCB_##_devcb);
#define MCFG_VIDEO992_INT_CB(_devcb) \
devcb = &downcast<bus::ti99::internal::video992_device &>(*device).set_int_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)
#endif // MAME_BUS_TI99_INTERNAL_992BOARD_H

View File

@ -1,266 +1,330 @@
// license:GPL-2.0+
// copyright-holders:Raphael Nabet
// copyright-holders: Michael Zapf
/*
Experimental ti99/2 driver
TI-99/2 driver
--------------
TODO :
* find a TI99/2 ROM dump (some TI99/2 ARE in private hands)
* test the driver !
* understand the "viden" pin
* implement cassette
* implement Hex-Bus
Drivers: ti99_224 - 24 KiB version
ti99_232 - 32 KiB version
Raphael Nabet (who really has too much time to waste), december 1999, 2000
*/
Codenamed "Ground Squirrel", the TI-99/2 was designed to be an extremely
low-cost, downstripped version of the TI-99/4A, competing with systems
like the ZX81 or the Times/Sinclair 1000. The targeted price was below $100.
/*
TI99/2 facts :
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.
References :
* TI99/2 main logic board schematics, 83/03/28-30 (on ftp.whtech.com, or just ask me)
(Thanks to Charles Good for posting this)
At many places, the tight price constraint shows up.
- 2 or 4 KiB of RAM
- Video memory is part of the CPU RAM
- Video controller is black/white only and has a fixed character set, no sprites
- No sound
- No GROMs (as the significant TI technology circuit)
- No P-Box connection
general :
* prototypes in 1983
* uses a 10.7MHz TMS9995 CPU, with the following features :
- 8-bit data bus
- 256 bytes 16-bit RAM (0xff00-0xff0b & 0xfffc-0xffff)
- only available int lines are INT4 (used by vdp), INT1*, and NMI* (both free for extension)
- on-chip decrementer (0xfffa-0xfffb)
- Unlike tms9900, CRU address range is full 0x0000-0xFFFE (A0 is not used as address).
This is possible because tms9995 uses d0-d2 instead of the address MSBits to support external
opcodes.
- quite more efficient than tms9900, and a few additional instructions and features
* 24 or 32kb ROM (16kb plain (1kb of which used by vdp), 16kb split into 2 8kb pages)
* 4kb 8-bit RAM, 256 bytes 16-bit RAM
* custom vdp shares CPU RAM/ROM. The display is quite alike to tms9928 graphics mode, except
that colors are a static B&W, and no sprite is displayed. The config (particularily the
table base addresses) cannot be changed. Since TI located the pattern generator table in
ROM, we cannot even redefine the char patterns (unless you insert a custom cartidge which
overrides the system ROMs). VBL int triggers int4 on tms9995.
* CRU is handled by one single custom chip, so the schematics don't show many details :-( .
* I/O :
- 48-key keyboard. Same as TI99/4a, without alpha lock, and with an additional break key.
Note that the hardware can make the difference between the two shift keys.
- cassette I/O (one unit)
- ALC bus (must be another name for Hex-Bus)
* 60-pin expansion/cartidge port on the back
Main board
----------
1 CPU @ 5.35 MHz
2 RAM circuits
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
and six selector or latch circuits
memory map :
* 0x0000-0x4000 : system ROM (0x1C00-0x2000 (?) : char ROM used by vdp)
* 0x4000-0x6000 : system ROM, mapped to either of two distinct 8kb pages according to the S0
bit from the keyboard interface (!), which is used to select the first key row.
[only on second-generation TI99/2 protos, first generation protos only had 24kb of ROM]
* 0x6000-0xE000 : free for expansion
* 0xE000-0xF000 : 8-bit "system" RAM (0xEC00-0xEF00 used by vdp)
* 0xF000-0xF0FB : 16-bit processor RAM (on tms9995)
* 0xF0FC-0xFFF9 : free for expansion
* 0xFFFA-0xFFFB : tms9995 internal decrementer
* 0xFFFC-0xFFFF : 16-bit processor RAM (provides the NMI vector)
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
- Hexbus connector
- System expansion slot for cartridges (ROM or RAM), 60 pins, on the back
CRU map :
* 0x0000-0x1EFC : reserved
* 0x1EE0-0x1EFE : tms9995 flag register
* 0x1F00-0x1FD8 : reserved
* 0x1FDA : tms9995 MID flag
* 0x1FDC-0x1FFF : reserved
* 0x2000-0xE000 : unaffected
* 0xE400-0xE40E : keyboard I/O (8 bits input, either 3 or 6 bit output)
* 0xE80C : cassette I/O
* 0xE80A : ALC BAV
* 0xE808 : ALC HSK
* 0xE800-0xE808 : ALC data (I/O)
* 0xE80E : video enable (probably input - seems to come from the VDP, and is present on the
expansion connector)
* 0xF000-0xFFFE : reserved
Note that only A15-A11 and A3-A1 (A15 = MSB, A0 = LSB) are decoded in the console, so the keyboard
is actually mapped to 0xE000-0xE7FE, and other I/O bits to 0xE800-0xEFFE.
Also, ti99/2 does not support external instructions better than ti99/4(a). This is crazy, it
would just have taken three extra tracks on the main board and a OR gate in an ASIC.
Keyboard: 48 keys, no Alpha Lock, two separate Shift keys, additional break key.
Description
-----------
Prototypes were developed in 1983. Despite a late marketing campaign,
including well-known faces, no devices were ever sold. A few of them
survived in the hands of the engineers, and were later sold to private
users.
The Ground Squirrel underwent several design changes. In the initial
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.
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
same address space block, selectable by a CRU bit. ROM may be added as
cartridges to be plugged into the expansion slot, and the same is true
for RAM. Actually, since the complete bus is available on that connector,
almost any kind of peripheral device could be added. Too bad, none were
developed.
However, the Hexbus seemed to have matured in the meantime, which became
the standard peripheral bus system for the TI-99/8, and for smaller
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.
The address space layout looks like this:
0000 - 3FFF: ROM, unbanked
4000 - 5FFF: ROM, banked for 32K version
6000 - DFFF: ROM or RAM; ROM growing upwards, RAM growing downwards
E000 - EFFF: 4K RAM
EC00 - EEFF: Area used by video controller (24 rows, 32 columns)
EF00 : Control byte for colors (black/white) for backdrop/border
EF01 - EFFF: RAM
F000 - F0FB: CPU-internal RAM
F0FC - FFF9: empty
FFFA - FFFF: CPU-internal decrementer and NMI vector
RAM expansions may be offered via the cartridge port, but they must be
contiguous with the built-in RAM, which means that it must grow
downwards from E000. The space from F0FC up to FFF9 may also be covered
by expansion RAM.
From the other side, ROM or other memory-mapped devices may occupy
address space, growing up from 6000.
One peculiar detail is the memory-mapped video RAM. The video controller
shares an area of RAM with the CPU. To avoid congestion, the video
controller must HOLD the CPU while it accesses the video RAM area.
This decreases processing speed of the CPU, of course. For that reason,
a special character may be placed in every row after which the row will
be filled with blank pixels, and the CPU will be released early. This
means that with a screen full of characters, the processing speed is
definitely slower than with a clear screen.
To be able to temporarily get full CPU power, there is a pin VIDENA* at
the video controller which causes the screen to be blank when asserted,
and to be restored as before when cleared. This is used during cassette
transfer.
Technical details
-----------------
- 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.
- 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,
there is no known documentation about this circuit.
- The two banks of the last 16 KiB of ROM are selected with the same
line that is used for selecting keyboard row 0. This should mean that
you cannot read the keyboard from the second ROM bank.
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
ROM dumps
---------
Hard to believe, but we have access to two people with one 24K and
one 32K version, and we were able to dumps the ROMs correctly.
The ROMs contain a stripped-down version of TI BASIC, but without
the specific graphics subprograms. Programs written on the 99/2 should
run on the 99/4A, but the opposite is not true.
TODO
----
* Implement cassette
* Add Hexbus
Original implementation: Raphael Nabet; December 1999, 2000
Michael Zapf, May 2018
References :
[1] TI-99/2 main logic board schematics, 83/03/28-30
[2] BYTE magazine, June 1983, pp. 128-134
*/
#include "emu.h"
#include "machine/tms9901.h"
#include "cpu/tms9900/tms9995.h"
#include "screen.h"
#include "bus/ti99/internal/992board.h"
#include "machine/ram.h"
#define TI_VDC_TAG "vdc"
#define TI_SCREEN_TAG "screen"
#define TI992_ROM "rom_region"
#define TI992_RAM_TAG "ram_region"
#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
// Minimum log should be config and warnings
#define VERBOSE ( LOG_GENERAL | LOG_WARN )
#include "logmacro.h"
class ti99_2_state : public driver_device
{
public:
ti99_2_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_videoram(*this, "videoram"),
m_ROM_paged(0),
m_irq_state(0),
m_keyRow(0),
m_maincpu(*this, "maincpu"),
m_gfxdecode(*this, "gfxdecode"),
m_palette(*this, "palette") { }
m_videoctrl(*this, "vdc"),
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_WRITE8_MEMBER(write_kbd);
DECLARE_WRITE8_MEMBER(write_misc_cru);
DECLARE_READ8_MEMBER(read_kbd);
DECLARE_READ8_MEMBER(read_misc_cru);
DECLARE_READ8_MEMBER(mem_read);
DECLARE_WRITE8_MEMBER(mem_write);
DECLARE_WRITE_LINE_MEMBER(hold);
DECLARE_WRITE_LINE_MEMBER(holda);
DECLARE_WRITE_LINE_MEMBER(interrupt);
void crumap(address_map &map);
void memmap(address_map &map);
required_shared_ptr<uint8_t> m_videoram;
int m_ROM_paged;
int m_irq_state;
int m_keyRow;
DECLARE_WRITE8_MEMBER(ti99_2_write_kbd);
DECLARE_WRITE8_MEMBER(ti99_2_write_misc_cru);
DECLARE_READ8_MEMBER(ti99_2_read_kbd);
DECLARE_READ8_MEMBER(ti99_2_read_misc_cru);
DECLARE_DRIVER_INIT(ti99_2_24);
DECLARE_DRIVER_INIT(ti99_2_32);
virtual void machine_reset() override;
uint32_t screen_update_ti99_2(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
INTERRUPT_GEN_MEMBER(ti99_2_vblank_interrupt);
required_device<cpu_device> m_maincpu;
required_device<gfxdecode_device> m_gfxdecode;
required_device<palette_device> m_palette;
void ti99_2(machine_config &config);
void ti99_2_io(address_map &map);
void ti99_2_memmap(address_map &map);
void ti99_224(machine_config &config);
void ti99_232(machine_config &config);
private:
required_device<tms9995_device> m_maincpu;
required_device<bus::ti99::internal::video992_device> m_videoctrl;
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;
int m_first_ram_page;
};
DRIVER_INIT_MEMBER(ti99_2_state,ti99_2_24)
MACHINE_START_MEMBER(ti99_2_state, ti99_224)
{
/* no ROM paging */
m_ROM_paged = 0;
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;
}
DRIVER_INIT_MEMBER(ti99_2_state,ti99_2_32)
MACHINE_START_MEMBER(ti99_2_state, ti99_232)
{
/* ROM paging enabled */
m_ROM_paged = 1;
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;
}
#define TI99_2_32_ROMPAGE0 (memregion("maincpu")->base()+0x4000)
#define TI99_2_32_ROMPAGE1 (memregion("maincpu")->base()+0x10000)
void ti99_2_state::machine_reset()
MACHINE_RESET_MEMBER(ti99_2_state, ti99_2)
{
m_irq_state = ASSERT_LINE;
if (! m_ROM_paged)
membank("bank1")->set_base(memregion("maincpu")->base()+0x4000);
else
membank("bank1")->set_base((memregion("maincpu")->base()+0x4000));
m_otherbank = false;
// Configure CPU to insert 1 wait state for each external memory access
// by lowering the READY line on reset
// TODO: Check with specs
tms9995_device* cpu = static_cast<tms9995_device*>(machine().device("maincpu"));
cpu->ready_line(CLEAR_LINE);
cpu->reset_line(ASSERT_LINE);
m_maincpu->ready_line(CLEAR_LINE);
m_maincpu->reset_line(ASSERT_LINE);
}
INTERRUPT_GEN_MEMBER(ti99_2_state::ti99_2_vblank_interrupt)
/*
Memory map - see description above
*/
void ti99_2_state::memmap(address_map &map)
{
m_maincpu->set_input_line(INT_9995_INT1, m_irq_state);
m_irq_state = (m_irq_state == ASSERT_LINE) ? CLEAR_LINE : ASSERT_LINE;
// 3 or 4 ROMs of 8 KiB. The 32K version has two ROMs mapped into 4000-5fff
// Part of the ROM is accessed by the video controller for the
// character definitions (1c00-1fff)
map(0x0000, 0xffff).rw(this, FUNC(ti99_2_state::mem_read), FUNC(ti99_2_state::mem_write));
}
/*
CRU map - see description above
Note that the CRU address space has only even numbers, and the
read addresses in the emulation gather 8 bits in one go, so the address
is the bit number times 16.
*/
void ti99_2_state::crumap(address_map &map)
{
// 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_kbd));
map(0x7000, 0x7007).mirror(0x03f8).w(this, FUNC(ti99_2_state::write_kbd));
// 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_misc_cru));
map(0x7400, 0x7407).mirror(0x03f8).w(this, FUNC(ti99_2_state::write_misc_cru));
}
/*
TI99/2 vdp emulation.
Things could not be simpler.
We display 24 rows and 32 columns of characters. Each 8*8 pixel character pattern is defined
in a 128-entry table located in ROM. Character code for each screen position are stored
sequentially in RAM. Colors are a fixed Black on White.
There is an EOL character that blanks the end of the current line, so that
the CPU can get more bus time.
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.
*/
uint32_t ti99_2_state::screen_update_ti99_2(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
WRITE8_MEMBER(ti99_2_state::write_kbd)
{
uint8_t *videoram = m_videoram;
int i, sx, sy;
// logerror("offset=%d, data=%d\n", offset, data);
if (data == 0) m_keyRow = offset;
sx = sy = 0;
for (i = 0; i < 768; i++)
// Now, we handle ROM paging
if (m_have_banked_ROM && offset == 0)
{
/* Is the char code masked or not ??? */
m_gfxdecode->gfx(0)->opaque(bitmap,cliprect, videoram[i] & 0x7F, 0,
0, 0, sx, sy);
sx += 8;
if (sx == 256)
{
sx = 0;
sy += 8;
}
}
return 0;
}
static const gfx_layout ti99_2_charlayout =
{
8,8, /* 8 x 8 characters */
128, /* 128 characters */
1, /* 1 bits per pixel */
{ 0 }, /* no bitplanes; 1 bit per pixel */
/* x offsets */
{ 0,1,2,3,4,5,6,7 },
/* y offsets */
{ 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8, },
8*8 /* every char takes 8 bytes */
};
static GFXDECODE_START( ti99_2 )
GFXDECODE_ENTRY( "maincpu", 0x1c00, ti99_2_charlayout, 0, 1 )
GFXDECODE_END
/*
Memory map - see description above
*/
void ti99_2_state::ti99_2_memmap(address_map &map)
{
map(0x0000, 0x3fff).rom(); /* system ROM */
map(0x4000, 0x5fff).bankr("bank1"); /* system ROM, banked on 32kb ROMs protos */
map(0x6000, 0xdfff).noprw(); /* free for expansion */
map(0xe000, 0xebff).ram(); /* system RAM */
map(0xec00, 0xeeff).ram().share("videoram");
map(0xef00, 0xefff).ram(); /* system RAM */
map(0xf000, 0xffff).noprw(); /* free for expansion (and internal processor RAM) */
}
/*
CRU map - see description above
*/
/* current keyboard row */
/* write the current keyboard row */
WRITE8_MEMBER(ti99_2_state::ti99_2_write_kbd)
{
offset &= 0x7; /* other address lines are not decoded */
if (offset <= 2)
{
/* this implementation is just a guess */
if (data)
m_keyRow |= 1 << offset;
else
m_keyRow &= ~ (1 << offset);
}
/* now, we handle ROM paging */
if (m_ROM_paged)
{ /* if we have paged ROMs, page according to S0 keyboard interface line */
membank("bank1")->set_base((m_keyRow == 0) ? TI99_2_32_ROMPAGE1 : TI99_2_32_ROMPAGE0);
LOGMASKED(LOG_BANK, "set bank = %d\n", data);
m_otherbank = (data==1);
}
}
WRITE8_MEMBER(ti99_2_state::ti99_2_write_misc_cru)
WRITE8_MEMBER(ti99_2_state::write_misc_cru)
{
offset &= 0x7; /* other address lines are not decoded */
switch (offset)
{
case 0:
@ -268,45 +332,156 @@ WRITE8_MEMBER(ti99_2_state::ti99_2_write_misc_cru)
case 2:
case 3:
/* ALC I/O */
LOGMASKED(LOG_CRU, "Hexbus I/O (%d) = %d\n", offset, data);
break;
case 4:
/* ALC HSK */
LOGMASKED(LOG_CRU, "Hexbus HSK = %d\n", data);
break;
case 5:
/* ALC BAV */
LOGMASKED(LOG_CRU, "Hexbus BAV = %d\n", data);
break;
case 6:
/* cassette output */
LOGMASKED(LOG_CRU, "Cassette output = %d\n", data);
break;
case 7:
/* video enable */
LOGMASKED(LOG_CRU, "VIDENA = %d\n", data);
m_videoctrl->videna(data);
break;
}
}
/* read keys in the current row */
READ8_MEMBER(ti99_2_state::ti99_2_read_kbd)
READ8_MEMBER(ti99_2_state::read_kbd)
{
static const char *const keynames[] = { "LINE0", "LINE1", "LINE2", "LINE3", "LINE4", "LINE5", "LINE6", "LINE7" };
return ioport(keynames[m_keyRow])->read();
}
READ8_MEMBER(ti99_2_state::ti99_2_read_misc_cru)
READ8_MEMBER(ti99_2_state::read_misc_cru)
{
return 0;
}
void ti99_2_state::ti99_2_io(address_map &map)
/*
These CRU addresses are actually inside the 9995 CPU, but they are
propagated to the outside world, so we can watch the changes.
*/
WRITE8_MEMBER(ti99_2_state::intflag_write)
{
map(0x0E00, 0x0E7f).r(this, FUNC(ti99_2_state::ti99_2_read_kbd));
map(0x0E80, 0x0Eff).r(this, FUNC(ti99_2_state::ti99_2_read_misc_cru));
map(0x7000, 0x73ff).w(this, FUNC(ti99_2_state::ti99_2_write_kbd));
map(0x7400, 0x77ff).w(this, FUNC(ti99_2_state::ti99_2_write_misc_cru));
int addr = 0x1ee0 | (offset<<1);
switch (addr)
{
case 0x1ee0:
LOGMASKED(LOG_CRU, "Setting 9995 decrementer to %s mode\n", (data==1)? "event" : "timer");
break;
case 0x1ee2:
LOGMASKED(LOG_CRU, "Setting 9995 decrementer to %s\n", (data==1)? "enabled" : "disabled");
break;
case 0x1ee4:
if (data==0) LOGMASKED(LOG_CRU, "Clear INT1 latch\n");
break;
case 0x1ee6:
if (data==0) LOGMASKED(LOG_CRU, "Clear INT3 latch\n");
break;
case 0x1ee8:
if (data==0) LOGMASKED(LOG_CRU, "Clear INT4 latch\n");
break;
case 0x1eea:
LOGMASKED(LOG_CRU, "Setting FLAG5 to %d\n", data);
break;
default:
LOGMASKED(LOG_CRU, "Writing internal flag %04x = %d\n", addr, data);
break;
}
}
/*
Called by CPU and video controller.
*/
READ8_MEMBER(ti99_2_state::mem_read)
{
int page = offset >> 12;
/* ti99/2 : 54-key keyboard */
if (page>=0 && page<4)
{
// ROM, unbanked
return m_rom[offset];
}
if (page>=4 && page<6)
{
// ROM, banked on 32K version
if (m_otherbank) offset = (offset & 0x1fff) | 0x10000;
return m_rom[offset];
}
if ((page >= m_first_ram_page) && (page < 15))
{
return m_ram->pointer()[offset - m_ram_start];
}
LOGMASKED(LOG_WARN, "Unmapped read access at %04x\n", offset);
return 0;
}
WRITE8_MEMBER(ti99_2_state::mem_write)
{
int page = offset >> 12;
if (page>=0 && page<4)
{
// ROM, unbanked
LOGMASKED(LOG_WARN, "Writing to ROM at %04x ignored (data=%02x)\n", offset, data);
return;
}
if (page>=4 && page<6)
{
LOGMASKED(LOG_WARN, "Writing to ROM at %04x (bank %d) ignored (data=%02x)\n", offset, m_otherbank? 1:0, data);
return;
}
if ((page >= m_first_ram_page) && (page < 15))
{
m_ram->pointer()[offset - m_ram_start] = data;
return;
}
LOGMASKED(LOG_WARN, "Unmapped write access at %04x\n", offset);
}
/*
Called by the VDC as a vblank interrupt
*/
WRITE_LINE_MEMBER(ti99_2_state::interrupt)
{
LOGMASKED(LOG_SIGNALS, "Interrupt: %d\n", state);
m_maincpu->set_input_line(INT_9995_INT4, state);
}
/*
Called by the VDC to HOLD the CPU
*/
WRITE_LINE_MEMBER(ti99_2_state::hold)
{
LOGMASKED(LOG_SIGNALS, "HOLD: %d\n", state);
m_maincpu->hold_line(state);
}
/*
Called by the CPU to ack the HOLD
*/
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 */
@ -377,51 +552,71 @@ static INPUT_PORTS_START(ti99_2)
INPUT_PORTS_END
MACHINE_CONFIG_START(ti99_2_state::ti99_2)
// basic machine hardware
// TMS9995, standard variant
// We have no lines connected yet
MCFG_TMS99xx_ADD("maincpu", TMS9995, 10700000, ti99_2_memmap, ti99_2_io)
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_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_DEVICE_VBLANK_INT_DRIVER("screen", ti99_2_state, ti99_2_vblank_interrupt)
/* video hardware */
/*MCFG_TMS9928A( &tms9918_interface )*/
MCFG_SCREEN_ADD_MONOCHROME("screen", RASTER, rgb_t::white())
MCFG_SCREEN_REFRESH_RATE(60)
MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(2500)) /* not accurate */
MCFG_SCREEN_SIZE(256, 192)
MCFG_SCREEN_VISIBLE_AREA(0, 256-1, 0, 192-1)
MCFG_SCREEN_UPDATE_DRIVER(ti99_2_state, screen_update_ti99_2)
MCFG_SCREEN_PALETTE("palette")
MCFG_GFXDECODE_ADD("gfxdecode", "palette", ti99_2)
MCFG_PALETTE_ADD_MONOCHROME_INVERTED("palette")
MCFG_MACHINE_START_OVERRIDE(ti99_2_state, ti99_224 )
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_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_MACHINE_START_OVERRIDE(ti99_2_state, ti99_232 )
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)
MCFG_TMS9995_HOLDA_HANDLER( WRITELINE(*this, ti99_2_state, holda) )
// RAM 4 KiB
// Early documents indicate 2 KiB RAM, but this does not work with
// either ROM version, so we have to assume that the 2 KiB were never
// sufficient, not even in the initial design
MCFG_RAM_ADD(TI992_RAM_TAG)
MCFG_RAM_DEFAULT_SIZE("4096")
MCFG_RAM_DEFAULT_VALUE(0)
MCFG_MACHINE_RESET_OVERRIDE(ti99_2_state, ti99_2 )
MACHINE_CONFIG_END
/*
ROM loading
*/
ROM_START(ti99_224)
/*CPU memory space*/
ROM_REGION(0x10000,"maincpu",0)
ROM_LOAD("992rom.bin", 0x0000, 0x6000, NO_DUMP) /* system ROMs */
// The 24K version is an early design; the PCB does not have any socket names
ROM_REGION(0x6000, TI992_ROM ,0)
ROM_LOAD("rom0000.bin", 0x0000, 0x2000, CRC(c57436f1) SHA1(71d9048fed0317cfcc4cd966dcbc3bc163080cf9))
ROM_LOAD("rom2000.bin", 0x2000, 0x2000, CRC(be22c6c4) SHA1(931931d61732bacdab1da227c01b8045ca860f0b))
ROM_LOAD("rom4000.bin", 0x4000, 0x2000, CRC(926ca20e) SHA1(91624a16aa2c62c7ebc23128308709efdebddca3))
ROM_END
ROM_START(ti99_232)
/*64kb CPU memory space + 8kb to read the extra ROM page*/
ROM_REGION(0x12000,"maincpu",0)
ROM_LOAD("992rom32.bin", 0x0000, 0x6000, NO_DUMP) /* system ROM - 32kb */
ROM_CONTINUE(0x10000,0x2000)
ROM_REGION(0x12000, TI992_ROM, 0)
// The 32K version is a more elaborate design; the ROMs for addresses 0000-1fff
// and the second bank for 4000-5fff are stacked on the socket U2
ROM_LOAD("rom0000.u2a", 0x0000, 0x2000, CRC(01b94f06) SHA1(ef2e0c5f0492d7d024ebfe3fad29c2b57ea849e1))
ROM_LOAD("rom2000.u12", 0x2000, 0x2000, CRC(0a32f80a) SHA1(32ed98481998be295e637eaa2117337cfa4a7984))
ROM_LOAD("rom4000a.u3", 0x4000, 0x2000, CRC(10c11fab) SHA1(d43e0952538e66e2cedc307b71b65cb388cbe8e3))
ROM_LOAD("rom4000b.u2b", 0x10000, 0x2000, CRC(34dd52ed) SHA1(e01892b1b110d7d592a7e7f1f39f9f46ea0818db))
ROM_END
/* one expansion/cartridge port on the back */
/* one cassette unit port */
/* Hex-bus disk controller: supports up to 4 floppy disk drives */
/* None of these is supported (tape should be easy to emulate) */
// YEAR NAME PARENT COMPAT MACHINE INPUT STATE INIT COMPANY FULLNAME FLAGS
COMP( 1983, ti99_224, 0, 0, ti99_2, ti99_2, ti99_2_state, ti99_2_24, "Texas Instruments", "TI-99/2 BASIC Computer (24kb ROMs)" , MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1983, ti99_232, ti99_224, 0, ti99_2, ti99_2, ti99_2_state, ti99_2_32, "Texas Instruments", "TI-99/2 BASIC Computer (32kb ROMs)" , MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
COMP( 1983, ti99_224, 0, 0, ti99_224, ti99_2, ti99_2_state, 0, "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, 0, "Texas Instruments", "TI-99/2 BASIC Computer (32 KiB ROM)" , MACHINE_NO_SOUND_HW )