// --------------------------------------------------------------------------- // erpm_decode.sv — QX-250 FPGA block B2 (bidirectional-DShot eRPM decode) // FPGA-QX250-001 · traces REQ-CTRL-005, MADe F4 motor-fault detection // // Decodes one captured bidirectional-DShot telemetry word into an eRPM period. // Input `sent` is the 20-bit NRZI-sampled GCR word from the per-channel line // capture front-end; this datapath recovers GCR, looks up the 5b->4b quintets, // checks the (inverted) CRC, and outputs the period. Logic is a 1:1 transcription // of the validated /tmp/b2_model.py receiver. // // SCOPE: this is the DECODE datapath. The analog line-capture front-end (measure // the bidir DShot line at DShot600 timing, ×4 channels, produce `sent` + strobe) // is timing-sensitive and is brought up in sim with a real ESC waveform — see // README status. eRPM = 60e6 / (period_us * pole_pairs) is applied in software. // --------------------------------------------------------------------------- `timescale 1ns/1ps `default_nettype none module erpm_decode ( input wire clk, input wire rst_n, input wire [19:0] sent, // NRZI-sampled GCR word from capture front-end input wire valid_in, // 1-cycle strobe: `sent` is ready to decode output reg [11:0] value12, // {shift[2:0], base[8:0]} output reg [15:0] period_us, // base << shift output reg decoded_valid, output reg err // 1 = GCR-invalid or CRC-fail ); // GCR 5b->4b: returns {valid, nibble} function automatic [4:0] gcr_dec(input [4:0] q); case (q) 5'h19: gcr_dec = {1'b1,4'h0}; 5'h1B: gcr_dec = {1'b1,4'h1}; 5'h12: gcr_dec = {1'b1,4'h2}; 5'h13: gcr_dec = {1'b1,4'h3}; 5'h1D: gcr_dec = {1'b1,4'h4}; 5'h15: gcr_dec = {1'b1,4'h5}; 5'h16: gcr_dec = {1'b1,4'h6}; 5'h17: gcr_dec = {1'b1,4'h7}; 5'h1A: gcr_dec = {1'b1,4'h8}; 5'h09: gcr_dec = {1'b1,4'h9}; 5'h0A: gcr_dec = {1'b1,4'hA}; 5'h0B: gcr_dec = {1'b1,4'hB}; 5'h1E: gcr_dec = {1'b1,4'hC}; 5'h0D: gcr_dec = {1'b1,4'hD}; 5'h0E: gcr_dec = {1'b1,4'hE}; 5'h0F: gcr_dec = {1'b1,4'hF}; default: gcr_dec = {1'b0,4'h0}; // invalid quintet endcase endfunction wire [19:0] gcr = sent ^ (sent >> 1); // NRZI recover wire [4:0] q3 = gcr[19:15], q2 = gcr[14:10], q1 = gcr[9:5], q0 = gcr[4:0]; wire [4:0] d3 = gcr_dec(q3), d2 = gcr_dec(q2), d1 = gcr_dec(q1), d0 = gcr_dec(q0); wire all_valid = d3[4] & d2[4] & d1[4] & d0[4]; wire [15:0] frame16 = {d3[3:0], d2[3:0], d1[3:0], d0[3:0]}; wire [11:0] v12 = frame16[15:4]; wire [3:0] crc = frame16[3:0]; wire [3:0] crc_calc = ~(v12[3:0] ^ v12[7:4] ^ v12[11:8]); wire crc_ok = (crc == crc_calc); wire [2:0] shift = v12[11:9]; wire [8:0] base = v12[8:0]; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin value12<=0; period_us<=0; decoded_valid<=1'b0; err<=1'b0; end else begin decoded_valid <= 1'b0; if (valid_in) begin if (all_valid & crc_ok) begin value12 <= v12; period_us <= ({7'd0, base} << shift); decoded_valid <= 1'b1; err <= 1'b0; end else begin err <= 1'b1; // rejected: bad GCR or CRC end end end end endmodule `default_nettype wire