mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-12-09 20:50:26 +00:00
general VM structure and a test OpenCV target
This commit is contained in:
parent
680e57013f
commit
218b3b77a0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
apple2e.rom
|
||||
disk.rom
|
||||
*~
|
||||
*.o
|
24
Makefile
24
Makefile
@ -1,16 +1,30 @@
|
||||
LDFLAGS=-L/usr/local/lib
|
||||
CXXFLAGS=-Wall -I .. -I . -O3
|
||||
LDFLAGS=-L/usr/local/lib -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d
|
||||
|
||||
CXXFLAGS=-Wall -I .. -I . -I apple -O3
|
||||
|
||||
TSRC=cpu.cpp util/testharness.cpp
|
||||
|
||||
all:
|
||||
@echo There is no 'all' target. Yet.
|
||||
OPENCVOBJS=cpu.o opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o apple/slot.o RingBuffer.o globals.o opencv/aiie.o
|
||||
|
||||
ROMS=apple/applemmu-rom.h apple/diskii-rom.h
|
||||
|
||||
all: opencv
|
||||
|
||||
opencv: roms $(OPENCVOBJS)
|
||||
g++ $(LDFLAGS) -o aiie-opencv $(OPENCVOBJS)
|
||||
|
||||
clean:
|
||||
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended
|
||||
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h
|
||||
|
||||
test: $(TSRC)
|
||||
g++ $(CXXFLAGS) -DBASICTEST $(TSRC) -o testharness.basic
|
||||
g++ $(CXXFLAGS) -DVERBOSETEST $(TSRC) -o testharness.verbose
|
||||
g++ $(CXXFLAGS) -DEXTENDEDTEST $(TSRC) -o testharness.extended
|
||||
|
||||
roms: apple2e.rom disk.rom
|
||||
./util/genrom.pl apple2e.rom disk.rom
|
||||
|
||||
apple/applemmu-rom.h: roms
|
||||
|
||||
apple/diskii-rom.h: roms
|
||||
|
||||
|
48
README.md
48
README.md
@ -19,6 +19,54 @@ Build log:
|
||||
|
||||
https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator
|
||||
|
||||
Building (on a Mac)
|
||||
===================
|
||||
|
||||
While this isn't the purpose of the emulator, it is functional, and is
|
||||
my first test target for most of the work. With MacOS 10.11.6 and
|
||||
Homebrew, you can build and run it like this:
|
||||
|
||||
$ make opencv
|
||||
$ ./aiie-opencv /path/to/disk.dsk
|
||||
|
||||
As the name implies, this requires that OpenCV is installed and in
|
||||
/usr/local/lib. I've done that with Homebrew like this
|
||||
|
||||
$ brew install opencv
|
||||
|
||||
"Why OpenCV?" you might ask. Well, it's just because I had code from
|
||||
another project lying around that directly manipulated OpenCV bitmap
|
||||
data. It's functional, and the Mac build is only about functional
|
||||
testing (for me).
|
||||
|
||||
VM
|
||||
==
|
||||
|
||||
The virtual machine architecture is broken in half - the virtual and
|
||||
physical pieces. There's the root VM object (vm.h), which ties
|
||||
together the MMU, virtual keyboard, and virtual display.
|
||||
|
||||
Then there are the physical interfaces, which aren't as well
|
||||
organized. They exist as globals in globals.cpp:
|
||||
|
||||
FileManager *g_filemanager = NULL;
|
||||
PhysicalDisplay *g_display = NULL;
|
||||
PhysicalKeyboard *g_keyboard = NULL;
|
||||
PhysicalSpeaker *g_speaker = NULL;
|
||||
PhysicalPaddles *g_paddles = NULL;
|
||||
|
||||
There are the two globals that point to the VM and the virtual CPU:
|
||||
|
||||
Cpu *g_cpu = NULL;
|
||||
VM *g_vm = NULL;
|
||||
|
||||
And there are two global configuration values that probably belong in
|
||||
some sort of Prefs class:
|
||||
|
||||
int16_t g_volume;
|
||||
uint8_t g_displayType;
|
||||
|
||||
|
||||
CPU
|
||||
===
|
||||
|
||||
|
113
RingBuffer.cpp
Normal file
113
RingBuffer.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include "RingBuffer.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
RingBuffer::RingBuffer(int16_t length)
|
||||
{
|
||||
this->buffer = (uint8_t *)malloc(length);
|
||||
this->max = length;
|
||||
this->fill = 0;
|
||||
this->ptr = 0;
|
||||
this->cursor = 0;
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer()
|
||||
{
|
||||
free (this->buffer);
|
||||
}
|
||||
|
||||
void RingBuffer::clear()
|
||||
{
|
||||
this->fill = 0;
|
||||
}
|
||||
|
||||
bool RingBuffer::isFull()
|
||||
{
|
||||
return (this->max == this->fill);
|
||||
}
|
||||
|
||||
bool RingBuffer::hasData()
|
||||
{
|
||||
return (this->fill != 0);
|
||||
}
|
||||
|
||||
bool RingBuffer::addByte(uint8_t b)
|
||||
{
|
||||
if (this->max == this->fill)
|
||||
return false;
|
||||
|
||||
int idx = (this->ptr + this->fill) % this->max;
|
||||
this->buffer[idx] = b;
|
||||
this->fill++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RingBuffer::replaceByte(uint8_t b)
|
||||
{
|
||||
if (cursor < fill) {
|
||||
buffer[cursor] = b;
|
||||
cursor++;
|
||||
if (cursor >= fill) {
|
||||
cursor = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool RingBuffer::addBytes(uint8_t *b, int count)
|
||||
{
|
||||
for (int i=0; i<count; i++) {
|
||||
if (!addByte(b[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t RingBuffer::consumeByte()
|
||||
{
|
||||
if (this->fill == 0)
|
||||
return 0;
|
||||
|
||||
uint8_t ret = this->buffer[this->ptr];
|
||||
this->fill--;
|
||||
this->ptr++;
|
||||
this->ptr %= this->max;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t RingBuffer::peek(int16_t idx)
|
||||
{
|
||||
uint16_t p = (this->ptr + idx) % this->max;
|
||||
return this->buffer[p];
|
||||
}
|
||||
|
||||
int16_t RingBuffer::count()
|
||||
{
|
||||
return this->fill;
|
||||
}
|
||||
|
||||
uint16_t RingBuffer::getPeekCursor()
|
||||
{
|
||||
return this->cursor;
|
||||
}
|
||||
|
||||
void RingBuffer::setPeekCursor(int16_t idx)
|
||||
{
|
||||
this->cursor = idx;
|
||||
}
|
||||
|
||||
void RingBuffer::resetPeekCursor()
|
||||
{
|
||||
this->cursor = 0;
|
||||
}
|
||||
|
||||
uint8_t RingBuffer::peekNext()
|
||||
{
|
||||
uint8_t ret = peek(cursor);
|
||||
cursor++;
|
||||
if (cursor >= fill) {
|
||||
cursor = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
34
RingBuffer.h
Normal file
34
RingBuffer.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef __RINGBUFFER_H
|
||||
#define __RINGBUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class RingBuffer {
|
||||
public:
|
||||
RingBuffer(int16_t length);
|
||||
~RingBuffer();
|
||||
|
||||
void clear();
|
||||
|
||||
bool isFull();
|
||||
bool hasData();
|
||||
bool addByte(uint8_t b);
|
||||
bool addBytes(uint8_t *b, int count);
|
||||
bool replaceByte(uint8_t b);
|
||||
uint8_t consumeByte();
|
||||
uint8_t peek(int16_t idx);
|
||||
uint16_t getPeekCursor();
|
||||
void setPeekCursor(int16_t idx);
|
||||
void resetPeekCursor();
|
||||
uint8_t peekNext();
|
||||
int16_t count();
|
||||
|
||||
private:
|
||||
uint8_t *buffer;
|
||||
int16_t max;
|
||||
int16_t ptr;
|
||||
int16_t fill;
|
||||
int16_t cursor;
|
||||
};
|
||||
|
||||
#endif
|
610
apple/appledisplay.cpp
Normal file
610
apple/appledisplay.cpp
Normal file
@ -0,0 +1,610 @@
|
||||
#include <ctype.h> // isgraph
|
||||
#include <stdlib.h> // calloc
|
||||
#include <string.h> // strlen
|
||||
#include "appledisplay.h"
|
||||
#include "applemmu.h" // for switch constants
|
||||
|
||||
#include "font.h"
|
||||
|
||||
/* Fourpossible Hi-Res color-drawing modes..
|
||||
MONOCHROME: show all the pixels, but only in green;
|
||||
BLACKANDWHITE: monochrome, but use B&W instead of B&G;
|
||||
NTSCLIKE: reduce the resolution to 140 pixels wide, similar to how an NTSC monitor would blend it
|
||||
PERFECTCOLOR: as the Apple RGB monitor shows it, which means you can't have a solid color field
|
||||
*/
|
||||
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
AppleDisplay::AppleDisplay(uint8_t *vb) : VMDisplay(vb)
|
||||
{
|
||||
this->switches = NULL;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
AppleDisplay::~AppleDisplay()
|
||||
{
|
||||
}
|
||||
|
||||
bool AppleDisplay::deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col)
|
||||
{
|
||||
if (address >= 0x800 && address < 0xC00) {
|
||||
address -= 0x400;
|
||||
}
|
||||
|
||||
uint8_t block = (address >> 7) - 0x08;
|
||||
uint8_t blockOffset = (address & 0x00FF) - ((block & 0x01) ? 0x80 : 0x00);
|
||||
if (blockOffset < 0x28) {
|
||||
*row = block;
|
||||
*col = blockOffset;
|
||||
} else if (blockOffset < 0x50) {
|
||||
*row = block + 8;
|
||||
*col = blockOffset - 0x28;
|
||||
} else {
|
||||
*row = block + 16;
|
||||
*col = blockOffset - 0x50;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// calculate x/y pixel offsets from a memory address.
|
||||
// Note that this is the first of 7 pixels that will be affected by this write;
|
||||
// we'll need to update all 7 starting at this x.
|
||||
bool AppleDisplay::deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint16_t *col)
|
||||
{
|
||||
// each row is 40 bytes, for 7 pixels each, totalling 128
|
||||
// pixels wide.
|
||||
// They are grouped in to 3 "runs" of 40-byte blocks, where
|
||||
// each group is 64 lines after the one before.
|
||||
|
||||
// Then repeat at +400, +800, +c00, +1000, +1400, +1800, +1c00 for
|
||||
// the other 7 pixels tall.
|
||||
|
||||
// Repeat the whole shebang at +0x80, +0x100, +0x180, ... to +280
|
||||
// for each 8-pixel tall group.
|
||||
|
||||
// There are 8 bytes at the end of each run that we ignore. Skip them.
|
||||
if ((address & 0x07f) >= 0x78 &&
|
||||
(address & 0x7f) <= 0x7f) {
|
||||
*row = 255;
|
||||
*col = 65535;
|
||||
return false;
|
||||
}
|
||||
|
||||
*row = ((address & 0x380) >> 4) +
|
||||
((address & 0x1c00)>>10) +
|
||||
64 * ((address & 0x7f) / 40);
|
||||
|
||||
*col = ((address & 0x7f) % 40) * 7;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void AppleDisplay::writeLores(uint16_t address, uint8_t v)
|
||||
{
|
||||
if (address >= 0x800 && !((*switches) & S_80COL)) {
|
||||
if (!((*switches) & S_PAGE2)) {
|
||||
// writing to page2 text/lores, but that's not displayed right now, so nothing to do
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(address, &row, &col);
|
||||
|
||||
if (col <= 39) {
|
||||
if ((*switches) & S_TEXT ||
|
||||
(((*switches) & S_MIXED) && row >= 20)) {
|
||||
if ((*switches) & S_80COL) {
|
||||
Draw80CharacterAt(v, col, row, ((*switches) & S_PAGE2) ? 0 : 1);
|
||||
} else {
|
||||
DrawCharacterAt(v, col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!((*switches) & S_TEXT) &&
|
||||
!((*switches) & S_HIRES)) {
|
||||
if (col <= 39) {
|
||||
if (row < 20 ||
|
||||
(! ((*switches) & S_MIXED))) {
|
||||
// low-res graphics mode. Each character has two 4-bit
|
||||
// values in it: first half is the "top" pixel, and second "bottom".
|
||||
if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) {
|
||||
Draw80LoresPixelAt(v, col, row, ((*switches) & S_PAGE2) ? 0 : 1);
|
||||
} else {
|
||||
DrawLoresPixelAt(v, col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void AppleDisplay::writeHires(uint16_t address, uint8_t v)
|
||||
{
|
||||
if ((*switches) & S_HIRES) {
|
||||
if ((*switches) & S_DHIRES) {
|
||||
// Double hires: make sure we're drawing to the page that's visible.
|
||||
// If S_80STORE is on, then it's $2000 (and S_PAGE2 controls main/aux bank r/w).
|
||||
// If S_80STORE is off, then it's $4000 (and RAMRD/RAMWT are used).
|
||||
|
||||
if (((*switches) & S_80STORE) && address >= 0x4000 && address <= 0x5FFF)
|
||||
return;
|
||||
if ( !((*switches) & S_80STORE) && address >= 0x2000 && address <= 0x3FFF)
|
||||
return;
|
||||
|
||||
Draw14DoubleHiresPixelsAt(address);
|
||||
dirty = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a write to single hires but the 80store byte is on, then what do we do?
|
||||
if ((*switches) & S_80STORE) {
|
||||
// FIXME
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're writing to the page that's visible before we draw
|
||||
if (address >= 0x2000 && address <= 0x3FFF && ((*switches) & S_PAGE2))
|
||||
return;
|
||||
if (address >= 0x4000 && address <= 0x5FFF && !((*switches) & S_PAGE2))
|
||||
return;
|
||||
|
||||
Draw14HiresPixelsAt(address & 0xFFFE);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// return a pointer to the right glyph, and set *invert appropriately
|
||||
const unsigned char *AppleDisplay::xlateChar(uint8_t c, bool *invert)
|
||||
{
|
||||
if (c <= 0x3F) {
|
||||
// 0-3f: inverted @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?
|
||||
// (same w/o mousetext, actually)
|
||||
*invert = true;
|
||||
return &ucase_glyphs[c * 8];
|
||||
} else if (c <= 0x5F) {
|
||||
// 40-5f: normal mousetext
|
||||
// (these are flashing @ABCDEFG..[\]^_ when not in mousetext mode)
|
||||
if ((*switches) & S_ALTCH) {
|
||||
*invert = false;
|
||||
return &mousetext_glyphs[(c - 0x40) * 8];
|
||||
} else {
|
||||
*invert = true;
|
||||
return &ucase_glyphs[(c - 0x40) * 8];
|
||||
}
|
||||
} else if (c <= 0x7F) {
|
||||
// 60-7f: inverted `abcdefghijklmnopqrstuvwxyz{|}~*
|
||||
// (these are flashing (sp)!"#$%...<=>? when not in mousetext)
|
||||
if ((*switches) & S_ALTCH) {
|
||||
*invert = true;
|
||||
return &lcase_glyphs[(c - 0x60) * 8];
|
||||
} else {
|
||||
*invert = true;
|
||||
return &ucase_glyphs[((c-0x60) + 0x20) * 8];
|
||||
}
|
||||
} else if (c <= 0xBF) {
|
||||
// 80-BF: normal @ABCD... <=>? in both character sets
|
||||
*invert = false;
|
||||
return &ucase_glyphs[(c - 0x80) * 8];
|
||||
} else if (c <= 0xDF) {
|
||||
// C0-DF: normal @ABCD...Z[\]^_ in both character sets
|
||||
*invert = false;
|
||||
return &ucase_glyphs[(c - 0xC0) * 8];
|
||||
} else {
|
||||
// E0- : normal `abcdef... in both character sets
|
||||
*invert = false;
|
||||
return &lcase_glyphs[(c - 0xE0) * 8];
|
||||
}
|
||||
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
void AppleDisplay::Draw80CharacterAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset)
|
||||
{
|
||||
bool invert;
|
||||
const uint8_t *cptr = xlateChar(c, &invert);
|
||||
|
||||
// Even characters are in bank 0 ram. Odd characters are in bank 1 ram.
|
||||
// Technically, this would need 560 columns to work correctly - and I
|
||||
// don't have that, so it's going to be a bit wonky.
|
||||
//
|
||||
// First pass: draw two pixels on top of each other, clearing only
|
||||
// if both are black. This would be blocky but probably passable if
|
||||
// it weren't for the fact that characters are 7 pixels wide, so we
|
||||
// wind up sharing a half-pixel between two characters. So we'll
|
||||
// render these as 3-pixel-wide characters and make sure they always
|
||||
// even-align the drawing on the left side so we don't overwrite
|
||||
// every other one on the left or right side.
|
||||
|
||||
uint16_t fgColor = g_displayType == m_monochrome?c_green:c_white;
|
||||
|
||||
for (uint8_t y2 = 0; y2<8; y2++) {
|
||||
uint8_t d = *(cptr + y2);
|
||||
for (uint8_t x2 = 0; x2 <= 7; x2+=2) {
|
||||
uint16_t basex = ((x * 2 + offset) * 7) & 0xFFFE; // even aligned
|
||||
bool pixelOn = ( (d & (1<<x2)) | (d & (1<<(x2+1))) );
|
||||
if (pixelOn) {
|
||||
uint8_t val = (invert ? c_black : fgColor);
|
||||
drawPixel(val, (basex+x2)/2, y*8+y2);
|
||||
} else {
|
||||
uint8_t val = (invert ? fgColor : c_black);
|
||||
drawPixel(val, (basex+x2)/2, y*8+y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppleDisplay::DrawCharacterAt(uint8_t c, uint8_t x, uint8_t y)
|
||||
{
|
||||
bool invert;
|
||||
const uint8_t *cptr = xlateChar(c, &invert);
|
||||
|
||||
uint16_t fgColor = g_displayType == m_monochrome?c_green:c_white;
|
||||
|
||||
for (uint8_t y2 = 0; y2<8; y2++) {
|
||||
uint8_t d = *(cptr + y2);
|
||||
for (uint8_t x2 = 0; x2 < 7; x2++) {
|
||||
if (d & (1 << x2)) {
|
||||
uint8_t val = (invert ? c_black : fgColor);
|
||||
drawPixel(val, x*7+x2, y*8+y2);
|
||||
} else {
|
||||
uint8_t val = (invert ? fgColor : c_black);
|
||||
drawPixel(val, x*7+x2, y*8+y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr)
|
||||
{
|
||||
// We will consult 4 bytes (2 in main, 2 in aux) for any single-byte
|
||||
// write. Align to the first byte in that series based on what
|
||||
// address we were given...
|
||||
addr &= ~0x01;
|
||||
|
||||
// Figure out the position of that address on the "normal" hires screen
|
||||
uint8_t row;
|
||||
uint16_t col;
|
||||
deinterlaceHiresAddress(addr, &row, &col);
|
||||
if (row >= 160 &&
|
||||
((*switches) & S_MIXED)) {
|
||||
// displaying text, so don't have to draw this line
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure it's a valid graphics area, not a dead hole
|
||||
if (col <= 280 && row <= 192) {
|
||||
// Grab the 4 bytes we care about
|
||||
uint8_t b1A = mmu->readDirect(addr, 0);
|
||||
uint8_t b2A = mmu->readDirect(addr+1, 0);
|
||||
uint8_t b1B = mmu->readDirect(addr, 1);
|
||||
uint8_t b2B = mmu->readDirect(addr+1, 1);
|
||||
|
||||
// Construct the 28 bit wide bitstream, like we do for the simpler 14 Hires pixel draw
|
||||
uint32_t bitTrain = b2A & 0x7F;
|
||||
bitTrain <<= 7;
|
||||
bitTrain |= (b2B & 0x7F);
|
||||
bitTrain <<= 7;
|
||||
bitTrain |= (b1A & 0x7F);
|
||||
bitTrain <<= 7;
|
||||
bitTrain |= (b1B & 0x7F);
|
||||
|
||||
// Now we pop groups of 4 bits off the bottom and draw our
|
||||
// NTSC-style-only color. The display for this project only has
|
||||
// 320 columns, so it's silly to try to do 560 columns of
|
||||
// monochrome; and likewise, we can't do "perfect" representation
|
||||
// of shifted color pixels. So NTSC it is, and we'll draw two screen
|
||||
// pixels for every color.
|
||||
|
||||
for (int8_t xoff = 0; xoff < 14; xoff += 2) {
|
||||
drawPixel(bitTrain & 0x0F, col+xoff, row);
|
||||
drawPixel(bitTrain & 0x0F, col+xoff+1, row);
|
||||
|
||||
bitTrain >>= 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Whenever we change a byte, it's possible that it will have an affect on the byte next to it -
|
||||
// because between two bytes there is a shared bit.
|
||||
// FIXME: what happens when the high bit of the left doesn't match the right? Which high bit does
|
||||
// the overlap bit get?
|
||||
void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr)
|
||||
{
|
||||
uint8_t row;
|
||||
uint16_t col;
|
||||
|
||||
deinterlaceHiresAddress(addr, &row, &col);
|
||||
if (row >= 160 &&
|
||||
((*switches) & S_MIXED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (col <= 280 && row <= 192) {
|
||||
/*
|
||||
The high bit only selects the color palette.
|
||||
|
||||
There are only really two bits here, and they can be one of six colors.
|
||||
|
||||
color highbit even odd restriction
|
||||
black x 0x80,0x00
|
||||
green 0 0x2A 0x55 odd only
|
||||
violet 0 0x55 0x2A even only
|
||||
white x 0xFF,0x7F
|
||||
orange 1 0xAA 0xD5 odd only
|
||||
blue 1 0xD5 0xAA even only
|
||||
|
||||
in other words, we can look at the pixels in pairs and we get
|
||||
|
||||
00 black
|
||||
01 green/orange
|
||||
10 violet/blue
|
||||
11 white
|
||||
|
||||
When the horizontal byte number is even, we ignore the last
|
||||
bit. When the horizontal byte number is odd, we use that dropped
|
||||
bit.
|
||||
|
||||
So each even byte turns in to 3 bits; and each odd byte turns in
|
||||
to 4. Our effective output is therefore 140 pixels (half the
|
||||
actual B&W resolution).
|
||||
|
||||
(Note that I swap 0x02 and 0x01 below, because we're running the
|
||||
bit train backward, so the bits are reversed.)
|
||||
*/
|
||||
|
||||
uint8_t b1 = mmu->read(addr);
|
||||
uint8_t b2 = mmu->read(addr+1);
|
||||
|
||||
// Used for color modes...
|
||||
bool highBitOne = (b1 & 0x80);
|
||||
bool highBitTwo = (b2 & 0x80);
|
||||
|
||||
uint16_t bitTrain = (b1 & 0x7F) | ((b2 & 0x7F) << 7);
|
||||
|
||||
for (int8_t xoff = 0; xoff < 14; xoff += 2) {
|
||||
|
||||
if (g_displayType == m_monochrome) {
|
||||
draw2Pixels(((bitTrain & 0x01 ? c_green : c_black) << 4) |
|
||||
(bitTrain & 0x02 ? c_green : c_black),
|
||||
col+xoff, row);
|
||||
} else if (g_displayType == m_blackAndWhite) {
|
||||
draw2Pixels(((bitTrain & 0x01 ? c_white : c_black) << 4) |
|
||||
(bitTrain & 0x02 ? c_white : c_black),
|
||||
col+xoff, row);
|
||||
} else if (g_displayType == m_ntsclike) {
|
||||
// Use the NTSC-like color mode, where we're only 140 pixels wide.
|
||||
|
||||
bool highBitSet = (xoff >= 7 ? highBitTwo : highBitOne);
|
||||
uint8_t color;
|
||||
switch (bitTrain & 0x03) {
|
||||
case 0x00:
|
||||
color = c_black;
|
||||
break;
|
||||
case 0x02:
|
||||
color = (highBitSet ? c_orange : c_green);
|
||||
break;
|
||||
case 0x01:
|
||||
color = (highBitSet ? c_medblue : c_purple);
|
||||
break;
|
||||
case 0x03:
|
||||
color = c_white;
|
||||
break;
|
||||
}
|
||||
|
||||
draw2Pixels( (color << 4) | color, col+xoff, row );
|
||||
} else {
|
||||
// Use the "perfect" color mode, like the Apple RGB monitor showed.
|
||||
bool highBitSet = (xoff >= 7 ? highBitTwo : highBitOne);
|
||||
uint8_t color;
|
||||
switch (bitTrain & 0x03) {
|
||||
case 0x00:
|
||||
color = c_black;
|
||||
break;
|
||||
case 0x02:
|
||||
color = (highBitSet ? c_orange : c_green);
|
||||
break;
|
||||
case 0x01:
|
||||
color = (highBitSet ? c_medblue : c_purple);
|
||||
break;
|
||||
case 0x03:
|
||||
color = c_white;
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t twoColors;
|
||||
|
||||
if (color == c_black || color == c_white || bitTrain & 0x01) {
|
||||
twoColors = color;
|
||||
} else {
|
||||
twoColors = c_black;
|
||||
}
|
||||
twoColors <<= 4;
|
||||
|
||||
if (color == c_black || color == c_white || bitTrain & 0x02) {
|
||||
twoColors |= color;
|
||||
} else {
|
||||
twoColors |= c_black;
|
||||
}
|
||||
draw2Pixels(twoColors, col+xoff, row);
|
||||
}
|
||||
bitTrain >>= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppleDisplay::modeChange()
|
||||
{
|
||||
if ((*switches) & S_TEXT) {
|
||||
if ((*switches) & S_80COL) {
|
||||
for (uint16_t addr = 0x400; addr <= 0x400 + 0x3FF; addr++) {
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(addr, &row, &col);
|
||||
if (col <= 39 && row <= 23) {
|
||||
Draw80CharacterAt(mmu->readDirect(addr, 0), col, row, 1);
|
||||
Draw80CharacterAt(mmu->readDirect(addr, 1), col, row, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400;
|
||||
for (uint16_t addr = start; addr <= start + 0x3FF; addr++) {
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(addr, &row, &col);
|
||||
if (col <= 39 && row <= 23) {
|
||||
DrawCharacterAt(mmu->read(addr), col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Not text mode - what mode are we in?
|
||||
if ((*switches) & S_HIRES) {
|
||||
// Hires
|
||||
// FIXME: make this draw a row efficiently
|
||||
// FIXME: can make more efficient by checking S_MIXED for lower bound
|
||||
uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000;
|
||||
if ((*switches) & S_80STORE) {
|
||||
// Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2
|
||||
start = 0x2000;
|
||||
}
|
||||
|
||||
for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) {
|
||||
if ((*switches) & S_DHIRES) {
|
||||
Draw14DoubleHiresPixelsAt(addr);
|
||||
} else {
|
||||
Draw14HiresPixelsAt(addr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Lores
|
||||
// FIXME: can make more efficient by checking S_MIXED for lower bound
|
||||
|
||||
if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) {
|
||||
for (uint16_t addr = 0x400; addr <= 0x400 + 0x3ff; addr++) {
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(addr, &row, &col);
|
||||
if (col <= 39 && row <= 23) {
|
||||
Draw80LoresPixelAt(mmu->readDirect(addr, 0), col, row, 1);
|
||||
Draw80LoresPixelAt(mmu->readDirect(addr, 1), col, row, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400;
|
||||
for (uint16_t addr = start; addr <= start + 0x3FF; addr++) {
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(addr, &row, &col);
|
||||
if (col <= 39 && row <= 23) {
|
||||
DrawLoresPixelAt(mmu->read(addr), col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((*switches) & S_MIXED) {
|
||||
// Text at the bottom of the screen...
|
||||
// FIXME: deal with 80-char properly
|
||||
uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400;
|
||||
for (uint16_t addr = start; addr <= start + 0x3FF; addr++) {
|
||||
uint8_t row, col;
|
||||
deinterlaceAddress(addr, &row, &col);
|
||||
if (col <= 39 && row >= 20 && row <= 23) {
|
||||
if ((*switches) & S_80COL) {
|
||||
Draw80CharacterAt(mmu->read(addr), col, row, ((*switches) & S_PAGE2) ? 0 : 1);
|
||||
} else {
|
||||
DrawCharacterAt(mmu->read(addr), col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void AppleDisplay::Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset)
|
||||
{
|
||||
// Just like 80-column text, this has a minor problem; we're talimg
|
||||
// a 7-pixel-wide space and dividing it in half. Here I'm drawing
|
||||
// every other column 1 pixel narrower (the ">= offset" in the for
|
||||
// loop condition).
|
||||
//
|
||||
// Make those ">= 0" and change the "*7" to "*8" and you've got
|
||||
// 320-pixel-wide slightly distorted but cleaner double-lores...
|
||||
|
||||
if (!offset) {
|
||||
// The colors in every other column are swizzled. Un-swizzle.
|
||||
c = ((c & 0x77) << 1) | ((c & 0x88) >> 3);
|
||||
}
|
||||
uint8_t pixel = c & 0x0F;
|
||||
for (uint8_t y2 = 0; y2<4; y2++) {
|
||||
for (int8_t x2 = 3; x2>=offset; x2--) {
|
||||
drawPixel(pixel, x*7+x2+offset*3, y*8+y2);
|
||||
}
|
||||
}
|
||||
|
||||
pixel = (c >> 4);
|
||||
for (uint8_t y2 = 4; y2<8; y2++) {
|
||||
for (int8_t x2 = 3; x2>=offset; x2--) {
|
||||
drawPixel(pixel, x*7+x2+offset*3, y*8+y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// col, row are still character-like positions, as in DrawCharacterAt.
|
||||
// Each holds two pixels (one on top of the other).
|
||||
void AppleDisplay::DrawLoresPixelAt(uint8_t c, uint8_t x, uint8_t y)
|
||||
{
|
||||
uint8_t pixel = c & 0x0F;
|
||||
for (uint8_t y2 = 0; y2<4; y2++) {
|
||||
for (int8_t x2 = 6; x2>=0; x2--) {
|
||||
drawPixel(pixel, x*7+x2, y*8+y2);
|
||||
}
|
||||
}
|
||||
|
||||
pixel = (c >> 4);
|
||||
for (uint8_t y2 = 4; y2<8; y2++) {
|
||||
for (int8_t x2 = 6; x2>=0; x2--) {
|
||||
drawPixel(pixel, x*7+x2, y*8+y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppleDisplay::draw2Pixels(uint16_t two4bitColors, uint16_t x, uint8_t y)
|
||||
{
|
||||
videoBuffer[(y * 320 + x) /2] = two4bitColors;
|
||||
}
|
||||
|
||||
void AppleDisplay::drawPixel(uint8_t color4bit, uint16_t x, uint8_t y)
|
||||
{
|
||||
uint16_t idx = (y * 320 + x) / 2;
|
||||
if (x & 1) {
|
||||
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | color4bit;
|
||||
} else {
|
||||
videoBuffer[idx] = (videoBuffer[idx] & 0x0F) | (color4bit << 4);
|
||||
}
|
||||
}
|
||||
|
||||
void AppleDisplay::setSwitches(uint16_t *switches)
|
||||
{
|
||||
dirty = true;
|
||||
this->switches = switches;
|
||||
}
|
||||
|
||||
bool AppleDisplay::needsRedraw()
|
||||
{
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void AppleDisplay::didRedraw()
|
||||
{
|
||||
// FIXME: there's a drawing bug somewhere that's not getting the dirty flag set right.
|
||||
// dirty = false;
|
||||
}
|
||||
|
75
apple/appledisplay.h
Normal file
75
apple/appledisplay.h
Normal file
@ -0,0 +1,75 @@
|
||||
#ifndef __APPLEDISPLAY_H
|
||||
#define __APPLEDISPLAY_H
|
||||
|
||||
#ifdef TEENSYDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "vmdisplay.h"
|
||||
|
||||
enum {
|
||||
c_black = 0,
|
||||
c_magenta = 1,
|
||||
c_darkblue = 2,
|
||||
c_purple = 3,
|
||||
c_darkgreen = 4,
|
||||
c_darkgrey = 5,
|
||||
c_medblue = 6,
|
||||
c_lightblue = 7,
|
||||
c_brown = 8,
|
||||
c_orange = 9,
|
||||
c_lightgray = 10,
|
||||
c_pink = 11,
|
||||
c_green = 12,
|
||||
c_yellow = 13,
|
||||
c_aqua = 14,
|
||||
c_white = 15
|
||||
};
|
||||
|
||||
enum {
|
||||
m_blackAndWhite = 0,
|
||||
m_monochrome = 1,
|
||||
m_ntsclike = 2,
|
||||
m_perfectcolor = 3
|
||||
};
|
||||
|
||||
class AppleMMU;
|
||||
|
||||
class AppleDisplay : public VMDisplay{
|
||||
public:
|
||||
AppleDisplay(uint8_t *vb);
|
||||
virtual ~AppleDisplay();
|
||||
virtual bool needsRedraw();
|
||||
virtual void didRedraw();
|
||||
|
||||
void modeChange(); // FIXME: rename 'redraw'?
|
||||
void setSwitches(uint16_t *switches);
|
||||
|
||||
void writeLores(uint16_t address, uint8_t v);
|
||||
void writeHires(uint16_t address, uint8_t v);
|
||||
|
||||
private:
|
||||
|
||||
bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col);
|
||||
bool deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint16_t *col);
|
||||
|
||||
void Draw80CharacterAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
|
||||
void DrawCharacterAt(uint8_t c, uint8_t x, uint8_t y);
|
||||
void Draw14DoubleHiresPixelsAt(uint16_t address);
|
||||
void Draw14HiresPixelsAt(uint16_t addr);
|
||||
void Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
|
||||
void DrawLoresPixelAt(uint8_t c, uint8_t x, uint8_t y);
|
||||
void draw2Pixels(uint16_t two4bitColors, uint16_t x, uint8_t y);
|
||||
void drawPixel(uint8_t cidx, uint16_t x, uint8_t y);
|
||||
|
||||
const unsigned char *xlateChar(uint8_t c, bool *invert);
|
||||
|
||||
private:
|
||||
volatile bool dirty;
|
||||
|
||||
uint16_t *switches; // pointer to the MMU's switches
|
||||
};
|
||||
|
||||
#endif
|
196
apple/applekeyboard.cpp
Normal file
196
apple/applekeyboard.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
#include "applekeyboard.h"
|
||||
#include "physicalkeyboard.h" // for LA/RA constants
|
||||
|
||||
#include "applemmu.h"
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
// How many CPU cycles before we begin repeating a key?
|
||||
#define STARTREPEAT 700000
|
||||
// How many CPU cycles between repeats of a key?
|
||||
#define REPEATAGAIN 66667
|
||||
|
||||
AppleKeyboard::AppleKeyboard(AppleMMU *m)
|
||||
{
|
||||
this->mmu = m;
|
||||
|
||||
for (uint16_t i=0; i<sizeof(keysDown); i++) {
|
||||
keysDown[i] = false;
|
||||
}
|
||||
anyKeyIsDown = false;
|
||||
startRepeatTimer = 0;
|
||||
repeatTimer = 0;
|
||||
|
||||
capsLockEnabled = true;
|
||||
}
|
||||
|
||||
AppleKeyboard::~AppleKeyboard()
|
||||
{
|
||||
}
|
||||
|
||||
bool AppleKeyboard::isVirtualKey(uint8_t kc)
|
||||
{
|
||||
if (kc >= 0x81 && kc <= 0x86) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// apply the apple keymap.
|
||||
// FIXME: easier with an array, but is that better?
|
||||
uint8_t AppleKeyboard::translateKeyWithModifiers(uint8_t k)
|
||||
{
|
||||
// tolower, so we know what we're working with...
|
||||
if (k >= 'A' && k <= 'Z') {
|
||||
k = k - 'A' + 'a';
|
||||
}
|
||||
|
||||
if (keysDown[_CTRL]) {
|
||||
if (k >= 'a' && k <= 'z') {
|
||||
return k - 'a' + 1;
|
||||
}
|
||||
// FIXME: any other control keys honored on the //e keyboard?
|
||||
}
|
||||
|
||||
if (capsLockEnabled && k >= 'a' && k <= 'z') {
|
||||
return k - 'a' + 'A';
|
||||
}
|
||||
|
||||
if (keysDown[LSHFT] || keysDown[RSHFT]) {
|
||||
if (k >= 'a' && k <= 'z') {
|
||||
return k - 'a' + 'A';
|
||||
}
|
||||
switch (k) {
|
||||
case '1':
|
||||
return '!';
|
||||
case '2':
|
||||
return '@';
|
||||
case '3':
|
||||
return '#';
|
||||
case '4':
|
||||
return '$';
|
||||
case '5':
|
||||
return '%';
|
||||
case '6':
|
||||
return '^';
|
||||
case '7':
|
||||
return '&';
|
||||
case '8':
|
||||
return '*';
|
||||
case '9':
|
||||
return '(';
|
||||
case '0':
|
||||
return ')';
|
||||
case '-':
|
||||
return '_';
|
||||
case '=':
|
||||
return '+';
|
||||
case '[':
|
||||
return '{';
|
||||
case ']':
|
||||
return '}';
|
||||
case '\\':
|
||||
return '|';
|
||||
case '`':
|
||||
return '~';
|
||||
case ';':
|
||||
return ':';
|
||||
case '\'':
|
||||
return '"';
|
||||
case ',':
|
||||
return '<';
|
||||
case '.':
|
||||
return '>';
|
||||
case '/':
|
||||
return '?';
|
||||
}
|
||||
// FIXME: what the heck is it? I guess we don't need to shift it?
|
||||
}
|
||||
|
||||
// And if we fall through, then just return it as-is
|
||||
return k;
|
||||
}
|
||||
|
||||
void AppleKeyboard::keyDepressed(uint8_t k)
|
||||
{
|
||||
keysDown[k] = true;
|
||||
|
||||
// If it's not a virtual key, then set the anyKeyDown flag
|
||||
// (the VM will see this as a keyboard key)
|
||||
if (!isVirtualKey(k)) {
|
||||
if (!anyKeyIsDown) {
|
||||
mmu->setKeyDown(true);
|
||||
anyKeyIsDown = true;
|
||||
}
|
||||
keyThatIsRepeating = translateKeyWithModifiers(k);
|
||||
startRepeatTimer = g_cpu->cycles + STARTREPEAT;
|
||||
mmu->keyboardInput(keyThatIsRepeating);
|
||||
} else if (k == LA) {
|
||||
// Special handling: apple keys
|
||||
mmu->isOpenApplePressed = true;
|
||||
return;
|
||||
} else if (k == RA) {
|
||||
// Special handling: apple keys
|
||||
mmu->isClosedApplePressed = true;
|
||||
return;
|
||||
} else if (k == LOCK) {
|
||||
// Special handling: caps lock
|
||||
capsLockEnabled = !capsLockEnabled;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AppleKeyboard::keyReleased(uint8_t k)
|
||||
{
|
||||
keysDown[k] = false;
|
||||
|
||||
// Special handling: apple keys
|
||||
if (k == LA) {
|
||||
mmu->isOpenApplePressed = false;
|
||||
return;
|
||||
}
|
||||
if (k == RA) {
|
||||
mmu->isClosedApplePressed = false;
|
||||
return;
|
||||
}
|
||||
if (k == LOCK) {
|
||||
// Nothing to do when the caps lock key is released.
|
||||
return;
|
||||
}
|
||||
|
||||
if (anyKeyIsDown) {
|
||||
anyKeyIsDown = false;
|
||||
for (int i=0; i<sizeof(keysDown); i++) {
|
||||
if (keysDown[i] && !isVirtualKey(i)) {
|
||||
anyKeyIsDown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anyKeyIsDown) {
|
||||
mmu->setKeyDown(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppleKeyboard::maintainKeyboard(uint32_t cycleCount)
|
||||
{
|
||||
if (anyKeyIsDown) {
|
||||
if (startRepeatTimer) {
|
||||
if (cycleCount >= startRepeatTimer) {
|
||||
// waiting to start repeating
|
||||
startRepeatTimer = 0;
|
||||
repeatTimer = 0;
|
||||
// Will fall through...
|
||||
} else {
|
||||
// Don't fall through; not time to start repeating yet
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// already repeating; keep it up
|
||||
if (cycleCount >= repeatTimer) {
|
||||
mmu->keyboardInput(keyThatIsRepeating);
|
||||
repeatTimer = cycleCount + REPEATAGAIN;
|
||||
}
|
||||
}
|
||||
}
|
57
apple/applekeyboard.h
Normal file
57
apple/applekeyboard.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef __APPLEKEYBOARD_H
|
||||
#define __APPLEKEYBOARD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "vmkeyboard.h"
|
||||
#include "applemmu.h"
|
||||
|
||||
class AppleKeyboard : public VMKeyboard {
|
||||
public:
|
||||
AppleKeyboard(AppleMMU *m);
|
||||
virtual ~AppleKeyboard();
|
||||
|
||||
virtual void keyDepressed(uint8_t k);
|
||||
virtual void keyReleased(uint8_t k);
|
||||
virtual void maintainKeyboard(uint32_t cycleCount);
|
||||
|
||||
protected:
|
||||
bool isVirtualKey(uint8_t kc);
|
||||
uint8_t translateKeyWithModifiers(uint8_t k);
|
||||
|
||||
private:
|
||||
AppleMMU *mmu;
|
||||
|
||||
bool capsLockEnabled;
|
||||
|
||||
// This is a trade-off. I'm choosing speed over RAM size. If we need
|
||||
// to reclaim RAM, we can get some bytes here at the expense of speed.
|
||||
|
||||
// These are flags for whether or not each of the individual keys are
|
||||
// down, so that we can repeat appropriately. We're tracking state
|
||||
// of all of the keys because of special modifier key situations.
|
||||
// It's lazily using 256 bytes instead of whatever 62 we'd actually need.
|
||||
bool keysDown[256];
|
||||
bool anyKeyIsDown;
|
||||
|
||||
// While one - and only one - key is down, we repeat keypresses
|
||||
// after about "534 to 801 milliseconds" (UTA2E, p. 7-15); and then
|
||||
// while repeating, we send that keypress (reset keystrobe) about 15
|
||||
// times a second (every 66667-ish CPU cycles).
|
||||
//
|
||||
// startRepeatTimer is the time (in CPU clock cycles) when we will
|
||||
// start repeating the key that's currently down (note: rollover
|
||||
// happens every 4925 seconds because it's a 32-bit counter, which means
|
||||
// that roughly once every 82 minutes it's possible that a key will begin
|
||||
// repeating early).
|
||||
//
|
||||
// keyThatIsRepeating is set to the actual key pressed.
|
||||
// repeatTimer is the cpu cycle count at which we would repeat again.
|
||||
// (It also has the rollover problem once every 82 minutes.)
|
||||
|
||||
uint32_t startRepeatTimer;
|
||||
uint8_t keyThatIsRepeating;
|
||||
uint32_t repeatTimer;
|
||||
};
|
||||
|
||||
#endif
|
853
apple/applemmu.cpp
Normal file
853
apple/applemmu.cpp
Normal file
@ -0,0 +1,853 @@
|
||||
#ifdef TEENSYDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include "applemmu.h"
|
||||
#include "applemmu-rom.h"
|
||||
#include "physicalspeaker.h"
|
||||
#include "cpu.h"
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
// apple //e memory map
|
||||
|
||||
/*
|
||||
page 0x00: zero page (straight ram)
|
||||
page 0x01: stack (straight ram)
|
||||
page 0x02:
|
||||
page 0x03:
|
||||
text/lores page 1: 0x0400 - 0x7FF
|
||||
text/lores page 2: 0x0800 - 0xBFF
|
||||
pages 0x0C - 0x1F: straight ram
|
||||
hires page 1: pages 0x20 - 0x3F
|
||||
hires page 2: pages 0x40 - 0x5F
|
||||
pages 0x60 - 0xBF: straight ram
|
||||
page 0xc0: I/O switches
|
||||
pages 0xc1 - 0xcf: slot ROMs
|
||||
pages 0xd0 - 0xdf: Basic ROM
|
||||
pages 0xe0 - 0xff: monitor ROM
|
||||
*/
|
||||
|
||||
AppleMMU::AppleMMU(AppleDisplay *display)
|
||||
{
|
||||
anyKeyDown = false;
|
||||
keyboardStrobe = 0x00;
|
||||
|
||||
isOpenApplePressed = false;
|
||||
isClosedApplePressed = false;
|
||||
|
||||
for (int8_t i=0; i<=7; i++) {
|
||||
slots[i] = NULL;
|
||||
}
|
||||
|
||||
allocateMemory();
|
||||
|
||||
this->display = display;
|
||||
this->display->setSwitches(&switches);
|
||||
resetRAM(); // initialize RAM, load ROM
|
||||
}
|
||||
|
||||
AppleMMU::~AppleMMU()
|
||||
{
|
||||
delete display;
|
||||
// FIXME: clean up the memory we allocated
|
||||
}
|
||||
|
||||
void AppleMMU::Reset()
|
||||
{
|
||||
resetRAM();
|
||||
resetDisplay(); // sets the switches properly
|
||||
}
|
||||
|
||||
uint8_t AppleMMU::read(uint16_t address)
|
||||
{
|
||||
if (address >= 0xC000 &&
|
||||
address <= 0xC0FF) {
|
||||
return readSwitches(address);
|
||||
}
|
||||
|
||||
uint8_t res = readPages[(address & 0xFF00) >> 8][address & 0xFF];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Bypass MMU and read directly from a given page - also bypasses switches
|
||||
uint8_t AppleMMU::readDirect(uint16_t address, uint8_t fromPage)
|
||||
{
|
||||
return ramPages[(address & 0xFF00) >> 8][fromPage][address & 0xFF];
|
||||
}
|
||||
|
||||
void AppleMMU::write(uint16_t address, uint8_t v)
|
||||
{
|
||||
if (address >= 0xC000 &&
|
||||
address <= 0xC0FF) {
|
||||
return writeSwitches(address, v);
|
||||
}
|
||||
|
||||
// Don't allow writes to ROM
|
||||
if (address >= 0xC100 && address <= 0xCFFF)
|
||||
return;
|
||||
if (address >= 0xD000 && address <= 0xFFFF && !writebsr) {
|
||||
// memory-protected, so don't allow writes
|
||||
return;
|
||||
}
|
||||
|
||||
writePages[(address & 0xFF00) >> 8][address & 0xFF] = v;
|
||||
|
||||
if (address >= 0x400 &&
|
||||
address <= 0x7FF) {
|
||||
display->writeLores(address, v);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address >= 0x2000 &&
|
||||
address <= 0x5FFF) {
|
||||
display->writeHires(address, v);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this is no longer "MMU", is it?
|
||||
void AppleMMU::resetDisplay()
|
||||
{
|
||||
updateMemoryPages();
|
||||
display->modeChange();
|
||||
}
|
||||
|
||||
void AppleMMU::handleMemorySwitches(uint16_t address, uint16_t lastSwitch)
|
||||
{
|
||||
// many of these are spelled out here:
|
||||
// http://apple2.org.za/gswv/a2zine/faqs/csa2pfaq.html
|
||||
switch (address) {
|
||||
|
||||
// These are write-only and perform no action on read
|
||||
|
||||
case 0xC000: // CLR80STORE
|
||||
switches &= ~S_80STORE;
|
||||
updateMemoryPages();
|
||||
break;
|
||||
case 0xC001: // SET80STORE
|
||||
switches |= S_80STORE;
|
||||
updateMemoryPages();
|
||||
break;
|
||||
case 0xC002: // CLRAUXRD read from main 48k RAM
|
||||
auxRamRead = false;
|
||||
break;
|
||||
case 0xC003: // SETAUXRD read from aux/alt 48k
|
||||
auxRamRead = true;
|
||||
break;
|
||||
case 0xC004: // CLRAUXWR write to main 48k RAM
|
||||
auxRamWrite = false;
|
||||
break;
|
||||
case 0xC005: // SETAUXWR write to aux/alt 48k
|
||||
auxRamWrite = true;
|
||||
break;
|
||||
case 0xC006: // CLRCXROM use ROM on cards
|
||||
intcxrom = false;
|
||||
break;
|
||||
case 0xC007: // SETCXROM use internal ROM
|
||||
intcxrom = true;
|
||||
break;
|
||||
case 0xC008: // CLRAUXZP use main zero page, stack, LC
|
||||
altzp = false;
|
||||
break;
|
||||
case 0xC009: // SETAUXZP use alt zero page, stack, LC
|
||||
altzp = true;
|
||||
break;
|
||||
case 0xC00A: // CLRC3ROM use internal slot 3 ROM
|
||||
slot3rom = false;
|
||||
break;
|
||||
case 0xC00B: // SETC3ROM use external slot 3 ROM
|
||||
slot3rom = true;
|
||||
break;
|
||||
|
||||
// these are probably read/write ?
|
||||
|
||||
case 0xC080: // READBSR2 and shadow copy
|
||||
case 0xC084:
|
||||
bank1 = false; // LC RAM bank2, Read and Write-prot RAM
|
||||
readbsr = true; // read from bank2
|
||||
writebsr = false; // write-protected
|
||||
break;
|
||||
|
||||
case 0xC081: // ROMIN
|
||||
case 0xC085:
|
||||
bank1 = false; // LC RAM bank2, read ROM, write-enable RAM
|
||||
readbsr = false; // if it's read twice in a row
|
||||
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
|
||||
break;
|
||||
|
||||
case 0xC082:
|
||||
case 0xC086:
|
||||
bank1 = false; // LC RAM bank2, Read ROM instead of RAM,
|
||||
readbsr = false; // WR-protect RAM
|
||||
writebsr = false;
|
||||
break;
|
||||
|
||||
case 0xC083: // READWRBSR2
|
||||
case 0xC087:
|
||||
bank1 = false;
|
||||
readbsr = true;
|
||||
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
|
||||
break;
|
||||
|
||||
case 0xC088: // READBSR1
|
||||
case 0xC08C:
|
||||
bank1 = true;
|
||||
readbsr = true;
|
||||
writebsr = false;
|
||||
break;
|
||||
|
||||
case 0xC089: // WRITEBSR1
|
||||
case 0xC08D:
|
||||
bank1 = true;
|
||||
readbsr = false;
|
||||
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
|
||||
break;
|
||||
|
||||
case 0xC08A: // OFFBSR1
|
||||
case 0xC08E:
|
||||
bank1 = true;
|
||||
readbsr = false;
|
||||
writebsr = false;
|
||||
break;
|
||||
|
||||
case 0xC08B: // READWRBSR1
|
||||
case 0xC08F:
|
||||
bank1 = true;
|
||||
readbsr = true;
|
||||
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
|
||||
break;
|
||||
}
|
||||
|
||||
updateMemoryPages();
|
||||
}
|
||||
|
||||
// many (most? all?) switches are documented here:
|
||||
// http://apple2.org.za/gswv/a2zine/faqs/csa2pfaq.html
|
||||
|
||||
uint8_t AppleMMU::readSwitches(uint16_t address)
|
||||
{
|
||||
static uint16_t lastReadSwitch = 0x0000;
|
||||
static uint16_t thisReadSwitch = 0x0000;
|
||||
|
||||
lastReadSwitch = thisReadSwitch;
|
||||
thisReadSwitch = address;
|
||||
|
||||
// If this is a read for any of the slot switches, and we have
|
||||
// hardware in that slot, then return its result.
|
||||
if (address >= 0xC090 && address <= 0xC0FF) {
|
||||
for (uint8_t i=1; i<=7; i++) {
|
||||
if (address >= (0xC080 | (i << 4)) &&
|
||||
address <= (0xC08F | (i << 4))) {
|
||||
if (slots[i]) {
|
||||
return slots[i]->readSwitches(address & ~(0xC080 | (i<<4)));
|
||||
}
|
||||
else
|
||||
return FLOATING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (address) {
|
||||
case 0xC010:
|
||||
// consume the keyboard strobe flag
|
||||
keyboardStrobe &= 0x7F;
|
||||
return (anyKeyDown ? 0x80 : 0x00);
|
||||
|
||||
#if 0
|
||||
case 0xC000:
|
||||
|
||||
case 0xC001:
|
||||
case 0xC002:
|
||||
case 0xC003:
|
||||
case 0xC004:
|
||||
case 0xC005:
|
||||
case 0xC006:
|
||||
case 0xC007:
|
||||
case 0xC008:
|
||||
case 0xC009:
|
||||
case 0xC00A:
|
||||
case 0xC00B:
|
||||
// nothing happens in read mode for these, so don't call this:
|
||||
// handleMemorySwitches(address, lastReadSwitch);
|
||||
|
||||
case 0xC00C:
|
||||
case 0xC00D:
|
||||
case 0xC00E:
|
||||
case 0xC00F:
|
||||
|
||||
// But according to UTA2E, these ALL return keyboard strobe data.
|
||||
// We do that after the switch statement so we get the other affects
|
||||
// from reads to these addresses within the switch statement.
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 0xC080:
|
||||
case 0xC081:
|
||||
case 0xC082:
|
||||
case 0xC083:
|
||||
case 0xC084:
|
||||
case 0xC085:
|
||||
case 0xC086:
|
||||
case 0xC087:
|
||||
case 0xC088:
|
||||
case 0xC089:
|
||||
case 0xC08A:
|
||||
case 0xC08B:
|
||||
case 0xC08C:
|
||||
case 0xC08D:
|
||||
case 0xC08E:
|
||||
case 0xC08F:
|
||||
// but read does affect these, same as write
|
||||
handleMemorySwitches(address, lastReadSwitch);
|
||||
break;
|
||||
|
||||
case 0xC00C: // CLR80VID disable 80-col video mode
|
||||
if (switches & S_80COL) {
|
||||
switches &= ~S_80COL;
|
||||
resetDisplay();
|
||||
}
|
||||
break;
|
||||
case 0xC00D: // SET80VID enable 80-col video mode
|
||||
if (!(switches & S_80COL)) {
|
||||
switches |= S_80COL;
|
||||
resetDisplay();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xC00E: // CLRALTCH use main char set - norm LC, flash UC
|
||||
switches &= ~S_ALTCH;
|
||||
break;
|
||||
case 0xC00F: // SETALTCH use alt char set - norm inverse, LC; no flash
|
||||
switches |= S_ALTCH;
|
||||
break;
|
||||
|
||||
|
||||
case 0xC011: // RDLCBNK2
|
||||
return bank1 ? 0x00 : 0x80;
|
||||
case 0xC012: // RDLCRAM
|
||||
return readbsr ? 0x80 : 0x00;
|
||||
case 0xC013: // RDRAMRD
|
||||
return auxRamRead ? 0x80 : 0x00;
|
||||
case 0xC014: // RDRAMWR
|
||||
return auxRamWrite ? 0x80 : 0x00;
|
||||
case 0xC015: // RDCXROM
|
||||
return intcxrom ? 0x80 : 0x00;
|
||||
case 0xC016: // RDAUXZP
|
||||
return altzp ? 0x80 : 0x00;
|
||||
case 0xC017: // RDC3ROM
|
||||
return slot3rom ? 0x80 : 0x00;
|
||||
|
||||
case 0xC018: // RD80COL
|
||||
return (switches & S_80STORE) ? 0x80 : 0x00;
|
||||
// 0xC019: RDVBLBAR -- is the vert |