mirror of
https://github.com/JorjBauer/aiie.git
synced 2025-01-13 22:32:00 +00:00
working on mockingboard support
This commit is contained in:
parent
4c5537a658
commit
e942d8a4dd
2
Makefile
2
Makefile
@ -4,7 +4,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -O3
|
||||
|
||||
TSRC=cpu.cpp util/testharness.cpp
|
||||
|
||||
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 RingBuffer.o globals.o opencv/aiie.o apple/parallelcard.o apple/fx80.o opencv/opencv-printer.o
|
||||
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 RingBuffer.o globals.o opencv/aiie.o apple/parallelcard.o apple/fx80.o opencv/opencv-printer.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o
|
||||
|
||||
ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h
|
||||
|
||||
|
@ -218,6 +218,14 @@ another project lying around that directly manipulated OpenCV bitmap
|
||||
data. It's functional, and the Mac build is only about functional
|
||||
testing (for me).
|
||||
|
||||
Mockingboard
|
||||
============
|
||||
|
||||
Mockingboard support is slowly taking shape, based on the schematic in
|
||||
the Apple II Documentation Project:
|
||||
|
||||
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Audio/Sweet%20Microsystems%20Mockingboard/Schematics/Mockingboard%20Schematic.gif
|
||||
|
||||
VM
|
||||
==
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "applemmu-rom.h"
|
||||
#include "physicalspeaker.h"
|
||||
#include "cpu.h"
|
||||
#include "mockingboard.h"
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
@ -69,6 +70,11 @@ uint8_t AppleMMU::read(uint16_t address)
|
||||
address <= 0xC0FF) {
|
||||
return readSwitches(address);
|
||||
}
|
||||
|
||||
// FIXME: assumes slot 4 is a mockingboard
|
||||
if (slots[4] && address >= 0xC400 && address <= 0xC4FF) {
|
||||
return ((Mockingboard *)slots[4])->read(address);
|
||||
}
|
||||
|
||||
uint8_t res = readPages[(address & 0xFF00) >> 8][address & 0xFF];
|
||||
|
||||
@ -88,6 +94,12 @@ void AppleMMU::write(uint16_t address, uint8_t v)
|
||||
return writeSwitches(address, v);
|
||||
}
|
||||
|
||||
// FIXME: assumes slot4 is a mockingboard
|
||||
if (slots[4] && address >= 0xC400 && address <= 0xC4FF) {
|
||||
((Mockingboard *)slots[4])->write(address, v);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow writes to ROM
|
||||
if (address >= 0xC100 && address <= 0xCFFF)
|
||||
return;
|
||||
|
@ -23,6 +23,9 @@ AppleVM::AppleVM()
|
||||
parallel = new ParallelCard();
|
||||
((AppleMMU *)mmu)->setSlot(1, parallel);
|
||||
|
||||
mockingboard = new Mockingboard();
|
||||
((AppleMMU *)mmu)->setSlot(4, mockingboard);
|
||||
|
||||
#ifdef TEENSYDUINO
|
||||
teensyClock = new TeensyClock((AppleMMU *)mmu);
|
||||
((AppleMMU *)mmu)->setSlot(7, teensyClock);
|
||||
@ -35,6 +38,8 @@ AppleVM::~AppleVM()
|
||||
delete teensyClock;
|
||||
#endif
|
||||
delete disk6;
|
||||
delete parallel;
|
||||
delete mockingboard;
|
||||
}
|
||||
|
||||
// fixme: make member vars
|
||||
@ -56,6 +61,7 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
|
||||
|
||||
keyboard->maintainKeyboard(cycles);
|
||||
parallel->update();
|
||||
mockingboard->update(cycles);
|
||||
}
|
||||
|
||||
void AppleVM::Reset()
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "diskii.h"
|
||||
#include "vmkeyboard.h"
|
||||
#include "parallelcard.h"
|
||||
#include "mockingboard.h"
|
||||
#ifdef TEENSYDUINO
|
||||
#include "teensy-clock.h"
|
||||
#endif
|
||||
@ -34,6 +35,7 @@ class AppleVM : public VM {
|
||||
DiskII *disk6;
|
||||
VMKeyboard *keyboard;
|
||||
ParallelCard *parallel;
|
||||
Mockingboard *mockingboard;
|
||||
#ifdef TEENSYDUINO
|
||||
TeensyClock *teensyClock;
|
||||
#endif
|
||||
|
129
apple/ay8910.cpp
Normal file
129
apple/ay8910.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include "ay8910.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
AY8910::AY8910()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AY8910::Reset()
|
||||
{
|
||||
printf("AY8910 reset\n");
|
||||
curRegister = 0;
|
||||
for (uint8_t i=0; i<16; i++)
|
||||
r[i] = 0xFF;
|
||||
waveformFlipTimer[0] = waveformFlipTimer[1] = waveformFlipTimer[2] = 0;
|
||||
outputState[0] = outputState[1] = outputState[2] = 0;
|
||||
}
|
||||
|
||||
uint8_t AY8910::read(uint8_t reg)
|
||||
{
|
||||
// FIXME: does anything ever need to read from this?
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// reg represents BC1, BDIR, /RST in bits 0, 1, 2.
|
||||
// val is the state of those three bits.
|
||||
// PortA is the state of whatever's currently on PortA when we do it.
|
||||
void AY8910::write(uint8_t reg, uint8_t PortA)
|
||||
{
|
||||
// Bit 2 (1 << 2 == 0x04) is wired to the Reset pin. If it goes low,
|
||||
// we reset the virtual chip.
|
||||
if ((reg & 0x04) == 0) {
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bit 0 (1 << 0 == 0x01) is the BC1 pin. BC2 is hard-wired to +5v.
|
||||
// We can ignore bit 3, b/c that was just checked above & triggered
|
||||
// a reset.
|
||||
reg &= ~0x04;
|
||||
|
||||
switch (reg) {
|
||||
case 0: // bDir==0 && BC1 == 0 (IAB)
|
||||
// Puts the DA bus in high-impedance state. Nothing for us to do?
|
||||
return;
|
||||
case 1: // bDir==0 && BC1 == 1 (DTB)
|
||||
// Contents of the currently addressed register are put in DA. FIXME?
|
||||
return;
|
||||
case 2: // bDir==1 && BC1 == 0 (DWS)
|
||||
// Write current PortA to PSG
|
||||
printf("Set register %d to %X\n", reg, PortA);
|
||||
r[curRegister] = PortA;
|
||||
if (curRegister <= 1) {
|
||||
cycleTime[0] = cycleTimeForPSG(0);
|
||||
} else if (curRegister <= 3) {
|
||||
cycleTime[1] = cycleTimeForPSG(1);
|
||||
} else if (curRegister <= 5) {
|
||||
cycleTime[2] = cycleTimeForPSG(2);
|
||||
} else if (curRegister == 7) {
|
||||
cycleTime[0] = cycleTimeForPSG(0);
|
||||
cycleTime[1] = cycleTimeForPSG(1);
|
||||
cycleTime[2] = cycleTimeForPSG(2);
|
||||
}
|
||||
|
||||
return;
|
||||
case 3: // bDir==1 && BC1 == 1 (INTAK)
|
||||
// Select current register
|
||||
curRegister = PortA & 0xF;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The lowest frequency the AY8910 makes is 30.6 Hz, which is ~33431
|
||||
// clock cycles.
|
||||
//
|
||||
// The highest frequency produced is 125kHz, which is ~8 cycles.
|
||||
//
|
||||
// The highest practicable, given our 24-cycle-main-loop, is
|
||||
// 41kHz. Which should be plenty fine.
|
||||
//
|
||||
// Conversely: we should be able to call update() as slowly as once
|
||||
// every 60-ish clock cycles before we start noticing it in the output
|
||||
// audio.
|
||||
uint16_t AY8910::cycleTimeForPSG(uint8_t psg)
|
||||
{
|
||||
// Convert the current registers in to a cycle count for how long
|
||||
// between flips of 0-to-1 from the square wave generator.
|
||||
|
||||
uint16_t regVal = (r[1+(psg*2)] << 8) | (r[0 + (psg*2)]);
|
||||
if (regVal == 0) regVal++;
|
||||
|
||||
// Ft = 4MHz / (32 * regVal); our clock is 1MHz
|
||||
// so we should return (32 * regVal) / 4 ?
|
||||
|
||||
return (32 * regVal) / 4;
|
||||
}
|
||||
|
||||
void AY8910::update(uint32_t cpuCycleCount)
|
||||
{
|
||||
// For any waveformFlipTimer that is > 0: if cpuCycleCount is larger
|
||||
// than the timer, we'll flip state. (It's a square wave!)
|
||||
|
||||
for (uint8_t i=0; i<3; i++) {
|
||||
uint32_t cc = cycleTime[i];
|
||||
|
||||
if (cc == 0) {
|
||||
waveformFlipTimer[i] = 0;
|
||||
} else {
|
||||
if (!waveformFlipTimer[i]) {
|
||||
// start a cycle, if necessary
|
||||
waveformFlipTimer[i] = cpuCycleCount + cc;
|
||||
}
|
||||
|
||||
if (waveformFlipTimer[i] && waveformFlipTimer[i] <= cpuCycleCount) {
|
||||
// flip when it's time to flip
|
||||
waveformFlipTimer[i] += cc;
|
||||
outputState[i] = !outputState[i];
|
||||
}
|
||||
}
|
||||
// If any of the square waves is on, then we want to be on.
|
||||
|
||||
// r[i+8] is the amplitude control.
|
||||
// FIXME: if r[i+8] & 0x10, then it's an envelope-specific amplitude
|
||||
g_speaker->mixOutput(outputState[i] ? (r[i+8] & 0x0F) : 0x00);
|
||||
}
|
||||
}
|
||||
|
28
apple/ay8910.h
Normal file
28
apple/ay8910.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef __AY8910_H
|
||||
#define __AY8910_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class AY8910 {
|
||||
public:
|
||||
AY8910();
|
||||
|
||||
void Reset();
|
||||
|
||||
uint8_t read(uint8_t reg);
|
||||
void write(uint8_t reg, uint8_t PortA);
|
||||
|
||||
void update(uint32_t cpuCycleCount);
|
||||
|
||||
protected:
|
||||
uint16_t cycleTimeForPSG(uint8_t psg);
|
||||
|
||||
private:
|
||||
uint8_t curRegister;
|
||||
uint8_t r[16];
|
||||
uint32_t waveformFlipTimer[3];
|
||||
uint8_t outputState[3];
|
||||
uint16_t cycleTime[3];
|
||||
};
|
||||
|
||||
#endif
|
@ -411,12 +411,12 @@ const char *DiskII::DiskName(int8_t num)
|
||||
void DiskII::loadROM(uint8_t *toWhere)
|
||||
{
|
||||
#ifdef TEENSYDUINO
|
||||
Serial.println("loading slot rom");
|
||||
Serial.println("loading DiskII rom");
|
||||
for (uint16_t i=0; i<=0xFF; i++) {
|
||||
toWhere[i] = pgm_read_byte(&romData[i]);
|
||||
}
|
||||
#else
|
||||
printf("loading slot rom\n");
|
||||
printf("loading DiskII rom\n");
|
||||
memcpy(toWhere, romData, 256);
|
||||
#endif
|
||||
}
|
||||
|
69
apple/mockingboard.cpp
Normal file
69
apple/mockingboard.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include "mockingboard.h"
|
||||
#include <string.h>
|
||||
|
||||
Mockingboard::Mockingboard()
|
||||
{
|
||||
}
|
||||
|
||||
Mockingboard::~Mockingboard()
|
||||
{
|
||||
}
|
||||
|
||||
void Mockingboard::Reset()
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t Mockingboard::readSwitches(uint8_t s)
|
||||
{
|
||||
// There are never any reads to the I/O switches
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void Mockingboard::writeSwitches(uint8_t s, uint8_t v)
|
||||
{
|
||||
// There are never any writes to the I/O switches
|
||||
}
|
||||
|
||||
void Mockingboard::loadROM(uint8_t *toWhere)
|
||||
{
|
||||
// We don't need a ROM; we're going to work via direct interaction
|
||||
// with memory 0xC400 - 0xC4FF
|
||||
}
|
||||
|
||||
uint8_t Mockingboard::read(uint16_t address)
|
||||
{
|
||||
address &= 0xFF;
|
||||
if ( (address >= 0x00 &&
|
||||
address <= 0x0F) ||
|
||||
(address >= 0x80 &&
|
||||
address <= 0x8F) ) {
|
||||
uint8_t idx = (address & 0x80 ? 1 : 0);
|
||||
if (idx == 0) { // FIXME: just debugging; remove this 'if'
|
||||
return sy6522[idx].read(address & 0x0F);
|
||||
}
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void Mockingboard::write(uint16_t address, uint8_t val)
|
||||
{
|
||||
address &= 0xFF;
|
||||
if ( (address >= 0x00 &&
|
||||
address <= 0x0F) ||
|
||||
(address >= 0x80 &&
|
||||
address <= 0x8F) ) {
|
||||
uint8_t idx = (address & 0x80 ? 1 : 0);
|
||||
if (idx == 0) { // FIXME: just debugging; remove this 'if'
|
||||
return sy6522[idx].write(address & 0x0F, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mockingboard::update(uint32_t cycles)
|
||||
{
|
||||
sy6522[0].update(cycles);
|
||||
// debugging: disabled the second update for the moment
|
||||
// sy6522[1].update(cycles);
|
||||
}
|
||||
|
34
apple/mockingboard.h
Normal file
34
apple/mockingboard.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef __MOCKINGBOARD_H
|
||||
#define __MOCKINGBOARD_H
|
||||
|
||||
#ifdef TEENSYDUINO
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#include "applemmu.h"
|
||||
#include "slot.h"
|
||||
#include "SY6522.h"
|
||||
|
||||
class Mockingboard : public Slot {
|
||||
public:
|
||||
Mockingboard();
|
||||
virtual ~Mockingboard();
|
||||
|
||||
virtual void Reset(); // used by BIOS cold-boot
|
||||
virtual uint8_t readSwitches(uint8_t s);
|
||||
virtual void writeSwitches(uint8_t s, uint8_t v);
|
||||
virtual void loadROM(uint8_t *toWhere);
|
||||
|
||||
void update(uint32_t cycles);
|
||||
|
||||
uint8_t read(uint16_t address);
|
||||
void write(uint16_t address, uint8_t val);
|
||||
|
||||
private:
|
||||
SY6522 sy6522[2];
|
||||
};
|
||||
|
||||
#endif
|
151
apple/sy6522.cpp
Normal file
151
apple/sy6522.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "sy6522.h"
|
||||
#include <stdio.h>
|
||||
|
||||
SY6522::SY6522()
|
||||
{
|
||||
ORB = ORA = 0;
|
||||
DDRB = DDRA = 0x00;
|
||||
T1_CTR = T2_CTR = 0;
|
||||
T1_CTR_LATCH = T2_CTR_LATCH = 0;
|
||||
ACR = 0x20; // free-running; FIXME: constant?
|
||||
PCR = 0xB0; // FIXME: ?
|
||||
IFR = 0x00; // FIXME: ?
|
||||
IER = 0x90; // FIXME: ?
|
||||
}
|
||||
|
||||
uint8_t SY6522::read(uint8_t address)
|
||||
{
|
||||
switch (address) {
|
||||
case SY_ORB:
|
||||
return ORB;
|
||||
case SY_ORA:
|
||||
return ORA;
|
||||
case SY_DDRB:
|
||||
return DDRB;
|
||||
case SY_DDRA:
|
||||
return DDRA;
|
||||
case SY_TMR1L:
|
||||
// FIXME: also updates IFR?
|
||||
return (T1_CTR & 0xFF);
|
||||
case SY_TMR1H:
|
||||
return (T1_CTR >> 8);
|
||||
case SY_TMR1LL:
|
||||
return (T1_CTR_LATCH & 0xFF);
|
||||
case SY_TMR1HL:
|
||||
return (T1_CTR_LATCH >> 8);
|
||||
case SY_TMR2L:
|
||||
// FIXME: alos udpates IFR?
|
||||
return (T2_CTR & 0xFF);
|
||||
case SY_TMR2H:
|
||||
return (T2_CTR >> 8);
|
||||
case SY_SS:
|
||||
// FIXME: floating
|
||||
return 0xFF;
|
||||
case SY_ACR:
|
||||
return ACR;
|
||||
case SY_PCR:
|
||||
return PCR;
|
||||
case SY_IFR:
|
||||
return IFR;
|
||||
case SY_IER:
|
||||
return 0x80 | IER;
|
||||
case SY_ORANOHS:
|
||||
return ORA;
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void SY6522::write(uint8_t address, uint8_t val)
|
||||
{
|
||||
printf("SY6522: %X = %02X\n", address, val);
|
||||
switch (address) {
|
||||
case SY_ORB:
|
||||
val &= DDRB;
|
||||
ORB = val;
|
||||
ay8910[0].write(val, ORA & DDRA);
|
||||
return;
|
||||
|
||||
case SY_ORA:
|
||||
ORA = val & DDRA;
|
||||
return;
|
||||
|
||||
case SY_DDRB:
|
||||
DDRB = val;
|
||||
return;
|
||||
|
||||
case SY_DDRA:
|
||||
DDRA = val;
|
||||
return;
|
||||
|
||||
case SY_TMR1L:
|
||||
case SY_TMR1LL:
|
||||
T1_CTR_LATCH = (T1_CTR_LATCH & 0xFF00) | val;
|
||||
return;
|
||||
|
||||
case SY_TMR1H:
|
||||
// FIXME: clear interrupt flag
|
||||
T1_CTR_LATCH = (T1_CTR_LATCH & 0x00FF) | (val << 8);
|
||||
T1_CTR = T1_CTR_LATCH;
|
||||
// FIXME: start timer?
|
||||
return;
|
||||
|
||||
case SY_TMR1HL:
|
||||
T1_CTR_LATCH = (T1_CTR_LATCH & 0x00FF) | (val << 8);
|
||||
// FIXME: clear interrupt flag
|
||||
return;
|
||||
|
||||
case SY_TMR2L:
|
||||
T2_CTR_LATCH = (T2_CTR_LATCH & 0xFF00) | val;
|
||||
return;
|
||||
|
||||
case SY_TMR2H:
|
||||
// FIXME: clear timer2 interrupt flag
|
||||
T2_CTR_LATCH = (T2_CTR_LATCH & 0x00FF) | (val << 8);
|
||||
T2_CTR = T2_CTR_LATCH;
|
||||
return;
|
||||
|
||||
case SY_SS:
|
||||
// FIXME: what is this for?
|
||||
return;
|
||||
|
||||
case SY_ACR:
|
||||
ACR = val;
|
||||
break;
|
||||
|
||||
case SY_PCR:
|
||||
PCR = val;
|
||||
break;
|
||||
|
||||
case SY_IFR:
|
||||
// Clear whatever low bits are set in IFR.
|
||||
val |= 0x80;
|
||||
val ^= 0x7F;
|
||||
IFR &= val;
|
||||
break;
|
||||
|
||||
case SY_IER:
|
||||
if (val & 0x80) {
|
||||
// Set bits based on val
|
||||
val &= 0x7F;
|
||||
IER |= val;
|
||||
// FIXME: start timer if necessary?
|
||||
} else {
|
||||
// Clear whatever low bits are set in IER.
|
||||
val |= 0x80;
|
||||
val ^= 0x7F;
|
||||
IER &= val;
|
||||
// FIXME: stop timer if it's running?
|
||||
}
|
||||
return;
|
||||
|
||||
case SY_ORANOHS:
|
||||
// FIXME: what is this for?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SY6522::update(uint32_t cycles)
|
||||
{
|
||||
ay8910[0].update(cycles);
|
||||
}
|
||||
|
54
apple/sy6522.h
Normal file
54
apple/sy6522.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef __SY6522_H
|
||||
#define __SY6522_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ay8910.h"
|
||||
|
||||
// 6522 interface registers
|
||||
enum {
|
||||
SY_ORB = 0x00, // ORB
|
||||
SY_ORA = 0x01, // ORA
|
||||
SY_DDRB = 0x02, // DDRB
|
||||
SY_DDRA = 0x03, // DDRA
|
||||
SY_TMR1L = 0x04, // TIMER1L_COUNTER
|
||||
SY_TMR1H = 0x05, // TIMER1H_COUNTER
|
||||
SY_TMR1LL = 0x06, // TIMER1L_LATCH
|
||||
SY_TMR1HL = 0x07, // TIMER1H_LATCH
|
||||
SY_TMR2L = 0x08, // TIMER2L
|
||||
SY_TMR2H = 0x09, // TIMER2H
|
||||
SY_SS = 0x0a, // SERIAL_SHIFT
|
||||
SY_ACR = 0x0b, // ACR
|
||||
SY_PCR = 0x0c, // PCR
|
||||
SY_IFR = 0x0d, // IFR
|
||||
SY_IER = 0x0e, // IER
|
||||
SY_ORANOHS = 0x0f // ORA_NO_HS
|
||||
};
|
||||
|
||||
class SY6522 {
|
||||
public:
|
||||
SY6522();
|
||||
|
||||
uint8_t read(uint8_t address);
|
||||
void write(uint8_t address, uint8_t val);
|
||||
|
||||
void update(uint32_t cycles);
|
||||
|
||||
private:
|
||||
uint8_t ORB; // port B
|
||||
uint8_t ORA; // port A
|
||||
uint8_t DDRB; // data direction register
|
||||
uint8_t DDRA; //
|
||||
uint16_t T1_CTR; // counters
|
||||
uint16_t T1_CTR_LATCH;
|
||||
uint16_t T2_CTR;
|
||||
uint16_t T2_CTR_LATCH;
|
||||
uint8_t ACR; // Aux Control Register
|
||||
uint8_t PCR; // Peripheral Control Register
|
||||
uint8_t IFR; // Interrupt Flag Register
|
||||
uint8_t IER; // Interrupt Enable Register
|
||||
|
||||
AY8910 ay8910[1]; // FIXME: an array in case we support more than one ... ?
|
||||
};
|
||||
|
||||
#endif
|
129
opencv/aiie.cpp
129
opencv/aiie.cpp
@ -4,8 +4,6 @@
|
||||
#include <termios.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
// Derived from http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
|
||||
#include "applevm.h"
|
||||
#include "opencv-display.h"
|
||||
#include "opencv-keyboard.h"
|
||||
@ -16,39 +14,17 @@
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
#include "timeutil.h"
|
||||
|
||||
//#define SHOWFPS
|
||||
//#define SHOWPC
|
||||
//#define DEBUGCPU
|
||||
//#define SHOWMEMPAGE
|
||||
|
||||
#define ORWL_NANO (+1.0E-9)
|
||||
#define ORWL_GIGA UINT64_C(1000000000)
|
||||
#define NANOSECONDS_PER_SECOND 1000000000UL
|
||||
#define CYCLES_PER_SECOND 1023000UL
|
||||
#define NANOSECONDS_PER_CYCLE (NANOSECONDS_PER_SECOND / CYCLES_PER_SECOND)
|
||||
|
||||
struct timespec nextInstructionTime, startTime;
|
||||
static struct timespec nextInstructionTime, startTime;
|
||||
uint64_t hitcount = 0;
|
||||
uint64_t misscount = 0;
|
||||
|
||||
static double orwl_timebase = 0.0;
|
||||
static uint64_t orwl_timestart = 0;
|
||||
static void _init_darwin_shim(void) {
|
||||
mach_timebase_info_data_t tb = { 0 };
|
||||
mach_timebase_info(&tb);
|
||||
orwl_timebase = tb.numer;
|
||||
orwl_timebase /= tb.denom;
|
||||
orwl_timestart = mach_absolute_time();
|
||||
}
|
||||
|
||||
int do_gettime(struct timespec *tp) {
|
||||
double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
|
||||
tp->tv_sec = diff * ORWL_NANO;
|
||||
tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NB_ENABLE 1
|
||||
#define NB_DISABLE 0
|
||||
|
||||
@ -96,102 +72,6 @@ void write(void *arg, uint16_t address, uint8_t v)
|
||||
// no action; this is a dummy function until we've finished initializing...
|
||||
}
|
||||
|
||||
// adds the number of microseconds that 'cycles' takes to *start and
|
||||
// returns it in *out
|
||||
void timespec_add_cycles(struct timespec *start,
|
||||
uint32_t cycles,
|
||||
struct timespec *out)
|
||||
{
|
||||
out->tv_sec = start->tv_sec;
|
||||
out->tv_nsec = start->tv_nsec;
|
||||
|
||||
uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles;
|
||||
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
|
||||
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
|
||||
|
||||
if (out->tv_nsec >= 1000000000L) {
|
||||
out->tv_sec++ ;
|
||||
out->tv_nsec -= 1000000000L;
|
||||
}
|
||||
}
|
||||
|
||||
void timespec_diff(struct timespec *start,
|
||||
struct timespec *end,
|
||||
struct timespec *diff,
|
||||
bool *negative) {
|
||||
struct timespec t;
|
||||
|
||||
if (negative)
|
||||
{
|
||||
*negative = false;
|
||||
}
|
||||
|
||||
// if start > end, swizzle...
|
||||
if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) )
|
||||
{
|
||||
t=*start;
|
||||
*start=*end;
|
||||
*end=t;
|
||||
if (negative)
|
||||
{
|
||||
*negative = true;
|
||||
}
|
||||
}
|
||||
|
||||
// assuming time_t is signed ...
|
||||
if (end->tv_nsec < start->tv_nsec)
|
||||
{
|
||||
t.tv_sec = end->tv_sec - start->tv_sec - 1;
|
||||
t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
|
||||
}
|
||||
else
|
||||
{
|
||||
t.tv_sec = end->tv_sec - start->tv_sec;
|
||||
t.tv_nsec = end->tv_nsec - start->tv_nsec;
|
||||
}
|
||||
|
||||
diff->tv_sec = t.tv_sec;
|
||||
diff->tv_nsec = t.tv_nsec;
|
||||
}
|
||||
|
||||
// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b)
|
||||
int8_t tsCompare(struct timespec *A, struct timespec *B)
|
||||
{
|
||||
if (A->tv_sec < B->tv_sec)
|
||||
return -1;
|
||||
|
||||
if (A->tv_sec > B->tv_sec)
|
||||
return 1;
|
||||
|
||||
if (A->tv_nsec < B->tv_nsec)
|
||||
return -1;
|
||||
|
||||
if (A->tv_nsec > B->tv_nsec)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct timespec tsSubtract(struct timespec time1, struct timespec time2)
|
||||
{
|
||||
struct timespec result;
|
||||
if ((time1.tv_sec < time2.tv_sec) ||
|
||||
((time1.tv_sec == time2.tv_sec) &&
|
||||
(time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */
|
||||
result.tv_sec = result.tv_nsec = 0 ;
|
||||
} else {/* TIME1 > TIME2 */
|
||||
result.tv_sec = time1.tv_sec - time2.tv_sec ;
|
||||
if (time1.tv_nsec < time2.tv_nsec) {
|
||||
result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ;
|
||||
result.tv_sec-- ;/* Borrow a second. */
|
||||
} else {
|
||||
result.tv_nsec = time1.tv_nsec - time2.tv_nsec ;
|
||||
}
|
||||
}
|
||||
|
||||
return (result) ;
|
||||
}
|
||||
|
||||
static void *cpu_thread(void *dummyptr) {
|
||||
struct timespec currentTime;
|
||||
|
||||
@ -226,9 +106,14 @@ static void *cpu_thread(void *dummyptr) {
|
||||
#endif
|
||||
timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime);
|
||||
|
||||
g_speaker->beginMixing();
|
||||
|
||||
// The paddles need to be triggered in real-time on the CPU
|
||||
// clock. That happens from the VM's CPU maintenance poller.
|
||||
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
||||
|
||||
// cpuMaintenance also maintained the sound card; update the speaker after
|
||||
g_speaker->maintainSpeaker(g_cpu->cycles);
|
||||
|
||||
#ifdef DEBUGCPU
|
||||
{
|
||||
|
@ -1,13 +1,99 @@
|
||||
#include "dummy-speaker.h"
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
#include "timeutil.h"
|
||||
|
||||
// FIXME: Globals; ick.
|
||||
static pthread_t speakerThreadID;
|
||||
static uint8_t curSpeakerData = 0x00;
|
||||
|
||||
static uint64_t hitcount;
|
||||
static uint64_t misscount;
|
||||
|
||||
static void *speaker_thread(void *dummyptr) {
|
||||
struct timespec currentTime;
|
||||
struct timespec startTime;
|
||||
struct timespec nextSampleTime;
|
||||
|
||||
_init_darwin_shim();
|
||||
do_gettime(&startTime);
|
||||
do_gettime(&nextSampleTime);
|
||||
|
||||
FILE *f = popen("play -q -t raw -b 8 -e unsigned-integer -r 8000 -", "w");
|
||||
|
||||
uint64_t sampleCount = 0;
|
||||
while (1) {
|
||||
do_gettime(¤tTime);
|
||||
struct timespec diff = tsSubtract(nextSampleTime, currentTime);
|
||||
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
|
||||
nanosleep(&diff, NULL);
|
||||
hitcount++;
|
||||
} else
|
||||
misscount++;
|
||||
|
||||
if ((sampleCount & 0xFFFF) == 0) {
|
||||
printf("sound hit: %lld miss: %lld\n", hitcount, misscount);
|
||||
}
|
||||
|
||||
fputc(curSpeakerData & 0xFF, f); fflush(f);
|
||||
nextSampleTime = startTime;
|
||||
timespec_add_ms(&startTime, sampleCount * 1000 / 8000, &nextSampleTime);
|
||||
sampleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DummySpeaker::DummySpeaker()
|
||||
{
|
||||
mixerValue = 0;
|
||||
_init_darwin_shim(); // set up the clock interface
|
||||
|
||||
if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) {
|
||||
printf("speaker thread created\n");
|
||||
}
|
||||
}
|
||||
|
||||
DummySpeaker::~DummySpeaker()
|
||||
{
|
||||
pclose(f);
|
||||
}
|
||||
|
||||
void DummySpeaker::toggleAtCycle(uint32_t c)
|
||||
{
|
||||
nextTransitionAt = c;
|
||||
}
|
||||
|
||||
void DummySpeaker::maintainSpeaker(uint32_t c)
|
||||
{
|
||||
/* if (nextTransitionAt && c >= nextTransitionAt) {
|
||||
// Override the mixer with a 1-bit "Terribad" audio sample change
|
||||
mixerValue = speakerState ? 0x00 : (0xFF<<3); // <<3 b/c of the >>=3 below
|
||||
nextTransitionAt = 0;
|
||||
}*/
|
||||
|
||||
if (numMixed) {
|
||||
mixerValue /= numMixed;
|
||||
}
|
||||
speakerState = mixerValue;
|
||||
|
||||
// FIXME: duplication of above? using a global? fix fix fix.
|
||||
curSpeakerData = mixerValue & 0xFF;
|
||||
}
|
||||
|
||||
bool DummySpeaker::currentState()
|
||||
{
|
||||
return speakerState;
|
||||
}
|
||||
|
||||
void DummySpeaker::beginMixing()
|
||||
{
|
||||
mixerValue = 0;
|
||||
numMixed = 0;
|
||||
}
|
||||
|
||||
void DummySpeaker::mixOutput(uint8_t v)
|
||||
{
|
||||
mixerValue += v;
|
||||
numMixed++;
|
||||
}
|
||||
|
@ -1,15 +1,28 @@
|
||||
#ifndef __DUMMYSPEAKER_H
|
||||
#define __DUMMYSPEAKER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "physicalspeaker.h"
|
||||
|
||||
class DummySpeaker : public PhysicalSpeaker {
|
||||
public:
|
||||
DummySpeaker();
|
||||
virtual ~DummySpeaker();
|
||||
|
||||
virtual void toggleAtCycle(uint32_t c);
|
||||
virtual void maintainSpeaker(uint32_t c);
|
||||
virtual bool currentState();
|
||||
virtual void beginMixing();
|
||||
virtual void mixOutput(uint8_t v);
|
||||
private:
|
||||
bool speakerState;
|
||||
|
||||
uint32_t mixerValue;
|
||||
uint8_t numMixed;
|
||||
uint32_t nextTransitionAt;
|
||||
|
||||
FILE *f;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
143
opencv/timeutil.h
Normal file
143
opencv/timeutil.h
Normal file
@ -0,0 +1,143 @@
|
||||
#include <time.h>
|
||||
#include <mach/mach_time.h>
|
||||
// Derived from
|
||||
// http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
|
||||
|
||||
#define ORWL_NANO (+1.0E-9)
|
||||
#define ORWL_GIGA UINT64_C(1000000000)
|
||||
#define NANOSECONDS_PER_SECOND 1000000000UL
|
||||
#define CYCLES_PER_SECOND 1023000UL
|
||||
#define NANOSECONDS_PER_CYCLE (NANOSECONDS_PER_SECOND / CYCLES_PER_SECOND)
|
||||
|
||||
static double orwl_timebase = 0.0;
|
||||
static uint64_t orwl_timestart = 0;
|
||||
static void _init_darwin_shim(void) {
|
||||
mach_timebase_info_data_t tb = { 0 };
|
||||
mach_timebase_info(&tb);
|
||||
orwl_timebase = tb.numer;
|
||||
orwl_timebase /= tb.denom;
|
||||
orwl_timestart = mach_absolute_time();
|
||||
}
|
||||
|
||||
static int do_gettime(struct timespec *tp) {
|
||||
double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
|
||||
tp->tv_sec = diff * ORWL_NANO;
|
||||
tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// adds the number of microseconds that 'cycles' takes to *start and
|
||||
// returns it in *out
|
||||
static void timespec_add_cycles(struct timespec *start,
|
||||
uint32_t cycles,
|
||||
struct timespec *out)
|
||||
{
|
||||
out->tv_sec = start->tv_sec;
|
||||
out->tv_nsec = start->tv_nsec;
|
||||
|
||||
uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles;
|
||||
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
|
||||
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
|
||||
|
||||
if (out->tv_nsec >= 1000000000L) {
|
||||
out->tv_sec++ ;
|
||||
out->tv_nsec -= 1000000000L;
|
||||
}
|
||||
}
|
||||
|
||||
// adds the number of microseconds given to *start and
|
||||
// returns it in *out
|
||||
static void timespec_add_ms(struct timespec *start,
|
||||
uint64_t micros,
|
||||
struct timespec *out)
|
||||
{
|
||||
out->tv_sec = start->tv_sec;
|
||||
out->tv_nsec = start->tv_nsec;
|
||||
|
||||
uint64_t nanosToAdd = micros * 1000000L;
|
||||
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
|
||||
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
|
||||
|
||||
if (out->tv_nsec >= 1000000000L) {
|
||||
out->tv_sec++ ;
|
||||
out->tv_nsec -= 1000000000L;
|
||||
}
|
||||
}
|
||||
|
||||
static void timespec_diff(struct timespec *start,
|
||||
struct timespec *end,
|
||||
struct timespec *diff,
|
||||
bool *negative) {
|
||||
struct timespec t;
|
||||
|
||||
if (negative)
|
||||
{
|
||||
*negative = false;
|
||||
}
|
||||
|
||||
// if start > end, swizzle...
|
||||
if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) )
|
||||
{
|
||||
t=*start;
|
||||
*start=*end;
|
||||
*end=t;
|
||||
if (negative)
|
||||
{
|
||||
*negative = true;
|
||||
}
|
||||
}
|
||||
|
||||
// assuming time_t is signed ...
|
||||
if (end->tv_nsec < start->tv_nsec)
|
||||
{
|
||||
t.tv_sec = end->tv_sec - start->tv_sec - 1;
|
||||
t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
|
||||
}
|
||||
else
|
||||
{
|
||||
t.tv_sec = end->tv_sec - start->tv_sec;
|
||||
t.tv_nsec = end->tv_nsec - start->tv_nsec;
|
||||
}
|
||||
|
||||
diff->tv_sec = t.tv_sec;
|
||||
diff->tv_nsec = t.tv_nsec;
|
||||
}
|
||||
|
||||
// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b)
|
||||
static int8_t tsCompare(struct timespec *A, struct timespec *B)
|
||||
{
|
||||
if (A->tv_sec < B->tv_sec)
|
||||
return -1;
|
||||
|
||||
if (A->tv_sec > B->tv_sec)
|
||||
return 1;
|
||||
|
||||
if (A->tv_nsec < B->tv_nsec)
|
||||
return -1;
|
||||
|
||||
if (A->tv_nsec > B->tv_nsec)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct timespec tsSubtract(struct timespec time1, struct timespec time2)
|
||||
{
|
||||
struct timespec result;
|
||||
if ((time1.tv_sec < time2.tv_sec) ||
|
||||
((time1.tv_sec == time2.tv_sec) &&
|
||||
(time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */
|
||||
result.tv_sec = result.tv_nsec = 0 ;
|
||||
} else {/* TIME1 > TIME2 */
|
||||
result.tv_sec = time1.tv_sec - time2.tv_sec ;
|
||||
if (time1.tv_nsec < time2.tv_nsec) {
|
||||
result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ;
|
||||
result.tv_sec-- ;/* Borrow a second. */
|
||||
} else {
|
||||
result.tv_nsec = time1.tv_nsec - time2.tv_nsec ;
|
||||
}
|
||||
}
|
||||
|
||||
return (result) ;
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ class PhysicalSpeaker {
|
||||
|
||||
virtual void toggleAtCycle(uint32_t c) = 0;
|
||||
virtual void maintainSpeaker(uint32_t c) = 0;
|
||||
virtual void beginMixing() = 0;
|
||||
virtual void mixOutput(uint8_t v) = 0;
|
||||
virtual bool currentState() = 0;
|
||||
|
||||
};
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
../apple/slot.cpp
|
@ -31,3 +31,8 @@ void TeensySpeaker::maintainSpeaker(uint32_t c)
|
||||
analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095
|
||||
}
|
||||
}
|
||||
|
||||
bool TeensySpeaker::currentState()
|
||||
{
|
||||
return speakerState;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ class TeensySpeaker : public PhysicalSpeaker {
|
||||
|
||||
virtual void toggleAtCycle(uint32_t c);
|
||||
virtual void maintainSpeaker(uint32_t c);
|
||||
virtual bool currentState();
|
||||
|
||||
private:
|
||||
bool speakerState;
|
||||
|
@ -219,8 +219,9 @@ void runCPU()
|
||||
#endif
|
||||
}
|
||||
|
||||
g_speaker->maintainSpeaker(g_cpu->cycles);
|
||||
g_speaker->beginMixing();
|
||||
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
||||
g_speaker->maintainSpeaker(g_cpu->cycles);
|
||||
}
|
||||
|
||||
// FIXME: move these memory-related functions elsewhere...
|
||||
|
Loading…
x
Reference in New Issue
Block a user