Skip to content

Commit

Permalink
Add PCA9506ish i/o expander logic
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanaelhuffman committed Jan 24, 2025
1 parent bb3dec2 commit 27dda86
Show file tree
Hide file tree
Showing 12 changed files with 1,327 additions and 6 deletions.
29 changes: 29 additions & 0 deletions hdl/ip/vhd/i2c/io_expanders/PCA9506ish/BUCK
Original file line number Diff line number Diff line change
@@ -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'],
)
201 changes: 201 additions & 0 deletions hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_function.vhd
Original file line number Diff line number Diff line change
@@ -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;
77 changes: 77 additions & 0 deletions hdl/ip/vhd/i2c/io_expanders/PCA9506ish/pca9506_pkg.vhd
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 27dda86

Please sign in to comment.