Files
picogus/sw/isa_analyzer.cpp
T

501 lines
17 KiB
C++

/*
* Copyright (C) 2025 Ian Scott
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <pico/stdlib.h>
#include <hardware/gpio.h>
#include <hardware/pio.h>
#include <hardware/structs/ioqspi.h>
#include <hardware/structs/qmi.h>
#include <hardware/structs/xip.h>
#include <hardware/sync.h>
#include <hardware/structs/pads_bank0.h>
#include "isa_analyzer.pio.h"
// PSRAM configuration (Pimoroni Pico Plus 2 onboard PSRAM)
#define PSRAM_CS_PIN 47
#define PSRAM_BASE_UNCACHED 0x15000000
#define PSRAM_SIZE (8 * 1024 * 1024)
// PIO state machine assignments (both on pio0)
#define IOW_SM 0
#define IOR_SM 1
// RLE compression constants
#define RLE_MAGIC 0xFF000000
#define RLE_COUNT_MASK 0x00FFFFFF
#define RLE_MAX_COUNT 0x00FFFFFF // 16,777,215
// Binary output markers
constexpr uint32_t BINARY_MARKER_MAGIC = 0x1DE1DE1D;
constexpr uint32_t BINARY_MARKER_START = 0x42494E53; // "BINS"
constexpr uint32_t BINARY_MARKER_END = 0x42494E45; // "BINE"
static uint32_t *data_buffer;
static size_t buffer_size = 0; // Total buffer capacity in 32-bit words
volatile static size_t write_idx = 0; // Next write position (monotonically increasing)
volatile static bool buffer_wrapped = false;
// RLE state
static uint32_t last_event = 0;
static uint32_t repeat_count = 0;
// Port filter bitmap: 1024 bits = 128 bytes, one bit per 10-bit address
static uint8_t port_bitmap[128];
// ---- FIFO polling ----
constexpr uint32_t iow_rxempty = 1u << (PIO_FSTAT_RXEMPTY_LSB + IOW_SM);
__force_inline bool iow_has_data() {
return !(pio0->fstat & iow_rxempty);
}
constexpr uint32_t ior_rxempty = 1u << (PIO_FSTAT_RXEMPTY_LSB + IOR_SM);
__force_inline bool ior_has_data() {
return !(pio0->fstat & ior_rxempty);
}
// ---- Ring buffer & RLE (from ide_analyzer.cpp) ----
__force_inline void ring_write(uint32_t value) {
data_buffer[write_idx % buffer_size] = value;
write_idx++;
if (write_idx >= buffer_size && !buffer_wrapped) {
buffer_wrapped = true;
}
}
__force_inline void flush_rle(void) {
if (repeat_count == 0) return;
// Write value first (value-first encoding for ring buffer safety)
ring_write(last_event);
// Then write trailer with count if repeated
if (repeat_count > 1) {
ring_write(RLE_MAGIC | (repeat_count - 1));
}
repeat_count = 0;
}
__force_inline void record_event(uint32_t event) {
if (repeat_count == 0) {
last_event = event;
repeat_count = 1;
} else if (event == last_event && repeat_count < RLE_MAX_COUNT) {
repeat_count++;
} else {
flush_rle();
last_event = event;
repeat_count = 1;
}
}
// ---- Port filter ----
// Add all ports matching (addr & mask) == (value & mask)
void add_port_filter(uint16_t mask, uint16_t value) {
for (int addr = 0; addr < 1024; addr++) {
if ((addr & mask) == (value & mask))
port_bitmap[addr >> 3] |= 1u << (addr & 7);
}
}
__force_inline bool port_match(uint16_t addr) {
return port_bitmap[addr >> 3] & (1u << (addr & 7));
}
// ---- PSRAM init (from ide_analyzer.cpp) ----
// Based on eightycc's PSRAM gist with timing adapted for 370MHz
// Only works on boards with QMI PSRAM (e.g. Pimoroni Pico Plus 2)
static size_t psram_init(void) {
gpio_set_function(PSRAM_CS_PIN, GPIO_FUNC_XIP_CS1);
uint32_t save = save_and_disable_interrupts();
// Initialize QMI in direct mode with slow clock for ID read
qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB |
QMI_DIRECT_CSR_EN_BITS;
while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS);
// Exit QPI mode if stuck (0xF5 command)
qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS |
QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB |
0xF5;
while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS);
(void)qmi_hw->direct_rx;
qmi_hw->direct_csr &= ~QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
// Read PSRAM ID (0x9F command) - 12 bytes total
qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
uint8_t kgd = 0, eid = 0;
for (size_t i = 0; i < 12; i++) {
qmi_hw->direct_tx = (i == 0) ? 0x9F : 0xFF;
while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS);
uint8_t rx = qmi_hw->direct_rx;
if (i == 5) kgd = rx;
if (i == 6) eid = rx;
}
qmi_hw->direct_csr &= ~QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
// Verify KGD (Known Good Die) = 0x5D for AP Memory PSRAM
if (kgd != 0x5D) {
qmi_hw->direct_csr = 0;
restore_interrupts(save);
return 0;
}
// Send initialization commands: RESETEN, RESET, QPI Enable, Wrap toggle
uint8_t cmds[] = {0x66, 0x99, 0x35, 0xC0};
for (int i = 0; i < 4; i++) {
qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | cmds[i];
while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS);
qmi_hw->direct_csr &= ~QMI_DIRECT_CSR_ASSERT_CS1N_BITS;
for (int j = 0; j < 20; j++) asm("nop");
(void)qmi_hw->direct_rx;
}
qmi_hw->direct_csr = 0; // Disable direct mode
// Configure QMI timing for 370MHz with CLKDIV=4 (92.5MHz PSRAM clock)
qmi_hw->m[1].timing =
QMI_M0_TIMING_PAGEBREAK_VALUE_1024 << QMI_M0_TIMING_PAGEBREAK_LSB |
3 << QMI_M0_TIMING_SELECT_HOLD_LSB |
1 << QMI_M0_TIMING_COOLDOWN_LSB |
2 << QMI_M0_TIMING_RXDELAY_LSB |
11 << QMI_M0_TIMING_MAX_SELECT_LSB | // 8us / (10.81ns x 64) = 11
5 << QMI_M0_TIMING_MIN_DESELECT_LSB | // 50ns / 10.81ns = 5
4 << QMI_M0_TIMING_CLKDIV_LSB; // 370MHz / 4 = 92.5MHz
// Read format: Quad mode, 6 dummy cycles (24 bits)
qmi_hw->m[1].rfmt =
QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB |
QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB |
QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB |
QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB |
QMI_M0_RFMT_DUMMY_LEN_VALUE_24 << QMI_M0_RFMT_DUMMY_LEN_LSB |
QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB |
QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB;
qmi_hw->m[1].rcmd = 0xEB << QMI_M0_RCMD_PREFIX_LSB; // Quad Read
// Write format: Quad mode, no dummy
qmi_hw->m[1].wfmt =
QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB |
QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB |
QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB |
QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB |
QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB |
QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB;
qmi_hw->m[1].wcmd = 0x38 << QMI_M0_WCMD_PREFIX_LSB; // Quad Write
// Enable writable access to M1 (PSRAM)
xip_ctrl_hw->ctrl |= XIP_CTRL_WRITABLE_M1_BITS;
__dmb();
restore_interrupts(save);
// Determine size from EID
size_t psram_size = 1024 * 1024; // Base 1MB
uint8_t size_id = eid >> 5;
if (eid == 0x26 || size_id == 2) psram_size *= 8; // 8MB
else if (size_id == 0) psram_size *= 2; // 2MB
else if (size_id == 1) psram_size *= 4; // 4MB
// Verify with write/read test
volatile uint32_t *psram = (volatile uint32_t *)PSRAM_BASE_UNCACHED;
psram[0] = 0x12345678;
if (psram[0] != 0x12345678) return 0;
return psram_size;
}
void *malloc_max(size_t *size_out) {
size_t low = 0;
size_t high = 512 * 1024; // 512KB for Pico 2
while (low < high) {
size_t mid = low + (high - low + 1) / 2;
void *p = malloc(mid);
if (p) {
free(p);
low = mid;
} else {
high = mid - 1;
}
}
void *buffer = NULL;
if (low > 0) {
buffer = malloc(low);
}
*size_out = buffer ? low : 0;
return buffer;
}
int main() {
stdio_init_all();
// System clock is already 370MHz from SDK auto-config (PLL_SYS_* defines)
puts("ISA bus analyzer starting up");
stdio_flush();
// Initialize PSRAM (must be AFTER clock setup since timing depends on clock speed)
printf("Initializing PSRAM... ");
stdio_flush();
size_t psram_size = psram_init();
if (psram_size == 0) {
puts("not found. Falling back to SRAM.");
stdio_flush();
data_buffer = (uint32_t*)malloc_max(&buffer_size);
if (!data_buffer) {
puts("Couldn't allocate SRAM either");
return 1;
}
printf("Using SRAM buffer: %u bytes\n", buffer_size);
stdio_flush();
buffer_size >>= 2; // Convert bytes to 32-bit words
} else {
printf("OK! Detected %u MB\n", psram_size / (1024 * 1024));
stdio_flush();
data_buffer = (uint32_t *)PSRAM_BASE_UNCACHED;
buffer_size = psram_size / sizeof(uint32_t);
printf("Using PSRAM buffer: %u entries (%u MB)\n",
buffer_size, psram_size / (1024 * 1024));
stdio_flush();
}
// Disable flash CS for BOOTSEL button detection
printf("Disabling flash CS (enabling BOOTSEL button)... ");
stdio_flush();
hw_write_masked(&ioqspi_hw->io[1].ctrl,
GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
puts("done.");
stdio_flush();
// Configure bus input pins: enable input, disable isolation (RP2350 requirement),
// and disable pulls. Without this, PIO reads all zeros.
const int bus_pins[] = {
IOW_PIN, IOR_PIN, // control signals
AD0_PIN, AD0_PIN+1, AD0_PIN+2, AD0_PIN+3, // AD0-AD9
AD0_PIN+4, AD0_PIN+5, AD0_PIN+6, AD0_PIN+7,
AD0_PIN+8, AD0_PIN+9,
DACK_PIN, TC_PIN, // DMA signals
};
for (int pin : bus_pins) {
gpio_set_input_enabled(pin, true);
hw_clear_bits(&pads_bank0_hw->io[pin], PADS_BANK0_GPIO0_ISO_BITS);
gpio_disable_pulls(pin);
}
// Drive IOCHRDY LOW = "ready" on ISA bus (no wait states inserted)
gpio_init(IOCHRDY_PIN);
gpio_set_dir(IOCHRDY_PIN, GPIO_OUT);
gpio_put(IOCHRDY_PIN, 0);
// Waggle ADS to set BUSOE latch (enable bus transceivers)
puts("Enabling bus transceivers...");
gpio_init(ADS_PIN);
gpio_set_dir(ADS_PIN, GPIO_OUT);
gpio_put(ADS_PIN, 1);
busy_wait_ms(10);
gpio_put(ADS_PIN, 0);
gpio_set_slew_rate(ADS_PIN, GPIO_SLEW_RATE_FAST);
// Configure PIO
puts("Starting ISA bus PIO...");
const uint iow_offset = pio_add_program(pio0, &iow_capture_program);
pio_sm_claim(pio0, IOW_SM);
const uint ior_offset = pio_add_program(pio0, &ior_capture_program);
pio_sm_claim(pio0, IOR_SM);
iow_capture_program_init(pio0, IOW_SM, iow_offset);
ior_capture_program_init(pio0, IOR_SM, ior_offset);
// Port filter
// To filter specific port ranges, clear bitmap and use add_port_filter():
memset(port_bitmap, 0, sizeof(port_bitmap));
add_port_filter(0x3F0, 0x220); // SB 0x220-0x22F
add_port_filter(0x3FC, 0x388); // OPL 0x388-0x38B
// add_port_filter(0x2F0, 0x240); // GUS 0x240-0x24F + 0x340-0x34F
// add_port_filter(0x3F8, 0x530); // WSS 0x530-0x537
// Otherwise to capture all, fill with 0xFF
// memset(port_bitmap, 0xFF, sizeof(port_bitmap));
printf("Buffer: %u entries (%u KB)\n", buffer_size, (buffer_size * 4) / 1024);
// Print active port filter ranges
{
int count = 0;
for (int i = 0; i < 1024; i++)
if (port_bitmap[i >> 3] & (1u << (i & 7))) count++;
if (count == 1024) {
puts("Filter: all ports");
} else if (count == 0) {
puts("Filter: DMA only (no I/O ports)");
} else {
printf("Filter: %d ports:", count);
int range_start = -1;
for (int i = 0; i <= 1024; i++) {
bool set = (i < 1024) && (port_bitmap[i >> 3] & (1u << (i & 7)));
if (set && range_start < 0) {
range_start = i;
} else if (!set && range_start >= 0) {
printf(" 0x%03X-0x%03X", range_start, i - 1);
range_start = -1;
}
}
puts("");
}
}
while (true) {
puts("Ready! Waiting for ISA bus activity (press BOOTSEL to stop capture)...");
stdio_flush();
// ---- Capture loop ----
for (;;) {
if (iow_has_data()) {
uint32_t raw = pio_sm_get(pio0, IOW_SM);
uint16_t addr = (raw >> 22) & 0x3FF;
uint8_t data = (raw >> 14) & 0xFF;
bool is_dma = (raw >> 13) & 1;
//printf("w %x ", addr);
if (!is_dma && !port_match(addr))
goto check_ior;
// Stored event format:
// Bit 31: Direction (0=IOW, 1=IOR)
// Bit 30: DMA flag
// Bits 17-8: 10-bit I/O address
// Bits 7-0: 8-bit data
uint32_t event = (addr << 8) | data;
if (is_dma) event |= (1u << 30);
record_event(event);
}
check_ior:
if (ior_has_data()) {
uint32_t raw = pio_sm_get(pio0, IOR_SM);
uint16_t addr = (raw >> 22) & 0x3FF;
uint8_t data = (raw >> 14) & 0xFF;
bool is_dma = (raw >> 13) & 1;
if (!is_dma && !port_match(addr))
goto check_bootsel;
uint32_t event = (1u << 31) | (addr << 8) | data;
if (is_dma) event |= (1u << 30);
record_event(event);
}
check_bootsel:
if (!(sio_hw->gpio_hi_in & SIO_GPIO_HI_IN_QSPI_CSN_BITS))
break;
}
// ---- Dump captured data ----
flush_rle();
// Calculate valid data range
size_t entries_captured;
size_t start_idx;
if (buffer_wrapped) {
entries_captured = buffer_size;
start_idx = write_idx % buffer_size;
// Skip orphaned RLE trailers at the start
while (entries_captured > 0 && (data_buffer[start_idx] & 0xFF000000) == RLE_MAGIC) {
start_idx = (start_idx + 1) % buffer_size;
entries_captured--;
}
} else {
entries_captured = write_idx;
start_idx = 0;
}
printf("\nCapture complete. %u buffer entries.\n", entries_captured);
stdio_flush();
// Write binary markers and data
uint32_t start_marker[2] = {BINARY_MARKER_MAGIC, BINARY_MARKER_START};
stdio_put_string((char*)start_marker, sizeof(start_marker), false, false);
stdio_flush();
if (buffer_wrapped) {
size_t first_chunk = buffer_size - start_idx;
stdio_put_string((char*)&data_buffer[start_idx],
first_chunk * sizeof(uint32_t), false, false);
stdio_flush();
if (start_idx > 0) {
stdio_put_string((char*)data_buffer,
start_idx * sizeof(uint32_t), false, false);
stdio_flush();
}
} else {
stdio_put_string((char*)data_buffer, write_idx * sizeof(uint32_t), false, false);
stdio_flush();
}
uint32_t end_marker[2] = {BINARY_MARKER_MAGIC, BINARY_MARKER_END};
stdio_put_string((char*)end_marker, sizeof(end_marker), false, false);
stdio_flush();
// Count events for statistics
size_t total_events = 0;
for (size_t j = 0; j < entries_captured; ++j) {
size_t i = buffer_wrapped ? ((start_idx + j) % buffer_size) : j;
uint32_t word = data_buffer[i];
if ((word & 0xFF000000) == RLE_MAGIC) {
total_events += (word & RLE_COUNT_MASK);
} else {
total_events++;
}
}
printf("\n--- END --- (%u events from %u buffer entries, %.1f%% compression)\n",
total_events, entries_captured,
100.0 * (1.0 - (float)entries_captured / (float)total_events));
stdio_flush();
// Clear events buffer
write_idx = 0;
// Wait until BOOTSEL is released (+ additional debounce delay)
while (!(sio_hw->gpio_hi_in & SIO_GPIO_HI_IN_QSPI_CSN_BITS)) {};
delay_ms(50);
}
}