read WOZ files instead of NIB files; implement Logic State Sequencer

This commit is contained in:
Christopher A. Mosher 2018-12-18 08:46:46 -05:00
parent 51085f6802
commit 8a393af531
23 changed files with 629 additions and 277 deletions

View File

@ -17,7 +17,7 @@ import motherboard rom 2800 $(PREFIX)lib/apple2/system/a2/monitor.a65
# Disk ][ card with 13-sector ROMs
slot 6 disk
slot 6 disk13
import slot 6 rom 0 $(PREFIX)lib/apple2/dos/13sector/disk2.a65
# Insert DOS 3.1 System Master disk into drive 1

View File

@ -45,7 +45,7 @@ import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.a65
# Disk ][ controller card in slot 5, with 13-sector ROMs.
# This will read (DOS 3.1, 3.2, and 3.2.1) disks, which
# have 13 sectors per track.
slot 5 disk
slot 5 disk13
import slot 5 rom 0 $(PREFIX)lib/apple2/dos/13sector/disk2.a65
# Insert the DOS 3.1 System Master disk into drive 1 of slot 5
load slot 5 drive 1 $(PREFIX)lib/apple2/dos/13sector/dos310/clean31sysmas_stock_rawdos.nib

View File

@ -44,7 +44,7 @@ import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.a65
# Disk ][ controller card in slot 5, with 13-sector ROMs.
# This will read (DOS 3.1, 3.2, and 3.2.1) disks, which
# have 13 sectors per track.
slot 5 disk
slot 5 disk13
import slot 5 rom 0 $(PREFIX)lib/apple2/dos/13sector/disk2.a65
# Insert the DOS 3.1 System Master disk into drive 1 of slot 5
load slot 5 drive 1 $(PREFIX)lib/apple2/dos/13sector/dos310/clean31sysmas_stock_rawdos.nib

View File

@ -14,7 +14,7 @@
# Demo system ROM for the emulator. This is only to allow the
# emulator to do something useful when there are no real Apple ROM
# images provided.
#
#
import motherboard rom 2C00 $(PREFIX)lib/epple2/system/epple2sys.a65
# These are how to load the real (proprietary) Apple ROMs.
@ -38,7 +38,7 @@ import slot 2 rom 0 $(PREFIX)lib/epple2/cards/stdin.a65
slot 4 clock
import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.a65
#slot 5 disk
#slot 5 disk13
#import slot 5 rom 0 $(PREFIX)lib/apple2/dos/13sector/disk2.a65
#load slot 5 drive 1 $(PREFIX)lib/apple2/dos/13sector/dos310/clean31sysmas_stock_rawdos.nib

View File

