/* * 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 #include #include #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 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; }