From 8fdfa33371b05ed151bd2d87e3d1b7e3b5ec380f Mon Sep 17 00:00:00 2001 From: Nathanael Huffman Date: Tue, 28 Jan 2025 13:57:03 -0600 Subject: [PATCH] Updates to muxes to support multiple instances sharing i2c bus. Add input signal so that we can ignore requests to enable multiple mux channels concurrently, even across virtual pca9545s, and test cases to check this stuff. adds the phy consolidator and test --- hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK | 1 + .../PCA9506ish/pca9506_function.vhd | 2 + .../sims/i2c_pca9506ish_sim_pkg.vhd | 28 ++-- .../PCA9506ish/sims/i2c_pca9506ish_tb.vhd | 154 ++++++++++-------- .../PCA9506ish/sims/i2c_pca9506ish_th.vhd | 92 ++++++++--- hdl/ip/vhd/i2c/muxes/PCA9545ish/BUCK | 2 + .../muxes/PCA9545ish/pca9545ish_function.vhd | 15 +- .../i2c/muxes/PCA9545ish/pca9545ish_top.vhd | 2 + .../PCA9545ish/sims/i2c_pca9545ish_tb.vhd | 94 +++++++---- .../PCA9545ish/sims/i2c_pca9545ish_th.vhd | 79 +++++++-- hdl/ip/vhd/i2c/target/BUCK | 7 +- .../vhd/i2c/target/i2c_phy_consolidator.vhd | 65 ++++++++ hdl/ip/vhd/i2c/target/i2c_target_phy.vhd | 38 +++-- 13 files changed, 421 insertions(+), 158 deletions(-) create mode 100644 hdl/ip/vhd/i2c/target/i2c_phy_consolidator.vhd diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK index 27ea5284..913c4f86 100644 --- a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK @@ -24,6 +24,7 @@ vunit_sim( deps = [ ":pca9506_top", "//hdl/ip/vhd/vunit_components:i2c_controller_vc", + "//hdl/ip/vhd/i2c/target:i2c_phy_consolidator", ], visibility = ['PUBLIC'], ) \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd index 5a770e91..7b047ca9 100644 --- a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd @@ -91,6 +91,8 @@ begin case r.state is when IDLE => v.increment := '0'; + v.data_valid := '0'; + v.post_ack_state := IDLE; if txn_header.valid = '1' and txn_header.tgt_addr = i2c_addr then v.state := ACK; if txn_header.read_write_n = '0' then diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd index 658887e1..8782b527 100644 --- a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd @@ -17,8 +17,6 @@ use work.i2c_ctrl_vc_pkg.all; package i2c_pca9506ish_sim_pkg is - constant i2c_tgt_addr: std_logic_vector(6 downto 0) := 7x"20"; - constant i2c_ctrl_vc : i2c_ctrl_vc_t := new_i2c_ctrl_vc("i2c_ctrl_vc"); -- Some helper functions for writing test benches. @@ -26,29 +24,29 @@ package i2c_pca9506ish_sim_pkg is -- want to be able to read/write categories of registers (wrapping) procedure single_write_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant register_addr : in integer range 0 to 16#27#; constant write_data: in std_logic_vector(7 downto 0); variable did_ack: inout boolean; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ); procedure write_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant starting_reg : in integer range 0 to 16#27#; constant byte_queue: in queue_t; constant ack_queue: in queue_t; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ); procedure read_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant starting_reg : in integer range 0 to 16#27#; constant num_regs_to_read : in integer; constant response_queue: in queue_t; constant ack_queue: in queue_t; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ); end package; @@ -57,17 +55,17 @@ package body i2c_pca9506ish_sim_pkg is procedure single_write_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant register_addr : in integer range 0 to 16#27#; constant write_data: in std_logic_vector(7 downto 0); variable did_ack: inout boolean; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ) is constant tx_queue : queue_t := new_queue; constant ack_queue: queue_t := new_queue; begin push_byte(tx_queue, to_integer(write_data)); - write_pca9506_reg(net, register_addr, tx_queue, tx_queue, auto_inc, i2c_addr); + write_pca9506_reg(net, i2c_addr, register_addr, tx_queue, tx_queue, auto_inc); did_ack := true; while not is_empty(ack_queue) loop if pop_byte(ack_queue) = 0 then @@ -79,11 +77,11 @@ package body i2c_pca9506ish_sim_pkg is procedure write_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant starting_reg : in integer range 0 to 16#27#; constant byte_queue: in queue_t; constant ack_queue: in queue_t; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ) is variable cmd_reg : std_logic_vector(7 downto 0); constant tx_queue : queue_t := new_queue; @@ -102,12 +100,12 @@ package body i2c_pca9506ish_sim_pkg is procedure read_pca9506_reg( signal net : inout network_t; + constant i2c_addr : in std_logic_vector(6 downto 0); constant starting_reg : in integer range 0 to 16#27#; constant num_regs_to_read : in integer; constant response_queue: in queue_t; constant ack_queue: in queue_t; - constant auto_inc: in boolean := true; - constant i2c_addr : in std_logic_vector(6 downto 0) := i2c_tgt_addr + constant auto_inc: in boolean := true ) is variable cmd_reg : std_logic_vector(7 downto 0); constant tx_queue : queue_t := new_queue; diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd index dd04518d..753a4982 100644 --- a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd @@ -28,6 +28,8 @@ end entity; architecture tb of i2c_pca9506ish_tb is + constant addr0 : std_logic_vector(6 downto 0) := 7x"20"; + constant addr1 : std_logic_vector(6 downto 0) := 7x"21"; begin @@ -35,10 +37,10 @@ begin bench: process alias reset is << signal th.reset : std_logic >>; - alias int_n is << signal th.int_n : std_logic >>; - alias io is << signal th.io : pca9506_pin_t >>; - alias io_o is << signal th.io_o : pca9506_pin_t >>; - alias io_oe is << signal th.io_oe : pca9506_pin_t >>; + alias int0_n is << signal th.int0_n : std_logic >>; + alias io0 is << signal th.io0 : pca9506_pin_t >>; + alias io0_o is << signal th.io0_o: pca9506_pin_t >>; + alias io0_oe is << signal th.io0_oe: pca9506_pin_t >>; variable exp_queue : queue_t := new_queue; variable tx_queue : queue_t := new_queue; variable rx_queue : queue_t := new_queue; @@ -59,20 +61,44 @@ begin while test_suite loop if run("check-read") then -- read a default value from an internal register - read_pca9506_reg(net, IP0_OFFSET, 1, rx_queue, ack_queue); - check_equal(pop_byte(rx_queue), 0, "Non-reset value read from IP0_OFFSET"); + read_pca9506_reg(net, addr0, IP0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 0, "Non-reset value read from IP0_OFFSET expander 0"); check_true(is_empty(rx_queue), "rx queue not empty"); -- check one of the default 1 registers - read_pca9506_reg(net, IOC0_OFFSET, 1, rx_queue, ack_queue); - check_equal(pop_byte(rx_queue), 255, "Non-reset value read from IOC0_OFFSET"); + read_pca9506_reg(net, addr0, IOC0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 255, "Non-reset value read from IOC0_OFFSET expander 0"); + check_true(is_empty(rx_queue), "rx queue not empty"); + + -- check other device + -- read a default value from an internal register + read_pca9506_reg(net, addr1, IP0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 0, "Non-reset value read from IP0_OFFSET expander 1"); + check_true(is_empty(rx_queue), "rx queue not empty"); + -- check one of the default 1 registers + read_pca9506_reg(net, addr1, IOC0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 255, "Non-reset value read from IOC0_OFFSET expander 1"); check_true(is_empty(rx_queue), "rx queue not empty"); elsif run("check-write") then -- write something else to a register that defaults to 1's push_byte(tx_queue, to_integer(std_logic_vector'(8x"aa"))); - write_pca9506_reg(net, IOC0_OFFSET, tx_queue, ack_queue); + write_pca9506_reg(net, addr0, IOC0_OFFSET, tx_queue, ack_queue); + flush(ack_queue); -- don't want lingering acks from this transaction + -- read it back and check + read_pca9506_reg(net, addr0, IOC0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 16#AA#, "Non-matching register at IOC0_OFFSET expander 0"); + check_true(is_empty(rx_queue), "rx queue not empty"); + -- check other channel + -- read a default value from an internal register + read_pca9506_reg(net, addr1, IOC0_OFFSET, 1, rx_queue, ack_queue); + check_equal(pop_byte(rx_queue), 255, "Non-reset value read from IOC0_OFFSET expander 1"); + check_true(is_empty(rx_queue), "rx queue not empty"); + + -- write something else to a register that defaults to 1's + push_byte(tx_queue, to_integer(std_logic_vector'(8x"aa"))); + write_pca9506_reg(net, addr1, IOC0_OFFSET, tx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction -- read it back and check - read_pca9506_reg(net, IOC0_OFFSET, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr1, IOC0_OFFSET, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), 16#AA#, "Non-matching register at IOC0_OFFSET"); check_true(is_empty(rx_queue), "rx queue not empty"); elsif run("check-auto-inc-no-sub-cat-no-wrap") then @@ -85,14 +111,14 @@ begin push_byte(tx_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); push_byte(exp_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); end loop; - write_pca9506_reg(net, IOC0_OFFSET, tx_queue, ack_queue); + write_pca9506_reg(net, addr0, IOC0_OFFSET, tx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction -- we'll read them out 1-by-one so we're not confounding any -- auto-inc or wrap issues for i in 0 to 2 loop -- read it back and check - read_pca9506_reg(net, IOC0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IOC0_OFFSET + i, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), pop_byte(exp_queue), "Non-matching register at IOC" & to_string(i) & "_OFFSET"); end loop; elsif run("check-auto-inc-no-full-cat-no-wrap") then @@ -106,14 +132,14 @@ begin push_byte(tx_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); push_byte(exp_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); end loop; - write_pca9506_reg(net, IOC0_OFFSET, tx_queue, ack_queue); + write_pca9506_reg(net, addr0, IOC0_OFFSET, tx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction -- we'll read them out 1-by-one so we're not confounding any -- auto-inc or wrap issues for i in 0 to 4 loop -- read it back and check - read_pca9506_reg(net, IOC0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IOC0_OFFSET + i, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), pop_byte(exp_queue), "Non-matching register at IOC" & to_string(i) & "_OFFSET"); end loop; @@ -132,7 +158,7 @@ begin push_byte(tx_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); push_byte(exp_queue, to_integer(To_StdLogicVector(user_int+i, 4) & To_StdLogicVector(user_int+i, 4))); end loop; - write_pca9506_reg(net, IOC0_OFFSET, tx_queue, ack_queue); + write_pca9506_reg(net, addr0, IOC0_OFFSET, tx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction -- we'll read them out 1-by-one so we're not confounding any @@ -141,10 +167,10 @@ begin -- of the loop for i in 1 to 4 loop -- read it back and check - read_pca9506_reg(net, IOC0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IOC0_OFFSET + i, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), pop_byte(exp_queue), "Non-matching register at IOC" & to_string(i) & "_OFFSET"); end loop; - read_pca9506_reg(net, IOC0_OFFSET, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IOC0_OFFSET, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), pop_byte(exp_queue), "Non-matching register at IOC0_OFFSET"); elsif run("check-no-inc-cat-writes") then @@ -160,92 +186,92 @@ begin push_byte(exp_queue, 16#FF#); -- expect reset vals for the remaining registers push_byte(exp_queue, 16#FF#); -- expect reset vals for the remaining registers - write_pca9506_reg(net, IOC0_OFFSET, tx_queue, ack_queue, auto_inc => false); + write_pca9506_reg(net, addr0, IOC0_OFFSET, tx_queue, ack_queue, auto_inc => false); flush(ack_queue); -- don't want lingering acks from this transaction for i in 0 to 4 loop -- read it back and check - read_pca9506_reg(net, IOC0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IOC0_OFFSET + i, 1, rx_queue, ack_queue); check_equal(pop_byte(rx_queue), pop_byte(exp_queue), "Non-matching register at IOC" & to_string(i) & "_OFFSET"); end loop; elsif run("check-basic-irq") then -- verify basic irq functionality - check_equal('1', int_n, "irq is asserted incorrectly at reset"); + check_equal('1', int0_n, "irq is asserted incorrectly at reset"); -- everything is an input and driven to '0' by the th initialization, but all irqs should be masked, -- toggle an input and we should still see no irq fire. - io(0)(0) <= '1'; + io0(0)(0) <= '1'; wait for 200 ns; -- allow synchronization etc - check_equal('1', int_n, "irq is asserted incorrectly after i/o toggle while masked"); - io(0)(0) <= '0'; -- set back to default + check_equal('1', int0_n, "irq is asserted incorrectly after i/o toggle while masked"); + io0(0)(0) <= '0'; -- set back to default wait for 200 ns; -- allow synchronization etc -- de-mask this irq - single_write_pca9506_reg(net, MSK0_OFFSET, X"FE", ack_status); + single_write_pca9506_reg(net, addr0, MSK0_OFFSET, X"FE", ack_status); wait for 200 ns; -- make sure things propagate. - check_equal('1', int_n, "irq is asserted incorrectly after de-masking with no toggle"); - io(0)(0) <= '1'; -- toggle pin + check_equal('1', int0_n, "irq is asserted incorrectly after de-masking with no toggle"); + io0(0)(0) <= '1'; -- toggle pin wait for 200 ns; -- allow synchronization etc - check_equal('0', int_n, "irq did not assert after de-masking and i/o toggle"); + check_equal('0', int0_n, "irq did not assert after de-masking and i/o toggle"); -- return the I/O to its previous state, irq should go away - io(0)(0) <= '0'; -- toggle pin + io0(0)(0) <= '0'; -- toggle pin wait for 200 ns; -- allow synchronization etc - check_equal('1', int_n, "irq still asserted after returning I/O to previous state"); + check_equal('1', int0_n, "irq still asserted after returning I/O to previous state"); elsif run("irq-clears-on-read") then -- de-mask this irq - single_write_pca9506_reg(net, MSK0_OFFSET, X"FE", ack_status); + single_write_pca9506_reg(net, addr0, MSK0_OFFSET, X"FE", ack_status); wait for 200 ns; -- make sure things propagate. - check_equal('1', int_n, "irq is asserted incorrectly after de-masking with no toggle"); - io(0)(0) <= '1'; -- toggle pin + check_equal('1', int0_n, "irq is asserted incorrectly after de-masking with no toggle"); + io0(0)(0) <= '1'; -- toggle pin wait for 200 ns; -- allow synchronization etc - check_equal('0', int_n, "irq did not assert after de-masking and i/o toggle"); + check_equal('0', int0_n, "irq did not assert after de-masking and i/o toggle"); -- now read the register, this should clear the irq - read_pca9506_reg(net, IP0_OFFSET, 1, rx_queue, ack_queue); - check_equal('1', int_n, "irq still asserted (incorrectly) after reading IP0_OFFSET"); + read_pca9506_reg(net, addr0, IP0_OFFSET, 1, rx_queue, ack_queue); + check_equal('1', int0_n, "irq still asserted (incorrectly) after reading IP0_OFFSET"); -- now toggle i/o again this should generate a new irq - io(0)(0) <= '0'; -- toggle pin low again + io0(0)(0) <= '0'; -- toggle pin low again wait for 200 ns; -- allow synchronization etc - check_equal('0', int_n, "irq did not assert after i/o toggle"); + check_equal('0', int0_n, "irq did not assert after i/o toggle"); elsif run("multi-register-irq") then -- verify multi-register irq functionality -- de-mask i/o (0,0) - single_write_pca9506_reg(net, MSK0_OFFSET, X"FE", ack_status); + single_write_pca9506_reg(net, addr0, MSK0_OFFSET, X"FE", ack_status); -- de-mask i/o (4,0) - single_write_pca9506_reg(net, MSK4_OFFSET, X"FE", ack_status); - check_equal('1', int_n, "irq is asserted incorrectly after de-masking with no toggle"); - io(0)(0) <= '1'; -- toggle pin - io(4)(0) <= '1'; -- toggle pin + single_write_pca9506_reg(net, addr0, MSK4_OFFSET, X"FE", ack_status); + check_equal('1', int0_n, "irq is asserted incorrectly after de-masking with no toggle"); + io0(0)(0) <= '1'; -- toggle pin + io0(4)(0) <= '1'; -- toggle pin wait for 200 ns; -- allow synchronization etc - check_equal('0', int_n, "irq did not assert after de-masking and i/o toggle"); + check_equal('0', int0_n, "irq did not assert after de-masking and i/o toggle"); -- read one of the registers, not the other, irq should still be asserted - read_pca9506_reg(net, IP0_OFFSET, 1, rx_queue, ack_queue); - check_equal('0', int_n, "irq incorrectly de-asserted after reading only IP0_OFFSET"); + read_pca9506_reg(net, addr0, IP0_OFFSET, 1, rx_queue, ack_queue); + check_equal('0', int0_n, "irq incorrectly de-asserted after reading only IP0_OFFSET"); -- now read other register, irq should clear - read_pca9506_reg(net, IP4_OFFSET, 1, rx_queue, ack_queue); - check_equal('1', int_n, "irq still asserted (incorrectly) after reading IP4_OFFSET"); + read_pca9506_reg(net, addr0, IP4_OFFSET, 1, rx_queue, ack_queue); + check_equal('1', int0_n, "irq still asserted (incorrectly) after reading IP4_OFFSET"); -- verify all inputs are read elsif run("read_inputs") then -- verify all inputs are read -- going to do this bit-wise per port so we make sure everything is matching correctly for i in 0 to 4 loop -- Loop each port for j in 0 to 7 loop -- Loop each bit - -- everything is an input so we should not see any io_oe bits set - check_equal(io_oe(i)(j), '0', "io_oe bit " & to_string(j) & " on port " & to_string(i) & " is set for an input"); + -- everything is an input so we should not see any io0_oebits set + check_equal(io0_oe(i)(j), '0', "io0_oebit " & to_string(j) & " on port " & to_string(i) & " is set for an input"); -- Set the bit under test to '1' - io(i)(j) <= '1'; + io0(i)(j) <= '1'; wait for 200 ns; -- allow synchronization etc -- Read the corresponding input register and check the value - read_pca9506_reg(net, IP0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IP0_OFFSET + i, 1, rx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction user_int := to_integer(shift_left(std_logic_vector'("00000001"), j)); check_equal(pop_byte(rx_queue), user_int, "Input bit " & to_string(j) & " on port " & to_string(i) & " not reading correctly"); -- Reset the input to its default state - io(i)(j) <= '0'; + io0(i)(j) <= '0'; wait for 200 ns; -- allow synchronization etc end loop; end loop; @@ -254,21 +280,21 @@ begin -- turn all the ports to outputs for i in 0 to 4 loop -- set port under test to all outputs - single_write_pca9506_reg(net, IOC0_OFFSET + i, X"00", ack_status); + single_write_pca9506_reg(net, addr0, IOC0_OFFSET + i, X"00", ack_status); for j in 0 to 7 loop - check_equal(io_oe(i)(j), '1', "Output enable bit " & to_string(j) & " on port " & to_string(i) & " not set correctly for output pin"); + check_equal(io0_oe(i)(j), '1', "Output enable bit " & to_string(j) & " on port " & to_string(i) & " not set correctly for output pin"); -- Set the bit under test to '1' user_int := to_integer(shift_left(std_logic_vector'("00000001"), j)); - single_write_pca9506_reg(net, OP0_OFFSET + i, to_std_logic_vector(user_int, 8), ack_status); + single_write_pca9506_reg(net, addr0, OP0_OFFSET + i, to_std_logic_vector(user_int, 8), ack_status); wait for 200 ns; -- allow synchronization etc -- check the output port - check_equal(io_o(i)(j), '1', "Output bit " & to_string(j) & " on port " & to_string(i) & " not setting correctly"); + check_equal(io0_o(i)(j), '1', "Output bit " & to_string(j) & " on port " & to_string(i) & " not setting correctly"); -- set back to 0 - single_write_pca9506_reg(net, OP0_OFFSET + i, to_std_logic_vector(0, 8), ack_status); + single_write_pca9506_reg(net, addr0, OP0_OFFSET + i, to_std_logic_vector(0, 8), ack_status); wait for 200 ns; -- allow synchronization etc -- check the output port - check_equal(io_o(i)(j), '0', "Output bit " & to_string(j) & " on port " & to_string(i) & " not clearing correctly"); + check_equal(io0_o(i)(j), '0', "Output bit " & to_string(j) & " on port " & to_string(i) & " not clearing correctly"); end loop; end loop; -- @@ -278,23 +304,23 @@ begin -- going to do this bit-wise per port so we make sure everything is matching correctly for i in 0 to 4 loop -- Loop each port -- invert all inputs - single_write_pca9506_reg(net, PI0_OFFSET + i, X"FF", ack_status); + single_write_pca9506_reg(net, addr0, PI0_OFFSET + i, X"FF", ack_status); for j in 0 to 7 loop -- Loop each bit - -- everything is an input so we should not see any io_oe bits set - check_equal(io_oe(i)(j), '0', "io_oe bit " & to_string(j) & " on port " & to_string(i) & " is set for an input"); + -- everything is an input so we should not see any io0_oebits set + check_equal(io0_oe(i)(j), '0', "io0_oebit " & to_string(j) & " on port " & to_string(i) & " is set for an input"); -- Set the bit under test to '1' - io(i)(j) <= '1'; + io0(i)(j) <= '1'; wait for 200 ns; -- allow synchronization etc -- Read the corresponding input register and check the value - read_pca9506_reg(net, IP0_OFFSET + i, 1, rx_queue, ack_queue); + read_pca9506_reg(net, addr0, IP0_OFFSET + i, 1, rx_queue, ack_queue); flush(ack_queue); -- don't want lingering acks from this transaction user_slv8 := (others => '1'); user_slv8(j) := '0'; user_int := to_integer(user_slv8); check_equal(pop_byte(rx_queue), user_int, "Input bit " & to_string(j) & " on port " & to_string(i) & " not reading correctly"); -- Reset the input to its default state - io(i)(j) <= '0'; + io0(i)(j) <= '0'; wait for 200 ns; -- allow synchronization etc end loop; end loop; diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd index c232af1e..05ff9ab4 100644 --- a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd @@ -27,16 +27,28 @@ architecture th of i2c_pca9506ish_th is signal reset : std_logic := '1'; signal i2c_bus_scl : std_logic := 'Z'; + signal i2c_bus_scl_o : std_logic; + signal i2c_bus_scl_oe : std_logic; signal i2c_bus_sda : std_logic := 'Z'; - signal tgt_scl_o : std_logic; - signal tgt_scl_oe : std_logic; - signal tgt_sda_o : std_logic; - signal tgt_sda_oe : std_logic; + signal i2c_bus_sda_o : std_logic; + signal i2c_bus_sda_oe : std_logic; - signal io : pca9506_pin_t := (others => (others => '0')); - signal io_oe : pca9506_pin_t; - signal io_o : pca9506_pin_t; - signal int_n : std_logic; + signal tgt_scl : std_logic_vector(1 downto 0); + signal tgt_scl_o : std_logic_vector(1 downto 0); + signal tgt_scl_oe : std_logic_vector(1 downto 0); + signal tgt_sda : std_logic_vector(1 downto 0); + signal tgt_sda_o : std_logic_vector(1 downto 0); + signal tgt_sda_oe : std_logic_vector(1 downto 0); + + signal io0 : pca9506_pin_t := (others => (others => '0')); + signal io0_oe : pca9506_pin_t; + signal io0_o : pca9506_pin_t; + signal int0_n : std_logic; + + signal io1 : pca9506_pin_t := (others => (others => '0')); + signal io1_oe : pca9506_pin_t; + signal io1_o : pca9506_pin_t; + signal int1_n : std_logic; begin @@ -54,27 +66,69 @@ begin sda => i2c_bus_sda ); - i2c_bus_scl <= tgt_scl_o when tgt_scl_oe = '1' else 'H'; - i2c_bus_sda <= tgt_sda_o when tgt_sda_oe = '1' else 'H'; + i2c_bus_scl <= i2c_bus_scl_o when i2c_bus_scl_oe = '1' else 'H'; + i2c_bus_sda <= i2c_bus_sda_o when i2c_bus_sda_oe = '1' else 'H'; - pca9506_top_inst: entity work.pca9506_top + DUT0: entity work.pca9506_top generic map( i2c_addr => 7x"20", giltch_filter_cycles => 3 + ) + port map( + clk => clk, + reset => reset, + scl => tgt_scl(0), + scl_o => tgt_scl_o(0), + scl_oe => tgt_scl_oe(0), + sda => tgt_sda(0), + sda_o => tgt_sda_o(0), + sda_oe => tgt_sda_oe(0), + io => io0, + io_oe => io0_oe, + io_o => io0_o, + int_n => int0_n + ); + + DUT1: entity work.pca9506_top + generic map( + i2c_addr => 7x"21", + giltch_filter_cycles => 3 + ) + port map( + clk => clk, + reset => reset, + scl => tgt_scl(1), + scl_o => tgt_scl_o(1), + scl_oe => tgt_scl_oe(1), + sda => tgt_sda(1), + sda_o => tgt_sda_o(1), + sda_oe => tgt_sda_oe(1), + io => io1, + io_oe => io1_oe, + io_o => io1_o, + int_n => int1_n + ); + + bus_consolidator: entity work.i2c_phy_consolidator + generic map( + TARGET_NUM => 2 ) port map( clk => clk, reset => reset, scl => i2c_bus_scl, - scl_o => tgt_scl_o, - scl_oe => tgt_scl_oe, + scl_o => i2c_bus_scl_o, + scl_oe => i2c_bus_scl_oe, sda => i2c_bus_sda, - sda_o => tgt_sda_o, - sda_oe => tgt_sda_oe, - io => io, - io_oe => io_oe, - io_o => io_o, - int_n => int_n + sda_o => i2c_bus_sda_o, + sda_oe => i2c_bus_sda_oe, + tgt_scl => tgt_scl, + tgt_scl_o => tgt_scl_o, + tgt_scl_oe => tgt_scl_oe, + tgt_sda => tgt_sda, + tgt_sda_o => tgt_sda_o, + tgt_sda_oe => tgt_sda_oe ); + end th; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/muxes/PCA9545ish/BUCK b/hdl/ip/vhd/i2c/muxes/PCA9545ish/BUCK index f762e116..3c5414d7 100644 --- a/hdl/ip/vhd/i2c/muxes/PCA9545ish/BUCK +++ b/hdl/ip/vhd/i2c/muxes/PCA9545ish/BUCK @@ -8,6 +8,7 @@ rdl_file( visibility = ['PUBLIC'] ) + vhdl_unit( name = "pca9545ish_top", srcs = glob(["*.vhd"]), @@ -24,6 +25,7 @@ vunit_sim( deps = [ ":pca9545ish_top", "//hdl/ip/vhd/vunit_components:i2c_controller_vc", + "//hdl/ip/vhd/i2c/target:i2c_phy_consolidator", ], visibility = ['PUBLIC'], ) \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_function.vhd b/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_function.vhd index 28d9bd18..a0df546a 100644 --- a/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_function.vhd +++ b/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_function.vhd @@ -29,6 +29,7 @@ entity pca9545ish_function is reset : in std_logic; mux_reset: in std_logic; mux_sel : out std_logic_vector(1 downto 0); + other_mux_selected : in std_logic; -- instruction interface inst_data : in std_logic_vector(7 downto 0); inst_valid : in std_logic; @@ -47,8 +48,14 @@ end entity; architecture rtl of pca9545ish_function is - function is_valid(data : std_logic_vector(7 downto 0)) return boolean is + function is_valid( + data : std_logic_vector(7 downto 0); + other_mux_selected : std_logic) return boolean is begin + -- if another mux is selected, only writes of 0 are allowed + if other_mux_selected = '1' and data /= "00000000" then + return false; + end if; -- only allow clear and one-hot bits 0-2 case data is when "00000000" => return true; @@ -70,7 +77,7 @@ architecture rtl of pca9545ish_function is begin inst_ready <= '1'; -- never block writes - resp_valid <= '1'; -- never block reads + resp_valid <= is_our_transaction; -- never block reads resp_data <= pack(control_reg); -- Only one register to read so hand it back always -- register block @@ -84,7 +91,7 @@ begin if mux_reset then control_reg <= rec_reset; elsif inst_valid = '1' and inst_ready = '1' - and is_valid(inst_data) + and is_valid(inst_data, other_mux_selected) and is_valid_write(txn_header) and is_our_transaction = '1' then control_reg <= unpack(inst_data); @@ -121,7 +128,7 @@ begin if in_ack_phase_last = '1' and in_ack_phase = '0' then ack_next <= '0'; -- Ack on valid data when we're in the middle of a transaction (and we're being addressed) - elsif inst_valid = '1' and inst_ready = '1' and is_valid(inst_data) and is_our_transaction = '1' then + elsif inst_valid = '1' and inst_ready = '1' and is_valid(inst_data, other_mux_selected) and is_our_transaction = '1' then ack_next <= '1'; -- Ack on the address byte when we're in the start of a transaction and we're being addressed elsif txn_header.tgt_addr = i2c_addr and txn_header.valid = '1' and is_our_transaction = '0' then diff --git a/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_top.vhd b/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_top.vhd index 6653bdec..40a1b8c6 100644 --- a/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_top.vhd +++ b/hdl/ip/vhd/i2c/muxes/PCA9545ish/pca9545ish_top.vhd @@ -40,6 +40,7 @@ entity pca9545ish_top is -- Signal to reset the mux-state out of band if desired. mux_reset: in std_logic; + other_mux_selected: in std_logic := '0'; -- I2C bus mux endpoint for control -- Does not support clock-stretching scl : in std_logic; @@ -102,6 +103,7 @@ begin reset => reset, mux_reset => mux_reset, mux_sel => mux_sel, + other_mux_selected => other_mux_selected, inst_data => inst_data, inst_valid => inst_valid, inst_ready => inst_ready, diff --git a/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_tb.vhd b/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_tb.vhd index fbf49c33..cccad3ae 100644 --- a/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_tb.vhd +++ b/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_tb.vhd @@ -32,13 +32,17 @@ architecture tb of i2c_pca9545ish_tb is constant chC_selected : std_logic_vector(1 downto 0) := "01"; constant not_selected : std_logic_vector(1 downto 0) := "11"; + constant mux0_addr : std_logic_vector(6 downto 0) := 7x"70"; + constant mux1_addr : std_logic_vector(6 downto 0) := 7x"71"; + begin th: entity work.i2c_pca9545ish_th; bench: process alias reset is << signal th.reset : std_logic >>; - alias mux_sel is << signal th.mux_sel : std_logic_vector(1 downto 0) >>; + alias mux0_sel is << signal th.mux0_sel : std_logic_vector(1 downto 0) >>; + alias mux1_sel is << signal th.mux1_sel : std_logic_vector(1 downto 0) >>; variable tx_queue : queue_t := new_queue; variable rx_queue : queue_t := new_queue; variable ack_queue : queue_t := new_queue; @@ -54,94 +58,128 @@ begin wait for 500 ns; -- let the resets propagate while test_suite loop - if run("Single-mux-select") then - check_equal(mux_sel, not_selected, "Mux not disconnected at start"); + if run("single-mux-select") then + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); -- Enable ch0 of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); check_true(contains_all_acks(ack_queue), "Either no acks or some Nacks were found"); -- Verify mux ch0 is selected - check_equal(mux_sel, chA_selected, "CHA Mux not selected after write"); + check_equal(mux0_sel, chA_selected, "CHA Mux not selected after write"); -- Now read back the register - i2c_read_txn(net, 7x"70", 1, rx_queue, ack_status); + i2c_read_txn(net, mux0_addr, 1, rx_queue, ack_status); + wait for 1 us; check_true(ack_status, "Target didn't ack during readback"); check_equal(pop_byte(rx_queue), 1, "CHA Readback didn't return expected value"); + elsif run("alternate-single-mux-select") then + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); + -- Enable ch0 of the mux via i2c command + push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); + i2c_write_txn (net, mux1_addr, tx_queue, ack_queue); + check_true(contains_all_acks(ack_queue), "Either no acks or some Nacks were found"); + -- Verify mux chA is selected on the alternate address + -- not on the default address + check_equal(mux0_sel, not_selected, "CHA on Mux0 was selected after write"); + check_equal(mux1_sel, chA_selected, "CHA on Mux1 was not selected after write"); + -- Now read back the register + i2c_read_txn(net, mux1_addr, 1, rx_queue, ack_status); + wait for 1 us; + check_true(ack_status, "Target didn't ack during readback"); + check_equal(pop_byte(rx_queue), 1, "CHA Readback didn't return expected value"); elsif run("nack-wrong-target") then - check_equal(mux_sel, not_selected, "Mux not disconnected at start"); + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); -- Write *something* to the wrong target address, expecting a NACK - i2c_write_txn (net, 7x"75", tx_queue, ack_queue); + i2c_write_txn(net, 7x"75", tx_queue, ack_queue); check_true(target_addr_nack(ack_queue), "Target unexpectedly ACK'd write at wrong address"); - check_equal(mux_sel, not_selected, "Mux not still disconnected after NACK'd write"); + check_equal(mux0_sel, not_selected, "Mux not still disconnected after NACK'd write"); push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); -- Write *something* to the wrong target address, expecting a NACK - i2c_read_txn (net, 7x"75", 1, rx_queue, ack_status); + i2c_read_txn(net, 7x"75", 1, rx_queue, ack_status); check_false(ack_status, "Target ACK'd read at wrong address"); elsif run("multi-channel-attempt") then - check_equal(mux_sel, not_selected, "Mux not disconnected at start"); + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); -- Enable ch0 of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- Verify mux ch0 is selected - check_equal(mux_sel, chA_selected, "CHA Mux not selected after write"); + check_equal(mux0_sel, chA_selected, "CHA Mux not selected after write"); -- Now errantly try to enable multiple channels push_byte(tx_queue, to_integer(std_logic_vector'(8x"03"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- We expect a NACK on the control register 2nd byte, and the mux should remain in its previous state push_boolean(expected_ack_queue, true); -- ACK on the target address byte push_boolean(expected_ack_queue, false); -- NACK on the control register 2nd byte check_true(ack_queue_matches(ack_queue, expected_ack_queue), "Ack queue does not match expected ack queue"); -- Verify mux ch0 is selected - check_equal(mux_sel, chA_selected, "CHA Mux not selected after write"); + check_equal(mux0_sel, chA_selected, "CHA Mux not selected after write"); + elsif run("multi-mux-attempt") then + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); + -- Enable chA of the mux via i2c command + push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); + -- Verify mux0 chA is selected + check_equal(mux0_sel, chA_selected, "CHA Mux0 not selected after write"); + -- Now errantly try to enable another mux + push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); + i2c_write_txn (net, mux1_addr, tx_queue, ack_queue); + -- We expect a NACK on the control register 2nd byte, and the mux should remain in its previous state + push_boolean(expected_ack_queue, true); -- ACK on the target address byte + push_boolean(expected_ack_queue, false); -- NACK on the control register 2nd byte + check_true(ack_queue_matches(ack_queue, expected_ack_queue), "Ack queue does not match expected ack queue"); + -- Verify mux0 chA is selected + check_equal(mux0_sel, chA_selected, "CHA Mux0 not selected after write"); + -- Verify mux1 chA is not selected + check_equal(mux1_sel, not_selected, "CHA Mux1 selected after write"); elsif run("sunny-day-select-deselect") then - check_equal(mux_sel, not_selected, "Mux not disconnected at start"); + check_equal(mux0_sel, not_selected, "Mux not disconnected at start"); -- Enable ch0 of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"01"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- Verify mux ch0 is selected - check_equal(mux_sel, chA_selected, "CHA Mux not selected after write"); + check_equal(mux0_sel, chA_selected, "CHA Mux not selected after write"); -- Now read back the register - i2c_read_txn(net, 7x"70", 1, rx_queue, ack_status); + i2c_read_txn(net, mux0_addr, 1, rx_queue, ack_status); check_true(ack_status, "Target didn't ack during readback"); check_equal(pop_byte(rx_queue), 1, "CHA Readback didn't return expected value"); -- Enable ch1 of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"02"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- Verify mux ch1 is selected - check_equal(mux_sel, chB_selected, "CHB Mux not selected after write"); + check_equal(mux0_sel, chB_selected, "CHB Mux not selected after write"); -- Now read back the register - i2c_read_txn(net, 7x"70", 1, rx_queue, ack_status); + i2c_read_txn(net, mux0_addr, 1, rx_queue, ack_status); check_true(ack_status, "Target didn't ack during readback"); check_equal(pop_byte(rx_queue), 2, "CHB Readback didn't return expected value"); -- Enable ch2 of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"04"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- Verify mux ch1 is selected - check_equal(mux_sel, chC_selected, "CHC Mux not selected after write"); + check_equal(mux0_sel, chC_selected, "CHC Mux not selected after write"); -- Now read back the register - i2c_read_txn(net, 7x"70", 1, rx_queue, ack_status); + i2c_read_txn(net, mux0_addr, 1, rx_queue, ack_status); check_true(ack_status, "Target didn't ack during readback"); check_equal(pop_byte(rx_queue), 4, "CHC Readback didn't return expected value"); -- Disable all of the mux via i2c command push_byte(tx_queue, to_integer(std_logic_vector'(8x"00"))); - i2c_write_txn (net, 7x"70", tx_queue, ack_queue); + i2c_write_txn (net, mux0_addr, tx_queue, ack_queue); -- Verify mux ch1 is selected - check_equal(mux_sel, not_selected, "Some mux still not selected after write"); + check_equal(mux0_sel, not_selected, "Some mux still not selected after write"); -- Now read back the register - i2c_read_txn(net, 7x"70", 1, rx_queue, ack_status); + i2c_read_txn(net, mux0_addr, 1, rx_queue, ack_status); check_true(ack_status, "Target didn't ack during readback"); check_equal(pop_byte(rx_queue), 0, "Readback didn't return expected value 0"); diff --git a/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_th.vhd b/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_th.vhd index 83361dfd..4072b9fc 100644 --- a/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_th.vhd +++ b/hdl/ip/vhd/i2c/muxes/PCA9545ish/sims/i2c_pca9545ish_th.vhd @@ -25,14 +25,23 @@ architecture th of i2c_pca9545ish_th is signal reset : std_logic := '1'; signal i2c_bus_scl : std_logic := 'Z'; + signal i2c_bus_scl_o : std_logic; + signal i2c_bus_scl_oe : std_logic; signal i2c_bus_sda : std_logic := 'Z'; - signal tgt_scl_o : std_logic; - signal tgt_scl_oe : std_logic; - signal tgt_sda_o : std_logic; - signal tgt_sda_oe : std_logic; + signal i2c_bus_sda_o : std_logic; + signal i2c_bus_sda_oe : std_logic; + + signal tgt_scl : std_logic_vector(1 downto 0); + signal tgt_scl_o : std_logic_vector(1 downto 0); + signal tgt_scl_oe : std_logic_vector(1 downto 0); + signal tgt_sda : std_logic_vector(1 downto 0); + signal tgt_sda_o : std_logic_vector(1 downto 0); + signal tgt_sda_oe : std_logic_vector(1 downto 0); signal mux_reset : std_logic := '0'; - signal mux_sel : std_logic_vector(1 downto 0); + signal mux0_sel : std_logic_vector(1 downto 0); + signal mux1_sel : std_logic_vector(1 downto 0); + signal other_mux_selected : std_logic_vector(1 downto 0); begin @@ -50,10 +59,13 @@ begin sda => i2c_bus_sda ); - i2c_bus_scl <= tgt_scl_o when tgt_scl_oe = '1' else 'H'; - i2c_bus_sda <= tgt_sda_o when tgt_sda_oe = '1' else 'H'; + i2c_bus_scl <= i2c_bus_scl_o when i2c_bus_scl_oe = '1' else 'H'; + i2c_bus_sda <= i2c_bus_sda_o when i2c_bus_sda_oe = '1' else 'H'; - DUT: entity work.pca9545ish_top + other_mux_selected(0) <= '1' when mux1_sel /= "11" else '0'; + other_mux_selected(1) <= '1' when mux0_sel /= "11" else '0'; + + DUT0: entity work.pca9545ish_top generic map( i2c_addr => 7x"70", giltch_filter_cycles => 3 @@ -62,13 +74,54 @@ begin clk => clk, reset => reset, mux_reset => mux_reset, + other_mux_selected => other_mux_selected(0), + scl => tgt_scl(0), + scl_o => tgt_scl_o(0), + scl_oe => tgt_scl_oe(0), + sda => tgt_sda(0), + sda_o => tgt_sda_o(0), + sda_oe => tgt_sda_oe(0), + mux_sel => mux0_sel + ); + + DUT1: entity work.pca9545ish_top + generic map( + i2c_addr => 7x"71", + giltch_filter_cycles => 3 + ) + port map( + clk => clk, + reset => reset, + mux_reset => mux_reset, + other_mux_selected => other_mux_selected(1), + scl => tgt_scl(1), + scl_o => tgt_scl_o(1), + scl_oe => tgt_scl_oe(1), + sda => tgt_sda(1), + sda_o => tgt_sda_o(1), + sda_oe => tgt_sda_oe(1), + mux_sel => mux1_sel + ); + + bus_consolidator: entity work.i2c_phy_consolidator + generic map( + TARGET_NUM => 2 + ) + port map( + clk => clk, + reset => reset, scl => i2c_bus_scl, - scl_o => tgt_scl_o, - scl_oe => tgt_scl_oe, + scl_o => i2c_bus_scl_o, + scl_oe => i2c_bus_scl_oe, sda => i2c_bus_sda, - sda_o => tgt_sda_o, - sda_oe => tgt_sda_oe, - mux_sel => mux_sel + sda_o => i2c_bus_sda_o, + sda_oe => i2c_bus_sda_oe, + tgt_scl => tgt_scl, + tgt_scl_o => tgt_scl_o, + tgt_scl_oe => tgt_scl_oe, + tgt_sda => tgt_sda, + tgt_sda_o => tgt_sda_o, + tgt_sda_oe => tgt_sda_oe ); end th; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/target/BUCK b/hdl/ip/vhd/i2c/target/BUCK index d6de73e9..57217d7d 100644 --- a/hdl/ip/vhd/i2c/target/BUCK +++ b/hdl/ip/vhd/i2c/target/BUCK @@ -1,8 +1,13 @@ load("//tools:hdl.bzl", "vhdl_unit", "vunit_sim") +vhdl_unit( + name = "i2c_phy_consolidator", + srcs = glob(["*consolidator.vhd"]), + visibility = ['PUBLIC'] +) vhdl_unit( name = "i2c_target_phy", - srcs = glob(["*.vhd"]), + srcs = glob(["*.vhd"], exclude = ["*consolidator.vhd"]), deps = [ "//hdl/ip/vhd/i2c/common:i2c_glitch_filter", ], diff --git a/hdl/ip/vhd/i2c/target/i2c_phy_consolidator.vhd b/hdl/ip/vhd/i2c/target/i2c_phy_consolidator.vhd new file mode 100644 index 00000000..9b0e21bc --- /dev/null +++ b/hdl/ip/vhd/i2c/target/i2c_phy_consolidator.vhd @@ -0,0 +1,65 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, You can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- Copyright 2025 Oxide Computer Company + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity i2c_phy_consolidator is + generic( + -- number of i2c targets to consolidate + TARGET_NUM : integer + ); + port ( + clk : in std_logic; + reset : in std_logic; + -- single, "pins" interface, consolidation of + -- multiple target interfaces + scl : in std_logic; + scl_o : out std_logic; + scl_oe : out std_logic; + sda : in std_logic; + sda_o : out std_logic; + sda_oe : out std_logic; + + -- multiple "target" interfaces + -- each sitting "on" the bus with + -- different i2c addresses + tgt_scl : out std_logic_vector(TARGET_NUM-1 downto 0); + tgt_scl_o : in std_logic_vector(TARGET_NUM-1 downto 0); + tgt_scl_oe : in std_logic_vector(TARGET_NUM-1 downto 0); + tgt_sda : out std_logic_vector(TARGET_NUM-1 downto 0); + tgt_sda_o : in std_logic_vector(TARGET_NUM-1 downto 0); + tgt_sda_oe : in std_logic_vector(TARGET_NUM-1 downto 0) + + ); +end entity; + +architecture rtl of i2c_phy_consolidator is + +begin + + -- input from pins and output to targets are easy everyone sees the bus + process(all) + begin + for i in 0 to TARGET_NUM-1 loop + tgt_scl(i) <= scl; + tgt_sda(i) <= sda; + end loop; + end process; + + -- output enable to bus will be active when any target is driving + -- using reduction OR operator here, oe is active high + scl_oe <= or tgt_scl_oe; + sda_oe <= or tgt_sda_oe; + + -- output is always low, we use OE to gate this on tristate bus + sda_o <= '0'; + scl_o <= '0'; + + + +end rtl; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd b/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd index e03615b4..25aa7260 100644 --- a/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd +++ b/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd @@ -1,15 +1,13 @@ --- watch for start condition --- 7 bit target address + read/write bit --- if write: --- -- shift 8 bytes and ack until stop or repeated start --- if read: --- -- shift 8 bytes and until nack, stop or repeated start +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, You can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- Copyright 2025 Oxide Computer Company --- otherwise we want to change data when SCL is low - --- Need 50ns glitch filters on SDA and SCL - --- ACK is DATA low for 9th SCL pulse +-- This block provides an i2c-compliant "phy" for use with additional logic +-- to create i2c target functions. It is intended to be generic and shareable +-- across multiple target functions, with the target function logic being +-- implemented in other modules. library ieee; use ieee.std_logic_1164.all; @@ -31,7 +29,6 @@ entity i2c_target_phy is sda : in std_logic; sda_o : out std_logic; sda_oe : out std_logic; - -- instruction interface inst_data : out std_logic_vector(7 downto 0); inst_valid : out std_logic; @@ -64,6 +61,7 @@ architecture rtl of i2c_target_phy is inst_data_buf : std_logic_vector(7 downto 0); inst_data_valid : std_logic; start_seen : std_logic; + selected : std_logic; got_ack : std_logic; got_nack : std_logic; end record; @@ -79,6 +77,7 @@ architecture rtl of i2c_target_phy is '0', '0', '0', + '0', '0' ); @@ -106,7 +105,7 @@ begin sda_o <= '0'; -- Only pull bus low, enable will control tristate -- enable becomes bit inversion of msb of tx reg when we're transmitting -- or acking - sda_oe <= '1' when (r.state = TX_DATA or r.state = SEND_ACK_NACK) and r.tx_reg(r.tx_reg'high) = '0' else '0'; + sda_oe <= '1' when (r.state = TX_DATA or r.state = SEND_ACK_NACK) and r.tx_reg(r.tx_reg'high) = '0' and r.selected = '1' else '0'; i2c_glitch_filter_inst: entity work.i2c_glitch_filter generic map( filter_cycles => giltch_filter_cycles @@ -204,14 +203,23 @@ begin when SEND_ACK_NACK => if do_ack and in_ack_phase then v.tx_reg := (others => '0'); + v.selected := '1'; elsif in_ack_phase then v.tx_reg := (others => '1'); --NACK end if; - if scl_fedge then + if scl_fedge and r.selected then v.state := r.post_ack_nxt_state; if r.post_ack_nxt_state = TX_DATA then v.tx_ready := '1'; end if; + elsif scl_fedge then + -- we were not selected b/c we didn't ack + -- so go back to idle and wait for another txn + v.state := IDLE; + v.txn_hdr.valid := '0'; + v.start_seen := '0'; + v.cntr := (others => '0'); + v.selected := '0'; end if; when GET_ACKNACK => @@ -236,10 +244,12 @@ begin v.txn_hdr.valid := '0'; v.start_seen := '0'; v.cntr := (others => '0'); + v.selected := '0'; elsif start_condition then v.start_seen := '1'; v.txn_hdr.valid := '0'; v.state := IDLE; + v.selected := '0'; end if; -- deal with handshake all the time