Skip to content

Commit

Permalink
Time travel - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cgreening committed Nov 25, 2024
1 parent eae3a62 commit f8ec4ae
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 39 deletions.
2 changes: 1 addition & 1 deletion firmware/src/AudioOutput/BuzzerOuput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ bool IRAM_ATTR BuzzerOutput::onTimer()
uint16_t sample = mBuffer[mCurrentIndex];
if (micValue)
{
sample = std::min(255, sample + 5);
sample = std::min(255, sample + 50);
}
// go up to 9 bits resolution
sample = sample << 1;
Expand Down
4 changes: 2 additions & 2 deletions firmware/src/AudioOutput/BuzzerOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class BuzzerOutput : public AudioOutput
BuzzerOutput(gpio_num_t buzzerPin) : AudioOutput()
{
// set up the ADC
// adc1_config_width(ADC_WIDTH_BIT_12);
// adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
mBuzzerPin = buzzerPin;
mBufferSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(mBufferSemaphore);
Expand Down
28 changes: 14 additions & 14 deletions firmware/src/Emulator/snaps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ bool loadZ80Version2or3(ZXSpectrum *speccy, uint8_t *buffer, int version, FILE *
int dataOffset = 30 + 2 + buffer[30];
fseek(fp, dataOffset, SEEK_SET);

uint8_t *pageMap[16] = {0};
MemoryPage *pageMap[16] = {0};
if (hwmodel == SPECMDL_48K)
{
pageMap[4] = speccy->mem.banks[2];
Expand Down Expand Up @@ -335,7 +335,7 @@ bool loadZ80Version2or3(ZXSpectrum *speccy, uint8_t *buffer, int version, FILE *
fread(pageMap[page], actualLength, 1, fp);
} else {
printf("Decompressing page %d\n", page);
decompressZ80BlockV2orV3(fp, length, pageMap[page], 0x4000);
decompressZ80BlockV2orV3(fp, length, pageMap[page]->data, 0x4000);
}
}
speccy->z80Regs->PC.B.l = buffer[32];
Expand Down Expand Up @@ -461,20 +461,20 @@ bool saveZ80(ZXSpectrum *speccy, const char *filename)
// Map pages based on 48K or 128K model
if (speccy->hwopt.hw_model == SPECMDL_48K)
{
pageMap[4] = speccy->mem.banks[2];
pageMap[5] = speccy->mem.banks[0];
pageMap[8] = speccy->mem.banks[5];
pageMap[4] = speccy->mem.banks[2]->data;
pageMap[5] = speccy->mem.banks[0]->data;
pageMap[8] = speccy->mem.banks[5]->data;
}
else if (speccy->hwopt.hw_model == SPECMDL_128K)
{
pageMap[3] = speccy->mem.banks[0];
pageMap[4] = speccy->mem.banks[1];
pageMap[5] = speccy->mem.banks[2];
pageMap[6] = speccy->mem.banks[3];
pageMap[7] = speccy->mem.banks[4];
pageMap[8] = speccy->mem.banks[5];
pageMap[9] = speccy->mem.banks[6];
pageMap[10] = speccy->mem.banks[7];
pageMap[3] = speccy->mem.banks[0]->data;
pageMap[4] = speccy->mem.banks[1]->data;
pageMap[5] = speccy->mem.banks[2]->data;
pageMap[6] = speccy->mem.banks[3]->data;
pageMap[7] = speccy->mem.banks[4]->data;
pageMap[8] = speccy->mem.banks[5]->data;
pageMap[9] = speccy->mem.banks[6]->data;
pageMap[10] = speccy->mem.banks[7]->data;
}

for (int page = 0; page < 16; ++page)
Expand Down Expand Up @@ -604,7 +604,7 @@ bool LoadSNA(ZXSpectrum *speccy, const char *filename)
}
else
{
fread(speccy->mem.banks[i], 0x4000, 1, fp);
fread(speccy->mem.banks[i]->data, 0x4000, 1, fp);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion firmware/src/Emulator/spectrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ void ZXSpectrum::reset()
int ZXSpectrum::runForFrame(AudioOutput *audioOutput, FILE *audioFile)
{
uint8_t audioBuffer[312];
uint8_t *attrBase = mem.currentScreen + 0x1800;
uint8_t *attrBase = mem.currentScreen->data + 0x1800;
int c = 0;
// Each line should be 224 tstates long...
// And a complete frame is (64+192+56)*224=69888 tstates long
Expand Down
37 changes: 24 additions & 13 deletions firmware/src/Emulator/spectrum.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,39 @@ typedef struct
uint8_t SoundBits;
} tipo_hwopt;

class MemoryPage {
public:
bool isDirty;
uint8_t *data;
MemoryPage() {
isDirty = false;
data = (uint8_t *) malloc(0x4000);
memset(data, 0, 0x4000);
}
};

class Memory {
public:
uint8_t hwBank = 0;
uint8_t *rom[2];
uint8_t *banks[8];
uint8_t *currentScreen;
uint8_t *mappedMemory[4];
MemoryPage *rom[2];
MemoryPage *banks[8];
// track is a memory bank is dirty
MemoryPage *currentScreen;
MemoryPage *mappedMemory[4];
Memory() {
// allocate space for the rom
for (int i = 0; i < 2; i++) {
rom[i] = (uint8_t *)malloc(0x4000);
if (rom[i] == 0) {
rom[i] = new MemoryPage();
if (rom[i] == nullptr || rom[i]->data == nullptr) {
printf("Failed to allocate ROM");
}
memset(rom[i], 0, 0x4000);
}
// allocate space for the memory banks
for (int i = 0; i < 8; i++) {
banks[i] = (uint8_t *)malloc(0x4000);
if (banks[i] == 0) {
banks[i] = new MemoryPage();
if (banks[i] == nullptr || banks[i]->data == nullptr) {
printf("Failed to allocate RAM");
}
memset(banks[i], 0, 0x4000);
}
// wire up the default memory configuration - this will work for the 48k model and is the default for the 128k model
mappedMemory[0] = rom[0];
Expand All @@ -105,23 +115,24 @@ class Memory {
inline uint8_t peek(int address) {
int memoryBank = address >> 14;
int bankAddress = address & 0x3fff;
return mappedMemory[memoryBank][bankAddress];
return mappedMemory[memoryBank]->data[bankAddress];
}
inline void poke(int address, uint8_t value) {
int memoryBank = address >> 14;
int bankAddress = address & 0x3fff;
if (memoryBank == 0) {
// ignore writes to rom
} else {
mappedMemory[memoryBank][bankAddress] = value;
mappedMemory[memoryBank]->data[bankAddress] = value;
mappedMemory[memoryBank]->isDirty = true;
}
}
void loadRom(const uint8_t *rom_data, int rom_len) {
printf("Loading ROM %d\n", rom_len);
int romCount = rom_len / 0x4000;
for (int i = 0; i < romCount; i++) {
printf("Copying ROM %d\n", i);
memcpy(rom[i], rom_data + (i * 0x4000), 0x4000);
memcpy(rom[i]->data, rom_data + (i * 0x4000), 0x4000);
}
}
};
Expand Down
9 changes: 5 additions & 4 deletions firmware/src/Emulator/z80/z80.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
#include "tables.h"
#include "z80.h"

#define Z80ReadMem(where) (mappedMemory[(where) >> 14][(where) & 0x3FFF])
#define Z80ReadMem(where) (mappedMemory[(where) >> 14]->data[(where) & 0x3FFF])
#define Z80WriteMem(where, A, regs) ({ \
int memoryBank = (where) >> 14; \
if (memoryBank != 0) \
{ \
mappedMemory[memoryBank][(where) & 0x3fff] = A; \
mappedMemory[memoryBank]->data[(where) & 0x3fff] = A; \
mappedMemory[memoryBank]->isDirty = true; \
} \
})
#define Z80InPort(regs, port) (spectrum->z80_in(port))
Expand Down Expand Up @@ -118,7 +119,7 @@ uint16_t Z80Run(Z80Regs *regs, int numcycles)
{
ZXSpectrum *spectrum = ((ZXSpectrum *)regs->userInfo);
Memory &memory = spectrum->mem;
uint8_t **mappedMemory = memory.mappedMemory;
MemoryPage **mappedMemory = memory.mappedMemory;
/* opcode and temp variables */
byte opcode;
eword tmpreg, ops, mread, tmpreg2;
Expand Down Expand Up @@ -194,7 +195,7 @@ void Z80Interrupt(Z80Regs *regs, uint16_t ivec)
{
ZXSpectrum *spectrum = ((ZXSpectrum *)regs->userInfo);
Memory &memory = spectrum->mem;
uint8_t **mappedMemory = memory.mappedMemory;
MemoryPage **mappedMemory = memory.mappedMemory;
uint16_t intaddress;

/* unhalt the computer */
Expand Down
2 changes: 1 addition & 1 deletion firmware/src/Screens/EmulatorScreen/GameLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void GameLoader::loadTape(std::string filename)
count++;
if (borderPos == 312) {
borderPos = 0;
renderer->triggerDraw(machine->getMachine()->mem.currentScreen, currentBorderColors);
renderer->triggerDraw(machine->getMachine()->mem.currentScreen->data, currentBorderColors);
}
if (count % 4000 == 0) {
float machineTime = (float) listener->getTotalTicks() / 3500000.0f;
Expand Down
11 changes: 8 additions & 3 deletions firmware/src/Screens/EmulatorScreen/Machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ void Machine::runEmulator() {
if (isRunning)
{
cycleCount += machine->runForFrame(audioOutput, audioFile);
renderer->triggerDraw(machine->mem.currentScreen, machine->borderColors);
renderer->triggerDraw(machine->mem.currentScreen->data, machine->borderColors);
unsigned long currentTime = millis();
unsigned long elapsed = currentTime - lastTime;
if (elapsed > 1000)
Expand All @@ -26,6 +26,10 @@ void Machine::runEmulator() {
Serial.printf("Executed at %.3FMHz cycles, frame rate=%.2f\n", cycles, fps);
renderer->resetFrameCount();
cycleCount = 0;
// save the state of the machine for time travel
timeTravel->record(machine);
Serial.printf("Free heap: %d\n", ESP.getFreeHeap());
Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram());
}
if (machine->romLoadingRoutineHit)
{
Expand All @@ -44,6 +48,7 @@ Machine::Machine(Renderer *renderer, AudioOutput *audioOutput, std::function<voi
: renderer(renderer), audioOutput(audioOutput), romLoadingRoutineHitCallback(romLoadingRoutineHitCallback) {
Serial.println("Creating machine");
machine = new ZXSpectrum();
timeTravel = new TimeTravel();
}

void Machine::updatekey(SpecKeys key, uint8_t state) {
Expand Down Expand Up @@ -87,7 +92,7 @@ void Machine::startLoading()
{
machine->runForFrame(nullptr, nullptr);
}
renderer->triggerDraw(machine->mem.currentScreen, machine->borderColors);
renderer->triggerDraw(machine->mem.currentScreen->data, machine->borderColors);
// TODO load screenshot...
if (machine->hwopt.hw_model == SPECMDL_48K)
{
Expand All @@ -103,5 +108,5 @@ void Machine::startLoading()
// 128K the tape loader is first in the menu
tapKey(SPECKEY_ENTER);
}
renderer->triggerDraw(machine->mem.currentScreen, machine->borderColors);
renderer->triggerDraw(machine->mem.currentScreen->data, machine->borderColors);
}
124 changes: 124 additions & 0 deletions firmware/src/Screens/EmulatorScreen/Machine.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,136 @@

#include "stdio.h"
#include <functional>
#include <list>
#include <vector>
#include <deque>
#include "../../Emulator/spectrum.h"
#include "../../Serial.h"

void runnerTask(void *pvParameter);

class Renderer;
class ZXSpectrum;
class AudioOutput;

// handles the time travel functionality - this captures the state of the machine at a point in time
struct MemoryBank {
// the index
uint8_t index = 0;
// the memory bank
uint8_t *data = nullptr;
};

struct TimeTravelInstant {
std::vector<MemoryBank *> memoryBanks;
Z80Regs z80Regs;
uint8_t borderColors[32] = {0};
};

class TimeTravel {
private:
// list of time travel instants that have been recorded
std::deque<TimeTravelInstant *> timeTravelInstants;
// pool of memory banks that we can reuse
std::list<MemoryBank *> memoryBanks;
bool ensureMemoryBanks(size_t required) {
while(memoryBanks.size() < required && timeTravelInstants.size() > 0) {
TimeTravelInstant *oldestInstant = timeTravelInstants.front();
timeTravelInstants.pop_front();
for (MemoryBank *memoryBank : oldestInstant->memoryBanks) {
memoryBanks.push_back(memoryBank);
}
delete oldestInstant;
}
return memoryBanks.size() >= required;
}
public:
TimeTravel() {
// allocate some memory banks
for (int i = 0; i < 240; i++) {
MemoryBank *memoryBank = new MemoryBank();
if (!memoryBank) {
Serial.println("Could not allocate memory bank");
return;
}
memoryBank->data = (uint8_t *) ps_malloc(0x4000);
if (!memoryBank->data) {
Serial.println("Could not allocate memory bank data");
return;
}
memoryBanks.push_back(memoryBank);
}
}
size_t size() {
return timeTravelInstants.size();
}
// record the current state of the machine
bool record(ZXSpectrum *machine) {
// how many memory banks do we need?
int memoryBankCount = 0;
for(int i = 0; i<8; i++) {
if (machine->mem.banks[i]->isDirty) {
memoryBankCount++;
}
}
ensureMemoryBanks(memoryBankCount);
Serial.printf("Saving %d memory banks\n", memoryBankCount);
// create a new time travel instant
TimeTravelInstant *instant = new TimeTravelInstant();
// copy the memory banks
for(int i = 0; i<8; i++) {
if (machine->mem.banks[i]->isDirty) {
MemoryBank *memoryBank = memoryBanks.front();
memoryBanks.pop_front();
memoryBank->index = i;
memcpy(memoryBank->data, machine->mem.banks[i]->data, 0x4000);
machine->mem.banks[i]->isDirty = false;
instant->memoryBanks.push_back(memoryBank);
}
}
// copy the z80 registers
memcpy(&instant->z80Regs, machine->z80Regs, sizeof(Z80Regs));
// copy the border colors
memcpy(instant->borderColors, machine->borderColors, 32);
// add the instant to the list
timeTravelInstants.push_back(instant);
// if we have more than 30 seconds of time travel, remove the oldest instant
if (timeTravelInstants.size() > 30) {
TimeTravelInstant *oldestInstant = timeTravelInstants.front();
timeTravelInstants.pop_front();
for (MemoryBank *memoryBank : oldestInstant->memoryBanks) {
memoryBanks.push_back(memoryBank);
}
delete oldestInstant;
}
Serial.printf("Recorded time travel instant %d\n", timeTravelInstants.size());
return true;
}
// rewind the machine to a previous state
void rewind(ZXSpectrum *machine, int index) {
TimeTravelInstant *instant = timeTravelInstants[index];
// copy the memory banks
for (MemoryBank *memoryBank : instant->memoryBanks) {
memcpy(machine->mem.mappedMemory[memoryBank->index], memoryBank->data, 0x4000);
}
// copy the z80 registers
memcpy(machine->z80Regs, &instant->z80Regs, sizeof(Z80Regs));
// copy the border colors
memcpy(machine->borderColors, instant->borderColors, 32);
}
// remove everything from this point in time
void reset(int index) {
while(timeTravelInstants.size() > index) {
TimeTravelInstant *instant = timeTravelInstants.back();
timeTravelInstants.pop_back();
for (MemoryBank *memoryBank : instant->memoryBanks) {
memoryBanks.push_back(memoryBank);
}
delete instant;
}
}
};

class Machine {
private:
// the actual machine
Expand All @@ -27,6 +149,8 @@ class Machine {
FILE *audioFile = nullptr;
// keeps track of how many tstates we've run
uint32_t cycleCount = 0;
// time travel
TimeTravel *timeTravel;
// callback for when rom loading routine is hit
std::function<void()> romLoadingRoutineHitCallback;
public:
Expand Down

0 comments on commit f8ec4ae

Please sign in to comment.