diff --git a/README.md b/README.md index db1653a..cbd1dde 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is mostly an _extremely hacky_ PicoSystem emulator. ./DERP_SDL file.uf2 ``` -(To run uf2s built with the PicoSystem SDK add `--picosystem-sdk` before the file to apply some workarounds) +(To run some uf2s built with the PicoSystem SDK add `--board pimoroni_picosystem` before the file to override the board value) ## Bootrom A copy of the RP2040 bootrom is required in bootrom.bin. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5003398..afcf953 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources(DERPCore INTERFACE I2C.cpp Logging.cpp MemoryBus.cpp + PIO.cpp PWM.cpp Timer.cpp UART.cpp diff --git a/core/DMA.cpp b/core/DMA.cpp index 0ef02d3..3af4924 100644 --- a/core/DMA.cpp +++ b/core/DMA.cpp @@ -55,7 +55,10 @@ void DMA::update(uint64_t target) for(uint32_t cycle = 0; cycle < passed; cycle++) { if(!channelTriggered) + { + clock.addCycles(passed - cycle); break; + } for(int i = 0; i < numChannels; i++, curChannel++) { @@ -74,21 +77,6 @@ void DMA::update(uint64_t target) { auto val = mem.read(this, readAddr[curChannel], cycles, false); mem.write(this, writeAddr[curChannel], val, cycles, false); - - // tufty hax - if(writeAddr[curChannel] == 0x50300010 /*PIO1 TXF0*/) - { - static int off = 0; - extern uint16_t screenData[]; - - auto screenData8 = reinterpret_cast(screenData); - screenData8[off ^ 1] = val; - off++; - - if(off == 320 * 240 * 2) - off = 0; - } - } else if(transferSize == 2) { @@ -97,18 +85,6 @@ void DMA::update(uint64_t target) val = val >> 8 | val << 8; mem.write(this, writeAddr[curChannel], val, cycles, false); - - // picosystem hax (hires) - if(writeAddr[curChannel] == 0x50200010 /*PIO0 TXF0*/) - { - static int off = 0; - extern uint16_t screenData[]; - - screenData[off++] = val; - - if(off == 240 * 240) - off = 0; - } } else { @@ -117,25 +93,6 @@ void DMA::update(uint64_t target) val = val >> 24 | val << 24 | (val & 0xFF00) << 8 | (val & 0xFF0000) >> 8; mem.write(this, writeAddr[curChannel], val, cycles, false); - - // picosystem hax (lores) - if(writeAddr[curChannel] == 0x50200010 /*PIO0 TXF0*/) - { - static int off = 0; - extern uint16_t screenData[]; - - // picosystem sdk unswaps in the pio program - if(bswap) - val = val >> 16 | val << 16; - - screenData[off++] = val; - screenData[off++] = val; - screenData[off++] = val >> 16; - screenData[off++] = val >> 16; - - if(off == 240 * 240) - off = 0; - } } if(ctrl[curChannel] & DMA_CH0_CTRL_TRIG_INCR_READ_BITS) @@ -157,9 +114,9 @@ void DMA::update(uint64_t target) } break; } - } - clock.addCycles(passed); + clock.addCycles(1); + } } uint64_t DMA::getNextInterruptTime(uint64_t target) @@ -320,4 +277,9 @@ void DMA::regWrite(uint32_t addr, uint32_t data) logf(LogLevel::NotImplemented, logComponent, "W %03X%s%08X", addr, op[atomic], data); } +} + +bool DMA::isChannelActive(int ch) const +{ + return channelTriggered & (1 << ch); } \ No newline at end of file diff --git a/core/DMA.h b/core/DMA.h index 3d27875..561b23d 100644 --- a/core/DMA.h +++ b/core/DMA.h @@ -29,6 +29,8 @@ class DMA final ClockTarget &getClock() {return clock;} + bool isChannelActive(int ch) const; + private: MemoryBus &mem; diff --git a/core/GPIO.h b/core/GPIO.h index 4f5b233..a8dccc3 100644 --- a/core/GPIO.h +++ b/core/GPIO.h @@ -73,6 +73,8 @@ class GPIO final void setReadCallback(ReadCallback cb); + uint32_t getPadState() const {return padState;} + void openLogFile(const char *filename); void closeLogFile(); diff --git a/core/Logging.cpp b/core/Logging.cpp index 3d80543..5e7ea5b 100644 --- a/core/Logging.cpp +++ b/core/Logging.cpp @@ -42,6 +42,8 @@ namespace Logging return "other"; case Component::ArmCore: return "armcore"; + case Component::Board: + return "board"; case Component::Clocks: return "clocks"; case Component::DMA: @@ -56,6 +58,8 @@ namespace Logging return "main"; case Component::MemoryBus: return "membus"; + case Component::PIO: + return "pio"; case Component::PWM: return "pwm"; case Component::Timer: @@ -95,6 +99,8 @@ namespace Logging return Component::Other; if(str == "armcore") return Component::ArmCore; + if(str == "board") + return Component::Board; if(str == "clocks") return Component::Clocks; if(str == "dma") @@ -109,6 +115,8 @@ namespace Logging return Component::Main; if(str == "membus") return Component::MemoryBus; + if(str == "pio") + return Component::PIO; if(str == "pwm") return Component::PWM; if(str == "timer") diff --git a/core/Logging.h b/core/Logging.h index fdf117c..8a59b61 100644 --- a/core/Logging.h +++ b/core/Logging.h @@ -26,6 +26,7 @@ namespace Logging Other = 0, ArmCore, + Board, Clocks, DMA, GDB, @@ -33,6 +34,7 @@ namespace Logging I2C, Main, MemoryBus, + PIO, PWM, Timer, UART, diff --git a/core/MemoryBus.cpp b/core/MemoryBus.cpp index a3d97aa..6e5e6d0 100644 --- a/core/MemoryBus.cpp +++ b/core/MemoryBus.cpp @@ -123,13 +123,16 @@ static inline uint32_t getStripedSRAMAddr(uint32_t addr) return bank * 64 * 1024 + word * 4 + (addr & 3); } -MemoryBus::MemoryBus() : gpio(*this), uart{{*this, 0}, {*this, 1}}, i2c{{*this, 0}, {*this, 1}}, pwm(*this), watchdog(*this), timer(*this), dma(*this), usb(*this) +MemoryBus::MemoryBus() : gpio(*this), uart{{*this, 0}, {*this, 1}}, i2c{{*this, 0}, {*this, 1}}, pwm(*this), watchdog(*this), timer(*this), dma(*this), usb(*this), pio{{*this, 0}, {*this, 1}} { clocks.addClockTarget(clk_ref, watchdog.getClock()); - clocks.addClockTarget(clk_sys, dma.getClock()); clocks.addClockTarget(clk_sys, gpio.getClock()); clocks.addClockTarget(clk_sys, pwm.getClock()); + + clocks.addClockTarget(clk_sys, dma.getClock()); + clocks.addClockTarget(clk_sys, pio[0].getClock()); + clocks.addClockTarget(clk_sys, pio[1].getClock()); } void MemoryBus::setBootROM(const uint8_t *rom) @@ -159,6 +162,8 @@ void MemoryBus::reset() dma.reset(); usb.reset(); + pio[0].reset(); + pio[1].reset(); nextInterruptTime = 0; @@ -386,6 +391,8 @@ void MemoryBus::peripheralUpdate(uint64_t target) dma.update(target); usb.update(target); + pio[0].update(target); + pio[1].update(target); } void MemoryBus::gpioUpdate(uint64_t target) @@ -394,6 +401,11 @@ void MemoryBus::gpioUpdate(uint64_t target) // TODO: need to ensure correct ordering... pwm.update(target); + // technically affects IO, but not implemented yet + // would also need to sync DMA most of the time + //pio[0].update(target); + //pio[1].update(target); + gpio.update(target); } @@ -420,6 +432,12 @@ void MemoryBus::peripheralUpdate(uint64_t target, uint32_t irqMask, ARMv6MCore * if(irqMask & (1 << USBCTRL_IRQ)) usb.updateForInterrupts(target); + + if(irqMask & (1 << PIO0_IRQ_0 | 1 << PIO0_IRQ_1)) + pio[0].update(target); + + if(irqMask & (1 << PIO1_IRQ_0 | 1 << PIO1_IRQ_1)) + pio[1].update(target); } void MemoryBus::calcNextInterruptTime() @@ -434,6 +452,8 @@ void MemoryBus::calcNextInterruptTime() nextInterruptTime = timer.getNextInterruptTime(nextInterruptTime); nextInterruptTime = dma.getNextInterruptTime(nextInterruptTime); nextInterruptTime = usb.getNextInterruptTime(nextInterruptTime); + nextInterruptTime = pio[0].getNextInterruptTime(nextInterruptTime); + nextInterruptTime = pio[1].getNextInterruptTime(nextInterruptTime); } void MemoryBus::setInterruptUpdateCallback(InterruptUpdateCallback cb) @@ -1030,10 +1050,14 @@ uint32_t MemoryBus::doAHBPeriphRead(ClockTarget &masterClock, uint32_t addr) auto peripheral = static_cast((addr >> 20) & 0xF); auto periphAddr = addr & 0xFFFF; + // update DMA for any periph read + // TODO: any access that DMA could affect, possibly filter by dest addrs + dma.update(masterClock.getTime()); + switch(peripheral) { case AHBPeripheral::DMA: - dma.update(masterClock.getTime()); + //dma.update(masterClock.getTime()); return dma.regRead(periphAddr); case AHBPeripheral::USB: @@ -1052,25 +1076,12 @@ uint32_t MemoryBus::doAHBPeriphRead(ClockTarget &masterClock, uint32_t addr) } case AHBPeripheral::PIO0: - { - if(periphAddr == PIO_FSTAT_OFFSET) - return PIO_FSTAT_TXEMPTY_BITS | PIO_FSTAT_RXEMPTY_BITS; // all FIFOs empty - if(periphAddr == PIO_FDEBUG_OFFSET) - return PIO_FDEBUG_TXSTALL_BITS; // all TXSTALL + pio[0].update(masterClock.getTime()); + return pio[0].regRead(periphAddr); - logf(LogLevel::NotImplemented, logComponent, "PIO0 R %08X", addr); - break; - } case AHBPeripheral::PIO1: - { - if(periphAddr == PIO_FSTAT_OFFSET) - return PIO_FSTAT_TXEMPTY_BITS | PIO_FSTAT_RXEMPTY_BITS; // all FIFOs empty - if(periphAddr == PIO_FDEBUG_OFFSET) - return PIO_FDEBUG_TXSTALL_BITS; // all TXSTALL - - logf(LogLevel::NotImplemented, logComponent, "PIO1 R %08X", addr); - break; - } + pio[1].update(masterClock.getTime()); + return pio[1].regRead(periphAddr); case AHBPeripheral::XIPAux: logf(LogLevel::NotImplemented, logComponent, "AHBP XIP_AUX R %08X", addr); @@ -1143,18 +1154,14 @@ void MemoryBus::doAHBPeriphWrite(ClockTarget &masterClock, uint32_t addr, T data case AHBPeripheral::PIO0: { - if(periphAddr == PIO_TXF0_OFFSET) - {} - else - logf(LogLevel::NotImplemented, logComponent, "PIO0 W %08X = %08X", addr, data); + pio[0].update(masterClock.getTime()); + pio[0].regWrite(periphAddr, data); return; } case AHBPeripheral::PIO1: { - if(periphAddr == PIO_TXF0_OFFSET) - {} - else - logf(LogLevel::NotImplemented, logComponent, "PIO1 W %08X = %08X", addr, data); + pio[1].update(masterClock.getTime()); + pio[1].regWrite(periphAddr, data); return; } diff --git a/core/MemoryBus.h b/core/MemoryBus.h index 3f7fee1..5dbb5ce 100644 --- a/core/MemoryBus.h +++ b/core/MemoryBus.h @@ -10,6 +10,7 @@ #include "GPIO.h" #include "FIFO.h" #include "I2C.h" +#include "PIO.h" #include "PWM.h" #include "Timer.h" #include "UART.h" @@ -88,7 +89,9 @@ class MemoryBus PWM &getPWM() {return pwm;} Watchdog &getWatchdog() {return watchdog;} + DMA &getDMA() {return dma;} USB &getUSB() {return usb;} + PIO &getPIO(int i) {return pio[i];} private: template @@ -168,6 +171,8 @@ class MemoryBus USB usb; + PIO pio[2]; + uint64_t nextInterruptTime; InterruptUpdateCallback interruptUpdateCallback; diff --git a/core/PIO.cpp b/core/PIO.cpp new file mode 100644 index 0000000..0612d92 --- /dev/null +++ b/core/PIO.cpp @@ -0,0 +1,318 @@ +#include + +#include "hardware/platform_defs.h" +#include "hardware/regs/pio.h" +#include "hardware/regs/intctrl.h" + +#include "PIO.h" + +#include "MemoryBus.h" +#include "Logging.h" + +using Logging::logf; +using LogLevel = Logging::Level; +constexpr auto logComponent = Logging::Component::PIO; + +PIO::PIO(MemoryBus &mem, int index) : mem(mem), index(index) +{ +} + +void PIO::reset() +{ + hw.ctrl = PIO_CTRL_RESET; + hw.fstat = PIO_FSTAT_RESET; + hw.fdebug = PIO_FDEBUG_RESET; + hw.flevel = PIO_FLEVEL_RESET; + + hw.irq = PIO_IRQ_RESET; + + hw.input_sync_bypass = PIO_INPUT_SYNC_BYPASS_RESET; + + hw.dbg_padout = PIO_DBG_PADOUT_RESET; + hw.dbg_padoe = PIO_DBG_PADOE_RESET; + hw.dbg_cfginfo = PIO_DBG_CFGINFO_RESET; + + for(auto &sm : hw.sm) + { + sm.clkdiv = PIO_SM0_CLKDIV_RESET; + sm.execctrl = PIO_SM0_EXECCTRL_RESET; + sm.shiftctrl = PIO_SM0_SHIFTCTRL_RESET; + sm.addr = PIO_SM0_ADDR_RESET; + // sm.instr = PIO_SM0_INSTR_RESET; + sm.pinctrl = PIO_SM0_PINCTRL_RESET; + } + + hw.intr = PIO_INTR_RESET; + hw.inte0 = PIO_IRQ0_INTE_RESET; + hw.intf0 = PIO_IRQ0_INTF_RESET; + hw.inte1 = PIO_IRQ1_INTE_RESET; + hw.intf1 = PIO_IRQ1_INTF_RESET; +} + +void PIO::update(uint64_t target) +{ + auto cycles = clock.getCyclesToTime(target); + + // this is almost certainly a hack to work around timing issues + if(!cycles && updateCallback) + updateCallback(clock.getTime(), *this); + + while(cycles) + { + auto step = cycles; + + if(updateCallback) + updateCallback(clock.getTime(), *this); + else + { + // fake progress + for(unsigned i = 0; i < NUM_PIO_STATE_MACHINES; i++) + { + if(hw.ctrl & (1 << (PIO_CTRL_SM_ENABLE_LSB + i))) + { + if(!txFifo[i].empty()) + { + txFifo[i].pop(); + updateFifoStatus(); + } + } + } + } + + clock.addCycles(step); + + cycles -= step; + } +} + +void PIO::setUpdateCallback(UpdateCallback cb) +{ + updateCallback = cb; +} + +uint64_t PIO::getNextInterruptTime(uint64_t target) +{ + if(!hw.inte0 || !hw.inte1) + return target; + + return target; // TODO +} + +uint32_t PIO::regRead(uint32_t addr) +{ + switch(addr) + { + case PIO_CTRL_OFFSET: + return hw.ctrl; + + case PIO_FSTAT_OFFSET: + return hw.fstat; + case PIO_FDEBUG_OFFSET: + { + // HACK: set txstall if FIFO empty + // FIXME: this is wrong but we're not running the program + for(unsigned i = 0; i < NUM_PIO_STATE_MACHINES; i++) + { + if(txFifo[i].empty()) + hw.fdebug |= 1 << (PIO_FDEBUG_TXSTALL_LSB + i); + } + + return hw.fdebug; + } + + case PIO_SM0_CLKDIV_OFFSET: + case PIO_SM1_CLKDIV_OFFSET: + case PIO_SM2_CLKDIV_OFFSET: + case PIO_SM3_CLKDIV_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + return hw.sm[sm].clkdiv; + } + + case PIO_SM0_EXECCTRL_OFFSET: + case PIO_SM1_EXECCTRL_OFFSET: + case PIO_SM2_EXECCTRL_OFFSET: + case PIO_SM3_EXECCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + return hw.sm[sm].execctrl; + } + + case PIO_SM0_SHIFTCTRL_OFFSET: + case PIO_SM1_SHIFTCTRL_OFFSET: + case PIO_SM2_SHIFTCTRL_OFFSET: + case PIO_SM3_SHIFTCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + return hw.sm[sm].shiftctrl; + } + + case PIO_SM0_ADDR_OFFSET: + { + static int counter = 0; + return counter++ & PIO_SM0_ADDR_BITS; + } + + // INSTR + + case PIO_SM0_PINCTRL_OFFSET: + case PIO_SM1_PINCTRL_OFFSET: + case PIO_SM2_PINCTRL_OFFSET: + case PIO_SM3_PINCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + return hw.sm[sm].pinctrl; + } + } + + logf(LogLevel::NotImplemented, logComponent, "%i R %04X", index, addr); + return 0; +} + +void PIO::regWrite(uint32_t addr, uint32_t data) +{ + int atomic = addr >> 12; + addr &= 0xFFF; + + static const char *op[]{" = ", " ^= ", " |= ", " &= ~"}; + + switch(addr) + { + case PIO_CTRL_OFFSET: + { + auto oldVal = hw.ctrl; + if(updateReg(hw.ctrl, data, atomic)) + { + auto enabled = (oldVal ^ hw.ctrl) & hw.ctrl & PIO_CTRL_SM_ENABLE_BITS; + for(unsigned i = 0; i < NUM_PIO_STATE_MACHINES; i++) + { + if(enabled & (1 << (PIO_CTRL_SM_ENABLE_LSB + i))) + logf(LogLevel::Debug, logComponent, "%i SM%i enabled CLKDIV %08X EXECCTRL %08X SHIFTCTRL %08X PINCTRL %08X", index, i, hw.sm[i].clkdiv, hw.sm[i].execctrl, hw.sm[i].shiftctrl, hw.sm[i].pinctrl); + } + } + return; + } + + case PIO_FDEBUG_OFFSET: + { + if(atomic == 0) + { + hw.fdebug &= ~data; + return; + } + + break; + } + + case PIO_TXF0_OFFSET: + case PIO_TXF1_OFFSET: + case PIO_TXF2_OFFSET: + case PIO_TXF3_OFFSET: + { + int index = (addr - PIO_TXF0_OFFSET) / 4; + if(txFifo[index].full()) + hw.fdebug |= 1 << (PIO_FDEBUG_TXOVER_LSB + index); + else + { + txFifo[index].push(data); + updateFifoStatus(); + } + return; + } + + case PIO_INSTR_MEM0_OFFSET: + case PIO_INSTR_MEM1_OFFSET: + case PIO_INSTR_MEM2_OFFSET: + case PIO_INSTR_MEM3_OFFSET: + case PIO_INSTR_MEM4_OFFSET: + case PIO_INSTR_MEM5_OFFSET: + case PIO_INSTR_MEM6_OFFSET: + case PIO_INSTR_MEM7_OFFSET: + case PIO_INSTR_MEM8_OFFSET: + case PIO_INSTR_MEM9_OFFSET: + case PIO_INSTR_MEM10_OFFSET: + case PIO_INSTR_MEM11_OFFSET: + case PIO_INSTR_MEM12_OFFSET: + case PIO_INSTR_MEM13_OFFSET: + case PIO_INSTR_MEM14_OFFSET: + case PIO_INSTR_MEM15_OFFSET: + case PIO_INSTR_MEM16_OFFSET: + case PIO_INSTR_MEM17_OFFSET: + case PIO_INSTR_MEM18_OFFSET: + case PIO_INSTR_MEM19_OFFSET: + case PIO_INSTR_MEM20_OFFSET: + case PIO_INSTR_MEM21_OFFSET: + case PIO_INSTR_MEM22_OFFSET: + case PIO_INSTR_MEM23_OFFSET: + case PIO_INSTR_MEM24_OFFSET: + case PIO_INSTR_MEM25_OFFSET: + case PIO_INSTR_MEM26_OFFSET: + case PIO_INSTR_MEM27_OFFSET: + case PIO_INSTR_MEM28_OFFSET: + case PIO_INSTR_MEM29_OFFSET: + case PIO_INSTR_MEM30_OFFSET: + case PIO_INSTR_MEM31_OFFSET: + { + int off = (addr - PIO_INSTR_MEM0_OFFSET) / 4; + updateReg(hw.instr_mem[off], data, atomic); + return; + } + + case PIO_SM0_CLKDIV_OFFSET: + case PIO_SM1_CLKDIV_OFFSET: + case PIO_SM2_CLKDIV_OFFSET: + case PIO_SM3_CLKDIV_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + updateReg(hw.sm[sm].clkdiv, data, atomic); + return; + } + + case PIO_SM0_EXECCTRL_OFFSET: + case PIO_SM1_EXECCTRL_OFFSET: + case PIO_SM2_EXECCTRL_OFFSET: + case PIO_SM3_EXECCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + updateReg(hw.sm[sm].execctrl, data, atomic); + return; + } + + case PIO_SM0_SHIFTCTRL_OFFSET: + case PIO_SM1_SHIFTCTRL_OFFSET: + case PIO_SM2_SHIFTCTRL_OFFSET: + case PIO_SM3_SHIFTCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + updateReg(hw.sm[sm].shiftctrl, data, atomic); + return; + } + + // ADDR, INSTR + + case PIO_SM0_PINCTRL_OFFSET: + case PIO_SM1_PINCTRL_OFFSET: + case PIO_SM2_PINCTRL_OFFSET: + case PIO_SM3_PINCTRL_OFFSET: + { + int sm = (addr - PIO_SM0_CLKDIV_OFFSET) / (PIO_SM1_CLKDIV_OFFSET - PIO_SM0_CLKDIV_OFFSET); + updateReg(hw.sm[sm].pinctrl, data, atomic); + return; + } + } + + logf(LogLevel::NotImplemented, logComponent, "%i W %04X%s%08X", index, addr, op[atomic], data); +} + +void PIO::updateFifoStatus() +{ + hw.fstat = PIO_FSTAT_RXEMPTY_BITS; // TODO + + for(unsigned i = 0; i < NUM_PIO_STATE_MACHINES; i++) + { + if(txFifo[i].full()) + hw.fstat |= 1 << (PIO_FSTAT_TXFULL_LSB + i); + else if(txFifo[i].empty()) + hw.fstat |= 1 << (PIO_FSTAT_TXEMPTY_LSB + i); + } +} \ No newline at end of file diff --git a/core/PIO.h b/core/PIO.h new file mode 100644 index 0000000..00c0550 --- /dev/null +++ b/core/PIO.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#include "hardware/structs/pio.h" + +#include "ClockTarget.h" +#include "FIFO.h" + +class MemoryBus; + +class PIO final +{ +public: + using UpdateCallback = std::function; + + PIO(MemoryBus &mem, int index); + + void reset(); + + void update(uint64_t target); + void updateForInterrupts(uint64_t target) + { + if(hw.inte0 || hw.inte1) + update(target); + } + + void setUpdateCallback(UpdateCallback cb); + + uint64_t getNextInterruptTime(uint64_t target); + + uint32_t regRead(uint32_t addr); + void regWrite(uint32_t addr, uint32_t data); + + ClockTarget &getClock() {return clock;} + + const pio_hw_t &getHW(){return hw;} + FIFO &getTXFIFO(int i) {return txFifo[i];} + + void updateFifoStatus(); + +private: + MemoryBus &mem; + int index; + + ClockTarget clock; + + pio_hw_t hw; + + UpdateCallback updateCallback; + + // TODO: rx, joined + FIFO txFifo[NUM_PIO_STATE_MACHINES]; +}; diff --git a/minsdl/CMakeLists.txt b/minsdl/CMakeLists.txt index 11db334..3332984 100644 --- a/minsdl/CMakeLists.txt +++ b/minsdl/CMakeLists.txt @@ -1,6 +1,10 @@ # minimal SDL shell -add_executable(DERP_SDL Main.cpp) +add_executable(DERP_SDL + Main.cpp + board/PicoSystem.cpp + board/Tufty2040.cpp +) find_package(SDL2 REQUIRED) diff --git a/minsdl/Main.cpp b/minsdl/Main.cpp index 32fcae9..979e6e0 100644 --- a/minsdl/Main.cpp +++ b/minsdl/Main.cpp @@ -10,11 +10,15 @@ #include "GDBServer.h" #include "Logging.h" +#include "board/Pico.h" +#include "board/PicoSystem.h" +#include "board/Tufty2040.h" + using Logging::logf; using LogLevel = Logging::Level; constexpr auto logComponent = Logging::Component::Main; -enum class Board +enum class BoardId { Unknown = -1, Pico = 0, @@ -33,19 +37,13 @@ static uint8_t bootROM[0x4000]; static std::ifstream uf2File; -static Board board = Board::Unknown; - -uint16_t screenData[320 * 240]; - -static uint32_t buttonState = 0; -static unsigned int displayScanline = 0; -static ClockTarget displayClock; +static BoardId boardId = BoardId::Unknown; +static Board *board = nullptr; -static ClockTarget audioClock; -static bool lastAudioVal = false; -static const int audioBufferSize = 1024; -static volatile int audioReadOff = 0, audioWriteOff = 0; -static int16_t audioSamples[audioBufferSize]{}; +static SDL_Renderer *renderer = nullptr; +static SDL_Texture *texture = nullptr; +static int screenWidth = 240; +static int screenHeight = 240; static const uint32_t uf2MagicStart0 = 0x0A324655, uf2MagicStart1 = 0x9E5D5157, uf2MagicEnd = 0x0AB16F30; @@ -63,50 +61,20 @@ struct UF2Block }; static_assert(sizeof(UF2Block) == 512); -static const std::unordered_map picosystemKeyMap { - {SDLK_RIGHT, 1 << 21}, - {SDLK_LEFT, 1 << 22}, - {SDLK_UP, 1 << 23}, - {SDLK_DOWN, 1 << 20}, - {SDLK_z, 1 << 18}, - {SDLK_x, 1 << 19}, - {SDLK_c, 1 << 17}, - {SDLK_v, 1 << 16}, -}; - -static Board stringToBoard(std::string_view str) +static BoardId stringToBoard(std::string_view str) { if(str == "pico") - return Board::Pico; + return BoardId::Pico; if(str == "pimoroni_picosystem") - return Board::PimoroniPicoSystem; + return BoardId::PimoroniPicoSystem; if(str == "pimoroni_tufty2040") - return Board::PimoroniTufty2040; + return BoardId::PimoroniTufty2040; logf(LogLevel::Warning, logComponent, "Unknown board \"%.*s\", falling back to \"pico\"", int(str.length()), str.data()); - return Board::Pico; -} - -static void getBoardScreenSize(Board board, int &w, int &h) -{ - switch(board) - { - case Board::PimoroniPicoSystem: - w = 240; - h = 240; - break; - case Board::PimoroniTufty2040: - w = 320; - h = 240; - break; - - default: - w = 0; - h = 0; - } + return BoardId::Pico; } static bool parseUF2(std::ifstream &file) @@ -190,8 +158,8 @@ static bool parseUF2(std::ifstream &file) logf(LogLevel::Info, logComponent, "\t%s: %s", idStr->second, str); // detect board - if(id == 0xb63cffbb/*pico_board*/ && board == Board::Unknown) - board = stringToBoard(str); + if(id == 0xb63cffbb/*pico_board*/ && boardId == BoardId::Unknown) + boardId = stringToBoard(str); } } } @@ -199,101 +167,6 @@ static bool parseUF2(std::ifstream &file) return true; } -// picosystem external hardware/IO -static void displayUpdate(uint64_t time, bool forIntr = false) -{ - auto lines = displayClock.getCyclesToTime(time); - - if(!lines) - return; - - while(lines) - { - int step = std::max(1u, std::min(lines, 239 - displayScanline)); - lines -= step; - - displayClock.addCycles(step); - - // set TE when we're on the last scanline - // TODO: use STE reg - auto newLine = (displayScanline + step) % 240; - - if(newLine == 239) // now last line - { - if(forIntr) - mem.gpioUpdate(displayClock.getTime()); - mem.getGPIO().setInputMask(1 << 8); - } - else if(displayScanline == 239) // was last line - { - if(forIntr) - mem.gpioUpdate(displayClock.getTime()); - mem.getGPIO().clearInputMask(1 << 8); - } - - displayScanline = newLine; - } -} - -static int getNumAudioSamples() -{ - int avail = audioWriteOff - audioReadOff; - if(avail < 0) - avail += audioBufferSize; - - return avail; -} - -static void audioUpdate(uint64_t time) -{ - auto samples = audioClock.getCyclesToTime(time); - - for(uint32_t i = 0; i < samples; i++) - { - while((audioWriteOff + 1) % audioBufferSize == audioReadOff); - - int level = 0x1000; - audioSamples[audioWriteOff++] = lastAudioVal ? level : -level; - audioWriteOff %= audioBufferSize; - } - - audioClock.addCycles(samples); -} - -static void onGPIORead(uint64_t time, GPIO &gpio) -{ - // apply buttons - int buttonMask = 0xFF0000; - - gpio.setInputFloatingMask(~buttonState & buttonMask); // not pressed -> floating - gpio.clearInputFloatingMask(buttonState); // pressed -> pulled down - - displayUpdate(time); -} - -static void onInterruptUpdate(uint64_t time, uint32_t irqMask) -{ - if(irqMask & (1 << 13) /*IO_IRQ_BANK0*/) - displayUpdate(time, true); -} - -static uint64_t onGetNextInterruptTime(uint64_t time) -{ - if(!mem.getGPIO().interruptsEnabledOnPin(8)) - return time; - - int lines = std::max(1u, 239 - displayScanline); - auto ret = displayClock.getTimeToCycles(lines); - - return ret; -} - -static void onPWMUpdate(uint64_t time, uint16_t pwm) -{ - audioUpdate(time); - lastAudioVal = pwm & (1 << 11); -} - static void runGDBServer() { gdbServer.setCPUs(cpuCores, 2); @@ -313,10 +186,9 @@ static void audioCallback(void *userdata, Uint8 *stream, int len) auto ptr = reinterpret_cast(stream); for(int i = 0; i < len / 2; i++) { - while(!getNumAudioSamples() && !quit); + while(!board->getNumAudioSamples() && !quit); - *ptr++ = audioSamples[audioReadOff++]; - audioReadOff %= audioBufferSize; + *ptr++ = board->getAudioSample(); } } @@ -325,22 +197,10 @@ static void pollEvents() SDL_Event event; while(SDL_PollEvent(&event)) { + board->handleEvent(event); + switch(event.type) { - case SDL_KEYDOWN: - { - auto it = picosystemKeyMap.find(event.key.keysym.sym); - if(it != picosystemKeyMap.end()) - buttonState |= it->second; - break; - } - case SDL_KEYUP: - { - auto it = picosystemKeyMap.find(event.key.keysym.sym); - if(it != picosystemKeyMap.end()) - buttonState &= ~it->second; - break; - } case SDL_QUIT: quit = true; break; @@ -374,15 +234,20 @@ static void handleLogArg(const char *arg) } } +void updateScreenSettings() +{ + if(!renderer) + return; + + texture = SDL_CreateTexture(renderer, board->getScreenFormat(), SDL_TEXTUREACCESS_STREAMING, screenWidth, screenHeight); +} + int main(int argc, char *argv[]) { - int screenWidth = 240; - int screenHeight = 240; int screenScale = 5; std::thread gdbServerThread; - bool picosystemSDK = false; bool usbEnabled = false; bool gdbEnabled = false; @@ -401,9 +266,13 @@ int main(int argc, char *argv[]) if(arg == "--scale" && i + 1 < argc) screenScale = std::stoi(argv[++i]); else if(arg == "--picosystem-sdk") - picosystemSDK = true; + { + // picosystem SDK does not require the correct board to be set... so most uf2s don't + boardId = BoardId::PimoroniPicoSystem; + logf(LogLevel::Warning, logComponent, "Use --board pimoroni_picosystem instead of --picosystem-sdk"); + } else if(arg == "--board" && i + 1 < argc) - board = stringToBoard(argv[++i]); + boardId = stringToBoard(argv[++i]); else if(arg == "--usb") usbEnabled = true; else if(arg == "--usbip") @@ -444,21 +313,15 @@ int main(int argc, char *argv[]) logf(LogLevel::Error, logComponent, "Failed to open UF2 \"%s\"", romFilename.c_str()); return 1; } - - // picosystem SDK does not require the correct board to be set... so most uf2s don't - if(picosystemSDK) - board = Board::PimoroniPicoSystem; } // default board if still not set - if(board == Board::Unknown) + if(boardId == BoardId::Unknown) { logf(LogLevel::Info, logComponent, "Board not specified, falling back to \"pico\""); - board = Board::Pico; + boardId = BoardId::Pico; } - getBoardScreenSize(board, screenWidth, screenHeight); - // emu init mem.setCPUs(cpuCores); @@ -482,25 +345,23 @@ int main(int argc, char *argv[]) mem.reset(); - // external hardware - if(board == Board::PimoroniPicoSystem) + // create board + switch(boardId) { - mem.setInterruptUpdateCallback(onInterruptUpdate); - mem.setGetNextInterruptTimeCallback(onGetNextInterruptTime); - - mem.getGPIO().setReadCallback(onGPIORead); - mem.getGPIO().clearInputFloatingMask(1 << 8); // TE - - mem.getPWM().setOutputCallback(onPWMUpdate, 1 << 11); // audio + case BoardId::PimoroniPicoSystem: + board = new PicoSystemBoard(mem); + break; - const int fps = picosystemSDK ? 40 : 50; - displayClock.setFrequency(fps * 240); - clocks.addClockTarget(-1, displayClock); + case BoardId::PimoroniTufty2040: + board = new Tufty2040Board(mem); + break; - audioClock.setFrequency(48000); - clocks.addClockTarget(-1, audioClock); + default: + board = new PicoBoard(mem); } + board->getScreenSize(screenWidth, screenHeight); + if(gdbEnabled) gdbServerThread = std::thread(runGDBServer); @@ -513,8 +374,6 @@ int main(int argc, char *argv[]) bool boardHasScreen = screenWidth && screenHeight; SDL_Window *window = nullptr; - SDL_Renderer *renderer = nullptr; - SDL_Texture *texture = nullptr; if(boardHasScreen) { @@ -526,22 +385,12 @@ int main(int argc, char *argv[]) SDL_RenderSetLogicalSize(renderer, screenWidth, screenHeight); SDL_RenderSetIntegerScale(renderer, SDL_TRUE); - Uint32 format; - - // TODO: implement screen registers instead of guessing - if(picosystemSDK) - format = SDL_PIXELFORMAT_ARGB4444; - else if(board == Board::PimoroniPicoSystem) // assume 32blit-sdk - format = SDL_PIXELFORMAT_BGR565; - else - format = SDL_PIXELFORMAT_RGB565; - - texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STREAMING, screenWidth, screenHeight); + texture = SDL_CreateTexture(renderer, board->getScreenFormat(), SDL_TEXTUREACCESS_STREAMING, screenWidth, screenHeight); } SDL_AudioDeviceID audioDevice = 0; - if(board == Board::PimoroniPicoSystem) + if(board->hasAudio()) { SDL_AudioSpec spec{}; @@ -613,11 +462,7 @@ int main(int argc, char *argv[]) if(gdbEnabled) gdbServer.getCPUMutex().unlock(); - if(board == Board::PimoroniPicoSystem) - { - displayUpdate(time); - audioUpdate(time); - } + board->update(time); // attempt to connect USB if(usbEnabled) @@ -637,7 +482,7 @@ int main(int argc, char *argv[]) if(renderer) { // TODO: sync - SDL_UpdateTexture(texture, nullptr, screenData, screenWidth * 2); + SDL_UpdateTexture(texture, nullptr, board->getScreenData(), screenWidth * 2); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderPresent(renderer); @@ -654,6 +499,8 @@ int main(int argc, char *argv[]) if(audioDevice) SDL_CloseAudioDevice(audioDevice); + delete board; + if(gdbEnabled) gdbServerThread.join(); return 0; diff --git a/minsdl/board/Board.h b/minsdl/board/Board.h new file mode 100644 index 0000000..3c063bf --- /dev/null +++ b/minsdl/board/Board.h @@ -0,0 +1,23 @@ +#pragma once +#include + +union SDL_Event; +class MemoryBus; + +class Board +{ +public: + virtual ~Board() = default; + + virtual void getScreenSize(int &w, int &h) = 0; + virtual int getScreenFormat() = 0; + virtual const uint8_t *getScreenData() = 0; + + virtual bool hasAudio() = 0; + virtual int getNumAudioSamples() = 0; + virtual int16_t getAudioSample() = 0; + + virtual void handleEvent(SDL_Event &event) = 0; + virtual void update(uint64_t time) = 0; +private: +}; \ No newline at end of file diff --git a/minsdl/board/Pico.h b/minsdl/board/Pico.h new file mode 100644 index 0000000..1ca3efe --- /dev/null +++ b/minsdl/board/Pico.h @@ -0,0 +1,20 @@ +#pragma once +#include "Board.h" + +// default board, has no attached hardware +class PicoBoard final : public Board +{ +public: + PicoBoard(MemoryBus &mem){} + + void getScreenSize(int &w, int &h) override {w = h = 0;} + int getScreenFormat() override {return 0;} + const uint8_t *getScreenData() override {return nullptr;} + + bool hasAudio() override {return false;} + int getNumAudioSamples() override {return 0;} + int16_t getAudioSample() override {return 0;} + + void handleEvent(SDL_Event &event) override {} + void update(uint64_t time) override {} +}; \ No newline at end of file diff --git a/minsdl/board/PicoSystem.cpp b/minsdl/board/PicoSystem.cpp new file mode 100644 index 0000000..68890bb --- /dev/null +++ b/minsdl/board/PicoSystem.cpp @@ -0,0 +1,412 @@ +#include + +#include + +#include "PicoSystem.h" + +#include "Logging.h" +#include "MemoryBus.h" + +using Logging::logf; +using LogLevel = Logging::Level; +constexpr auto logComponent = Logging::Component::Board; + +extern void updateScreenSettings(); // TODO: declare this... somewhere + +static const std::unordered_map picosystemKeyMap { + {SDLK_RIGHT, 1 << 21}, + {SDLK_LEFT, 1 << 22}, + {SDLK_UP, 1 << 23}, + {SDLK_DOWN, 1 << 20}, + + {SDLK_z, 1 << 18}, + {SDLK_x, 1 << 19}, + {SDLK_c, 1 << 17}, + {SDLK_v, 1 << 16}, +}; + +PicoSystemBoard::PicoSystemBoard(MemoryBus &mem) : mem(mem) +{ + auto &clocks = mem.getClocks(); + + mem.setInterruptUpdateCallback([this](auto time, auto irqMask){onInterruptUpdate(time, irqMask);}); + mem.setGetNextInterruptTimeCallback([this](auto time){return onGetNextInterruptTime(time);}); + + int buttonMask = 0xFF0000; + + mem.getGPIO().setReadCallback([this](auto time, auto &gpio){onGPIORead(time, gpio);}); + mem.getGPIO().clearInputFloatingMask(1 << 8/*TE*/ | buttonMask); // buttons pulled high on board + + mem.getPIO(0).setUpdateCallback([this](auto time, auto &pio){onPIOUpdate(time, pio);}); + + mem.getPWM().setOutputCallback([this](auto time, auto pwm){onPWMUpdate(time, pwm);}, 1 << 11); // audio + + const int fps = 40; // default to picosystem sdk setting + displayClock.setFrequency(fps * 240); + clocks.addClockTarget(-1, displayClock); + + audioClock.setFrequency(48000); + clocks.addClockTarget(-1, audioClock); + + updateDisplayFormat(); +} + +void PicoSystemBoard::getScreenSize(int &w, int &h) +{ + w = 240; + h = 240; +} + +int PicoSystemBoard::getScreenFormat() +{ + return displayFormat; +} + +const uint8_t *PicoSystemBoard::getScreenData() +{ + return reinterpret_cast(screenData); +} + +bool PicoSystemBoard::hasAudio() +{ + return true; +} + +int PicoSystemBoard::getNumAudioSamples() +{ + int avail = audioWriteOff - audioReadOff; + if(avail < 0) + avail += audioBufferSize; + + return avail; +} + +int16_t PicoSystemBoard::getAudioSample() +{ + if(!getNumAudioSamples()) + return 0; + + auto val = audioSamples[audioReadOff++]; + audioReadOff %= audioBufferSize; + + return val; +} + +void PicoSystemBoard::handleEvent(SDL_Event &event) +{ + switch(event.type) + { + case SDL_KEYDOWN: + { + auto it = picosystemKeyMap.find(event.key.keysym.sym); + if(it != picosystemKeyMap.end()) + buttonState |= it->second; + break; + } + case SDL_KEYUP: + { + auto it = picosystemKeyMap.find(event.key.keysym.sym); + if(it != picosystemKeyMap.end()) + buttonState &= ~it->second; + break; + } + + } +} + +void PicoSystemBoard::update(uint64_t time) +{ + displayUpdate(time); + audioUpdate(time); +} + +void PicoSystemBoard::displayUpdate(uint64_t time, bool forIntr) +{ + auto lines = displayClock.getCyclesToTime(time); + + if(!lines) + return; + + while(lines) + { + int step = std::max(1u, std::min(lines, 239 - displayScanline)); + lines -= step; + + displayClock.addCycles(step); + + // set TE when we're on the last scanline + // TODO: use STE reg + auto newLine = (displayScanline + step) % 240; + + if(newLine == 239) // now last line + { + if(forIntr) + mem.gpioUpdate(displayClock.getTime()); + mem.getGPIO().setInputMask(1 << 8); + } + else if(displayScanline == 239) // was last line + { + if(forIntr) + mem.gpioUpdate(displayClock.getTime()); + mem.getGPIO().clearInputMask(1 << 8); + } + + displayScanline = newLine; + } +} + +void PicoSystemBoard::audioUpdate(uint64_t time) +{ + auto samples = audioClock.getCyclesToTime(time); + + for(uint32_t i = 0; i < samples; i++) + { + while((audioWriteOff + 1) % audioBufferSize == audioReadOff); + + int level = 0x1000; + audioSamples[audioWriteOff++] = lastAudioVal ? level : -level; + audioWriteOff %= audioBufferSize; + } + + audioClock.addCycles(samples); +} + +void PicoSystemBoard::onGPIORead(uint64_t time, GPIO &gpio) +{ + // apply buttons + int buttonMask = 0xFF0000; + + gpio.setInputMask(~buttonState & buttonMask); // not pressed -> pulled high + gpio.clearInputMask(buttonState); // pressed -> pulled down + + displayUpdate(time); +} + +void PicoSystemBoard::onInterruptUpdate(uint64_t time, uint32_t irqMask) +{ + if(irqMask & (1 << 13) /*IO_IRQ_BANK0*/) + displayUpdate(time, true); +} + +uint64_t PicoSystemBoard::onGetNextInterruptTime(uint64_t time) +{ + if(!mem.getGPIO().interruptsEnabledOnPin(8)) + return time; + + int lines = std::max(1u, 239 - displayScanline); + auto ret = displayClock.getTimeToCycles(lines); + + return ret; +} + +void PicoSystemBoard::onPWMUpdate(uint64_t time, uint16_t pwm) +{ + audioUpdate(time); + lastAudioVal = pwm & (1 << 11); +} + +void PicoSystemBoard::onPIOUpdate(uint64_t time, PIO &pio) +{ + // display is usually PIO0 SM0 + auto &txFifo = pio.getTXFIFO(0); + auto &smHW = pio.getHW().sm[0]; + + if(txFifo.empty()) + return; + + // avoid entirely emptying FIFO while DMA is active + // (workaround for PIO brokenness) + int minLevel = mem.getDMA().isChannelActive(0) ? 1 : 0; + + while(txFifo.getCount() > minLevel) + { + auto data = txFifo.pop(); + + if(smHW.shiftctrl & PIO_SM0_SHIFTCTRL_AUTOPULL_BITS) + { + // 32blit-sdk hires or command + auto pullThresh = (smHW.shiftctrl & PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS) >> PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB; + if(pullThresh == 8) + { + // commands + bool dc = mem.getGPIO().getPadState() & (1 << 9); + + if(!dc) + { + displayCommand = data & 0xFF; + displayCommandOff = 0; + + if(displayCommand == 0x2C) + { + screenDataOffX = windowMinX; + screenDataOffY = windowMinY; + } + } + else + { + handleDisplayCommandData(data & 0xFF); + displayCommandOff++; + } + } + else + { + // hires data + screenData[screenDataOffX + screenDataOffY * 240] = data & 0xFFFF; + + if(screenDataOffX++ == windowMaxX) + { + screenDataOffX = windowMinX; + + if(screenDataOffY++ == windowMaxY) + screenDataOffY = windowMinY; + } + } + } + else + { + // 32blit-sdk lores or picosystem-sdk + bool lores = true; + + // assume ARGB4444 == picosystem-sdk + if(displayFormat == SDL_PIXELFORMAT_ARGB4444) + { + // picosystem sdk un-swaps in the pio program + data = data >> 16 | data << 16; + + // try to work out if this is hires mode + int top = (smHW.execctrl & PIO_SM0_EXECCTRL_WRAP_TOP_BITS) >> PIO_SM0_EXECCTRL_WRAP_TOP_LSB; + int bottom = (smHW.execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB; + + if(top - bottom < 12) // hires program is shorter (8 vs 18 instrs) + lores = false; + } + + auto offset = screenDataOffX + screenDataOffY * 240; + + screenData[offset++] = data & 0xFFFF; + if(lores) + screenData[offset++] = data & 0xFFFF; + screenData[offset++] = data >> 16; + if(lores) + screenData[offset++] = data >> 16; + + // update offset + screenDataOffX += lores ? 3 : 1; + assert(screenDataOffX <= windowMaxX); + + if(screenDataOffX++ == windowMaxX) + { + screenDataOffX = windowMinX; + + if(screenDataOffY++ == windowMaxY) + screenDataOffY = windowMinY; + } + } + } + + pio.updateFifoStatus(); +} + +void PicoSystemBoard::handleDisplayCommandData(uint8_t data) +{ + if(displayCommandOff < 4) + displayCommandData[displayCommandOff] = data; + + switch(displayCommand) + { + case 0x2A: // set column address + { + if(displayCommandOff == 3) + { + auto start = displayCommandData[0] << 8 | displayCommandData[1]; + auto end = displayCommandData[2] << 8 | displayCommandData[3]; + logf(LogLevel::Debug, logComponent, "display cols %i -> %i", start, end); + + windowMinX = start; + windowMaxX = end; + } + break; + } + case 0x2B: // set row address + { + if(displayCommandOff == 3) + { + auto start = displayCommandData[0] << 8 | displayCommandData[1]; + auto end = displayCommandData[2] << 8 | displayCommandData[3]; + logf(LogLevel::Debug, logComponent, "display rows %i -> %i", start, end); + + windowMinY = start; + windowMaxY = end; + } + break; + } + + case 0x36: // set address mode + { + addressMode = data; + + bool mh = data & (1 << 2); + bool rgb = data & (1 << 3); + bool ml = data & (1 << 4); + bool mv = data & (1 << 5); + bool mx = data & (1 << 6); + bool my = data & (1 << 7); + logf(LogLevel::Debug, logComponent, "display addr mode:%s%s%s%s%s%s", + mh ? " MH" : "", rgb ? " RGB" : "", ml ? " ML" : "", mv ? " MV" : "", mx ? " MX" : "", my ? " MY" : ""); + + updateDisplayFormat(); + break; + } + + case 0x3A: // set pixel format + pixelFormat = data; + logf(LogLevel::Debug, logComponent, "display format %x", data & 7); + + updateDisplayFormat(); + break; + + + case 0xC6: // frame rate control + { + int rtna = data & 0x1F; + int fpa = 0xC, bpa = 0xC; // assuming defaults + + // datasheet says 10MHz, but 10.24 matches the results in the table... + int framerate = 10240000 / ((320 + fpa + bpa) * (250 + rtna * 16)); + logf(LogLevel::Debug, logComponent, "display fps %i", framerate); + + displayClock.setFrequency(framerate * 240); + break; + } + + default: + logf(LogLevel::Debug, logComponent, "display cmd %02X %i = %02X", displayCommand, displayCommandOff, data); + } +} + +void PicoSystemBoard::updateDisplayFormat() +{ + bool bgr = addressMode & (1 << 3); // bit is called "RGB", but RGB=1 means BGR... + + SDL_PixelFormatEnum newFormat; + + switch(pixelFormat & 0x7) + { + case 3: // 12-bit + newFormat = bgr ? SDL_PIXELFORMAT_BGRA4444 : SDL_PIXELFORMAT_ARGB4444; + break; + case 5: // 16-bit + newFormat = bgr ? SDL_PIXELFORMAT_BGR565 : SDL_PIXELFORMAT_RGB565; + break; + // case 6: // 18-bit + + default: + newFormat = SDL_PIXELFORMAT_UNKNOWN; + } + + if(newFormat != displayFormat) + { + displayFormat = newFormat; + updateScreenSettings(); + } +} \ No newline at end of file diff --git a/minsdl/board/PicoSystem.h b/minsdl/board/PicoSystem.h new file mode 100644 index 0000000..626bc05 --- /dev/null +++ b/minsdl/board/PicoSystem.h @@ -0,0 +1,64 @@ +#pragma once +#include "Board.h" + +#include "ClockTarget.h" + +class GPIO; +class PIO; + +class PicoSystemBoard final : public Board +{ +public: + PicoSystemBoard(MemoryBus &mem); + + void getScreenSize(int &w, int &h) override; + int getScreenFormat() override; + const uint8_t *getScreenData() override; + + bool hasAudio() override; + int getNumAudioSamples() override; + int16_t getAudioSample() override; + + void handleEvent(SDL_Event &event) override; + void update(uint64_t time) override; + +private: + void handleDisplayCommandData(uint8_t data); + void updateDisplayFormat(); + + MemoryBus &mem; + + uint32_t buttonState = 0; + + uint16_t screenData[240 * 240]; + unsigned int displayScanline = 0; + ClockTarget displayClock; + int screenDataOffX = 0, screenDataOffY = 0; + int displayFormat = 0; + + int displayCommand = -1; + int displayCommandOff = 0; + uint8_t displayCommandData[4]; + + // some registers (picosystem-sdk values by default as we don't handle SPI yet) + uint8_t pixelFormat = 0x03, addressMode = 0x04; + uint16_t windowMinX = 0, windowMinY = 0, windowMaxX = 239, windowMaxY = 239; + + ClockTarget audioClock; + bool lastAudioVal = false; + static const int audioBufferSize = 1024; + volatile int audioReadOff = 0, audioWriteOff = 0; + int16_t audioSamples[audioBufferSize]{}; + + void displayUpdate(uint64_t time, bool forIntr = false); + void audioUpdate(uint64_t time); + + void onGPIORead(uint64_t time, GPIO &gpio); + + void onInterruptUpdate(uint64_t time, uint32_t irqMask); + + uint64_t onGetNextInterruptTime(uint64_t time); + + void onPWMUpdate(uint64_t time, uint16_t pwm); + void onPIOUpdate(uint64_t time, PIO &pio); +}; \ No newline at end of file diff --git a/minsdl/board/Tufty2040.cpp b/minsdl/board/Tufty2040.cpp new file mode 100644 index 0000000..32ece09 --- /dev/null +++ b/minsdl/board/Tufty2040.cpp @@ -0,0 +1,70 @@ +#include + +#include "Tufty2040.h" + +#include "Logging.h" +#include "MemoryBus.h" + +using Logging::logf; +using LogLevel = Logging::Level; +constexpr auto logComponent = Logging::Component::Board; + +Tufty2040Board::Tufty2040Board(MemoryBus &mem) : mem(mem) +{ + mem.getPIO(1).setUpdateCallback([this](auto time, auto &pio){onPIOUpdate(time, pio);}); +} + +void Tufty2040Board::getScreenSize(int &w, int &h) +{ + w = 320; + h = 240; +} + +int Tufty2040Board::getScreenFormat() +{ + return SDL_PIXELFORMAT_RGB565; +} + +const uint8_t *Tufty2040Board::getScreenData() +{ + return screenData; +} + +void Tufty2040Board::onPIOUpdate(uint64_t time, PIO &pio) +{ + // display is usually PIO1 SM0 + auto &txFifo = pio.getTXFIFO(0); + + bool rs = mem.getGPIO().getPadState() & (1 << 11); + + if(txFifo.empty()) + return; + + while(!txFifo.empty()) + { + auto data = txFifo.pop() & 0xFF; + if(!rs) + { + doDisplayWrite = false; + + // RAM write command + if(data == 0x2C) + { + screenDataOff = 0; + doDisplayWrite = true; + } + else if(data) + logf(LogLevel::Debug, logComponent, "tf display cmd %02X", data); + } + else if(doDisplayWrite) + { + screenData[screenDataOff ^ 1] = data & 0xFF; // byte swap + screenDataOff++; + + if(screenDataOff == 320 * 240 * 2) + screenDataOff = 0; + } + } + + pio.updateFifoStatus(); +} \ No newline at end of file diff --git a/minsdl/board/Tufty2040.h b/minsdl/board/Tufty2040.h new file mode 100644 index 0000000..8c5936b --- /dev/null +++ b/minsdl/board/Tufty2040.h @@ -0,0 +1,30 @@ +#pragma once +#include "Board.h" + +class PIO; + +class Tufty2040Board final : public Board +{ +public: + Tufty2040Board(MemoryBus &mem); + + void getScreenSize(int &w, int &h) override; + int getScreenFormat() override; + const uint8_t *getScreenData() override; + + bool hasAudio() override {return false;} + int getNumAudioSamples() override {return 0;} + int16_t getAudioSample() override {return 0;} + + void handleEvent(SDL_Event &event) override {} + void update(uint64_t time) override {} + +private: + MemoryBus &mem; + + uint8_t screenData[320 * 240 * 2]; + int screenDataOff = 0; + bool doDisplayWrite = false; + + void onPIOUpdate(uint64_t time, PIO &pio); +}; \ No newline at end of file