// tmanager.cpp -- for the TFR/901 -- strick // // SPDX-License-Identifier: MIT #define OS9_ACIA_PORT 0xFF14 #define OS9_EMUDSK_PORT 0xFF80 #define OS9_CARDKB_PORT 0xFF0C // USE_CARDKB slows us down 0.6% // #define USE_CARDKB 1 // [note] PicoIO with base 0xFF00: // FF04: Write LED (0 or 1) // FF05: Read Rand // FF06: Set direction GPIO12..19 // FF07: Set data GPIO12..19 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define printf T::Logf // If we use the "W" version of a pico, // it requires pico/cyw43_arch.h: // #include "pico/cyw43_arch.h" #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define force_inline inline __attribute__((always_inline)) #define MUMBLE(X) \ { \ printf("MUMBLE: " X "\n"); \ ShowStr(X " "); \ sleep_ms(100); \ } typedef unsigned char byte; typedef unsigned int word; typedef unsigned char T_byte; typedef unsigned int T_word; typedef unsigned char T_16[16]; // RAPID_BURST_CYCLES is how many cycles // to run quickly without checking for IRQs // and other stuff that slows us down. // For a slower but more accurate simulation, // reduce the scale. // In order for simulated clock ticks to work, // RAPID_BURST_CYCLES must be a power of two, // so RAPID_BURST_SCALE is that power. constexpr uint RAPID_BURST_SCALE = 8; constexpr uint RAPID_BURST_CYCLES = (1u << RAPID_BURST_SCALE); // POKE EVENTS in Ram can cause USB traffic. // Sometimes we don't want that, in the middle of // some other USB operations. So Quiet() // and Noisy() can squelch that. uint quiet_ram; inline void Quiet() { quiet_ram++; } inline void Noisy() { quiet_ram--; } enum message_type : byte { // Long form codes, 128 to 191. // Followed by a 1-byte or 2-byte Size value. // If following byte in 128 to 191, it is 1-byte, use low 6 bits for size. // If following byte in 192 to 255, it is 2-byte, use low 6 bits times 64, // plus low 6 bits of next byte. C_LOGGING = 130, // Ten levels: 130 to 139. C_PRE_LOAD = 163, // Console pokes to Manager C_RAM_CONFIG = 164, // Pico tells tconsole. C_DUMP_RAM = 167, C_DUMP_LINE = 168, C_DUMP_STOP = 169, C_DUMP_PHYS = 170, C_EVENT = 172, // event.h C_DISK_READ = 173, C_DISK_WRITE = 174, EVENT_RTI = 176, EVENT_SWI2 = 177, // Short form codes, 192 to 255. // The packet length does not follow, // but is in the low nybble. C_REBOOT = 192, // n=0 C_PUTCHAR = 193, // n=1 C_RAM2_WRITE = 195, // n=3 C_RAM3_WRITE = 196, // n=4 C_RAM5_WRITE = 198, // n=6 C_CYCLE = 200, // tracing one cycle }; extern void putbyte(byte x); extern void PreLoadPacket(); uint current_opcode_cy; // what was the CY of the current opcode? uint current_opcode_pc; // what was the PC of the current opcode? uint current_opcode; // what was the current opcode? bool enable_show_irqs; bool enable_trace = true; uint trace_at_what_cycle; uint interest; uint stop_at_what_cycle; bool gime_irq_enabled; bool gime_vsync_irq_enabled; bool gime_vsync_irq_firing; bool acia_irq_enabled; bool acia_irq_firing; bool acia_char_in_ready; int acia_char; // Variabls for CardKb work just like the acia_ variables above. bool cardkb_irq_enabled; bool cardkb_irq_firing; bool cardkb_char_in_ready; int cardkb_char; uint nmi_needed; uint prev_nmi_needed; void ShowChar(byte ch) { if (ch < 1 || ch > 127) { putchar(C_PUTCHAR); } putchar(ch); } void ShowStr(const char* s) { while (*s) { ShowChar(*s++); } } // putbyte does CR/LF escaping for Binary Data void putbyte(byte x) { putchar_raw(x); } // Put Size with 1-byte / 2-byte encoding void putsz(uint n) { assert(n < 4096); if (n < 64) { putbyte(0x80 + n); } else { putbyte(0xC0 + (n >> 6)); // div 64 putbyte(0x80 + (n & 63)); // mod 64 } } // Include logging first. #include "benchmark-cycles.h" #include "logging.h" #include "pcrange.h" #include "picotimer.h" #include "seen.h" #include "trace.h" // SET_LED is for LED usage outside of an engine. // Inside an engine, use T::OrganicLED(bool) or T::SetLED(bool). #define SET_LED(X) gpio_put(LED_PIN, (X)) template struct DontShowIrqs { force_inline static void ShowIrqs(char ch) { } }; template struct DoShowIrqs { force_inline static void ShowIrqs(char ch) { if (enable_show_irqs) ShowChar(ch); } }; using IOReader = std::function; using IOWriter = std::function; IOReader IOReaders[256]; IOWriter IOWriters[256]; void PollUsbInput(); void InstallVector(uint i, uint addr) { IOReaders[255 & (0xFFF0 + 2 * i + 0)] = [addr](uint _a, byte _d) { return (byte)(addr >> 8); }; IOReaders[255 & (0xFFF0 + 2 * i + 1)] = [addr](uint _a, byte _d) { return (byte)(addr >> 0); }; } #include "acia.h" #include "cardkb.h" #include "gime.h" // Configuration #include "tfr9ports.gen.h" // PIO code #include "latch.pio.h" #include "tpio.pio.h" /* LED_W for Pico W: LED_W(1) for on, LED_W(0) for off. // #define LED_W(X) cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, (X)) */ #define HL_JOIN(H, L) (((255 & (H)) << 8) | ((255 & (L)) << 0)) #define HL_SPLIT(H, L, X) (H = (byte)((X) >> 8), L = (byte)((X) >> 0)) #define QUAD_JOIN(A, B, C, D) \ (((255 & (A)) << 24) | ((255 & (B)) << 16) | ((255 & (C)) << 8) | \ ((255 & (D)) << 0)) #define QUAD_SPLIT(A, B, C, D, X) \ (A = (byte)((X) >> 24), B = (byte)((X) >> 16), C = (byte)((X) >> 8), \ D = (byte)((X) >> 0)) constexpr uint RTI_SZ = 12; constexpr uint SWI2_SZ = 17; static byte hist_data[24]; static uint hist_addr[24]; #define MAX_INTEREST 0x7FFFffff #define getchar(X) NeverUseGetChar const byte Level1_Rom[] = { #include "../generated/level1.rom.h" }; const byte Level2_Rom[] = { #include "../generated/level2.rom.h" }; #define DELAY sleep_us(1) #define F_READ 0x01 #define F_AVMA 0x02 #define F_LIC 0x04 #define F_BA 0x08 #define F_BS 0x10 #define F_BUSY 0x20 #define F_HIGH (F_BA | F_BS | F_BUSY) #define PIN_E 8 // clock output pin #define PIN_Q 9 // clock output pin #define COUNTER_CLOCK 10 // 74hc161 counter control output pin #define COUNTER_RESET 11 // 74hc161 counter control output pin #define STATE_Y5_RESET_PIN 0 #define STATE_Y5_IRQ_PIN 2 extern "C" { extern int stdio_usb_in_chars(char* buf, int length); } const char* HighFlags(uint high) { if (!high) { return ""; } static char high_buf[8]; char* p = high_buf; if (high & F_BA) *p++ = 'A'; if (high & F_BS) *p++ = 'S'; if (high & F_BUSY) *p++ = 'Y'; *p = '\0'; return high_buf; } #include "circbuf.h" // SmallRam & BigRam #include "ram.h" // Debugging #include "event.h" #include "hyper.h" #include "reboot.h" // I/O devices // Initially, dont include "cocosdc.h"; use emudsk instead. #include "cocopias.h" #include "emudsk.h" #include "floppy.h" #include "pico-io.h" #include "samvdg.h" #include "ssd1306.h" #include "cardkb.h" #include "cyberterm.h" #include "turbo9sim.h" bool is_an_os9; // Operating Systems #include "nitros9level1.h" #include "nitros9level2.h" #include "turbo9os.h" void ViewAt(const char* label, uint hi, uint lo) { #if 0 Quiet(); uint addr = (hi << 8) | lo; VIEWF("=== %s: @%04x: ", label, addr); for (uint i = 0; i < 8; i++) { uint x = T::Peek2(addr+i+i); VIEWF("%04x ", x); } VIEWF("|"); for (uint i = 0; i < 16; i++) { byte ch = 0x7f & T::Peek(addr+i); if (32 <= ch && ch <= 126) { VIEWF("%c", ch); } else if (ch==0) { VIEWF("-"); } else { VIEWF("."); } } VIEWF("|\n"); Noisy(); #endif } void StrobePin(uint pin) { gpio_put(pin, 0); DELAY; gpio_put(pin, 1); DELAY; } // Use SetY(uint) to manually change the Multiplex Counter // to the specified state, from outisde the PIO state machines. // GPIO will have to "own" the COUNTER_RESET and COUNTER_CLOCK // pins, instead of PIO owning them (see InitializePinsForGpio()). // So really this is only used to reset the CPU // (see ResetCpu()) before PIO begins. void SetY(uint y) { StrobePin(COUNTER_RESET); for (uint i = 0; i < y; i++) { StrobePin(COUNTER_CLOCK); } } void InitializePinsForGpio() { for (uint i = 0; i < 8; i++) { gpio_init(i); gpio_set_dir(i, GPIO_IN); } for (uint i = 8; i < 12; i++) { gpio_init(i); gpio_set_dir(i, GPIO_OUT); gpio_put(i, 1); } // Set pull ups on all the GPIO that we use. // Omit GPIO 23:25 which are not exposed. // 1. Pull ups will feel like TTL. // 2. This will allow open-collector inputs. // 3. Avoids the worst case of a floating CMOS input // (if input near VCC/2, it can sink current and burn up). for (uint i = 0; i < 23; i++) { gpio_pull_up(i); } for (uint i = 26; i < 29; i++) { gpio_pull_up(i); } } // These were copied from Pico's headers // and made "hasty" by removing assertions. static force_inline bool hasty_pio_sm_is_rx_fifo_empty(PIO pio, uint sm) { return (pio->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + sm))) != 0; } static force_inline uint32_t hasty_pio_sm_get(PIO pio, uint sm) { return pio->rxf[sm]; } static force_inline void hasty_pio_sm_put(PIO pio, uint sm, uint32_t data) { pio->txf[sm] = data; } static force_inline uint WAIT_GET() { const PIO pio = pio0; constexpr uint sm = 0; // My own loop is faster than calling get_blocking: // return pio_sm_get_blocking(pio, sm); while (hasty_pio_sm_is_rx_fifo_empty(pio, sm)) continue; return hasty_pio_sm_get(pio, sm); } static force_inline void PUT(uint x) { const PIO pio = pio0; constexpr uint sm = 0; hasty_pio_sm_put(pio, sm, x); } void StartPio() { const PIO pio = pio0; constexpr uint sm = 0; pio_clear_instruction_memory(pio); pio_add_program_at_offset(pio, &tpio_program, 0); tpio_program_init(pio, sm, 0); } const char* DecodeCC(byte cc) { static char buf[9]; buf[0] = 0x80 & cc ? 'E' : 'e'; buf[1] = 0x40 & cc ? 'F' : 'f'; buf[2] = 0x20 & cc ? 'H' : 'h'; buf[3] = 0x10 & cc ? 'I' : 'i'; buf[4] = 0x08 & cc ? 'N' : 'n'; buf[5] = 0x04 & cc ? 'Z' : 'z'; buf[6] = 0x02 & cc ? 'V' : 'v'; buf[7] = 0x01 & cc ? 'C' : 'c'; buf[8] = '\0'; return buf; } bool TryGetUsbByte(char* ptr) { int rc = stdio_usb_in_chars(ptr, 1); return (rc != PICO_ERROR_NO_DATA); } void PollJustUsbInput() { // Try from USB to `usb_input` object. while (1) { char x = 0; bool ok = TryGetUsbByte(&x); if (ok) { usb_input.Put(x); } else { break; } } } void Fatal(const char* s) { while (*s) { putchar(*s); s++; } while (1) { putchar('#'); sleep_ms(2000); } } void PollUsbInput() { // Try from USB to `usb_input` object. while (1) { char x = 0; bool ok = TryGetUsbByte(&x); if (ok) { usb_input.Put(x); } else { break; } } // Try from `usb_input` object to `term_input`, if it Peeks as ASCII while (1) { int peek = usb_input.HasAtLeast(1) ? (int)usb_input.Peek() : -1; if (1 <= peek && peek <= 126) { byte c = usb_input.Take(); assert((int)c == peek); if (c == 10) { c = 13; } term_input.Put(c); } else { break; } } // Try from `usb_input` object to `disk_input`, if it Peeks as C_DISK_READ. int peek = usb_input.HasAtLeast(1) ? (int)usb_input.Peek() : -1; // Do not take, until a full packet is available. // This way, the initial C_DISK_READ byte will clog the buffer // and prevent any of these from ending up on term_input. switch (peek) { case C_REBOOT: ShowStr("\n*** REBOOTING ***\n"); #if 0 delay_ms(200); rom_REBOOT(REBOOT_TYPE_NORMAL | REBOOT_TO_ARM | NO_RETURN_UNTIL_SUCCESS, 200 /* delay_ms */, 0 /* p0 */ 0 /* p1 */); #else Reboot(); #endif while (1) { sleep_ms(50); ShowChar('.'); } break; case C_DISK_READ: if (usb_input.HasAtLeast(kDiskReadSize)) { for (uint i = 0; i < kDiskReadSize; i++) { byte t = usb_input.Take(); disk_input.Put(t); } } break; case C_PRE_LOAD: while (usb_input.HasAtLeast(2)) { byte sz = 63 & usb_input.Peek(1); if (usb_input.HasAtLeast(2 + sz)) { PreLoadPacket(); } } break; case -1: break; case 0: (void)usb_input.Take(); break; default: Fatal("PollUsbInput -- default"); } return; } // yak1 uint data; uint num_resets; uint event; uint when; uint num_swi2s; bool vma; // Valid Memory Address ( = delayed AVMA ) bool fic; // First Instruction Cycle ( = delayed LIC ) uint next_pc; // for multibyte ops. uint TildePowerOf2; uint OuterLoops; template struct EngineBase { static void UseRamForVectors() { IOReader ram_reader = [](uint addr, byte _d) { return T::Peek(addr); }; for (uint i = 0; i < 16; i++) { IOReaders[255 & (0xFFF0 + i)] = ram_reader; } } static void DumpPhys() { uint sz = T::PhysSize(); // Dont DumpPhys if DumpRam is the same. if (sz <= 0x10000) return; Quiet(); putbyte(C_DUMP_PHYS); for (uint i = 0; i < sz; i += 16) { for (uint j = 0; j < 16; j++) { if (T::ReadPhys(i + j)) goto yes; } continue; yes: putbyte(C_DUMP_LINE); putbyte(i >> 16); putbyte(i >> 8); putbyte(i); for (uint j = 0; j < 16; j++) { putbyte(T::ReadPhys(i + j)); } } putbyte(C_DUMP_STOP); Noisy(); } static void DumpRam() { Quiet(); putbyte(C_DUMP_RAM); for (uint i = 0; i < 0x10000; i += 16) { for (uint j = 0; j < 16; j++) { if (T::Peek(i + j)) goto yes; } continue; yes: putbyte(C_DUMP_LINE); putbyte(i >> 16); putbyte(i >> 8); putbyte(i); for (uint j = 0; j < 16; j++) { putbyte(T::Peek(i + j)); } } putbyte(C_DUMP_STOP); Noisy(); } static void GET_STUCK() { while (1) { putbyte(255); // signal for console to hang up. sleep_ms(1000); } } static void DumpRamAndGetStuck(const char* why, uint what) { interest = MAX_INTEREST; ShowStr("***\n*** DumpRamAndGetStuck: "); ShowStr(why); ShowStr("\n***\n"); printf("\n(((((((((([[[[[[[[[[{{{{{{{{{{\n"); printf("DumpRamAndGetStuck: %s ($%x = %d.)\n", why, what, what); DumpPhys(); DumpRam(); printf("\n}}}}}}}}}}]]]]]]]]]]))))))))))\n"); GET_STUCK(); } static bool ChangeInterruptPin(bool irq, bool nmi) { constexpr uint PULL_BLOCK_PC = 2; const PIO pio = pio0; constexpr uint sm = 0; int attempt = 200; T::ShowIrqs('>'); while (pio_sm_get_pc(pio, sm) != PULL_BLOCK_PC) { T::ShowIrqs('^'); attempt--; if (!attempt) { // We failed to hit the PULL_BLOCK_PC return false; } } // Disable the running TPIO program. pio_sm_set_enabled(pio, sm, false); // and switch to the Latch program. pio_clear_instruction_memory(pio); pio_add_program_at_offset(pio, &latch_program, 0); latch_program_init(pio, sm, 0); constexpr byte unused = 0x00; constexpr byte inputs = 0x00; // const byte control_bits = irq? 0xFB : 0xFF; byte control_bits = 0xFF; if (irq) control_bits &= 0xFB; // bit 0x04 is IRQ if (nmi) control_bits &= 0xEF; // bit 0x10 is NMI constexpr byte outputs = 0xFF; pio_sm_put(pio, sm, QUAD_JOIN(unused, inputs, control_bits, outputs)); // Wait for Finished signal on FIFO, then stop pio. (void)pio_sm_get_blocking(pio, sm); pio_sm_set_enabled(pio, sm, false); pio_clear_instruction_memory(pio); pio_add_program_at_offset(pio, &tpio_program, 0); tpio_program_init(pio, sm, 0); T::ShowIrqs(nmi ? '!' : irq ? ';' : ','); return true; } // Preroll ignores post-reset cycles until it sees a read from FFFE. static void PreRoll() { MUMBLE("PR0"); const PIO pio = pio0; constexpr uint sm = 0; IOReader r = IOReaders[255 & 0xFFFE]; if (!r) { MUMBLE("empty-FFFE"); // GET_STUCK(); byte th = ram[0xFFFE]; byte tl = ram[0xFFFF]; uint target = (uint(th) << 8) | uint(tl); InstallVector(7, target); MUMBLE("fixed?"); r = IOReaders[255 & 0xFFFE]; if (!r) { MUMBLE("still-empty-FFFE"); GET_STUCK(); } } // const byte x = T::Peek(0xFFFE); const byte hi = r(0xFFFE, 0xFF); MUMBLE("PR1"); while (1) { MUMBLE("PR2"); constexpr uint GO_AHEAD = 0x12345678; pio_sm_put(pio, sm, GO_AHEAD); MUMBLE("PR3"); const uint got32 = WAIT_GET(); MUMBLE("PR4"); byte junk, alo, ahi, flags; QUAD_SPLIT(junk, alo, ahi, flags, got32); const uint addr = HL_JOIN(ahi, alo); MUMBLE("PR5"); const bool reading = (flags & F_READ); MUMBLE("PR6"); printf("Preroll: got=%08x addr=%x flags=%x reading=%x hi=%x\n", got32, addr, flags, reading, hi); MUMBLE("PR7"); if (reading) { MUMBLE("PR8"); PUT(QUAD_JOIN(0xAA /*=unused*/, 0x00 /*=inputs*/, hi, 0xFF /*=outputs*/)); } else { MUMBLE("PR9"); {} // do nothing. } // end if reading MUMBLE("PR10"); if (addr == 0xFFFE) { MUMBLE("PR11"); printf("Preroll: done\n"); return; } MUMBLE("PR12"); } } ///////////// static void HandleIOWrite(uint addr, byte data) { const bool reading = false; byte dev = addr & 0xFF; // TODO -- handle f256 with 2 IO pages IOWriter writer = IOWriters[dev]; if (writer) { // New style, pluggable, not all is converted yet: ///// data = (*reader)(addr, data); writer(addr, data); } else switch (255 & addr) { case 0x90: // GIME INIT0 gime_irq_enabled = bool((data & 0x20) != 0); break; case 0x92: // GIME IRQEN gime_vsync_irq_enabled = bool((data & 0x08) != 0); break; } // switch addr & 255 } // HandleIOWrite static void HandleIORead(uint addr) { #if 0 data = T::Peek(addr); // default behavior #else data = 0xFF; #endif byte dev = addr & 0xFF; IOReader r = IOReaders[dev]; if (r) { // New style, pluggable, not all is converted yet: ///// data = (*reader)(addr, data); data = r(addr, data); } else switch (dev) { case 0x92: // GIME IRQEN register if (gime_irq_enabled && gime_vsync_irq_enabled && gime_vsync_irq_firing) { data = 0x08; gime_vsync_irq_firing = false; // Reading this register clears the IRQ. } else { data = 0; } break; default: break; } // switch PUT(QUAD_JOIN(0xAA /*=unused*/, 0x00 /*=inputs*/, data, 0xFF /*=outputs*/)); } // HandleIORead static void ResetCpu() { printf("Resetting CPU ... "); InitializePinsForGpio(); // Activate the 6309 RESET line SetY(4); for (uint i = 0; i < 8; i++) { gpio_put(i, 1); gpio_set_dir(i, GPIO_OUT); gpio_put(i, 1); } gpio_put(STATE_Y5_RESET_PIN, 0); // 0 is active StrobePin(COUNTER_CLOCK); // Y5 StrobePin(COUNTER_CLOCK); // Y6 for (uint i = 0; i < 8; i++) { gpio_set_dir(i, GPIO_IN); } SetY(0); const uint EnoughCyclesToReset = 32; gpio_put(PIN_Q, 0); DELAY; gpio_put(PIN_E, 0); DELAY; for (uint i = 0; i < EnoughCyclesToReset; i++) { gpio_put(PIN_Q, 1); DELAY; gpio_put(PIN_E, 1); DELAY; gpio_put(PIN_Q, 0); DELAY; gpio_put(PIN_E, 0); DELAY; } // Release the 6309 RESET line SetY(4); for (uint i = 0; i < 8; i++) { gpio_put(i, 1); gpio_set_dir(i, GPIO_OUT); gpio_put(i, 1); } gpio_put(STATE_Y5_RESET_PIN, 1); // 1 is release StrobePin(COUNTER_CLOCK); // Y5 StrobePin(COUNTER_CLOCK); // Y6 for (uint i = 0; i < 8; i++) { gpio_set_dir(i, GPIO_IN); } SetY(0); printf("... done.\n"); } static void Run() { MUMBLE("CF"); T::SendRamConfigOverUSB(); MUMBLE("INS"); T::Install(); MUMBLE("RC"); ResetCpu(); MUMBLE("LZ"); SET_LED(0); MUMBLE("PIO"); StartPio(); T::StartTimer(16666 /* 60 Hz */); MUMBLE("RMC"); RunMachineCycles(); sleep_ms(100); printf("\nEngine Finished.\n"); sleep_ms(100); MUMBLE("STUCK"); GET_STUCK(); } // end Run static bool PeekDiskInput() { int peek = disk_input.HasAtLeast(1) ? (int)disk_input.Peek() : -1; switch (peek) { case C_DISK_READ: if (disk_input.HasAtLeast(kDiskReadSize)) { return true; } break; } return false; } static void ReadDisk(uint device, uint lsn, byte* buf) { printf("READ SDC SECTOR %x %x\n", device, lsn); putbyte(C_DISK_READ); putbyte(device); putbyte(lsn >> 16); putbyte(lsn >> 8); putbyte(lsn >> 0); while (1) { PollUsbInput(); if (PeekDiskInput()) { for (uint k = 0; k < kDiskReadSize - 256; k++) { (void)disk_input.Take(); // 4-byte device & LSN. } for (uint k = 0; k < 256; k++) { buf[k] = disk_input.Take(); } break; } } } static void RunMachineCycles() { uint cy = 0; // This is faster if local. bool prev_irq_needed = false; // TOP const PIO pio = pio0; constexpr uint sm = 0; MUMBLE("PRE"); PreRoll(); MUMBLE("FFFF"); IOReader rff = IOReaders[0xFF]; assert(rff); // const byte value_FFFF = T::Peek(0xFFFF); const byte value_FFFF = rff(0xFFFF, 0xFF); printf("value_FFFF = %x\n", value_FFFF); const uint value_FFFF_shift_8_plus_FF = (value_FFFF << 8) + 0xFF; MUMBLE("LOOP:"); ShowStr("\n========\n"); printf("========\n"); TildePowerOf2 = 1; for (OuterLoops = 0; true; OuterLoops++) { ///////////////////////////////// Outer Machine Loop bool irq_needed = false; irq_needed |= T::Turbo9sim_IrqNeeded(); // either Timer or RX if (T::Does_CocoPias()) { irq_needed |= (T::VsyncIrqEnabled() && T::VsyncIrqFiring()); T::ShowIrqs('V'); } if (T::DoesAcia()) { irq_needed |= (acia_irq_enabled && acia_irq_firing); T::ShowIrqs('A'); } if (T::DoesCardKb()) { irq_needed |= (cardkb_irq_enabled && cardkb_irq_firing); T::ShowIrqs('K'); } if (T::DoesGime()) { irq_needed |= (gime_irq_enabled && gime_vsync_irq_enabled && gime_vsync_irq_firing); T::ShowIrqs('G'); } if (irq_needed != prev_irq_needed || nmi_needed != prev_nmi_needed) { bool ok = ChangeInterruptPin(irq_needed, nmi_needed); if (ok) { prev_irq_needed = irq_needed; prev_nmi_needed = nmi_needed; T::OrganicLED(irq_needed || nmi_needed); } } PollUsbInput(); if (T::Does_CocoKeyboard()) { static int outer_counter; outer_counter++; if (outer_counter >= 50) { // really 59 outer_counter = 0; T::TriggerVSync(); T::Keyboard_Tick(0); if (T::Keyboard_CanRx()) { if (term_input.HasAtLeast(1)) { if (term_input.HasAtLeast(1)) { byte ch = term_input.Take(); T::Keyboard_SetRx(ch); } } } } } if (T::Does_Turbo9sim()) { if (T::Turbo9sim_CanRx()) { if (term_input.HasAtLeast(1)) { byte ch = term_input.Take(); T::Turbo9sim_SetRx(ch); } } } if (T::DoesAcia()) { if (not acia_char_in_ready) { if (term_input.HasAtLeast(1)) { acia_char = term_input.Take(); acia_char_in_ready = true; acia_irq_firing = true; } else { acia_char = 0; acia_char_in_ready = false; acia_irq_firing = false; } } } if (T::DoesCardKb()) { static int ctr; ctr++; if (ctr >= 100) { ctr = 0; byte b = CardKbRead(); if (b) { cardkb_input.Put(b); } if (not cardkb_char_in_ready) { if (cardkb_input.HasAtLeast(1)) { cardkb_char = cardkb_input.Take(); cardkb_char_in_ready = true; cardkb_irq_firing = true; } else { cardkb_char = 0; cardkb_char_in_ready = false; cardkb_irq_firing = false; } } } } if (!T::DoesPicoTimer()) { // Simulate timer firing every so-many cycles, // but not with realtime timer, // because we are running slowly. if ((cy & VSYNC_TICK_MASK) == 0) { TimerFired = true; } // Notice that relies on RAPID_BURST_CYCLES // being a power of two. } if (TimerFired) { TimerFired = false; T::Turbo9sim_SetTimerFired(); if (T::Does_CocoPias()) { #if 0 T::Poke(0xFF03, T::Peek(0xFF03) | 0x80); // Set the bit indicating VSYNC occurred. #endif T::TriggerVSync(); } if (T::DoesGime()) { if (gime_irq_enabled && gime_vsync_irq_enabled) { gime_vsync_irq_firing = true; } } } // end if TimerFired T::BenchmarkCycle(cy); if (T::DoesLog()) { if (cy >= trace_at_what_cycle) interest = 999999999; } for (uint loop = 0; loop < RAPID_BURST_CYCLES; loop++) { /////// Inner Machine Loop #if 1 if (nmi_needed != prev_nmi_needed) { bool ok = ChangeInterruptPin(irq_needed, nmi_needed); if (ok) { prev_nmi_needed = nmi_needed; T::OrganicLED(irq_needed || nmi_needed); } } #endif constexpr uint GO_AHEAD = 0x12345678; pio_sm_put(pio, sm, GO_AHEAD); const uint get32 = WAIT_GET(); const uint addr = (0xFF00 & get32) | (get32 >> 16); const uint flags = get32; const bool reading = (flags & F_READ) != 0; // ============================================================= // ============================================================= if (likely(addr < 0xFE00)) { if (reading) { PUT((T::FastPeek(addr) << 8) + 0xFF); if (T::DoesLog()) { data = T::FastPeek(addr); } } else { const uint data_and_more = WAIT_GET(); T::FastPoke(addr, (byte)data_and_more); if (T::DoesLog()) { data = (byte)data_and_more; } } // end if reading // ============================================================= } else if (addr == 0xFFFF) { if (reading) { PUT(value_FFFF_shift_8_plus_FF); if (T::DoesLog()) { data = value_FFFF; } } else { const byte foo = WAIT_GET(); (void)foo; if (T::DoesLog()) { data = foo; } } // ============================================================= } else if (addr < 0xFF00) { if (reading) { // if reading FExx PUT((T::Peek(addr) << 8) + 0xFF); if (T::DoesLog()) { data = T::Peek(addr); } } else { // if writing FExx const byte foo = WAIT_GET(); T::Poke(addr, foo, 0x3F); if (T::DoesLog()) { data = foo; } } // end if reading // ============================================================= } else { if (reading) { // CPU reads, Pico Tx HandleIORead(addr); } else { // if writing // CPU writes, Pico Rx data = WAIT_GET(); T::Poke(addr, data, 0x3F); HandleIOWrite(addr, data); } // end if reading / writing } // end four addr type cases // ============================================================= // ============================================================= if (fic and reading and addr < 0xFFF0) { current_opcode_cy = cy; current_opcode_pc = addr; current_opcode = data; if (T::BadPc(addr)) { DumpRamAndGetStuck("PC out of range", addr); } T::Hyper(data, addr); } if (T::DoesLog()) { if (reading and addr != 0xFFFF) { if (current_opcode == 0x10 /* prefix */ && current_opcode_cy + 1 == cy) { current_opcode = 0x1000 | data; // printf("change to opcode %x\n", current_opcode); } if (current_opcode == 0x11 /* prefix */ && current_opcode_cy + 1 == cy) { current_opcode = 0x1100 | data; // printf("change to opcode %x\n", current_opcode); } if (is_an_os9 and T::DoesEvent()) { if (current_opcode == 0x3B) { // RTI uint age = cy - current_opcode_cy - 2 /*one byte opcode, one extra cycle */; if (0) printf("~RTI~R<%d,ccy=%d,cpc=%d,cop=%x,a=%d> %04x:%02x\n", cy, current_opcode_cy, current_opcode_pc, current_opcode, age, addr, data); // interest += 50; // TODO -- recognize E==0 for FIRQ if (age < RTI_SZ) { hist_data[age] = data; hist_addr[age] = addr; if (age == RTI_SZ - 1) { T::SendEventHist(EVENT_RTI, RTI_SZ); } } } // end RTI if (current_opcode == 0x103F) { // SWI2/OS9 uint age = cy - current_opcode_cy - 2 /*two byte opcode. extra cycle contains OS9 call number. */ ; if (0) printf("~OS9~R<%d,ccy=%d,cpc=%d,cop=%x,a=%d> %04x:%02x\n", cy, current_opcode_cy, current_opcode_pc, current_opcode, age, addr, data); // interest += 50; if (age < SWI2_SZ) { hist_data[age] = data; hist_addr[age] = addr; if (age == SWI2_SZ - 1) { T::SendEventHist(EVENT_SWI2, SWI2_SZ); } } } } } if (!reading) { if (T::DoesLog()) { if (current_opcode == 0x103F) { // SWI2/OS9 uint age = cy - current_opcode_cy - 2 /*two byte opcode*/; if (0) printf("~OS9~W<%d,ccy=%d,cpc=%d,cop=%x,a=%d> %04x:%02x\n", cy, current_opcode_cy, current_opcode_pc, current_opcode, age, addr, data); // interest += 50; if (age < SWI2_SZ) { hist_data[age] = data; hist_addr[age] = addr; } } } } if (T::DoesPcRange()) { if (current_opcode == 0x20 /*BRA*/ && current_opcode_cy + 1 == cy) { if (data == 0xFE) { DumpRamAndGetStuck("Infinite BRA loop", addr); } } } } // T::DoesLog() uint high = flags & F_HIGH; if (T::DoesTrace() and interest) { if (reading and (not vma) and (addr == 0xFFFF)) { #if TRACE_IDLE T::TransmitCycle(cy, high & 31, CY_IDLE, 0, 0); #endif } else { const char* label = reading ? (vma ? "r" : "-") : "w"; byte kind = reading ? (vma ? CY_READ : CY_IDLE) : CY_WRITE; if (reading) { if (fic) { label = "@"; kind = CY_SEEN; if (!T::WasItSeen(addr)) { label = "@@"; kind = CY_UNSEEN; } next_pc = addr + 1; } else { // case: Reading but not FIC if (next_pc == addr) { label = "&"; kind = CY_MORE; next_pc++; } } // end case Reading but not FIC } // end if reading T::TransmitCycle(cy, high & 31, kind, data, addr); } // end if valid cycle if (fic) { T::SeeIt(addr); } } if (T::DoesLog()) { vma = (0 != (flags & F_AVMA)); fic = (0 != (flags & F_LIC)); } cy++; } // next inner machine loop if (stop_at_what_cycle) { if (cy >= stop_at_what_cycle) { printf("=== TFR9 STOPPING BECAUSE CYCLE %d >= %d\n", cy, stop_at_what_cycle); goto exit; } } } // while true // outer loops exit: ShowStr("\n<<< exit: TFR9 STOPPING >>>\n"); Reboot(); } // end RunMachineCycles }; // end struct EngineBase template struct Slow_Mixins : DoPcRange, DoTrace, DoSeen, Logging, // DoLogMmu, DoShowIrqs, DoTraceRamWrites, DoHyper, DoEvent, DontDumpRamOnEvent, DontPicoTimer {}; template struct Fast_Mixins : DontPcRange, DontTrace, DontSeen, Logging, // DontLogMmu, DontShowIrqs, DontTraceRamWrites, DontHyper, DontEvent, DontDumpRamOnEvent, DoPicoTimer {}; template struct Fast_C2_Mixins : DontPcRange, DontTrace, DontSeen, Logging, // DontLogMmu, DontShowIrqs, DoTraceLowRamWrites, DontHyper, DontEvent, DontDumpRamOnEvent, DoPicoTimer {}; template struct Common_Mixins : EngineBase, CommonRam, DoPicoIO, DoSsd1306, #ifdef USE_CARDKB DoCardKb, #else DontCardKb, #endif #ifdef USE_CARDKB DoCyberTerm #else DontCyberTerm #endif { static void CommonInstall(uint picoio_base = 0xFF00) { MUMBLE(" COM: "); ShowChar('i'); T::PicoIO_Install(picoio_base); ShowChar('p'); #if 0 MUMBLE("

