diff --git a/hdl/ip/bsv/I2C/I2CBitController.bsv b/hdl/ip/bsv/I2C/I2CBitController.bsv index 6fa9c1de..44a34bc9 100644 --- a/hdl/ip/bsv/I2C/I2CBitController.bsv +++ b/hdl/ip/bsv/I2C/I2CBitController.bsv @@ -48,6 +48,9 @@ interface I2CBitController; interface Put#(Event) send; interface Get#(Event) receive; + // way of indicating when there is no bus activity + method Bool busy(); + // clock stretching information sent sideband from the state machine events method Bool scl_stretch_seen(); method Bool scl_stretch_timeout(); @@ -155,14 +158,14 @@ module mkI2CBitController #( // After the delay we know SCL is being stretched if we aren't the ones // holding it low. (* fire_when_enabled *) - rule do_sample_scl_stretch(scl_stretch_sample_strobe && scl_in == 0); - scl_stretching <= scl_out_en == 0; + rule do_sample_scl_stretch(scl_stretch_sample_strobe); + scl_stretching <= scl_out_en == 0 && scl_in == 0; scl_stretch_sample_delay <= False; endrule // If SCL is high then no one is holding it (* fire_when_enabled *) - rule do_clear_scl_stretch(scl_in == 1); + rule do_clear_scl_stretch(scl_in == 1 && !scl_stretch_sample_strobe); scl_stretching <= False; endrule @@ -251,6 +254,8 @@ module mkI2CBitController #( (* fire_when_enabled *) rule do_scl_stretch_timeout(scl_stretch_timeout_cntr); state <= AwaitStart; + scl_active <= False; + sda_out_en <= 0; scl_stretch_timeout_r <= True; incoming_events.clear(); endrule @@ -416,6 +421,8 @@ module mkI2CBitController #( endinterface interface Get receive = toGet(outgoing_events); + method busy = state != AwaitStart; + method scl_stretch_seen = scl_stretch_seen_r; method scl_stretch_timeout = scl_stretch_timeout_r; endmodule diff --git a/hdl/ip/bsv/I2C/I2CCore.bsv b/hdl/ip/bsv/I2C/I2CCore.bsv index 74d60177..8e302225 100644 --- a/hdl/ip/bsv/I2C/I2CCore.bsv +++ b/hdl/ip/bsv/I2C/I2CCore.bsv @@ -13,6 +13,7 @@ export Pins(..); export I2CCore(..); export mkI2CCore; +import ConfigReg::*; import DefaultValue::*; import DReg::*; import FIFO::*; @@ -105,12 +106,11 @@ module mkI2CCore#(Integer core_clk_freq, Reg#(Bool) in_random_read <- mkReg(False); Reg#(Bool) in_write_ack_poll <- mkReg(False); Reg#(Bool) write_acked <- mkReg(False); - + ConfigReg#(Bool) state_cleared <- mkConfigReg(False); + Reg#(Bool) clearing_state <- mkReg(False); + PulseWire clear_state <- mkPulseWire; PulseWire next_send_data <- mkPulseWire; - // relabel this net for brevity - let timed_out = bit_ctrl.scl_stretch_timeout; - (* fire_when_enabled, no_implicit_conditions *) rule do_valid_command; valid_command <= isValid(cur_command); @@ -118,27 +118,45 @@ module mkI2CCore#(Integer core_clk_freq, // when the bit controller has timed out, clear core state (* fire_when_enabled *) - rule do_handle_stretch_timeout(timed_out); - next_command.clear(); - error_r <= tagged Invalid; - state_r <= Idle; + rule do_clearing_state_reg; + clearing_state <= (bit_ctrl.scl_stretch_timeout && !state_cleared) || clear_state; endrule (* fire_when_enabled *) - rule do_register_command(state_r == Idle && !valid_command && !timed_out); - cur_command <= tagged Valid next_command.first; - error_r <= tagged Invalid; - state_r <= SendStart; + rule do_handle_stretch_timeout(clearing_state); + next_command.deq(); + bytes_done <= 0; + in_random_read <= False; + in_write_ack_poll <= False; + write_acked <= False; + cur_command <= tagged Invalid; + state_r <= Idle; endrule (* fire_when_enabled *) - rule do_send_start (state_r == SendStart && valid_command && !timed_out); + rule do_state_cleared_reg; + if (clearing_state) begin + state_cleared <= True; + end else if (valid_command && bit_ctrl.busy()) begin + state_cleared <= False; + end + endrule + + (* fire_when_enabled *) + rule do_register_command(state_r == Idle && !valid_command && !clearing_state); + cur_command <= tagged Valid next_command.first; + error_r <= tagged Invalid; + state_r <= SendStart; + endrule + + (* fire_when_enabled *) + rule do_send_start (state_r == SendStart && valid_command && !clearing_state); bit_ctrl.send.put(tagged Start); state_r <= SendAddr; endrule (* fire_when_enabled *) - rule do_send_addr (state_r == SendAddr && valid_command && !timed_out); + rule do_send_addr (state_r == SendAddr && valid_command && !clearing_state); let cmd = fromMaybe(?, cur_command); let is_read = (cmd.op == Read) || in_random_read; let addr_byte = {cmd.i2c_addr, pack(is_read)}; @@ -149,14 +167,14 @@ module mkI2CCore#(Integer core_clk_freq, (* fire_when_enabled *) rule do_await_addr_ack (state_r == AwaitAddrAck && valid_command - && !timed_out); + && !clearing_state); let ack_nack <- bit_ctrl.receive.get(); let cmd = fromMaybe(?, cur_command); case (ack_nack) matches tagged Ack: begin - // Begin a Read if (cmd.op == Read || in_random_read) begin + // begin a Read bytes_done <= 1; bit_ctrl.send.put(tagged Read (cmd.num_bytes == 1)); state_r <= Reading; @@ -164,6 +182,7 @@ module mkI2CCore#(Integer core_clk_freq, write_acked <= True; state_r <= Stop; end else begin + // begin a Write bit_ctrl.send.put(tagged Write cmd.reg_addr); state_r <= AwaitWriteAck; end @@ -180,7 +199,7 @@ module mkI2CCore#(Integer core_clk_freq, endrule (* fire_when_enabled *) - rule do_writing (state_r == Writing && valid_command && !timed_out); + rule do_writing (state_r == Writing && valid_command && !clearing_state); bit_ctrl.send.put(tagged Write tx_data); next_send_data.send(); state_r <= AwaitWriteAck; @@ -189,7 +208,7 @@ module mkI2CCore#(Integer core_clk_freq, (* fire_when_enabled *) rule do_await_writing_ack (state_r == AwaitWriteAck && valid_command - && !timed_out); + && !clearing_state); let ack_nack <- bit_ctrl.receive.get(); let cmd = fromMaybe(?, cur_command); @@ -215,7 +234,7 @@ module mkI2CCore#(Integer core_clk_freq, endrule (* fire_when_enabled *) - rule do_reading (state_r == Reading && valid_command && !timed_out); + rule do_reading (state_r == Reading && valid_command && !clearing_state); let rdata <- bit_ctrl.receive.get(); let cmd = fromMaybe(?, cur_command); @@ -233,14 +252,14 @@ module mkI2CCore#(Integer core_clk_freq, endcase endrule - rule do_next_read (state_r == NextRead && valid_command && !timed_out); + rule do_next_read (state_r == NextRead && valid_command && !clearing_state); let cmd = fromMaybe(?, cur_command); bit_ctrl.send.put(tagged Read (cmd.num_bytes == bytes_done)); state_r <= Reading; endrule (* fire_when_enabled *) - rule do_stop (state_r == Stop && valid_command && !timed_out); + rule do_stop (state_r == Stop && valid_command && !clearing_state); bit_ctrl.send.put(tagged Stop); let cmd = fromMaybe(?, cur_command); @@ -253,14 +272,8 @@ module mkI2CCore#(Integer core_clk_freq, endrule (* fire_when_enabled *) - rule do_done (state_r == Done && valid_command && !timed_out); - next_command.deq(); - bytes_done <= 0; - in_random_read <= False; - in_write_ack_poll <= False; - write_acked <= False; - cur_command <= tagged Invalid; - state_r <= Idle; + rule do_done (state_r == Done && valid_command && !clearing_state && !bit_ctrl.busy()); + clear_state.send(); endrule interface pins = bit_ctrl.pins; diff --git a/hdl/ip/bsv/I2C/test/I2CBitControllerTests.bsv b/hdl/ip/bsv/I2C/test/I2CBitControllerTests.bsv index dfa547a7..b3843152 100644 --- a/hdl/ip/bsv/I2C/test/I2CBitControllerTests.bsv +++ b/hdl/ip/bsv/I2C/test/I2CBitControllerTests.bsv @@ -89,7 +89,6 @@ module mkBench (Bench); Reg#(Vector#(3,Bit#(8))) prev_written_bytes <- mkReg(replicate(0)); Reg#(UInt#(2)) bytes_done <- mkReg(0); Reg#(Bool) is_last_byte <- mkReg(False); - Reg#(Bool) stretch_timeout <- mkReg(False); FSM write_seq <- mkFSMWithPred(seq dut.send.put(tagged Start); @@ -120,7 +119,7 @@ module mkBench (Bench); dut.send.put(tagged Stop); check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); - endseq, command_r.op == Write && !stretch_timeout); + endseq, command_r.op == Write && !dut.scl_stretch_timeout); FSM read_seq <- mkFSMWithPred(seq dut.send.put(tagged Start); @@ -153,7 +152,7 @@ module mkBench (Bench); dut.send.put(tagged Stop); check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); - endseq, command_r.op == Read && !stretch_timeout); + endseq, command_r.op == Read && !dut.scl_stretch_timeout); FSM rnd_read_seq <- mkFSMWithPred(seq dut.send.put(tagged Start); diff --git a/hdl/ip/bsv/I2C/test/I2CCoreTests.bsv b/hdl/ip/bsv/I2C/test/I2CCoreTests.bsv index 531e8bfd..1978f8b0 100644 --- a/hdl/ip/bsv/I2C/test/I2CCoreTests.bsv +++ b/hdl/ip/bsv/I2C/test/I2CCoreTests.bsv @@ -135,7 +135,7 @@ module mkBench (Bench); endaction endseq check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); - endseq, command_r.op == Write); + endseq, command_r.op == Write && !dut.scl_stretch_timeout); FSM read_seq <- mkFSMWithPred(seq dut.send_command.put(command_r); @@ -156,7 +156,7 @@ module mkBench (Bench); check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read"); check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); bytes_done <= 0; - endseq, command_r.op == Read); + endseq, command_r.op == Read && !dut.scl_stretch_timeout); FSM rand_read_seq <- mkFSMWithPred(seq dut.send_command.put(command_r); @@ -181,7 +181,7 @@ module mkBench (Bench); check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read"); check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); bytes_done <= 0; - endseq, command_r.op == RandomRead); + endseq, command_r.op == RandomRead && !dut.scl_stretch_timeout); rule do_handle_stretch_timeout(dut.scl_stretch_timeout); write_seq.abort(); diff --git a/hdl/ip/bsv/I2C/test/I2CPeripheralModel.bsv b/hdl/ip/bsv/I2C/test/I2CPeripheralModel.bsv index 19ecef8f..1c386d18 100644 --- a/hdl/ip/bsv/I2C/test/I2CPeripheralModel.bsv +++ b/hdl/ip/bsv/I2C/test/I2CPeripheralModel.bsv @@ -33,6 +33,8 @@ interface I2CPeripheralModel; interface Get#(ModelEvent) receive; method Action nack_response(Bool ack); method Action stretch_next(Bool timeout); + method Action bus_pullups(Bool present); + method Action reset_device; endinterface typedef union tagged { @@ -110,6 +112,9 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, Reg#(Bool) countdown_reset <- mkReg(False); Reg#(Bool) back_to_rx <- mkReg(False); + ConfigReg#(Bool) pullups_lost <- mkConfigReg(False); + PulseWire reset_device_ <- mkPulseWire(); + (* fire_when_enabled *) rule do_detect_scl_fedge; scl_in_prev <= scl_in; @@ -146,13 +151,19 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, scl_stretch_countdown <= 0; endrule + (* fire_when_enabled *) + rule do_reset_state (reset_device_); + state <= AwaitStartByte; + outgoing_events.clear(); + endrule + (* fire_when_enabled *) rule do_scl_stretch_tick(state == SclStretch && scl_in == 1); scl_stretch_countdown.send(); endrule (* fire_when_enabled *) - rule do_await_start (state == AwaitStartByte); + rule do_await_start (!reset_device_ && state == AwaitStartByte); shift_bits <= shift_bits_reset; is_sequential <= False; if (start_detected) begin @@ -162,7 +173,7 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_receive_start_byte (state == ReceiveStartByte); + rule do_receive_start_byte (!reset_device_ && state == ReceiveStartByte); addr_set <= False; if (scl_redge) begin case (last(shift_bits)) matches @@ -186,11 +197,10 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, end end endcase - endrule (* fire_when_enabled *) - rule do_receive_byte (state == ReceiveByte); + rule do_receive_byte (!reset_device_ && state == ReceiveByte); if (stop_detected) begin state <= AwaitStartByte; outgoing_events.enq(tagged ReceivedStop); @@ -235,7 +245,7 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_transmit_byte (state == TransmitByte); + rule do_transmit_byte (!reset_device_ && state == TransmitByte); if (scl_stretch_countdown.count() > 0) begin state <= SclStretch; back_to_rx <= False; @@ -255,7 +265,7 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_receive_ack (state == ReceiveAck); + rule do_receive_ack (!reset_device_ && state == ReceiveAck); if (scl_redge) begin if (sda_in == 0) begin // ACK'd, set up next byte to read @@ -273,7 +283,7 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_transmit_ack (state == TransmitAck); + rule do_transmit_ack (!reset_device_ && state == TransmitAck); sda_out <= pack(nack_response_); do_write <= False; do_read <= False; @@ -290,7 +300,7 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_scl_stretch (state == SclStretch); + rule do_scl_stretch (!reset_device_ && state == SclStretch); if (scl_stretch_countdown) begin scl_out <= 1; if (back_to_rx) begin @@ -304,13 +314,18 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endrule (* fire_when_enabled *) - rule do_await_stop (state == AwaitStop); + rule do_await_stop (!reset_device_ && state == AwaitStop); if (stop_detected) begin state <= AwaitStartByte; outgoing_events.enq(tagged ReceivedStop); end endrule + (* fire_when_enabled *) + rule do_pullup_sda (state != TransmitAck && state != TransmitByte); + sda_out <= 1; + endrule + method Action scl_i(Bit#(1) scl_i_next); scl_in._write(scl_i_next); if (scl_i_next == 1 && scl_in == 0) begin @@ -319,13 +334,21 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, endmethod method Bit#(1) scl_o(); - return scl_out; + if (pullups_lost) begin + return 0; + end else begin + return scl_out; + end endmethod method Action sda_i(Bit#(1) sda_i_next) = sda_in._write(sda_i_next); method Bit#(1) sda_o(); - return sda_out; + if (pullups_lost) begin + return 0; + end else begin + return sda_out; + end endmethod method Action nack_response(Bool ack) = nack_response_._write(ack); @@ -341,6 +364,10 @@ module mkI2CPeripheralModel #(Bit#(7) i2c_address, end endmethod + method Action bus_pullups(Bool present) = pullups_lost._write(!present); + + method Action reset_device = reset_device_.send(); + interface Get receive = toGet(outgoing_events); endmodule diff --git a/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.bsv b/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.bsv index 2fb7d412..cc501ca3 100644 --- a/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.bsv +++ b/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.bsv @@ -33,7 +33,7 @@ QsfpModuleController::Parameters qsfp_test_params = core_clk_period_ns: i2c_test_params.core_clk_period_ns, i2c_frequency_hz: i2c_test_params.scl_freq_hz, power_good_timeout_ms: 10, - t_init_ms: 20, // normally 2000, but sped up for simulation + t_init_ms: 5, // normally 2000, but sped up for simulation t_clock_hold_us: i2c_test_params.max_scl_stretch_us }; @@ -92,6 +92,9 @@ interface Bench; // Expose if the module has been initialized or not method Bool module_initialized; + + // Reset the state of the perhiperhal model + method Action reset_peripheral; endinterface module mkBench (Bench); @@ -130,9 +133,17 @@ module mkBench (Bench); // Connect I2C busses since TriStates cannot be simulated mkConnection(controller.pins.scl.out, periph.scl_i); - mkConnection(controller.pins.scl.in, periph.scl_o); + mkConnection(periph.scl_o, controller.pins.scl.in); mkConnection(controller.pins.sda.out, periph.sda_i); - mkConnection(controller.pins.sda.in, periph.sda_o); + mkConnection(periph.sda_o, controller.pins.sda.in); + + // We need the ability to simulate the bus losing its pull-ups when a module has not been + // inserted since that is how the design behaves. We only apply power to the module (and by the + // board design, it's bus) when a module is present. This is kind of janky given we can't + // properly simulate tristate logic in bluesim. + rule do_pullup_simulation; + periph.bus_pullups(controller.pg); + endrule // Used to make dummy data for the DUT to pull from Reg#(UInt#(8)) fifo_idx <- mkReg(0); @@ -141,6 +152,7 @@ module mkBench (Bench); Reg#(Command) command_r <- mkReg(defaultValue); PulseWire new_command <- mkPulseWire(); Reg#(UInt#(8)) bytes_done <- mkReg(0); + Reg#(Bool) timeout_expected <- mkReg(False); // TODO: This should become a RAM that I can dynamically read/write to so I // can read values I expect to have written without relying on bytes_done @@ -166,6 +178,12 @@ module mkBench (Bench); check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); bytes_done <= 0; + + // The I2CCore will ack-poll to make sure the write took. In this test bench the + // peripheral will ack the first try. We need to handle those events. + check_peripheral_event(periph, tagged ReceivedStart, "Expected model to receive START"); + check_peripheral_event(periph, tagged AddressMatch, "Expected address to match"); + check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); endseq endseq, command_r.op == Write); @@ -180,37 +198,37 @@ module mkBench (Bench); check_peripheral_event(periph, tagged ReceivedStart, "Expected model to receive START"); check_peripheral_event(periph, tagged AddressMatch, "Expected address to match"); - while (bytes_done < command_r.num_bytes) seq - check_peripheral_event(periph, tagged TransmittedData pack(bytes_done)[7:0], "Expected to transmit the data which was previously written"); + // hacky way to handle the timeout in the testbench context + if (timeout_expected) seq + await(unpack(controller.registers.port_status.error[2:0]) == I2cSclStretchTimeout); + endseq else seq + while (bytes_done < command_r.num_bytes) seq + check_peripheral_event(periph, tagged TransmittedData pack(bytes_done)[7:0], "Expected to transmit the data which was previously written"); - if (bytes_done + 1 < command_r.num_bytes) seq - check_peripheral_event(periph, tagged ReceivedAck, "Expected to receive ACK to send next byte"); - endseq + if (bytes_done + 1 < command_r.num_bytes) seq + check_peripheral_event(periph, tagged ReceivedAck, "Expected to receive ACK to send next byte"); + endseq - bytes_done <= bytes_done + 1; - endseq - - check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read"); - check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); - bytes_done <= 0; - await(!(unpack(controller.registers.port_status.busy))); + bytes_done <= bytes_done + 1; + endseq - // drain read data FIFO - while (bytes_done < command_r.num_bytes) seq - action - let d <- controller.registers.i2c_data; - assert_eq(d, pack(bytes_done), "Expected data in FIFO to match"); - endaction - bytes_done <= bytes_done + 1; + check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read"); + check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP"); + bytes_done <= 0; + await(!(unpack(controller.registers.port_status.busy))); + + // drain read data FIFO + while (bytes_done < command_r.num_bytes) seq + action + let d <- controller.registers.i2c_data; + assert_eq(d, pack(bytes_done), "Expected data in FIFO to match"); + endaction + bytes_done <= bytes_done + 1; + endseq endseq endseq endseq, command_r.op == Read); - rule do_handle_stretch_timeout(unpack(controller.registers.port_status.error[2:0]) == I2cSclStretchTimeout); - write_seq.abort(); - read_seq.abort(); - endrule - interface registers = controller.registers; method i2c_busy = !write_seq.done() || !read_seq.done() || new_command; @@ -231,6 +249,7 @@ module mkBench (Bench); end else if (stretch_timeout) begin periph.stretch_next(True); end + timeout_expected <= stretch_timeout; endmethod method intl = pack(controller.intl); @@ -268,6 +287,8 @@ module mkBench (Bench); method hsc_pg_lost = controller.pg_lost; method module_initialized = controller.module_initialized; + + method Action reset_peripheral = periph.reset_device(); endmodule function Stmt insert_and_power_module(Bench bench); @@ -289,6 +310,21 @@ function Stmt insert_and_power_module(Bench bench); endseq); endfunction +function Stmt remove_and_power_down_module(Bench bench); + return (seq + bench.set_modprsl(1); + // modprsl is debounced, so wait for it to transition + await(bench.modprsl == 1); + delay(5); + assert_false(bench.hsc_en(), + "Hot swap should be disabled when module is missing"); + // after some delay, remove power good + bench.set_hsc_pg(0); + // power good is debounced, so wait for it to transition + await(!bench.hsc_pg); + endseq); +endfunction + function Stmt deassert_reset_and_await_init(Bench bench); return (seq // release reset @@ -326,6 +362,9 @@ module mkNoModuleTest (Empty); assert_eq(unpack(bench.registers.port_status.error[2:0]), NoModule, "NoModule error should be present when attempting to communicate with a device which is not present."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -352,6 +391,9 @@ module mkNoPowerTest (Empty); assert_eq(unpack(bench.registers.port_status.error[2:0]), NoPower, "NoPower error should be present when attempting to communicate before the hot swap is stable."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -385,6 +427,9 @@ module mkRemovePowerEnableTest (Empty); await(!bench.hsc_pg); delay(3); assert_eq(bench.hsc_en(), False, "Expect hot swap to no longer be enabled."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -410,6 +455,9 @@ module mkPowerGoodTimeoutTest (Empty); assert_eq(unpack(bench.registers.port_status.error[2:0]), PowerFault, "PowerFault error should be present when attempting to communicate after the hot swap has timed out"); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -437,6 +485,9 @@ module mkPowerGoodLostTest (Empty); assert_eq(unpack(bench.registers.port_status.error[2:0]), PowerFault, "PowerFault error should be present when attempting to communicate after the hot swap has aborted"); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -462,6 +513,9 @@ module mkI2CReadTest (Empty); assert_eq(unpack(bench.registers.port_status.stretching_seen), False, "Should not have observed and SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); delay(5); endseq); endmodule @@ -487,6 +541,9 @@ module mkI2CWriteTest (Empty); assert_eq(unpack(bench.registers.port_status.stretching_seen), False, "Should not have observed and SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); delay(5); endseq); endmodule @@ -535,6 +592,9 @@ module mkInitializationTest (Empty); assert_eq(unpack(bench.registers.port_status.error[2:0]), NotInitialized, "NotInitialized error should be present when resetl is asserted."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); delay(5); endseq); endmodule @@ -562,19 +622,18 @@ module mkUninitializationAfterRemovalTest (Empty); NoError, "NoError should be present when attempting to communicate after t_init has elapsed."); - bench.set_modprsl(1); - // ModPrsL is debounced and thus won't transition immediately - await(bench.modprsl == 1); - bench.set_modprsl(0); - await(bench.modprsl == 0); - delay(3); // wait a few cycles for power to re-enable - bench.command(read_cmd, False, False); + remove_and_power_down_module(bench); + insert_and_power_module(bench); + bench.command(read_cmd, False, False); await(!bench.i2c_busy()); delay(3); assert_eq(unpack(bench.registers.port_status.error[2:0]), NotInitialized, "NotInitialized error should be present when a module has been reseated but not initialized."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); endseq); endmodule @@ -605,6 +664,9 @@ module mkNoLPModeWhenModuleIsUnpoweredTest (Empty); await(bench.hsc_pg); // wait out debounce assert_set(bench.lpmode, "LpMode should be asserted now that 3.3V is up."); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); endseq); endmodule @@ -620,6 +682,9 @@ module mkIntLTest (Empty); bench.set_intl(0); await(bench.intl == 0); assert_not_set(bench.intl, "IntL should be low after debounce"); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); endseq); endmodule @@ -635,13 +700,17 @@ module mkModPrsLTest (Empty); bench.set_modprsl(0); await(bench.modprsl == 0); assert_not_set(bench.modprsl, "ModPrsL should be low after debounce"); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed and SCL stretching."); endseq); endmodule // mkI2CSclStretchTest // // This test reads an entire page 8 bytes of module memory and the module will -// stretch SCL. +// stretch SCL. It also tests various conditions around module removal, re-insertion, and a device +// that won't stretch. module mkI2CSclStretchTest (Empty); Bench bench <- mkBench(); @@ -652,6 +721,13 @@ module mkI2CSclStretchTest (Empty); num_bytes: 8 }; + Command set_addr_cmd = Command { + op: Write, + i2c_addr: i2c_test_params.peripheral_addr, + reg_addr: 8'h00, + num_bytes: 0 + }; + mkAutoFSM(seq delay(5); add_and_initialize_module(bench); @@ -659,18 +735,51 @@ module mkI2CSclStretchTest (Empty); await(!bench.i2c_busy()); assert_eq(unpack(bench.registers.port_status.stretching_seen), True, - "Should have observed and SCL stretching."); + "Should have observed SCL stretching."); assert_eq(unpack(bench.registers.port_status.error[2:0]), NoError, "NoError should be present when a transaction completed successfully."); delay(5); + + // SCL stretch is latched per I2C transaction, so expect it to stick around even after the + // module has been removed and reinserted. + remove_and_power_down_module(bench); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + True, + "Should still have observed SCL stretching after module removal"); + insert_and_power_module(bench); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + True, + "Should still have observed SCL stretching after module reinsertion"); + + // The module should be able to complete the next transaction successfully and the SCL + // stretch seen register cleared. + deassert_reset_and_await_init(bench); + bench.command(set_addr_cmd, False, False); + await(!bench.i2c_busy()); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); + + bench.command(read_cmd, False, False); + await(!bench.i2c_busy()); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); endseq); endmodule // mkI2CSclStretchTimeoutTest // // This test reads an entire page 8 bytes of module memory and the module will -// stretch SCL for too long and the I2C core should timeout. +// stretch SCL for too long and the I2C core should timeout. It also tests various conditions around +// module removal, re-insertion, and a device that won't stretch. module mkI2CSclStretchTimeoutTest (Empty); Bench bench <- mkBench(); @@ -681,6 +790,13 @@ module mkI2CSclStretchTimeoutTest (Empty); num_bytes: 8 }; + Command set_addr_cmd = Command { + op: Write, + i2c_addr: i2c_test_params.peripheral_addr, + reg_addr: 8'h00, + num_bytes: 0 + }; + mkAutoFSM(seq delay(5); add_and_initialize_module(bench); @@ -693,6 +809,47 @@ module mkI2CSclStretchTimeoutTest (Empty); I2cSclStretchTimeout, "I2cSclStretchTimeout error should be present when a module stretching SCL too long."); delay(5); + + // unwedge the timed out peripheral since the I2CCore gives up + bench.reset_peripheral(); + + // SCL stretch is latched per I2C transaction, so expect it to stick around even after the + // module has been removed and reinserted. + remove_and_power_down_module(bench); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + True, + "Should still have observed SCL stretching after module removal"); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + I2cSclStretchTimeout, + "I2cSclStretchTimeout error should be present when a module stretching SCL too long."); + insert_and_power_module(bench); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + True, + "Should still have observed SCL stretching after module reinsertion"); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + I2cSclStretchTimeout, + "I2cSclStretchTimeout error should be present when a module stretching SCL too long."); + + // The module should be able to complete the next transaction successfully and the SCL + // stretch seen register cleared. + deassert_reset_and_await_init(bench); + bench.command(set_addr_cmd, False, False); + await(!bench.i2c_busy()); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); + + bench.command(read_cmd, False, False); + await(!bench.i2c_busy()); + assert_eq(unpack(bench.registers.port_status.stretching_seen), + False, + "Should not have observed SCL stretching."); + assert_eq(unpack(bench.registers.port_status.error[2:0]), + NoError, + "Should not have an I2C error."); endseq); endmodule diff --git a/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.gtkw b/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.gtkw index 197c73f6..56ab986f 100644 --- a/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.gtkw +++ b/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.gtkw @@ -1,34 +1,67 @@ [*] -[*] GTKWave Analyzer v3.3.100 (w)1999-2019 BSI -[*] Mon Sep 16 21:06:14 2024 +[*] GTKWave Analyzer v3.3.104 (w)1999-2020 BSI +[*] Fri Dec 6 18:48:29 2024 [*] -[dumpfile] "\\wsl$\Ubuntu-20.04\home\aaron\Oxide\git\quartz\build\vcd\QsfpModuleControllerTests_mkI2CSclStretchTimeoutTest.vcd" -[dumpfile_mtime] "Mon Sep 16 17:50:10 2024" -[dumpfile_size] 71544432 -[savefile] "\\wsl$\Ubuntu-20.04\home\aaron\Oxide\git\quartz\hdl\projects\sidecar\qsfp_x32\QSFPModule\test\QsfpModuleControllerTests.gtkw" -[timestart] 15988190 -[size] 2558 1360 +[dumpfile] "/home/aaron/Oxide/git/quartz/build/vcd/QsfpModuleControllerTests_mkI2CSclStretchTimeoutTest.vcd" +[dumpfile_mtime] "Fri Dec 6 18:47:48 2024" +[dumpfile_size] 37880178 +[savefile] "/home/aaron/Oxide/git/quartz/hdl/projects/sidecar/qsfp_x32/QSFPModule/test/QsfpModuleControllerTests.gtkw" +[timestart] 8337500 +[size] 2592 635 [pos] -1 -1 -*-13.036575 16052670 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +*-15.176154 8552710 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 [treeopen] main. [treeopen] main.top. -[sst_width] 446 -[signals_width] 526 +[sst_width] 445 +[signals_width] 631 [sst_expanded] 1 -[sst_vpaned_height] 404 +[sst_vpaned_height] 158 @200 -DUT - QsfpModuleController -Pins @28 main.top.bench_controller_lpmode_ main.top.bench_controller_resetl_ +main.top.bench_modprsl_r @200 - @28 main.top.bench_controller_hot_swap_enabled_r main.top.bench_hsc_pg_r +main.top.bench_controller_power_good__output_r @200 - +@c00022 +main.top.bench_command_r[24:0] +@28 +(0)main.top.bench_command_r[24:0] +(1)main.top.bench_command_r[24:0] +(2)main.top.bench_command_r[24:0] +(3)main.top.bench_command_r[24:0] +(4)main.top.bench_command_r[24:0] +(5)main.top.bench_command_r[24:0] +(6)main.top.bench_command_r[24:0] +(7)main.top.bench_command_r[24:0] +(8)main.top.bench_command_r[24:0] +(9)main.top.bench_command_r[24:0] +(10)main.top.bench_command_r[24:0] +(11)main.top.bench_command_r[24:0] +(12)main.top.bench_command_r[24:0] +(13)main.top.bench_command_r[24:0] +(14)main.top.bench_command_r[24:0] +(15)main.top.bench_command_r[24:0] +(16)main.top.bench_command_r[24:0] +(17)main.top.bench_command_r[24:0] +(18)main.top.bench_command_r[24:0] +(19)main.top.bench_command_r[24:0] +(20)main.top.bench_command_r[24:0] +(21)main.top.bench_command_r[24:0] +(22)main.top.bench_command_r[24:0] +(23)main.top.bench_command_r[24:0] +(24)main.top.bench_command_r[24:0] +@1401200 +-group_end +@200 -State @28 main.top.bench_controller_i2c_attempt @@ -45,24 +78,46 @@ main.top.bench_controller_error[2:0] @28 main.top.bench_controller_rdata_fifo_deq main.top.bench_controller_wdata_fifo_deq +@22 +main.top.bench_bytes_done[7:0] @200 - -- -I2CCore +@28 +main.top.bench_controller_i2c_core_bit_ctrl_sda_out_en +main.top.bench_controller_i2c_core_bit_ctrl_scl_out_en +main.top.bench_controller_i2c_core_bit_ctrl_sda_in +main.top.bench_controller_i2c_core_bit_ctrl_scl_in @22 main.top.bench_controller_i2c_core_state_r[3:0] +main.top.bench_controller_i2c_core_cur_command[25:0] +@25 +main.top.bench_controller_i2c_core_bit_ctrl_state[2:0] +@28 +main.top.bench_controller_i2c_core_state_cleared +@22 +main.top.bench_controller_i2c_core_bytes_done[7:0] @28 +main.top.bench_controller_i2c_core_in_random_read +main.top.bench_controller_i2c_core_write_acked +main.top.bench_controller_i2c_core_in_write_ack_poll +main.top.bench_periph_scl_redge main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_seen_r -@29 main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_timeout_r -@28 main.top.bench_controller_i2c_core_bit_ctrl_scl_stretching +@24 +main.top.bench_periph_scl_stretch_countdown_count_r[15:0] +@28 +main.top.bench_periph_scl_stretch_countdown_q +@24 +main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_timeout_cntr_count[15:0] +@28 +main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_timeout_cntr_q +@24 +main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_sample_strobe_count[5:0] +@28 +main.top.bench_controller_i2c_core_bit_ctrl_scl_stretch_sample_strobe_q @200 --FIFO: Next Command --FIFO: BitControl Incoming Events --FIFO: BitControl Outgoing Events --FIFO: RX Data --FIFO: TX Data - -WDATA FIFO @28 @@ -104,7 +159,6 @@ main.top.bench_controller_rdata_fifo_memory.ADDRB[7:0] main.top.bench_fifo_idx[7:0] @200 - -- -Model - I2CPeripheralModel @28 main.top.bench_periph_scl_in @@ -113,13 +167,13 @@ main.top.bench_periph_sda_in main.top.bench_periph_sda_out main.top.bench_periph_addr_set @22 +main.top.bench_periph_cur_addr[7:0] +@28 +main.top.bench_periph_is_read +@22 main.top.bench_periph_shift_bits[15:0] @800024 main.top.bench_periph_state[2:0] -@28 -(0)main.top.bench_periph_state[2:0] -(1)main.top.bench_periph_state[2:0] -(2)main.top.bench_periph_state[2:0] @1001200 -group_end @22