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.
This commit is contained in:
maksyuki committed 2026-08-22 07:15:41 +08:00
1 parent e6e3a06136
commit e219973c45
86 files changed
+11305 -48

No files matched your search

+4 -2
View File
@@ -4,7 +4,7 @@
`include "rib_defs.svh"
module axi4_interconnect_tb;
localparam int NumMasters = 6;
localparam int NumMasters = 7;
localparam int NumTargets = 9;
logic clk_i = 1'b0;
@@ -64,6 +64,7 @@ module axi4_interconnect_tb;
.perf_dma_wait_o (),
.perf_sdio0_wait_o (),
.perf_sdio1_wait_o (),
.perf_usb2_wait_o (),
.perf_apb4_periph_wait_o(),
.perf_apb4_system_wait_o(),
.perf_sdram_wait_o (),
@@ -194,7 +195,8 @@ module axi4_interconnect_tb;
while (!masters[0].rvalid && cycles < 100) begin
if (masters[3].arvalid || masters[3].awvalid || masters[3].wvalid ||
masters[4].arvalid || masters[4].awvalid || masters[4].wvalid ||
masters[5].arvalid || masters[5].awvalid || masters[5].wvalid) begin
masters[5].arvalid || masters[5].awvalid || masters[5].wvalid ||
masters[6].arvalid || masters[6].awvalid || masters[6].wvalid) begin
$fatal(1, "inactive storage master drove a request during management boot traffic");
end
@(negedge clk_i);
+2 -1
View File
@@ -39,6 +39,7 @@ module soc_irq_topology_tb;
16: expect_irq(32'd1 << 10);
17: expect_irq(32'd1 << 21);
18: expect_irq(32'd1 << 23);
19: expect_irq(32'd1 << 24);
default: expect_irq(32'd1 << (bit_index + 7));
endcase
end
@@ -63,7 +64,7 @@ module soc_irq_topology_tb;
$fatal(1, "SDIO core IRQ bits are not low when sources are low: IRQ10=%b IRQ21=%b",
s_irq[10], s_irq[21]);
end
if ((s_irq[31:24] !== '0) || (s_irq[19:18] !== '0)) begin
if ((s_irq[31:25] !== '0) || (s_irq[19:18] !== '0)) begin
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq);
end
+3
View File
@@ -86,6 +86,7 @@ module sysctrl_tb;
sysctrl.perf_dma_wait_i = 64'd13;
sysctrl.perf_sdio0_wait_i = 64'd131;
sysctrl.perf_sdio1_wait_i = 64'd141;
sysctrl.perf_usb2_wait_i = 64'd151;
sysctrl.perf_apb4_periph_wait_i = 64'd14;
sysctrl.perf_apb4_system_wait_i = 64'd15;
sysctrl.perf_sdram_wait_i = 64'd16;
@@ -150,6 +151,8 @@ module sysctrl_tb;
if (read_data !== 32'd131) $fatal(1, "SDIO0 performance snapshot was not recorded");
read_register(32'h1000_B094, read_data);
if (read_data !== 32'd141) $fatal(1, "SDIO1 performance snapshot was not recorded");
read_register(32'h1000_B09C, read_data);
if (read_data !== 32'd151) $fatal(1, "USB2 performance snapshot was not recorded");
read_register(32'h1000_B020, read_data);
if (read_data !== 32'h0000_003F) $fatal(1, "user cores were not held in reset");
+84
View File
@@ -0,0 +1,84 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module usb2_descriptor_tb;
logic [31:0] s_buffer_addr;
logic [31:0] s_byte_length;
logic [31:0] s_next_addr;
logic [31:0] s_control;
logic [31:0] s_actual_length;
logic [31:0] s_status;
logic [31:0] s_frame;
logic [31:0] s_reserved;
logic [ 3:0] s_desc_index;
logic s_valid;
usb2_pkg::usb2_desc_error_e s_error;
usb2_dma_descriptor #(
.MaxDescriptors(16)
) u_descriptor (
.buffer_addr_i (s_buffer_addr),
.byte_length_i (s_byte_length),
.next_addr_i (s_next_addr),
.control_i (s_control),
.actual_length_i(s_actual_length),
.status_i (s_status),
.frame_i (s_frame),
.reserved_i (s_reserved),
.desc_index_i (s_desc_index),
.own_o (),
.chain_o (),
.end_o (),
.irq_o (),
.short_ok_o (),
.zero_packet_o (),
.valid_o (s_valid),
.error_o (s_error)
);
initial begin
s_buffer_addr = 32'h2000_0000;
s_byte_length = 32'd512;
s_next_addr = 32'd0;
s_control = 32'h0000_000D;
s_actual_length = 32'd0;
s_status = 32'd0;
s_frame = 32'd0;
s_reserved = 32'd0;
s_desc_index = 4'd0;
#1;
if (!s_valid || (s_error != usb2_pkg::Usb2DescOk)) begin
$fatal(1, "USB2 terminal descriptor rejected");
end
s_buffer_addr = 32'h2000_0001;
#1;
if (s_valid || (s_error != usb2_pkg::Usb2DescBufferAlign)) begin
$fatal(1, "USB2 unaligned buffer accepted");
end
s_buffer_addr = 32'hFFFF_FFFC;
s_byte_length = 32'd8;
#1;
if (s_valid || (s_error != usb2_pkg::Usb2DescAddressWrap)) begin
$fatal(1, "USB2 wrapping buffer accepted");
end
s_buffer_addr = 32'h2000_0000;
s_byte_length = 32'd512;
s_control = 32'h0000_0003;
s_next_addr = 32'h2000_0FE0;
#1;
if (!s_valid) begin
$fatal(1, "USB2 aligned chained descriptor rejected");
end
s_next_addr = 32'h2000_0FF0;
#1;
if (s_valid || (s_error != usb2_pkg::Usb2DescNextAlign)) begin
$fatal(1, "USB2 descriptor crossing 4 KiB boundary accepted");
end
$display("USB2 descriptor validation passed");
$finish;
end
endmodule
+216
View File
@@ -0,0 +1,216 @@
`timescale 1ns / 1ps
module usb2_dma_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
logic abort_i = 1'b0;
logic memory_to_packet_i = 1'b1;
logic allow_short_i = 1'b0;
logic [31:0] desc_base_i = 32'd0;
logic [15:0] desc_limit_i = 16'd1;
logic [31:0] transfer_bytes_i = 32'd5;
logic packet_in_valid_i = 1'b0;
logic packet_in_ready_o;
logic [31:0] packet_in_data_i = '0;
logic [ 3:0] packet_in_strb_i = '0;
logic packet_in_last_i = 1'b0;
logic packet_out_valid_o;
logic packet_out_ready_i = 1'b1;
logic [31:0] packet_out_data_o;
logic [ 3:0] packet_out_strb_o;
logic packet_out_last_o;
logic busy_o;
logic done_o;
logic error_o;
logic [ 7:0] error_code_o;
logic [31:0] current_desc_o;
logic [31:0] next_desc_o;
logic [31:0] bytes_done_o;
logic [31:0] frame_o;
logic descriptor_irq_o;
logic abort_done_o;
integer output_count;
logic [31:0] output_data [0:1];
logic [ 3:0] output_strb [0:1];
logic output_last [0:1];
logic descriptor_irq_seen;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
always_ff @(posedge clk_i) begin
if (!rst_n_i) begin
output_count <= 0;
descriptor_irq_seen <= 1'b0;
end else begin
if (packet_out_valid_o && packet_out_ready_i) begin
output_data[output_count] <= packet_out_data_o;
output_strb[output_count] <= packet_out_strb_o;
output_last[output_count] <= packet_out_last_o;
output_count <= output_count + 1;
end
if (descriptor_irq_o) begin
descriptor_irq_seen <= 1'b1;
end
end
end
usb2_dma #(
.AddrWidth (32),
.DataWidth (32),
.MaxDescriptors(16)
) u_usb2_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.memory_to_packet_i(memory_to_packet_i),
.allow_short_i (allow_short_i),
.desc_base_i (desc_base_i),
.desc_limit_i (desc_limit_i),
.transfer_bytes_i (transfer_bytes_i),
.packet_in_valid_i (packet_in_valid_i),
.packet_in_ready_o (packet_in_ready_o),
.packet_in_data_i (packet_in_data_i),
.packet_in_strb_i (packet_in_strb_i),
.packet_in_last_i (packet_in_last_i),
.packet_out_valid_o(packet_out_valid_o),
.packet_out_ready_i(packet_out_ready_i),
.packet_out_data_o (packet_out_data_o),
.packet_out_strb_o (packet_out_strb_o),
.packet_out_last_o (packet_out_last_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.error_code_o (error_code_o),
.current_desc_o (current_desc_o),
.next_desc_o (next_desc_o),
.bytes_done_o (bytes_done_o),
.frame_o (frame_o),
.descriptor_irq_o (descriptor_irq_o),
.abort_done_o (abort_done_o),
.dma_axi4 (dma_axi4)
);
sdio_axi_memory_responder #(
.DepthWords(16384)
) u_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
task automatic reset_dut;
begin
rst_n_i = 1'b0;
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
@(posedge clk_i);
end
endtask
task automatic start_dma;
begin
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
end
endtask
task automatic send_packet_word(input logic [31:0] data_i, input logic [3:0] strb_i,
input logic last_i);
begin
@(negedge clk_i);
packet_in_data_i = data_i;
packet_in_strb_i = strb_i;
packet_in_last_i = last_i;
packet_in_valid_i = 1'b1;
while (!packet_in_ready_o) @(negedge clk_i);
@(negedge clk_i);
packet_in_valid_i = 1'b0;
end
endtask
initial begin
u_memory.clear_memory();
reset_dut();
// Memory-to-packet transfer splits at 4 KiB and finishes with one valid byte.
u_memory.write_word(32'h0000_0000, 32'h0000_0FFC);
u_memory.write_word(32'h0000_0004, 32'd5);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_000D);
u_memory.write_word(32'h0000_0010, 32'd0);
u_memory.write_word(32'h0000_0014, 32'd0);
u_memory.write_word(32'h0000_0018, 32'd0);
u_memory.write_word(32'h0000_001C, 32'd0);
u_memory.write_word(32'h0000_0FFC, 32'h4433_2211);
u_memory.write_word(32'h0000_1000, 32'h0000_0055);
memory_to_packet_i = 1'b1;
start_dma();
wait (done_o || error_o);
@(posedge clk_i);
if (error_o || (bytes_done_o != 32'd5) || (output_count != 2) ||
(output_data[0] != 32'h4433_2211) || (output_strb[0] != 4'hF) || output_last[0] ||
(output_data[1] != 32'h0000_0055) || (output_strb[1] != 4'h1) || !output_last[1] ||
(u_memory.read_burst_count != 3) || (u_memory.max_read_beats > 16) ||
(u_memory.read_word(
32'h0000_0010
) != 32'd5) || (u_memory.read_word(
32'h0000_0014
) != 32'h0001_0000) || (u_memory.read_word(
32'h0000_000C
) != 32'h0000_000C) || !descriptor_irq_seen) begin
$fatal(1, "memory-to-packet DMA failed err=%b code=%h bytes=%0d outputs=%0d", error_o,
error_code_o, bytes_done_o, output_count);
end
// Packet-to-memory transfer preserves tail strobes and commits OWN last.
reset_dut();
u_memory.write_word(32'h0000_0000, 32'h0000_0200);
u_memory.write_word(32'h0000_0004, 32'd5);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_000D);
u_memory.write_word(32'h0000_0010, 32'd0);
u_memory.write_word(32'h0000_0014, 32'd0);
u_memory.write_word(32'h0000_0018, 32'd0);
u_memory.write_word(32'h0000_001C, 32'd0);
u_memory.write_word(32'h0000_0200, 32'd0);
u_memory.write_word(32'h0000_0204, 32'd0);
memory_to_packet_i = 1'b0;
start_dma();
send_packet_word(32'h8877_6655, 4'hF, 1'b0);
send_packet_word(32'h0000_0099, 4'h1, 1'b1);
wait (done_o || error_o);
@(posedge clk_i);
if (error_o || (bytes_done_o != 32'd5) || (u_memory.read_word(
32'h0000_0200
) != 32'h8877_6655) || (u_memory.read_word(
32'h0000_0204
) != 32'h0000_0099) || (u_memory.read_word(
32'h0000_0010
) != 32'd5) || (u_memory.read_word(
32'h0000_0014
) != 32'h0001_0000) || (u_memory.read_word(
32'h0000_000C
) != 32'h0000_000C) || !descriptor_irq_seen) begin
$fatal(1, "packet-to-memory DMA failed err=%b code=%h bytes=%0d data=%h tail=%h", error_o,
error_code_o, bytes_done_o, u_memory.read_word(32'h0000_0200), u_memory.read_word(
32'h0000_0204));
end
$display("USB2 descriptor DMA test passed");
$finish;
end
endmodule
+115
View File
@@ -0,0 +1,115 @@
`timescale 1ns / 1ps
module usb2_link_domain_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic enable_i = 1'b0;
logic fill_cmd_valid_i = 1'b1;
logic drain_cmd_valid_i = 1'b1;
logic [ 7:0][31:0] endpoint_cfg_i = '0;
logic work_ready_o;
logic result_valid_o;
logic [63:0] result_data_o;
logic setup_valid_o;
logic [63:0] setup_data_o;
logic fill_cmd_ready_o;
logic fill_stream_ready_o;
logic drain_cmd_ready_o;
logic drain_stream_valid_o;
logic [36:0] drain_stream_data_o;
logic buffer_event_valid_o;
logic [ 2:0] buffer_event_data_o;
logic viewport_ready_o;
logic viewport_resp_valid_o;
logic [ 8:0] viewport_resp_data_o;
logic [ 1:0] line_state_o;
logic [ 1:0] vbus_state_o;
logic id_ground_o;
logic link_ready_o;
logic [13:0] frame_o;
logic transaction_busy_o;
logic retry_o;
logic protocol_error_o;
logic [31:0] ecc_corrected_count_o;
logic [31:0] ecc_uncorrectable_count_o;
usb2_ulpi_if ulpi ();
always #5 clk_i = ~clk_i;
assign ulpi.data_di_i = 8'd0;
assign ulpi.dir_i = 1'b0;
assign ulpi.nxt_i = 1'b1;
usb2_link_domain u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.phy_reset_n_i (1'b1),
.high_speed_i (1'b1),
.active_role_i (usb2_pkg::Usb2RoleIdle),
.device_addr_i (7'd0),
.endpoint_cfg_i (endpoint_cfg_i),
.timeout_i (32'd1000),
.work_valid_i (1'b0),
.work_ready_o (work_ready_o),
.work_data_i ('0),
.result_valid_o (result_valid_o),
.result_ready_i (1'b1),
.result_data_o (result_data_o),
.setup_valid_o (setup_valid_o),
.setup_ready_i (1'b1),
.setup_data_o (setup_data_o),
.fill_cmd_valid_i (fill_cmd_valid_i),
.fill_cmd_ready_o (fill_cmd_ready_o),
.fill_cmd_data_i ('0),
.fill_stream_valid_i (1'b0),
.fill_stream_ready_o (fill_stream_ready_o),
.fill_stream_data_i ('0),
.drain_cmd_valid_i (drain_cmd_valid_i),
.drain_cmd_ready_o (drain_cmd_ready_o),
.drain_cmd_data_i ('0),
.drain_stream_valid_o (drain_stream_valid_o),
.drain_stream_ready_i (1'b1),
.drain_stream_data_o (drain_stream_data_o),
.buffer_event_valid_o (buffer_event_valid_o),
.buffer_event_ready_i (1'b1),
.buffer_event_data_o (buffer_event_data_o),
.viewport_valid_i (1'b0),
.viewport_ready_o (viewport_ready_o),
.viewport_data_i ('0),
.viewport_resp_valid_o (viewport_resp_valid_o),
.viewport_resp_ready_i (1'b1),
.viewport_resp_data_o (viewport_resp_data_o),
.line_state_o (line_state_o),
.vbus_state_o (vbus_state_o),
.id_ground_o (id_ground_o),
.link_ready_o (link_ready_o),
.frame_o (frame_o),
.transaction_busy_o (transaction_busy_o),
.retry_o (retry_o),
.protocol_error_o (protocol_error_o),
.ecc_corrected_count_o (ecc_corrected_count_o),
.ecc_uncorrectable_count_o(ecc_uncorrectable_count_o),
.ulpi (ulpi)
);
initial begin
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
repeat (4) @(posedge clk_i);
if (fill_cmd_ready_o || drain_cmd_ready_o || transaction_busy_o) begin
$fatal(1, "USB2 disabled packet-store isolation failed");
end
fill_cmd_valid_i = 1'b0;
drain_cmd_valid_i = 1'b0;
enable_i = 1'b1;
repeat (8) @(posedge clk_i);
if (!link_ready_o || !ulpi.reset_n_o || ulpi.data_oe_o || ulpi.stp_o ||
transaction_busy_o || result_valid_o || setup_valid_o || protocol_error_o ||
(ecc_corrected_count_o != 32'd0) || (ecc_uncorrectable_count_o != 32'd0)) begin
$fatal(1, "USB2 link domain idle smoke failed");
end
$display("USB2 link domain smoke passed");
$finish;
end
endmodule
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module usb2_packet_ram_tb;
logic s_clk;
logic s_rst_n;
logic s_write;
logic s_read;
logic [11:0] s_addr;
logic [31:0] s_write_data;
logic s_inject_single;
logic s_inject_double;
logic [31:0] s_read_data;
logic s_read_valid;
logic s_corrected;
logic s_uncorrectable;
usb2_packet_ram #(
.EnableFaultInjection(1'b1)
) u_packet_ram (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.write_i (s_write),
.read_i (s_read),
.addr_i (s_addr),
.write_data_i (s_write_data),
.inject_single_i(s_inject_single),
.inject_double_i(s_inject_double),
.read_data_o (s_read_data),
.read_valid_o (s_read_valid),
.corrected_o (s_corrected),
.uncorrectable_o(s_uncorrectable)
);
always #5 s_clk = ~s_clk;
task automatic write_word(input logic [11:0] addr_i, input logic [31:0] data_i,
input logic single_i, input logic double_i);
begin
@(negedge s_clk);
s_addr = addr_i;
s_write_data = data_i;
s_inject_single = single_i;
s_inject_double = double_i;
s_write = 1'b1;
@(negedge s_clk);
s_write = 1'b0;
s_inject_single = 1'b0;
s_inject_double = 1'b0;
end
endtask
task automatic read_word(input logic [11:0] addr_i);
begin
@(negedge s_clk);
s_addr = addr_i;
s_read = 1'b1;
@(posedge s_clk);
#1;
s_read = 1'b0;
end
endtask
initial begin
s_clk = 1'b0;
s_rst_n = 1'b0;
s_write = 1'b0;
s_read = 1'b0;
s_addr = '0;
s_write_data = '0;
s_inject_single = 1'b0;
s_inject_double = 1'b0;
repeat (2) @(negedge s_clk);
s_rst_n = 1'b1;
write_word(12'h010, 32'h1357_9BDF, 1'b0, 1'b0);
read_word(12'h010);
if (!s_read_valid || s_corrected || s_uncorrectable || (s_read_data != 32'h1357_9BDF)) begin
$fatal(1, "USB2 packet RAM clean read failed");
end
write_word(12'h011, 32'h2468_ACE0, 1'b1, 1'b0);
read_word(12'h011);
if (!s_read_valid || !s_corrected || s_uncorrectable || (s_read_data != 32'h2468_ACE0)) begin
$fatal(1, "USB2 packet RAM single-bit correction failed");
end
write_word(12'h012, 32'h55AA_F00D, 1'b0, 1'b1);
read_word(12'h012);
if (!s_read_valid || !s_uncorrectable) begin
$fatal(1, "USB2 packet RAM double-bit detection failed");
end
$display("USB2 packet RAM ECC test passed");
$finish;
end
endmodule
+213
View File
@@ -0,0 +1,213 @@
`timescale 1ns / 1ps
module usb2_packet_store_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic fill_start_valid_i = 1'b0;
logic fill_start_ready_o;
logic [11:0] fill_base_i = '0;
logic [14:0] fill_bytes_i = '0;
logic fill_valid_i = 1'b0;
logic fill_ready_o;
logic [31:0] fill_data_i = '0;
logic [ 3:0] fill_strb_i = '0;
logic fill_last_i = 1'b0;
logic fill_done_o;
logic fill_error_o;
logic drain_start_valid_i = 1'b0;
logic drain_start_ready_o;
logic [11:0] drain_base_i = '0;
logic [14:0] drain_bytes_i = '0;
logic drain_valid_o;
logic drain_ready_i = 1'b1;
logic [31:0] drain_data_o;
logic [ 3:0] drain_strb_o;
logic drain_last_o;
logic drain_done_o;
logic rx_start_valid_i = 1'b0;
logic rx_start_ready_o;
logic [11:0] rx_base_i = '0;
logic [14:0] rx_limit_i = '0;
logic rx_valid_i = 1'b0;
logic rx_ready_o;
logic [ 7:0] rx_data_i = '0;
logic rx_commit_i = 1'b0;
logic rx_cancel_i = 1'b0;
logic rx_done_o;
logic [14:0] rx_bytes_o;
logic rx_overflow_o;
logic tx_start_valid_i = 1'b0;
logic tx_start_ready_o;
logic [11:0] tx_base_i = '0;
logic [14:0] tx_bytes_i = '0;
logic tx_valid_o;
logic tx_ready_i = 1'b1;
logic [ 7:0] tx_data_o;
logic tx_done_o;
logic busy_o;
logic ecc_corrected_o;
logic ecc_uncorrectable_o;
integer tx_count;
integer drain_count;
logic [ 7:0] tx_bytes [0:4];
logic [31:0] drain_words [0:1];
logic [ 3:0] drain_strb [0:1];
always #5 clk_i = ~clk_i;
always_ff @(posedge clk_i) begin
if (!rst_n_i) begin
tx_count <= 0;
drain_count <= 0;
end else begin
if (tx_valid_o && tx_ready_i) begin
tx_bytes[tx_count] <= tx_data_o;
tx_count <= tx_count + 1;
end
if (drain_valid_o && drain_ready_i) begin
drain_words[drain_count] <= drain_data_o;
drain_strb[drain_count] <= drain_strb_o;
drain_count <= drain_count + 1;
end
end
end
usb2_packet_store u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.fill_start_valid_i (fill_start_valid_i),
.fill_start_ready_o (fill_start_ready_o),
.fill_base_i (fill_base_i),
.fill_bytes_i (fill_bytes_i),
.fill_valid_i (fill_valid_i),
.fill_ready_o (fill_ready_o),
.fill_data_i (fill_data_i),
.fill_strb_i (fill_strb_i),
.fill_last_i (fill_last_i),
.fill_done_o (fill_done_o),
.fill_error_o (fill_error_o),
.drain_start_valid_i(drain_start_valid_i),
.drain_start_ready_o(drain_start_ready_o),
.drain_base_i (drain_base_i),
.drain_bytes_i (drain_bytes_i),
.drain_valid_o (drain_valid_o),
.drain_ready_i (drain_ready_i),
.drain_data_o (drain_data_o),
.drain_strb_o (drain_strb_o),
.drain_last_o (drain_last_o),
.drain_done_o (drain_done_o),
.rx_start_valid_i (rx_start_valid_i),
.rx_start_ready_o (rx_start_ready_o),
.rx_base_i (rx_base_i),
.rx_limit_i (rx_limit_i),
.rx_valid_i (rx_valid_i),
.rx_ready_o (rx_ready_o),
.rx_data_i (rx_data_i),
.rx_commit_i (rx_commit_i),
.rx_cancel_i (rx_cancel_i),
.rx_done_o (rx_done_o),
.rx_bytes_o (rx_bytes_o),
.rx_overflow_o (rx_overflow_o),
.tx_start_valid_i (tx_start_valid_i),
.tx_start_ready_o (tx_start_ready_o),
.tx_base_i (tx_base_i),
.tx_bytes_i (tx_bytes_i),
.tx_valid_o (tx_valid_o),
.tx_ready_i (tx_ready_i),
.tx_data_o (tx_data_o),
.tx_done_o (tx_done_o),
.busy_o (busy_o),
.ecc_corrected_o (ecc_corrected_o),
.ecc_uncorrectable_o(ecc_uncorrectable_o)
);
task automatic pulse_fill(input logic [31:0] data_i, input logic [3:0] strb_i,
input logic last_i);
begin
@(negedge clk_i);
fill_data_i = data_i;
fill_strb_i = strb_i;
fill_last_i = last_i;
fill_valid_i = 1'b1;
while (!fill_ready_o) @(negedge clk_i);
@(negedge clk_i);
fill_valid_i = 1'b0;
end
endtask
task automatic pulse_rx(input logic [7:0] data_i);
begin
@(negedge clk_i);
rx_data_i = data_i;
rx_valid_i = 1'b1;
while (!rx_ready_o) @(negedge clk_i);
@(negedge clk_i);
rx_valid_i = 1'b0;
end
endtask
initial begin
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
fill_base_i = 12'd8;
fill_bytes_i = 15'd5;
fill_start_valid_i = 1'b1;
@(negedge clk_i);
fill_start_valid_i = 1'b0;
pulse_fill(32'h4433_2211, 4'hF, 1'b0);
pulse_fill(32'h0000_0055, 4'h1, 1'b1);
wait (fill_done_o);
if (fill_error_o) $fatal(1, "packet store fill failed");
@(negedge clk_i);
tx_base_i = 12'd8;
tx_bytes_i = 15'd5;
tx_start_valid_i = 1'b1;
@(negedge clk_i);
tx_start_valid_i = 1'b0;
wait (tx_done_o);
@(posedge clk_i);
if ((tx_count != 5) || (tx_bytes[0] != 8'h11) || (tx_bytes[1] != 8'h22) ||
(tx_bytes[2] != 8'h33) || (tx_bytes[3] != 8'h44) || (tx_bytes[4] != 8'h55)) begin
$fatal(1, "packet store transmit mismatch count=%0d", tx_count);
end
@(negedge clk_i);
rx_base_i = 12'd16;
rx_limit_i = 15'd5;
rx_start_valid_i = 1'b1;
@(negedge clk_i);
rx_start_valid_i = 1'b0;
pulse_rx(8'hA1);
pulse_rx(8'hB2);
pulse_rx(8'hC3);
pulse_rx(8'hD4);
pulse_rx(8'hE5);
@(negedge clk_i);
rx_commit_i = 1'b1;
@(negedge clk_i);
rx_commit_i = 1'b0;
wait (rx_done_o);
if ((rx_bytes_o != 15'd5) || rx_overflow_o) $fatal(1, "packet store receive failed");
@(negedge clk_i);
drain_base_i = 12'd16;
drain_bytes_i = 15'd5;
drain_start_valid_i = 1'b1;
@(negedge clk_i);
drain_start_valid_i = 1'b0;
wait (drain_done_o);
@(posedge clk_i);
if ((drain_count != 2) || (drain_words[0] != 32'hD4C3_B2A1) ||
(drain_words[1][7:0] != 8'hE5) || (drain_strb[0] != 4'hF) ||
(drain_strb[1] != 4'h1)) begin
$fatal(1, "packet store drain mismatch count=%0d", drain_count);
end
if (ecc_corrected_o || ecc_uncorrectable_o) $fatal(1, "unexpected ECC event");
$display("USB2 packet store paths passed");
$finish;
end
endmodule
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module usb2_pkg_tb;
logic [15:0] s_crc16;
initial begin
if (usb2_pkg::usb2_pid_byte(usb2_pkg::Usb2PidOut) != 8'hE1) begin
$fatal(1, "USB2 OUT PID encoding mismatch");
end
if (!usb2_pkg::usb2_pid_valid(8'hD2) || usb2_pkg::usb2_pid_valid(8'h52)) begin
$fatal(1, "USB2 PID complement validation mismatch");
end
if (usb2_pkg::usb2_token_crc5(
11'd0
) != 5'h02 || usb2_pkg::usb2_token_crc5(
11'd1
) != 5'h1D) begin
$fatal(1, "USB2 token CRC5 vector mismatch");
end
s_crc16 = 16'hFFFF;
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "1");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "2");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "3");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "4");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "5");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "6");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "7");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "8");
s_crc16 = usb2_pkg::usb2_crc16_byte(s_crc16, "9");
if (usb2_pkg::usb2_crc16_finish(s_crc16) != 16'hB4C8) begin
$fatal(1, "USB2 CRC16 vector mismatch");
end
$display("USB2 PID and CRC vectors passed");
$finish;
end
endmodule
+235
View File
@@ -0,0 +1,235 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`include "usb2_define.svh"
module usb2_reg_tb;
localparam logic [31:0] UsbBase = 32'h1001_6000;
logic s_clk;
logic s_rst_n;
logic [15:0] s_irq_event;
logic s_irq;
logic s_enable;
logic [ 1:0] s_force_role;
logic s_auto_role;
logic [ 7:0][31:0] s_endpoint_status;
logic [ 7:0][31:0] s_endpoint_bytes_in;
logic [ 7:0][31:0] s_endpoint_bytes_out;
logic [15:0][31:0] s_channel_status;
logic [15:0][31:0] s_channel_bytes;
logic [ 7:0][31:0] s_endpoint_cfg;
logic [ 7:0][31:0] s_endpoint_ram_in;
logic [ 7:0][31:0] s_endpoint_ram_out;
logic [ 7:0][31:0] s_endpoint_desc_in;
logic [ 7:0][31:0] s_endpoint_desc_out;
logic [ 7:0][31:0] s_endpoint_command;
logic [31:0] s_endpoint_complete_clear_in;
logic [31:0] s_endpoint_complete_clear_out;
logic s_endpoint_clear_seen;
logic [15:0][31:0] s_channel_cfg;
logic [15:0][31:0] s_channel_target;
logic [15:0][31:0] s_channel_interval;
logic [15:0][31:0] s_channel_ram;
logic [15:0][31:0] s_channel_desc;
logic [15:0][31:0] s_channel_command;
apb4_if u_apb4 (
.pclk (s_clk),
.presetn(s_rst_n)
);
usb2_reg u_reg (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.global_status_i (32'd0),
.role_status_i (32'd0),
.phy_status_i (32'd0),
.frame_i (32'd0),
.device_status_i (32'd0),
.setup_packet_i (64'd0),
.endpoint_pending_in_i (32'd0),
.endpoint_pending_out_i (32'd0),
.endpoint_complete_in_i (32'd0),
.endpoint_complete_out_i (32'd0),
.endpoint_status_i (s_endpoint_status),
.endpoint_bytes_in_i (s_endpoint_bytes_in),
.endpoint_bytes_out_i (s_endpoint_bytes_out),
.host_status_i (32'd0),
.port_status_i (32'd0),
.schedule_status_i (32'd0),
.channel_status_i (s_channel_status),
.channel_bytes_i (s_channel_bytes),
.ram_status_i (32'd0),
.ecc_status_i (32'd0),
.ecc_corrected_count_i (32'd0),
.ecc_uncorrectable_count_i (32'd0),
.debug_status_i (32'd0),
.perf_tx_bytes_i (32'd0),
.perf_rx_bytes_i (32'd0),
.perf_packets_i (32'd0),
.perf_retries_i (32'd0),
.perf_axi_stall_i (32'd0),
.perf_ram_stall_i (32'd0),
.perf_irq_count_i (32'd0),
.irq_event_i (s_irq_event),
.error_capture_i (1'b0),
.error_status_i (32'd0),
.error_code_i (32'd0),
.error_info_i (32'd0),
.error_desc_addr_i (32'd0),
.error_buffer_addr_i (32'd0),
.viewport_resp_valid_i (1'b0),
.viewport_read_data_i (8'd0),
.viewport_error_i (1'b0),
.enable_o (s_enable),
.soft_reset_o (),
.abort_o (),
.force_role_o (s_force_role),
.auto_role_o (s_auto_role),
.phy_reset_n_o (),
.phy_suspend_o (),
.remote_wake_o (),
.timeout_o (),
.test_ctrl_o (),
.perf_clear_o (),
.device_addr_o (),
.device_ctrl_o (),
.endpoint_cfg_o (s_endpoint_cfg),
.endpoint_ram_in_o (s_endpoint_ram_in),
.endpoint_ram_out_o (s_endpoint_ram_out),
.endpoint_desc_in_o (s_endpoint_desc_in),
.endpoint_desc_out_o (s_endpoint_desc_out),
.endpoint_command_o (s_endpoint_command),
.endpoint_complete_clear_in_o (s_endpoint_complete_clear_in),
.endpoint_complete_clear_out_o(s_endpoint_complete_clear_out),
.host_ctrl_o (),
.port_ctrl_o (),
.schedule_ctrl_o (),
.channel_cfg_o (s_channel_cfg),
.channel_target_o (s_channel_target),
.channel_interval_o (s_channel_interval),
.channel_ram_o (s_channel_ram),
.channel_desc_o (s_channel_desc),
.channel_command_o (s_channel_command),
.ram_ctrl_o (),
.ram_bist_start_o (),
.viewport_valid_o (),
.viewport_write_o (),
.viewport_addr_o (),
.viewport_write_data_o (),
.irq_o (s_irq),
.apb4 (u_apb4)
);
always #5 s_clk = ~s_clk;
always_ff @(posedge s_clk or negedge s_rst_n) begin
if (!s_rst_n) begin
s_endpoint_clear_seen <= 1'b0;
end else if ((s_endpoint_complete_clear_in == 32'h0000_00A5) &&
(s_endpoint_complete_clear_out == 32'd0)) begin
s_endpoint_clear_seen <= 1'b1;
end
end
task automatic apb_write(input logic [31:0] addr_i, input logic [31:0] data_i,
input logic expected_error_i);
begin
@(negedge s_clk);
u_apb4.paddr = addr_i;
u_apb4.pwdata = data_i;
u_apb4.pstrb = 4'hF;
u_apb4.pwrite = 1'b1;
u_apb4.psel = 1'b1;
u_apb4.penable = 1'b0;
@(negedge s_clk);
u_apb4.penable = 1'b1;
@(posedge s_clk);
#1;
if (!u_apb4.pready || (u_apb4.pslverr != expected_error_i)) begin
$fatal(1, "USB2 APB write failure addr=%h ready=%b error=%b", addr_i, u_apb4.pready,
u_apb4.pslverr);
end
@(negedge s_clk);
u_apb4.psel = 1'b0;
u_apb4.penable = 1'b0;
end
endtask
task automatic apb_read(input logic [31:0] addr_i, output logic [31:0] data_o,
input logic expected_error_i);
begin
@(negedge s_clk);
u_apb4.paddr = addr_i;
u_apb4.pwdata = 32'd0;
u_apb4.pstrb = 4'd0;
u_apb4.pwrite = 1'b0;
u_apb4.psel = 1'b1;
u_apb4.penable = 1'b0;
@(negedge s_clk);
u_apb4.penable = 1'b1;
@(posedge s_clk);
#1;
if (!u_apb4.pready || (u_apb4.pslverr != expected_error_i)) begin
$fatal(1, "USB2 APB read failure addr=%h ready=%b error=%b", addr_i, u_apb4.pready,
u_apb4.pslverr);
end
data_o = u_apb4.prdata;
@(negedge s_clk);
u_apb4.psel = 1'b0;
u_apb4.penable = 1'b0;
end
endtask
initial begin
logic [31:0] value;
s_clk = 1'b0;
s_rst_n = 1'b0;
s_irq_event = 16'd0;
u_apb4.paddr = 32'd0;
u_apb4.pprot = 3'd0;
u_apb4.psel = 1'b0;
u_apb4.penable = 1'b0;
u_apb4.pwrite = 1'b0;
u_apb4.pwdata = 32'd0;
u_apb4.pstrb = 4'd0;
for (int endpoint = 0; endpoint < 8; endpoint++) begin
s_endpoint_status[endpoint] = 32'd0;
s_endpoint_bytes_in[endpoint] = 32'd0;
s_endpoint_bytes_out[endpoint] = 32'd0;
end
for (int channel = 0; channel < 16; channel++) begin
s_channel_status[channel] = 32'd0;
s_channel_bytes[channel] = 32'd0;
end
repeat (3) @(posedge s_clk);
s_rst_n = 1'b1;
apb_read(UsbBase + `APB4_USB2__IP_ID, value, 1'b0);
if (value != 32'h5553_4232) $fatal(1, "USB2 IP ID mismatch");
apb_read(UsbBase + `APB4_USB2__CAPABILITY0, value, 1'b0);
if ((value[19:16] != 4'd7) || (value[27:24] != 4'd15)) begin
$fatal(1, "USB2 capability scale mismatch: %h", value);
end
apb_write(UsbBase + `APB4_USB2__ROLE_CTRL, 32'h0000_0006, 1'b0);
if ((s_force_role != 2'd2) || !s_auto_role) $fatal(1, "USB2 role control mismatch");
apb_write(
UsbBase + `APB4_USB2__ENDPOINT_BASE + `APB4_USB2__ENDPOINT_STRIDE +
`APB4_USB2__ENDPOINT_CFG,
32'h0000_020D, 1'b0);
apb_read(
UsbBase + `APB4_USB2__ENDPOINT_BASE + `APB4_USB2__ENDPOINT_STRIDE +
`APB4_USB2__ENDPOINT_CFG,
value, 1'b0);
if (value != 32'h0000_020D) $fatal(1, "USB2 endpoint configuration mismatch");
apb_write(UsbBase + `APB4_USB2__ENDPOINT_COMPLETE_IN, 32'h0000_00A5, 1'b0);
if (!s_endpoint_clear_seen) $fatal(1, "USB2 endpoint completion W1C mismatch");
apb_write(UsbBase + `APB4_USB2__IRQ_ENABLE, 32'h0000_0020, 1'b0);
apb_write(UsbBase + `APB4_USB2__IRQ_TEST, 32'h0000_0020, 1'b0);
apb_write(UsbBase + `APB4_USB2__GLOBAL_CTRL, 32'h0000_0009, 1'b0);
if (!s_enable || !s_irq) $fatal(1, "USB2 interrupt test failed");
apb_write(UsbBase + `APB4_USB2__ROLE_CTRL, 32'h0000_0001, 1'b1);
apb_read(UsbBase + 32'h0000_0001, value, 1'b1);
$display("USB2 APB register contract passed");
$finish;
end
endmodule
+180
View File
@@ -0,0 +1,180 @@
`timescale 1ns / 1ps
module usb2_scheduler_tb;
logic s_clk = 1'b0;
logic s_rst_n = 1'b0;
logic s_reinitialize = 1'b0;
logic [13:0] s_frame = 14'd0;
logic [ 7:0][31:0] s_endpoint_cfg = '0;
logic [ 7:0][31:0] s_endpoint_ram_in = '0;
logic [ 7:0][31:0] s_endpoint_ram_out = '0;
logic [ 7:0][31:0] s_endpoint_desc_in = '0;
logic [ 7:0][31:0] s_endpoint_desc_out = '0;
logic [ 7:0][31:0] s_endpoint_command = '0;
logic [15:0][31:0] s_channel_cfg = '0;
logic [15:0][31:0] s_channel_target = '0;
logic [15:0][31:0] s_channel_interval = '0;
logic [15:0][31:0] s_channel_ram = '0;
logic [15:0][31:0] s_channel_desc = '0;
logic [15:0][31:0] s_channel_command = '0;
logic s_work_valid;
logic [63:0] s_work_data;
logic s_result_valid = 1'b0;
logic [63:0] s_result_data = '0;
logic s_fill_ready = 1'b0;
logic s_dma_start;
logic s_dma_done = 1'b0;
logic s_buffer_event_valid = 1'b0;
logic [ 2:0] s_buffer_event_data = 3'd0;
logic s_busy;
logic [ 3:0] s_seen_index [0:3];
integer s_work_count = 0;
always #5 s_clk = ~s_clk;
always_ff @(posedge s_clk) begin
s_dma_done <= 1'b0;
s_buffer_event_valid <= 1'b0;
s_result_valid <= 1'b0;
if (s_dma_start) begin
s_dma_done <= 1'b1;
s_buffer_event_valid <= 1'b1;
end
if (s_work_valid) begin
s_seen_index[s_work_count] <= s_work_data[usb2_pkg::USB2_WORK_INDEX_LSB+:4];
s_work_count <= s_work_count + 1;
s_result_data <= 64'd0;
s_result_data[usb2_pkg::USB2_RESULT_ROLE_LSB+:2] <= usb2_pkg::Usb2RoleHost;
s_result_data[usb2_pkg::USB2_RESULT_INDEX_LSB+:4] <=
s_work_data[usb2_pkg::USB2_WORK_INDEX_LSB+:4];
s_result_data[usb2_pkg::USB2_RESULT_LENGTH_LSB+:15] <= 15'd4;
s_result_valid <= 1'b1;
end
end
usb2_scheduler u_scheduler (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.enable_i (1'b1),
.abort_i (1'b0),
.reinitialize_i (s_reinitialize),
.perf_clear_i (1'b0),
.high_speed_i (1'b1),
.frame_i (s_frame),
.active_role_i (usb2_pkg::Usb2RoleHost),
.endpoint_cfg_i (s_endpoint_cfg),
.endpoint_ram_in_i (s_endpoint_ram_in),
.endpoint_ram_out_i (s_endpoint_ram_out),
.endpoint_desc_in_i (s_endpoint_desc_in),
.endpoint_desc_out_i (s_endpoint_desc_out),
.endpoint_command_i (s_endpoint_command),
.endpoint_complete_clear_in_i (32'd0),
.endpoint_complete_clear_out_i(32'd0),
.channel_cfg_i (s_channel_cfg),
.channel_target_i (s_channel_target),
.channel_interval_i (s_channel_interval),
.channel_ram_i (s_channel_ram),
.channel_desc_i (s_channel_desc),
.channel_command_i (s_channel_command),
.work_valid_o (s_work_valid),
.work_ready_i (1'b1),
.work_data_o (s_work_data),
.result_valid_i (s_result_valid),
.result_ready_o (),
.result_data_i (s_result_data),
.fill_cmd_valid_o (),
.fill_cmd_ready_i (s_fill_ready),
.fill_cmd_data_o (),
.drain_cmd_valid_o (),
.drain_cmd_ready_i (1'b1),
.drain_cmd_data_o (),
.buffer_event_valid_i (s_buffer_event_valid),
.buffer_event_ready_o (),
.buffer_event_data_i (s_buffer_event_data),
.dma_start_o (s_dma_start),
.dma_abort_o (),
.dma_memory_to_packet_o (),
.dma_desc_base_o (),
.dma_transfer_bytes_o (),
.dma_busy_i (1'b0),
.dma_done_i (s_dma_done),
.dma_error_i (1'b0),
.dma_error_code_i (8'd0),
.dma_current_desc_i (32'd0),
.dma_bytes_done_i (32'd4),
.retry_i (1'b0),
.endpoint_pending_in_o (),
.endpoint_pending_out_o (),
.endpoint_complete_in_o (),
.endpoint_complete_out_o (),
.endpoint_status_o (),
.endpoint_bytes_in_o (),
.endpoint_bytes_out_o (),
.channel_status_o (),
.channel_bytes_o (),
.irq_event_o (),
.error_capture_o (),
.error_status_o (),
.error_code_o (),
.error_info_o (),
.error_desc_addr_o (),
.error_buffer_addr_o (),
.perf_tx_bytes_o (),
.perf_rx_bytes_o (),
.perf_packets_o (),
.perf_retries_o (),
.perf_irq_count_o (),
.busy_o (s_busy)
);
task automatic issue_channel(input int channel_i);
begin
@(negedge s_clk);
s_channel_command[channel_i] = 32'd1;
@(negedge s_clk);
s_channel_command[channel_i] = 32'd0;
end
endtask
initial begin
for (int channel = 0; channel < 16; channel++) begin
s_channel_cfg[channel] = 32'h0040_0009;
s_channel_ram[channel] = 32'h0004_0000;
s_channel_desc[channel] = 32'h4000_0000 + (32'(channel) << 5);
end
s_channel_cfg[3][3:2] = usb2_pkg::Usb2TransferInterrupt;
s_channel_interval[3] = 32'd8;
repeat (4) @(posedge s_clk);
s_rst_n = 1'b1;
issue_channel(1);
wait (s_busy);
issue_channel(2);
repeat (2) @(posedge s_clk);
s_fill_ready = 1'b1;
wait (s_work_count == 2);
wait (!s_busy);
if ((s_seen_index[0] != 4'd1) || (s_seen_index[1] != 4'd2)) begin
$fatal(1, "USB2 busy command queue dropped or reordered work");
end
issue_channel(3);
wait (s_work_count == 3);
wait (!s_busy);
s_frame = 14'd1;
issue_channel(3);
repeat (8) @(posedge s_clk);
if (s_work_count != 3) $fatal(1, "USB2 periodic command ran before its interval");
s_frame = 14'd8;
wait (s_work_count == 4);
if (s_seen_index[3] != 4'd3) $fatal(1, "USB2 periodic command was not retained");
@(negedge s_clk);
s_reinitialize = 1'b1;
@(negedge s_clk);
s_reinitialize = 1'b0;
$display("USB2 scheduler queue and interval passed");
$finish;
end
endmodule
+208
View File
@@ -0,0 +1,208 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module usb2_sie_tb;
logic s_clk;
logic s_rst_n;
logic s_rx_start;
logic s_rx_valid;
logic [ 7:0] s_rx_data;
logic s_rx_end;
logic s_rx_error;
logic s_payload_valid;
logic [ 7:0] s_payload_data;
logic s_packet_done;
logic s_packet_good;
logic [ 3:0] s_packet_pid;
logic [ 6:0] s_token_addr;
logic [ 3:0] s_token_endpoint;
logic [10:0] s_payload_length;
logic s_crc_error;
logic s_request_valid;
logic s_request_ready;
logic [ 3:0] s_request_pid;
logic [10:0] s_request_token;
logic [10:0] s_request_length;
logic s_link_start_valid;
logic s_link_start_ready;
logic [ 3:0] s_link_pid;
logic s_link_has_data;
logic s_link_data_valid;
logic s_link_data_ready;
logic [ 7:0] s_link_data;
logic s_link_data_last;
logic s_link_done;
logic s_tx_done;
logic [ 7:0] s_payload_seen [0:1];
logic [ 1:0] s_payload_seen_count;
logic [ 7:0] s_tx_seen [0:1];
logic [ 1:0] s_tx_seen_count;
usb2_sie_rx u_rx (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.rx_start_i (s_rx_start),
.rx_valid_i (s_rx_valid),
.rx_data_i (s_rx_data),
.rx_end_i (s_rx_end),
.rx_error_i (s_rx_error),
.payload_valid_o (s_payload_valid),
.payload_data_o (s_payload_data),
.packet_done_o (s_packet_done),
.packet_good_o (s_packet_good),
.packet_pid_o (s_packet_pid),
.token_addr_o (s_token_addr),
.token_endpoint_o(s_token_endpoint),
.frame_number_o (),
.payload_length_o(s_payload_length),
.pid_error_o (),
.crc_error_o (s_crc_error),
.framing_error_o ()
);
usb2_sie_tx u_tx (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.request_valid_i (s_request_valid),
.request_ready_o (s_request_ready),
.request_pid_i (s_request_pid),
.request_token_i (s_request_token),
.request_length_i (s_request_length),
.payload_valid_i (1'b0),
.payload_ready_o (),
.payload_data_i (8'd0),
.link_start_valid_o(s_link_start_valid),
.link_start_ready_i(s_link_start_ready),
.link_pid_o (s_link_pid),
.link_has_data_o (s_link_has_data),
.link_data_valid_o (s_link_data_valid),
.link_data_ready_i (s_link_data_ready),
.link_data_o (s_link_data),
.link_data_last_o (s_link_data_last),
.link_done_i (s_link_done),
.link_error_i (1'b0),
.busy_o (),
.done_o (s_tx_done),
.error_o ()
);
always #5 s_clk = ~s_clk;
always_ff @(posedge s_clk) begin
if (s_payload_valid) begin
s_payload_seen[s_payload_seen_count] <= s_payload_data;
s_payload_seen_count <= s_payload_seen_count + 1'b1;
end
if (s_link_data_valid && s_link_data_ready) begin
s_tx_seen[s_tx_seen_count] <= s_link_data;
s_tx_seen_count <= s_tx_seen_count + 1'b1;
end
end
task automatic rx_begin;
begin
@(negedge s_clk);
s_rx_start = 1'b1;
@(negedge s_clk);
s_rx_start = 1'b0;
end
endtask
task automatic rx_byte(input logic [7:0] data_i);
begin
s_rx_data = data_i;
s_rx_valid = 1'b1;
@(negedge s_clk);
s_rx_valid = 1'b0;
@(negedge s_clk);
end
endtask
task automatic rx_finish;
begin
s_rx_end = 1'b1;
@(posedge s_clk);
#1;
s_rx_end = 1'b0;
end
endtask
initial begin
logic [15:0] data_crc;
s_clk = 1'b0;
s_rst_n = 1'b0;
s_rx_start = 1'b0;
s_rx_valid = 1'b0;
s_rx_data = 8'd0;
s_rx_end = 1'b0;
s_rx_error = 1'b0;
s_request_valid = 1'b0;
s_request_pid = usb2_pkg::Usb2PidOut;
s_request_token = 11'd0;
s_request_length = 11'd0;
s_link_start_ready = 1'b1;
s_link_data_ready = 1'b1;
s_link_done = 1'b0;
s_payload_seen_count = '0;
s_tx_seen_count = '0;
repeat (2) @(negedge s_clk);
s_rst_n = 1'b1;
rx_begin();
rx_byte(usb2_pkg::usb2_pid_byte(usb2_pkg::Usb2PidOut));
rx_byte(8'h01);
rx_byte(8'hE8);
rx_finish();
if (!s_packet_done || !s_packet_good || (s_packet_pid != usb2_pkg::Usb2PidOut) ||
(s_token_addr != 7'd1) || (s_token_endpoint != 4'd0)) begin
$fatal(1, "USB2 token packet decode failed");
end
data_crc = usb2_pkg::usb2_crc16_byte(16'hFFFF, 8'h41);
data_crc = usb2_pkg::usb2_crc16_byte(data_crc, 8'h42);
data_crc = usb2_pkg::usb2_crc16_finish(data_crc);
rx_begin();
rx_byte(usb2_pkg::usb2_pid_byte(usb2_pkg::Usb2PidData0));
rx_byte(8'h41);
rx_byte(8'h42);
rx_byte(data_crc[7:0]);
rx_byte(data_crc[15:8]);
rx_finish();
if (!s_packet_done || !s_packet_good || (s_payload_length != 11'd2) ||
(s_payload_seen_count != 2'd2) || (s_payload_seen[0] != 8'h41) ||
(s_payload_seen[1] != 8'h42)) begin
$fatal(1, "USB2 data packet decode failed");
end
rx_begin();
rx_byte(usb2_pkg::usb2_pid_byte(usb2_pkg::Usb2PidData0));
rx_byte(8'h01);
rx_byte(8'h00);
rx_finish();
if (!s_packet_done || s_packet_good || !s_crc_error) begin
$fatal(1, "USB2 bad data CRC accepted");
end
@(negedge s_clk);
s_request_pid = usb2_pkg::Usb2PidOut;
s_request_token = 11'd1;
s_request_valid = 1'b1;
@(negedge s_clk);
s_request_valid = 1'b0;
wait (s_link_data_last);
@(negedge s_clk);
s_link_done = 1'b1;
@(posedge s_clk);
@(posedge s_clk);
#1;
s_link_done = 1'b0;
if (!s_tx_done || (s_link_pid != usb2_pkg::Usb2PidOut) || !s_link_has_data ||
(s_tx_seen_count != 2'd2) || (s_tx_seen[0] != 8'h01) || (s_tx_seen[1] != 8'hE8)) begin
$fatal(1, "USB2 token packet encode failed");
end
$display("USB2 SIE packet encode/decode passed");
$finish;
end
endmodule
+277
View File
@@ -0,0 +1,277 @@
`timescale 1ns / 1ps
module usb2_transaction_engine_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
usb2_pkg::usb2_role_e active_role_i = usb2_pkg::Usb2RoleHost;
logic [ 7:0][31:0] endpoint_cfg_i = '0;
logic work_valid_i = 1'b0;
logic work_ready_o;
logic [63:0] work_data_i = '0;
logic result_valid_o;
logic [63:0] result_data_o;
logic setup_valid_o;
logic [63:0] setup_data_o;
logic tx_request_valid_o;
logic tx_request_ready_i = 1'b1;
logic [ 3:0] tx_request_pid_o;
logic [10:0] tx_request_token_o;
logic [10:0] tx_request_length_o;
logic tx_payload_valid_o;
logic tx_payload_ready_i = 1'b1;
logic [ 7:0] tx_payload_data_o;
logic tx_done_i = 1'b0;
logic tx_error_i = 1'b0;
logic rx_payload_valid_i = 1'b0;
logic [ 7:0] rx_payload_data_i = '0;
logic rx_packet_done_i = 1'b0;
logic rx_packet_good_i = 1'b0;
logic [ 3:0] rx_packet_pid_i = '0;
logic store_rx_start_valid;
logic store_rx_start_ready;
logic [11:0] store_rx_base;
logic [14:0] store_rx_limit;
logic store_rx_valid;
logic store_rx_ready;
logic [ 7:0] store_rx_data;
logic store_rx_commit;
logic store_rx_cancel;
logic store_rx_done;
logic [14:0] store_rx_bytes;
logic store_rx_overflow;
logic store_tx_start_valid;
logic store_tx_start_ready;
logic [11:0] store_tx_base;
logic [14:0] store_tx_bytes;
logic store_tx_valid;
logic store_tx_ready;
logic [ 7:0] store_tx_data;
logic store_tx_done;
logic fill_start_valid_i = 1'b0;
logic fill_start_ready_o;
logic fill_valid_i = 1'b0;
logic fill_ready_o;
logic [31:0] fill_data_i = '0;
logic [ 3:0] fill_strb_i = '0;
logic fill_last_i = 1'b0;
logic fill_done_o;
logic fill_error_o;
logic busy_o;
logic retry_o;
logic protocol_error_o;
logic pending_tx_q = 1'b0;
logic pending_data_q = 1'b0;
logic data_consumed_q = 1'b0;
integer pid_count;
logic [ 3:0] observed_pid [0:3];
always #5 clk_i = ~clk_i;
always_ff @(posedge clk_i) begin
tx_done_i <= 1'b0;
rx_packet_done_i <= 1'b0;
rx_packet_good_i <= 1'b0;
if (!rst_n_i) begin
pending_tx_q <= 1'b0;
pending_data_q <= 1'b0;
data_consumed_q <= 1'b0;
pid_count <= 0;
end else begin
if (tx_request_valid_o && tx_request_ready_i) begin
observed_pid[pid_count] <= tx_request_pid_o;
pid_count <= pid_count + 1;
if (usb2_pkg::usb2_pid_is_data(tx_request_pid_o)) begin
pending_data_q <= 1'b1;
end else begin
pending_tx_q <= 1'b1;
end
end
if (pending_tx_q) begin
tx_done_i <= 1'b1;
pending_tx_q <= 1'b0;
end
if (pending_data_q && tx_payload_valid_o && tx_payload_ready_i &&
(tx_payload_data_o == 8'h55)) begin
data_consumed_q <= 1'b1;
end
if (pending_data_q && data_consumed_q) begin
tx_done_i <= 1'b1;
pending_data_q <= 1'b0;
data_consumed_q <= 1'b0;
end
if ((pid_count == 2) && !pending_data_q && !pending_tx_q && tx_done_i) begin
rx_packet_pid_i <= usb2_pkg::Usb2PidAck;
rx_packet_done_i <= 1'b1;
rx_packet_good_i <= 1'b1;
end
end
end
usb2_transaction_engine u_engine (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.active_role_i (active_role_i),
.device_addr_i (7'd0),
.endpoint_cfg_i (endpoint_cfg_i),
.timeout_i (32'd1000),
.work_valid_i (work_valid_i),
.work_ready_o (work_ready_o),
.work_data_i (work_data_i),
.result_valid_o (result_valid_o),
.result_ready_i (1'b1),
.result_data_o (result_data_o),
.setup_valid_o (setup_valid_o),
.setup_ready_i (1'b1),
.setup_data_o (setup_data_o),
.tx_request_valid_o (tx_request_valid_o),
.tx_request_ready_i (tx_request_ready_i),
.tx_request_pid_o (tx_request_pid_o),
.tx_request_token_o (tx_request_token_o),
.tx_request_length_o (tx_request_length_o),
.tx_payload_valid_o (tx_payload_valid_o),
.tx_payload_ready_i (tx_payload_ready_i),
.tx_payload_data_o (tx_payload_data_o),
.tx_done_i (tx_done_i),
.tx_error_i (tx_error_i),
.rx_payload_valid_i (rx_payload_valid_i),
.rx_payload_data_i (rx_payload_data_i),
.rx_packet_done_i (rx_packet_done_i),
.rx_packet_good_i (rx_packet_good_i),
.rx_packet_pid_i (rx_packet_pid_i),
.rx_token_addr_i (7'd0),
.rx_token_endpoint_i (4'd0),
.rx_payload_length_i (11'd0),
.store_rx_start_valid_o(store_rx_start_valid),
.store_rx_start_ready_i(store_rx_start_ready),
.store_rx_base_o (store_rx_base),
.store_rx_limit_o (store_rx_limit),
.store_rx_valid_o (store_rx_valid),
.store_rx_ready_i (store_rx_ready),
.store_rx_data_o (store_rx_data),
.store_rx_commit_o (store_rx_commit),
.store_rx_cancel_o (store_rx_cancel),
.store_rx_done_i (store_rx_done),
.store_rx_bytes_i (store_rx_bytes),
.store_rx_overflow_i (store_rx_overflow),
.store_tx_start_valid_o(store_tx_start_valid),
.store_tx_start_ready_i(store_tx_start_ready),
.store_tx_base_o (store_tx_base),
.store_tx_bytes_o (store_tx_bytes),
.store_tx_valid_i (store_tx_valid),
.store_tx_ready_o (store_tx_ready),
.store_tx_data_i (store_tx_data),
.store_tx_done_i (store_tx_done),
.busy_o (busy_o),
.retry_o (retry_o),
.protocol_error_o (protocol_error_o)
);
usb2_packet_store u_store (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.fill_start_valid_i (fill_start_valid_i),
.fill_start_ready_o (fill_start_ready_o),
.fill_base_i (12'd4),
.fill_bytes_i (15'd5),
.fill_valid_i (fill_valid_i),
.fill_ready_o (fill_ready_o),
.fill_data_i (fill_data_i),
.fill_strb_i (fill_strb_i),
.fill_last_i (fill_last_i),
.fill_done_o (fill_done_o),
.fill_error_o (fill_error_o),
.drain_start_valid_i(1'b0),
.drain_start_ready_o(),
.drain_base_i ('0),
.drain_bytes_i ('0),
.drain_valid_o (),
.drain_ready_i (1'b0),
.drain_data_o (),
.drain_strb_o (),
.drain_last_o (),
.drain_done_o (),
.rx_start_valid_i (store_rx_start_valid),
.rx_start_ready_o (store_rx_start_ready),
.rx_base_i (store_rx_base),
.rx_limit_i (store_rx_limit),
.rx_valid_i (store_rx_valid),
.rx_ready_o (store_rx_ready),
.rx_data_i (store_rx_data),
.rx_commit_i (store_rx_commit),
.rx_cancel_i (store_rx_cancel),
.rx_done_o (store_rx_done),
.rx_bytes_o (store_rx_bytes),
.rx_overflow_o (store_rx_overflow),
.tx_start_valid_i (store_tx_start_valid),
.tx_start_ready_o (store_tx_start_ready),
.tx_base_i (store_tx_base),
.tx_bytes_i (store_tx_bytes),
.tx_valid_o (store_tx_valid),
.tx_ready_i (store_tx_ready),
.tx_data_o (store_tx_data),
.tx_done_o (store_tx_done),
.busy_o (),
.ecc_corrected_o (),
.ecc_uncorrectable_o()
);
initial begin
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
fill_start_valid_i = 1'b1;
@(negedge clk_i);
fill_start_valid_i = 1'b0;
@(negedge clk_i);
fill_data_i = 32'h4433_2211;
fill_strb_i = 4'hF;
fill_last_i = 1'b0;
fill_valid_i = 1'b1;
@(negedge clk_i);
fill_data_i = 32'h0000_0055;
fill_strb_i = 4'h1;
fill_last_i = 1'b1;
@(negedge clk_i);
fill_valid_i = 1'b0;
wait (fill_done_o);
if (fill_error_o) $fatal(1, "protocol test prefill failed");
work_data_i = '0;
work_data_i[usb2_pkg::USB2_WORK_ROLE_LSB+:2] = usb2_pkg::Usb2RoleHost;
work_data_i[usb2_pkg::USB2_WORK_INDEX_LSB+:4] = 4'd3;
work_data_i[usb2_pkg::USB2_WORK_DIRECTION_IN] = 1'b0;
work_data_i[usb2_pkg::USB2_WORK_TYPE_LSB+:2] = usb2_pkg::Usb2TransferBulk;
work_data_i[usb2_pkg::USB2_WORK_ADDRESS_LSB+:7] = 7'd5;
work_data_i[usb2_pkg::USB2_WORK_ENDPOINT_LSB+:4] = 4'd2;
work_data_i[usb2_pkg::USB2_WORK_RAM_BASE_LSB+:12] = 12'd4;
work_data_i[usb2_pkg::USB2_WORK_LENGTH_LSB+:15] = 15'd5;
@(negedge clk_i);
work_valid_i = 1'b1;
@(negedge clk_i);
work_valid_i = 1'b0;
wait (result_valid_o);
if ((result_data_o[usb2_pkg::USB2_RESULT_CODE_LSB+:4] != usb2_pkg::Usb2ResultSuccess) ||
(result_data_o[usb2_pkg::USB2_RESULT_INDEX_LSB+:4] != 4'd3) || (pid_count != 2) ||
(observed_pid[0] != usb2_pkg::Usb2PidOut) ||
(observed_pid[1] != usb2_pkg::Usb2PidData0) || retry_o || protocol_error_o) begin
$fatal(1, "host OUT transaction failed result=%h pids=%h,%h", result_data_o, observed_pid[0],
observed_pid[1]);
end
wait (work_ready_o);
work_data_i[usb2_pkg::USB2_WORK_INDEX_LSB+:4] = 4'd4;
work_data_i[usb2_pkg::USB2_WORK_TYPE_LSB+:2] = usb2_pkg::Usb2TransferIsochronous;
@(negedge clk_i);
work_valid_i = 1'b1;
@(negedge clk_i);
work_valid_i = 1'b0;
wait (result_valid_o);
if ((result_data_o[usb2_pkg::USB2_RESULT_CODE_LSB+:4] != usb2_pkg::Usb2ResultSuccess) ||
(result_data_o[usb2_pkg::USB2_RESULT_INDEX_LSB+:4] != 4'd4) || (pid_count != 4) ||
(observed_pid[2] != usb2_pkg::Usb2PidOut) ||
(observed_pid[3] != usb2_pkg::Usb2PidData0) || retry_o || protocol_error_o) begin
$fatal(1, "host ISO OUT transaction waited for a handshake result=%h", result_data_o);
end
$display("USB2 transaction engine host OUT passed");
$finish;
end
endmodule