Skip to content

Commit 9ecd722

Browse files
Add PCA9506ish i/o expander logic
1 parent 8e3d3c0 commit 9ecd722

File tree

12 files changed

+1570
-6
lines changed

12 files changed

+1570
-6
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
load("//tools:hdl.bzl", "vhdl_unit", "vunit_sim")
2+
load("//tools:rdl.bzl", "rdl_file")
3+
4+
rdl_file(
5+
name = "pca9506_regs_pkg",
6+
src = "pca9506_regs.rdl",
7+
outputs = ["pca9506_regs_pkg.vhd", "pca9506_regs_pkg.html"],
8+
visibility = ['PUBLIC']
9+
)
10+
11+
vhdl_unit(
12+
name = "pca9506_top",
13+
srcs = glob(["*.vhd"]),
14+
deps = [
15+
"//hdl/ip/vhd/i2c/target:i2c_target_phy",
16+
":pca9506_regs_pkg",
17+
],
18+
visibility = ['PUBLIC']
19+
)
20+
21+
vunit_sim(
22+
name = "i2c_pca9506ish_tb",
23+
srcs = glob(["sims/**/*.vhd"]),
24+
deps = [
25+
":pca9506_top",
26+
"//hdl/ip/vhd/vunit_components:i2c_controller_vc",
27+
],
28+
visibility = ['PUBLIC'],
29+
)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
-- This Source Code Form is subject to the terms of the Mozilla Public
2+
-- License, v. 2.0. If a copy of the MPL was not distributed with this
3+
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
--
5+
-- Copyright 2025 Oxide Computer Company
6+
7+
library ieee;
8+
use ieee.std_logic_1164.all;
9+
use ieee.numeric_std_unsigned.all;
10+
11+
use work.i2c_base_types_pkg.all;
12+
use work.pca9506_pkg.all;
13+
14+
entity pca9506ish_function is
15+
generic(
16+
-- i2c address of the mux
17+
i2c_addr : std_logic_vector(6 downto 0) := 7x"70"
18+
);
19+
port(
20+
clk : in std_logic;
21+
reset : in std_logic;
22+
-- PHY interface
23+
-- instruction interface
24+
inst_data : in std_logic_vector(7 downto 0);
25+
inst_valid : in std_logic;
26+
inst_ready : out std_logic;
27+
in_ack_phase: in std_logic;
28+
ack_next : out std_logic;
29+
txn_header : in i2c_header;
30+
start_condition : in std_logic;
31+
stop_condition : in std_logic;
32+
-- response interface
33+
resp_data : out std_logic_vector(7 downto 0);
34+
resp_valid : out std_logic;
35+
resp_ready : in std_logic;
36+
37+
-- internal register interface
38+
read_data : in std_logic_vector(7 downto 0);
39+
write_data : out std_logic_vector(7 downto 0);
40+
read_strobe : out std_logic;
41+
write_strobe : out std_logic;
42+
cmd_ptr : out cmd_t
43+
);
44+
end entity;
45+
46+
architecture rtl of pca9506ish_function is
47+
48+
type state_t is (IDLE, WAIT_FOR_ACKNACK, WAIT_FOR_ACK_PHASE, ACK, NACK, COMMAND, DO_WRITE, DO_READ);
49+
50+
type reg_t is record
51+
state : state_t;
52+
post_ack_state : state_t;
53+
read_strobe : std_logic;
54+
write_strobe : std_logic;
55+
cmd_reg : cmd_t;
56+
data : std_logic_vector(7 downto 0);
57+
data_valid: std_logic;
58+
increment : std_logic;
59+
end record;
60+
61+
constant rec_reset : reg_t := (
62+
state => IDLE,
63+
post_ack_state => IDLE,
64+
read_strobe => '0',
65+
write_strobe => '0',
66+
cmd_reg => default_reset,
67+
data => (others => '0'),
68+
data_valid => '0',
69+
increment => '0'
70+
);
71+
72+
signal r, rin : reg_t;
73+
74+
begin
75+
76+
--assign some outputs
77+
read_strobe <= r.read_strobe;
78+
write_strobe <= r.write_strobe;
79+
cmd_ptr <= r.cmd_reg;
80+
resp_data <= r.data;
81+
ack_next <= '1' when r.state = ACK else '0';
82+
write_data <= r.data;
83+
inst_ready <= '1' when r.state = COMMAND or r.state = DO_WRITE else '0';
84+
resp_valid <= r.data_valid;
85+
86+
cm: process(all)
87+
variable v : reg_t;
88+
begin
89+
v := r;
90+
91+
case r.state is
92+
when IDLE =>
93+
v.increment := '0';
94+
if txn_header.valid = '1' and txn_header.tgt_addr = i2c_addr then
95+
v.state := ACK;
96+
if txn_header.read_write_n = '0' then
97+
-- all writes go through command state after target
98+
v.post_ack_state := COMMAND;
99+
else
100+
-- post repeated start, reads bypass command state
101+
-- and immediately do reads
102+
v.post_ack_state := DO_READ;
103+
-- pointer is valid
104+
end if;
105+
end if;
106+
107+
108+
when COMMAND =>
109+
if inst_valid = '1' and inst_ready = '1' then
110+
v.cmd_reg.ai := inst_data(7);
111+
v.cmd_reg.pointer := inst_data(5 downto 0);
112+
v.post_ack_state := DO_WRITE;
113+
v.state := ACK;
114+
end if;
115+
116+
when DO_WRITE =>
117+
if inst_valid = '1' and inst_ready = '1' then
118+
v.data := inst_data;
119+
v.state := ACK;
120+
v.write_strobe := '1';
121+
v.increment := v.cmd_reg.ai;
122+
end if;
123+
124+
when DO_READ =>
125+
v.read_strobe := '1';
126+
v.data_valid := '1';
127+
v.data := read_data;
128+
v.state := WAIT_FOR_ACK_PHASE;
129+
130+
when ACK =>
131+
-- clear any single-cycle strobes
132+
v.write_strobe := '0';
133+
v.read_strobe := '0';
134+
-- wait for ack time to finish
135+
if in_ack_phase = '0' then
136+
v.state := r.post_ack_state;
137+
if r.increment then
138+
-- do a category-wrapping increment
139+
v.cmd_reg.pointer := category_wrapping_increment(r.cmd_reg.pointer);
140+
end if;
141+
end if;
142+
when NACK =>
143+
if in_ack_phase = '0' then
144+
v.state := IDLE;
145+
end if;
146+
147+
when WAIT_FOR_ACK_PHASE =>
148+
-- clear any single-cycle strobes
149+
v.write_strobe := '0';
150+
v.read_strobe := '0';
151+
if resp_valid = '1' and resp_ready = '1' then
152+
v.data_valid := '0';
153+
v.increment := v.cmd_reg.ai;
154+
end if;
155+
if in_ack_phase = '1' then
156+
v.state := WAIT_FOR_ACKNACK;
157+
end if;
158+
159+
when WAIT_FOR_ACKNACK =>
160+
-- clear any single-cycle strobes
161+
v.write_strobe := '0';
162+
v.read_strobe := '0';
163+
if in_ack_phase = '0' then
164+
v.state := DO_READ;
165+
if r.increment then
166+
-- do a category-wrapping increment
167+
v.cmd_reg.pointer := category_wrapping_increment(r.cmd_reg.pointer);
168+
end if;
169+
end if;
170+
171+
when others =>
172+
v.state := IDLE;
173+
174+
end case;
175+
176+
if resp_valid = '1' and resp_ready = '1' then
177+
v.data_valid := '0';
178+
end if;
179+
180+
-- No matter where we were, do cleanup if we see a stop
181+
if start_condition = '1' or stop_condition = '1' then
182+
v.state := IDLE;
183+
v.write_strobe := '0';
184+
v.read_strobe := '0';
185+
end if;
186+
187+
rin <= v;
188+
end process;
189+
190+
191+
reg: process(clk, reset)
192+
begin
193+
if reset = '1' then
194+
r <= rec_reset;
195+
elsif rising_edge(clk) then
196+
r <= rin;
197+
end if;
198+
end process;
199+
200+
201+
end rtl;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
-- This Source Code Form is subject to the terms of the Mozilla Public
2+
-- License, v. 2.0. If a copy of the MPL was not distributed with this
3+
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
--
5+
-- Copyright 2025 Oxide Computer Company
6+
7+
library ieee;
8+
use ieee.std_logic_1164.all;
9+
use ieee.numeric_std_unsigned.all;
10+
11+
use work.pca9506_regs_pkg.all;
12+
13+
package pca9506_pkg is
14+
15+
type cmd_t is record
16+
ai : std_logic; -- auto-increment
17+
pointer: std_logic_vector(5 downto 0); -- register number
18+
end record; -- default to 0x80
19+
20+
constant default_reset : cmd_t := (
21+
ai => '1',
22+
pointer => (others => '0')
23+
);
24+
25+
type pca9506_pin_t is array (0 to 4) of std_logic_vector(7 downto 0);
26+
27+
function get_irq_pend(
28+
cur_reg: io_type;
29+
reg_at_last_read: io_type;
30+
reg_mask: io_type
31+
) return std_logic;
32+
33+
function category_wrapping_increment(
34+
pointer: std_logic_vector(5 downto 0) -- register number
35+
) return std_logic_vector;
36+
37+
38+
39+
end package;
40+
41+
package body pca9506_pkg is
42+
43+
function get_irq_pend(
44+
cur_reg: io_type;
45+
reg_at_last_read: io_type;
46+
reg_mask: io_type
47+
) return std_logic
48+
is
49+
50+
begin
51+
-- bitwise XOR current register with last to detect any changes
52+
-- then bitwise mask off any masked bits
53+
-- for the mask, '1' indicates "masked" so we need a bitwise inversion
54+
-- before the bitwise AND for masking.
55+
-- Finally, we need to reduce this down to a single bit to return,
56+
-- We don't currently support unary reduction operators for record
57+
-- types generated by RDL so we use the "compress" function to get
58+
-- back a std_logic_vector of the used bits, and then we use a unary
59+
-- reduction "or" operator on that to return 1 if any bits were 1
60+
return or compress((cur_reg xor reg_at_last_read) and (not reg_mask));
61+
end function;
62+
63+
function category_wrapping_increment(
64+
pointer: std_logic_vector(5 downto 0) -- register number
65+
) return std_logic_vector is
66+
variable lsbs : std_logic_vector(2 downto 0);
67+
68+
begin
69+
lsbs := pointer(2 downto 0) + 1;
70+
if lsbs > 4 then
71+
lsbs := (others => '0');
72+
end if;
73+
74+
-- increment the 3 lsb by one from 0 to 4, wrapping around to 0
75+
return pointer(5 downto 3) & lsbs;
76+
end function;
77+
end package body pca9506_pkg;

0 commit comments

Comments
 (0)