Research-Stack/4-Infrastructure/hardware/research_stack_top.v
Brandon Schneider 0178d5d820 feat: spatial hash grid — GPU-style particle physics (Python + FPGA)
Ported from ScaleSpaceSynth (WebGPU particle simulator):
- 64×64×64 spatial hash, 32 particles/cell, lock-free insertion
- Curl noise: divergence-free 3D turbulence
- Pairwise forces: attractive (ratio>0.15) + repulsive (ratio<=0.15)
- Trilinear density interpolation
- HalfLife particle lifecycle
- Q16_16 encode/decode for VCN transport

Python (spatial_hash_grid.py): 6/6 tests pass
  10K particles, neighbor query, forces, 100 sim steps, curl noise verified

FPGA (spatial_hash_bram.v):
  16×16×16 grid, dual-port BRAM, 27-cycle neighbor scan
  Density → voltage mode selector (STORE/COMPUTE/APPROX/MORPHIC)
  Integrated into research_stack_top.v

Same pattern as ScaleSpaceSynth GPU:
  GPU: atomicAdd for lock-free cell assignment
  FPGA: BRAM read-modify-write for cell assignment
  Ray: content-addressed ObjectRef for lock-free reads
  All: partition space → compute density → find structure at multiple scales
2026-05-30 01:25:19 -05:00

