lss: Add I2C slave AXI lite master module and testbench

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2025-08-02 00:44:14 -07:00
parent 37825a02f4
commit 4620370035
6 changed files with 814 additions and 0 deletions

View File

@@ -92,6 +92,7 @@ To facilitate the dual-license model, contributions to the project can only be a
* I2C master
* I2C single register
* I2C slave
* I2C slave AXI lite master
* MDIO master
* UART
* Primitives

View File

@@ -0,0 +1,4 @@
taxi_i2c_slave_axil_master.sv
taxi_i2c_slave.sv
../lib/taxi/src/axis/rtl/taxi_axis_if.sv
../lib/taxi/src/axi/rtl/taxi_axil_if.sv

View File

@@ -0,0 +1,467 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2019-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* I2C slave AXI lite master wrapper
*/
module taxi_i2c_slave_axil_master #
(
parameter FILTER_LEN = 4
)
(
input wire logic clk,
input wire logic rst,
/*
* I2C interface
*/
input wire logic i2c_scl_i,
output wire logic i2c_scl_o,
input wire logic i2c_sda_i,
output wire logic i2c_sda_o,
/*
* AXI4-Lite master interface
*/
taxi_axil_if.wr_mst m_axil_wr,
taxi_axil_if.rd_mst m_axil_rd,
/*
* Status
*/
output wire logic busy,
output wire logic [6:0] bus_address,
output wire logic bus_addressed,
output wire logic bus_active,
/*
* Configuration
*/
input wire logic enable,
input wire logic [6:0] device_address
);
/*
I2C
Read
__ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ __
sda \__/_6_X_5_X_4_X_3_X_2_X_1_X_0_/ R \_A_/_7_X_6_X_5_X_4_X_3_X_2_X_1_X_0_\_A_/_7_X_6_X_5_X_4_X_3_X_2_X_1_X_0_/ N \__/
____ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ____
scl ST \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ SP
Write
__ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ __
sda \__/_6_X_5_X_4_X_3_X_2_X_1_X_0_\_W___A_/_7_X_6_X_5_X_4_X_3_X_2_X_1_X_0_\_A_/_7_X_6_X_5_X_4_X_3_X_2_X_1_X_0_\_A____/
____ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ____
scl ST \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ SP
Operation:
This module enables I2C control over an AXI lite bus, useful for enabling a
design to operate as a peripheral to an external microcontroller or similar.
The AXI lite interface are fully parametrizable, with the restriction that the
bus must be divided into 2**m words of 8*2**n bits.
Writing via I2C first accesses an internal address register, followed by the
actual AXI lite bus. The first k bytes go to the address register, where
k = ceil(log2(ADDR_W+log2(DATA_W/SELECT_W))/8)
. The address pointer will automatically increment with reads and writes.
For buses with word size > 8 bits, the address register is in bytes and
unaligned writes will be padded with zeros. Writes to the same bus address in
the same I2C transaction are coalesced and written either once a complete
word is ready or when the I2C transaction terminates with a stop or repeated
start.
Reading via the I2C interface immediately starts reading from the AXI lite
interface starting from the current value of the internal address register.
Like writes, reads are also coalesced when possible. One AXI lite read is
performed on the first I2C read. Once that has been completely transferred
out, another read will be performed on the start of the next I2C read
operation.
Read
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_| |_|_|_|_|_|_|_|_|_|_ ... _|_|_|_|_|_|_|_|_|_| |_|_|_|_|_|_|_|_|___|_|_|_|_|_|_|_|_|_ ... _|_|_|_|_|_|_|_|_| |_|
ST Device Addr W A Address MSB A Address LSB A RS Device Addr R A Data byte 0 A Data byte N N SP
Write
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_| |_|_|_|_|_|_|_|_|_|_ ... _|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_ ... _|_|_|_|_|_|_|_|_|___|
ST Device Addr W A Address MSB A Address LSB A Data byte 0 A Data byte N A SP
Status:
busy
module is communicating over the bus
bus_control
module has control of bus in active state
bus_active
bus is active, not necessarily controlled by this module
Parameters:
device_address
address of slave device
Example of interfacing with tristate pins:
assign scl_i = scl_pin;
assign scl_pin = scl_o ? 1'bz : 1'b0;
assign sda_i = sda_pin;
assign sda_pin = sda_o ? 1'bz : 1'b0;
Example of two interconnected internal I2C devices:
assign scl_1_i = scl_1_o & scl_2_o;
assign scl_2_i = scl_1_o & scl_2_o;
assign sda_1_i = sda_1_o & sda_2_o;
assign sda_2_i = sda_1_o & sda_2_o;
Example of two I2C devices sharing the same pins:
assign scl_1_i = scl_pin;
assign scl_2_i = scl_pin;
assign scl_pin = (scl_1_o & scl_2_o) ? 1'bz : 1'b0;
assign sda_1_i = sda_pin;
assign sda_2_i = sda_pin;
assign sda_pin = (sda_1_o & sda_2_o) ? 1'bz : 1'b0;
Notes:
scl_o should not be connected directly to scl_i, only via AND logic or a tristate
I/O pin. This would prevent devices from stretching the clock period.
*/
// TODO REMOVE THIS
/* verilator lint_off WIDTHTRUNC */
localparam DATA_W = m_axil_wr.DATA_W;
localparam ADDR_W = m_axil_wr.ADDR_W;
localparam STRB_W = m_axil_wr.STRB_W;
// for interfaces that are more than one word wide, disable address lines
localparam VALID_ADDR_W = ADDR_W - $clog2(STRB_W);
// width of data port in words
localparam BYTE_LANES = STRB_W;
// size of words
localparam BYTE_SIZE = DATA_W/BYTE_LANES;
localparam WORD_PART_ADDR_W = $clog2(BYTE_SIZE/8);
localparam ADDR_W_ADJ = ADDR_W+WORD_PART_ADDR_W;
localparam ADDR_WORD_W = (ADDR_W_ADJ+7)/8;
// check configuration
if (BYTE_LANES * BYTE_SIZE != DATA_W)
$fatal(0, "Error: AXI data width not evenly divisible (instance %m)");
if (2**$clog2(BYTE_LANES) != BYTE_LANES)
$fatal(0, "Error: AXI word width must be even power of two (instance %m)");
if (8*2**$clog2(BYTE_SIZE/8) != BYTE_SIZE)
$fatal(0, "Error: AXI word size must be a power of two multiple of 8 bits (instance %m)");
localparam [2:0]
STATE_IDLE = 3'd0,
STATE_ADDRESS = 3'd1,
STATE_READ_1 = 3'd2,
STATE_READ_2 = 3'd3,
STATE_WRITE_1 = 3'd4,
STATE_WRITE_2 = 3'd5;
logic [2:0] state_reg = STATE_IDLE, state_next;
logic [7:0] count_reg = '0, count_next;
logic last_cycle_reg = 1'b0;
logic [ADDR_W_ADJ-1:0] addr_reg = '0, addr_next;
logic [DATA_W-1:0] data_reg = '0, data_next;
logic m_axil_awvalid_reg = 1'b0, m_axil_awvalid_next;
logic [STRB_W-1:0] m_axil_wstrb_reg = '0, m_axil_wstrb_next;
logic m_axil_wvalid_reg = 1'b0, m_axil_wvalid_next;
logic m_axil_bready_reg = 1'b0, m_axil_bready_next;
logic m_axil_arvalid_reg = 1'b0, m_axil_arvalid_next;
logic m_axil_rready_reg = 1'b0, m_axil_rready_next;
logic busy_reg = 1'b0;
taxi_axis_if #(.DATA_W(8)) data_in();
taxi_axis_if #(.DATA_W(8)) data_out();
logic [7:0] data_in_reg = '0, data_in_next;
logic data_in_valid_reg = 1'b0, data_in_valid_next;
assign data_in.tdata = data_in_reg;
assign data_in.tvalid = data_in_valid_reg;
assign data_in.tlast = 1'b1;
assign data_in.tid = '0;
assign data_in.tdest = '0;
assign data_in.tuser = '0;
logic data_out_ready_reg = 1'b0, data_out_ready_next;
assign data_out.tready = data_out_ready_reg;
assign m_axil_wr.awaddr = addr_reg;
assign m_axil_wr.awprot = 3'b010;
assign m_axil_wr.awvalid = m_axil_awvalid_reg;
assign m_axil_wr.wdata = data_reg;
assign m_axil_wr.wstrb = m_axil_wstrb_reg;
assign m_axil_wr.wvalid = m_axil_wvalid_reg;
assign m_axil_wr.bready = m_axil_bready_reg;
assign m_axil_rd.araddr = addr_reg;
assign m_axil_rd.arprot = 3'b010;
assign m_axil_rd.arvalid = m_axil_arvalid_reg;
assign m_axil_rd.rready = m_axil_rready_reg;
assign busy = busy_reg;
always_comb begin
state_next = STATE_IDLE;
count_next = count_reg;
data_in_next = data_in_reg;
data_in_valid_next = data_in_valid_reg && !data_in.tready;
data_out_ready_next = 1'b0;
addr_next = addr_reg;
data_next = data_reg;
m_axil_awvalid_next = m_axil_awvalid_reg && !m_axil_wr.awready;
m_axil_wstrb_next = m_axil_wstrb_reg;
m_axil_wvalid_next = m_axil_wvalid_reg && !m_axil_wr.wready;
m_axil_bready_next = 1'b0;
m_axil_arvalid_next = m_axil_arvalid_reg && !m_axil_rd.arready;
m_axil_rready_next = 1'b0;
case (state_reg)
STATE_IDLE: begin
// idle, wait for I2C interface
if (data_out.tvalid) begin
// store address and write
count_next = 8'(ADDR_WORD_W-1);
state_next = STATE_ADDRESS;
end else if (data_in.tready && !data_in_valid_reg) begin
// read
m_axil_arvalid_next = 1'b1;
m_axil_rready_next = 1'b1;
state_next = STATE_READ_1;
end
end
STATE_ADDRESS: begin
// store address
data_out_ready_next = 1'b1;
if (data_out_ready_reg && data_out.tvalid) begin
// store pointers
addr_next[8*count_reg +: 8] = data_out.tdata;
count_next = count_reg - 1;
if (count_reg == 0) begin
// end of header
// set initial word offset
if (ADDR_W == VALID_ADDR_W && WORD_PART_ADDR_W == 0) begin
count_next = '0;
end else begin
count_next = 8'(addr_next[ADDR_W_ADJ-VALID_ADDR_W-1:0]);
end
m_axil_wstrb_next = 'd0;
data_next = 'd0;
if (data_out.tlast) begin
// end of transaction
state_next = STATE_IDLE;
end else begin
// start writing
state_next = STATE_WRITE_1;
end
end else begin
if (data_out.tlast) begin
// end of transaction
state_next = STATE_IDLE;
end else begin
state_next = STATE_ADDRESS;
end
end
end else begin
state_next = STATE_ADDRESS;
end
end
STATE_READ_1: begin
// wait for data
m_axil_rready_next = 1'b1;
if (m_axil_rd.rready && m_axil_rd.rvalid) begin
// read cycle complete, store result
m_axil_rready_next = 1'b0;
data_next = m_axil_rd.rdata;
addr_next = addr_reg + (1 << (ADDR_W-VALID_ADDR_W+WORD_PART_ADDR_W));
state_next = STATE_READ_2;
end else begin
state_next = STATE_READ_1;
end
end
STATE_READ_2: begin
// send data
if (data_out.tvalid || !bus_addressed) begin
// no longer addressed or now addressed for write, return to idle
state_next = STATE_IDLE;
end else if (data_in.tready && !data_in_valid_reg) begin
// transfer word and update pointers
data_in_next = data_reg[8*count_reg +: 8];
data_in_valid_next = 1'b1;
count_next = count_reg + 1;
if (count_reg == 8'((STRB_W*BYTE_SIZE/8)-1)) begin
// end of stored data word; return to idle
count_next = 0;
state_next = STATE_IDLE;
end else begin
state_next = STATE_READ_2;
end
end else begin
state_next = STATE_READ_2;
end
end
STATE_WRITE_1: begin
// write data
data_out_ready_next = 1'b1;
if (data_out_ready_reg && data_out.tvalid) begin
// store word
data_next[8*count_reg +: 8] = data_out.tdata;
count_next = count_reg + 1;
m_axil_wstrb_next[count_reg >> ((BYTE_SIZE/8)-1)] = 1'b1;
if (count_reg == 8'((STRB_W*BYTE_SIZE/8)-1) || data_out.tlast) begin
// have full word or at end of block, start write operation
count_next = 0;
m_axil_awvalid_next = 1'b1;
m_axil_wvalid_next = 1'b1;
m_axil_bready_next = 1'b1;
state_next = STATE_WRITE_2;
end else begin
state_next = STATE_WRITE_1;
end
end else begin
state_next = STATE_WRITE_1;
end
end
STATE_WRITE_2: begin
// wait for write completion
m_axil_bready_next = 1'b1;
if (m_axil_wr.bready && m_axil_wr.bvalid) begin
// end of write operation
data_next = 'd0;
addr_next = addr_reg + (1 << (ADDR_W-VALID_ADDR_W+WORD_PART_ADDR_W));
m_axil_bready_next = 1'b0;
m_axil_wstrb_next = 'd0;
if (last_cycle_reg) begin
// end of transaction
state_next = STATE_IDLE;
end else begin
state_next = STATE_WRITE_1;
end
end else begin
state_next = STATE_WRITE_2;
end
end
default: begin
// invalid state - return to idle
state_next = STATE_IDLE;
end
endcase
end
always_ff @(posedge clk) begin
state_reg <= state_next;
count_reg <= count_next;
if (data_out_ready_reg & data_out.tvalid) begin
last_cycle_reg <= data_out.tlast;
end
addr_reg <= addr_next;
data_reg <= data_next;
m_axil_awvalid_reg <= m_axil_awvalid_next;
m_axil_wstrb_reg <= m_axil_wstrb_next;
m_axil_wvalid_reg <= m_axil_wvalid_next;
m_axil_bready_reg <= m_axil_bready_next;
m_axil_arvalid_reg <= m_axil_arvalid_next;
m_axil_rready_reg <= m_axil_rready_next;
busy_reg <= state_next != STATE_IDLE;
data_in_reg <= data_in_next;
data_in_valid_reg <= data_in_valid_next;
data_out_ready_reg <= data_out_ready_next;
if (rst) begin
state_reg <= STATE_IDLE;
data_in_valid_reg <= 1'b0;
data_out_ready_reg <= 1'b0;
m_axil_awvalid_reg <= 1'b0;
m_axil_wvalid_reg <= 1'b0;
m_axil_bready_reg <= 1'b0;
m_axil_arvalid_reg <= 1'b0;
m_axil_rready_reg <= 1'b0;
busy_reg <= 1'b0;
end
end
taxi_i2c_slave #(
.FILTER_LEN(FILTER_LEN)
)
i2c_slave_inst (
.clk(clk),
.rst(rst),
// Host interface
.release_bus(1'b0),
.s_axis_data(data_in),
.m_axis_data(data_out),
// I2C Interface
.scl_i(i2c_scl_i),
.scl_o(i2c_scl_o),
.sda_i(i2c_sda_i),
.sda_o(i2c_sda_o),
// Status
.busy(),
.bus_address(bus_address),
.bus_addressed(bus_addressed),
.bus_active(bus_active),
// Configuration
.enable(enable),
.device_address(device_address),
.device_address_mask(7'h7f)
);
endmodule
`resetall

View File

@@ -0,0 +1,52 @@
# SPDX-License-Identifier: CERN-OHL-S-2.0
#
# Copyright (c) 2020-2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
TOPLEVEL_LANG = verilog
SIM ?= verilator
WAVES ?= 0
COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
RTL_DIR = ../../rtl
LIB_DIR = ../../lib
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
DUT = taxi_i2c_slave_axil_master
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = test_$(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).f
# handle file list files
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
# module parameters
export PARAM_FILTER_LEN := 4
export PARAM_AXIL_DATA_W := 32
export PARAM_AXIL_ADDR_W := 16
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
else ifeq ($(SIM), verilator)
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -0,0 +1,195 @@
#!/usr/bin/env python
# SPDX-License-Identifier: CERN-OHL-S-2.0
"""
Copyright (c) 2020-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
"""
import itertools
import logging
import os
import random
import struct
import cocotb_test.simulator
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer
from cocotb.regression import TestFactory
from cocotbext.i2c import I2cMaster
from cocotbext.axi import AxiLiteBus, AxiLiteRam
class TB:
def __init__(self, dut):
self.dut = dut
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.clk, 8, units="ns").start())
self.i2c_master = I2cMaster(sda=dut.i2c_sda_o, sda_o=dut.i2c_sda_i,
scl=dut.i2c_scl_o, scl_o=dut.i2c_scl_i, speed=4000e3)
self.axil_ram = AxiLiteRam(AxiLiteBus.from_entity(dut.m_axil), dut.clk, dut.rst, size=2**16)
dut.enable.setimmediatevalue(1)
dut.device_address.setimmediatevalue(0x50)
def set_idle_generator(self, generator=None):
if generator:
self.axil_ram.write_if.b_channel.set_pause_generator(generator())
self.axil_ram.read_if.r_channel.set_pause_generator(generator())
def set_backpressure_generator(self, generator=None):
if generator:
self.axil_ram.write_if.aw_channel.set_pause_generator(generator())
self.axil_ram.write_if.w_channel.set_pause_generator(generator())
self.axil_ram.read_if.ar_channel.set_pause_generator(generator())
async def cycle_reset(self):
self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 1
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
async def run_test_write(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
tb = TB(dut)
byte_lanes = tb.axil_ram.write_if.byte_lanes
await tb.cycle_reset()
tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter)
for length in range(1, byte_lanes*2):
for offset in range(byte_lanes):
tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
tb.axil_ram.write(addr-128, b'\xaa'*(length+256))
await tb.i2c_master.write(0x50, struct.pack('>H', addr)+test_data)
await tb.i2c_master.send_stop()
tb.log.debug("%s", tb.axil_ram.hexdump_str((addr & ~0xf)-16, (((addr & 0xf)+length-1) & ~0xf)+48))
assert tb.axil_ram.read(addr, length) == test_data
assert tb.axil_ram.read(addr-1, 1) == b'\xaa'
assert tb.axil_ram.read(addr+length, 1) == b'\xaa'
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
tb = TB(dut)
byte_lanes = tb.axil_ram.write_if.byte_lanes
await tb.cycle_reset()
tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter)
for length in range(1, byte_lanes*2):
for offset in range(byte_lanes):
tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
tb.axil_ram.write(addr, test_data)
await tb.i2c_master.write(0x50, struct.pack('>H', addr))
data = await tb.i2c_master.read(0x50, length)
await tb.i2c_master.send_stop()
assert data == test_data
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
def cycle_pause():
return itertools.cycle([1, 1, 1, 0])
if cocotb.SIM_NAME:
for test in [run_test_write, run_test_read]:
factory = TestFactory(test)
factory.add_option("idle_inserter", [None, cycle_pause])
factory.add_option("backpressure_inserter", [None, cycle_pause])
factory.generate_tests()
# cocotb-test
tests_dir = os.path.abspath(os.path.dirname(__file__))
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
def process_f_files(files):
lst = {}
for f in files:
if f[-2:].lower() == '.f':
with open(f, 'r') as fp:
l = fp.read().split()
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
lst[os.path.basename(f)] = f
else:
lst[os.path.basename(f)] = f
return list(lst.values())
def test_taxi_i2c_slave_axil_master(request):
dut = "taxi_i2c_slave_axil_master"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = module
verilog_sources = [
os.path.join(tests_dir, f"{toplevel}.sv"),
os.path.join(rtl_dir, f"{dut}.f"),
]
parameters = {}
parameters['FILTER_LEN'] = 4
parameters['AXIL_DATA_W'] = 32
parameters['AXIL_ADDR_W'] = 16
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, "sim_build",
request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run(
simulator="verilator",
python_search=[tests_dir],
verilog_sources=verilog_sources,
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
)

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* I2C slave AXI lite master testbench
*/
module test_taxi_i2c_slave_axil_master #
(
/* verilator lint_off WIDTHTRUNC */
parameter FILTER_LEN = 4,
parameter AXIL_DATA_W = 32,
parameter AXIL_ADDR_W = 16
/* verilator lint_on WIDTHTRUNC */
)
();
logic clk;
logic rst;
logic i2c_scl_i;
logic i2c_scl_o;
logic i2c_sda_i;
logic i2c_sda_o;
taxi_axil_if #(
.DATA_W(AXIL_DATA_W),
.ADDR_W(AXIL_ADDR_W),
.AWUSER_EN(0),
.WUSER_EN(0),
.BUSER_EN(0),
.ARUSER_EN(0),
.RUSER_EN(0)
) m_axil();
logic busy;
logic [6:0] bus_address;
logic bus_addressed;
logic bus_active;
logic [15:0] prescale;
logic stop_on_idle;
logic enable;
logic [6:0] device_address;
taxi_i2c_slave_axil_master #(
.FILTER_LEN(FILTER_LEN)
)
uut (
.clk(clk),
.rst(rst),
/*
* I2C interface
*/
.i2c_scl_i(i2c_scl_i),
.i2c_scl_o(i2c_scl_o),
.i2c_sda_i(i2c_sda_i),
.i2c_sda_o(i2c_sda_o),
/*
* AXI4-Lite master interface
*/
.m_axil_wr(m_axil),
.m_axil_rd(m_axil),
/*
* Status
*/
.busy(busy),
.bus_address(bus_address),
.bus_addressed(bus_addressed),
.bus_active(bus_active),
/*
* Configuration
*/
.enable(enable),
.device_address(device_address)
);
endmodule
`resetall