mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-07 16:28:40 -08:00
lss: Add I2C slave AXI lite master module and testbench
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
@@ -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
|
||||
|
||||
4
src/lss/rtl/taxi_i2c_slave_axil_master.f
Normal file
4
src/lss/rtl/taxi_i2c_slave_axil_master.f
Normal 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
|
||||
467
src/lss/rtl/taxi_i2c_slave_axil_master.sv
Normal file
467
src/lss/rtl/taxi_i2c_slave_axil_master.sv
Normal 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
|
||||
52
src/lss/tb/taxi_i2c_slave_axil_master/Makefile
Normal file
52
src/lss/tb/taxi_i2c_slave_axil_master/Makefile
Normal 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
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user