// // Copyright 2021 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: LGPL-3.0-or-later // // Module: ctrlport_byte_serializer // // Description: // Serializes CtrlPort requests into a byte stream. // // The serialized data is similar to an AXI4-Streaming interface with one byte // per clock cycle and a valid signal. Direction controls the current transmission // direction. 0 = Master to Slave, 1 = Slave to Master, where this module is the // master. Direction is always present in direction master to slave where the // other signals valid and data can be shared on a tri-state bus. // // The transmission is defined as described below. The bytes are transmitted MSB // first. // Write request: // 1'b1 = write, 15 bit address, 32 bit data (MOSI) = 6 bytes request // 5 bit padding, 1 bit ack, 2 bit status = 1 byte response // Read request: // 1'b0 = read, 15 bit address = 2 bytes request // 32 bit data, 5 bit padding, 1 bit ack, 2 bit status = 5 bytes response // // When sharing valid and data signal lines between master and slave the // timing is defined as: // // clk __/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__/--\__ // direction _______________________/-----------------\____________ // master output enable _____/-----------------\______________________________ // slave output enable _____________________________/-----------------\______ // valid & data zzzzz| Master driven | zzz | Slave driven | zzzzz // transaction <--- Request ---><--- Response ---> // // The slave should use the direction signal to derive it's own output enable // leaving the master the option to terminate the transaction. // On switch from slave to master the direction has to be changed at least // one clock cycle before enabling the master output enable to avoid driving the // bus from two sources. // `default_nettype none module ctrlport_byte_serializer ( // Clock and Reset input wire ctrlport_clk, input wire ctrlport_rst, // Request input wire s_ctrlport_req_wr, input wire s_ctrlport_req_rd, input wire [19:0] s_ctrlport_req_addr, input wire [31:0] s_ctrlport_req_data, // Response output reg s_ctrlport_resp_ack = 1'b0, output reg [ 1:0] s_ctrlport_resp_status = 2'b0, output reg [31:0] s_ctrlport_resp_data = 32'b0, // GPIO interface input wire [ 7:0] bytestream_data_in, input wire bytestream_valid_in, output reg [ 7:0] bytestream_data_out = 8'b0, output reg bytestream_valid_out = 1'b0, output reg bytestream_direction = 1'b0, output reg bytestream_output_enable = 1'b1 ); `include "../../../lib/rfnoc/core/ctrlport.vh" //--------------------------------------------------------------- // transfer constants //--------------------------------------------------------------- // derived from the transaction format (see description above) localparam NUM_BYTES_TX_READ = 2; localparam NUM_BYTES_RX_READ = 5; localparam NUM_BYTES_TX_WRITE = 6; localparam NUM_BYTES_RX_WRITE = 1; localparam TIMEOUT_COUNTER_WIDTH = 6; //---------------------------------------------------------- // FSM to handle transfers //---------------------------------------------------------- localparam IDLE = 3'd0; localparam SENDING = 3'd1; localparam INIT_RX = 3'd2; localparam DIR_SWITCH = 3'd3; localparam DIR_SWITCH_DLY = 3'd4; localparam RECEIVING = 3'd5; localparam ACK = 3'd6; localparam TIMEOUT = 3'd7; // input registers to relax input timing reg [7:0] bytestream_data_in_reg = 8'b0; reg bytestream_valid_in_reg = 1'b0; always @ (posedge ctrlport_clk) begin bytestream_data_in_reg <= bytestream_data_in; bytestream_valid_in_reg <= bytestream_valid_in; end // internal registers reg [ 2:0] state = IDLE; reg [NUM_BYTES_TX_WRITE*8-1:0] request_cache = {NUM_BYTES_TX_WRITE*8{1'b0}}; reg [ NUM_BYTES_RX_READ*8-1:0] response_cache = {NUM_BYTES_RX_READ*8{1'b0}}; reg [ 2:0] byte_counter = 3'b0; reg write_transfer = 1'b0; reg [TIMEOUT_COUNTER_WIDTH-1:0] timeout_counter = {TIMEOUT_COUNTER_WIDTH {1'b0}}; always @ (posedge ctrlport_clk) begin if (ctrlport_rst) begin state <= IDLE; bytestream_valid_out <= 1'b0; byte_counter <= 3'b0; bytestream_direction <= 1'b0; bytestream_output_enable <= 1'b1; s_ctrlport_resp_ack <= 1'b0; end else begin case (state) IDLE: begin // reset values from previous states s_ctrlport_resp_ack <= 1'b0; bytestream_valid_out <= 1'b0; bytestream_output_enable <= 1'b1; byte_counter <= 3'b0; timeout_counter <= {TIMEOUT_COUNTER_WIDTH {1'b0}}; // start transmission on read or write if (s_ctrlport_req_rd || s_ctrlport_req_wr) begin state <= SENDING; request_cache <= {s_ctrlport_req_wr, s_ctrlport_req_addr[14:0], s_ctrlport_req_data}; write_transfer <= s_ctrlport_req_wr; end end // send as many bytes as required for read / write SENDING: begin bytestream_data_out <= request_cache[NUM_BYTES_TX_WRITE*8-8+:8]; request_cache <= {request_cache[NUM_BYTES_TX_WRITE*8-9:0], 8'b0}; bytestream_valid_out <= 1'b1; byte_counter <= byte_counter + 1'b1; if ((write_transfer && byte_counter == NUM_BYTES_TX_WRITE-1) || (~write_transfer && byte_counter == NUM_BYTES_TX_READ-1)) begin state <= INIT_RX; end end // first cycle for switching to make sure valid signal is driven // from slave when being in RECEIVING state INIT_RX: begin state <= DIR_SWITCH; bytestream_direction <= 1'b1; bytestream_output_enable <= 1'b0; bytestream_valid_out <= 1'b0; byte_counter <= 3'b0; end // second switching cycle to let CPLD load the lines based on direction DIR_SWITCH: begin state <= DIR_SWITCH_DLY; end // third switching cycle to compensate data input register DIR_SWITCH_DLY: begin state <= RECEIVING; end // wait for response to be received // immediately change direction after successful reception to have one // clock cycle of pause to avoid double driving the bus RECEIVING: begin timeout_counter <= timeout_counter + 1; if (bytestream_valid_in_reg) begin byte_counter <= byte_counter + 1'b1; response_cache = {response_cache[NUM_BYTES_RX_READ*8-9:0], bytestream_data_in_reg}; if ((write_transfer && byte_counter == NUM_BYTES_RX_WRITE-1) || (~write_transfer && byte_counter == NUM_BYTES_RX_READ-1)) begin state <= ACK; bytestream_direction <= 1'b0; end end if (timeout_counter == {TIMEOUT_COUNTER_WIDTH {1'b1}}) begin state <= TIMEOUT; bytestream_direction <= 1'b0; end end // issue ctrlport response ACK: begin state <= IDLE; s_ctrlport_resp_ack <= 1'b1; // status based on received ack s_ctrlport_resp_status <= response_cache[2] ? response_cache[1:0] : CTRL_STS_CMDERR; if (write_transfer) begin s_ctrlport_resp_data <= 32'b0; end else begin s_ctrlport_resp_data <= response_cache[39:8]; end end TIMEOUT: begin state <= IDLE; s_ctrlport_resp_ack <= 1'b1; s_ctrlport_resp_status <= CTRL_STS_CMDERR; s_ctrlport_resp_data <= 32'b0; end default: begin state <= IDLE; end endcase end end endmodule `default_nettype wire