From 2c4cfc1f6a2aef6968ab90b70c7c8b0983b8cd18 Mon Sep 17 00:00:00 2001 From: Marta Sevillano Date: Sat, 13 Apr 2024 19:35:51 +0200 Subject: [PATCH] Cleanup ULA video signal generation The new implementations uses the ULA fixed screen drawing order to avoid using a bunch of if-else if-else constructions. --- source/src/ULA.cc | 144 ++++++++++++++++++---------------------------- source/src/ULA.h | 42 ++++++++++---- source/src/Z80.cc | 3 + 3 files changed, 91 insertions(+), 98 deletions(-) diff --git a/source/src/ULA.cc b/source/src/ULA.cc index 9ec9a7d..0a31809 100644 --- a/source/src/ULA.cc +++ b/source/src/ULA.cc @@ -15,17 +15,11 @@ #include "ULA.h" +#include #include using namespace std; -float ULA::voltages[4][4] = { - {0.391, 0.728, 3.653, 3.790}, // ULA 5C (Issue 2) - {0.342, 0.652, 3.591, 3.753}, // ULA 6C (Issue 3) - {0.342, 0.652, 3.591, 3.753}, // ULA 7C (128K) - {0.342, 0.652, 3.591, 3.753} // GA40085 (+2A/+3) -}; - bool ULA::delayTable[16] = { false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, false @@ -105,42 +99,50 @@ uint32_t ULA::average(uint32_t *ptr) { void ULA::generateVideoControlSignals() { - if (pixel == videoStart) { - video = (scan < vBorderStart); - } else if (pixel == hBorderStart) { - border = true; - } else if (pixel == videoEnd) { - video = false; - } else if (pixel == hBlankStart) { - blanking = true; - ++scan; - - if (scan == vSyncEnd) { - vSync = true; - frame = 1 - frame; - yPos = 0; - } else if (scan == vBlankEnd) { // As long as vSyncEnd != vBlankEnd - flash += 0x08; - } else if (scan == maxScan) { - scan = 0; - } - } else if (pixel == hSyncEnd) { - xPos = 0; - } else if (pixel == hBlankEnd) { - blanking = (scan >= vBlankStart) && (scan <= vBlankEnd); - if (blanking == false) { - yPos += yInc; + if (pixel == checkPointValues[ulaVersion][checkPoint]) { + // I've simplified the if-else if-else tree using this block because + // these stages always happen in the same order. + switch (checkPoint) { + case 0: // On VideoStart + video = (scan < vBorderStart); + break; + case 1: // On HBorderStart + border = true; + break; + case 2: // On VideoEnd + video = false; + break; + case 3: // On HBlankStart + blanking = true; + ++scan; + if (scan == vSyncEnd) { + vSync = true; + frame = 1 - frame; + yPos = 0; + } else if (scan == vBlankEnd) { + flash += 0x8; + } else if (scan == maxScan) { + scan = 0; + } + break; + case 4: // On HBlankEnd + xPos = 0; + blanking = (scan >= vBlankStart) && (scan <= vBlankEnd); + if (!blanking) { + yPos += yInc; + } + break; + case 5: // On MaxPixel + pixel = 0; + border = (scan >= vBorderStart); + ulaReset = false; + dataAddr = ((scan & 0x38) << 2) | ((scan & 0x07) << 8) | ((scan & 0xC0) << 5); // ...76210 543xxxxx + attrAddr = ((scan & 0xF8) << 2) | 0x1800; // ...HHL76 543xxxxx + break; + default: assert(false); break; // Should not happen } - } else if (pixel == maxPixel) { - pixel = 0; - border = (scan >= vBorderStart); - ulaReset = false; - dataAddr = ((scan & 0x38) << 2) // 00076210 54376540 - | ((scan & 0x07) << 8) - | ((scan & 0xC0) << 5); - - attrAddr = ((scan & 0xF8) << 2) // 00000076 54376540 - | 0x1800; + + checkPoint = (checkPoint + 1) % NUM_CHECKPOINTS; } } @@ -307,7 +309,7 @@ void ULA::tapeEarMic() { // These operations are too costly to do them every cycle. static uint_fast32_t count = 0; - if (ulaVersion < 3 && !(++count & 0x3F)) { + if (ulaVersion < ULA_PLUS2 && !(++count & 0x3F)) { vInc *= 0.954949; vCap = vEnd - vInc; ear = vCap; @@ -336,10 +338,10 @@ void ULA::ioWrite(uint_fast8_t byte) { soundBits = (byte & 0x18) >> 3; borderAttr = byte & 0x07; - if (ulaVersion < 3) { - vEnd = voltage[soundBits]; + if (ulaVersion < ULA_PLUS2) { + vEnd = voltages[ulaVersion][soundBits]; vInc = vEnd - vCap; - } else if (ulaVersion == 5 && !video) { + } else if (ulaVersion == ULA_PENTAGON && !video) { colour[1] = colourTable[0x80 | borderAttr]; } } @@ -413,6 +415,7 @@ void ULA::start() { video = true; border = false; z80_c_2 = z80_c_1 = 0xFFFF; + checkPoint = 0; } void ULA::reset() { @@ -426,13 +429,6 @@ void ULA::setUlaVersion(uint_fast8_t version) { paintPixel = 0x04; micMask = 0x03; - videoStart = 0x008; - videoEnd = 0x108; - hBlankStart = 0x140; - hBlankEnd = 0x19F; - - hBorderStart = 0x100; - vBorderStart = 0x0C0; vBlankStart = 0x0F8; vBlankEnd = 0x0FF; @@ -442,41 +438,26 @@ void ULA::setUlaVersion(uint_fast8_t version) { generateVideoData = &ULA::generateVideoDataUla; switch (ulaVersion) { - case 0: // 48K, Issue 2 - hSyncEnd = 0x170; - maxPixel = 0x1C0; - interruptStart = 0x000; - interruptEnd = 0x040; - maxScan = 0x138; - break; - case 1: // 48K, Issue 3 - hSyncEnd = 0x178; - maxPixel = 0x1C0; + case ULA_48KISS2: // Spectrum 48K (Ferranti 5C/6C) + case ULA_48KISS3: // fall-through interruptStart = 0x000; interruptEnd = 0x040; maxScan = 0x138; break; - case 2: // 128K - hSyncEnd = 0x178; - maxPixel = 0x1C8; + case ULA_128K: // Spectrum 128K (Ferranti 7K) interruptStart = 0x004; interruptEnd = 0x04A; maxScan = 0x137; micMask = 0x01; break; - case 3: // +2 (128K with late timings) - hSyncEnd = 0x178; - maxPixel = 0x1C8; + case ULA_PLUS2: // Spectrum +2 (Amstrad 40056) interruptStart = 0x002; interruptEnd = 0x04A; maxScan = 0x137; micMask = 0x01; break; - case 4: // +2A, +3 + case ULA_PLUS3: // Spectrum +2A/+2B/+3 (Amstrad 40077) paintPixel = 0x06; - hBorderStart = 0x104; - hSyncEnd = 0x178; - maxPixel = 0x1C8; interruptStart = 0x000; interruptEnd = 0x040; maxScan = 0x137; @@ -486,12 +467,8 @@ void ULA::setUlaVersion(uint_fast8_t version) { idle = true; generateVideoData = &ULA::generateVideoDataGa; break; - case 5: // Pentagon + case ULA_PENTAGON: // Pentagon video circuitry paintPixel = 0x02; - hSyncEnd = 0x158; - hBlankStart = 0x138; - hBlankEnd = 0x198; - maxPixel = 0x1C0; vBlankStart = 0x0F0; vBlankEnd = 0x100; vSyncStart = 0x0F0; @@ -505,9 +482,6 @@ void ULA::setUlaVersion(uint_fast8_t version) { generateVideoData = &ULA::generateVideoDataPentagon; break; default: - hBorderStart = 0x101; - hSyncEnd = 0x178; - maxPixel = 0x1C0; interruptStart = 0x000; interruptEnd = 0x040; maxScan = 0x138; @@ -541,12 +515,12 @@ void ULA::setUlaVersion(uint_fast8_t version) { }; for (uint_fast8_t ii = 0; ii < 16; ++ii) { - if (ulaVersion == 4) { + if (ulaVersion == ULA_PLUS3) { // +2A/+3 has no floating bus. // idleTable is not necessary for +2A/+3; idle = true always. delayTable[ii] = delayGa[ii]; memTable[ii] = memGa[ii]; - } else if (ulaVersion == 5) { + } else if (ulaVersion == ULA_PENTAGON) { // Pentagon has neither floating bus, nor contention. // idleTable is not necessary for Pentagon; idle = true always, // and delay is irrelevant. @@ -557,9 +531,5 @@ void ULA::setUlaVersion(uint_fast8_t version) { memTable[ii] = memUla[ii]; } } - - for (uint_fast32_t ii = 0; ii < 4; ++ii) { - voltage[ii] = voltages[ulaVersion][ii]; - } } // vim: et:sw=4:ts=4: diff --git a/source/src/ULA.h b/source/src/ULA.h index 8e21e80..4030387 100644 --- a/source/src/ULA.h +++ b/source/src/ULA.h @@ -35,6 +35,16 @@ uint_fast32_t constexpr DUPL = 2; uint_fast32_t constexpr SNOW = 1; uint_fast32_t constexpr NONE = 0; +uint_fast32_t constexpr ULA_48KISS2 = 0; +uint_fast32_t constexpr ULA_48KISS3 = 1; +uint_fast32_t constexpr ULA_128K = 2; +uint_fast32_t constexpr ULA_PLUS2 = 3; +uint_fast32_t constexpr ULA_PLUS3 = 4; +uint_fast32_t constexpr ULA_PENTAGON = 5; +uint_fast32_t constexpr NUM_MODELS = 6; + +uint_fast32_t constexpr NUM_CHECKPOINTS = 6; + class ULA { public: @@ -62,14 +72,6 @@ class ULA { uint32_t average(uint32_t *ptr); - uint_fast16_t videoStart = 0x008; - uint_fast16_t videoEnd = 0x108; - uint_fast16_t hBorderStart = 0x100; - uint_fast16_t hBlankStart = 0x140; - uint_fast16_t hBlankEnd = 0x19F; - uint_fast16_t hSyncEnd = 0x178; - uint_fast16_t maxPixel = 0x1C0; - uint_fast16_t vBorderStart = 0x0C0; uint_fast16_t vBlankStart = 0x0F8; uint_fast16_t vBlankEnd = 0x0FF; @@ -79,8 +81,15 @@ class ULA { uint_fast16_t paintPixel = 0x004; - float voltage[4]; - static float voltages[4][4]; + static float constexpr voltages[6][4] = { + {0.391, 0.728, 3.653, 3.790}, // ULA 5C (Issue 2) + {0.342, 0.652, 3.591, 3.753}, // ULA 6C (Issue 3) + {0.342, 0.652, 3.591, 3.753}, // ULA 7K (128K) + {0.342, 0.652, 3.591, 3.753}, // GA 40056 (Plus2) + {0.342, 0.652, 3.591, 3.753}, // GA 40077 (Plus2A/Plus2B/Plus3) + {0.342, 0.652, 3.591, 3.753} // Pentagon + }; + float vEnd = 0.0; float vInc = 0.0; float vCap = 0.0; @@ -103,9 +112,20 @@ class ULA { uint_fast32_t frame = 0; // These values depend on the model - uint_fast8_t ulaVersion = 1; + uint_fast8_t ulaVersion = 0; void (ULA::*generateVideoData)() = &ULA::generateVideoDataUla; + uint_fast16_t checkPoint = 0; + static uint_fast16_t constexpr checkPointValues[NUM_MODELS][6] { + // videoStart, hBorderStart, videoEnd, hBlankStart, hBlankEnd, maxPixel + { 0x008, 0x100, 0x108, 0x140, 0x19F, 0x1C0 }, // 48K Issue 2 + { 0x008, 0x100, 0x108, 0x140, 0x19F, 0x1C0 }, // 48K Issue 3 + { 0x008, 0x100, 0x108, 0x140, 0x19F, 0x1C8 }, // 128K Toastrack + { 0x008, 0x100, 0x108, 0x140, 0x19F, 0x1C8 }, // Plus2 + { 0x008, 0x104, 0x108, 0x140, 0x19F, 0x1C8 }, // Plus2A/Plus3 + { 0x008, 0x100, 0x108, 0x138, 0x198, 0x1C0 } // Pentagon + }; + // ULA internals uint_fast16_t pixel = 0; uint_fast16_t scan = 0; diff --git a/source/src/Z80.cc b/source/src/Z80.cc index 3860624..d2af263 100644 --- a/source/src/Z80.cc +++ b/source/src/Z80.cc @@ -50,6 +50,9 @@ void Z80::clock() { iff &= ~IFF1; } + // INT is level-triggered. + // If INT is low at the beginning of the last T-state, an interrupt + // will be accepted. intAccept = !(c_d & SIGNAL_INT_); c_d = c;