From 7adec69c0873fa128ff669b574d8caa2e0cc602c Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Sat, 5 May 2018 22:04:21 +0200 Subject: [PATCH] ti99_2: New WORKING driver. New and verified ROM dumps, re-implementation, fixes. --- scripts/src/bus.lua | 2 + src/devices/bus/ti99/internal/992board.cpp | 298 +++++++++ src/devices/bus/ti99/internal/992board.h | 115 ++++ src/mame/drivers/ti99_2.cpp | 705 +++++++++++++-------- 4 files changed, 865 insertions(+), 255 deletions(-) create mode 100644 src/devices/bus/ti99/internal/992board.cpp create mode 100644 src/devices/bus/ti99/internal/992board.h diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua index 39edfe5d9ac..4114928de76 100644 --- a/scripts/src/bus.lua +++ b/scripts/src/bus.lua @@ -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", diff --git a/src/devices/bus/ti99/internal/992board.cpp b/src/devices/bus/ti99/internal/992board.cpp new file mode 100644 index 00000000000..669b43798de --- /dev/null +++ b/src/devices/bus/ti99/internal/992board.cpp @@ -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= 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)); +} + +} } } + + diff --git a/src/devices/bus/ti99/internal/992board.h b/src/devices/bus/ti99/internal/992board.h new file mode 100644 index 00000000000..1d8f581f879 --- /dev/null +++ b/src/devices/bus/ti99/internal/992board.h @@ -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 devcb_base &set_readmem_callback(Object &&cb) { return m_mem_read_cb.set_callback(std::forward(cb)); } + template devcb_base &set_hold_callback(Object &&cb) { return m_hold_cb.set_callback(std::forward(cb)); } + template devcb_base &set_int_callback(Object &&cb) { return m_int_cb.set_callback(std::forward(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(*device).set_readmem_callback(DEVCB_##_devcb); + +#define MCFG_VIDEO992_HOLD_CB(_devcb) \ + devcb = &downcast(*device).set_hold_callback(DEVCB_##_devcb); + +#define MCFG_VIDEO992_INT_CB(_devcb) \ + devcb = &downcast(*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 diff --git a/src/mame/drivers/ti99_2.cpp b/src/mame/drivers/ti99_2.cpp index e252d6f2f1e..7fd1d620d6b 100644 --- a/src/mame/drivers/ti99_2.cpp +++ b/src/mame/drivers/ti99_2.cpp @@ -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 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 m_maincpu; - required_device m_gfxdecode; - required_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 m_maincpu; + required_device m_videoctrl; + required_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(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 )