// --------------------------------------------------------------------------- // dshot600_tx.sv — QX-250 FPGA block B1 (single channel) // FPGA-QX250-001 · traces REQ-CTRL-003, IF-04, functional F3 // // Deterministic DShot600 serializer for one BLHeli_32 ESC channel. // // DShot frame = 16 bits, MSB first: // [15:5] throttle (11 bits, 0=disarmed, 48..2047 = throttle range) // [4] telemetry (request telemetry on this frame) // [3:0] CRC (nibble XOR of the 12-bit value; inverted if bidir) // // Bit encoding (pulse-width): every bit is one BIT period; the line is held // HIGH for T1H (logic 1) or T0H (logic 0), then LOW for the remainder. // DShot600: T = 1667 ns, T1H = 1250 ns (3/4 T), T0H = 625 ns (3/8 T). // At CLK_HZ = 48 MHz these fall on exact integer cycle counts (80/60/30). // // The core is intentionally vendor-neutral: no hard primitives. Only B8's PLL // and the I/O buffer are vendor-specific and live outside this module. // --------------------------------------------------------------------------- `timescale 1ns/1ps `default_nettype none module dshot600_tx #( parameter int CLK_HZ = 48_000_000, // system clock feeding this block parameter bit BIDIR = 1'b0 // 0 = normal DShot, 1 = bidirectional (inverted CRC) ) ( input wire clk, input wire rst_n, // frame content (sampled on the cycle `send` is asserted) input wire [10:0] throttle, // 0..2047 input wire telemetry, // telemetry-request bit input wire send, // 1-cycle strobe: begin one frame output reg busy, // high while a frame is in flight output reg done, // 1-cycle pulse when the frame completes output reg dshot_out // serial line to the ESC (idle low) ); // ---- Timing (derived; exact integers at 48 MHz) ------------------------ localparam int BIT_TICKS = CLK_HZ / 600_000; // 80 → 1666.7 ns localparam int T1H_TICKS = (BIT_TICKS * 3) / 4; // 60 → 1250 ns localparam int T0H_TICKS = (BIT_TICKS * 3) / 8; // 30 → 625 ns // ---- Frame assembly (combinational) ------------------------------------ // value = {throttle, telemetry} (12 bits); CRC = XOR of its three nibbles. wire [11:0] value = {throttle, telemetry}; wire [3:0] csum = value[3:0] ^ value[7:4] ^ value[11:8]; wire [3:0] crc = BIDIR ? ~csum : csum; wire [15:0] frame = {value, crc}; // MSB (throttle[10]) first // ---- Serializer FSM ---------------------------------------------------- typedef enum logic [1:0] {S_IDLE, S_BIT, S_DONE} state_t; state_t state; reg [15:0] shreg; // shifts left, MSB is the current bit reg [4:0] bitno; // bits remaining (16..1) reg [15:0] tick; // cycle counter within the current bit wire cur_bit = shreg[15]; wire [15:0] high_ticks = cur_bit ? T1H_TICKS[15:0] : T0H_TICKS[15:0]; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= S_IDLE; dshot_out <= 1'b0; busy <= 1'b0; done <= 1'b0; tick <= 16'd0; bitno <= 5'd0; shreg <= 16'd0; end else begin done <= 1'b0; // default; pulsed for one cycle in S_DONE case (state) // ------------------------------------------------------------- S_IDLE: begin dshot_out <= 1'b0; busy <= 1'b0; if (send) begin shreg <= frame; bitno <= 5'd16; tick <= 16'd0; busy <= 1'b1; state <= S_BIT; end end // ------------------------------------------------------------- S_BIT: begin // HIGH for the first `high_ticks` cycles of the bit, then LOW. dshot_out <= (tick < high_ticks); if (tick == BIT_TICKS[15:0] - 16'd1) begin tick <= 16'd0; shreg <= {shreg[14:0], 1'b0}; // advance to next bit bitno <= bitno - 5'd1; if (bitno == 5'd1) state <= S_DONE; // last bit just finished end else begin tick <= tick + 16'd1; end end // ------------------------------------------------------------- S_DONE: begin dshot_out <= 1'b0; busy <= 1'b0; done <= 1'b1; // 1-cycle completion pulse state <= S_IDLE; end default: state <= S_IDLE; endcase end end endmodule `default_nettype wire