From 2fd346269f718683ed151d74fa7a93ec4952bab9 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Wed, 19 Mar 2025 15:56:39 -0700 Subject: [PATCH] xfcp: Add XFCP I2C master module Signed-off-by: Alex Forencich --- README.md | 1 + rtl/xfcp/taxi_xfcp_mod_i2c_master.f | 3 + rtl/xfcp/taxi_xfcp_mod_i2c_master.sv | 815 ++++++++++++++++++ tb/xfcp/taxi_xfcp_mod_i2c_master/Makefile | 46 + .../test_taxi_xfcp_mod_i2c_master.py | 365 ++++++++ .../test_taxi_xfcp_mod_i2c_master.sv | 60 ++ tb/xfcp/taxi_xfcp_mod_i2c_master/xfcp.py | 159 ++++ 7 files changed, 1449 insertions(+) create mode 100644 rtl/xfcp/taxi_xfcp_mod_i2c_master.f create mode 100644 rtl/xfcp/taxi_xfcp_mod_i2c_master.sv create mode 100644 tb/xfcp/taxi_xfcp_mod_i2c_master/Makefile create mode 100644 tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.py create mode 100644 tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.sv create mode 100644 tb/xfcp/taxi_xfcp_mod_i2c_master/xfcp.py diff --git a/README.md b/README.md index 93d7128..566f729 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ To facilitate the dual-license model, contributions to the project can only be a * XFCP UART interface * XFCP AXI module * XFCP AXI lite module + * XFCP I2C master module * XFCP switch ## Example designs diff --git a/rtl/xfcp/taxi_xfcp_mod_i2c_master.f b/rtl/xfcp/taxi_xfcp_mod_i2c_master.f new file mode 100644 index 0000000..6080cab --- /dev/null +++ b/rtl/xfcp/taxi_xfcp_mod_i2c_master.f @@ -0,0 +1,3 @@ +taxi_xfcp_mod_i2c_master.sv +../lss/taxi_i2c_master.sv +../axis/taxi_axis_if.sv diff --git a/rtl/xfcp/taxi_xfcp_mod_i2c_master.sv b/rtl/xfcp/taxi_xfcp_mod_i2c_master.sv new file mode 100644 index 0000000..0f7af9a --- /dev/null +++ b/rtl/xfcp/taxi_xfcp_mod_i2c_master.sv @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2017-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * XFCP I2C master module + */ +module taxi_xfcp_mod_i2c_master # +( + parameter logic [15:0] XFCP_ID_TYPE = 16'h2C00, + parameter XFCP_ID_STR = "I2C Master", + parameter logic [8*16-1:0] XFCP_EXT_ID = 0, + parameter XFCP_EXT_ID_STR = "", + parameter logic [15:0] DEFAULT_PRESCALE = 16'(125000000/400000/4) +) +( + input wire logic clk, + input wire logic rst, + + /* + * XFCP upstream port + */ + taxi_axis_if.snk xfcp_usp_ds, + taxi_axis_if.src xfcp_usp_us, + + /* + * 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 +); + +localparam START_TAG = 8'hFF; +localparam RPATH_TAG = 8'hFE; +localparam I2C_REQ = 8'h2C; +localparam I2C_RESP = 8'h2D; +localparam ID_REQ = 8'hFE; +localparam ID_RESP = 8'hFF; + +// ID ROM +localparam ID_PTR_W = (XFCP_EXT_ID != 0 || XFCP_EXT_ID_STR != 0) ? 6 : 5; +localparam ID_ROM_SIZE = 2**ID_PTR_W; +logic [7:0] id_rom[ID_ROM_SIZE]; + +logic [ID_PTR_W-1:0] id_ptr_reg = '0, id_ptr_next; + +integer j; + +initial begin + // init ID ROM + for (integer i = 0; i < ID_ROM_SIZE; i = i + 1) begin + id_rom[i] = 0; + end + + // binary part + {id_rom[1], id_rom[0]} = 16'h2C00 | (16'h00FF & XFCP_ID_TYPE); // module type + + // string part + // find string length + j = 0; + for (integer i = 1; i <= 16; i = i + 1) begin + if (j == i-1 && (XFCP_ID_STR >> (i*8)) > 0) begin + j = i; + end + end + + // pack string + for (integer i = 0; i <= j; i = i + 1) begin + id_rom[i+16] = XFCP_ID_STR[8*(j-i) +: 8]; + end + + if (XFCP_EXT_ID != 0 || XFCP_EXT_ID_STR != 0) begin + // extended ID + + // binary part + for (integer i = 0; i < 16; i = i + 1) begin + id_rom[i+32] = XFCP_EXT_ID[8*i +: 8]; + end + + // string part + // find string length + j = 0; + for (integer i = 1; i <= 16; i = i + 1) begin + if (j == i-1 && (XFCP_EXT_ID_STR >> (i*8)) > 0) begin + j = i; + end + end + + // pack string + for (integer i = 0; i <= j; i = i + 1) begin + id_rom[i+48] = XFCP_EXT_ID_STR[8*(j-i) +: 8]; + end + end +end + +localparam [3:0] + STATE_IDLE = 4'd0, + STATE_HEADER_1 = 4'd1, + STATE_HEADER_2 = 4'd2, + STATE_PROCESS = 4'd3, + STATE_STATUS = 4'd4, + STATE_PRESCALE_L = 4'd5, + STATE_PRESCALE_H = 4'd6, + STATE_COUNT = 4'd7, + STATE_NEXT_CMD= 4'd8, + STATE_WRITE_DATA = 4'd9, + STATE_READ_DATA = 4'd10, + STATE_WAIT_LAST = 4'd11, + STATE_ID = 4'd12; + +logic [3:0] state_reg = STATE_IDLE, state_next; + +logic [7:0] count_reg = 8'd0, count_next; + +logic last_cycle_reg = 1'b0; + +logic [6:0] i2c_cmd_address_reg = 7'd0, i2c_cmd_address_next; +logic i2c_cmd_start_reg = 1'b0, i2c_cmd_start_next; +logic i2c_cmd_read_reg = 1'b0, i2c_cmd_read_next; +logic i2c_cmd_write_reg = 1'b0, i2c_cmd_write_next; +logic i2c_cmd_write_multi_reg = 1'b0, i2c_cmd_write_multi_next; +logic i2c_cmd_stop_reg = 1'b0, i2c_cmd_stop_next; +logic i2c_cmd_valid_reg = 1'b0, i2c_cmd_valid_next; + +logic cmd_txn_stop_reg = 1'b0, cmd_txn_stop_next; + +logic [7:0] i2c_wr_data_reg = 8'd0, i2c_wr_data_next; +logic i2c_wr_data_valid_reg = 1'b0, i2c_wr_data_valid_next; +logic i2c_wr_data_last_reg = 1'b0, i2c_wr_data_last_next; + +logic i2c_rd_data_ready_reg = 1'b0, i2c_rd_data_ready_next; + +logic [15:0] prescale_reg = DEFAULT_PRESCALE, prescale_next; +logic stop_on_idle_reg = 1'b0, stop_on_idle_next; + +logic missed_ack_reg = 1'b0, missed_ack_next; + +logic xfcp_usp_ds_tready_reg = 1'b0, xfcp_usp_ds_tready_next; + +// internal datapath +logic [7:0] xfcp_usp_us_tdata_int; +logic xfcp_usp_us_tvalid_int; +logic xfcp_usp_us_tready_int_reg = 1'b0; +logic xfcp_usp_us_tlast_int; +logic xfcp_usp_us_tuser_int; +wire xfcp_usp_us_tready_int_early; + +taxi_axis_if #(.DATA_W(12), .KEEP_W(1)) i2c_cmd(); +taxi_axis_if #(.DATA_W(8)) i2c_rd_data(), i2c_wr_data(); + +assign i2c_cmd.tdata[6:0] = i2c_cmd_address_reg; +assign i2c_cmd.tdata[7] = i2c_cmd_start_reg; +assign i2c_cmd.tdata[8] = i2c_cmd_read_reg; +assign i2c_cmd.tdata[9] = i2c_cmd_write_reg; +assign i2c_cmd.tdata[10] = i2c_cmd_write_multi_reg; +assign i2c_cmd.tdata[11] = i2c_cmd_stop_reg; +assign i2c_cmd.tvalid = i2c_cmd_valid_reg; + +assign i2c_wr_data.tdata = i2c_wr_data_reg; +assign i2c_wr_data.tvalid = i2c_wr_data_valid_reg; +assign i2c_wr_data.tlast = i2c_wr_data_last_reg; + +assign i2c_rd_data.tready = i2c_rd_data_ready_reg; + +wire busy; +wire bus_control; +wire bus_active; +wire missed_ack; + +assign xfcp_usp_ds.tready = xfcp_usp_ds_tready_reg; + +always_comb begin + state_next = STATE_IDLE; + + count_next = count_reg; + + id_ptr_next = id_ptr_reg; + + i2c_cmd_address_next = i2c_cmd_address_reg; + i2c_cmd_start_next = i2c_cmd_start_reg; + i2c_cmd_read_next = i2c_cmd_read_reg; + i2c_cmd_write_next = i2c_cmd_write_reg; + i2c_cmd_write_multi_next = i2c_cmd_write_multi_reg; + i2c_cmd_stop_next = i2c_cmd_stop_reg; + i2c_cmd_valid_next = i2c_cmd_valid_reg && !i2c_cmd.tready; + + cmd_txn_stop_next = cmd_txn_stop_reg; + + i2c_wr_data_next = i2c_wr_data_reg; + i2c_wr_data_valid_next = i2c_wr_data_valid_reg && !i2c_wr_data.tready; + i2c_wr_data_last_next = i2c_wr_data_last_reg; + + i2c_rd_data_ready_next = 1'b0; + + prescale_next = prescale_reg; + stop_on_idle_next = stop_on_idle_reg; + + missed_ack_next = missed_ack_reg || missed_ack; + + xfcp_usp_ds_tready_next = 1'b0; + + xfcp_usp_us_tdata_int = 8'd0; + xfcp_usp_us_tvalid_int = 1'b0; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + + case (state_reg) + STATE_IDLE: begin + // idle, wait for start of packet + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + id_ptr_next = 5'd0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + if (xfcp_usp_ds.tlast) begin + // last asserted, ignore cycle + state_next = STATE_IDLE; + end else if (xfcp_usp_ds.tdata == RPATH_TAG) begin + // need to pass through rpath + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + state_next = STATE_HEADER_1; + end else if (xfcp_usp_ds.tdata == START_TAG) begin + // process header + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + state_next = STATE_HEADER_2; + end else begin + // bad start byte, drop packet + state_next = STATE_WAIT_LAST; + end + end else begin + state_next = STATE_IDLE; + end + end + STATE_HEADER_1: begin + // transfer through header + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + // transfer through + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_ds.tlast) begin + // last asserted in header, mark as such and drop + xfcp_usp_us_tuser_int = 1'b1; + state_next = STATE_IDLE; + end else if (xfcp_usp_ds.tdata == START_TAG) begin + // process header + state_next = STATE_HEADER_2; + end else begin + state_next = STATE_HEADER_1; + end + end else begin + state_next = STATE_HEADER_1; + end + end + STATE_HEADER_2: begin + // read packet type + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + if (xfcp_usp_ds.tdata == I2C_REQ && !xfcp_usp_ds.tlast) begin + // start of read + xfcp_usp_us_tdata_int = I2C_RESP; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + state_next = STATE_PROCESS; + end else if (xfcp_usp_ds.tdata == ID_REQ) begin + // identify + xfcp_usp_us_tdata_int = ID_RESP; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + state_next = STATE_ID; + end else begin + // invalid + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b1; + xfcp_usp_us_tuser_int = 1'b1; + if (xfcp_usp_ds.tlast) begin + state_next = STATE_IDLE; + end else begin + state_next = STATE_WAIT_LAST; + end + end + end else begin + state_next = STATE_HEADER_2; + end + end + STATE_PROCESS: begin + // process commands + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = xfcp_usp_ds.tready && xfcp_usp_ds.tvalid; + xfcp_usp_us_tlast_int = xfcp_usp_ds.tlast; + xfcp_usp_us_tuser_int = 1'b0; + + count_next = 8'd0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + if (xfcp_usp_ds.tdata[7]) begin + // set address + i2c_cmd_address_next = xfcp_usp_ds.tdata[6:0]; + state_next = STATE_PROCESS; + end else if (xfcp_usp_ds.tdata[6]) begin + if (xfcp_usp_ds.tdata[5:0] == 6'b000000) begin + // status query + xfcp_usp_ds_tready_next = 1'b0; + xfcp_usp_us_tlast_int = 1'b0; + state_next = STATE_STATUS; + end else if (xfcp_usp_ds.tdata[5:0] == 6'b100000) begin + // set prescale + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_PRESCALE_L; + end else begin + // unknown command + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + state_next = STATE_PROCESS; + end + end + end else begin + i2c_cmd_start_next = xfcp_usp_ds.tdata[0]; + i2c_cmd_read_next = xfcp_usp_ds.tdata[1]; + i2c_cmd_write_next = xfcp_usp_ds.tdata[2]; + i2c_cmd_stop_next = xfcp_usp_ds.tdata[3]; + i2c_cmd_valid_next = (i2c_cmd_start_next || i2c_cmd_read_next || i2c_cmd_write_next || i2c_cmd_stop_next); + + cmd_txn_stop_next = i2c_cmd_stop_next; + + if (xfcp_usp_ds.tdata[4]) begin + i2c_cmd_stop_next = 1'b0; + + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + // read in count value + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_COUNT; + end + end else if (i2c_cmd_write_next && !i2c_cmd_read_next) begin + // write + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + // start writing + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_wr_data_valid_reg; + state_next = STATE_WRITE_DATA; + end + end else if (i2c_cmd_read_next && !i2c_cmd_write_next) begin + // read + xfcp_usp_ds_tready_next = 1'b0; + xfcp_usp_us_tlast_int = 1'b0; + state_next = STATE_READ_DATA; + end else begin + // unknown + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + state_next = STATE_PROCESS; + end + end + end + end else begin + state_next = STATE_PROCESS; + end + end + STATE_STATUS: begin + // read status + xfcp_usp_ds_tready_next = 1'b0; + + xfcp_usp_us_tdata_int = '0; + xfcp_usp_us_tdata_int[0] = busy; + xfcp_usp_us_tdata_int[1] = bus_control; + xfcp_usp_us_tdata_int[2] = bus_active; + xfcp_usp_us_tdata_int[3] = missed_ack_reg; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = last_cycle_reg; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_us_tready_int_reg) begin + missed_ack_next = missed_ack; + + if (last_cycle_reg) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + // process next command + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + state_next = STATE_PROCESS; + end + end else begin + state_next = STATE_STATUS; + end + end + STATE_PRESCALE_L: begin + // store prescale value + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = xfcp_usp_ds.tready && xfcp_usp_ds.tvalid; + xfcp_usp_us_tlast_int = xfcp_usp_ds.tlast; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + prescale_next[7:0] = xfcp_usp_ds.tdata; + + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + state_next = STATE_IDLE; + end else begin + state_next = STATE_PRESCALE_H; + end + end else begin + state_next = STATE_PRESCALE_L; + end + end + STATE_PRESCALE_H: begin + // store prescale value + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = xfcp_usp_ds.tready && xfcp_usp_ds.tvalid; + xfcp_usp_us_tlast_int = xfcp_usp_ds.tlast; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + prescale_next[15:8] = xfcp_usp_ds.tdata; + + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + state_next = STATE_IDLE; + end else begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + state_next = STATE_PROCESS; + end + end else begin + state_next = STATE_PRESCALE_H; + end + end + STATE_COUNT: begin + // store count value + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = xfcp_usp_ds.tready && xfcp_usp_ds.tvalid; + xfcp_usp_us_tlast_int = xfcp_usp_ds.tlast; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + count_next = xfcp_usp_ds.tdata; + + if (i2c_cmd_write_reg && !i2c_cmd_read_reg) begin + // write + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + // start writing + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_wr_data_valid_reg; + state_next = STATE_WRITE_DATA; + end + end else if (i2c_cmd_read_reg && !i2c_cmd_write_reg) begin + // start reading + xfcp_usp_ds_tready_next = 1'b0; + xfcp_usp_us_tlast_int = 1'b0; + state_next = STATE_READ_DATA; + end else begin + // neither, process next command + if (xfcp_usp_ds.tlast) begin + // last cycle; return to idle + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + state_next = STATE_PROCESS; + end + end + end else begin + state_next = STATE_COUNT; + end + end + STATE_NEXT_CMD: begin + // next command + + if (~i2c_cmd_valid_reg) begin + i2c_cmd_start_next = 1'b0; + i2c_cmd_valid_next = 1'b1; + + count_next = count_reg - 1; + + if (count_reg == 2) begin + i2c_cmd_stop_next = cmd_txn_stop_reg; + end + + if (i2c_cmd_write_reg) begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_wr_data_valid_reg; + state_next = STATE_WRITE_DATA; + end else if (i2c_cmd_read_reg) begin + xfcp_usp_ds_tready_next = 1'b0; + state_next = STATE_READ_DATA; + end + end else begin + state_next = STATE_NEXT_CMD; + end + end + STATE_WRITE_DATA: begin + // write data + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_wr_data_valid_reg; + + xfcp_usp_us_tdata_int = xfcp_usp_ds.tdata; + xfcp_usp_us_tvalid_int = xfcp_usp_ds.tready && xfcp_usp_ds.tvalid; + xfcp_usp_us_tlast_int = xfcp_usp_ds.tlast; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + i2c_wr_data_next = xfcp_usp_ds.tdata; + i2c_wr_data_valid_next = 1'b1; + + if (xfcp_usp_ds.tlast) begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + if (count_reg > 1) begin + xfcp_usp_ds_tready_next = 1'b0; + state_next = STATE_NEXT_CMD; + end else begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + state_next = STATE_PROCESS; + end + end + end else begin + state_next = STATE_WRITE_DATA; + end + end + STATE_READ_DATA: begin + // read data + xfcp_usp_ds_tready_next = 1'b0; + i2c_rd_data_ready_next = xfcp_usp_us_tready_int_early; + + xfcp_usp_us_tdata_int = i2c_rd_data.tdata; + xfcp_usp_us_tvalid_int = i2c_rd_data.tvalid; + xfcp_usp_us_tlast_int = last_cycle_reg; + xfcp_usp_us_tuser_int = 1'b0; + + if (i2c_rd_data_ready_reg && i2c_rd_data.tvalid) begin + if (count_reg > 1) begin + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_ds_tready_next = 1'b0; + state_next = STATE_NEXT_CMD; + end else begin + if (last_cycle_reg) begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early && !i2c_cmd_valid_reg; + state_next = STATE_PROCESS; + end + end + end else begin + state_next = STATE_READ_DATA; + end + end + STATE_ID: begin + // send ID + + // drop padding + xfcp_usp_ds_tready_next = !(last_cycle_reg || (xfcp_usp_ds.tvalid && xfcp_usp_ds.tlast)); + + xfcp_usp_us_tdata_int = id_rom[id_ptr_reg]; + xfcp_usp_us_tvalid_int = 1'b1; + xfcp_usp_us_tlast_int = 1'b0; + xfcp_usp_us_tuser_int = 1'b0; + + if (xfcp_usp_us_tready_int_reg) begin + id_ptr_next = id_ptr_reg + 1; + if (id_ptr_reg == ID_ROM_SIZE-1) begin + xfcp_usp_us_tlast_int = 1'b1; + if (!(last_cycle_reg || (xfcp_usp_ds.tvalid && xfcp_usp_ds.tlast))) begin + state_next = STATE_WAIT_LAST; + end else begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end + end else begin + state_next = STATE_ID; + end + end else begin + state_next = STATE_ID; + end + end + STATE_WAIT_LAST: begin + // wait for end of frame + xfcp_usp_ds_tready_next = 1'b1; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + if (xfcp_usp_ds.tlast) begin + xfcp_usp_ds_tready_next = xfcp_usp_us_tready_int_early; + state_next = STATE_IDLE; + end else begin + state_next = STATE_WAIT_LAST; + end + end else begin + state_next = STATE_WAIT_LAST; + end + end + default: begin + // return to idle + state_next = STATE_IDLE; + end + endcase +end + +always_ff @(posedge clk) begin + state_reg <= state_next; + + id_ptr_reg <= id_ptr_next; + + count_reg <= count_next; + + if (xfcp_usp_ds.tready && xfcp_usp_ds.tvalid) begin + last_cycle_reg <= xfcp_usp_ds.tlast; + end + + i2c_cmd_address_reg <= i2c_cmd_address_next; + i2c_cmd_start_reg <= i2c_cmd_start_next; + i2c_cmd_read_reg <= i2c_cmd_read_next; + i2c_cmd_write_reg <= i2c_cmd_write_next; + i2c_cmd_write_multi_reg <= i2c_cmd_write_multi_next; + i2c_cmd_stop_reg <= i2c_cmd_stop_next; + i2c_cmd_valid_reg <= i2c_cmd_valid_next; + + cmd_txn_stop_reg <= cmd_txn_stop_next; + + i2c_wr_data_reg <= i2c_wr_data_next; + i2c_wr_data_valid_reg <= i2c_wr_data_valid_next; + i2c_wr_data_last_reg <= i2c_wr_data_last_next; + + i2c_rd_data_ready_reg <= i2c_rd_data_ready_next; + + prescale_reg <= prescale_next; + stop_on_idle_reg <= stop_on_idle_next; + + missed_ack_reg <= missed_ack_next; + + xfcp_usp_ds_tready_reg <= xfcp_usp_ds_tready_next; + + if (rst) begin + state_reg <= STATE_IDLE; + i2c_cmd_address_reg <= 7'd0; + i2c_cmd_valid_reg <= 1'b0; + i2c_wr_data_valid_reg <= 1'b0; + i2c_rd_data_ready_reg <= 1'b0; + prescale_reg <= DEFAULT_PRESCALE; + stop_on_idle_reg <= 1'b0; + missed_ack_reg <= 1'b0; + xfcp_usp_ds_tready_reg <= 1'b0; + end +end + +// output datapath logic +reg [7:0] xfcp_usp_us_tdata_reg = 8'd0; +reg xfcp_usp_us_tvalid_reg = 1'b0, xfcp_usp_us_tvalid_next; +reg xfcp_usp_us_tlast_reg = 1'b0; +reg xfcp_usp_us_tuser_reg = 1'b0; + +reg [7:0] temp_xfcp_usp_us_tdata_reg = 8'd0; +reg temp_xfcp_usp_us_tvalid_reg = 1'b0, temp_xfcp_usp_us_tvalid_next; +reg temp_xfcp_usp_us_tlast_reg = 1'b0; +reg temp_xfcp_usp_us_tuser_reg = 1'b0; + +// datapath control +reg store_up_xfcp_int_to_output; +reg store_up_xfcp_int_to_temp; +reg store_up_xfcp_temp_to_output; + +assign xfcp_usp_us.tdata = xfcp_usp_us_tdata_reg; +assign xfcp_usp_us.tkeep = '1; +assign xfcp_usp_us.tstrb = xfcp_usp_us.tkeep; +assign xfcp_usp_us.tvalid = xfcp_usp_us_tvalid_reg; +assign xfcp_usp_us.tlast = xfcp_usp_us_tlast_reg; +assign xfcp_usp_us.tid = '0; +assign xfcp_usp_us.tdest = '0; +assign xfcp_usp_us.tuser = xfcp_usp_us_tuser_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign xfcp_usp_us_tready_int_early = xfcp_usp_us.tready || (!temp_xfcp_usp_us_tvalid_reg && (!xfcp_usp_us_tvalid_reg || !xfcp_usp_us_tvalid_int)); + +always_comb begin + // transfer sink ready state to source + xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_reg; + temp_xfcp_usp_us_tvalid_next = temp_xfcp_usp_us_tvalid_reg; + + store_up_xfcp_int_to_output = 1'b0; + store_up_xfcp_int_to_temp = 1'b0; + store_up_xfcp_temp_to_output = 1'b0; + + if (xfcp_usp_us_tready_int_reg) begin + // input is ready + if (xfcp_usp_us.tready || !xfcp_usp_us_tvalid_reg) begin + // output is ready or currently not valid, transfer data to output + xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_int; + store_up_xfcp_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_xfcp_usp_us_tvalid_next = xfcp_usp_us_tvalid_int; + store_up_xfcp_int_to_temp = 1'b1; + end + end else if (xfcp_usp_us.tready) begin + // input is not ready, but output is ready + xfcp_usp_us_tvalid_next = temp_xfcp_usp_us_tvalid_reg; + temp_xfcp_usp_us_tvalid_next = 1'b0; + store_up_xfcp_temp_to_output = 1'b1; + end +end + +always_ff @(posedge clk) begin + if (rst) begin + xfcp_usp_us_tvalid_reg <= 1'b0; + xfcp_usp_us_tready_int_reg <= 1'b0; + temp_xfcp_usp_us_tvalid_reg <= 1'b0; + end else begin + xfcp_usp_us_tvalid_reg <= xfcp_usp_us_tvalid_next; + xfcp_usp_us_tready_int_reg <= xfcp_usp_us_tready_int_early; + temp_xfcp_usp_us_tvalid_reg <= temp_xfcp_usp_us_tvalid_next; + end + + // datapath + if (store_up_xfcp_int_to_output) begin + xfcp_usp_us_tdata_reg <= xfcp_usp_us_tdata_int; + xfcp_usp_us_tlast_reg <= xfcp_usp_us_tlast_int; + xfcp_usp_us_tuser_reg <= xfcp_usp_us_tuser_int; + end else if (store_up_xfcp_temp_to_output) begin + xfcp_usp_us_tdata_reg <= temp_xfcp_usp_us_tdata_reg; + xfcp_usp_us_tlast_reg <= temp_xfcp_usp_us_tlast_reg; + xfcp_usp_us_tuser_reg <= temp_xfcp_usp_us_tuser_reg; + end + + if (store_up_xfcp_int_to_temp) begin + temp_xfcp_usp_us_tdata_reg <= xfcp_usp_us_tdata_int; + temp_xfcp_usp_us_tlast_reg <= xfcp_usp_us_tlast_int; + temp_xfcp_usp_us_tuser_reg <= xfcp_usp_us_tuser_int; + end +end + +taxi_i2c_master +i2c_master_inst ( + .clk(clk), + .rst(rst), + + /* + * Host interface + */ + .s_axis_cmd(i2c_cmd), + .s_axis_data(i2c_wr_data), + .m_axis_data(i2c_rd_data), + + /* + * I2C interface + */ + .scl_i(i2c_scl_i), + .scl_o(i2c_scl_o), + .sda_i(i2c_sda_i), + .sda_o(i2c_sda_o), + + /* + * Status + */ + .busy(busy), + .bus_control(bus_control), + .bus_active(bus_active), + .missed_ack(missed_ack), + + /* + * Configuration + */ + .prescale(prescale_reg), + .stop_on_idle(stop_on_idle_reg) +); + +endmodule + +`resetall diff --git a/tb/xfcp/taxi_xfcp_mod_i2c_master/Makefile b/tb/xfcp/taxi_xfcp_mod_i2c_master/Makefile new file mode 100644 index 0000000..c5cfe31 --- /dev/null +++ b/tb/xfcp/taxi_xfcp_mod_i2c_master/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = taxi_xfcp_mod_i2c_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/xfcp/$(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_DEFAULT_PRESCALE := $(shell expr 125000000 / 400000 / 4 ) + +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 diff --git a/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.py b/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.py new file mode 100644 index 0000000..8780f16 --- /dev/null +++ b/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import itertools +import logging +import os +import struct +import sys + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiStreamBus, AxiStreamSource, AxiStreamSink +from cocotbext.i2c import I2cMemory + +try: + from xfcp import XfcpFrame +except ImportError: + # attempt import from current directory + sys.path.insert(0, os.path.join(os.path.dirname(__file__))) + try: + from xfcp import XfcpFrame + finally: + del sys.path[0] + + +class TB(object): + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, 8, units="ns").start()) + + self.usp_source = AxiStreamSource(AxiStreamBus.from_entity(dut.xfcp_usp_ds), dut.clk, dut.rst) + self.usp_sink = AxiStreamSink(AxiStreamBus.from_entity(dut.xfcp_usp_us), dut.clk, dut.rst) + + self.i2c_mem = [] + + self.i2c_mem.append(I2cMemory(sda=dut.i2c_sda_o, sda_o=dut.i2c_sda_i, + scl=dut.i2c_scl_o, scl_o=dut.i2c_scl_i, addr=0x50, size=1024)) + self.i2c_mem.append(I2cMemory(sda=dut.i2c_sda_o, sda_o=dut.i2c_sda_i, + scl=dut.i2c_scl_o, scl_o=dut.i2c_scl_i, addr=0x51, size=1024)) + + def set_idle_generator(self, generator=None): + if generator: + self.usp_source.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.usp_sink.set_pause_generator(generator()) + + async def 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 get_status(self): + self.log.debug("Get status") + + pkt = XfcpFrame() + pkt.ptype = 0x2C + pkt.payload = b'\x40' + + self.log.debug("TX packet: %s", pkt) + + await self.usp_source.send(pkt.build()) + + rx_frame = await self.usp_sink.recv() + rx_pkt = XfcpFrame.parse(rx_frame.tdata) + + self.log.debug("RX packet: %s", rx_pkt) + + status = rx_pkt.payload[-1] + + self.log.debug("Status: 0x%x", status) + + return status + + async def set_prescale(self, val): + self.log.debug("Set prescale: %s", val) + + payload = bytearray() + payload.append(0x60) # set prescale + payload.extend(struct.pack('H', 0x0004)+test_data + payload = bytearray() + payload.append(0x80 | mem.addr) # set address + payload.append(0x1C) # start write + payload.append(len(data)) # length + payload.extend(data) # data + + pkt = XfcpFrame() + pkt.ptype = 0x2C + pkt.payload = payload + + tb.log.debug("TX packet: %s", pkt) + + await tb.usp_source.send(pkt.build()) + + rx_frame = await tb.usp_sink.recv() + rx_pkt = XfcpFrame.parse(rx_frame.tdata) + + tb.log.debug("RX packet: %s", rx_pkt) + + for k in range(1000): + await RisingEdge(dut.clk) + + data = mem.read_mem(4, 4) + + tb.log.info("Read data: %s", data) + + assert data == test_data + + status = await tb.get_status() + + # no missed ACKs + assert not (status & 8) + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + # change prescale setting + await tb.set_prescale(125000000//4000000//4) + + test_data = b'\x11\x22\x33\x44' + + for mem in tb.i2c_mem: + + mem.write_mem(4, test_data) + + payload = bytearray() + payload.append(0x80 | mem.addr) # set address + payload.append(0x14) # start write + payload.append(2) # length + payload.extend(struct.pack('>H', 0x0004)) # address + payload.append(0x1A) # start read + payload.append(4) # length + + pkt = XfcpFrame() + pkt.ptype = 0x2C + pkt.payload = payload + + tb.log.debug("TX packet: %s", pkt) + + await tb.usp_source.send(pkt.build()) + + rx_frame = await tb.usp_sink.recv() + rx_pkt = XfcpFrame.parse(rx_frame.tdata) + + tb.log.debug("RX packet: %s", rx_pkt) + + data = rx_pkt.payload[-4:] + + tb.log.info("Read data: %s", data) + + assert data == test_data + + status = await tb.get_status() + + # no missed ACKs + assert not (status & 8) + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_nack(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + # change prescale setting + await tb.set_prescale(125000000//4000000//4) + + payload = bytearray() + payload.append(0x80 | 0x55) # set address + payload.append(0x14) # start write + payload.append(2) # length + payload.extend(struct.pack('>H', 0x0004)) # address + + pkt = XfcpFrame() + pkt.ptype = 0x2C + pkt.payload = payload + + tb.log.debug("TX packet: %s", pkt) + + await tb.usp_source.send(pkt.build()) + + rx_frame = await tb.usp_sink.recv() + rx_pkt = XfcpFrame.parse(rx_frame.tdata) + + tb.log.debug("RX packet: %s", rx_pkt) + + status = await tb.get_status() + + # no missed ACKs + assert (status & 8) + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +async def run_test_id(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + pkt = XfcpFrame() + pkt.ptype = 0xFE + pkt.payload = b'' + + tb.log.debug("TX packet: %s", pkt) + + await tb.usp_source.send(pkt.build()) + + rx_frame = await tb.usp_sink.recv() + rx_pkt = XfcpFrame.parse(rx_frame.tdata) + + tb.log.debug("RX packet: %s", rx_pkt) + + assert len(rx_pkt.payload) == 32 + + 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, + run_test_nack, + run_test_id, + ]: + + 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.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', '..', 'rtl')) + + +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_xfcp_mod_i2c_master(request): + + dut = "taxi_xfcp_mod_i2c_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, "xfcp", f"{dut}.f"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['DEFAULT_PRESCALE'] = 125000000//400000//4 + + 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, + ) diff --git a/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.sv b/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.sv new file mode 100644 index 0000000..3751b5d --- /dev/null +++ b/tb/xfcp/taxi_xfcp_mod_i2c_master/test_taxi_xfcp_mod_i2c_master.sv @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * XFCP I2C master module testbench + */ +module test_taxi_xfcp_mod_i2c_master # +( + /* verilator lint_off WIDTHTRUNC */ + parameter logic [15:0] DEFAULT_PRESCALE = 125000000/400000/4 + /* verilator lint_on WIDTHTRUNC */ +) +(); + +logic clk; +logic rst; + +taxi_axis_if #(.DATA_W(8), .LAST_EN(1), .USER_EN(1), .USER_W(1)) xfcp_usp_ds(), xfcp_usp_us(); + +logic i2c_scl_i; +logic i2c_scl_o; +logic i2c_sda_i; +logic i2c_sda_o; + +taxi_xfcp_mod_i2c_master #( + .DEFAULT_PRESCALE(DEFAULT_PRESCALE) +) +uut ( + .clk(clk), + .rst(rst), + + /* + * XFCP upstream port + */ + .xfcp_usp_ds(xfcp_usp_ds), + .xfcp_usp_us(xfcp_usp_us), + + /* + * 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) +); + +endmodule + +`resetall diff --git a/tb/xfcp/taxi_xfcp_mod_i2c_master/xfcp.py b/tb/xfcp/taxi_xfcp_mod_i2c_master/xfcp.py new file mode 100644 index 0000000..174fbb0 --- /dev/null +++ b/tb/xfcp/taxi_xfcp_mod_i2c_master/xfcp.py @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: MIT +""" + +Copyright (c) 2017-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import struct + + +def cobs_encode(block): + block = bytes(block) + enc = bytearray() + + seg = bytearray() + code = 1 + + new_data = True + + for b in block: + if b == 0: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = True + else: + code += 1 + seg.append(b) + new_data = True + if code == 255: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = False + + if new_data: + enc.append(code) + enc.extend(seg) + + return bytes(enc) + + +def cobs_decode(block): + block = bytes(block) + dec = bytearray() + + code = 0 + + i = 0 + + if 0 in block: + return None + + while i < len(block): + code = block[i] + i += 1 + if i+code-1 > len(block): + return None + dec.extend(block[i:i+code-1]) + i += code-1 + if code < 255 and i < len(block): + dec.append(0) + + return bytes(dec) + + +class XfcpFrame(object): + def __init__(self, payload=b'', path=[], rpath=[], ptype=0): + self._payload = b'' + self.path = path + self.rpath = rpath + self.ptype = ptype + + if type(payload) is bytes: + self.payload = payload + if type(payload) is XfcpFrame: + self.payload = payload.payload + self.path = list(payload.path) + self.rpath = list(payload.rpath) + self.ptype = payload.ptype + + @property + def payload(self): + return self._payload + + @payload.setter + def payload(self, value): + self._payload = bytes(value) + + def build(self): + data = bytearray() + + for p in self.path: + data.extend(struct.pack('B', p)) + + if self.rpath: + data.extend(struct.pack('B', 0xFE)) + for p in self.rpath: + data.extend(struct.pack('B', p)) + + data.extend(struct.pack('B', 0xFF)) + + data.extend(struct.pack('B', self.ptype)) + + data.extend(self.payload) + + return data + + def build_cobs(self): + return cobs_encode(self.build())+b'\x00' + + @classmethod + def parse(cls, data): + data = bytes(data) + + i = 0 + + path = [] + rpath = [] + + while i < len(data) and data[i] < 0xFE: + path.append(data[i]) + i += 1 + + if data[i] == 0xFE: + i += 1 + while i < len(data) and data[i] < 0xFE: + rpath.append(data[i]) + i += 1 + + assert data[i] == 0xFF + i += 1 + + ptype = data[i] + i += 1 + + payload = data[i:] + + return cls(payload, path, rpath, ptype) + + @classmethod + def parse_cobs(cls, data): + return cls.parse(cobs_decode(bytes(data))) + + def __eq__(self, other): + if type(other) is XfcpFrame: + return (self.path == other.path and + self.rpath == other.rpath and + self.ptype == other.ptype and + self.payload == other.payload) + return False + + def __repr__(self): + return f"XfcpFrame(payload={self.payload!r}, path={self.path!r}, rpath={self.rpath!r}, ptype={self.ptype})"