// STRIX raw capture tool: boots the STRIX FPGA image and serves raw ring-oscillator // captures over USB serial for jitter characterization (see tools/capture.py). // // Commands (one per line): // id print the FPGA identification // cap capture a burst and send it as "DATA \n" + n raw bytes // source: 0 tap A, 1 enhanced A, 2 enhanced B, 3 pair XOR #include #include #include "wiring_private.h" // STRIX SPI link: SERCOM0 on its alternate pins A3 MOSI (PAD0), A4 SCK (PAD1), A6 MISO (PAD3). SPIClassSAMD strixSpi(&sercom0, A6, A4, A3, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3); const int STRIX_SS = A5; const SPISettings STRIX_SPI_SETTINGS(8000000, MSBFIRST, SPI_MODE0); enum : uint8_t { REG_ID0 = 0x00, REG_VERSION = 0x04, REG_SOURCE = 0x08, REG_DECIM_HIGH = 0x09, REG_DECIM_LOW = 0x0A, REG_CONTROL = 0x0B, REG_STATUS = 0x0C, REG_DATA = 0x10, }; const uint8_t READ = 0x80; const uint8_t CONTROL_START = 0x01; const uint8_t STATUS_DONE = 0x02; const size_t CAPTURE_BYTES = 32768; const size_t CHUNK_BYTES = 512; VidorUtils fpga; void beginTransaction(uint8_t command) { strixSpi.beginTransaction(STRIX_SPI_SETTINGS); digitalWrite(STRIX_SS, LOW); strixSpi.transfer(command); } void endTransaction() { digitalWrite(STRIX_SS, HIGH); strixSpi.endTransaction(); } void writeRegister(uint8_t reg, uint8_t value) { beginTransaction(reg); strixSpi.transfer(value); endTransaction(); } uint8_t readRegister(uint8_t reg) { beginTransaction(READ | reg); uint8_t value = strixSpi.transfer(0); endTransaction(); return value; } void printId() { char id[5] = {0}; for (uint8_t i = 0; i < 4; i++) id[i] = readRegister(REG_ID0 + i); Serial.print(id); Serial.print(" v"); Serial.println(readRegister(REG_VERSION)); } void capture(uint8_t source, uint16_t decimation) { writeRegister(REG_SOURCE, source); writeRegister(REG_DECIM_HIGH, decimation >> 8); writeRegister(REG_DECIM_LOW, decimation & 0xFF); writeRegister(REG_CONTROL, CONTROL_START); unsigned long start = millis(); while (!(readRegister(REG_STATUS) & STATUS_DONE)) { // 262,144 samples at 120 MHz take up to 143 s at the maximum decimation of 65535. if (millis() - start > 180000) { Serial.println("ERR timeout"); return; } } Serial.print("DATA "); Serial.println(CAPTURE_BYTES); static uint8_t chunk[CHUNK_BYTES]; for (size_t sent = 0; sent < CAPTURE_BYTES; sent += CHUNK_BYTES) { beginTransaction(READ | REG_DATA); for (size_t i = 0; i < CHUNK_BYTES; i++) chunk[i] = strixSpi.transfer(0); endTransaction(); Serial.write(chunk, CHUNK_BYTES); } } void handleCommand(String line) { line.trim(); if (line == "id") { printId(); } else if (line.startsWith("cap ")) { int space = line.indexOf(' ', 4); if (space < 0) { Serial.println("ERR usage: cap "); return; } long source = line.substring(4, space).toInt(); long decimation = line.substring(space + 1).toInt(); if (source < 0 || source > 3 || decimation < 1 || decimation > 65535) { Serial.println("ERR source 0-3, decimation 1-65535"); return; } capture(source, decimation); } else if (line.length() > 0) { Serial.println("ERR unknown command"); } } void setup() { Serial.begin(115200); while (!Serial); // Starts the 48 MHz FPGA clock and switches the FPGA from Arduino's loader to our image. if (!fpga.begin(true)) { Serial.println("ERR FPGA boot failed"); } delay(100); // configuration and PLL lock pinMode(STRIX_SS, OUTPUT); digitalWrite(STRIX_SS, HIGH); strixSpi.begin(); // SPIClass::begin() applies the pins' default mux; A3-A6 need SERCOM0's alternate mux. pinPeripheral(A3, PIO_SERCOM_ALT); pinPeripheral(A4, PIO_SERCOM_ALT); pinPeripheral(A6, PIO_SERCOM_ALT); printId(); } void loop() { if (Serial.available()) handleCommand(Serial.readStringUntil('\n')); }