From 5d334bfdd666e8b999eb5055bd18242da9fb6a89 Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:03:08 +0545 Subject: [PATCH] =?UTF-8?q?fix(fpga):=20TX-N9=20=E2=80=94=20sim-only=20pay?= =?UTF-8?q?load-hold=20checker=20on=20cmd=20CDC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd_data / cmd_opcode / cmd_addr / cmd_value feed downstream CDC sync chains; the safety property is that they only change on the cycle cmd_valid rises (RD_PROCESS), and stay held on every other cycle so the receiver's 2-FF synchronizer sees a clean payload regardless of where its sample window lands. The FSM satisfies this implicitly today, but nothing flagged a regression that introduced a stray write somewhere in the same always block. Added an `ifdef SIMULATION block at the bottom of both usb_data_interface.v (FT601 / ft601_clk_in / ft601_reset_n) and usb_data_interface_ft2232h.v (FT2232H / ft_clk / ft_reset_n). It snapshots the payload + cmd_valid each cycle and fires [ASSERT FAIL] TX-N9: cmd_ changed while cmd_valid=0 (old -> new) on any payload change while cmd_valid is low. Local regs suffixed _n9 to avoid future name collisions. Synthesis-inert. Quick FPGA regression unchanged: USB Data Interface 91/91 PASS, overall 28/29 (same baseline; the 1 fail is the pre-existing iverilog/Xilinx-IP RX-NEW-3 gap). --- 9_Firmware/9_2_FPGA/usb_data_interface.v | 48 +++++++++++++++ .../9_2_FPGA/usb_data_interface_ft2232h.v | 58 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/9_Firmware/9_2_FPGA/usb_data_interface.v b/9_Firmware/9_2_FPGA/usb_data_interface.v index a84f088..1eca084 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface.v @@ -682,4 +682,52 @@ end assign ft601_clk_out = ft601_clk_in; `endif +// ============================================================================ +// TX-N9: payload-hold checker (simulation only) +// +// cmd_data / cmd_opcode / cmd_addr / cmd_value feed downstream CDC sync +// chains; safety property is that they only change on the cycle cmd_valid +// rises (RD_PROCESS), and are held stable on every other cycle. The FSM +// satisfies this implicitly today; this checker fires `[ASSERT FAIL]` on +// any payload change while cmd_valid is low so a future regression is +// caught in the simulation log. Synthesis-inert. +// ============================================================================ +`ifdef SIMULATION +reg [31:0] cmd_data_prev_n9; +reg [7:0] cmd_opcode_prev_n9; +reg [7:0] cmd_addr_prev_n9; +reg [15:0] cmd_value_prev_n9; +reg cmd_valid_prev_n9; + +always @(posedge ft601_clk_in or negedge ft601_reset_n) begin + if (!ft601_reset_n) begin + cmd_data_prev_n9 <= 32'd0; + cmd_opcode_prev_n9 <= 8'd0; + cmd_addr_prev_n9 <= 8'd0; + cmd_value_prev_n9 <= 16'd0; + cmd_valid_prev_n9 <= 1'b0; + end else begin + if (!cmd_valid && !cmd_valid_prev_n9) begin + if (cmd_data !== cmd_data_prev_n9) + $display("[ASSERT FAIL] TX-N9: cmd_data changed while cmd_valid=0 (%h -> %h)", + cmd_data_prev_n9, cmd_data); + if (cmd_opcode !== cmd_opcode_prev_n9) + $display("[ASSERT FAIL] TX-N9: cmd_opcode changed while cmd_valid=0 (%h -> %h)", + cmd_opcode_prev_n9, cmd_opcode); + if (cmd_addr !== cmd_addr_prev_n9) + $display("[ASSERT FAIL] TX-N9: cmd_addr changed while cmd_valid=0 (%h -> %h)", + cmd_addr_prev_n9, cmd_addr); + if (cmd_value !== cmd_value_prev_n9) + $display("[ASSERT FAIL] TX-N9: cmd_value changed while cmd_valid=0 (%h -> %h)", + cmd_value_prev_n9, cmd_value); + end + cmd_data_prev_n9 <= cmd_data; + cmd_opcode_prev_n9 <= cmd_opcode; + cmd_addr_prev_n9 <= cmd_addr; + cmd_value_prev_n9 <= cmd_value; + cmd_valid_prev_n9 <= cmd_valid; + end +end +`endif + endmodule \ No newline at end of file diff --git a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v index 67fd100..c27de23 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v @@ -967,4 +967,62 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin end end +// ============================================================================ +// TX-N9: payload-hold checker (simulation only) +// +// The cmd_* outputs feed a CDC sync chain in the consumer clock domain. +// Safety property: cmd_data / cmd_opcode / cmd_addr / cmd_value must only +// change on the cycle that cmd_valid rises (RD_PROCESS in this module). +// On every other cycle they must be held stable so the receiver's 2-FF +// synchronizer sees a clean payload regardless of where its sample window +// lands relative to cmd_valid. +// +// The current FSM satisfies this implicitly — the payload regs are only +// written in RD_PROCESS, never elsewhere — but until now nothing flagged +// a regression that introduced a stray write somewhere in the always +// block. This checker fires `[ASSERT FAIL]` on any payload change while +// cmd_valid is low, surfacing the violation in the simulation log +// without affecting synthesis. +// ============================================================================ +`ifdef SIMULATION +reg [31:0] cmd_data_prev; +reg [7:0] cmd_opcode_prev; +reg [7:0] cmd_addr_prev; +reg [15:0] cmd_value_prev; +reg cmd_valid_prev; + +always @(posedge ft_clk or negedge ft_reset_n) begin + if (!ft_reset_n) begin + cmd_data_prev <= 32'd0; + cmd_opcode_prev <= 8'd0; + cmd_addr_prev <= 8'd0; + cmd_value_prev <= 16'd0; + cmd_valid_prev <= 1'b0; + end else begin + // Payload may legally change only on the cycle cmd_valid rises + // (cmd_valid_prev=0, cmd_valid=1). Any other change is a hold + // violation. + if (!cmd_valid && !cmd_valid_prev) begin + if (cmd_data !== cmd_data_prev) + $display("[ASSERT FAIL] TX-N9: cmd_data changed while cmd_valid=0 (%h -> %h)", + cmd_data_prev, cmd_data); + if (cmd_opcode !== cmd_opcode_prev) + $display("[ASSERT FAIL] TX-N9: cmd_opcode changed while cmd_valid=0 (%h -> %h)", + cmd_opcode_prev, cmd_opcode); + if (cmd_addr !== cmd_addr_prev) + $display("[ASSERT FAIL] TX-N9: cmd_addr changed while cmd_valid=0 (%h -> %h)", + cmd_addr_prev, cmd_addr); + if (cmd_value !== cmd_value_prev) + $display("[ASSERT FAIL] TX-N9: cmd_value changed while cmd_valid=0 (%h -> %h)", + cmd_value_prev, cmd_value); + end + cmd_data_prev <= cmd_data; + cmd_opcode_prev <= cmd_opcode; + cmd_addr_prev <= cmd_addr; + cmd_value_prev <= cmd_value; + cmd_valid_prev <= cmd_valid; + end +end +`endif + endmodule