mirror of
https://github.com/holub/mame
synced 2025-04-19 23:12:11 +03:00
apple1: completely rewrote the driver in modern idioms. All functionality should be the same. [R. Belmont]
This commit is contained in:
parent
54a53b4dba
commit
9205d549a7
@ -1,135 +1,78 @@
|
||||
// license:???
|
||||
// copyright-holders:Paul Daniels, Colin Howell, R. Belmont
|
||||
/**********************************************************************
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:R. Belmont
|
||||
/***************************************************************************
|
||||
|
||||
Apple I
|
||||
apple1.cpp - Apple I
|
||||
|
||||
CPU: 6502 @ 1.023 MHz
|
||||
(Effective speed with RAM refresh waits is 0.960 MHz.)
|
||||
Next generation driver written in February 2016 by R. Belmont.
|
||||
Thanks to the original crew.
|
||||
|
||||
Apple I has:
|
||||
6502 @ 1.023 MHz (~0.960 MHz with RAM refresh)
|
||||
4 or 8 KB RAM on-board
|
||||
256 byte Monitor ROM
|
||||
No IRQs, no sound, dumb terminal video
|
||||
6820 PIA for keyboard / terminal interface
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
||||
How to use cassettes:
|
||||
The system has no error checking or checksums, and the cassette
|
||||
has no header.
|
||||
Therefore, you must know the details, and pass these to the
|
||||
interface yourself.
|
||||
|
||||
BASIC has no cassette handling. You must enter the monitor
|
||||
with: CALL -151
|
||||
then when finished, re-enter BASIC with: E2B3R
|
||||
|
||||
RAM: 4-8 KB on main board (4 KB standard)
|
||||
Examples:
|
||||
|
||||
Additional memory could be added via the expansion
|
||||
connector, but the user was responsible for making sure
|
||||
the extra memory was properly interfaced.
|
||||
|
||||
Some users replaced the onboard 4-kilobit RAM chips with
|
||||
16-kilobit RAM chips, increasing on-board memory to 32 KB,
|
||||
but this required modifying the RAM interface circuitry.
|
||||
|
||||
ROM: 256 bytes for Monitor program
|
||||
|
||||
Optional cassette interface included 256 bytes for
|
||||
cassette routines.
|
||||
|
||||
Interrupts: None.
|
||||
(The system board had jumpers to allow interrupts, but
|
||||
these were not connected in a standard system.)
|
||||
|
||||
Video: Dumb terminal, based on 7 1K-bit shift registers
|
||||
|
||||
Sound: None
|
||||
|
||||
Hardware: Motorola 6820 PIA for keyboard and display interface
|
||||
|
||||
Memory map:
|
||||
|
||||
$0000-$1FFF: RAM address space
|
||||
$0000-$00FF: 6502 zero page
|
||||
$0024-$002B: Zero page locations used by the Monitor
|
||||
$0100-$01FF: 6502 processor stack
|
||||
$0200-$027F: Keyboard input buffer storage used by the Monitor
|
||||
$0280-$0FFF: RAM space available for a program in a 4 KB system
|
||||
$1000-$1FFF: Extra RAM space available for a program in an 8 KB system
|
||||
not using cassette BASIC
|
||||
|
||||
$2000-$BFFF: Unused address space, available for RAM in systems larger
|
||||
than 8 KB.
|
||||
|
||||
$C000-$CFFF: Address space for optional cassette interface
|
||||
$C000-$C0FF: Cassette interface I/O range
|
||||
$C100-$C1FF: Cassette interface ROM
|
||||
|
||||
$D000-$DFFF: I/O address space
|
||||
$D010-$D013: Motorola 6820 PIA registers.
|
||||
$D010: Keyboard input port
|
||||
$D011: Control register for keyboard input port, with
|
||||
key-available flag.
|
||||
$D012: Display output port (bit 7 is a status input)
|
||||
$D013: Control register for display output port
|
||||
(PIA registers also mirrored at $D014-$D017, $D018-$D01B, $D01C-$D01F,
|
||||
$D030-$D03F, $D050-$D05F, ... , $DFD0-$DFDF, $DFF0-$DFFF.)
|
||||
|
||||
$E000-$EFFF: Extra RAM space available for a program in an 8 KB system
|
||||
modified to use cassette BASIC
|
||||
(The system simulated here always includes this RAM.)
|
||||
|
||||
If you wanted to load the BASIC as rom, here are the details:
|
||||
ROM_LOAD("basic.bin", 0xE000, 0x1000, CRC(d5e86efc) SHA1(04269c1c66e7d5b4aa5035462c6e612bf2ae9b91) )
|
||||
A machine-language program will typically be like this:
|
||||
C100R (enter the interface)
|
||||
0300.0FFFR (enter the load and end addresses, then load the tape)
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
0300R (run your program)
|
||||
|
||||
|
||||
$F000-$FFFF: ROM address space
|
||||
$FF00-$FFFF: Apple Monitor ROM
|
||||
To Load Tape Basic:
|
||||
C100R
|
||||
E000.EFFFR
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
E000R (It must say 4C - if not, your tape is no good).
|
||||
The BASIC prompt will appear
|
||||
>@
|
||||
|
||||
|
||||
How to use cassettes:
|
||||
The system has no error checking or checksums, and the cassette
|
||||
has no header.
|
||||
Therefore, you must know the details, and pass these to the
|
||||
interface yourself.
|
||||
BASIC has no cassette handling. You must enter the monitor
|
||||
with: CALL -151
|
||||
then when finished, re-enter BASIC with: E2B3R
|
||||
A BASIC program is split into two areas, one for the scratch pad,
|
||||
and one for the program proper.
|
||||
In BASIC you may have to adjust the allowed memory area, such as
|
||||
LOMEM = 768
|
||||
Then, go to the monitor: CALL -151
|
||||
C100R (enter the interface)
|
||||
00A4.00FFR 0300.0FFFR (load the 2 parts)
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
E2B3R (back to BASIC)
|
||||
You can LIST or RUN now.
|
||||
|
||||
|
||||
Examples:
|
||||
Saving is almost the same, when you specify the address range, enter
|
||||
W instead of R. The difficulty is finding out how long your program is.
|
||||
|
||||
A machine-language program will typically be like this:
|
||||
C100R (enter the interface)
|
||||
0300.0FFFR (enter the load and end addresses, then load the tape)
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
0300R (run your program)
|
||||
|
||||
|
||||
To Load Tape Basic:
|
||||
C100R
|
||||
E000.EFFFR
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
E000R (It must say 4C - if not, your tape is no good).
|
||||
The BASIC prompt will appear
|
||||
>@
|
||||
|
||||
|
||||
A BASIC program is split into two areas, one for the scratch pad,
|
||||
and one for the program proper.
|
||||
In BASIC you may have to adjust the allowed memory area, such as
|
||||
LOMEM = 768
|
||||
Then, go to the monitor: CALL -151
|
||||
C100R (enter the interface)
|
||||
00A4.00FFR 0300.0FFFR (load the 2 parts)
|
||||
You start the tape.
|
||||
When the prompt returns you stop the tape.
|
||||
E2B3R (back to BASIC)
|
||||
You can LIST or RUN now.
|
||||
|
||||
|
||||
Saving is almost the same, when you specify the address range, enter
|
||||
W instead of R. The difficulty is finding out how long your program is.
|
||||
|
||||
Insert a blank tape
|
||||
C100R
|
||||
0300.0FFFW
|
||||
Quickly press Record.
|
||||
When the prompt returns, press Stop.
|
||||
Insert a blank tape
|
||||
C100R
|
||||
0300.0FFFW
|
||||
Quickly press Record.
|
||||
When the prompt returns, press Stop.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "emu.h"
|
||||
#include "cpu/m6502/m6502.h"
|
||||
#include "machine/6821pia.h"
|
||||
#include "includes/apple1.h"
|
||||
#include "imagedev/snapquik.h"
|
||||
#include "machine/ram.h"
|
||||
|
||||
@ -139,59 +82,428 @@ When the prompt returns, press Stop.
|
||||
|
||||
#include "softlist.h"
|
||||
|
||||
/* port i/o functions */
|
||||
#define A1_CPU_TAG "maincpu"
|
||||
#define A1_PIA_TAG "pia6821"
|
||||
#define A1_BUS_TAG "a1bus"
|
||||
#define A1_BASICRAM_TAG "basicram"
|
||||
|
||||
/* memory w/r functions */
|
||||
|
||||
static ADDRESS_MAP_START( apple1_map, AS_PROGRAM, 8, apple1_state )
|
||||
/* In $D000-$DFFF, PIA is selected by address bit 4 being high,
|
||||
and PIA registers are addressed with address bits 0-1. All
|
||||
other address bits are ignored. Thus $D010-$D013 is mirrored
|
||||
at all $Dxxx addresses with bit 4 high. */
|
||||
AM_RANGE(0xd010, 0xd013) AM_MIRROR(0x0fec) AM_DEVREADWRITE("pia",pia6821_device, read, write)
|
||||
|
||||
/* We always include the remapped RAM for cassette BASIC, both for
|
||||
simplicity and to allow the running of BASIC programs. */
|
||||
AM_RANGE(0xe000, 0xefff) AM_RAM
|
||||
|
||||
AM_RANGE(0xf000, 0xfeff) AM_NOP
|
||||
|
||||
/* Monitor ROM: */
|
||||
AM_RANGE(0xff00, 0xffff) AM_ROM AM_REGION("maincpu", 0)
|
||||
ADDRESS_MAP_END
|
||||
|
||||
/* graphics output */
|
||||
|
||||
const gfx_layout apple1_charlayout =
|
||||
class apple1_state : public driver_device
|
||||
{
|
||||
7, 8, /* character cell is 7 pixels wide by 8 pixels high */
|
||||
64, /* 64 characters in 2513 character generator ROM */
|
||||
1, /* 1 bitplane */
|
||||
{ 0 },
|
||||
/* 5 visible pixels per row, starting at bit 3, with MSB being 0: */
|
||||
{ 3, 4, 5, 6, 7 },
|
||||
/* pixel rows stored from top to bottom: */
|
||||
{ 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 },
|
||||
8 * 8 /* 8 8-bit pixel rows per character */
|
||||
public:
|
||||
apple1_state(const machine_config &mconfig, device_type type, const char *tag)
|
||||
: driver_device(mconfig, type, tag),
|
||||
m_maincpu(*this, A1_CPU_TAG),
|
||||
m_pia(*this, A1_PIA_TAG),
|
||||
m_ram(*this, RAM_TAG),
|
||||
m_basicram(*this, A1_BASICRAM_TAG),
|
||||
m_kb0(*this, "KEY0"),
|
||||
m_kb1(*this, "KEY1"),
|
||||
m_kb2(*this, "KEY2"),
|
||||
m_kb3(*this, "KEY3"),
|
||||
m_kbspecial(*this, "KBSPECIAL")
|
||||
{ }
|
||||
|
||||
required_device<cpu_device> m_maincpu;
|
||||
required_device<pia6821_device> m_pia;
|
||||
required_device<ram_device> m_ram;
|
||||
required_shared_ptr<UINT8> m_basicram;
|
||||
required_ioport m_kb0, m_kb1, m_kb2, m_kb3, m_kbspecial;
|
||||
|
||||
virtual void machine_start() override;
|
||||
virtual void machine_reset() override;
|
||||
|
||||
DECLARE_PALETTE_INIT(apple2);
|
||||
UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
|
||||
|
||||
DECLARE_READ8_MEMBER(ram_r);
|
||||
DECLARE_WRITE8_MEMBER(ram_w);
|
||||
DECLARE_READ8_MEMBER(pia_keyboard_r);
|
||||
DECLARE_WRITE8_MEMBER(pia_display_w);
|
||||
DECLARE_WRITE_LINE_MEMBER(pia_display_gate_w);
|
||||
DECLARE_SNAPSHOT_LOAD_MEMBER( apple1 );
|
||||
TIMER_CALLBACK_MEMBER(ready_start_cb);
|
||||
TIMER_CALLBACK_MEMBER(ready_end_cb);
|
||||
TIMER_CALLBACK_MEMBER(keyboard_strobe_cb);
|
||||
|
||||
private:
|
||||
UINT8 *m_ram_ptr, *m_char_ptr;
|
||||
int m_ram_size, m_char_size;
|
||||
|
||||
UINT8 m_vram[40*24];
|
||||
int m_cursx, m_cursy;
|
||||
|
||||
bool m_reset_down;
|
||||
bool m_clear_down;
|
||||
|
||||
UINT8 m_transchar;
|
||||
UINT16 m_lastports[4];
|
||||
|
||||
void plot_text_character(bitmap_ind16 &bitmap, int xpos, int ypos, int xscale, UINT32 code, const UINT8 *textgfx_data, UINT32 textgfx_datalen);
|
||||
void poll_keyboard();
|
||||
|
||||
emu_timer *m_ready_start_timer, *m_ready_end_timer, *m_kbd_strobe_timer;
|
||||
};
|
||||
|
||||
static GFXDECODE_START( apple1 )
|
||||
GFXDECODE_ENTRY( "gfx1", 0x0000, apple1_charlayout, 0, 1 )
|
||||
GFXDECODE_END
|
||||
static const UINT8 apple1_keymap[] =
|
||||
{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '=', '[', ']', ';', '\'', // KEY0
|
||||
',', '.', '/', '\\', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', // KEY1
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\r', '_', // KEY2
|
||||
' ', '\x1b', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // KEY3
|
||||
|
||||
/* keyboard input */
|
||||
/*
|
||||
It's very likely that the keyboard assgnments are totally wrong: the code in machine/apple1.c
|
||||
makes arbitrary assumptions about the mapping of the keys. The schematics that are available
|
||||
on the web can help revealing the real layout.
|
||||
The large picture of Woz's Apple I at http://home.earthlink.net/~judgementcall/apple1.jpg
|
||||
show probably how the real keyboard was meant to be: note how the shifted symbols on the digits
|
||||
and on some letters are different from the ones produced by current emulation and the presence
|
||||
of the gray keys.
|
||||
*/
|
||||
')', '!', '@', '#', '$', '%', '^', '&', '*', '(', '_', '+', '[', ']', ':', '"', // KEY0 + shift
|
||||
'<', '>', '?', '\\', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', // KEY1 + shift
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\r', '_', // KEY2 + shift
|
||||
' ', '\x1b', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // KEY3 + shift
|
||||
|
||||
'0', '1', '\x00', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '8', '9', '\x1f', '=', '\x1b', '\x1d', ';', '\'', // KEY0 + CTRL
|
||||
',', '.', '/', '\x1c', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c', // KEY1 + CTRL
|
||||
'\x0d', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\r', '_', // KEY2 + CTRL
|
||||
' ', '\x1b', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // KEY3 + CTRL
|
||||
|
||||
};
|
||||
|
||||
// header is "LOAD:abcdDATA:" where abcd is the starting address
|
||||
SNAPSHOT_LOAD_MEMBER( apple1_state, apple1 )
|
||||
{
|
||||
UINT64 snapsize;
|
||||
UINT8 *data;
|
||||
UINT16 start, end;
|
||||
static const char hd1[6] = "LOAD:";
|
||||
static const char hd2[6] = "DATA:";
|
||||
|
||||
// get the snapshot's size
|
||||
snapsize = image.length();
|
||||
|
||||
if (snapsize < 12)
|
||||
{
|
||||
logerror("Snapshot is too short\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
if ((snapsize - 12) > 65535)
|
||||
{
|
||||
logerror("Snapshot is too long\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
data = (UINT8 *)image.ptr();
|
||||
if (!data)
|
||||
{
|
||||
logerror("Internal error loading snapshot\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
if ((memcmp(hd1, data, 5)) || (memcmp(hd2, &data[7], 5)))
|
||||
{
|
||||
logerror("Snapshot is invalid\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
start = (data[5]<<8) | data[6];
|
||||
end = (snapsize - 12) + start;
|
||||
|
||||
// check if this fits in RAM; load below 0xe000 must fit in RAMSIZE,
|
||||
// load at 0xe000 must fit in 4K
|
||||
if (((start < 0xe000) && (end > (m_ram_size - 1))) || (end > 0xefff))
|
||||
{
|
||||
logerror("Snapshot can't fit in RAM\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
if (start < 0xe000)
|
||||
{
|
||||
memcpy(m_ram_ptr + start, &data[12], snapsize - 12);
|
||||
}
|
||||
else if ((start >= 0xe000) && (start <= 0xefff))
|
||||
{
|
||||
memcpy(m_basicram + (start - 0xe000), &data[12], snapsize - 12);
|
||||
}
|
||||
else
|
||||
{
|
||||
logerror("Snapshot has invalid load address %04x\n", start);
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
return IMAGE_INIT_PASS;
|
||||
}
|
||||
|
||||
void apple1_state::poll_keyboard()
|
||||
{
|
||||
UINT8 special = m_kbspecial->read();
|
||||
UINT16 ports[4];
|
||||
int rawkey = 0;
|
||||
bool bKeypress = false;
|
||||
|
||||
// handle special keys first:
|
||||
if (special & 0x10) // RESET
|
||||
{
|
||||
m_reset_down = true;
|
||||
m_maincpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
|
||||
m_pia->reset();
|
||||
}
|
||||
else if (m_reset_down)
|
||||
{
|
||||
m_reset_down = false;
|
||||
m_maincpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
|
||||
}
|
||||
|
||||
if (special & 0x20) // CLEAR SCREEN
|
||||
{
|
||||
m_clear_down = true;
|
||||
memset(m_vram, 0, sizeof(m_vram));
|
||||
m_cursx = m_cursy = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_clear_down = false;
|
||||
}
|
||||
|
||||
// lower the keyboard strobe
|
||||
m_pia->ca1_w(0);
|
||||
|
||||
// cache all the rows
|
||||
ports[0] = m_kb0->read();
|
||||
ports[1] = m_kb1->read();
|
||||
ports[2] = m_kb2->read();
|
||||
ports[3] = m_kb3->read();
|
||||
|
||||
for (int port = 0; port < 4; port++)
|
||||
{
|
||||
UINT16 ptread = ports[port] ^ m_lastports[port];
|
||||
|
||||
for (int bit = 0; bit < 16; bit++)
|
||||
{
|
||||
// key changed?
|
||||
if (ptread & (1 << bit))
|
||||
{
|
||||
// key down?
|
||||
if (ports[port] & (1 << bit))
|
||||
{
|
||||
rawkey = (port * 16) + bit;
|
||||
m_lastports[port] |= (1 << bit);
|
||||
port = 4; // force outer for loop to quit too
|
||||
bKeypress = true;
|
||||
}
|
||||
else // key up
|
||||
{
|
||||
m_lastports[port] &= ~(1 << bit);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bKeypress)
|
||||
{
|
||||
if ((special & 0xc) != 0)
|
||||
{
|
||||
m_transchar = apple1_keymap[rawkey + (8*16)];
|
||||
}
|
||||
else if ((special & 0x3) != 0)
|
||||
{
|
||||
m_transchar = apple1_keymap[rawkey + (4*16)];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_transchar = apple1_keymap[rawkey];
|
||||
}
|
||||
// pulse the strobe line
|
||||
m_pia->ca1_w(1);
|
||||
}
|
||||
}
|
||||
|
||||
void apple1_state::plot_text_character(bitmap_ind16 &bitmap, int xpos, int ypos, int xscale, UINT32 code,
|
||||
const UINT8 *textgfx_data, UINT32 textgfx_datalen)
|
||||
{
|
||||
int x, y, i;
|
||||
const UINT8 *chardata;
|
||||
UINT16 color;
|
||||
int fg = 1, bg = 0;
|
||||
int charcode = (code & 0x1f) | (((code ^ 0x40) & 0x40) >> 1);
|
||||
|
||||
/* look up the character data */
|
||||
chardata = &textgfx_data[(charcode * 8)];
|
||||
|
||||
for (y = 0; y < 8; y++)
|
||||
{
|
||||
for (x = 0; x < 7; x++)
|
||||
{
|
||||
color = (chardata[y] & (1 << (6-x))) ? fg : bg;
|
||||
|
||||
for (i = 0; i < xscale; i++)
|
||||
{
|
||||
bitmap.pix16(ypos + y, xpos + (x * xscale) + i) = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 apple1_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
|
||||
{
|
||||
int vramad;
|
||||
int cursor_blink = 0;
|
||||
UINT8 curs_save = 0;
|
||||
|
||||
poll_keyboard();
|
||||
|
||||
// the cursor 555 timer counts 0.52 of a second; the cursor is ON for
|
||||
// 2 of those counts and OFF for the last one.
|
||||
if (((int)(machine().time().as_double() / (0.52 / 3.0)) % 3) < 2)
|
||||
{
|
||||
curs_save = m_vram[(m_cursy * 40) + m_cursx];
|
||||
m_vram[(m_cursy * 40) + m_cursx] = 0x40;
|
||||
cursor_blink = 1;
|
||||
}
|
||||
|
||||
for (int row = 0; row < cliprect.max_y; row += 8)
|
||||
{
|
||||
for (int col = 0; col < 40; col++)
|
||||
{
|
||||
vramad = ((row/8) * 40) + col;
|
||||
|
||||
plot_text_character(bitmap, col * 14, row, 2, m_vram[vramad],
|
||||
m_char_ptr, m_char_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor_blink)
|
||||
{
|
||||
m_vram[(m_cursy * 40) + m_cursx] = curs_save;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void apple1_state::machine_start()
|
||||
{
|
||||
m_ram_ptr = m_ram->pointer();
|
||||
m_ram_size = m_ram->size();
|
||||
m_char_ptr = memregion("gfx1")->base();
|
||||
m_char_size = memregion("gfx1")->bytes();
|
||||
|
||||
m_reset_down = m_clear_down = false;
|
||||
|
||||
m_ready_start_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(apple1_state::ready_start_cb), this));
|
||||
m_ready_end_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(apple1_state::ready_end_cb), this));
|
||||
m_kbd_strobe_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(apple1_state::keyboard_strobe_cb), this));
|
||||
}
|
||||
|
||||
void apple1_state::machine_reset()
|
||||
{
|
||||
memset(m_vram, 0, sizeof(m_vram));
|
||||
m_transchar = 0;
|
||||
m_cursx = m_cursy = 0;
|
||||
m_lastports[0] = m_lastports[1] = m_lastports[2] = m_lastports[3] = 0;
|
||||
}
|
||||
|
||||
READ8_MEMBER(apple1_state::ram_r)
|
||||
{
|
||||
if (offset < m_ram_size)
|
||||
{
|
||||
return m_ram_ptr[offset];
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
WRITE8_MEMBER(apple1_state::ram_w)
|
||||
{
|
||||
if (offset < m_ram_size)
|
||||
{
|
||||
m_ram_ptr[offset] = data;
|
||||
}
|
||||
}
|
||||
|
||||
static ADDRESS_MAP_START( apple1_map, AS_PROGRAM, 8, apple1_state )
|
||||
AM_RANGE(0x0000, 0xbfff) AM_READWRITE(ram_r, ram_w)
|
||||
AM_RANGE(0xd010, 0xd013) AM_MIRROR(0x0fec) AM_DEVREADWRITE(A1_PIA_TAG, pia6821_device, read, write)
|
||||
AM_RANGE(0xe000, 0xefff) AM_RAM AM_SHARE(A1_BASICRAM_TAG)
|
||||
AM_RANGE(0xff00, 0xffff) AM_ROM AM_REGION(A1_CPU_TAG, 0)
|
||||
ADDRESS_MAP_END
|
||||
|
||||
READ8_MEMBER(apple1_state::pia_keyboard_r)
|
||||
{
|
||||
return m_transchar | 0x80; // bit 7 is wired high, similar-ish to the Apple II
|
||||
}
|
||||
|
||||
WRITE8_MEMBER(apple1_state::pia_display_w)
|
||||
{
|
||||
data &= 0x7f; // D7 is ignored by the video h/w
|
||||
|
||||
// ignore characters if CLEAR is down
|
||||
if (m_clear_down)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// video h/w rejects control characters except CR
|
||||
if ((data < 32) && (data != '\r'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (data == '\r')
|
||||
{
|
||||
m_cursx = 0;
|
||||
m_cursy++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vram[(m_cursy * 40) + m_cursx] = data;
|
||||
|
||||
m_cursx++;
|
||||
if (m_cursx > 39)
|
||||
{
|
||||
m_cursx = 0;
|
||||
m_cursy++;
|
||||
}
|
||||
}
|
||||
|
||||
// scroll the screen if we're at the bottom
|
||||
if (m_cursy > 23)
|
||||
{
|
||||
for (int sy = 0; sy < 23; sy++)
|
||||
{
|
||||
memcpy(&m_vram[sy * 40], &m_vram[(sy + 1) * 40], 40);
|
||||
}
|
||||
memset(&m_vram[23*40], 0, 40);
|
||||
m_cursy = 23;
|
||||
}
|
||||
}
|
||||
|
||||
// CB2 here is connected two places: Port B bit 7 for CPU readback,
|
||||
// and to the display hardware
|
||||
WRITE_LINE_MEMBER(apple1_state::pia_display_gate_w)
|
||||
{
|
||||
m_pia->portb_w((state << 7) ^ 0x80);
|
||||
|
||||
// falling edge means start the display timer
|
||||
if (state == CLEAR_LINE)
|
||||
{
|
||||
m_ready_start_timer->adjust(machine().first_screen()->time_until_pos(m_cursy, m_cursx));
|
||||
}
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::ready_start_cb)
|
||||
{
|
||||
// we're ready, pulse CB1 for 3500 nanoseconds
|
||||
m_pia->cb1_w(0);
|
||||
m_ready_end_timer->adjust(attotime::from_nsec(3500));
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::ready_end_cb)
|
||||
{
|
||||
m_pia->cb1_w(1);
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::keyboard_strobe_cb)
|
||||
{
|
||||
m_pia->ca1_w(0);
|
||||
}
|
||||
|
||||
static INPUT_PORTS_START( apple1 )
|
||||
PORT_START("KEY0") /* first sixteen keys */
|
||||
PORT_START("KEY0")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_0) PORT_CHAR('0') PORT_CHAR(')')
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_1) PORT_CHAR('1') PORT_CHAR('!')
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_2) PORT_CHAR('2') PORT_CHAR('@')
|
||||
@ -209,7 +521,7 @@ static INPUT_PORTS_START( apple1 )
|
||||
PORT_BIT( 0x4000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_COLON) PORT_CHAR(';') PORT_CHAR(':')
|
||||
PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_QUOTE) PORT_CHAR('\'') PORT_CHAR('"')
|
||||
|
||||
PORT_START("KEY1") /* second sixteen keys */
|
||||
PORT_START("KEY1")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_COMMA) PORT_CHAR(',') PORT_CHAR('<')
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_STOP) PORT_CHAR('.') PORT_CHAR('>')
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_SLASH) PORT_CHAR('/') PORT_CHAR('?')
|
||||
@ -227,7 +539,7 @@ static INPUT_PORTS_START( apple1 )
|
||||
PORT_BIT( 0x4000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_K) PORT_CHAR('K')
|
||||
PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_L) PORT_CHAR('L')
|
||||
|
||||
PORT_START("KEY2") /* third sixteen keys */
|
||||
PORT_START("KEY2")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_M) PORT_CHAR('M')
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_N) PORT_CHAR('N')
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_O) PORT_CHAR('O')
|
||||
@ -243,21 +555,19 @@ static INPUT_PORTS_START( apple1 )
|
||||
PORT_BIT( 0x1000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_Y) PORT_CHAR('Y')
|
||||
PORT_BIT( 0x2000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_Z) PORT_CHAR('Z')
|
||||
PORT_BIT( 0x4000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_ENTER) PORT_CHAR(13)
|
||||
PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Backarrow") PORT_CODE(KEYCODE_BACKSPACE) PORT_CHAR('_')
|
||||
PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Backspace") PORT_CODE(KEYCODE_BACKSPACE) PORT_CHAR('_')
|
||||
|
||||
PORT_START("KEY3") /* fourth sixteen keys */
|
||||
PORT_START("KEY3")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_CODE(KEYCODE_SPACE) PORT_CHAR(' ')
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Escape") PORT_CODE(KEYCODE_ESC) PORT_CHAR(UCHAR_MAMEKEY(ESC))
|
||||
|
||||
PORT_START("KEY4") /* shift keys */
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Shift (Left)") PORT_CODE(KEYCODE_LSHIFT) PORT_CHAR(UCHAR_SHIFT_1)
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Shift (Right)") PORT_CODE(KEYCODE_RSHIFT) PORT_CHAR(UCHAR_SHIFT_1)
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Control (Left)") PORT_CODE(KEYCODE_LCONTROL) PORT_CHAR(UCHAR_SHIFT_2)
|
||||
PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Control (Right)") PORT_CODE(KEYCODE_RCONTROL) PORT_CHAR(UCHAR_SHIFT_2)
|
||||
|
||||
PORT_START("KEY5") /* RESET and CLEAR SCREEN pushbutton switches */
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Reset") PORT_CODE(KEYCODE_F12) PORT_CHAR(UCHAR_MAMEKEY(F1))
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Clear") PORT_CODE(KEYCODE_F2) PORT_CHAR(UCHAR_MAMEKEY(F2))
|
||||
PORT_START("KBSPECIAL")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Left Shift") PORT_CODE(KEYCODE_LSHIFT) PORT_CHAR(UCHAR_SHIFT_1)
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Right Shift") PORT_CODE(KEYCODE_RSHIFT) PORT_CHAR(UCHAR_SHIFT_1)
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Left Control") PORT_CODE(KEYCODE_LCONTROL) PORT_CHAR(UCHAR_SHIFT_2)
|
||||
PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Right Control") PORT_CODE(KEYCODE_RCONTROL) PORT_CHAR(UCHAR_SHIFT_2)
|
||||
PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Reset") PORT_CODE(KEYCODE_F12) PORT_CHAR(UCHAR_MAMEKEY(F1))
|
||||
PORT_BIT( 0x0020, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Clear") PORT_CODE(KEYCODE_F2) PORT_CHAR(UCHAR_MAMEKEY(F2))
|
||||
INPUT_PORTS_END
|
||||
|
||||
static SLOT_INTERFACE_START(apple1_cards)
|
||||
@ -265,69 +575,44 @@ static SLOT_INTERFACE_START(apple1_cards)
|
||||
SLOT_INTERFACE("cffa", A1BUS_CFFA)
|
||||
SLOT_INTERFACE_END
|
||||
|
||||
/* machine definition */
|
||||
static MACHINE_CONFIG_START( apple1, apple1_state )
|
||||
/* basic machine hardware */
|
||||
/* Actual CPU speed is 1.023 MHz, but RAM refresh effectively
|
||||
slows it to 960 kHz. */
|
||||
MCFG_CPU_ADD("maincpu", M6502, 960000) /* 1.023 MHz */
|
||||
MCFG_CPU_ADD(A1_CPU_TAG, M6502, 960000) // effective CPU speed
|
||||
MCFG_CPU_PROGRAM_MAP(apple1_map)
|
||||
|
||||
MCFG_QUANTUM_TIME(attotime::from_hz(60))
|
||||
|
||||
// video timings are identical to the Apple II, unsurprisingly
|
||||
MCFG_SCREEN_ADD("screen", RASTER)
|
||||
MCFG_SCREEN_REFRESH_RATE(60)
|
||||
/* Video is blanked for 70 out of 262 scanlines per refresh cycle.
|
||||
Each scanline is composed of 65 character times, 40 of which
|
||||
are visible, and each character time is 7 dot times; a dot time
|
||||
is 2 cycles of the fundamental 14.31818 MHz oscillator. The
|
||||
total blanking time is about 4450 microseconds. */
|
||||
MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC((int) (70 * 65 * 7 * 2 / 14.31818)))
|
||||
/* It would be nice if we could implement some sort of display
|
||||
overscan here. */
|
||||
MCFG_SCREEN_SIZE(40 * 7, 24 * 8)
|
||||
MCFG_SCREEN_VISIBLE_AREA(0, 40 * 7 - 1, 0, 24 * 8 - 1)
|
||||
MCFG_SCREEN_UPDATE_DRIVER(apple1_state, screen_update_apple1)
|
||||
MCFG_SCREEN_RAW_PARAMS(XTAL_14_31818MHz, (65*7)*2, 0, (40*7)*2, 262, 0, 192)
|
||||
MCFG_SCREEN_UPDATE_DRIVER(apple1_state, screen_update)
|
||||
MCFG_SCREEN_PALETTE("palette")
|
||||
|
||||
MCFG_GFXDECODE_ADD("gfxdecode", "palette", apple1)
|
||||
|
||||
MCFG_PALETTE_ADD_BLACK_AND_WHITE("palette")
|
||||
|
||||
MCFG_DEVICE_ADD( "pia", PIA6821, 0)
|
||||
MCFG_PIA_READPA_HANDLER(READ8(apple1_state,apple1_pia0_kbdin))
|
||||
MCFG_PIA_WRITEPB_HANDLER(WRITE8(apple1_state,apple1_pia0_dspout))
|
||||
MCFG_PIA_CB2_HANDLER(WRITELINE(apple1_state,apple1_pia0_dsp_write_signal))
|
||||
MCFG_DEVICE_ADD( A1_PIA_TAG, PIA6821, 0)
|
||||
MCFG_PIA_READPA_HANDLER(READ8(apple1_state, pia_keyboard_r))
|
||||
MCFG_PIA_WRITEPB_HANDLER(WRITE8(apple1_state, pia_display_w))
|
||||
MCFG_PIA_CB2_HANDLER(WRITELINE(apple1_state, pia_display_gate_w))
|
||||
|
||||
MCFG_DEVICE_ADD("a1bus", A1BUS, 0)
|
||||
MCFG_DEVICE_ADD(A1_BUS_TAG, A1BUS, 0)
|
||||
MCFG_A1BUS_CPU("maincpu")
|
||||
MCFG_A1BUS_SLOT_ADD("a1bus", "exp", apple1_cards, "cassette")
|
||||
MCFG_A1BUS_SLOT_ADD(A1_BUS_TAG, "exp", apple1_cards, "cassette")
|
||||
|
||||
/* snapshot */
|
||||
MCFG_SNAPSHOT_ADD("snapshot", apple1_state, apple1, "snp", 0)
|
||||
|
||||
MCFG_SOFTWARE_LIST_ADD("cass_list","apple1")
|
||||
MCFG_SOFTWARE_LIST_ADD("cass_list", "apple1")
|
||||
|
||||
/* Note that because we always include 4K of RAM at $E000-$EFFF,
|
||||
the RAM amounts listed here will be 4K below the actual RAM
|
||||
total. */
|
||||
/* internal ram */
|
||||
MCFG_RAM_ADD(RAM_TAG)
|
||||
MCFG_RAM_DEFAULT_SIZE("48K")
|
||||
MCFG_RAM_EXTRA_OPTIONS("4K,8K,12K,16K,20K,24K,28K,32K,36K,40K,44K")
|
||||
|
||||
MACHINE_CONFIG_END
|
||||
|
||||
ROM_START(apple1)
|
||||
ROM_REGION(0x100, "maincpu",0)
|
||||
/* 256-byte main monitor ROM, in two 82s129 or mmi6301 256x4 proms at A1 and A2 called APPLE-A1(bits D3-D0) and APPLE-A2(bits D7-D4) */
|
||||
ROM_LOAD_NIB_HIGH( "apple-a2.a2", 0x0000, 0x0100, CRC(254bfb95) SHA1(b6468b72295b7d8ac288d104d252f24de1f1d611) )
|
||||
ROM_LOAD_NIB_LOW( "apple-a1.a1", 0x0000, 0x0100, CRC(434f8ce6) SHA1(9deee2d39903209b20c3fc6b58e16372f8efece1) )
|
||||
/* 512-byte Signetics 2513 character generator ROM at location D2-D3 */
|
||||
ROM_REGION(0x100, A1_CPU_TAG, 0)
|
||||
ROM_LOAD_NIB_HIGH("apple-a2.a2", 0x0000, 0x0100, CRC(254bfb95) SHA1(b6468b72295b7d8ac288d104d252f24de1f1d611) )
|
||||
ROM_LOAD_NIB_LOW("apple-a1.a1", 0x0000, 0x0100, CRC(434f8ce6) SHA1(9deee2d39903209b20c3fc6b58e16372f8efece1) )
|
||||
ROM_REGION(0x0200, "gfx1",0)
|
||||
ROM_LOAD("s2513.d2", 0x0000, 0x0200, CRC(a7e567fc) SHA1(b18aae0a2d4f92f5a7e22640719bbc4652f3f4ee)) // apple1.vid
|
||||
ROM_END
|
||||
|
||||
/* YEAR NAME PARENT COMPAT MACHINE INPUT STATE INIT COMPANY FULLNAME */
|
||||
COMP( 1976, apple1, 0, 0, apple1, apple1, driver_device, 0, "Apple Computer", "Apple I", MACHINE_NO_SOUND_HW )
|
||||
|
||||
/* YEAR NAME PARENT COMPAT MACHINE INPUT INIT COMPANY FULLNAME */
|
||||
COMP( 1976, apple1, 0, 0, apple1, apple1, apple1_state, apple1, "Apple Computer", "Apple I" , MACHINE_NO_SOUND )
|
||||
|
@ -1,92 +0,0 @@
|
||||
// license:???
|
||||
// copyright-holders:Paul Daniels, Colin Howell, R. Belmont
|
||||
/*****************************************************************************
|
||||
*
|
||||
* includes/apple1.h
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef APPLE1_H_
|
||||
#define APPLE1_H_
|
||||
|
||||
#include "imagedev/snapquik.h"
|
||||
#include "machine/ram.h"
|
||||
|
||||
typedef short termchar_t;
|
||||
|
||||
struct terminal_t
|
||||
{
|
||||
tilemap_t *tm;
|
||||
int gfx;
|
||||
int blank_char;
|
||||
int char_bits;
|
||||
int num_cols;
|
||||
int num_rows;
|
||||
int (*getcursorcode)(int original_code);
|
||||
int cur_offset;
|
||||
int cur_hidden;
|
||||
termchar_t mem[1];
|
||||
};
|
||||
|
||||
|
||||
class apple1_state : public driver_device
|
||||
{
|
||||
public:
|
||||
apple1_state(const machine_config &mconfig, device_type type, const char *tag)
|
||||
: driver_device(mconfig, type, tag),
|
||||
m_maincpu(*this, "maincpu"),
|
||||
m_ram(*this, RAM_TAG),
|
||||
m_gfxdecode(*this, "gfxdecode"),
|
||||
m_screen(*this, "screen") { }
|
||||
|
||||
int m_vh_clrscrn_pressed;
|
||||
int m_kbd_data;
|
||||
UINT32 m_kbd_last_scan[4];
|
||||
int m_reset_flag;
|
||||
terminal_t *m_current_terminal;
|
||||
terminal_t *m_terminal;
|
||||
int m_blink_on;
|
||||
DECLARE_DRIVER_INIT(apple1);
|
||||
TILE_GET_INFO_MEMBER(terminal_gettileinfo);
|
||||
virtual void machine_reset() override;
|
||||
virtual void video_start() override;
|
||||
UINT32 screen_update_apple1(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
|
||||
TIMER_CALLBACK_MEMBER(apple1_kbd_poll);
|
||||
TIMER_CALLBACK_MEMBER(apple1_kbd_strobe_end);
|
||||
TIMER_CALLBACK_MEMBER(apple1_dsp_ready_start);
|
||||
TIMER_CALLBACK_MEMBER(apple1_dsp_ready_end);
|
||||
DECLARE_READ8_MEMBER(apple1_pia0_kbdin);
|
||||
DECLARE_WRITE8_MEMBER(apple1_pia0_dspout);
|
||||
DECLARE_WRITE_LINE_MEMBER(apple1_pia0_dsp_write_signal);
|
||||
required_device<cpu_device> m_maincpu;
|
||||
void terminal_draw(screen_device &screen, bitmap_ind16 &dest, const rectangle &cliprect, terminal_t *terminal);
|
||||
void verify_coords(terminal_t *terminal, int x, int y);
|
||||
void terminal_putchar(terminal_t *terminal, int x, int y, int ch);
|
||||
int terminal_getchar(terminal_t *terminal, int x, int y);
|
||||
void terminal_putblank(terminal_t *terminal, int x, int y);
|
||||
void terminal_dirtycursor(terminal_t *terminal);
|
||||
void terminal_setcursor(terminal_t *terminal, int x, int y);
|
||||
void terminal_hidecursor(terminal_t *terminal);
|
||||
void terminal_showcursor(terminal_t *terminal);
|
||||
void terminal_getcursor(terminal_t *terminal, int *x, int *y);
|
||||
void terminal_fill(terminal_t *terminal, int val);
|
||||
void terminal_clear(terminal_t *terminal);
|
||||
void apple1_vh_dsp_w (int data);
|
||||
void apple1_vh_dsp_clr ();
|
||||
void apple1_vh_cursor_blink ();
|
||||
int apple1_verify_header (UINT8 *data);
|
||||
terminal_t *terminal_create(int gfx, int blank_char, int char_bits,int (*getcursorcode)(int original_code),int num_cols, int num_rows);
|
||||
attotime apple1_vh_dsp_time_to_ready();
|
||||
DECLARE_SNAPSHOT_LOAD_MEMBER( apple1 );
|
||||
required_device<ram_device> m_ram;
|
||||
required_device<gfxdecode_device> m_gfxdecode;
|
||||
required_device<screen_device> m_screen;
|
||||
};
|
||||
|
||||
|
||||
/*----------- defined in drivers/apple1.c -----------*/
|
||||
|
||||
extern const gfx_layout apple1_charlayout;
|
||||
|
||||
|
||||
#endif /* APPLE1_H_ */
|
@ -1,404 +0,0 @@
|
||||
// license:???
|
||||
// copyright-holders:Paul Daniels, Colin Howell, R. Belmont
|
||||
/***************************************************************************
|
||||
|
||||
machine.c
|
||||
|
||||
Functions to emulate general aspects of the machine (RAM, ROM, interrupts,
|
||||
I/O ports)
|
||||
|
||||
The Apple I used a Motorola 6820 PIA for its keyboard and display
|
||||
I/O. The keyboard was mapped to PIA port A, and the display to port
|
||||
B.
|
||||
|
||||
Port A, the keyboard, was an input port connected to a standard
|
||||
ASCII-encoded keyboard. The high bit of the port was tied to +5V.
|
||||
The keyboard strobe signal was connected to the PIA's CA1 control
|
||||
input so that the keyboard could signal each keypress to the PIA.
|
||||
The processor could check for a keypress by testing the IRQA1 flag
|
||||
in the Port A Control Register and then reading the character value
|
||||
from Port A.
|
||||
|
||||
The keyboard connector also had two special lines, RESET and CLEAR
|
||||
SCREEN, which were meant to be connected to pushbutton switches on
|
||||
the keyboard. RESET was tied to the reset inputs for the CPU and
|
||||
PIA; it allowed the user to stop a program and return control to the
|
||||
Monitor. CLEAR SCREEN was directly tied to the video hardware and
|
||||
would clear the display.
|
||||
|
||||
Port B, the display, was an output port which accepted 7-bit ASCII
|
||||
characters from the PIA and wrote them on the display. The details
|
||||
of this are described in video/apple1.c. Control line CB2 served
|
||||
as an output signal to inform the display of a new character. (CB2
|
||||
was also connected to line 7 of port B, which was configured as an
|
||||
input, so that the CPU could more easily check the status of the
|
||||
write.) The CB1 control input signaled the PIA when the display had
|
||||
finished writing the character and could accept a new one.
|
||||
|
||||
MAME models the 6821 instead of the earlier 6820 used in the Apple
|
||||
I, but there is no difference in functionality between the two
|
||||
chips; the 6821 simply has a better ability to drive electrical
|
||||
loads.
|
||||
|
||||
The Apple I had an optional cassette interface which plugged into
|
||||
the expansion connector. This is described below in the "Cassette
|
||||
interface I/O" section.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "emu.h"
|
||||
#include "includes/apple1.h"
|
||||
#include "machine/6821pia.h"
|
||||
#include "cpu/m6502/m6502.h"
|
||||
#include "imagedev/cassette.h"
|
||||
#include "machine/ram.h"
|
||||
|
||||
/*****************************************************************************
|
||||
** Structures
|
||||
*****************************************************************************/
|
||||
|
||||
/* Use the same keyboard mapping as on a modern keyboard. This is not
|
||||
the same as the keyboard mapping of the actual teletype-style
|
||||
keyboards used with the Apple I, but it's less likely to cause
|
||||
confusion for people who haven't memorized that layout.
|
||||
|
||||
The Backspace key is mapped to the '_' (underscore) character
|
||||
because the Apple I ROM Monitor used "back-arrow" to erase
|
||||
characters, rather than backspace, and back-arrow is an earlier
|
||||
form of the underscore. */
|
||||
|
||||
#define ESCAPE '\x1b'
|
||||
|
||||
static const UINT8 apple1_unshifted_keymap[] =
|
||||
{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', '-', '=', '[', ']', ';', '\'',
|
||||
',', '.', '/', '\\', 'A', 'B', 'C', 'D',
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', '\r', '_',
|
||||
' ', ESCAPE
|
||||
};
|
||||
|
||||
static const UINT8 apple1_shifted_keymap[] =
|
||||
{
|
||||
')', '!', '@', '#', '$', '%', '^', '&',
|
||||
'*', '(', '_', '+', '[', ']', ':', '"',
|
||||
'<', '>', '?', '\\', 'A', 'B', 'C', 'D',
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', '\r', '_',
|
||||
' ', ESCAPE
|
||||
};
|
||||
|
||||
/* Control key mappings, like the other mappings, conform to a modern
|
||||
keyboard where possible. Note that the Apple I ROM Monitor ignores
|
||||
most control characters. */
|
||||
|
||||
static const UINT8 apple1_control_keymap[] =
|
||||
{
|
||||
'0', '1', '\x00', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
|
||||
'8', '9', '\x1f', '=', '\x1b', '\x1d', ';', '\'',
|
||||
',', '.', '/', '\x1c', '\x01', '\x02', '\x03', '\x04',
|
||||
'\x05', '\x06', '\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c',
|
||||
'\x0d', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14',
|
||||
'\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\r', '_',
|
||||
'\x00', ESCAPE
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
** DRIVER_INIT: driver-specific setup, executed once at MESS startup.
|
||||
*****************************************************************************/
|
||||
|
||||
DRIVER_INIT_MEMBER(apple1_state,apple1)
|
||||
{
|
||||
address_space& space = m_maincpu->space(AS_PROGRAM);
|
||||
/* Set up the handlers for MESS's dynamically-sized RAM. */
|
||||
space.install_readwrite_bank(0x0000, m_ram->size() - 1, "bank1");
|
||||
membank("bank1")->set_base(m_ram->pointer());
|
||||
|
||||
/* Poll the keyboard input ports periodically. These include both
|
||||
ordinary keys and the RESET and CLEAR SCREEN pushbutton
|
||||
switches. We can't handle these switches in a VBLANK_INT or
|
||||
PERIODIC_INT because both switches need to be monitored even
|
||||
while the CPU is suspended during RESET; VBLANK_INT and
|
||||
PERIODIC_INT callbacks aren't run while the CPU is in this
|
||||
state.
|
||||
|
||||
A 120-Hz poll rate seems to be fast enough to ensure no
|
||||
keystrokes are missed. */
|
||||
machine().scheduler().timer_pulse(attotime::from_hz(120), timer_expired_delegate(FUNC(apple1_state::apple1_kbd_poll),this));
|
||||
}
|
||||
|
||||
|
||||
void apple1_state::machine_reset()
|
||||
{
|
||||
/* Reset the display hardware. */
|
||||
apple1_vh_dsp_clr();
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
** apple1_verify_header
|
||||
*****************************************************************************/
|
||||
int apple1_state::apple1_verify_header (UINT8 *data)
|
||||
{
|
||||
/* Verify the format for the snapshot */
|
||||
if ((data[0] == 'L') &&
|
||||
(data[1] == 'O') &&
|
||||
(data[2] == 'A') &&
|
||||
(data[3] == 'D') &&
|
||||
(data[4] == ':') &&
|
||||
(data[7] == 'D') &&
|
||||
(data[8] == 'A') &&
|
||||
(data[9] == 'T') &&
|
||||
(data[10]== 'A') &&
|
||||
(data[11]== ':'))
|
||||
{
|
||||
return(IMAGE_VERIFY_PASS);
|
||||
}
|
||||
else
|
||||
{
|
||||
return(IMAGE_VERIFY_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
#define SNAP_HEADER_LEN 12
|
||||
|
||||
/*****************************************************************************
|
||||
** snapshot_load_apple1
|
||||
**
|
||||
** Format of the binary snapshot image is:
|
||||
**
|
||||
** [ LOAD:xxyyDATA:zzzzzz...]
|
||||
**
|
||||
** where xxyy is the binary starting address (in big-endian byte
|
||||
** order) to load the binary data zzzzzz to.
|
||||
**
|
||||
** The image can be of arbitrary length, but it must fit in available
|
||||
** memory.
|
||||
*****************************************************************************/
|
||||
SNAPSHOT_LOAD_MEMBER( apple1_state,apple1)
|
||||
{
|
||||
UINT64 filesize, datasize;
|
||||
UINT8 *snapbuf, *snapptr;
|
||||
UINT16 start_addr, end_addr, addr;
|
||||
|
||||
filesize = image.length();
|
||||
|
||||
/* Read the snapshot data into a temporary array */
|
||||
if (filesize < SNAP_HEADER_LEN)
|
||||
return IMAGE_INIT_FAIL;
|
||||
snapbuf = (UINT8*)image.ptr();
|
||||
if (!snapbuf)
|
||||
return IMAGE_INIT_FAIL;
|
||||
|
||||
/* Verify the snapshot header */
|
||||
if (apple1_verify_header(snapbuf) == IMAGE_VERIFY_FAIL)
|
||||
{
|
||||
logerror("apple1 - Snapshot Header is in incorrect format - needs to be LOAD:xxyyDATA:\n");
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
datasize = filesize - SNAP_HEADER_LEN;
|
||||
|
||||
/* Extract the starting address to load the snapshot to. */
|
||||
start_addr = (snapbuf[5] << 8) | (snapbuf[6]);
|
||||
logerror("apple1 - LoadAddress is 0x%04x\n", start_addr);
|
||||
|
||||
end_addr = start_addr + datasize - 1;
|
||||
|
||||
if ((start_addr < 0xE000 && end_addr > m_ram->size() - 1)
|
||||
|| end_addr > 0xEFFF)
|
||||
{
|
||||
logerror("apple1 - Snapshot won't fit in this memory configuration;\n"
|
||||
"needs memory from $%04X to $%04X.\n", start_addr, end_addr);
|
||||
return IMAGE_INIT_FAIL;
|
||||
}
|
||||
|
||||
/* Copy the data into memory space. */
|
||||
for (addr = start_addr, snapptr = snapbuf + SNAP_HEADER_LEN;
|
||||
addr <= end_addr;
|
||||
addr++, snapptr++)
|
||||
m_maincpu->space(AS_PROGRAM).write_byte(addr, *snapptr);
|
||||
|
||||
|
||||
return IMAGE_INIT_PASS;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
** apple1_kbd_poll
|
||||
**
|
||||
** Keyboard polling handles both ordinary keys and the special RESET
|
||||
** and CLEAR SCREEN switches.
|
||||
**
|
||||
** For ordinary keys, this implements 2-key rollover to reduce the
|
||||
** chance of missed keypresses. If we press a key and then press a
|
||||
** second key while the first hasn't been completely released, as
|
||||
** might happen during rapid typing, only the second key is
|
||||
** registered; the first key is ignored.
|
||||
**
|
||||
** If multiple newly-pressed keys are found, the one closest to the
|
||||
** end of the input ports list is counted; the others are ignored.
|
||||
*****************************************************************************/
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::apple1_kbd_poll)
|
||||
{
|
||||
int port, bit;
|
||||
int key_pressed;
|
||||
UINT32 shiftkeys, ctrlkeys;
|
||||
pia6821_device *pia = machine().device<pia6821_device>("pia");
|
||||
static const char *const keynames[] = { "KEY0", "KEY1", "KEY2", "KEY3" };
|
||||
|
||||
/* This holds the values of all the input ports for ordinary keys
|
||||
seen during the last scan. */
|
||||
|
||||
/* First we check the RESET and CLEAR SCREEN pushbutton switches. */
|
||||
|
||||
/* The RESET switch resets the CPU and the 6820 PIA. */
|
||||
if (ioport("KEY5")->read() & 0x0001)
|
||||
{
|
||||
if (!m_reset_flag) {
|
||||
m_reset_flag = 1;
|
||||
/* using PULSE_LINE does not allow us to press and hold key */
|
||||
m_maincpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
|
||||
pia->reset();
|
||||
}
|
||||
}
|
||||
else if (m_reset_flag) {
|
||||
/* RESET released--allow the processor to continue. */
|
||||
m_reset_flag = 0;
|
||||
m_maincpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
|
||||
}
|
||||
|
||||
/* The CLEAR SCREEN switch clears the video hardware. */
|
||||
if (ioport("KEY5")->read() & 0x0002)
|
||||
{
|
||||
if (!m_vh_clrscrn_pressed)
|
||||
{
|
||||
/* Ignore further video writes, and clear the screen. */
|
||||
m_vh_clrscrn_pressed = 1;
|
||||
apple1_vh_dsp_clr();
|
||||
}
|
||||
}
|
||||
else if (m_vh_clrscrn_pressed)
|
||||
{
|
||||
/* CLEAR SCREEN released--pay attention to video writes again. */
|
||||
m_vh_clrscrn_pressed = 0;
|
||||
}
|
||||
|
||||
/* Now we scan all the input ports for ordinary keys, recording
|
||||
new keypresses while ignoring keys that were already pressed in
|
||||
the last scan. */
|
||||
|
||||
m_kbd_data = 0;
|
||||
key_pressed = 0;
|
||||
|
||||
/* The keyboard strobe line should always be low when a scan starts. */
|
||||
pia->ca1_w(0);
|
||||
|
||||
shiftkeys = ioport("KEY4")->read() & 0x0003;
|
||||
ctrlkeys = ioport("KEY4")->read() & 0x000c;
|
||||
|
||||
for (port = 0; port < 4; port++)
|
||||
{
|
||||
UINT32 portval, newkeys;
|
||||
|
||||
portval = ioport(keynames[port])->read();
|
||||
newkeys = portval & ~(m_kbd_last_scan[port]);
|
||||
|
||||
if (newkeys)
|
||||
{
|
||||
key_pressed = 1;
|
||||
for (bit = 0; bit < 16; bit++) {
|
||||
if (newkeys & 1)
|
||||
{
|
||||
m_kbd_data = (ctrlkeys)
|
||||
? apple1_control_keymap[port*16 + bit]
|
||||
: (shiftkeys)
|
||||
? apple1_shifted_keymap[port*16 + bit]
|
||||
: apple1_unshifted_keymap[port*16 + bit];
|
||||
}
|
||||
newkeys >>= 1;
|
||||
}
|
||||
}
|
||||
m_kbd_last_scan[port] = portval;
|
||||
}
|
||||
|
||||
if (key_pressed)
|
||||
{
|
||||
/* The keyboard will pulse its strobe line when a key is
|
||||
pressed. A 10-usec pulse is typical. */
|
||||
pia->ca1_w(1);
|
||||
machine().scheduler().timer_set(attotime::from_usec(10), timer_expired_delegate(FUNC(apple1_state::apple1_kbd_strobe_end),this));
|
||||
}
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::apple1_kbd_strobe_end)
|
||||
{
|
||||
pia6821_device *pia = machine().device<pia6821_device>("pia");
|
||||
|
||||
/* End of the keyboard strobe pulse. */
|
||||
pia->ca1_w(0);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
** READ/WRITE HANDLERS
|
||||
*****************************************************************************/
|
||||
READ8_MEMBER(apple1_state::apple1_pia0_kbdin)
|
||||
{
|
||||
/* Bit 7 of the keyboard input is permanently wired high. This is
|
||||
what the ROM Monitor software expects. */
|
||||
return m_kbd_data | 0x80;
|
||||
}
|
||||
|
||||
WRITE8_MEMBER(apple1_state::apple1_pia0_dspout)
|
||||
{
|
||||
/* Send an ASCII character to the video hardware. */
|
||||
apple1_vh_dsp_w(data);
|
||||
}
|
||||
|
||||
WRITE_LINE_MEMBER(apple1_state::apple1_pia0_dsp_write_signal)
|
||||
{
|
||||
device_t *device = machine().device("pia");
|
||||
/* PIA output CB2 is inverted to become the DA signal, used to
|
||||
signal a display write to the video hardware. */
|
||||
|
||||
/* DA is directly connected to PIA input PB7, so the processor can
|
||||
read bit 7 of port B to test whether the display has completed
|
||||
a write. */
|
||||
pia6821_device *pia = downcast<pia6821_device *>(device);
|
||||
pia->portb_w((!state) << 7);
|
||||
|
||||
/* Once DA is asserted, the display will wait until it can perform
|
||||
the write, when the cursor position is about to be refreshed.
|
||||
Only then will it assert \RDA to signal readiness for another
|
||||
write. Thus the write delay depends on the cursor position and
|
||||
where the display is in the refresh cycle. */
|
||||
if (!state)
|
||||
machine().scheduler().timer_set(apple1_vh_dsp_time_to_ready(), timer_expired_delegate(FUNC(apple1_state::apple1_dsp_ready_start),this));
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::apple1_dsp_ready_start)
|
||||
{
|
||||
pia6821_device *pia = machine().device<pia6821_device>("pia");
|
||||
|
||||
/* When the display asserts \RDA to signal it is ready, it
|
||||
triggers a 74123 one-shot to send a 3.5-usec low pulse to PIA
|
||||
input CB1. The end of this pulse will tell the PIA that the
|
||||
display is ready for another write. */
|
||||
pia->cb1_w(0);
|
||||
machine().scheduler().timer_set(attotime::from_nsec(3500), timer_expired_delegate(FUNC(apple1_state::apple1_dsp_ready_end),this));
|
||||
}
|
||||
|
||||
TIMER_CALLBACK_MEMBER(apple1_state::apple1_dsp_ready_end)
|
||||
{
|
||||
pia6821_device *pia = machine().device<pia6821_device>("pia");
|
||||
|
||||
/* The one-shot pulse has ended; return CB1 to high, so we can do
|
||||
another display write. */
|
||||
pia->cb1_w(1);
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
// license:???
|
||||
// copyright-holders:Paul Daniels, Colin Howell, R. Belmont
|
||||
/***************************************************************************
|
||||
|
||||
apple1.c
|
||||
|
||||
Functions to emulate the video hardware of the Apple I.
|
||||
|
||||
The Apple I video hardware was basically a dumb video terminal; in
|
||||
fact it was based on Steve Wozniak's own design for a simple video
|
||||
terminal. It had 40 columns by 24 lines of uppercase-only text.
|
||||
Text could only be output at 60 characters per second, one character
|
||||
per video frame. The cursor (a blinking @) could only be advanced
|
||||
using spaces or carriage returns. Carriage returns were the only
|
||||
control characters recognized. Previously written text could not be
|
||||
altered, only scrolled off the top of the screen.
|
||||
|
||||
The video memory used seven 1k-bit dynamic shift registers. Six of
|
||||
these held the 6-bit visible character codes, and one stored the
|
||||
cursor location as a simple bitmap--the bit for the cursor position
|
||||
was set to 0, and all the other bits were 1.
|
||||
|
||||
These shift registers were continuously recirculated, completing one
|
||||
cycle per video frame. As a new line of characters was about to be
|
||||
scanned by the video beam, that character line would be recirculated
|
||||
into the shift registers and would simultaneously be stored into a
|
||||
6x40-bit line buffer (also a shift register). At this point, if the
|
||||
cursor location was in this line, a new character could be written
|
||||
into that location in the shift registers and the cursor could be
|
||||
advanced. (Carriage returns were not written into the shift
|
||||
registers; they only advanced the cursor.)
|
||||
|
||||
The characters in the line buffer were recirculated 7 times to
|
||||
display the 8 scan lines of the characters, before being replaced by
|
||||
a new line of characters from the main shift registers.
|
||||
|
||||
Cursor blinking was performed by a Signetics 555 timer IC whose
|
||||
output was gated into the character code signals as they passed into
|
||||
the line buffer.
|
||||
|
||||
Character images were provided by a Signetics 2513 character
|
||||
generator ROM, a chip also used in computer terminals such as the
|
||||
ADM-3A. This ROM had 9 address lines and 5 data lines; it contained
|
||||
64 character images, each 5 pixels wide by 8 pixels high, with one
|
||||
line of pixels being blank for vertical separation. The video
|
||||
circuitry added the 2 pixels of horizontal separation for each
|
||||
character.
|
||||
|
||||
A special CLEAR SCREEN switch on the keyboard, directly connected to
|
||||
the video hardware, could be used to clear the video memory and
|
||||
return the cursor to the home position. This was completely
|
||||
independent of the processor.
|
||||
|
||||
A schematic of the Apple I video hardware can be found in the
|
||||
Apple-1 Operation Manual; look for the schematic titled "Terminal
|
||||
Section". Most of the functionality modeled here was determined by
|
||||
reading this schematic. Many of the chips used were standard 74xx
|
||||
TTL chips, but the shift registers used for the video memory and
|
||||
line buffer were Signetics 25xx PMOS ICs. These were already
|
||||
becoming obsolete when the Apple I was built, and detailed
|
||||
information on them is very hard to find today.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "emu.h"
|
||||
#include "includes/apple1.h"
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
Terminal code
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
TILE_GET_INFO_MEMBER(apple1_state::terminal_gettileinfo)
|
||||
{
|
||||
int ch, gfxfont, code, color;
|
||||
|
||||
ch = m_current_terminal->mem[tile_index];
|
||||
code = ch & ((1 << m_current_terminal->char_bits) - 1);
|
||||
color = ch >> m_current_terminal->char_bits;
|
||||
gfxfont = m_current_terminal->gfx;
|
||||
|
||||
if ((tile_index == m_current_terminal->cur_offset) && !m_current_terminal->cur_hidden && m_current_terminal->getcursorcode)
|
||||
code = m_current_terminal->getcursorcode(code);
|
||||
|
||||
SET_TILE_INFO_MEMBER(gfxfont, /* gfx */
|
||||
code, /* character */
|
||||
color, /* color */
|
||||
0); /* flags */
|
||||
}
|
||||
|
||||
void apple1_state::terminal_draw(screen_device &screen, bitmap_ind16 &dest, const rectangle &cliprect, terminal_t *terminal)
|
||||
{
|
||||
m_current_terminal = terminal;
|
||||
terminal->tm->draw(screen, dest, cliprect, 0, 0);
|
||||
m_current_terminal = nullptr;
|
||||
}
|
||||
|
||||
void apple1_state::verify_coords(terminal_t *terminal, int x, int y)
|
||||
{
|
||||
assert(x >= 0);
|
||||
assert(y >= 0);
|
||||
assert(x < terminal->num_cols);
|
||||
assert(y < terminal->num_rows);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_putchar(terminal_t *terminal, int x, int y, int ch)
|
||||
{
|
||||
int offs;
|
||||
|
||||
verify_coords(terminal, x, y);
|
||||
|
||||
offs = y * terminal->num_cols + x;
|
||||
if (terminal->mem[offs] != ch)
|
||||
{
|
||||
terminal->mem[offs] = ch;
|
||||
terminal->tm->mark_tile_dirty(offs);
|
||||
}
|
||||
}
|
||||
|
||||
int apple1_state::terminal_getchar(terminal_t *terminal, int x, int y)
|
||||
{
|
||||
int offs;
|
||||
|
||||
verify_coords(terminal, x, y);
|
||||
offs = y * terminal->num_cols + x;
|
||||
return terminal->mem[offs];
|
||||
}
|
||||
|
||||
void apple1_state::terminal_putblank(terminal_t *terminal, int x, int y)
|
||||
{
|
||||
terminal_putchar(terminal, x, y, terminal->blank_char);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_dirtycursor(terminal_t *terminal)
|
||||
{
|
||||
if (terminal->cur_offset >= 0)
|
||||
terminal->tm->mark_tile_dirty(terminal->cur_offset);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_setcursor(terminal_t *terminal, int x, int y)
|
||||
{
|
||||
terminal_dirtycursor(terminal);
|
||||
terminal->cur_offset = y * terminal->num_cols + x;
|
||||
terminal_dirtycursor(terminal);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_hidecursor(terminal_t *terminal)
|
||||
{
|
||||
terminal->cur_hidden = 1;
|
||||
terminal_dirtycursor(terminal);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_showcursor(terminal_t *terminal)
|
||||
{
|
||||
terminal->cur_hidden = 0;
|
||||
terminal_dirtycursor(terminal);
|
||||
}
|
||||
|
||||
void apple1_state::terminal_getcursor(terminal_t *terminal, int *x, int *y)
|
||||
{
|
||||
*x = terminal->cur_offset % terminal->num_cols;
|
||||
*y = terminal->cur_offset / terminal->num_cols;
|
||||
}
|
||||
|
||||
void apple1_state::terminal_fill(terminal_t *terminal, int val)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < terminal->num_cols * terminal->num_rows; i++)
|
||||
terminal->mem[i] = val;
|
||||
terminal->tm->mark_all_dirty();
|
||||
}
|
||||
|
||||
void apple1_state::terminal_clear(terminal_t *terminal)
|
||||
{
|
||||
terminal_fill(terminal, terminal->blank_char);
|
||||
}
|
||||
|
||||
terminal_t *apple1_state::terminal_create(
|
||||
int gfx, int blank_char, int char_bits,
|
||||
int (*getcursorcode)(int original_code),
|
||||
int num_cols, int num_rows)
|
||||
{
|
||||
terminal_t *term;
|
||||
int char_width, char_height;
|
||||
|
||||
char_width = m_gfxdecode->gfx(gfx)->width();
|
||||
char_height = m_gfxdecode->gfx(gfx)->height();
|
||||
|
||||
term = (terminal_t *) auto_alloc_array(machine(), char, sizeof(terminal_t) - sizeof(term->mem)
|
||||
+ (num_cols * num_rows * sizeof(termchar_t)));
|
||||
|
||||
term->tm = &machine().tilemap().create(m_gfxdecode, tilemap_get_info_delegate(FUNC(apple1_state::terminal_gettileinfo),this), TILEMAP_SCAN_ROWS,
|
||||
char_width, char_height, num_cols, num_rows);
|
||||
|
||||
term->gfx = gfx;
|
||||
term->blank_char = blank_char;
|
||||
term->char_bits = char_bits;
|
||||
term->num_cols = num_cols;
|
||||
term->num_rows = num_rows;
|
||||
term->getcursorcode = getcursorcode;
|
||||
term->cur_offset = -1;
|
||||
term->cur_hidden = 0;
|
||||
terminal_clear(term);
|
||||
return term;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
|
||||
|
||||
/* The cursor blinking is generated by a free-running timer with a
|
||||
0.52-second period. It is on for 2/3 of this period and off for
|
||||
1/3. */
|
||||
#define CURSOR_OFF_LENGTH (0.52/3)
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
static int apple1_getcursorcode(int original_code)
|
||||
{
|
||||
/* Cursor uses symbol 0 (an @ sign) in the character generator ROM. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
void apple1_state::video_start()
|
||||
{
|
||||
m_blink_on = 1; /* cursor is visible initially */
|
||||
m_terminal = terminal_create(
|
||||
0, /* graphics font 0 (the only one we have) */
|
||||
32, /* Blank character is symbol 32 in the ROM */
|
||||
8, /* use 8 bits for the character code */
|
||||
apple1_getcursorcode,
|
||||
40, 24); /* 40 columns, 24 rows */
|
||||
|
||||
terminal_setcursor(m_terminal, 0, 0);
|
||||
}
|
||||
|
||||
/* This function handles all writes to the video display. */
|
||||
void apple1_state::apple1_vh_dsp_w (int data)
|
||||
{
|
||||
int x, y;
|
||||
int cursor_x, cursor_y;
|
||||
|
||||
/* While CLEAR SCREEN is being held down, the hardware is forced
|
||||
to clear the video memory, so video writes have no effect. */
|
||||
if (m_vh_clrscrn_pressed)
|
||||
return;
|
||||
|
||||
/* The video display port only accepts the 7 lowest bits of the char. */
|
||||
data &= 0x7f;
|
||||
|
||||
terminal_getcursor(m_terminal, &cursor_x, &cursor_y);
|
||||
|
||||
if (data == '\r') {
|
||||
/* Carriage-return moves the cursor to the start of the next
|
||||
line. */
|
||||
cursor_x = 0;
|
||||
cursor_y++;
|
||||
}
|
||||
else if (data < ' ') {
|
||||
/* Except for carriage-return, the video hardware completely
|
||||
ignores all control characters. */
|
||||
return;
|
||||
}
|
||||
else {
|
||||
/* For visible characters, only 6 bits of the ASCII code are
|
||||
used, because the 2513 character generator ROM only
|
||||
contains 64 symbols. The low 5 bits of the ASCII code are
|
||||
used directly. Bit 6 is ignored, since it is the same for
|
||||
all the available characters in the ROM. Bit 7 is inverted
|
||||
before being used as the high bit of the 6-bit ROM symbol
|
||||
index, because the block of 32 ASCII symbols containing the
|
||||
uppercase letters comes first in the ROM. */
|
||||
|
||||
int romindx = (data & 0x1f) | (((data ^ 0x40) & 0x40) >> 1);
|
||||
|
||||
terminal_putchar(m_terminal, cursor_x, cursor_y, romindx);
|
||||
if (cursor_x < 39)
|
||||
{
|
||||
cursor_x++;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor_x = 0;
|
||||
cursor_y++;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the cursor went past the bottom line, scroll the text up one line. */
|
||||
if (cursor_y == 24)
|
||||
{
|
||||
for (y = 1; y < 24; y++)
|
||||
for (x = 0; x < 40; x++)
|
||||
terminal_putchar(m_terminal, x, y-1,
|
||||
terminal_getchar(m_terminal, x, y));
|
||||
|
||||
for (x = 0; x < 40; x++)
|
||||
terminal_putblank(m_terminal, x, 23);
|
||||
|
||||
cursor_y--;
|
||||
}
|
||||
|
||||
terminal_setcursor(m_terminal, cursor_x, cursor_y);
|
||||
}
|
||||
|
||||
/* This function handles clearing the video display on cold-boot or in
|
||||
response to a press of the CLEAR SCREEN switch. */
|
||||
void apple1_state::apple1_vh_dsp_clr ()
|
||||
{
|
||||
terminal_setcursor(m_terminal, 0, 0);
|
||||
terminal_clear(m_terminal);
|
||||
}
|
||||
|
||||
/* Calculate how long it will take for the display to assert the RDA
|
||||
signal in response to a video display write. This signal indicates
|
||||
the display has completed the write and is ready to accept another
|
||||
write. */
|
||||
attotime apple1_state::apple1_vh_dsp_time_to_ready ()
|
||||
{
|
||||
int cursor_x, cursor_y;
|
||||
int cursor_scanline;
|
||||
double scanline_period = m_screen->scan_period().as_double();
|
||||
double cursor_hfrac;
|
||||
|
||||
/* The video hardware refreshes the screen by reading the
|
||||
character codes from its circulating shift-register memory.
|
||||
Because of the way this memory works, a new character can only
|
||||
be written into the cursor location at the moment this location
|
||||
is about to be read. This happens during the first scanline of
|
||||
the cursor's character line, when the beam reaches the cursor's
|
||||
horizontal position. */
|
||||
|
||||
terminal_getcursor(m_terminal, &cursor_x, &cursor_y);
|
||||
cursor_scanline = cursor_y * apple1_charlayout.height;
|
||||
|
||||
/* Each scanline is composed of 455 pixel times. The first 175 of
|
||||
these are the horizontal blanking period; the remaining 280 are
|
||||
for the visible part of the scanline. */
|
||||
cursor_hfrac = (175 + cursor_x * apple1_charlayout.width) / 455;
|
||||
|
||||
if (m_screen->vpos() == cursor_scanline) {
|
||||
/* video_screen_get_hpos() doesn't account for the horizontal
|
||||
blanking interval; it acts as if the scanline period is
|
||||
entirely composed of visible pixel times. However, we can
|
||||
still use it to find what fraction of the current scanline
|
||||
period has elapsed. */
|
||||
double current_hfrac = m_screen->hpos() /
|
||||
m_screen->width();
|
||||
if (current_hfrac < cursor_hfrac)
|
||||
return attotime::from_double(scanline_period * (cursor_hfrac - current_hfrac));
|
||||
}
|
||||
|
||||
return attotime::from_double(
|
||||
m_screen->time_until_pos(cursor_scanline, 0).as_double() +
|
||||
scanline_period * cursor_hfrac);
|
||||
}
|
||||
|
||||
/* Blink the cursor on or off, as appropriate. */
|
||||
void apple1_state::apple1_vh_cursor_blink ()
|
||||
{
|
||||
int new_blink_on;
|
||||
|
||||
/* The cursor is on for 2/3 of its blink period and off for 1/3.
|
||||
This is most easily handled by dividing the total elapsed time
|
||||
by the length of the off-portion of the cycle, giving us the
|
||||
number of one-third-cycles elapsed, then checking the result
|
||||
modulo 3. */
|
||||
|
||||
if (((int) (machine().time().as_double() / CURSOR_OFF_LENGTH)) % 3 < 2)
|
||||
new_blink_on = 1;
|
||||
else
|
||||
new_blink_on = 0;
|
||||
|
||||
if (new_blink_on != m_blink_on) { /* have we changed state? */
|
||||
if (new_blink_on)
|
||||
terminal_showcursor(m_terminal);
|
||||
else
|
||||
terminal_hidecursor(m_terminal);
|
||||
m_blink_on = new_blink_on;
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 apple1_state::screen_update_apple1(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
|
||||
{
|
||||
apple1_vh_cursor_blink();
|
||||
terminal_draw(screen, bitmap, cliprect, m_terminal);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user