// --------------------------------------------------------------------------- // imu_spi.sv — QX-250 FPGA block B3 (IMU SPI sequencer) // FPGA-QX250-001 · traces REQ-CTRL-001, functional F6 // // Deterministic MPU-6000 front-end: // 1. INIT — write a fixed register set (wake, sample rate, DLPF, ranges) at a // ≤1 MHz SCLK (MPU-6000 requires slow SCLK for config writes). // 2. RUN — on every `sample_tick`, burst-read 14 bytes from ACCEL_XOUT_H // (0x3B) at 8 MHz, decode accel/temp/gyro, pulse `data_valid`. // // One transaction = CS low, N bytes via spi_master, CS high. SCLK, MOSI/MISO and // the byte engine are the validated spi_master; this block only orchestrates // framing, CS, and the slow/fast SCLK selection. // --------------------------------------------------------------------------- `timescale 1ns/1ps `default_nettype none module imu_spi #( parameter int SCLK_HALF_FAST = 3, // 48MHz/6 = 8 MHz (data reads) parameter int SCLK_HALF_SLOW = 30, // 48MHz/60 = 800 kHz (config writes) parameter int CS_SETUP = 4, // clk cycles CS-low before first SCLK parameter int CS_HOLD = 4, // clk cycles after last SCLK before CS-high parameter int CS_GAP = 8 // clk cycles CS-high between transactions ) ( input wire clk, input wire rst_n, input wire sample_tick, // from B5: start one burst read (~8 kHz) // SPI pins (to MPU-6000) output wire imu_sclk, output wire imu_cs_n, output wire imu_mosi, input wire imu_miso, // decoded sample (registered; stable between data_valid pulses) output reg [15:0] accel_x, accel_y, accel_z, output reg [15:0] temp, output reg [15:0] gyro_x, gyro_y, gyro_z, output reg data_valid, // 1-cycle: new sample latched output reg initialized // high once INIT completes ); // ---- MPU-6000 register addresses --------------------------------------- localparam [6:0] REG_BURST = 7'h3B; // ACCEL_XOUT_H (auto-increment source) localparam RD = 1'b1, WR = 1'b0; // ---- INIT table: {addr[6:0], data[7:0]} -------------------------------- localparam int N_INIT = 5; function automatic [14:0] init_entry(input int i); case (i) 0: init_entry = {7'h6B, 8'h01}; // PWR_MGMT_1: wake, PLL(X-gyro) clk 1: init_entry = {7'h19, 8'h00}; // SMPLRT_DIV: 0 2: init_entry = {7'h1A, 8'h01}; // CONFIG: DLPF ~184 Hz 3: init_entry = {7'h1B, 8'h18}; // GYRO_CONFIG: ±2000 dps 4: init_entry = {7'h1C, 8'h10}; // ACCEL_CONFIG: ±8 g default: init_entry = {7'h00, 8'h00}; endcase endfunction // ---- spi_master instance ------------------------------------------------ reg [15:0] sclk_half; reg start; reg [7:0] tx_byte; wire [7:0] rx_byte; wire busy, done; reg cs_n; spi_master u_spi ( .clk(clk), .rst_n(rst_n), .sclk_half(sclk_half), .start(start), .tx_byte(tx_byte), .rx_byte(rx_byte), .busy(busy), .done(done), .sclk(imu_sclk), .mosi(imu_mosi), .miso(imu_miso) ); assign imu_cs_n = cs_n; // ---- Sequencer ---------------------------------------------------------- typedef enum logic [3:0] { S_POR, S_INIT_CSON, S_INIT_B0, S_INIT_B1, S_INIT_CSOFF, S_IDLE, S_RD_CSON, S_RD_CMD, S_RD_DATA, S_RD_CSOFF } state_t; state_t state; reg [15:0] dly; reg [2:0] init_idx; reg [3:0] byte_idx; // 0..14 within the read (0 = cmd) reg issued; // start pulse handshake with spi_master reg [7:0] buf [0:13]; // 14 sensor bytes wire [14:0] cur_init = init_entry(init_idx); // Byte send handshake (inlined per state): assert `start` for one cycle, // then wait for spi_master's `done`. integer k; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= S_POR; dly <= 16'd0; init_idx <= 3'd0; byte_idx <= 4'd0; issued <= 1'b0; start <= 1'b0; tx_byte <= 8'd0; cs_n <= 1'b1; sclk_half <= SCLK_HALF_SLOW[15:0]; data_valid <= 1'b0; initialized <= 1'b0; accel_x <= 0; accel_y <= 0; accel_z <= 0; temp <= 0; gyro_x <= 0; gyro_y <= 0; gyro_z <= 0; for (k = 0; k < 14; k = k + 1) buf[k] <= 8'd0; end else begin start <= 1'b0; // default; pulsed high for exactly 1 cycle data_valid <= 1'b0; case (state) // ---------------- power-up settle ----------------------------- S_POR: begin cs_n <= 1'b1; sclk_half <= SCLK_HALF_SLOW[15:0]; if (dly == 16'd63) begin dly <= 0; init_idx <= 0; state <= S_INIT_CSON; end else dly <= dly + 16'd1; end // ---------------- INIT: write one register --------------------- S_INIT_CSON: begin cs_n <= 1'b0; if (dly == CS_SETUP[15:0]) begin dly <= 0; issued <= 1'b0; state <= S_INIT_B0; end else dly <= dly + 16'd1; end S_INIT_B0: begin // byte0 = {WR, addr} if (!issued) begin tx_byte <= {WR, cur_init[14:8]}; start <= 1'b1; issued <= 1'b1; end else if (done) begin issued <= 1'b0; state <= S_INIT_B1; end end S_INIT_B1: begin // byte1 = data if (!issued) begin tx_byte <= cur_init[7:0]; start <= 1'b1; issued <= 1'b1; end else if (done) begin issued <= 1'b0; dly <= 0; state <= S_INIT_CSOFF; end end S_INIT_CSOFF: begin if (dly == CS_HOLD[15:0]) cs_n <= 1'b1; if (dly == (CS_HOLD[15:0] + CS_GAP[15:0])) begin dly <= 0; if (init_idx == N_INIT - 1) begin initialized <= 1'b1; sclk_half <= SCLK_HALF_FAST[15:0]; state <= S_IDLE; end else begin init_idx <= init_idx + 3'd1; state <= S_INIT_CSON; end end else dly <= dly + 16'd1; end // ---------------- RUN: wait for tick -------------------------- S_IDLE: begin cs_n <= 1'b1; if (sample_tick) begin dly <= 0; state <= S_RD_CSON; end end S_RD_CSON: begin cs_n <= 1'b0; if (dly == CS_SETUP[15:0]) begin dly <= 0; issued <= 1'b0; byte_idx <= 4'd0; state <= S_RD_CMD; end else dly <= dly + 16'd1; end S_RD_CMD: begin // byte0 = {RD, 0x3B} if (!issued) begin tx_byte <= {RD, REG_BURST}; start <= 1'b1; issued <= 1'b1; end else if (done) begin issued <= 1'b0; byte_idx <= 4'd0; state <= S_RD_DATA; end end S_RD_DATA: begin // 14 dummy reads, capture rx into buf[] if (!issued) begin tx_byte <= 8'h00; start <= 1'b1; issued <= 1'b1; end else if (done) begin issued <= 1'b0; buf[byte_idx] <= rx_byte; if (byte_idx == 4'd13) begin dly <= 0; state <= S_RD_CSOFF; end else byte_idx <= byte_idx + 4'd1; end end S_RD_CSOFF: begin if (dly == CS_HOLD[15:0]) cs_n <= 1'b1; if (dly == (CS_HOLD[15:0] + CS_GAP[15:0])) begin dly <= 0; // decode {H,L} big-endian per MPU-6000 map accel_x <= {buf[0], buf[1]}; accel_y <= {buf[2], buf[3]}; accel_z <= {buf[4], buf[5]}; temp <= {buf[6], buf[7]}; gyro_x <= {buf[8], buf[9]}; gyro_y <= {buf[10], buf[11]}; gyro_z <= {buf[12], buf[13]}; data_valid <= 1'b1; state <= S_IDLE; end else dly <= dly + 16'd1; end default: state <= S_POR; endcase end end endmodule `default_nettype wire