[usbdev] Add reference signals for synchronization of USB clock
This commit adds two new outputs to usbdev:
- `usb_ref_pulse_o`: indicates the reception of a start of frame (SOF)
packet, sent by the host every 1 ms.
- `usb_ref_val_o`: indicates if the `usb_ref_pulse_o` signal is valid.
This information can be used as a reference to synchronize the USB clock
with the host.
Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/hw/ip/usbdev/data/usbdev.hjson b/hw/ip/usbdev/data/usbdev.hjson
index 9044314..ce2cdf8 100644
--- a/hw/ip/usbdev/data/usbdev.hjson
+++ b/hw/ip/usbdev/data/usbdev.hjson
@@ -23,6 +23,22 @@
{ name: "tx_mode_se", desc: "USB single-ended transmit mode control" }
{ name: "suspend", desc: "USB link suspend state" }
],
+ inter_signal_list: [
+ { name: "usb_ref_val",
+ type: "uni",
+ act: "req",
+ package: "",
+ struct: "logic",
+ width: "1"
+ }
+ { name: "usb_ref_pulse",
+ type: "uni",
+ act: "req",
+ package: "",
+ struct: "logic",
+ width: "1"
+ }
+ ]
param_list: [
{ name: "NEndpoints",
type: "int",
diff --git a/hw/ip/usbdev/doc/_index.md b/hw/ip/usbdev/doc/_index.md
index b917eb2..e365d95 100644
--- a/hw/ip/usbdev/doc/_index.md
+++ b/hw/ip/usbdev/doc/_index.md
@@ -55,6 +55,14 @@
The USB specification for a Full-Speed device requires the average bit rate is 12 Mbps +/- 0.25%, so the clock needs to support maximum error of 2,500 ppm.
The maximum allowable integrated jitter is +/- 1 ns over 1 to 7 bit periods.
+This module features the following output signals to provide a reference for synchronizing the 48 MHz clock source:
+- `usb_ref_pulse_o` indicates the reception of a start of frame (SOF) packet.
+ The host is required to send a SOF packet every 1 ms.
+- `usb_ref_val_o` serves as a valid signal for `usb_ref_pulse_o`.
+ It is set to one after the first SOF packet is received and remains high as long as `usb_ref_pulse_o` continues to behave as expected.
+ As soon as it is detected that SOF will not be received as expected (usually because the link is no longer active), `usb_ref_val_o` deasserts to zero until after the next `usb_ref_pulse_o`.
+Both these signals are synchronous to the 48 MHz clock.
+
Control transfers pass through asynchronous FIFOs or have a ready bit
synchronized across the clock domain boundary. A dual-port
asynchronous buffer SRAM is used for data transfers between the bus
diff --git a/hw/ip/usbdev/rtl/usbdev.sv b/hw/ip/usbdev/rtl/usbdev.sv
index 48810a8..dfbb9de 100644
--- a/hw/ip/usbdev/rtl/usbdev.sv
+++ b/hw/ip/usbdev/rtl/usbdev.sv
@@ -43,6 +43,10 @@
output logic cio_tx_mode_se_o,
output logic cio_tx_mode_se_en_o,
+ // SOF reference for clock calibration
+ output logic usb_ref_val_o,
+ output logic usb_ref_pulse_o,
+
// Interrupts
output logic intr_pkt_received_o, // Packet received
output logic intr_pkt_sent_o, // Packet sent
@@ -116,6 +120,7 @@
logic usb_event_rx_bitstuff_err;
logic usb_event_in_err;
logic usb_event_frame;
+ logic usb_link_active;
logic event_link_reset, event_link_suspend, event_link_resume;
logic event_host_lost, event_disconnect, event_connect;
@@ -499,6 +504,7 @@
.link_disconnect_o (usb_event_disconnect),
.link_connect_o (usb_event_connect),
.link_reset_o (usb_event_link_reset),
+ .link_active_o (usb_link_active),
.link_suspend_o (usb_event_link_suspend),
.link_resume_o (usb_event_link_resume),
.host_lost_o (usb_event_host_lost),
@@ -916,4 +922,30 @@
assign cio_dp_pullup_o = 1'b1;
assign cio_dn_pullup_o = 1'b1;
+ /////////////////////////////////////////
+ // SOF Reference for Clock Calibration //
+ /////////////////////////////////////////
+
+ logic usb_ref_val_d, usb_ref_val_q;
+
+ // Directly forward the pulse.
+ assign usb_ref_pulse_o = usb_event_frame;
+
+ // The first pulse is always ignored, but causes the valid to be asserted.
+ // The valid signal is deasserted when:
+ // - The link is no longer active.
+ // - The host is lost (no SOF for 4ms).
+ assign usb_ref_val_d = usb_ref_pulse_o ? 1'b1 :
+ (!usb_link_active || usb_event_host_lost) ? 1'b0 : usb_ref_val_q;
+
+ always_ff @(posedge clk_usb_48mhz_i or negedge rst_usb_48mhz_ni) begin
+ if (!rst_usb_48mhz_ni) begin
+ usb_ref_val_q <= 1'b0;
+ end else begin
+ usb_ref_val_q <= usb_ref_val_d;
+ end
+ end
+
+ assign usb_ref_val_o = usb_ref_val_q;
+
endmodule
diff --git a/hw/ip/usbdev/rtl/usbdev_linkstate.sv b/hw/ip/usbdev/rtl/usbdev_linkstate.sv
index 7a206e0..d4dea2d 100644
--- a/hw/ip/usbdev/rtl/usbdev_linkstate.sv
+++ b/hw/ip/usbdev/rtl/usbdev_linkstate.sv
@@ -16,6 +16,7 @@
output logic link_disconnect_o, // level
output logic link_connect_o, // level
output logic link_reset_o, // level
+ output logic link_active_o, // level
output logic link_suspend_o, // level
output logic link_resume_o, // pulse
output logic host_lost_o, // level
@@ -50,7 +51,6 @@
} link_inac_state_e;
link_state_e link_state_d, link_state_q;
- logic link_active;
logic line_se0_raw, line_idle_raw;
logic see_se0, see_idle, see_pwr_sense;
@@ -75,7 +75,7 @@
assign link_connect_o = (link_state_q != LinkDisconnect);
assign link_suspend_o = (link_state_q == LinkSuspend ||
link_state_q == LinkPoweredSuspend);
- assign link_active = (link_state_q == LinkActive);
+ assign link_active_o = (link_state_q == LinkActive);
// Link state is stable, so we can output it to the register
assign link_state_o = link_state_q;
@@ -303,7 +303,7 @@
if (!rst_ni) begin
host_presence_timer <= '0;
end else begin
- if (sof_valid_i || !link_active || link_reset) begin
+ if (sof_valid_i || !link_active_o || link_reset) begin
host_presence_timer <= '0;
end else if (us_tick_i && !host_lost_o) begin
host_presence_timer <= host_presence_timer + 1;
diff --git a/hw/ip/usbdev/rtl/usbdev_usbif.sv b/hw/ip/usbdev/rtl/usbdev_usbif.sv
index b79e4ce..a912ea3 100644
--- a/hw/ip/usbdev/rtl/usbdev_usbif.sv
+++ b/hw/ip/usbdev/rtl/usbdev_usbif.sv
@@ -80,6 +80,7 @@
output logic link_disconnect_o,
output logic link_connect_o,
output logic link_reset_o,
+ output logic link_active_o,
output logic link_suspend_o,
output logic link_resume_o,
output logic link_in_err_o,
@@ -346,6 +347,7 @@
.link_disconnect_o (link_disconnect_o),
.link_connect_o (link_connect_o),
.link_reset_o (link_reset),
+ .link_active_o (link_active_o),
.link_suspend_o (link_suspend_o),
.link_resume_o (link_resume_o),
.link_state_o (link_state_o),
diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
index 48d8cb6..f7d754e 100644
--- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
@@ -1603,6 +1603,29 @@
]
alert_list: []
scan: "false"
+ inter_signal_list:
+ [
+ {
+ name: usb_ref_val
+ type: uni
+ act: req
+ package: ""
+ struct: logic
+ width: "1"
+ inst_name: usbdev
+ index: -1
+ }
+ {
+ name: usb_ref_pulse
+ type: uni
+ act: req
+ package: ""
+ struct: logic
+ width: "1"
+ inst_name: usbdev
+ index: -1
+ }
+ ]
}
]
memory:
@@ -3408,6 +3431,26 @@
index: -1
}
{
+ name: usb_ref_val
+ type: uni
+ act: req
+ package: ""
+ struct: logic
+ width: "1"
+ inst_name: usbdev
+ index: -1
+ }
+ {
+ name: usb_ref_pulse
+ type: uni
+ act: req
+ package: ""
+ struct: logic
+ width: "1"
+ inst_name: usbdev
+ index: -1
+ }
+ {
struct: flash
type: req_rsp
name: flash_ctrl
diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
index f6b694d..7abb4d8 100644
--- a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
+++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
@@ -813,6 +813,10 @@
.intr_frame_o (intr_usbdev_frame),
.intr_connected_o (intr_usbdev_connected),
+ // Inter-module signals
+ .usb_ref_val_o(),
+ .usb_ref_pulse_o(),
+
.clk_i (clkmgr_clocks.clk_io_peri),
.clk_usb_48mhz_i (clkmgr_clocks.clk_usb_peri),
.rst_ni (rstmgr_resets.rst_sys_io_n),