@ -20,7 +20,10 @@ import slot 6 rom 0 /usr/lib/apple2/dos3x/16sector/controller/disk2.ex65
The first line uses the <<slot>> command to
insert a disk contoller card into slot 6, which is the standard
slot used for disk cards. The next line uses the <<import>>
slot used for disk cards.
The +disk+ keyword loads the 16-sector P6 ROM (Logic State Sequencer).
Alternatively, use +disk13+ to load the 13-sector P6 ROM (for DOS 3.2 or earlier).
The next line uses the <<import>>
command to load the card's ROM with the disk controller
firmware. This firmware is known as the ``bootstrap'' or ``P5'' ROM code.
It is seen by the Apple ][ at memory addresses $Cs00-$CsFF, where s is the
@ -28,7 +31,7 @@ slot number (so in the common case of the card being in slot 6, the ROM is
at $C600-$C6FF). The firmware is copyright by Apple, and is available from
the http://mosher.mine.nu/apple2/[Apple II Library].
You can also load a floppy disk image (nibble format) into the drive,either by putting
You can also load a floppy disk image (nibble format) into the drive, either by putting
the <<load>> command into the +epple2.conf+ file, or by using the command prompt
in the emulator (+F5+ key). For example, you could load the DOS 3.3 system master into
slot 6, drive 1, with this command

View File

@ -65,7 +65,8 @@ Apple2::~Apple2()
void Apple2::tick()
{
this->cpu.tick();
this->slts.tick();
this->cpu.tick();
this->video.tick();
this->paddles.tick();
this->speaker.tick();

View File

@ -34,6 +34,9 @@ void Card::reset()
{
}
void Card::tick()
{
}
unsigned char Card::io(const unsigned short /*address*/, const unsigned char data, const bool /*writing*/)

View File

@ -34,7 +34,8 @@ protected:
public:
Card();
virtual ~Card();
virtual void reset();
virtual void tick();
virtual void reset();
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
virtual unsigned char readRom(const unsigned short address, const unsigned char data);
virtual bool hasSeventhRom() { return false; }

View File

@ -392,12 +392,17 @@ void Config::insertCard(const std::string& cardType, int slot, Slots& slts, Scre
{
card = new FirmwareCard(gui,slot);
}
else if (cardType == "disk")
else if (cardType == "disk") // 16-sector LSS ROM
{
card = new DiskController(gui,slot);
card = new DiskController(gui,slot,false);
disk_mask |= (1 << slot);
}
else if (cardType == "clock")
else if (cardType == "disk13") // 13-sector LSS ROM
{
card = new DiskController(gui,slot,true);
disk_mask |= (1 << slot);
}
else if (cardType == "clock")
{
card = new ClockCard();
}

View File

@ -23,7 +23,8 @@
#include <ostream>
#include <fstream>
DiskBytes::DiskBytes()
DiskBytes::DiskBytes(bool lss13):
lss13(lss13)
{
unload();
}

View File

@ -27,7 +27,9 @@ private:
enum { TRACKS_PER_DISK = 0x23 };
enum { BYTES_PER_TRACK = 0x1A00 };
std::vector<unsigned char> bytes[TRACKS_PER_DISK];
bool lss13;
std::vector<unsigned char> bytes[TRACKS_PER_DISK];
std::string fileName;
std::string filePath;
@ -36,11 +38,11 @@ private:
unsigned int byt; // represents rotational position of disk
bool modified;
void nextByte();
void nextByte();
void checkForWriteProtection();
public:
DiskBytes();
DiskBytes(bool lss13);
~DiskBytes();
bool load(const std::string& filePath);

View File

@ -17,66 +17,131 @@
*/
#include "diskcontroller.h"
DiskController::DiskController(ScreenImage& gui, int slot):
gui(gui),
slot(slot),
drive1(diskBytes1,arm1),
drive2(diskBytes2,arm2),
currentDrive(&this->drive1)
{
DiskController::DiskController(ScreenImage& gui, int slot, bool lss13):
gui(gui),
slot(slot),
drive1(diskBytes1,arm1),
drive2(diskBytes2,arm2),
currentDrive(&this->drive1),
load(false),
write(false),
lssp6rom(lss13),
seq(0x20), // gotta start somewhere
t(0) {
}
DiskController::~DiskController()
{
DiskController::~DiskController() {
}
unsigned char DiskController::io(const unsigned short addr, const unsigned char d, const bool writing)
{
unsigned char data(d);
const unsigned char q = (addr & 0x000E) >> 1;
const bool on = (addr & 0x0001);
unsigned char DiskController::io(const unsigned short addr, const unsigned char d, const bool writing) {
this->dataBusReadOnlyCopy = d;
switch (q)
{
case 0:
case 1:
case 2:
case 3:
this->currentDrive->setMagnet(q,on);
this->gui.setTrack(this->slot,getCurrentDriveNumber(),getTrack());
break;
case 4:
this->motorOn = on;
this->gui.setIO(this->slot,getCurrentDriveNumber(),on);
break;
case 5:
this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber());
this->currentDrive = (on ? &this->drive2 : &this->drive1);
this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),this->motorOn);
break;
case 6:
if (on && this->write && writing)
{
set(data);
this->gui.setIO(this->slot,getCurrentDriveNumber(),this->motorOn);
this->gui.setDirty(this->slot,getCurrentDriveNumber(),true);
}
else if (!(on || this->write))
{
data = get();
}
break;
case 7:
this->write = on;
if (this->currentDrive->isWriteProtected())
{
data |= 0x80;
}
else
{
data &= 0x7F;
}
break;
}
return data;
const unsigned char q = (addr & 0x000E) >> 1;
const bool on = (addr & 0x0001);
switch (q) {
case 0:
case 1: // TODO if phase-1 is on, it also acts as write-protect (UA2, 9-8)
case 2:
case 3:
this->currentDrive->setMagnet(q,on);
this->gui.setTrack(this->slot,getCurrentDriveNumber(),getTrack());
break;
case 4:
this->motorOn = on;
this->gui.setIO(this->slot,getCurrentDriveNumber(),on);
break;
case 5:
this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber());
this->currentDrive = (on ? &this->drive2 : &this->drive1);
this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),this->motorOn);
break;
case 6:
this->load = on;
// TODO use LSS
// if (on && this->write && writing) {
// set(data);
// this->gui.setIO(this->slot,getCurrentDriveNumber(),this->motorOn);
// this->gui.setDirty(this->slot,getCurrentDriveNumber(),true);
// } else if (!(on || this->write)) {
// data = get();
// }
break;
case 7:
this->write = on;
// TODO use LSS
// if (this->currentDrive->isWriteProtected()) {
// data |= 0x80;
// } else {
// data &= 0x7F;
// }
break;
}
// if (this->motorOn) {
// if (this->dataRegister == 0xD5) {
// printf("\ndata register --> ");
// }
// if (this->dataRegister & 0x80) {
// printf("%02x", this->dataRegister);
// }
// }
return on ? d : this->dataRegister;
}
/*
* Get a timing cycle here, based on the MPU clock (1 MHz).
* In the real Apple we don't get really get this here. But...
* We need a 2MHz clock for the LSS; and
* we need to rotate the floppy @ 1 bit per 4 microseconds.
* (When the motor is on, that is.)
*/
void DiskController::tick() {
if (!this->motorOn) {
return;
}
rotateCurrentDisk();
// run two LSS cycles = 2MHz
stepLss();
stepLss();
}
void DiskController::rotateCurrentDisk() {
++t;
if (t < 0 || 4 <= t) { // 4us interval between bits
this->currentDrive->rotateDiskOneBit(); // (will also generate a read-pulse when it reads a 1-bit)
t = 0;
} else {
// clear the read pulse (to make it last only 1us)
this->currentDrive->clearPulse();
}
}
void DiskController::stepLss() {
std::uint8_t adr = this->write<<3 | this->load<<2 | (this->dataRegister>>7)<<1 | this->currentDrive->readPulse();
std::uint8_t cmd = this->lssp6rom.read(this->seq|adr);
// if (cmd & 3) printf("LSS ROM command byte: 0x%2x\n", cmd);
this->seq = cmd & 0xF0u;
// LSS command functions (UA2, 9-15, Table 9.3)
if (cmd & 8u) {
switch (cmd & 3u) {
case 3:
this->dataRegister = this->dataBusReadOnlyCopy;
break;
case 2:
this->dataRegister >>= 1;
this->dataRegister |= (isWriteProtected() << 7);
// TODO how to handle writing?
break;
case 1:
this->dataRegister <<= 1;
this->dataRegister |= ((cmd & 4u) >> 2);
// TODO how to handle writing?
break;
}
} else {
this->dataRegister = 0;
}
}