310 lines
11 KiB
Verilog
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Research Stack Top-Level Module
// Unified FPGA design for Tang Nano 9K (GW1NR-9C)
// Combines: Blitter6502OISC + Q16 LUT + Memory Map + Voltage Controller
// + Scale Space BRAM + HiGHS Pivot Accelerator
// + Fractal Box Counter + FD Selector
// + Spatial Hash BRAM + Density Selector
`timescale 1ns / 1ps
module research_stack_top (
input wire clk, // 27 MHz oscillator (pin 52)
input wire rst_n, // Active-low reset (pin 4)
input wire user_btn, // Active-low user button (pin 3)
output wire [5:0] led, // Status LEDs (pins 10,11,13,14,15,16)
output wire uart_tx, // UART TX (pin 17)
input wire uart_rx // UART RX (pin 18, unused)
);
// ── Reset & Button ─────────────────────────────────────────────
wire rst = ~rst_n;
wire btn_pressed = ~user_btn;
// Button debounce
reg btn_d1, btn_d2;
reg [19:0] debounce_cnt;
reg btn_stable, btn_stable_prev;
wire btn_rise;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
btn_d1 <= 0; btn_d2 <= 0;
debounce_cnt <= 0;
btn_stable <= 0; btn_stable_prev <= 0;
end else begin
btn_d1 <= btn_pressed;
btn_d2 <= btn_d1;
if (btn_d2) begin
if (debounce_cnt < 20'd500000)
debounce_cnt <= debounce_cnt + 1;
end else begin
debounce_cnt <= 0;
end
btn_stable <= (debounce_cnt >= 20'd500000);
btn_stable_prev <= btn_stable;
end
end
assign btn_rise = btn_stable & ~btn_stable_prev;
// Auto-start: trigger CPU 100ms after reset (no button needed)
reg [31:0] auto_start_cnt;
reg auto_start;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
auto_start_cnt <= 0;
auto_start <= 0;
end else if (!auto_start) begin
if (auto_start_cnt >= 2700000) begin // 100ms at 27MHz
auto_start <= 1;
end else begin
auto_start_cnt <= auto_start_cnt + 1;
end
end
end
// ── Blitter CPU Signals ────────────────────────────────────────
wire cpu_busy;
wire [5:0] cpu_led;
wire cpu_uart_tx;
wire [7:0] cpu_rdata;
wire [11:0] cpu_mem_addr;
wire [7:0] cpu_mem_wdata;
wire cpu_mem_we;
// ── Memory Map Signals ─────────────────────────────────────────
wire [7:0] map_rdata;
wire [31:0] map_q16_a;
wire [31:0] map_q16_b;
wire [2:0] map_q16_op;
wire map_q16_trigger;
wire [1:0] map_voltage_mode;
wire [1:0] map_scale_select;
wire [31:0] map_highs_pivot;
wire map_highs_trigger;
// ── Q16 LUT Core Signals ───────────────────────────────────────
wire [31:0] q16_result;
wire q16_valid;
// ── Voltage Controller Signals ─────────────────────────────────
wire [31:0] vctrl_dout;
wire [1:0] vctrl_voltage;
wire [4:0] vctrl_precision;
wire vctrl_active;
// ── Scale Space BRAM Signals ───────────────────────────────────
wire [31:0] ss_dout;
wire [31:0] ss_kernel_sum;
// ── HiGHS Pivot Signals ────────────────────────────────────────
wire [31:0] highs_result;
wire highs_done;
wire highs_write_en;
wire [5:0] highs_write_idx;
wire [31:0] highs_write_data;
// ── Fractal Box Counter Signals ────────────────────────────────
wire [31:0] frac_fd_q16;
wire frac_fd_valid;
wire [1:0] frac_voltage_mode;
wire frac_mode_valid;
// ── Spatial Hash BRAM Signals ─────────────────────────────────
wire [15:0] sh_cell_density;
wire [15:0] sh_neighbor_density;
wire sh_query_done;
// ── Spatial Hash Selector Signals ─────────────────────────────
wire [1:0] sh_voltage_mode;
wire sh_mode_valid;
// ── Q16 LUT: use lower 16 bits of operands ────────────────────
wire [15:0] q16_a_16 = map_q16_a[15:0];
wire [15:0] q16_b_16 = map_q16_b[15:0];
// ── Result mux: select result source based on address ──────────
reg [31:0] result_latched;
reg q16_done_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
result_latched <= 0;
q16_done_reg <= 0;
end else begin
if (map_q16_trigger && !q16_busy) begin
q16_done_reg <= 0;
end
if (q16_valid && !q16_done_reg) begin
result_latched <= q16_result;
q16_done_reg <= 1;
end
end
end
reg q16_busy;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q16_busy <= 0;
else if (map_q16_trigger) q16_busy <= 1;
else if (q16_valid) q16_busy <= 0;
end
// ── Instantiations ─────────────────────────────────────────────
// Blitter6502OISC CPU
Blitter6502OISC cpu (
.clk(clk),
.rst_n(rst_n),
.start(auto_start),
.busy(cpu_busy),
.led(cpu_led),
.uart_tx(cpu_uart_tx),
.mem_we(1'b0),
.mem_addr(16'd0),
.mem_wdata(8'd0),
.mem_rdata()
);
// Blitter Memory Map (8-bit CPU ↔ 32-bit peripherals)
blitter_memory_map mem_map (
.clk(clk),
.rst_n(rst_n),
.addr({4'b0, cpu_mem_addr}), // pad 12-bit to 16-bit
.wdata(cpu_mem_wdata),
.we(cpu_mem_we),
.rdata(map_rdata),
.q16_a(map_q16_a),
.q16_b(map_q16_b),
.q16_op(map_q16_op),
.q16_trigger(map_q16_trigger),
.voltage_mode(map_voltage_mode),
.scale_select(map_scale_select),
.highs_pivot_element(map_highs_pivot),
.highs_trigger(map_highs_trigger)
);
// Q16 LUT Core (8 operations, 2-stage pipeline)
q16_lut_core q16 (
.clk(clk),
.rst(rst),
.op_select(map_q16_op),
.a(q16_a_16),
.b(q16_b_16),
.result(q16_result),
.valid(q16_valid)
);
// Voltage Mode Controller (4 BRAM modes)
voltage_mode_controller vctrl (
.clk(clk),
.rst_n(rst_n),
.mode(map_voltage_mode),
.bram_addr(q16_a_16[9:0]),
.bram_din(map_q16_b),
.bram_we(map_q16_trigger),
.morphic_amp(map_highs_pivot),
.bram_dout(vctrl_dout),
.voltage_level(vctrl_voltage),
.precision_bits(vctrl_precision),
.active(vctrl_active)
);
// Scale Space BRAM (4 Gaussian kernel banks)
scale_space_bram ss_bram (
.clk(clk),
.we(1'b0),
.bank_select(map_scale_select),
.addr(q16_a_16[7:0]),
.din(32'd0),
.dout(ss_dout),
.kernel_sum(ss_kernel_sum)
);
// HiGHS Pivot Accelerator
highs_pivot_accelerator highs (
.clk(clk),
.rst_n(rst_n),
.start(map_highs_trigger),
.pivot_element(map_highs_pivot),
.column_in(map_q16_a),
.column_idx(map_q16_b[5:0]),
.result(highs_result),
.done(highs_done),
.write_en(highs_write_en),
.write_idx(highs_write_idx),
.write_data(highs_write_data)
);
// Fractal Box Counter (DBC algorithm, 8-bit input, Q16_16 FD output)
fractal_box_counter #(
.MAX_SCALE(8),
.DATA_WIDTH(8)
) frac_bc (
.clk(clk),
.rst_n(rst_n),
.data_in(map_q16_a[7:0]), // 8-bit data from memory map
.data_valid(map_highs_trigger), // reuse highs_trigger as data strobe
.data_count(map_q16_b[15:0]), // element count from memory map
.fd_q16(frac_fd_q16),
.fd_valid(frac_fd_valid)
);
// Fractal FD → Voltage Mode Selector
// Maps fractal dimension to voltage mode:
// FD < 2.3 → STORE (0), FD < 2.6 → COMPUTE (1),
// FD < 2.9 → APPROX (2), FD >= 2.9 → MORPHIC (3)
fractal_fd_selector frac_sel (
.clk(clk),
.rst_n(rst_n),
.fd_q16(frac_fd_q16),
.fd_valid(frac_fd_valid),
.voltage_mode(frac_voltage_mode),
.mode_valid(frac_mode_valid)
);
// Spatial Hash BRAM (16×16×16 grid, 8 particles/cell, dual-port)
// Insert: particle position from map_q16_a[3:0], trigger from map_highs_trigger
// Query: query position from map_q16_b[3:0], trigger from map_q16_trigger
spatial_hash_bram spatial_hash (
.clk(clk),
.rst_n(rst_n),
.particle_x(map_q16_a[3:0]),
.particle_y(map_q16_a[7:4]),
.particle_z(map_q16_a[11:8]),
.particle_valid(map_highs_trigger),
.query_x(map_q16_b[3:0]),
.query_y(map_q16_b[7:4]),
.query_z(map_q16_b[11:8]),
.query_valid(map_q16_trigger),
.cell_density(sh_cell_density),
.neighbor_density(sh_neighbor_density),
.query_done(sh_query_done)
);
// Spatial Hash Density → Voltage Mode Selector
// Maps particle density to voltage mode:
// density < 10 → STORE, < 50 → COMPUTE, < 200 → APPROX, >= 200 → MORPHIC
spatial_hash_selector sh_sel (
.clk(clk),
.rst_n(rst_n),
.density_in(sh_cell_density),
.density_valid(sh_query_done),
.voltage_mode(sh_voltage_mode),
.mode_valid(sh_mode_valid)
);
// ── LED Output ─────────────────────────────────────────────────
// When CPU is busy: show running pattern (blinking)
// When CPU is halted: show cpu_led (register values from Blitter)
// Otherwise: show status
reg [24:0] heartbeat;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) heartbeat <= 0;
else heartbeat <= heartbeat + 1;
end
assign led = cpu_busy ? {1'b1, heartbeat[23], 1'b0, heartbeat[21], 1'b0, heartbeat[19]}
: cpu_led; // Blitter's register output after halt
// ── UART ───────────────────────────────────────────────────────
assign uart_tx = cpu_uart_tx;
endmodule