Files
retroSoC/rtl/ip/usb/usb2_ulpi_link.sv
T
maksyuki e219973c45 feat: add USB2 support and related tests
- Introduced a new USB2 transaction engine testbench to validate the functionality of the USB2 transaction engine.
- Updated clock and reset domain tests to include USB2 ULPI crossings.
- Enhanced memory map tests to verify USB2 base addresses and performance offsets.
- Modified pin map tests to ensure USB2 ULPI pins are correctly generated.
- Updated SOC topology tests to account for USB2 peripherals and IRQ mappings.
- Added static timing analysis constraints for USB2 clock domains.
- Created dedicated USB2 tests for various components including packet RAM, descriptor validation, and transaction engine.
- Implemented a register parity test to ensure ABI consistency between RTL and C header definitions.
2026-08-22 07:15:41 +08:00

436 lines
13 KiB
Systemverilog

// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module usb2_ulpi_link #(
parameter int ViewportTimeoutCycles = 1024
) (
// verilog_format: off -- preserve ULPI, packet, and viewport port groups
input logic clk_i,
input logic rst_n_i,
input logic enable_i,
input logic phy_reset_n_i,
input logic tx_start_valid_i,
output logic tx_start_ready_o,
input logic [3:0] tx_pid_i,
input logic tx_has_data_i,
input logic tx_data_valid_i,
output logic tx_data_ready_o,
input logic [7:0] tx_data_i,
input logic tx_data_last_i,
output logic tx_done_o,
output logic tx_error_o,
output logic rx_start_o,
output logic rx_valid_o,
output logic [7:0] rx_data_o,
output logic rx_end_o,
output logic rx_error_o,
output logic [1:0] line_state_o,
output logic [1:0] vbus_state_o,
output logic id_ground_o,
input logic viewport_valid_i,
output logic viewport_ready_o,
input logic viewport_write_i,
input logic [5:0] viewport_addr_i,
input logic [7:0] viewport_write_data_i,
output logic viewport_resp_valid_o,
output logic [7:0] viewport_read_data_o,
output logic viewport_error_o,
output logic link_ready_o,
usb2_ulpi_if.dut ulpi
// verilog_format: on
);
localparam int ViewportCountWidth = (ViewportTimeoutCycles > 1) ? $clog2(
ViewportTimeoutCycles + 1
) : 1;
typedef enum logic [3:0] {
Idle,
TxCommand,
TxPayload,
TxStop,
ViewCommand,
ViewWriteData,
ViewWriteStop,
ViewReadTurn,
ViewReadData,
ViewReadRelease
} link_state_e;
link_state_e s_state_d, s_state_q;
logic [3:0] s_state_bits_q;
logic [3:0] s_tx_pid_d, s_tx_pid_q;
logic s_tx_has_data_d, s_tx_has_data_q;
logic s_view_write_d, s_view_write_q;
logic [5:0] s_view_addr_d, s_view_addr_q;
logic [7:0] s_view_write_data_d, s_view_write_data_q;
logic [7:0] s_view_read_data_d, s_view_read_data_q;
logic [ViewportCountWidth-1:0] s_view_timeout_d, s_view_timeout_q;
logic [1:0] s_line_state_d, s_line_state_q;
logic [1:0] s_vbus_state_d, s_vbus_state_q;
logic s_id_ground_d, s_id_ground_q;
logic s_rx_active_d, s_rx_active_q;
logic s_rx_err_d, s_rx_err_q;
logic s_tx_done_d, s_tx_done_q;
logic s_tx_err_d, s_tx_err_q;
logic s_view_resp_valid_d, s_view_resp_valid_q;
logic s_view_err_d, s_view_err_q;
logic s_view_timeout_expired;
logic s_rx_cmd;
logic s_rx_active_now;
assign s_state_q = link_state_e'(s_state_bits_q);
assign s_rx_cmd = enable_i && phy_reset_n_i && ulpi.dir_i && !ulpi.nxt_i && (s_state_q == Idle);
assign s_rx_active_now = s_rx_cmd ? ulpi.data_di_i[4] : s_rx_active_q;
assign s_view_timeout_expired = s_view_timeout_q == ViewportCountWidth'(ViewportTimeoutCycles);
assign tx_start_ready_o = enable_i && phy_reset_n_i && (s_state_q == Idle) && !ulpi.dir_i;
assign viewport_ready_o = phy_reset_n_i && (s_state_q == Idle) && !ulpi.dir_i &&
!tx_start_valid_i;
assign tx_data_ready_o = (s_state_q == TxPayload) && !ulpi.dir_i && ulpi.nxt_i;
assign tx_done_o = s_tx_done_q;
assign tx_error_o = s_tx_err_q;
assign viewport_resp_valid_o = s_view_resp_valid_q;
assign viewport_read_data_o = s_view_read_data_q;
assign viewport_error_o = s_view_err_q;
assign link_ready_o = enable_i && phy_reset_n_i && (s_state_q == Idle) && !ulpi.dir_i;
assign line_state_o = s_line_state_q;
assign vbus_state_o = s_vbus_state_q;
assign id_ground_o = s_id_ground_q;
assign rx_start_o = s_rx_cmd && ulpi.data_di_i[4] && !s_rx_active_q;
assign rx_end_o = s_rx_cmd && !ulpi.data_di_i[4] && s_rx_active_q;
assign rx_valid_o = enable_i && phy_reset_n_i && (s_state_q == Idle) && ulpi.dir_i &&
ulpi.nxt_i && s_rx_active_q;
assign rx_data_o = ulpi.data_di_i;
assign rx_error_o = s_rx_err_q || (s_rx_cmd && ulpi.data_di_i[5]);
always_comb begin
s_state_d = s_state_q;
s_tx_pid_d = s_tx_pid_q;
s_tx_has_data_d = s_tx_has_data_q;
s_view_write_d = s_view_write_q;
s_view_addr_d = s_view_addr_q;
s_view_write_data_d = s_view_write_data_q;
s_view_read_data_d = s_view_read_data_q;
s_view_timeout_d = s_view_timeout_q;
s_line_state_d = s_line_state_q;
s_vbus_state_d = s_vbus_state_q;
s_id_ground_d = s_id_ground_q;
s_rx_active_d = s_rx_active_now;
s_rx_err_d = s_rx_err_q;
s_tx_done_d = 1'b0;
s_tx_err_d = 1'b0;
s_view_resp_valid_d = 1'b0;
s_view_err_d = 1'b0;
if (s_rx_cmd) begin
s_line_state_d = ulpi.data_di_i[1:0];
s_vbus_state_d = ulpi.data_di_i[3:2];
s_id_ground_d = ulpi.data_di_i[6];
if (ulpi.data_di_i[5]) begin
s_rx_err_d = 1'b1;
end
if (!ulpi.data_di_i[4]) begin
s_rx_err_d = 1'b0;
end
end
unique case (s_state_q)
Idle: begin
s_view_timeout_d = '0;
if (!enable_i || !phy_reset_n_i) begin
s_rx_active_d = 1'b0;
s_rx_err_d = 1'b0;
end else if (tx_start_valid_i && tx_start_ready_o) begin
s_tx_pid_d = tx_pid_i;
s_tx_has_data_d = tx_has_data_i;
s_state_d = TxCommand;
end else if (viewport_valid_i && viewport_ready_o) begin
s_view_write_d = viewport_write_i;
s_view_addr_d = viewport_addr_i;
s_view_write_data_d = viewport_write_data_i;
s_state_d = ViewCommand;
end
end
TxCommand: begin
if (ulpi.dir_i) begin
s_state_d = Idle;
s_tx_err_d = 1'b1;
end else if (ulpi.nxt_i) begin
s_state_d = s_tx_has_data_q ? TxPayload : TxStop;
end
end
TxPayload: begin
if (ulpi.dir_i) begin
s_state_d = Idle;
s_tx_err_d = 1'b1;
end else if (tx_data_valid_i && tx_data_ready_o && tx_data_last_i) begin
s_state_d = TxStop;
end
end
TxStop: begin
s_state_d = Idle;
s_tx_done_d = 1'b1;
end
ViewCommand: begin
s_view_timeout_d = s_view_timeout_q + 1'b1;
if (ulpi.dir_i || s_view_timeout_expired) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
s_view_err_d = 1'b1;
end else if (ulpi.nxt_i) begin
s_view_timeout_d = '0;
s_state_d = s_view_write_q ? ViewWriteData : ViewReadTurn;
end
end
ViewWriteData: begin
s_view_timeout_d = s_view_timeout_q + 1'b1;
if (ulpi.dir_i || s_view_timeout_expired) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
s_view_err_d = 1'b1;
end else if (ulpi.nxt_i) begin
s_state_d = ViewWriteStop;
end
end
ViewWriteStop: begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
end
ViewReadTurn: begin
s_view_timeout_d = s_view_timeout_q + 1'b1;
if (s_view_timeout_expired) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
s_view_err_d = 1'b1;
end else if (ulpi.dir_i) begin
s_view_timeout_d = '0;
s_state_d = ViewReadData;
end
end
ViewReadData: begin
s_view_timeout_d = s_view_timeout_q + 1'b1;
if (s_view_timeout_expired) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
s_view_err_d = 1'b1;
end else if (ulpi.dir_i && ulpi.nxt_i) begin
s_view_read_data_d = ulpi.data_di_i;
s_view_timeout_d = '0;
s_state_d = ViewReadRelease;
end
end
ViewReadRelease: begin
s_view_timeout_d = s_view_timeout_q + 1'b1;
if (!ulpi.dir_i) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
end else if (s_view_timeout_expired) begin
s_state_d = Idle;
s_view_resp_valid_d = 1'b1;
s_view_err_d = 1'b1;
end
end
default: begin
s_state_d = Idle;
s_tx_err_d = 1'b1;
end
endcase
end
always_comb begin
ulpi.data_do_o = 8'd0;
ulpi.data_oe_o = 1'b0;
ulpi.stp_o = 1'b0;
ulpi.reset_n_o = phy_reset_n_i;
unique case (s_state_q)
TxCommand: begin
ulpi.data_do_o = {2'b01, 2'b00, s_tx_pid_q};
ulpi.data_oe_o = !ulpi.dir_i;
end
TxPayload: begin
ulpi.data_do_o = tx_data_i;
ulpi.data_oe_o = !ulpi.dir_i && tx_data_valid_i;
end
TxStop: begin
ulpi.stp_o = 1'b1;
end
ViewCommand: begin
ulpi.data_do_o = {s_view_write_q ? 2'b10 : 2'b11, s_view_addr_q};
ulpi.data_oe_o = !ulpi.dir_i;
end
ViewWriteData: begin
ulpi.data_do_o = s_view_write_data_q;
ulpi.data_oe_o = !ulpi.dir_i;
end
ViewWriteStop: begin
ulpi.stp_o = 1'b1;
end
default: begin
end
endcase
end
dffr #(
.DATA_WIDTH(4)
) u_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_state_d),
.dat_o (s_state_bits_q)
);
dffr #(
.DATA_WIDTH(4)
) u_tx_pid_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_pid_d),
.dat_o (s_tx_pid_q)
);
dffr #(
.DATA_WIDTH(1)
) u_tx_has_data_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_has_data_d),
.dat_o (s_tx_has_data_q)
);
dffr #(
.DATA_WIDTH(1)
) u_view_write_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_write_d),
.dat_o (s_view_write_q)
);
dffr #(
.DATA_WIDTH(6)
) u_view_addr_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_addr_d),
.dat_o (s_view_addr_q)
);
dffr #(
.DATA_WIDTH(8)
) u_view_write_data_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_write_data_d),
.dat_o (s_view_write_data_q)
);
dffr #(
.DATA_WIDTH(8)
) u_view_read_data_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_read_data_d),
.dat_o (s_view_read_data_q)
);
dffr #(
.DATA_WIDTH(ViewportCountWidth)
) u_view_timeout_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_timeout_d),
.dat_o (s_view_timeout_q)
);
dffr #(
.DATA_WIDTH(2)
) u_line_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_line_state_d),
.dat_o (s_line_state_q)
);
dffr #(
.DATA_WIDTH(2)
) u_vbus_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_vbus_state_d),
.dat_o (s_vbus_state_q)
);
dffr #(
.DATA_WIDTH(1)
) u_id_ground_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_id_ground_d),
.dat_o (s_id_ground_q)
);
dffr #(
.DATA_WIDTH(1)
) u_rx_active_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_active_d),
.dat_o (s_rx_active_q)
);
dffr #(
.DATA_WIDTH(1)
) u_rx_error_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_rx_err_d),
.dat_o (s_rx_err_q)
);
dffr #(
.DATA_WIDTH(1)
) u_tx_done_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_done_d),
.dat_o (s_tx_done_q)
);
dffr #(
.DATA_WIDTH(1)
) u_tx_error_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_tx_err_d),
.dat_o (s_tx_err_q)
);
dffr #(
.DATA_WIDTH(1)
) u_view_resp_valid_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_resp_valid_d),
.dat_o (s_view_resp_valid_q)
);
dffr #(
.DATA_WIDTH(1)
) u_view_error_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_view_err_d),
.dat_o (s_view_err_q)
);
`ifndef SV_ASSRT_DISABLE
property p_link_never_drives_with_dir;
@(posedge clk_i) disable iff (!rst_n_i) ulpi.dir_i |-> !ulpi.data_oe_o;
endproperty
assert property (p_link_never_drives_with_dir);
property p_tx_payload_stable_when_stalled;
@(posedge clk_i) disable iff (!rst_n_i)
(s_state_q == TxPayload && tx_data_valid_i && !tx_data_ready_o) |=>
$stable(
tx_data_i
) && $stable(
tx_data_last_i
);
endproperty
assert property (p_tx_payload_stable_when_stalled);
`endif
`ifndef SYNTHESIS
initial begin
if (ViewportTimeoutCycles < 2) begin
$fatal(1, "usb2_ulpi_link: ViewportTimeoutCycles must be at least two");
end
end
`endif
endmodule