View File

@ -17,48 +17,60 @@
*/
#include "card.h"
#include "drive.h"
#include "wozfile.h"
#include "lss.h"
#include "screenimage.h"
#include <string>
#include <iostream>
#include <cstdint>
class DiskController : public Card
{
private:
ScreenImage& gui;
int slot;
DiskBytes diskBytes1;
WozFile diskBytes1;
StepperMotor arm1;
Drive drive1;
DiskBytes diskBytes2;
WozFile diskBytes2;
StepperMotor arm2;
Drive drive2;
Drive* currentDrive;
bool write;
bool motorOn;
bool load; // Q6
bool write; // Q7
bool motorOn; // TODO WOZ make it delay power-off by about 1 second.
// Maintain a copy of the last thing on the data bus, so it can
// be read by the LSS algorithm when needed.
std::uint8_t dataBusReadOnlyCopy;
LSS lssp6rom; // the LSS PROM P6 chip (one command per sequence/state combination)
std::uint8_t dataRegister; // C3 the controller's LS323 data register
std::uint8_t seq; // A3 sequence control LS174 (current sequence number, 0-F)
// For ease of use, we store the 4-bit seq number in the _high order_ nibble here.
// On the real Apple the read pulse goes thru this LS174 too, but we don't emulate that here.
// TODO for a rev. 0 motherboard, the disk controller will auto reset the CPU
std::uint8_t t; // used to keep track of 4 MPU cycles
void set(unsigned char data)
{
if (!this->motorOn)
{
return;
}
this->currentDrive->set(data);
}
// TODO for a rev. 0 motherboard, the disk controller will auto reset the CPU (see UA2, 9-13)
unsigned char get() const
{
if (!this->motorOn)
{
return 0xFF;
}
return this->currentDrive->get();
}
void writeBit(bool on) {
if (!this->motorOn) {
return;
}
this->currentDrive->writeBit(on);
}
// unsigned char get() const
// {
// if (!this->motorOn)
// {
// return 0xFF;
// }
// return this->currentDrive->get();
// }
Drive& getDrive(const unsigned char drive)
{
@ -70,13 +82,15 @@ private:
return (this->currentDrive == &this->drive1) ? this->drive2 : this->drive1;
}
void rotateCurrentDisk();
void stepLss();
public:
DiskController(ScreenImage& gui, int slot);
DiskController(ScreenImage& gui, int slot, bool lss13);
~DiskController();
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
void tick();
virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing);
void reset()
{
@ -117,10 +131,10 @@ public:
return this->motorOn;
}
const DiskBytes& getDiskBytes(unsigned char disk)
{
return this->getDrive(disk).getDiskBytes();
}
// const WozFile& getDiskBytes(unsigned char disk)
// {
// return this->getDrive(disk).getDiskBytes();
// }
unsigned char getTrack()
{

View File

@ -18,87 +18,122 @@
#ifndef DRIVE_H
#define DRIVE_H
#include <random>
#include <chrono>
#include <string>
#include "diskbytes.h"
#include <cstdint>
#include <iostream>
#include "wozfile.h"
#include "steppermotor.h"
class Drive
{
class Drive {
private:
enum { TRACKS_PER_DISK = 0x23 };
WozFile& disk;
StepperMotor& arm;
DiskBytes& disk;
StepperMotor& arm;
bool pulse;
std::uint8_t cContiguousZeroBits;
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution;
bool randomBit() {
return !distribution(generator);
}
public:
Drive(DiskBytes& disk, StepperMotor& arm):
disk(disk),
arm(arm)
{
}
Drive(WozFile& disk, StepperMotor& arm):
disk(disk),
arm(arm),
pulse(false),
cContiguousZeroBits(0),
generator(std::chrono::system_clock::now().time_since_epoch().count()),
distribution(0,1) {
}
~Drive() {}
~Drive() {
}
bool loadDisk(const std::string& fnib)
{
return this->disk.load(fnib);
}
bool loadDisk(const std::string& fnib) {
return this->disk.load(fnib);
}
void unloadDisk()
{
this->disk.unload();
}
bool isLoaded()
{
return this->disk.isLoaded();
}
void unloadDisk() {
this->disk.unload();
}
bool isLoaded() {
return this->disk.isLoaded();
}
void saveDisk()
{
this->disk.save();
}
void saveDisk() {
this->disk.save();
}
bool isWriteProtected() const
{
return this->disk.isWriteProtected();
}
bool isWriteProtected() const {
return this->disk.isWriteProtected();
}
bool isModified() const
{
return this->disk.isModified();
}
bool isModified() const {
return this->disk.isModified();
}
void setMagnet(unsigned char q, bool on)
{
this->arm.setMagnet(q,on);
}
void setMagnet(unsigned char q, bool on) {
this->arm.setMagnet(q,on);
}
int getTrack() const
{
return this->arm.getTrack();
}
int getTrack() const {
return this->arm.getTrack();
}
void rotateDiskOneBit() {
this->disk.rotateOneBit(this->arm.getQuarterTrack());
if (this->disk.getBit(this->arm.getQuarterTrack())) {
this->pulse = true;
cContiguousZeroBits = 0;
} else {
// keep a count of contiguous zero-bits and generate random bits when
// we see more than three (emulating the MC3470, see UA2, 9-11)
++cContiguousZeroBits;
if (3 < cContiguousZeroBits) {
// if (cContiguousZeroBits == 4) printf("\n<GENERATING RANDOM BIT(S).....>\n");
if (randomBit()) {
this->pulse = true;
}
}
}
}
bool readPulse() {
return this->pulse;
}
void clearPulse() {
this->pulse = false;
}
void writeBit(bool on) {
this->disk.setBit(this->arm.getQuarterTrack());
}
// unsigned char get() const
// {
// return this->disk.get(this->arm.getTrack());
// }
// void set(unsigned char value)
// {
// this->disk.put(this->arm.getTrack(),value);
// }
unsigned char get() const
{
return this->disk.get(this->arm.getTrack());
}
void set(unsigned char value)
{
this->disk.put(this->arm.getTrack(),value);
}
const DiskBytes& getDiskBytes()
{
return this->disk;
}
// const WozFile& getDiskBytes() {
// return this->disk;
// }
};
#endif

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "../config.h"
#endif
#include <iostream>

View File

@ -175,11 +175,14 @@ LSS::LSS(bool use13SectorDos32LSS):
setseq(lss13rom,0x23u,0x30u);
setseq(lss13rom,0x33u,0xD0u);
for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
showua2seq(lssrom,seq);
}
for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
showua2seq(lss13rom,seq);
if (use13SectorDos32LSS) {
for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
showua2seq(lss13rom,seq);
}
} else {
for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
showua2seq(lssrom,seq);
}
}
}

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "../config.h"
#endif
#include <SDL2/SDL.h>
@ -30,12 +30,12 @@
#include <stdexcept>
#include <iostream>
#include <iomanip>
static int run(const std::string& config_file)
{
static int run(const std::string& config_file) {
GUI gui;
std::auto_ptr<Emulator> emu(new Emulator());
std::unique_ptr<Emulator> emu(new Emulator());
Config cfg(config_file);
emu->config(cfg);
@ -45,26 +45,77 @@ static int run(const std::string& config_file)
return emu->run();
}
//std::uint8_t dataBus;
//std::uint8_t lssrom[0x100];
//std::uint8_t lss13rom[0x100];
//std::uint8_t wp(0);
//std::uint8_t q7(0);
//std::uint8_t q6(0);
//std::uint8_t dreg(0);
//std::uint8_t seq(0x20);
//std::uint8_t cmd;
//std::uint8_t adr;
//std::uint8_t cprint(0);
//void debugLog(const std::uint8_t x) {
// if (++cprint == 128 || x==0xD5u) {
// cprint = 0;
// printf("\n");
// }
// printf("%02x", x);
//}
//void lss(const std::uint8_t cmd) {
// if (cmd & 8) {
// switch (cmd & 3) {
// case 3: dreg = dataBus; break;
// case 2: dreg >>= 1; dreg |= (wp << 7); break;
// case 1: dreg <<= 1; dreg |= ((cmd & 4) >> 2); break;
// }
// } else {
// debugLog(dreg);
// dreg = 0;
// }
//}
//void doTrack(std::uint8_t *ptrk, const std::uint16_t ctrk) {
// std::uint8_t *pend(ptrk+ctrk);
// while (ptrk < pend) {
// for (std::uint8_t m(0x80); m; m >>= 1) {
// std::uint8_t pls = (*ptrk & m) ? 1 : 0;
// for (int i(0); i < 8; ++i) {
// adr = q7<<3 | q6<<2 | (dreg>>7)<<1 | pls;
// pls = 0;
// cmd = lssrom[seq|adr];
// seq = cmd & 0xF0;
// lss(cmd);
// }
// }
// ++ptrk;
// }
//}
#ifdef __cplusplus
extern "C"
#endif
int main(int argc, char* argv[])
{
if (argc > 2)
{
int main(int argc, char* argv[]) {
if (argc > 2) {
throw std::runtime_error("usage: epple2 [config-file]" );
}
int x = E2Const::test();
if (x != -1)
{
if (x != -1) {
std::cerr << x << std::endl;
throw std::runtime_error("bad constant in e2const.h" );
}
std::string config_file;
if (argc > 1)
{
if (argc > 1) {
config_file = argv[1];
}

View File

@ -46,6 +46,16 @@ void Slots::reset()
std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_reset());
}
struct Slots_Card_tick
{
void operator() (Card* p) { p->tick(); }
};
void Slots::tick()
{
std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_tick());
}
unsigned char Slots::readRom(const int islot, const unsigned short addr, const unsigned char data)
{
return this->cards[islot]->readRom(addr,data);

View File

@ -35,6 +35,7 @@ public:
Slots(ScreenImage& gui);
~Slots();
void tick();
unsigned char io(const int islot, const int iswch, const unsigned char b, const bool writing);
void reset();
unsigned char readRom(const int islot, const unsigned short addr, const unsigned char data);

View File

@ -17,9 +17,7 @@
*/
/**
* Emulates the arm stepper motor in the Disk ][.
* This implementation differs from the actual Disk ][ in
* that it rounds half- and quarter-tracks down to the
* next whole track. Also, this emulator moves the arm
* This emulator moves the arm
* instantaneously, whereas the Disk ][ arm would actually
* take some time to reach its new position (this would
* cause a difference if the state of the magnets changed
@ -48,70 +46,60 @@ mags ps magval
(undefined, i.e., no movement)
0000 ? 0
0101 ? 5
1010 ? A
0101 ? 5 // <-- TODO pick one at random?
1010 ? A // <-- TODO pick one at random?
1111 ? F
*/
#include "steppermotor.h"
#include "util.h"
#include <iostream>
StepperMotor::StepperMotor():
quarterTrack(QTRACKS >> 1), // start in the middle of the disk... just for fun
quarterTrack(QTRACK_MAX >> 1), // start in the middle of the disk... just for fun
// TODO if we want to be extremely accurate, we should save each arm's position on shutdown and restore on startup
// (because in the real-life Apple ][, the arm stays in the same position when powered off).
pos(0),
mags(0)
{
mags(0) {
}
StepperMotor::~StepperMotor()
{
StepperMotor::~StepperMotor() {
}
signed char StepperMotor::mapMagPos[] = {-1,0,2,1,4,-1,3,2,6,7,-1,0,5,6,4,-1};
void StepperMotor::setMagnet(const unsigned char magnet, const bool on)
{
const unsigned char mask = 1 << magnet;
if (on)
{
this->mags |= mask;
}
else
{
this->mags &= ~mask;
}
void StepperMotor::setMagnet(const unsigned char magnet, const bool on) {
const unsigned char mask = 1 << magnet;
if (on) {
this->mags |= mask;
} else {
this->mags &= ~mask;
}
const char newPos = mapMagPos[this->mags];
char d;
if (newPos >= 0)
{
d = calcDeltaPos(this->pos,newPos);
this->pos = newPos;
const char newPos = mapMagPos[this->mags];
char d;
if (newPos >= 0) {
d = calcDeltaPos(this->pos,newPos);
this->pos = newPos;
this->quarterTrack += d;
if (this->quarterTrack < 0)
this->quarterTrack = 0;
else if (this->quarterTrack > QTRACKS)
this->quarterTrack = QTRACKS;
}
/*
std::cout << " ARM: magnet " << (unsigned int)magnet << " " << (on ? "on " : "off" );
std::cout << " [" <<
((mags&1)?"*":".") <<
((mags&2)?"*":".") <<
((mags&4)?"*":".") <<
((mags&8)?"*":".") <<
"]";
if (d != 0)
{
std::cout << " track " << std::hex << (unsigned int)(this->quarterTrack >> 2);
int fract = this->quarterTrack & 3;
if (fract != 0)
{
std::cout << (fract == 1 ? " +.25" : fract == 2 ? " +.5" : " +.75");
}
}
std::cout << std::endl;
*/
this->quarterTrack += d;
if (this->quarterTrack < 0)
this->quarterTrack = 0;
else if (this->quarterTrack > QTRACK_MAX)
this->quarterTrack = QTRACK_MAX;
}
std::cout << " ARM: magnet " << (unsigned int)magnet << " " << (on ? "on " : "off" );
std::cout << " [" <<
((mags&1)?"*":".") <<
((mags&2)?"*":".") <<
((mags&4)?"*":".") <<
((mags&8)?"*":".") <<
"]";
if (d != 0) {
std::cout << " track " << std::hex << (unsigned int)(this->quarterTrack >> 2);
int fract = this->quarterTrack & 3;
if (fract != 0) {
std::cout << (fract == 1 ? " +.25" : fract == 2 ? " +.5" : " +.75");
}
}
std::cout << std::endl;
}

View File

@ -18,47 +18,46 @@
#ifndef STEPPERMOTOR_H
#define STEPPERMOTOR_H
class StepperMotor
{
#include <cstdint>
class StepperMotor {
private:
enum { TRACKS_PER_DISK = 0x23 };
enum { QTRACKS = TRACKS_PER_DISK << 2 };
enum { TRACKS_PER_DISK = 0x23 };
enum { QTRACK_MAX = TRACKS_PER_DISK << 2 };
signed short quarterTrack;
signed char pos; // 0 - 7
unsigned char mags;
// quarter track: 0=t0, 1=t0.25, 2=t0.5, 3=t0.75, 4=t1, ... 140=t35.00
// (see TMAP in WOZ file format)
signed short quarterTrack;
signed char pos; // 0 - 7
unsigned char mags;
static signed char mapMagPos[];
static signed char mapMagPos[];
static signed char calcDeltaPos(const unsigned char cur, const signed char next)
{
signed char d = next-cur; // -7 to +7
static signed char calcDeltaPos(const unsigned char cur, const signed char next) {
signed char d = next-cur; // -7 to +7
if (d==4 || d==-4)
{
d = 0;
}
else if (d>4)
{
d -= 8;
}
else if (d<-4)
{
d += 8;
}
if (d == 4 || d == -4) {
d = 0; // <--- TODO pick random direction?
} else if (4 < d) {
d -= 8;
} else if (d < -4) {
d += 8;
}
return d;
}
return d;
}
public:
StepperMotor();
~StepperMotor();
StepperMotor();
~StepperMotor();
void setMagnet(const unsigned char magnet, const bool on);
unsigned char getTrack()
{
return ((unsigned short)(this->quarterTrack)) >> 2;
}
void setMagnet(const unsigned char magnet, const bool on);
unsigned char getTrack() {
return ((unsigned short)(this->quarterTrack)) >> 2;
}
std::uint8_t getQuarterTrack() {
return this->quarterTrack;
}
};
#endif

View File

@ -23,7 +23,7 @@
#include <ostream>
#include <fstream>
WozFile::WozFile() {
WozFile::WozFile() : lastQuarterTrack(0) {
unload();
}
@ -201,10 +201,8 @@ void WozFile::checkForWriteProtection() {
outf.close();
}
void WozFile::save()
{
if (isWriteProtected() || !isLoaded())
{
void WozFile::save() {
if (isWriteProtected() || !isLoaded()) {
return;
}
// std::ofstream out(filePath.c_str(),std::ios::binary);
@ -215,11 +213,155 @@ void WozFile::save()
// this->modified = false;
}
void WozFile::unload()
{
this->byt = 0;
void WozFile::unload() {
this->bit = 0x80u;
this->byt = 0x00u;
this->writable = true;
this->loaded = false;
this->filePath = "";
this->modified = false;
}
static std::uint8_t bc(std::uint8_t bit) {
switch (bit) {
case 0x80u: return 0u;
case 0x40u: return 1u;
case 0x20u: return 2u;
case 0x10u: return 3u;
case 0x08u: return 4u;
case 0x04u: return 5u;
case 0x02u: return 6u;
case 0x01u: return 7u;
}
return 255u; // should never happen
}
static std::uint8_t cb(std::uint8_t bit) {
switch (bit) {
case 0u: return 0x80u;
case 1u: return 0x40u;
case 2u: return 0x20u;
case 3u: return 0x10u;
case 4u: return 0x08u;
case 5u: return 0x04u;
case 6u: return 0x02u;
case 7u: return 0x01u;
}
return 255u; // should never happen
}
static void dumpQTrack(std::uint8_t currentQuarterTrack) {
if (currentQuarterTrack % 4) {
const std::uint8_t hundredths((currentQuarterTrack%4) * 25);
printf(" Reading from <---------- track 0x%02X +.%02d\n", currentQuarterTrack/4, hundredths);
} else {
printf(" Reading from <---------- track 0x%02X\n", currentQuarterTrack/4);
}
}
/*
* Rotate the floppy disk by one bit.
* In real life we don't care what track we're one, but for the
* emulator we need to know. This is because the tracks within the
* WOZ file could be different lengths. So in order to know when
* we need to loop back to the beginning of the track (circular
* track on the floppy), we need to know the actual bit length
* of that track in our WOZ file.
*/
void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
if (!isLoaded()) {
return; // there's no disk to rotate
}
// In WOZ track image, bits (i.e., magnetic field reversal on disk,
// or lack thereof) are packed into bytes, high bit to low bit.
// Really, it's the stream of bits returned by the MC3470 (but also
// possibly with random bits zeroed out).
// std::uint16_t before = (this->byt*8+bc(this->bit));
// printf("disk at bit: %d\n", this->byt*8+bc(this->bit));
// Move to next bit:
this->bit >>= 1;
// If we hit end of this byte, move on to beginning of next byte
if (this->bit == 0) {
++this->byt;
this->bit = 0x80u;
}
if (this->tmap[currentQuarterTrack] == 0xFFu) {
return;
}
if (currentQuarterTrack != this->lastQuarterTrack) {
double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]];
double newLen = this->trk_bits[this->tmap[currentQuarterTrack]];
double dif = newLen/oldLen;
if (dif < -0.000001 || 0.000001 < dif) {
// dumpQTrack(currentQuarterTrack);
// printf(" new track: bit pos: %d ", this->byt*8+bc(this->bit));
std::uint16_t newBit = (this->byt*8+bc(this->bit)) * dif;
this->byt = newBit / 8;
this->bit = cb(newBit % 8);
// printf("--> %d\n\n", this->byt*8+bc(this->bit));
}
this->lastQuarterTrack = currentQuarterTrack;
}
// Check for hitting the end of our track image,
// and if so, move back to the beginning.
// This is how we emulate a circular track on the floppy.
if (this->trk_bits[this->tmap[currentQuarterTrack]] <= this->byt*8+bc(this->bit)) {
// printf("\n<rewinding track here>\n");
this->byt = 0;
this->bit = 0x80u;
}
// std::uint16_t after = (this->byt*8+bc(this->bit));
// if (!(after % 0x100u)) {
// printf("\nnow at bit %04x\n", after);
// }
// if (after != before+1) {
// printf("\nbit changing from %04x to %04x\n", before, after);
// }
}
bool WozFile::getBit(std::uint8_t currentQuarterTrack) {
if (!isLoaded()) {
printf("\nNO DISK TO READ FROM (will generate random data)\n");
return false; // there's no disk, so no pulse
}
if (this->tmap[currentQuarterTrack] == 0xFFu) {
// printf("\nreading (random) from empty q-track: %d\n", currentQuarterTrack);
return false; // track doesn't exist
}
if (this->c_trks <= this->tmap[currentQuarterTrack]) { // shouldn't happen
printf("\nBAD TRACK quarterTrack %d mapped to TRKS index %d (count of tracks: %d)\n", currentQuarterTrack, this->tmap[currentQuarterTrack], this->c_trks);
return false; // track doesn't exist
}
// if (!(this->byt % 128) && this->bit == 0x01) {
// printf("\ngetBit--> ");
// }
// printf("%02x", this->byt*8+bc(this->bit));
return this->trks[this->tmap[currentQuarterTrack]][this->byt] & this->bit;
}
void WozFile::setBit(std::uint8_t currentQuarterTrack) {
if (!isLoaded()) {
return; // there's no disk to write data to
}
if (!this->writable) {
return; // write-protected
}
if (this->c_trks <= this->tmap[currentQuarterTrack]) { // shouldn't happen
return; // TODO track doesn't exist: create a new one
}
if (this->tmap[currentQuarterTrack] == 0xFFu) {
// track does not exist, create new one
}
// TODO extend track length if needed
this->trks[this->tmap[currentQuarterTrack]][this->byt] |= this->bit;
}

View File

@ -21,22 +21,46 @@
#define WOZFILE_H
#include <string>
#include <cstdint>
/**
* @brief WOZ file (floppy disk image)
* Represents a floppy disk. We only handle 5.25" disks.
* The disk has 141 quarter-track possible positions, but
* typically will have 35 tracks.
* The floppy disk "knows" it's rotational position.
*
* Note, the floppy has no notion of the current track;
* rather, that information is known by the stepper motor and arm.
*/
class WozFile {
std::string fileName;
std::string filePath;
bool writable;
bool loaded;
// TODO add bit position:
unsigned int byt; // represents rotational position of disk
bool modified;
std::uint8_t tmap[141]; // quarter-tracks from 0 through 35, values are indexes into trks
// represents (negation of) write-protect tab
// We consider the disk to be write-protected if the user does
// not have write access to the file OR the WOZ file INFO chuck
// indicates that the floppy was write-protected.
bool writable;
std::uint8_t tmap[141]; // quarter-tracks from T0 through T35, values are indexes into trks
std::uint8_t c_trks; // count of actual tracks:
std::uint8_t trks[141][6646]; // 141 is theoretical max; will always be less
std::uint16_t trk_bits[141]; // count of bits in each track
void nextByte();
// bit and byt together represent the rotational position
// of the floppy disk.
// bit is a mask indicating current bit within current byte (byt).
// WOZ file bits are packed into bytes starting at bit 7
// through bit 0 of the first byte, then bits 7-0 of the
// next byte, etc., from bits 0 through trk_bits-1
std::uint8_t bit;
std::uint16_t byt;
std::uint8_t lastQuarterTrack;
void checkForWriteProtection();
public:
@ -62,6 +86,10 @@ public:
bool isModified() {
return this->modified;
}
void rotateOneBit(std::uint8_t currentQuarterTrack);
bool getBit(std::uint8_t currentQuarterTrack);
void setBit(std::uint8_t currentQuarterTrack);
};
#endif // WOZFILE_H