diff --git a/conf/epple2.a2dos31.conf.in b/conf/epple2.a2dos31.conf.in index 5f918ad..eb413ae 100644 --- a/conf/epple2.a2dos31.conf.in +++ b/conf/epple2.a2dos31.conf.in @@ -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 diff --git a/conf/epple2.a2loaded.conf.in b/conf/epple2.a2loaded.conf.in index 1d2e828..19b5cd9 100644 --- a/conf/epple2.a2loaded.conf.in +++ b/conf/epple2.a2loaded.conf.in @@ -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 diff --git a/conf/epple2.a2ploaded.conf.in b/conf/epple2.a2ploaded.conf.in index 5f6b9da..c0cda59 100644 --- a/conf/epple2.a2ploaded.conf.in +++ b/conf/epple2.a2ploaded.conf.in @@ -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 diff --git a/conf/epple2.conf.in b/conf/epple2.conf.in index 994a5eb..3284c9d 100644 --- a/conf/epple2.conf.in +++ b/conf/epple2.conf.in @@ -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 diff --git a/doc/cards.asciidoc b/doc/cards.asciidoc index 15bc9e8..17dac85 100644 --- a/doc/cards.asciidoc +++ b/doc/cards.asciidoc @@ -20,7 +20,10 @@ import slot 6 rom 0 /usr/lib/apple2/dos3x/16sector/controller/disk2.ex65 The first line uses the <> command to insert a disk contoller card into slot 6, which is the standard -slot used for disk cards. The next line uses the <> +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 <> 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 <> 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 diff --git a/src/apple2.cpp b/src/apple2.cpp index 258e03f..a8c0104 100644 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -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(); diff --git a/src/card.cpp b/src/card.cpp index f6bd733..7a2e33a 100644 --- a/src/card.cpp +++ b/src/card.cpp @@ -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*/) diff --git a/src/card.h b/src/card.h index 014650b..33ee532 100644 --- a/src/card.h +++ b/src/card.h @@ -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; } diff --git a/src/configep2.cpp b/src/configep2.cpp index f02dc48..447a13f 100644 --- a/src/configep2.cpp +++ b/src/configep2.cpp @@ -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(); } diff --git a/src/diskbytes.cpp b/src/diskbytes.cpp index c5c17fe..6fdccf0 100644 --- a/src/diskbytes.cpp +++ b/src/diskbytes.cpp @@ -23,7 +23,8 @@ #include #include -DiskBytes::DiskBytes() +DiskBytes::DiskBytes(bool lss13): + lss13(lss13) { unload(); } diff --git a/src/diskbytes.h b/src/diskbytes.h index 6a92314..6a6b7c9 100644 --- a/src/diskbytes.h +++ b/src/diskbytes.h @@ -27,7 +27,9 @@ private: enum { TRACKS_PER_DISK = 0x23 }; enum { BYTES_PER_TRACK = 0x1A00 }; - std::vector bytes[TRACKS_PER_DISK]; + bool lss13; + + std::vector 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); diff --git a/src/diskcontroller.cpp b/src/diskcontroller.cpp index 3d69d55..198e770 100644 --- a/src/diskcontroller.cpp +++ b/src/diskcontroller.cpp @@ -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; + } } diff --git a/src/diskcontroller.h b/src/diskcontroller.h index 15fdb0b..e48e35b 100644 --- a/src/diskcontroller.h +++ b/src/diskcontroller.h @@ -17,48 +17,60 @@ */ #include "card.h" #include "drive.h" +#include "wozfile.h" +#include "lss.h" #include "screenimage.h" #include #include +#include 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() { diff --git a/src/drive.h b/src/drive.h index de3b232..80e7c5a 100644 --- a/src/drive.h +++ b/src/drive.h @@ -18,87 +18,122 @@ #ifndef DRIVE_H #define DRIVE_H +#include +#include #include - -#include "diskbytes.h" +#include +#include +#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 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\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 diff --git a/src/gui.cpp b/src/gui.cpp index d2e6c0d..d3c5f07 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -16,7 +16,7 @@ along with this program. If not, see . */ #ifdef HAVE_CONFIG_H -#include "config.h" +#include "../config.h" #endif #include diff --git a/src/lss.cpp b/src/lss.cpp index ff422b1..394323e 100644 --- a/src/lss.cpp +++ b/src/lss.cpp @@ -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); + } } } diff --git a/src/main.cpp b/src/main.cpp index f2f4a92..1ac4b04 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,7 @@ along with this program. If not, see . */ #ifdef HAVE_CONFIG_H -#include "config.h" +#include "../config.h" #endif #include @@ -30,12 +30,12 @@ #include #include +#include -static int run(const std::string& config_file) -{ +static int run(const std::string& config_file) { GUI gui; - std::auto_ptr emu(new Emulator()); + std::unique_ptr 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]; } diff --git a/src/slots.cpp b/src/slots.cpp index 86335cf..221702c 100644 --- a/src/slots.cpp +++ b/src/slots.cpp @@ -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); diff --git a/src/slots.h b/src/slots.h index aff057e..b286475 100644 --- a/src/slots.h +++ b/src/slots.h @@ -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); diff --git a/src/steppermotor.cpp b/src/steppermotor.cpp index 8f4f815..c979925 100644 --- a/src/steppermotor.cpp +++ b/src/steppermotor.cpp @@ -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 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; } diff --git a/src/steppermotor.h b/src/steppermotor.h index d746212..ee543c6 100644 --- a/src/steppermotor.h +++ b/src/steppermotor.h @@ -18,47 +18,46 @@ #ifndef STEPPERMOTOR_H #define STEPPERMOTOR_H -class StepperMotor -{ +#include + +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 diff --git a/src/wozfile.cpp b/src/wozfile.cpp index 0c2e237..08494db 100644 --- a/src/wozfile.cpp +++ b/src/wozfile.cpp @@ -23,7 +23,7 @@ #include #include -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\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; +} diff --git a/src/wozfile.h b/src/wozfile.h index 402de2c..9e43c9b 100644 --- a/src/wozfile.h +++ b/src/wozfile.h @@ -21,22 +21,46 @@ #define WOZFILE_H #include +#include +/** + * @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