From 2d1eb19f432e1ff7dfdaa295871d48b0fc482738 Mon Sep 17 00:00:00 2001 From: Nathanael Huffman Date: Tue, 14 Jan 2025 11:33:06 -0600 Subject: [PATCH] Add PCA9506ish i/o expander logic --- hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK | 29 ++ .../PCA9506ish/pca9506_function.vhd | 201 +++++++++++ .../io_expanders/PCA9506ish/pca9506_pkg.vhd | 77 +++++ .../io_expanders/PCA9506ish/pca9506_regs.rdl | 163 +++++++++ .../io_expanders/PCA9506ish/pca9506_regs.vhd | 323 ++++++++++++++++++ .../io_expanders/PCA9506ish/pca9506_top.vhd | 140 ++++++++ .../sims/i2c_pca9506ish_sim_pkg.vhd | 94 +++++ .../PCA9506ish/sims/i2c_pca9506ish_tb.vhd | 89 +++++ .../PCA9506ish/sims/i2c_pca9506ish_th.vhd | 98 ++++++ hdl/ip/vhd/i2c/target/i2c_target_phy.vhd | 10 +- .../i2c_controller/i2c_ctrlr_vc.vhd | 6 +- .../i2c_controller/i2c_ctrlr_vc_pkg.vhd | 103 ++++++ 12 files changed, 1327 insertions(+), 6 deletions(-) create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_pkg.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.rdl create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_top.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd create mode 100644 hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK new file mode 100644 index 00000000..27ea5284 --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK @@ -0,0 +1,29 @@ +load("//tools:hdl.bzl", "vhdl_unit", "vunit_sim") +load("//tools:rdl.bzl", "rdl_file") + +rdl_file( + name = "pca9506_regs_pkg", + src = "pca9506_regs.rdl", + outputs = ["pca9506_regs_pkg.vhd", "pca9506_regs_pkg.html"], + visibility = ['PUBLIC'] +) + +vhdl_unit( + name = "pca9506_top", + srcs = glob(["*.vhd"]), + deps = [ + "//hdl/ip/vhd/i2c/target:i2c_target_phy", + ":pca9506_regs_pkg", + ], + visibility = ['PUBLIC'] +) + +vunit_sim( + name = "i2c_pca9506ish_tb", + srcs = glob(["sims/**/*.vhd"]), + deps = [ + ":pca9506_top", + "//hdl/ip/vhd/vunit_components:i2c_controller_vc", + ], + 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 new file mode 100644 index 00000000..1863978f --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd @@ -0,0 +1,201 @@ +-- 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_unsigned.all; + +use work.i2c_base_types_pkg.all; +use work.pca9506_pkg.all; + +entity pca9506ish_function is + generic( + -- i2c address of the mux + i2c_addr : std_logic_vector(6 downto 0) := 7x"70" + ); + port( + clk : in std_logic; + reset : in std_logic; + -- PHY interface + -- instruction interface + inst_data : in std_logic_vector(7 downto 0); + inst_valid : in std_logic; + inst_ready : out std_logic; + in_ack_phase: in std_logic; + ack_next : out std_logic; + txn_header : in i2c_header; + start_condition : in std_logic; + stop_condition : in std_logic; + -- response interface + resp_data : out std_logic_vector(7 downto 0); + resp_valid : out std_logic; + resp_ready : in std_logic; + + -- internal register interface + read_data : in std_logic_vector(7 downto 0); + write_data : out std_logic_vector(7 downto 0); + read_strobe : out std_logic; + write_strobe : out std_logic; + cmd_ptr : out cmd_t + ); +end entity; + +architecture rtl of pca9506ish_function is + + type state_t is (IDLE, WAIT_FOR_ACKNACK, WAIT_FOR_ACK_PHASE, ACK, NACK, COMMAND, DO_WRITE, DO_READ); + + type reg_t is record + state : state_t; + post_ack_state : state_t; + read_strobe : std_logic; + write_strobe : std_logic; + cmd_reg : cmd_t; + data : std_logic_vector(7 downto 0); + data_valid: std_logic; + increment : std_logic; + end record; + + constant rec_reset : reg_t := ( + state => IDLE, + post_ack_state => IDLE, + read_strobe => '0', + write_strobe => '0', + cmd_reg => default_reset, + data => (others => '0'), + data_valid => '0', + increment => '0' + ); + + signal r, rin : reg_t; + +begin + + --assign some outputs + read_strobe <= r.read_strobe; + write_strobe <= r.write_strobe; + cmd_ptr <= r.cmd_reg; + resp_data <= r.data; + ack_next <= '1' when r.state = ACK else '0'; + write_data <= r.data; + inst_ready <= '1' when r.state = COMMAND or r.state = DO_WRITE else '0'; + resp_valid <= r.data_valid; + + cm: process(all) + variable v : reg_t; + begin + v := r; + + case r.state is + when 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 + -- all writes go through command state after target + v.post_ack_state := COMMAND; + else + -- post repeated start, reads bypass command state + -- and immediately do reads + v.post_ack_state := DO_READ; + -- pointer is valid + end if; + end if; + + + when COMMAND => + if inst_valid = '1' and inst_ready = '1' then + v.cmd_reg.ai := inst_data(7); + v.cmd_reg.pointer := inst_data(5 downto 0); + v.post_ack_state := DO_WRITE; + v.state := ACK; + end if; + + when DO_WRITE => + if inst_valid = '1' and inst_ready = '1' then + v.data := inst_data; + v.state := ACK; + v.write_strobe := '1'; + v.increment := v.cmd_reg.ai; + end if; + + when DO_READ => + v.read_strobe := '1'; + v.data_valid := '1'; + v.data := read_data; + v.state := WAIT_FOR_ACK_PHASE; + + when ACK => + -- clear any single-cycle strobes + v.write_strobe := '0'; + v.read_strobe := '0'; + -- wait for ack time to finish + if in_ack_phase = '0' then + v.state := r.post_ack_state; + if r.increment then + -- do a category-wrapping increment + v.cmd_reg.pointer := category_wrapping_increment(r.cmd_reg.pointer); + end if; + end if; + when NACK => + if in_ack_phase = '0' then + v.state := IDLE; + end if; + + when WAIT_FOR_ACK_PHASE => + -- clear any single-cycle strobes + v.write_strobe := '0'; + v.read_strobe := '0'; + if resp_valid = '1' and resp_ready = '1' then + v.data_valid := '0'; + v.increment := v.cmd_reg.ai; + end if; + if in_ack_phase = '1' then + v.state := WAIT_FOR_ACKNACK; + end if; + + when WAIT_FOR_ACKNACK => + -- clear any single-cycle strobes + v.write_strobe := '0'; + v.read_strobe := '0'; + -- TODO: what about NACK? + if in_ack_phase = '0' then + v.state := DO_READ; + if r.increment then + -- do a category-wrapping increment + v.cmd_reg.pointer := category_wrapping_increment(r.cmd_reg.pointer); + end if; + end if; + + when others => + v.state := IDLE; + + end case; + + if resp_valid = '1' and resp_ready = '1' then + v.data_valid := '0'; + end if; + + -- No matter where we were, do cleanup if we see a stop + if start_condition = '1' or stop_condition = '1' then + v.state := IDLE; + v.write_strobe := '0'; + v.read_strobe := '0'; + end if; + + rin <= v; + end process; + + + reg: process(clk, reset) + begin + if reset = '1' then + r <= rec_reset; + elsif rising_edge(clk) then + r <= rin; + end if; +end process; + + +end rtl; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_pkg.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_pkg.vhd new file mode 100644 index 00000000..7024e1bd --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_pkg.vhd @@ -0,0 +1,77 @@ +-- 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_unsigned.all; + +use work.pca9506_regs_pkg.all; + +package pca9506_pkg is + + type cmd_t is record + ai : std_logic; -- auto-increment + pointer: std_logic_vector(5 downto 0); -- register number + end record; -- default to 0x80 + + constant default_reset : cmd_t := ( + ai => '1', + pointer => (others => '0') + ); + + type pca9506_pin_t is array (0 to 4) of std_logic_vector(7 downto 0); + + function get_irq_pend( + cur_reg: io_type; + reg_at_last_read: io_type; + reg_mask: io_type + ) return std_logic; + + function category_wrapping_increment( + pointer: std_logic_vector(5 downto 0) -- register number + ) return std_logic_vector; + + + +end package; + +package body pca9506_pkg is + + function get_irq_pend( + cur_reg: io_type; + reg_at_last_read: io_type; + reg_mask: io_type + ) return std_logic + is + + begin + -- bitwise XOR current register with last to detect any changes + -- then bitwise mask off any masked bits + -- for the mask, '1' indicates "masked" so we need a bitwise inversion + -- before the bitwise AND for masking. + -- Finally, we need to reduce this down to a single bit to return, + -- We don't currently support unary reduction operators for record + -- types generated by RDL so we use the "compress" function to get + -- back a std_logic_vector of the used bits, and then we use a unary + -- reduction "or" operator on that to return 1 if any bits were 1 + return or compress((cur_reg xor reg_at_last_read) and (not reg_mask)); + end function; + + function category_wrapping_increment( + pointer: std_logic_vector(5 downto 0) -- register number + ) return std_logic_vector is + variable lsbs : std_logic_vector(2 downto 0); + + begin + lsbs := pointer(2 downto 0) + 1; + if lsbs > 4 then + lsbs := (others => '0'); + end if; + + -- increment the 3 lsb by one from 0 to 4, wrapping around to 0 + return pointer(5 downto 3) & lsbs; + end function; +end package body pca9506_pkg; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.rdl b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.rdl new file mode 100644 index 00000000..c84ff15c --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.rdl @@ -0,0 +1,163 @@ +// Copyright 2025 Oxide Computer Company +// This is SystemRDL description of emulated i2c mux registers + +addrmap pca9506_regs { + name = "pca9506 registers"; + desc = "Registers accessible on the i2c bus for controlling the emulated PCA9545 i2c mux"; + + default regwidth = 8; + default sw = rw; + default hw = r; + + reg io { + field { + desc = "bits for current port"; + } bits[7:0]; + }; + + io ip0 @ 0x00; + ip0->name = "Ip0"; + ip0->desc = "I/O port input state after polarity inversion (if enabled)"; + ip0.bits->sw = r; + + io ip1; + ip1->name = "Ip1"; + ip1->desc = "I/O port input state after polarity inversion (if enabled)"; + ip1.bits->sw = r; + + io ip2; + ip2->name = "Ip2"; + ip2->desc = "I/O port input state after polarity inversion (if enabled)"; + ip2.bits->sw = r; + + io ip3; + ip3->name = "Ip3"; + ip3->desc = "I/O port input state after polarity inversion (if enabled)"; + ip3.bits->sw = r; + + io ip4; + ip4->name = "Ip4"; + ip4->desc = "I/O port input state after polarity inversion (if enabled)"; + ip4.bits->sw = r; + + io op0 @ 0x08; + op0->name = "Op0"; + op0->desc = "These registers reflect the outgoing logic levels of the pins defined as outputs by the +I/O Configuration register. Bit values in these registers have no effect on pins defined +as inputs. In turn, reads from these registers reflect the values that are in the flip-flops +controlling the output selection, not the actual pin values."; + + io op1; + op1->name = "Op1"; + op1->desc = "These registers reflect the outgoing logic levels of the pins defined as outputs by the +I/O Configuration register. Bit values in these registers have no effect on pins defined +as inputs. In turn, reads from these registers reflect the values that are in the flip-flops +controlling the output selection, not the actual pin values."; + + io op2; + op2->name = "Op2"; + op2->desc = "These registers reflect the outgoing logic levels of the pins defined as outputs by the +I/O Configuration register. Bit values in these registers have no effect on pins defined +as inputs. In turn, reads from these registers reflect the values that are in the flip-flops +controlling the output selection, not the actual pin values."; + + io op3; + op3->name = "Op3"; + op3->desc = "These registers reflect the outgoing logic levels of the pins defined as outputs by the +I/O Configuration register. Bit values in these registers have no effect on pins defined +as inputs. In turn, reads from these registers reflect the values that are in the flip-flops +controlling the output selection, not the actual pin values."; + + io op4; + op4->name = "Op4"; + op4->desc = "These registers reflect the outgoing logic levels of the pins defined as outputs by the +I/O Configuration register. Bit values in these registers have no effect on pins defined +as inputs. In turn, reads from these registers reflect the values that are in the flip-flops +controlling the output selection, not the actual pin values."; + + io pi0 @ 0x10; + pi0->name = "pi0"; + pi0->desc = "Polarity Inversion of inputs. 0: no inversion, 1: invert input"; + + io pi1 ; + pi1->name = "pi1"; + pi1->desc = "Polarity Inversion of inputs. 0: no inversion, 1: invert input"; + + io pi2 ; + pi2->name = "pi2"; + pi2->desc = "Polarity Inversion of inputs. 0: no inversion, 1: invert input"; + + io pi3 ; + pi3->name = "pi3"; + pi3->desc = "Polarity Inversion of inputs. 0: no inversion, 1: invert input"; + + io pi4 ; + pi4->name = "pi4"; + pi4->desc = "Polarity Inversion of inputs. 0: no inversion, 1: invert input"; + + io ioc0 @ 0x18; + ioc0->name = "ioc0"; + ioc0->desc = "Config registers. 0: output, 1: input"; + ioc0.bits->reset = 0xff; + + io ioc1; + ioc1->name = "ioc1"; + ioc1->desc = "Config registers. 0: output, 1: input"; + ioc1.bits->reset = 0xff; + + io ioc2; + ioc2->name = "ioc2"; + ioc2->desc = "Config registers. 0: output, 1: input"; + ioc2.bits->reset = 0xff; + + io ioc3; + ioc3->name = "ioc3"; + ioc3->desc = "Config registers. 0: output, 1: input"; + ioc3.bits->reset = 0xff; + + io ioc4; + ioc4->name = "ioc4"; + ioc4->desc = "Config registers. 0: output, 1: input"; + ioc4.bits->reset = 0xff; + + io msk0 @ 0x20; + msk0->name = "msk0"; + msk0->desc = "Mx[y] = 0: A level change at the I/O will generate an interrupt if IOx_y defined as input +(Cx[y] in IOC register = 1). +Mx[y] = 1: A level change in the input port will not generate an interrupt if IOx_y +defined as input (Cx[y] in IOC register = 1)."; + msk0.bits->reset = 0xff; + + io msk1; + msk1->name = "msk1"; + msk1->desc = "Mx[y] = 0: A level change at the I/O will generate an interrupt if IOx_y defined as input +(Cx[y] in IOC register = 1). +Mx[y] = 1: A level change in the input port will not generate an interrupt if IOx_y +defined as input (Cx[y] in IOC register = 1)."; + msk1.bits->reset = 0xff; + + io msk2; + msk2->name = "msk2"; + msk2->desc = "Mx[y] = 0: A level change at the I/O will generate an interrupt if IOx_y defined as input +(Cx[y] in IOC register = 1). +Mx[y] = 1: A level change in the input port will not generate an interrupt if IOx_y +defined as input (Cx[y] in IOC register = 1)."; + msk2.bits->reset = 0xff; + + io msk3; + msk3->name = "msk3"; + msk3->desc = "Mx[y] = 0: A level change at the I/O will generate an interrupt if IOx_y defined as input +(Cx[y] in IOC register = 1). +Mx[y] = 1: A level change in the input port will not generate an interrupt if IOx_y +defined as input (Cx[y] in IOC register = 1)."; + msk3.bits->reset = 0xff; + + io msk4; + msk4->name = "msk4"; + msk4->desc = "Mx[y] = 0: A level change at the I/O will generate an interrupt if IOx_y defined as input +(Cx[y] in IOC register = 1). +Mx[y] = 1: A level change in the input port will not generate an interrupt if IOx_y +defined as input (Cx[y] in IOC register = 1)."; + msk4.bits->reset = 0xff; + +}; \ No newline at end of file diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.vhd new file mode 100644 index 00000000..f94a59a7 --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_regs.vhd @@ -0,0 +1,323 @@ +-- 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_unsigned.all; + +use work.pca9506_pkg.all; +use work.pca9506_regs_pkg.all; + +entity pca9506_regs is + port ( + clk : in std_logic; + reset : in std_logic; + + -- i2c control interface + cmd_ptr : in cmd_t; + write_strobe: in std_logic; + read_strobe: in std_logic; + data_in: in std_logic_vector(7 downto 0); + data_out: out std_logic_vector(7 downto 0); + + -- io interface + output_disable: in std_logic; + io : in pca9506_pin_t; + io_oe : out pca9506_pin_t; + io_o : out pca9506_pin_t; + int_n : out std_logic + + + ); +end entity; + +architecture rtl of pca9506_regs is + + signal ip0_reg : io_type; + signal ip1_reg : io_type; + signal ip2_reg : io_type; + signal ip3_reg : io_type; + signal ip4_reg : io_type; + + signal ip0_reg_irq_at_last_read : io_type; + signal ip1_reg_irq_at_last_read : io_type; + signal ip2_reg_irq_at_last_read : io_type; + signal ip3_reg_irq_at_last_read : io_type; + signal ip4_reg_irq_at_last_read : io_type; + + -- We have to monitor each input port for changes independently + signal int_pend : std_logic_vector(4 downto 0); + + signal op0_reg : io_type; + signal op1_reg : io_type; + signal op2_reg : io_type; + signal op3_reg : io_type; + signal op4_reg : io_type; + + signal pi0_reg : io_type; + signal pi1_reg : io_type; + signal pi2_reg : io_type; + signal pi3_reg : io_type; + signal pi4_reg : io_type; + + signal ioc0_reg : io_type; + signal ioc1_reg : io_type; + signal ioc2_reg : io_type; + signal ioc3_reg : io_type; + signal ioc4_reg : io_type; + + signal msk0_reg : io_type; + signal msk1_reg : io_type; + signal msk2_reg : io_type; + signal msk3_reg : io_type; + signal msk4_reg : io_type; + +begin + + -- assign register outputs to output pins unconditionally + io_o(0) <= pack(op0_reg); + io_o(1) <= pack(op1_reg); + io_o(2) <= pack(op2_reg); + io_o(3) <= pack(op3_reg); + io_o(4) <= pack(op4_reg); + + -- register the output enables since they might have + -- some logic associated with them + output_pins_reg: process(clk, reset) + begin + if reset then + io_oe <= (others => (others =>'0')); + elsif rising_edge(clk) then + if output_disable = '1' then + io_oe <= (others => (others =>'0')); + else + -- ioc bit = 0 means output, so invert here + io_oe(0) <= not pack(ioc0_reg); + io_oe(1) <= not pack(ioc1_reg); + io_oe(2) <= not pack(ioc2_reg); + io_oe(3) <= not pack(ioc3_reg); + io_oe(4) <= not pack(ioc4_reg); + end if; + + end if; + end process; + + interrupt_logic: process(clk, reset) + begin + if reset then + int_n <= '1'; + int_pend <= (others => '0'); + + elsif rising_edge(clk) then + -- each register gets its own pending interrupt bit + -- here. These will clear once the corresponding register + -- is read, or the input state returns to the previously read state + int_pend(0) <= get_irq_pend(ip0_reg, ip0_reg_irq_at_last_read, msk0_reg); + int_pend(1) <= get_irq_pend(ip1_reg, ip1_reg_irq_at_last_read, msk1_reg); + int_pend(2) <= get_irq_pend(ip2_reg, ip2_reg_irq_at_last_read, msk2_reg); + int_pend(3) <= get_irq_pend(ip3_reg, ip3_reg_irq_at_last_read, msk3_reg); + int_pend(4) <= get_irq_pend(ip4_reg, ip4_reg_irq_at_last_read, msk4_reg); + + int_n <= '1' when int_pend = 0 else '0'; + end if; + end process; + + + + + i2c_write_regs: process(clk, reset) + begin + + if reset then + ip0_reg <= reset_0s; + ip1_reg <= reset_0s; + ip2_reg <= reset_0s; + ip3_reg <= reset_0s; + ip4_reg <= reset_0s; + op0_reg <= reset_0s; + op1_reg <= reset_0s; + op2_reg <= reset_0s; + op3_reg <= reset_0s; + op4_reg <= reset_0s; + pi0_reg <= reset_0s; + pi1_reg <= reset_0s; + pi2_reg <= reset_0s; + pi3_reg <= reset_0s; + pi4_reg <= reset_0s; + ioc0_reg <= reset_1s; + ioc1_reg <= reset_1s; + ioc2_reg <= reset_1s; + ioc3_reg <= reset_1s; + ioc4_reg <= reset_1s; + msk0_reg <= reset_1s; + msk1_reg <= reset_1s; + msk2_reg <= reset_1s; + msk3_reg <= reset_1s; + msk4_reg <= reset_1s; + + elsif rising_edge(clk) then + -- deal with inputs: + --TODO deal with inversion + ip0_reg <= unpack(io(0)); + ip1_reg <= unpack(io(1)); + ip2_reg <= unpack(io(2)); + ip3_reg <= unpack(io(3)); + ip4_reg <= unpack(io(4)); + -- deal with registers that are writeable by the + -- i2c system + -- TODO: deal with ext_reset + if write_strobe = '1' then + case to_integer(cmd_ptr.pointer) is + -- IP registers don't accept writes + when OP0_OFFSET => + op0_reg <= unpack(data_in); + when OP1_OFFSET => + op1_reg <= unpack(data_in); + when OP2_OFFSET => + op2_reg <= unpack(data_in); + when OP3_OFFSET => + op3_reg <= unpack(data_in); + when OP4_OFFSET => + op4_reg <= unpack(data_in); + + when PI0_OFFSET => + pi0_reg <= unpack(data_in); + when PI1_OFFSET => + pi1_reg <= unpack(data_in); + when PI2_OFFSET => + pi2_reg <= unpack(data_in); + when PI3_OFFSET => + pi3_reg <= unpack(data_in); + when PI4_OFFSET => + pi4_reg <= unpack(data_in); + + when IOC0_OFFSET => + ioc0_reg <= unpack(data_in); + when IOC1_OFFSET => + ioc1_reg <= unpack(data_in); + when IOC2_OFFSET => + ioc2_reg <= unpack(data_in); + when IOC3_OFFSET => + ioc3_reg <= unpack(data_in); + when IOC4_OFFSET => + ioc4_reg <= unpack(data_in); + + when MSK0_OFFSET => + msk0_reg <= unpack(data_in); + when MSK1_OFFSET => + msk1_reg <= unpack(data_in); + when MSK2_OFFSET => + msk2_reg <= unpack(data_in); + when MSK3_OFFSET => + msk3_reg <= unpack(data_in); + when MSK4_OFFSET => + msk4_reg <= unpack(data_in); + + when others => + null; + end case; + end if; + + end if; + + end process; + + i2c_read_regs: process(clk, reset) + begin + if reset then + data_out <= (others => '0'); + ip0_reg_irq_at_last_read <= reset_0s; + ip1_reg_irq_at_last_read <= reset_0s; + ip2_reg_irq_at_last_read <= reset_0s; + ip3_reg_irq_at_last_read <= reset_0s; + ip4_reg_irq_at_last_read <= reset_0s; + + elsif rising_edge(clk) then + + -- no need to gate on writes + -- here so always assign output + -- but deal with read-strobe for side-effects: + -- we have to keep track of the last-read + -- register state for interrupt generation. + case to_integer(cmd_ptr.pointer) is + when IP0_OFFSET => + data_out <= pack(ip0_reg); + if read_strobe then + ip0_reg_irq_at_last_read <= ip0_reg; + end if; + when IP1_OFFSET => + data_out <= pack(ip1_reg); + if read_strobe then + ip1_reg_irq_at_last_read <= ip1_reg; + end if; + when IP2_OFFSET => + data_out <= pack(ip2_reg); + if read_strobe then + ip2_reg_irq_at_last_read <= ip2_reg; + end if; + when IP3_OFFSET => + data_out <= pack(ip3_reg); + if read_strobe then + ip3_reg_irq_at_last_read <= ip3_reg; + end if; + when IP4_OFFSET => + data_out <= pack(ip4_reg); + if read_strobe then + ip4_reg_irq_at_last_read <= ip4_reg; + end if; + + when OP0_OFFSET => + data_out <= pack(op0_reg); + when OP1_OFFSET => + data_out <= pack(op1_reg); + when OP2_OFFSET => + data_out <= pack(op2_reg); + when OP3_OFFSET => + data_out <= pack(op3_reg); + when OP4_OFFSET => + data_out <= pack(op4_reg); + + when PI0_OFFSET => + data_out <= pack(pi0_reg); + when PI1_OFFSET => + data_out <= pack(pi1_reg); + when PI2_OFFSET => + data_out <= pack(pi2_reg); + when PI3_OFFSET => + data_out <= pack(pi3_reg); + when PI4_OFFSET => + data_out <= pack(pi4_reg); + + when IOC0_OFFSET => + data_out <= pack(ioc0_reg); + when IOC1_OFFSET => + data_out <= pack(ioc1_reg); + when IOC2_OFFSET => + data_out <= pack(ioc2_reg); + when IOC3_OFFSET => + data_out <= pack(ioc3_reg); + when IOC4_OFFSET => + data_out <= pack(ioc4_reg); + + when MSK0_OFFSET => + data_out <= pack(msk0_reg); + when MSK1_OFFSET => + data_out <= pack(msk1_reg); + when MSK2_OFFSET => + data_out <= pack(msk2_reg); + when MSK3_OFFSET => + data_out <= pack(msk3_reg); + when MSK4_OFFSET => + data_out <= pack(msk4_reg); + + when others => + data_out <= (others => '0'); + end case; + + end if; + end process; + +end rtl; diff --git a/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_top.vhd b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_top.vhd new file mode 100644 index 00000000..41732302 --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_top.vhd @@ -0,0 +1,140 @@ +-- 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 + +-- This provides an I/O expander sw-compatible with the PCA9506 memory map +-- upper level logic will decide whether or not to use the tri-state signals +-- and wether the individual pins are inputs or outputs. + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std_unsigned.all; + +use work.i2c_base_types_pkg.all; +use work.pca9506_pkg.all; + +entity pca9506_top is + generic( + -- i2c address of the mux + i2c_addr : std_logic_vector(6 downto 0); + giltch_filter_cycles : integer := 5 + ); + port( + clk : in std_logic; + reset : in std_logic; + + -- I2C bus mux endpoint for control + -- Does not support clock-stretching + 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; + + -- Ports (5x 1 byte array, from pca9506_pkg) + io : in pca9506_pin_t; + io_oe : out pca9506_pin_t; + io_o : out pca9506_pin_t; + + int_n: out std_logic --can tri-state at top if desired using 1='Z' + + ); + +end entity; + +architecture rtl of pca9506_top is + signal inst_data : std_logic_vector(7 downto 0); + signal inst_valid : std_logic; + signal inst_ready : std_logic; + signal in_ack_phase : std_logic; + signal ack_next : std_logic; + signal txn_header : i2c_header; + signal start_condition : std_logic; + signal stop_condition : std_logic; + signal resp_data : std_logic_vector(7 downto 0); + signal resp_valid : std_logic; + signal resp_ready : std_logic; + signal read_strobe : std_logic; + signal write_strobe : std_logic; + signal cmd_ptr : cmd_t; + signal read_data : std_logic_vector(7 downto 0); + signal write_data : std_logic_vector(7 downto 0); + + +begin + + -- basic i2c state machine + i2c_target_phy_inst: entity work.i2c_target_phy + generic map( + giltch_filter_cycles => giltch_filter_cycles + ) + port map( + clk => clk, + reset => reset, + scl => scl, + scl_o => scl_o, + scl_oe => scl_oe, + sda => sda, + sda_o => sda_o, + sda_oe => sda_oe, + inst_data => inst_data, + inst_valid => inst_valid, + inst_ready => inst_ready, + in_ack_phase => in_ack_phase, + do_ack => ack_next, + start_condition => start_condition, + stop_condition => stop_condition, + txn_header => txn_header, + resp_data => resp_data, + resp_valid => resp_valid, + resp_ready => resp_ready + ); + + -- pca9506 functional block + pca9506ish_function_inst: entity work.pca9506ish_function + generic map( + i2c_addr => i2c_addr + ) + port map( + clk => clk, + reset => reset, + inst_data => inst_data, + inst_valid => inst_valid, + inst_ready => inst_ready, + in_ack_phase => in_ack_phase, + ack_next => ack_next, + txn_header => txn_header, + start_condition => start_condition, + stop_condition => stop_condition, + resp_data => resp_data, + resp_valid => resp_valid, + resp_ready => resp_ready, + read_strobe => read_strobe, + write_strobe => write_strobe, + read_data => read_data, + write_data => write_data, + cmd_ptr => cmd_ptr + ); + + -- registers block + pca9506_regs_inst: entity work.pca9506_regs + port map( + clk => clk, + reset => reset, + cmd_ptr => cmd_ptr, + write_strobe => write_strobe, + read_strobe => read_strobe, + data_in => write_data, + data_out => read_data, + output_disable => '0', + io => io, + io_oe => io_oe, + io_o => io_o, + int_n => int_n + ); + + +end rtl; \ No newline at end of file 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 new file mode 100644 index 00000000..c7241e41 --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_sim_pkg.vhd @@ -0,0 +1,94 @@ +-- 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; +use ieee.numeric_std_unsigned.all; + +library vunit_lib; + context vunit_lib.vunit_context; + context vunit_lib.com_context; + +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. + -- want to be able to read/write arbitrary registers + -- want to be able to read/write categories of registers (wrapping) + procedure write_pca9506_reg( + signal net : inout network_t; + constant starting_reg : in integer range 0 to 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 + ); + + procedure read_pca9506_reg( + signal net : inout network_t; + constant starting_reg : in integer range 0 to 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 + ); + +end package; + +package body i2c_pca9506ish_sim_pkg is + + procedure write_pca9506_reg( + signal net : inout network_t; + constant starting_reg : in integer range 0 to 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 + ) is + variable cmd_reg : std_logic_vector(7 downto 0); + constant tx_queue : queue_t := new_queue; + begin + -- set up the cmd register + cmd_reg := std_logic_vector(to_unsigned(starting_reg, 8)); + if auto_inc then + cmd_reg(7) := '1'; + end if; + push_byte(tx_queue, to_integer(cmd_reg)); + while not is_empty(byte_queue) loop + push_byte(tx_queue, pop_byte(byte_queue)); + end loop; + i2c_write_txn (net, i2c_addr, tx_queue, ack_queue); + end procedure; + + procedure read_pca9506_reg( + signal net : inout network_t; + constant starting_reg : in integer range 0 to 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 + ) is + variable cmd_reg : std_logic_vector(7 downto 0); + constant tx_queue : queue_t := new_queue; + begin + -- set up the cmd register + cmd_reg := std_logic_vector(to_unsigned(starting_reg, 8)); + if auto_inc then + cmd_reg(7) := '1'; + end if; + push_byte(tx_queue, to_integer(cmd_reg)); + i2c_mixed_txn(net, i2c_addr, tx_queue, num_regs_to_read, response_queue, ack_queue); + end procedure; + +end package body; \ No newline at end of file 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 new file mode 100644 index 00000000..75328576 --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_tb.vhd @@ -0,0 +1,89 @@ +-- 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; +use ieee.numeric_std_unsigned.all; + +library vunit_lib; + context vunit_lib.com_context; + context vunit_lib.vunit_context; + context vunit_lib.vc_context; + +use work.i2c_ctrl_vc_pkg.all; +use work.i2c_pca9506ish_sim_pkg.all; +use work.pca9506_regs_pkg.all; + +entity i2c_pca9506ish_tb is + generic ( + + runner_cfg : string + ); +end entity; + +architecture tb of i2c_pca9506ish_tb is + + +begin + + th: entity work.i2c_pca9506ish_th; + + bench: process + alias reset is << signal th.reset : std_logic >>; + variable tx_queue : queue_t := new_queue; + variable rx_queue : queue_t := new_queue; + variable ack_queue : queue_t := new_queue; + variable expected_ack_queue : queue_t := new_queue; + variable ack_status : boolean := false; + + begin + -- Always the first thing in the process, set up things for the VUnit test runner + test_runner_setup(runner, runner_cfg); + -- Reach into the test harness, which generates and de-asserts reset and hold the + -- test cases off until we're out of reset. This runs for every test case + wait until reset = '0'; + wait for 500 ns; -- let the resets propagate + + 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"); + 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"); + check_true(is_empty(rx_queue), "rx queue not empty"); + + -- elsif run("check-write") then + -- -- write a non-default value to an internal register and read it back + -- push_byte(tx_queue, to_integer(std_logic_vector'(8x"88"))); + -- push_byte(tx_queue, to_integer(std_logic_vector'(8x"a5"))); + + -- verify ack on addr + -- verify no ack on non-addr + -- verify auto-increment + -- verify no increment also works + -- read all defaults + -- verify irq mask + -- verify irq basic + -- verify multi register irqs + -- verify all pin defaults --TODO: overrides?? + + end if; + + end loop; + + wait for 2 us; + test_runner_cleanup(runner); + wait; + end process; + + -- Example total test timeout dog + test_runner_watchdog(runner, 10 ms); + +end tb; \ No newline at end of file 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 new file mode 100644 index 00000000..ea3593bf --- /dev/null +++ b/hdl/ip/vhd/i2c/io_expanders/PCA9506ish/sims/i2c_pca9506ish_th.vhd @@ -0,0 +1,98 @@ +-- 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; + +library vunit_lib; + context vunit_lib.vunit_context; + context vunit_lib.com_context; + context vunit_lib.vc_context; + +use work.i2c_ctrl_vc_pkg.all; +use work.i2c_pca9506ish_sim_pkg.all; + +use work.pca9506_pkg.all; + +entity i2c_pca9506ish_th is +end entity; + +architecture th of i2c_pca9506ish_th is + + signal clk : std_logic := '0'; + signal reset : std_logic := '1'; + + signal i2c_bus_scl : std_logic := 'Z'; + 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 io : pca9506_pin_t := (others => (others => '0')); + signal io_oe : pca9506_pin_t; + signal io_o : pca9506_pin_t; + signal int_n : std_logic; + +begin + + -- set up a fastish clock for the sim env + -- and release reset after a bit of time + clk <= not clk after 4 ns; + reset <= '0' after 200 ns; + + i2c_controller_vc_inst: entity work.i2c_controller_vc + generic map( + i2c_ctrl_vc => i2c_ctrl_vc + ) + port map( + scl => i2c_bus_scl, + 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'; + + pca9506_top_inst: entity work.pca9506_top + generic map( + i2c_addr => 7x"20", + giltch_filter_cycles => 3 + ) + port map( + clk => clk, + reset => reset, + scl => i2c_bus_scl, + scl_o => tgt_scl_o, + scl_oe => tgt_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 + ); + + -- DUT: entity work.pca9545ish_top + -- generic map( + -- i2c_addr => 7x"70", + -- giltch_filter_cycles => 3 + -- ) + -- port map( + -- clk => clk, + -- reset => reset, + -- mux_reset => mux_reset, + -- scl => i2c_bus_scl, + -- scl_o => tgt_scl_o, + -- scl_oe => tgt_scl_oe, + -- sda => i2c_bus_sda, + -- sda_o => tgt_sda_o, + -- sda_oe => tgt_sda_oe, + -- mux_sel => mux_sel + -- ); + +end th; \ 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 c079d8da..e03615b4 100644 --- a/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd +++ b/hdl/ip/vhd/i2c/target/i2c_target_phy.vhd @@ -39,6 +39,8 @@ entity i2c_target_phy is in_ack_phase: out std_logic; do_ack : in std_logic; txn_header : out i2c_header; + stop_condition : out std_logic; + start_condition : out std_logic; -- response interface resp_data : in std_logic_vector(7 downto 0); @@ -82,8 +84,6 @@ architecture rtl of i2c_target_phy is constant BYTE_DONE : integer := 8; - signal start_condition : std_logic; - signal stop_condition : std_logic; signal sda_redge : std_logic; signal sda_fedge : std_logic; signal scl_fedge : std_logic; @@ -178,7 +178,6 @@ begin -- the registers are updated if v.txn_hdr.valid = '1' and v.txn_hdr.read_write_n = '1' then v.post_ack_nxt_state := TX_DATA; - v.tx_ready := '1'; else v.post_ack_nxt_state := RX_DATA; end if; @@ -191,14 +190,12 @@ begin v.tx_reg := resp_data; v.tx_ready := '0'; end if; - -- this biffs a shift after an ack if scl_fedge then v.tx_reg := shift_left(r.tx_reg, 1); v.cntr := r.cntr + 1; end if; -- deal with stop or ack/nack if r.cntr = BYTE_DONE then - v.tx_ready := '1'; v.tx_reg := shift_left(r.tx_reg, 1); -- ack if upstream allows, else nack v.state := GET_ACKNACK; @@ -212,6 +209,9 @@ begin end if; if scl_fedge then v.state := r.post_ack_nxt_state; + if r.post_ack_nxt_state = TX_DATA then + v.tx_ready := '1'; + end if; end if; when GET_ACKNACK => diff --git a/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc.vhd b/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc.vhd index 14e5f938..afc4f9a1 100644 --- a/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc.vhd +++ b/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc.vhd @@ -106,6 +106,10 @@ begin procedure gen_start is -- High to low transition of SDA while SCL is high begin + if scl = '0' then + sda <= 'Z'; + wait until rising_edge(aligner_int); + end if; state <= START; scl <= 'Z'; sda <= 'Z'; @@ -140,7 +144,7 @@ begin -- scl must be low coming in and going out -- msb first for i in 7 downto 0 loop - sda <= payload(i); -- clock is low put data out on sda + sda <= '0' when payload(i) = '0' else 'Z'; -- clock is low put data out on sda wait until rising_edge(aligner_int); scl <= 'Z'; wait until falling_edge(aligner_int); diff --git a/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc_pkg.vhd b/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc_pkg.vhd index c1f84ee8..5bfff30c 100644 --- a/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc_pkg.vhd +++ b/hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc_pkg.vhd @@ -91,6 +91,21 @@ package i2c_ctrl_vc_pkg is constant user_actor : in actor_t := null_actor ); + -- send a mixed transaction to the i2c bus + -- a mixed transaction has some number of writes + -- followed by a restart with read + -- and the some number of reads + procedure i2c_mixed_txn ( + signal net : inout network_t; + constant target_addr : std_logic_vector(6 downto 0); + constant tx_data : queue_t; + constant bytes_to_read : integer; + constant rx_data : queue_t; + constant ack_queue : queue_t; + constant ack_last_read : boolean := false; + constant user_actor : in actor_t := null_actor + ); + end package; @@ -277,5 +292,93 @@ package body i2c_ctrl_vc_pkg is send(net, actor, request_msg); end procedure; + procedure i2c_mixed_txn ( + signal net : inout network_t; + constant target_addr : std_logic_vector(6 downto 0); + constant tx_data : queue_t; + constant bytes_to_read : integer; + constant rx_data : queue_t; + constant ack_queue : queue_t; + constant ack_last_read : boolean := false; + constant user_actor : in actor_t := null_actor + ) is + variable request_msg : msg_t := new_msg(i2c_send_start); + variable reply_msg : msg_t; + variable actor : actor_t; + variable tgt_addr_rw : std_logic_vector(7 downto 0); + begin + if user_actor = null_actor then + actor := find("i2c_ctrl_vc"); + else + actor := user_actor; + end if; + + -- send start (msg defined in variable initialization) + send(net, actor, request_msg); + -- send target address + request_msg := new_msg(i2c_send_byte); + -- set rw bit , and put in the target address + tgt_addr_rw := target_addr & WRITE_BIT; + push(request_msg, to_integer(tgt_addr_rw)); + send(net, actor, request_msg); + -- Get Ack/Nack from target + request_msg := new_msg(i2c_get_ack_nack); + send(net, actor, request_msg); + receive_reply(net, request_msg, reply_msg); + -- Put Ack/Nack into ack queue + push_boolean(ack_queue, pop_boolean(reply_msg)); + delete(reply_msg); + -- loop through wr sending and waiting for acks + while not is_empty(tx_data) loop + -- send data byte + request_msg := new_msg(i2c_send_byte); + push(request_msg, pop_byte(tx_data)); + send(net, actor, request_msg); + -- Get Ack/Nack from target, expecting ack + request_msg := new_msg(i2c_get_ack_nack); + send(net, actor, request_msg); + receive_reply(net, request_msg, reply_msg); + -- Put Ack/Nack into ack queue + push_boolean(ack_queue, pop_boolean(reply_msg)); + delete(reply_msg); + end loop; + -- send restart + request_msg := new_msg(i2c_send_start); + send(net, actor, request_msg); + -- send target address, now as a read + request_msg := new_msg(i2c_send_byte); + -- set rw bit to 1 (read), and put in the target address + tgt_addr_rw := target_addr & READ_BIT; + push(request_msg, to_integer(tgt_addr_rw)); + send(net, actor, request_msg); + -- Get Ack/Nack from target + request_msg := new_msg(i2c_get_ack_nack); + send(net, actor, request_msg); + receive_reply(net, request_msg, reply_msg); + -- Put Ack/Nack into ack queue + push_boolean(ack_queue, pop_boolean(reply_msg)); + delete(reply_msg); + + -- Now we read target bytes on these transactions the controller should ack + for i in 0 to bytes_to_read-1 loop + -- get data byte + request_msg := new_msg(i2c_get_byte); + send(net, actor, request_msg); + receive_reply(net, request_msg, reply_msg); + -- store data into rx queue + push_byte(rx_data, pop(reply_msg)); + -- Ack/Nack data + if i = bytes_to_read-1 and ack_last_read = false then + request_msg := new_msg(i2c_send_nack); + else + request_msg := new_msg(i2c_send_ack); + end if; + send(net, actor, request_msg); + end loop; + -- send stop + request_msg := new_msg(i2c_send_stop); + send(net, actor, request_msg); + end procedure; + end package body; \ No newline at end of file