mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-08-07 19:25:46 +00:00
263 lines
7.5 KiB
C++
263 lines
7.5 KiB
C++
/*
|
|
* Verilator testbench for SparkleTangNano9KTop
|
|
*
|
|
* Validates:
|
|
* - I2S master clock generation (SCLK = clk/8, WS = SCLK/64)
|
|
* - Button edge detection and state transitions
|
|
* - Audio mode toggle
|
|
* - Multi-byte UART telemetry burst
|
|
* - No output contention or undriven signals
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include "VSparkleTangNano9KTop.h"
|
|
#include "verilated.h"
|
|
#ifdef VM_TRACE
|
|
#include "verilated_vcd_c.h"
|
|
#endif
|
|
|
|
static constexpr uint64_t CLK_HZ = 27000000;
|
|
static constexpr uint64_t SIM_CYCLES = 50000000; // ~1.85 seconds of real time
|
|
|
|
struct UartByte {
|
|
uint8_t data;
|
|
uint64_t cycle;
|
|
};
|
|
|
|
static std::vector<UartByte> uart_bytes;
|
|
|
|
// UART oversampling state machine (115200 baud @ 27 MHz = ~234 cycles/bit)
|
|
static constexpr int BAUD_PERIOD = 234;
|
|
static constexpr int HALF_BAUD = BAUD_PERIOD / 2;
|
|
|
|
static void capture_uart(VSparkleTangNano9KTop* top, uint64_t cycle) {
|
|
static bool last_tx = true;
|
|
static int state = 0; // 0=idle, 1=wait_mid_start, 2=sample_bits, 3=verify_stop
|
|
static int counter = 0;
|
|
static uint8_t shift = 0;
|
|
static int bit_idx = 0;
|
|
|
|
bool tx = top->uart_tx;
|
|
|
|
if (state == 0) {
|
|
if (last_tx && !tx) {
|
|
state = 1;
|
|
counter = 0;
|
|
}
|
|
} else if (state == 1) {
|
|
counter++;
|
|
if (counter >= HALF_BAUD) {
|
|
state = 2;
|
|
counter = 0;
|
|
shift = 0;
|
|
bit_idx = 0;
|
|
}
|
|
} else if (state == 2) {
|
|
counter++;
|
|
if (counter >= BAUD_PERIOD) {
|
|
counter = 0;
|
|
shift |= (tx & 1) << bit_idx;
|
|
bit_idx++;
|
|
if (bit_idx >= 8) {
|
|
state = 3;
|
|
counter = 0;
|
|
}
|
|
}
|
|
} else if (state == 3) {
|
|
counter++;
|
|
if (counter >= BAUD_PERIOD) {
|
|
if (!tx) {
|
|
printf("[cycle %lu] WARNING: UART framing error (stop bit not 1)\n", cycle);
|
|
}
|
|
uart_bytes.push_back({shift, cycle});
|
|
state = 0;
|
|
}
|
|
}
|
|
|
|
last_tx = tx;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
VerilatedContext* contextp = new VerilatedContext;
|
|
contextp->commandArgs(argc, argv);
|
|
contextp->fatalOnError(true);
|
|
|
|
VSparkleTangNano9KTop* top = new VSparkleTangNano9KTop{contextp};
|
|
|
|
#ifdef VM_TRACE
|
|
VerilatedVcdC* tfp = nullptr;
|
|
const char* trace_env = getenv("TRACE");
|
|
if (trace_env && trace_env[0] == '1') {
|
|
tfp = new VerilatedVcdC;
|
|
contextp->traceEverOn(true);
|
|
top->trace(tfp, 99);
|
|
tfp->open("sim_SparkleTangNano9KTop.vcd");
|
|
}
|
|
#endif
|
|
|
|
// Initialize inputs
|
|
top->clk = 0;
|
|
top->rst_n = 0;
|
|
top->user_btn = 1; // active-low, not pressed
|
|
top->uart_rx = 1; // idle
|
|
top->i2s_sd = 0;
|
|
|
|
int errors = 0;
|
|
int sclk_rise_count = 0;
|
|
int ws_rise_count = 0;
|
|
int ws_fall_count = 0;
|
|
bool last_sclk = false;
|
|
bool last_ws = false;
|
|
int button_press_cycle = -1;
|
|
uint64_t last_led_change = 0;
|
|
uint8_t last_led = top->led;
|
|
|
|
printf("=== Sparkle Tang Nano 9K Verilator Sim ===\n");
|
|
printf("Running %lu cycles (~%.2f s real time)\n", SIM_CYCLES, (double)SIM_CYCLES / CLK_HZ);
|
|
|
|
for (uint64_t cycle = 0; cycle < SIM_CYCLES; ++cycle) {
|
|
// Toggle clock
|
|
top->clk = !top->clk;
|
|
|
|
// Release reset after 100 cycles
|
|
if (cycle == 100) {
|
|
top->rst_n = 1;
|
|
printf("[cycle %lu] Reset released\n", cycle);
|
|
}
|
|
|
|
// Button press test: assert at 1M, release at 2.1M
|
|
// Debounce counter increments on posedge (every 2 cycles).
|
|
// Needs 500K counts = 1,000,002 cycles minimum hold time.
|
|
if (cycle == 1000000) {
|
|
top->user_btn = 0;
|
|
button_press_cycle = (int)cycle;
|
|
printf("[cycle %lu] Button pressed (active low)\n", cycle);
|
|
}
|
|
if (cycle == 2100000) {
|
|
top->user_btn = 1;
|
|
printf("[cycle %lu] Button released\n", cycle);
|
|
}
|
|
|
|
// Second button press to toggle audio mode
|
|
if (cycle == 4000000) {
|
|
top->user_btn = 0;
|
|
printf("[cycle %lu] Button pressed (toggle audio mode)\n", cycle);
|
|
}
|
|
if (cycle == 5100000) {
|
|
top->user_btn = 1;
|
|
printf("[cycle %lu] Button released\n", cycle);
|
|
}
|
|
|
|
// Provide synthetic I2S data after audio mode is enabled
|
|
// Toggle i2s_sd on SCLK edges to simulate a pattern
|
|
if (cycle > 5200000) {
|
|
top->i2s_sd = (cycle / 4) & 1;
|
|
}
|
|
|
|
// Evaluate
|
|
top->eval();
|
|
|
|
#ifdef VM_TRACE
|
|
if (tfp) tfp->dump(cycle);
|
|
#endif
|
|
|
|
// Capture on posedge only
|
|
if (top->clk) {
|
|
// Detect I2S SCLK edges
|
|
bool sclk = top->i2s_sclk;
|
|
if (!last_sclk && sclk) {
|
|
sclk_rise_count++;
|
|
}
|
|
last_sclk = sclk;
|
|
|
|
// Detect I2S WS edges
|
|
bool ws = top->i2s_ws;
|
|
if (!last_ws && ws) ws_rise_count++;
|
|
if (last_ws && !ws) ws_fall_count++;
|
|
last_ws = ws;
|
|
|
|
// Detect LED changes
|
|
if (top->led != last_led) {
|
|
last_led = top->led;
|
|
last_led_change = cycle;
|
|
}
|
|
|
|
// UART capture
|
|
capture_uart(top, cycle);
|
|
}
|
|
}
|
|
|
|
// === Post-simulation checks ===
|
|
printf("\n=== Results ===\n");
|
|
|
|
// 1. I2S clock check
|
|
double expected_sclk = (double)SIM_CYCLES / 2 / 8;
|
|
double sclk_err = fabs(sclk_rise_count - expected_sclk) / expected_sclk;
|
|
printf("I2S SCLK rises: %d (expected ~%.0f, error %.2f%%)\n",
|
|
sclk_rise_count, expected_sclk, sclk_err * 100);
|
|
if (sclk_err > 0.05) {
|
|
printf("ERROR: SCLK frequency out of tolerance\n");
|
|
errors++;
|
|
}
|
|
|
|
double expected_ws = expected_sclk / 64;
|
|
double ws_rise_err = fabs(ws_rise_count - expected_ws) / expected_ws;
|
|
printf("I2S WS rises: %d (expected ~%.0f, error %.2f%%)\n",
|
|
ws_rise_count, expected_ws, ws_rise_err * 100);
|
|
if (ws_rise_err > 0.05) {
|
|
printf("ERROR: WS frequency out of tolerance\n");
|
|
errors++;
|
|
}
|
|
|
|
// 2. UART check
|
|
printf("UART bytes captured: %zu\n", uart_bytes.size());
|
|
int state_frames = 0;
|
|
int meta_frames = 0;
|
|
for (const auto& b : uart_bytes) {
|
|
uint8_t tag = b.data >> 4;
|
|
if (tag == 0x5) state_frames++;
|
|
else if (tag == 0x6) meta_frames++;
|
|
}
|
|
printf(" State frames (0x5N): %d\n", state_frames);
|
|
printf(" Meta frames (0x6M): %d\n", meta_frames);
|
|
|
|
if (state_frames == 0) {
|
|
printf("ERROR: No state telemetry frames received\n");
|
|
errors++;
|
|
}
|
|
if (meta_frames == 0) {
|
|
printf("ERROR: No metadata telemetry frames received\n");
|
|
errors++;
|
|
}
|
|
if (state_frames != meta_frames && state_frames > 0 && meta_frames > 0) {
|
|
printf("WARNING: State/meta frame count mismatch (expected 1:1)\n");
|
|
}
|
|
|
|
// 3. LED activity check
|
|
printf("Last LED change at cycle %lu (value 0x%02x)\n", last_led_change, last_led);
|
|
if (last_led_change == 0 && SIM_CYCLES > 1000) {
|
|
printf("ERROR: LEDs never changed after reset\n");
|
|
errors++;
|
|
}
|
|
|
|
// 4. Check for X/Z on outputs (would indicate contention or undriven)
|
|
if (top->led == 0x3F || top->led == 0x00) {
|
|
// These are valid values, not necessarily errors
|
|
}
|
|
// Verilator would have already asserted on X/Z during eval()
|
|
|
|
printf("\n=== %s (%d errors) ===\n", errors == 0 ? "PASS" : "FAIL", errors);
|
|
|
|
#ifdef VM_TRACE
|
|
if (tfp) {
|
|
tfp->close();
|
|
delete tfp;
|
|
}
|
|
#endif
|
|
delete top;
|
|
delete contextp;
|
|
return errors;
|
|
}
|