SummerCart64/fw/rtl/usb/usb_ft1248.sv

222 lines
6.1 KiB
Systemverilog

module usb_ft1248 (
if_system.sys sys,
output usb_clk,
output usb_cs,
input usb_miso,
inout [3:0] usb_miosi,
input usb_pwren,
input rx_flush,
output rx_empty,
input rx_read,
output [7:0] rx_rdata,
input tx_flush,
output tx_full,
input tx_write,
input [7:0] tx_wdata
);
// FIFOs
logic rx_full;
logic rx_write;
logic [7:0] rx_wdata;
logic tx_empty;
logic tx_read;
logic [7:0] tx_rdata;
intel_fifo_8 fifo_8_rx_inst (
.clock(sys.clk),
.sclr(rx_flush),
.empty(rx_empty),
.rdreq(rx_read),
.q(rx_rdata),
.full(rx_full),
.wrreq(rx_write),
.data(rx_wdata)
);
intel_fifo_8 fifo_8_tx_inst (
.clock(sys.clk),
.sclr(tx_flush),
.empty(tx_empty),
.rdreq(tx_read),
.q(tx_rdata),
.full(tx_full),
.wrreq(tx_write),
.data(tx_wdata)
);
// FT1248 interface controller
typedef enum bit [1:0] {
S_TRY_RX,
S_TRY_TX,
S_COMMAND,
S_DATA
} e_state;
typedef enum bit [7:0] {
C_WRITE = 8'h00,
C_READ = 8'h04
} e_command;
typedef enum bit [1:0] {
P_PRE_RISING,
P_RISING,
P_PRE_FALLING,
P_FALLING
} e_clock_phase;
e_state state;
logic [3:0] clock_phase;
logic usb_clk_output;
logic usb_cs_output;
logic [3:0] usb_miosi_input;
logic [3:0] usb_miosi_output;
logic [3:0] usb_miosi_output_data;
logic usb_miosi_output_enable;
logic usb_miosi_output_enable_data;
logic usb_miso_input;
logic usb_pwren_input;
logic is_cmd_write;
logic [1:0] nibble_counter;
logic [7:0] tx_buffer;
always_ff @(posedge sys.clk) begin
if (sys.reset || state == S_TRY_RX || state == S_TRY_TX) begin
clock_phase <= 4'b0001;
end else begin
clock_phase <= {clock_phase[2:0], clock_phase[3]};
end
end
always_ff @(posedge sys.clk) begin
usb_clk <= usb_clk_output;
usb_cs <= usb_cs_output;
usb_miosi_input <= usb_miosi;
usb_miosi_output <= usb_miosi_output_data;
usb_miosi_output_enable <= usb_miosi_output_enable_data;
usb_miso_input <= usb_miso;
usb_pwren_input <= usb_pwren;
tx_buffer <= tx_rdata;
end
always_comb begin
usb_miosi = usb_miosi_output_enable ? usb_miosi_output : 4'bZZZZ;
end
always_comb begin
case (state)
S_COMMAND: begin
usb_clk_output = clock_phase[P_PRE_FALLING] || clock_phase[P_FALLING];
usb_cs_output = 1'b0;
if (is_cmd_write) begin
usb_miosi_output_data = nibble_counter[0] ? C_WRITE[3:0] : C_WRITE[7:4];
end else begin
usb_miosi_output_data = nibble_counter[0] ? C_READ[3:0] : C_READ[7:4];
end
usb_miosi_output_enable_data = nibble_counter < 2'd2;
end
S_DATA: begin
usb_clk_output = clock_phase[P_PRE_FALLING] || clock_phase[P_FALLING];
usb_cs_output = 1'b0;
usb_miosi_output_data = nibble_counter[0] ? tx_buffer[7:4] : tx_buffer[3:0];
usb_miosi_output_enable_data = is_cmd_write;
end
default: begin
usb_clk_output = 1'b0;
usb_cs_output = 1'b1;
usb_miosi_output_data = 4'hF;
usb_miosi_output_enable_data = 1'b0;
end
endcase
end
always_ff @(posedge sys.clk) begin
rx_write <= 1'b0;
tx_read <= 1'b0;
if (clock_phase[P_RISING]) begin
nibble_counter <= nibble_counter + 1'd1;
end
if (sys.reset) begin
state <= S_TRY_RX;
end else begin
case (state)
S_TRY_RX: begin
if (!rx_full) begin
state <= S_COMMAND;
is_cmd_write <= 1'b0;
nibble_counter <= 2'b11;
end else begin
state <= S_TRY_TX;
end
end
S_TRY_TX: begin
if (!tx_empty) begin
state <= S_COMMAND;
is_cmd_write <= 1'b1;
nibble_counter <= 2'b11;
end else begin
state <= S_TRY_RX;
end
end
S_COMMAND: begin
if (clock_phase[P_RISING]) begin
if (nibble_counter == 2'd2) begin
if (usb_miso_input) begin
state <= is_cmd_write ? S_TRY_RX : S_TRY_TX;
end else begin
state <= S_DATA;
nibble_counter <= 2'd0;
end
end
end
end
S_DATA: begin
if (clock_phase[P_FALLING]) begin
if (nibble_counter[0]) begin
tx_read <= is_cmd_write;
end
end
if (clock_phase[P_RISING]) begin
rx_wdata <= {usb_miosi_input, rx_wdata[7:4]};
if (nibble_counter[0]) begin
rx_write <= !is_cmd_write;
end
if (usb_miso_input || (!is_cmd_write && rx_full) || (is_cmd_write && tx_empty)) begin
state <= is_cmd_write ? S_TRY_RX : S_TRY_TX;
end
end
end
default: begin
state <= S_TRY_RX;
end
endcase
end
end
endmodule