Skip to content

Commit 2223895

Browse files
Add an SPD proxy mux (#294)
This commit adds the `spd_proxy_top` block whose intention is to house an I2C controller which will attempt to utilize the DIMM bus while a CPU is not using it. When the CPU begins using the bus the internal controller will wrap up what it is doing as quickly as possible and we will attempt to gracefully hand the bus over the CPU in a way that doesn't cause problems.
1 parent 2b6ac64 commit 2223895

File tree

9 files changed

+649
-16
lines changed

9 files changed

+649
-16
lines changed

hdl/ip/vhd/i2c/controller/link_layer/i2c_ctrl_link_layer.vhd

+17-14
Original file line numberDiff line numberDiff line change
@@ -293,21 +293,11 @@ begin
293293
v.sda_oe := '0';
294294

295295
if tx_start then
296-
v.state := WAIT_TBUF;
297-
v.counter := STO_TO_STA_BUF_TICKS;
298-
v.count_load := '1';
299-
end if;
300-
301-
-- Wait out tbuf to ensure STOP/START spacing
302-
when WAIT_TBUF =>
303-
if sm_count_done then
304-
-- tbuf is always greater than or equal to the START setup requirement, skip to
305-
-- hold
296+
-- Coming back to IDLE after a transaction means we've waited out tbuf, and tbuf
297+
-- is always greater than or equal to the START setup requirement, skip to hold
306298
v.state := START_HOLD;
307299
v.counter := START_SETUP_HOLD_TICKS;
308300
v.count_load := '1';
309-
else
310-
v.count_decr := '1';
311301
end if;
312302

313303
when WAIT_REPEAT_START =>
@@ -369,7 +359,9 @@ begin
369359
v.state := ACK_RX;
370360
v.bits_shifted := 0;
371361
elsif v.stop_requested then
372-
v.state := STOP_SDA;
362+
-- this is a valid SDA transition cycle so drive SDA low and skip STOP_SDA
363+
v.state := STOP_SCL;
364+
v.sda_oe := '1';
373365
else
374366
v.sda_oe := not sm_reg.tx_data(7);
375367
v.tx_data := sm_reg.tx_data(sm_reg.tx_data'high-1 downto sm_reg.tx_data'low) & '1';
@@ -433,7 +425,18 @@ begin
433425

434426
when STOP_SETUP =>
435427
if sm_count_done then
436-
v := SM_REG_RESET;
428+
v.state := WAIT_TBUF;
429+
v.counter := STO_TO_STA_BUF_TICKS;
430+
v.count_load := '1';
431+
v.sda_oe := '0';
432+
else
433+
v.count_decr := '1';
434+
end if;
435+
436+
-- Wait out tbuf to ensure STOP/START spacing
437+
when WAIT_TBUF =>
438+
if sm_count_done then
439+
v := SM_REG_RESET;
437440
else
438441
v.count_decr := '1';
439442
end if;

hdl/ip/vhd/i2c/controller/txn_layer/sims/i2c_ctrl_txn_layer_th.vhd

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ library vunit_lib;
1313
context vunit_lib.com_context;
1414
context vunit_lib.vc_context;
1515

16-
use work.i2c_common_pkg.all;
1716
use work.i2c_common_pkg.all;
1817
use work.tristate_if_pkg.all;
1918
use work.stream8_pkg;

hdl/ip/vhd/vunit_components/i2c_controller/i2c_ctrlr_vc.vhd

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ begin
120120
sda <= 'Z';
121121
wait for 60 ns; -- need to be longer than 50ns glitch timing
122122
sda <= '0';
123-
wait for thd_sta * 4;
123+
wait for thd_sta;
124124
wait until falling_edge(aligner_int);
125125
scl <= '0'; -- scl is now low, ready for bits
126126
wait for 60 ns; -- need to be longer than 50ns glitch timing

hdl/projects/cosmo_seq/spd_proxy/BUCK

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("//tools:hdl.bzl", "vhdl_unit", "vunit_sim")
2+
3+
vhdl_unit(
4+
name = "spd_proxy_top",
5+
srcs = glob(["*.vhd"]),
6+
deps = [
7+
"//hdl/ip/vhd/common:tristate_if_pkg",
8+
"//hdl/ip/vhd/i2c/common:i2c_common_pkg",
9+
"//hdl/ip/vhd/i2c/common:i2c_glitch_filter",
10+
"//hdl/ip/vhd/i2c/controller:i2c_ctrl_txn_layer",
11+
],
12+
standard = "2019",
13+
visibility = ['PUBLIC']
14+
)
15+
16+
vunit_sim(
17+
name = "spd_proxy_top_tb",
18+
srcs = glob(["sims/*.vhd"]),
19+
deps = [
20+
":spd_proxy_top",
21+
"//hdl/ip/vhd/vunit_components:basic_stream",
22+
"//hdl/ip/vhd/vunit_components:i2c_cmd_vc",
23+
"//hdl/ip/vhd/vunit_components:i2c_target_vc",
24+
"//hdl/ip/vhd/vunit_components:i2c_controller_vc"
25+
],
26+
visibility = ['PUBLIC'],
27+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
[*]
2+
[*] GTKWave Analyzer v3.3.104 (w)1999-2020 BSI
3+
[*] Wed Feb 19 22:29:57 2025
4+
[*]
5+
[dumpfile] "/home/aaron/Oxide/git/quartz/vunit_out/test_output/lib.spd_proxy_top_tb.cpu_with_simulated_start_009e7a6b9dbd8a7136824e0237951cc80f6be48b/nvc/spd_proxy_top_tb.fst"
6+
[dumpfile_mtime] "Wed Feb 19 22:23:27 2025"
7+
[dumpfile_size] 5095
8+
[savefile] "/home/aaron/Oxide/git/quartz/hdl/projects/cosmo_seq/spd_proxy/sims/spd_proxy_top_tb.gtkw"
9+
[timestart] 0
10+
[size] 2816 1283
11+
[pos] 1074 -22
12+
*-34.132660 19580000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
13+
[treeopen] spd_proxy_top_tb.
14+
[treeopen] spd_proxy_top_tb.th.
15+
[treeopen] spd_proxy_top_tb.th.spd_proxy_top_inst.
16+
[treeopen] spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.
17+
[treeopen] spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.i2c_ctrl_link_layer_inst.
18+
[sst_width] 281
19+
[signals_width] 337
20+
[sst_expanded] 1
21+
[sst_vpaned_height] 382
22+
@200
23+
-CPU Bus
24+
@28
25+
spd_proxy_top_tb.th.i2c_controller_vc_inst.state
26+
spd_proxy_top_tb.th.cpu_scl
27+
spd_proxy_top_tb.th.cpu_sda
28+
@200
29+
-
30+
-DIMM Bus
31+
@28
32+
spd_proxy_top_tb.th.i2c_target_vc_inst.state
33+
spd_proxy_top_tb.th.dimm_scl
34+
spd_proxy_top_tb.th.dimm_sda
35+
@200
36+
-
37+
-FPGA Bus
38+
@28
39+
spd_proxy_top_tb.th.spd_proxy_top_inst.fpga_scl_if.i
40+
spd_proxy_top_tb.th.spd_proxy_top_inst.fpga_sda_if.i
41+
@200
42+
-
43+
-SPD Proxy Logic
44+
@28
45+
spd_proxy_top_tb.th.spd_proxy_top_inst.cpu_start_detected
46+
spd_proxy_top_tb.th.spd_proxy_top_inst.cpu_stop_detected
47+
spd_proxy_top_tb.th.spd_proxy_top_inst.cpu_busy
48+
spd_proxy_top_tb.th.spd_proxy_top_inst.cpu_has_mux
49+
spd_proxy_top_tb.th.spd_proxy_top_inst.ctrlr_has_int_mux
50+
@200
51+
-
52+
-Simulated START State
53+
@28
54+
spd_proxy_top_tb.th.spd_proxy_top_inst.scl_sim
55+
spd_proxy_top_tb.th.spd_proxy_top_inst.sda_sim
56+
@29
57+
spd_proxy_top_tb.th.spd_proxy_top_inst.sda_sim_fedge
58+
@28
59+
spd_proxy_top_tb.th.spd_proxy_top_inst.need_start
60+
spd_proxy_top_tb.th.spd_proxy_top_inst.start_simulated
61+
@200
62+
-
63+
-Internal I2C Controller
64+
-Transaction Layer
65+
@28
66+
spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.abort
67+
spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.sm_reg.do_stop
68+
spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.sm_reg.state
69+
@200
70+
-Link Layer
71+
@28
72+
spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.i2c_ctrl_link_layer_inst.sm_reg.stop_requested
73+
spd_proxy_top_tb.th.spd_proxy_top_inst.i2c_ctrl_txn_layer_inst.i2c_ctrl_link_layer_inst.sm_reg.state
74+
[pattern_trace] 1
75+
[pattern_trace] 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
library osvvm;
12+
use osvvm.RandomPkg.RandomPType;
13+
14+
library vunit_lib;
15+
context vunit_lib.com_context;
16+
context vunit_lib.vunit_context;
17+
context vunit_lib.vc_context;
18+
19+
-- VCs
20+
use work.basic_stream_pkg.all;
21+
use work.i2c_cmd_vc_pkg.all;
22+
use work.i2c_common_pkg.all;
23+
use work.i2c_ctrl_vc_pkg.all;
24+
use work.i2c_target_vc_pkg.all;
25+
26+
use work.spd_proxy_top_tb_pkg.all;
27+
28+
entity spd_proxy_top_tb is
29+
generic (
30+
runner_cfg : string
31+
);
32+
end entity;
33+
34+
architecture tb of spd_proxy_top_tb is
35+
begin
36+
37+
th: entity work.spd_proxy_top_th;
38+
39+
bench: process
40+
alias reset is << signal th.reset : std_logic >>;
41+
variable rnd : RandomPType;
42+
variable i2c_ctrlr_msg : msg_t;
43+
44+
variable command : cmd_t;
45+
variable ack : boolean := false;
46+
47+
variable data : std_logic_vector(7 downto 0);
48+
variable exp_addr : std_logic_vector(7 downto 0);
49+
variable exp_data : std_logic_vector(7 downto 0);
50+
variable byte_len : natural;
51+
variable byte_idx : natural;
52+
53+
variable cpu_tx_q : queue_t := new_queue;
54+
variable cpu_ack_q : queue_t := new_queue;
55+
variable fpga_tx_q : queue_t := new_queue;
56+
variable fpga_exp_q : queue_t := new_queue;
57+
58+
-- helper to get the internal FPGA controller doing _something_ before we have the CPU
59+
-- attempting to interrupt
60+
procedure init_controller is
61+
begin
62+
-- arbitrary for the test
63+
exp_addr := X"00";
64+
byte_len := 8;
65+
for i in 0 to byte_len - 1 loop
66+
push_byte(fpga_tx_q, rnd.RandInt(0, 255));
67+
end loop;
68+
fpga_exp_q := copy(fpga_tx_q);
69+
70+
-- write some data in
71+
command := (
72+
op => WRITE,
73+
addr => address(I2C_TGT_VC),
74+
reg => std_logic_vector(exp_addr),
75+
len => to_std_logic_vector(byte_len, command.len'length)
76+
);
77+
issue_i2c_cmd(net, command, fpga_tx_q);
78+
end procedure;
79+
begin
80+
-- Always the first thing in the process, set up things for the VUnit test runner
81+
test_runner_setup(runner, runner_cfg);
82+
-- Reach into the test harness, which generates and de-asserts reset and hold the
83+
-- test cases off until we're out of reset. This runs for every test case
84+
wait until reset = '0';
85+
wait for 500 ns; -- let the resets propagate
86+
87+
while test_suite loop
88+
if run("no_cpu_transaction") then
89+
init_controller;
90+
91+
byte_idx := to_integer(exp_addr);
92+
while not is_empty(fpga_exp_q) loop
93+
data := to_std_logic_vector(pop_byte(fpga_exp_q), data'length);
94+
exp_addr := to_std_logic_vector(byte_idx, exp_addr'length);
95+
check_written_byte(net, I2C_TGT_VC, data, exp_addr);
96+
byte_idx := byte_idx + 1;
97+
end loop;
98+
99+
expect_stop(net, I2C_TGT_VC);
100+
elsif run("cpu_transaction") then
101+
-- Get the FPGA controller started on a transaction
102+
init_controller;
103+
104+
-- At some point into the transaction, have the CPU start its own
105+
wait for rnd.RandInt(500, 4000) * 1 ns;
106+
107+
push_byte(cpu_tx_q, to_integer(rnd.RandSlv(0, 255, 8)));
108+
i2c_write_txn(net, address(I2C_TGT_VC), cpu_tx_q, cpu_ack_q, I2C_CTRL_VC.p_actor);
109+
110+
elsif run("cpu_with_simulated_start") then
111+
-- Get the FPGA controller started on a transaction
112+
init_controller;
113+
114+
-- At some point into the transaction, have the CPU start its own
115+
wait for 9500 ns;
116+
117+
push_byte(cpu_tx_q, to_integer(rnd.RandSlv(0, 255, 8)));
118+
i2c_write_txn(net, address(I2C_TGT_VC), cpu_tx_q, cpu_ack_q, I2C_CTRL_VC.p_actor);
119+
end if;
120+
end loop;
121+
122+
wait for 2 us;
123+
test_runner_cleanup(runner);
124+
wait;
125+
end process;
126+
127+
test_runner_watchdog(runner, 10 ms);
128+
129+
end architecture;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
library vunit_lib;
12+
context vunit_lib.vunit_context;
13+
context vunit_lib.com_context;
14+
context vunit_lib.vc_context;
15+
16+
use work.i2c_cmd_vc_pkg.all;
17+
use work.i2c_ctrl_vc_pkg.all;
18+
use work.i2c_target_vc_pkg.all;
19+
use work.basic_stream_pkg.all;
20+
21+
use work.i2c_common_pkg.all;
22+
23+
package spd_proxy_top_tb_pkg is
24+
-- Constants
25+
constant CLK_PER_NS : positive := 8;
26+
27+
-- Verification Components
28+
constant I2C_CTRL_VC : i2c_ctrl_vc_t := new_i2c_ctrl_vc("cpu_i2c_vc");
29+
constant I2C_TGT_VC : i2c_target_vc_t := new_i2c_target_vc("dimm_i2c_vc");
30+
constant I2C_CMD_VC : i2c_cmd_vc_t := new_i2c_cmd_vc;
31+
constant TX_DATA_SOURCE_VC : basic_source_t := new_basic_source(8);
32+
constant RX_DATA_SINK_VC : basic_sink_t := new_basic_sink(8);
33+
34+
procedure issue_i2c_cmd (
35+
signal net : inout network_t;
36+
constant command : cmd_t;
37+
constant tx_data : queue_t;
38+
);
39+
40+
end package;
41+
42+
package body spd_proxy_top_tb_pkg is
43+
44+
procedure issue_i2c_cmd (
45+
signal net : inout network_t;
46+
constant command : cmd_t;
47+
constant tx_data : queue_t;
48+
) is
49+
variable ack : boolean := FALSE;
50+
begin
51+
push_i2c_cmd(net, I2C_CMD_VC, command);
52+
start_byte_ack(net, I2C_TGT_VC, ack);
53+
check_true(ack, "Peripheral did not ACK correct address");
54+
while not is_empty(tx_data) loop
55+
push_basic_stream(net, TX_DATA_SOURCE_VC, to_std_logic_vector(pop_byte(tx_data), 8));
56+
end loop;
57+
end procedure;
58+
59+
end package body;

0 commit comments

Comments
 (0)