Skip to content

Commit 417d932

Browse files
SPI<->AXI interface
1 parent 2ca20ea commit 417d932

File tree

10 files changed

+1177
-0
lines changed

10 files changed

+1177
-0
lines changed

hdl/ip/vhd/spi/axi_controller/BUCK

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("//tools:hdl.bzl", "vhdl_unit", "vunit_sim")
2+
load("//tools:rdl.bzl", "rdl_file")
3+
4+
5+
vhdl_unit(
6+
name = "spi_axi_controller",
7+
srcs = glob(["*.vhd",]),
8+
deps = [
9+
"//hdl/ip/vhd/spi/spi_target_phy:spi_target_phy",
10+
],
11+
visibility = ['PUBLIC']
12+
)
13+
14+
vunit_sim(
15+
name = "spi_axi_tb",
16+
srcs = glob(["sims/**/*.vhd"]),
17+
deps = [
18+
":spi_axi_controller",
19+
"//hdl/ip/vhd/vunit_components:spi_vcs",
20+
"//hdl/ip/vhd/i2c/target:i2c_phy_consolidator",
21+
],
22+
visibility = ['PUBLIC'],
23+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
It supports Mode 0 (0,0) and mode3 (1,1) transfers. It is expected that there are fewer than 256 addressable registers, but we’re picking 16bit addressing for future extensibility without changing the protocol and to support a circular buffer for storing event history.
2+
3+
SPI instructions
4+
[cols=4,options="header"]
5+
|===
6+
|Opcode| Instruction Name | Description| Notes
7+
| 0x0 | Write byte(s) | Write one or more contiguous bytes |
8+
| 0x1| Read byte(s) | Read one or more contiguous bytes|
9+
| 0x2| Bit set | hardware does a bit-wise OR with data and current register state | new_reg = old_reg \|\| data
10+
| 0x3| Bit clr | hardware does a bit-wise clear with data and current register state |new_reg = old_reg && !data
11+
|===
12+
13+
Note that for the bit-set and bit-clear spi instructions, the AXI controller internally does a read-modify-write operation, resulting
14+
in a read of the current register state, a bitwise operation with the data, and a write back of the new register state. These are
15+
not good operations to use if the registers you're modifiying have read-side effects.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 2024 Oxide Computer Company
6+
7+
library ieee;
8+
use ieee.std_logic_1164.all;
9+
use ieee.numeric_std.all;
10+
use ieee.numeric_std_unsigned.all;
11+
12+
library vunit_lib;
13+
context vunit_lib.com_context;
14+
context vunit_lib.vunit_context;
15+
context vunit_lib.vc_context;
16+
17+
use vunit_lib.spi_pkg.all;
18+
19+
use work.spi_axi_tb_pkg.all;
20+
use work.spi_axi_pkg.all;
21+
22+
23+
entity spi_axi_tb is
24+
generic (
25+
26+
runner_cfg : string
27+
);
28+
end entity;
29+
30+
architecture tb of spi_axi_tb is
31+
32+
begin
33+
34+
th: entity work.spi_axi_th;
35+
36+
bench: process
37+
alias reset is << signal th.reset : std_logic >>;
38+
alias csn is << signal th.csn : std_logic >>;
39+
variable buf : buffer_t;
40+
variable buf2 : buffer_t;
41+
variable data : std_logic_vector(7 downto 0) := (others => '0');
42+
variable expected_data : std_logic_vector(7 downto 0) := (others => '0');
43+
variable address : std_logic_vector(15 downto 0) := (others => '0');
44+
alias addr_h : std_logic_vector(7 downto 0) is address(15 downto 8);
45+
alias addr_l : std_logic_vector(7 downto 0) is address(7 downto 0);
46+
begin
47+
-- Always the first thing in the process, set up things for the VUnit test runner
48+
test_runner_setup(runner, runner_cfg);
49+
show_all(wr_logger, display_handler);
50+
-- Reach into the test harness, which generates and de-asserts reset and hold the
51+
-- test cases off until we're out of reset. This runs for every test case
52+
wait until reset = '0';
53+
wait for 500 ns; -- let the resets propagate
54+
55+
while test_suite loop
56+
if run("single-read") then
57+
-- set up the "memory to be read with a known value"
58+
buf := allocate(rmemory, 1 * 64, alignment => 8);
59+
-- Use the simulation interface to set the data we're going to read back
60+
expected_data := X"AA";
61+
write_word(rmemory, 0, expected_data);
62+
-- TB will fault if DUT tries to write to this memory
63+
set_permissions(rmemory, 0, read_only);
64+
-- issue spi read command (data is dummy byte here since this is a)
65+
spi_send_byte(net, spi_opcode_read, address, (others => '0'), csn);
66+
-- Read back word rx'd from DUT and check it matches expected.
67+
-- -- we're going to have 3 dummy bytes here, skip them, keeping the 4th
68+
for i in 0 to 3 loop
69+
pop_stream(net, master_rstream, data);
70+
end loop;
71+
check_equal(data, expected_data, "Read data did not match expected");
72+
elsif run("single-write") then
73+
-- set up the "memory to be read with a known value"
74+
buf := allocate(wmemory, 1 * 64, alignment => 8);
75+
-- Use the simulation interface to set the data we're expected to write
76+
expected_data := X"AA";
77+
set_expected_word(wmemory, 0, expected_data);
78+
-- issue spi write command
79+
spi_send_byte(net, spi_opcode_write, address, expected_data, csn);
80+
-- no reach into the ram and see what is there now
81+
wait for 20 ns;
82+
check_expected_was_written(buf);
83+
elsif run("single-bit-set") then
84+
-- Due to how the axi blocks by vunit work, we need to set up 2 buffers, one for the
85+
-- read side and one for the write-side
86+
-- READ SIDE is buf
87+
-- set up the "memory to be read with a known value"
88+
buf := allocate(rmemory, 1 * 64, alignment => 8);
89+
-- WRITE SIDE is buf2
90+
buf2 := allocate(wmemory, 1 * 64, alignment => 8);
91+
-- Sick X"AA" into the read buffer, this is going to be our starting point for the bit-set
92+
-- Use the simulation interface to set the data we're going to read back
93+
expected_data := X"AA";
94+
write_word(rmemory, 0, expected_data);
95+
-- Our Write address now has X"AA" in it.
96+
data := X"05";
97+
-- This is the expected write into the write-side after the read-modify-write bit-set operation
98+
expected_data := expected_data or data;
99+
set_expected_word(wmemory, 0, expected_data);
100+
101+
-- issue spi write command for bit set with data bits
102+
spi_send_byte(net, spi_opcode_bit_set, address, data, csn);
103+
104+
-- no reach into the ram and see what is there now
105+
wait for 1 us;
106+
check_expected_was_written(buf2);
107+
elsif run("single-bit-clr") then
108+
-- Due to how the axi blocks by vunit work, we need to set up 2 buffers, one for the
109+
-- read side and one for the write-side
110+
-- READ SIDE is buf
111+
-- set up the "memory to be read with a known value"
112+
buf := allocate(rmemory, 1 * 64, alignment => 8);
113+
-- WRITE SIDE is buf2
114+
buf2 := allocate(wmemory, 1 * 64, alignment => 8);
115+
-- Sick X"AA" into the read buffer, this is going to be our starting point for the bit-set
116+
117+
-- Use the simulation interface to set the data we're going to read back
118+
expected_data := X"AA";
119+
write_word(rmemory, 0, expected_data);
120+
-- Our Write address now has X"AA" in it.
121+
data := X"0A";
122+
-- This is the expected write into the write-side after the read-modify-write bit-set operation
123+
expected_data := expected_data and (not data);
124+
set_expected_word(wmemory, 0, expected_data);
125+
126+
-- issue spi write command for bit set with data bits
127+
spi_send_byte(net, spi_opcode_bit_clr, address, data, csn);
128+
-- no reach into the ram and see what is there now
129+
wait for 20 ns;
130+
check_expected_was_written(buf2);
131+
132+
-- multi-read
133+
-- multi-write
134+
-- Note: by and large, multi-bit-set/clr doesn't really make sense over this interface
135+
-- it may still work, do we need to test it?
136+
-- invalid transaction, ok after
137+
-- early termination of transaction, ok after
138+
end if;
139+
end loop;
140+
141+
wait for 2 us;
142+
test_runner_cleanup(runner);
143+
wait;
144+
end process;
145+
146+
-- Example total test timeout dog
147+
test_runner_watchdog(runner, 10 ms);
148+
149+
end tb;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.all;
10+
use ieee.numeric_std_unsigned.all;
11+
12+
library vunit_lib;
13+
context vunit_lib.vunit_context;
14+
context vunit_lib.com_context;
15+
context vunit_lib.vc_context;
16+
17+
use vunit_lib.spi_pkg.all;
18+
19+
package spi_axi_tb_pkg is
20+
21+
constant rd_logger : logger_t := get_logger("axi_rd");
22+
constant rmemory : memory_t := new_memory;
23+
constant axi_read_target : axi_slave_t := new_axi_slave(address_fifo_depth => 1,
24+
memory => rmemory,
25+
logger => rd_logger);
26+
27+
constant wr_logger : logger_t := get_logger("axi_wr");
28+
constant wmemory : memory_t := new_memory;
29+
constant axi_write_target : axi_slave_t := new_axi_slave(address_fifo_depth => 1,
30+
memory => wmemory,
31+
logger => wr_logger);
32+
33+
34+
constant master_spi : spi_master_t := new_spi_master;
35+
constant master_wstream : stream_master_t := as_stream(master_spi);
36+
constant master_rstream : stream_slave_t := as_stream(master_spi);
37+
38+
procedure spi_send_byte(
39+
signal net: inout network_t;
40+
constant opcode: in std_logic_vector(3 downto 0);
41+
constant addr: in std_logic_vector(15 downto 0);
42+
constant byte: in std_logic_vector(7 downto 0);
43+
signal csn : out std_logic
44+
);
45+
46+
end package;
47+
48+
package body spi_axi_tb_pkg is
49+
procedure spi_send_byte(
50+
signal net: inout network_t;
51+
constant opcode: in std_logic_vector(3 downto 0);
52+
constant addr: in std_logic_vector(15 downto 0);
53+
constant byte: in std_logic_vector(7 downto 0);
54+
signal csn : out std_logic
55+
) is
56+
alias addr_h : std_logic_vector(7 downto 0) is addr(15 downto 8);
57+
alias addr_l : std_logic_vector(7 downto 0) is addr(7 downto 0);
58+
begin
59+
csn <= '0';
60+
-- read opcode
61+
push_stream(net, master_wstream, resize(opcode, 8));
62+
-- addr h
63+
push_stream(net, master_wstream, addr_h);
64+
-- addr l
65+
push_stream(net, master_wstream, addr_l);
66+
-- data
67+
push_stream(net, master_wstream, byte);
68+
wait_until_idle(net, as_sync(master_spi));
69+
csn <= '1';
70+
end procedure;
71+
72+
end package body;

0 commit comments

Comments
 (0)