mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-07 16:28:40 -08:00
468 lines
15 KiB
Systemverilog
468 lines
15 KiB
Systemverilog
// 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)) axis_tx();
|
|
taxi_axis_if #(.DATA_W(8)) axis_rx();
|
|
|
|
logic [7:0] axis_tx_reg = '0, axis_tx_next;
|
|
logic axis_tx_valid_reg = 1'b0, axis_tx_valid_next;
|
|
assign axis_tx.tdata = axis_tx_reg;
|
|
assign axis_tx.tvalid = axis_tx_valid_reg;
|
|
assign axis_tx.tlast = 1'b1;
|
|
assign axis_tx.tid = '0;
|
|
assign axis_tx.tdest = '0;
|
|
assign axis_tx.tuser = '0;
|
|
|
|
logic axis_rx_ready_reg = 1'b0, axis_rx_ready_next;
|
|
assign axis_rx.tready = axis_rx_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;
|
|
|
|
axis_tx_next = axis_tx_reg;
|
|
axis_tx_valid_next = axis_tx_valid_reg && !axis_tx.tready;
|
|
|
|
axis_rx_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 (axis_rx.tvalid) begin
|
|
// store address and write
|
|
count_next = 8'(ADDR_WORD_W-1);
|
|
state_next = STATE_ADDRESS;
|
|
end else if (axis_tx.tready && !axis_tx_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
|
|
axis_rx_ready_next = 1'b1;
|
|
|
|
if (axis_rx_ready_reg && axis_rx.tvalid) begin
|
|
// store pointers
|
|
addr_next[8*count_reg +: 8] = axis_rx.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 (axis_rx.tlast) begin
|
|
// end of transaction
|
|
state_next = STATE_IDLE;
|
|
end else begin
|
|
// start writing
|
|
state_next = STATE_WRITE_1;
|
|
end
|
|
end else begin
|
|
if (axis_rx.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 (axis_rx.tvalid || !bus_addressed) begin
|
|
// no longer addressed or now addressed for write, return to idle
|
|
state_next = STATE_IDLE;
|
|
end else if (axis_tx.tready && !axis_tx_valid_reg) begin
|
|
// transfer word and update pointers
|
|
axis_tx_next = data_reg[8*count_reg +: 8];
|
|
axis_tx_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
|
|
axis_rx_ready_next = 1'b1;
|
|
|
|
if (axis_rx_ready_reg && axis_rx.tvalid) begin
|
|
// store word
|
|
data_next[8*count_reg +: 8] = axis_rx.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) || axis_rx.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 (axis_rx_ready_reg & axis_rx.tvalid) begin
|
|
last_cycle_reg <= axis_rx.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;
|
|
|
|
axis_tx_reg <= axis_tx_next;
|
|
axis_tx_valid_reg <= axis_tx_valid_next;
|
|
|
|
axis_rx_ready_reg <= axis_rx_ready_next;
|
|
|
|
if (rst) begin
|
|
state_reg <= STATE_IDLE;
|
|
axis_tx_valid_reg <= 1'b0;
|
|
axis_rx_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_tx(axis_tx),
|
|
.m_axis_rx(axis_rx),
|
|
|
|
// 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
|