"); T::Ssd1306_Init(0xFF00); MUMBLE(" "); ShowChar('z'); #endif MUMBLE(" BILBO "); #ifdef USE_CARDKB T::CyberTerm_Init(0xFF10); #endif MUMBLE(" FRODO "); } }; template struct T9_Mixins : Common_Mixins, SmallRam, DontBenchmarkCycles, DontCocoKeyboard, DontCocoSamVdg, DontAcia, DontGime, DontCocoPias, DoTurbo9sim, DoTurbo9os { static void Install() { T::CommonInstall(); ShowChar('A'); T::Install_OS(); ShowChar('B'); T::Turbo9sim_Install(0xFF00); ShowChar('C'); ShowChar('\n'); } }; struct T9_Slow : T9_Mixins, Slow_Mixins {}; struct T9_Fast : T9_Mixins, Fast_Mixins {}; template struct X9_Mixins : Common_Mixins, DontBenchmarkCycles, DontCocoKeyboard, DontCocoSamVdg, DontAcia, DontGime, DontCocoPias, DoTurbo9sim { static void Install() { // Without OS. Must use PreLoadPacket() or some other way of loading a // program. ShowChar('9'); T::CommonInstall(); ShowChar('X'); T::Turbo9sim_Install(0xFF00); ShowChar('Y'); // Override previous vectors with Ram. T::UseRamForVectors(); } }; // X1 is a blank machine with no OS, Turbo9Sim-like IO, and a Small Ram. struct X1_Slow : SmallRam, X9_Mixins, Slow_Mixins {}; struct X1_Fast : SmallRam, X9_Mixins, Fast_Mixins {}; // X2 is a blank machine with no OS, Turbo9Sim-like IO, and a Big Ram. struct X2_Slow : BigRam, X9_Mixins, Slow_Mixins {}; struct X2_Fast : BigRam, X9_Mixins, Fast_Mixins {}; // C2 == a real Coco2 template struct C2_Mixins : Common_Mixins, DontBenchmarkCycles, DoCocoKeyboard, DoCocoSamVdg, DoDrive, DoFloppy, DontAcia, DontGime, DoCocoPias, DontTurbo9sim { static void Install() { // Without OS. Must use PreLoadPacket() or some other way of loading a // program. MUMBLE("C2::Install "); T::CommonInstall(0xFF30); // pico-io base. MUMBLE(".COM "); T::CocoPias_Install(printf); MUMBLE(".PIAS "); T::Drive_Install(0xFF40); MUMBLE(".DRIVE "); T::Floppy_Install(0xFF48); MUMBLE(".FLOPPY "); for (uint i = 0; i < 8; i++) { InstallVector(i, Coco2Vectors[i]); } InstallVector(7, 0xA027); MUMBLE(".VEC "); T::DumpRam(); SamBits &= ~0x8000u; // back to ROM } }; struct C2_Slow : SmallRam, C2_Mixins, Slow_Mixins {}; struct C2_Fast : SmallRam, C2_Mixins, Fast_C2_Mixins {}; // F3 == try Fuxiz on a Coco3 template struct F3_Mixins : Common_Mixins, DontBenchmarkCycles, DoCocoKeyboard, DoCocoSamVdg, DoDrive, DoFloppy, DontAcia, DoGime, DoCocoPias, DontTurbo9sim { static void Install() { // Without OS. Must use PreLoadPacket() or some other way of loading a // program. MUMBLE("F3::Install "); T::CommonInstall(0xFF30); // pico-io base. MUMBLE(".COM "); T::CocoPias_Install(printf); MUMBLE(".PIAS "); T::Drive_Install(0xFF40); MUMBLE(".DRIVE "); T::Floppy_Install(0xFF48); MUMBLE(".FLOPPY "); #if 0 for (uint i = 0; i < 8; i++) { InstallVector(i, Coco3Vectors[i]); } InstallVector(7, 0xA027); #endif MUMBLE("no-VEC "); T::DumpRam(); MUMBLE("DR "); // SamBits &= ~0x8000u; // back to ROM MUMBLE("F3::Installed "); } }; struct F3_Slow : BigRam, F3_Mixins, Slow_Mixins { // MUMBLE("F3_Slow::ctor"); }; struct F3_Fast : BigRam, F3_Mixins, Fast_Mixins { // MUMBLE("F3_Fast::ctor"); }; template struct L1_Mixins : Common_Mixins, SmallRam, DontBenchmarkCycles, DontCocoKeyboard, DontCocoSamVdg, DoAcia, DoEmudsk, DontGime, DoCocoPias, DontTurbo9sim, DoNitros9level1 { static void Install() { MUMBLE("Level1 "); T::CommonInstall(); ShowChar('A'); T::Install_OS(); ShowChar('B'); T::CocoPias_Install(printf); ShowChar('C'); T::Emudsk_Install(OS9_EMUDSK_PORT); ShowChar('D'); T::Acia_Install(OS9_ACIA_PORT); ShowChar('E'); ShowChar('\n'); } }; struct L1_Slow : L1_Mixins, Slow_Mixins {}; struct L1_Fast : L1_Mixins, Fast_Mixins {}; template struct L2_Mixins : Common_Mixins, BigRam, DontBenchmarkCycles, DontCocoKeyboard, DontCocoSamVdg, DoAcia, DoEmudsk, DoGime, DoCocoPias, DontTurbo9sim, DoNitros9level2 { static void Install() { T::CommonInstall(); ShowChar('A'); T::Install_OS(); ShowChar('B'); T::CocoPias_Install(printf); ShowChar('C'); T::Emudsk_Install(OS9_EMUDSK_PORT); ShowChar('D'); T::Acia_Install(OS9_ACIA_PORT); ShowChar('E'); ShowChar('\n'); } }; struct L2_Slow : L2_Mixins, Slow_Mixins {}; struct L2_Fast : L2_Mixins, Fast_Mixins {}; struct harness { std::function engines[10]; std::function fast_engines[10]; harness() { memset(engines, 0, sizeof engines); memset(fast_engines, 0, sizeof fast_engines); engines[0] = T9_Slow::Run; engines[1] = L1_Slow::Run; engines[2] = L2_Slow::Run; engines[3] = X1_Slow::Run; engines[4] = F3_Slow::Run; engines[6] = C2_Slow::Run; fast_engines[0] = T9_Fast::Run; fast_engines[1] = L1_Fast::Run; fast_engines[2] = L2_Fast::Run; fast_engines[3] = X1_Fast::Run; fast_engines[4] = F3_Fast::Run; fast_engines[6] = C2_Fast::Run; } }; void PreLoadPacket() { (void)usb_input.Take(); // command byte C_PRE_LOAD uint sz = 63 & usb_input.Take(); assert(sz > 2); // sz is packet size (number of bytes that follow sz). uint hi = usb_input.Take(); uint lo = usb_input.Take(); uint addr = (hi << 8) | lo; uint n = sz - 2; // n is number of following bytes to be poked. putchar('L'); for (uint i = 0; i < n; i++) { ram[addr] = ram[addr + 0x10000] = usb_input.Take(); // set upper and lower bank. addr++; // if ((i & 7) == 0) putchar('.'); } // putchar(')'); if (addr == 0xFFFE and n==2) { stdio_puts("PreLoadPacket: (addr == 0xFFFE and n==2)\n"); // Reset Vector due to "FF" clause at end of decb binary. byte th = ram[0xFFFE]; byte tl = ram[0xFFFF]; uint target = (uint(th) << 8) | uint(tl); InstallVector(7, target); //printf("PreLoadPacket: InstalVector(7) at %x\n", target); } } void Shell() { struct harness harness; Verbosity = 5; Traceosity = 5; while (true) { // 200 loops for a 2-second period with sleep_ms(10) constexpr uint SLEEP_MS = 10; constexpr uint PERIOD_MS = 2000; constexpr uint n = PERIOD_MS / SLEEP_MS; constexpr uint QUARTER_PERIOD = n / 4; for (int i = 0; i < n; i++) { sleep_ms(SLEEP_MS); if (i % QUARTER_PERIOD == 0) { ShowChar(".:,;"[(i / QUARTER_PERIOD) & 3]); } PollUsbInput(); if (term_input.HasAtLeast(1)) { byte ch = term_input.Take(); ShowChar('<'); if (32 <= ch && ch <= 126) { ShowChar(ch); } else { ShowChar('#'); } ShowChar('>'); if (ch == '/') { for (uint i = 0; i < 5; i++) { harness.engines[i] = harness.engines[i+5]; harness.fast_engines[i] = harness.fast_engines[i+5]; } machine_shifted = 100; } else if ('0' <= ch && ch <= '4') { uint num = ch - '0'; if (harness.fast_engines[num]) { machine_number = ch; harness.fast_engines[num](); } else { ShowStr("-S?-"); } } else if ('5' <= ch && ch <= '9') { uint num = ch - '5'; if (harness.engines[num]) { machine_number = ch; harness.engines[num](); } else { ShowStr("-F?-"); } } else if (ch == 'q') { Traceosity = 6; } else if (ch == 'w') { Traceosity = 7; } else if (ch == 'e') { Traceosity = 8; } else if (ch == 'r') { Traceosity = 9; } else if (ch == 'j') { Verbosity = 2; } else if (ch == 'k') { Verbosity = 3; } else if (ch == 'l') { Verbosity = 4; } else if (ch == 'a') { Verbosity = 6; } else if (ch == 's') { Verbosity = 7; } else if (ch == 'd') { Verbosity = 8; } else if (ch == 'f') { Verbosity = 9; } else if (ch == 'v') { set_sys_clock_khz(200000, true); } else if (ch == 'c') { set_sys_clock_khz(250000, true); } else if (ch == 'x') { set_sys_clock_khz(260000, true); } else if (ch == 'z') { set_sys_clock_khz(270000, true); } else if (ch == 'k') { trace_at_what_cycle += 100 * 1000; // printf(" [%d] ", trace_at_what_cycle) } else if (ch == 'l') { trace_at_what_cycle += 1000 * 1000; // printf(" [%d] ", trace_at_what_cycle) } else { ShowStr("-#?-"); } } // term_input if (90 <= i && i <= 100) { SET_LED(1); } else if (120 < i && i < 130) { SET_LED(1); } else { SET_LED(0); } } } // Shell never returns. } int main() { stdio_usb_init(); gpio_init(25); gpio_set_dir(25, GPIO_OUT); SET_LED(0); InitializePinsForGpio(); #if 0 for (uint i = 0; i < 5; i++) { SET_LED(1); sleep_ms(100); SET_LED(0); sleep_ms(150); } #endif interest = 0; // MAX_INTEREST; /// XXX quiet_ram = 0; Shell(); }