../src/lowrisc_ip_usbdev_0.1/rtl/usbdev_linkstate.sv Cov: 100%
1: // Copyright lowRISC contributors.
2: // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3: // SPDX-License-Identifier: Apache-2.0
4: //
5: // Link state detection
6: //
7:
8: module usbdev_linkstate (
9: input logic clk_48mhz_i,
10: input logic rst_ni,
11: input logic us_tick_i,
12: input logic usb_sense_i,
13: input logic usb_rx_d_i,
14: input logic usb_rx_se0_i,
15: input logic sof_valid_i,
16: output logic link_disconnect_o, // level
17: output logic link_connect_o, // level
18: output logic link_reset_o, // level
19: output logic link_active_o, // level
20: output logic link_suspend_o, // level
21: output logic link_resume_o, // pulse
22: output logic host_lost_o, // level
23:
24: output logic [2:0] link_state_o
25: );
26:
27: localparam logic [11:0] SUSPEND_TIMEOUT = 12'd3000; // 3ms by spec
28: localparam logic [2:0] RESET_TIMEOUT = 3'd3; // 3us. Can be 2.5us - 10ms by spec
29:
30: typedef enum logic [2:0] {
31: // Unpowered state
32: LinkDisconnect = 0,
33: // Powered states
34: LinkPowered = 1,
35: LinkPoweredSuspend = 2,
36: // Active states
37: LinkActive = 3,
38: LinkSuspend = 4
39: } link_state_e;
40:
41: typedef enum logic [1:0] {
42: NoRst,
43: RstCnt,
44: RstPend
45: } link_rst_state_e;
46:
47: typedef enum logic [1:0] {
48: Active,
49: InactCnt,
50: InactPend
51: } link_inac_state_e;
52:
53: link_state_e link_state_d, link_state_q;
54: logic line_se0_raw, line_idle_raw;
55: logic see_se0, see_idle, see_pwr_sense;
56:
57: // Reset FSM
58: logic [2:0] link_rst_timer_d, link_rst_timer_q;
59: link_rst_state_e link_rst_state_d, link_rst_state_q;
60: logic link_reset; // reset detected (level)
61:
62: // Link inactivity detection
63: logic monitor_inac; // monitor link inactivity
64: logic [11:0] link_inac_timer_d, link_inac_timer_q;
65: link_inac_state_e link_inac_state_d, link_inac_state_q;
66:
67:
68: // Events that are not triggered by a timeout
69: logic ev_bus_active;
70:
71: // Events that are triggered by timeout
72: logic ev_bus_inactive, ev_reset;
73:
74: assign link_disconnect_o = (link_state_q == LinkDisconnect);
75: assign link_connect_o = (link_state_q != LinkDisconnect);
76: assign link_suspend_o = (link_state_q == LinkSuspend ||
77: link_state_q == LinkPoweredSuspend);
78: assign link_active_o = (link_state_q == LinkActive);
79: // Link state is stable, so we can output it to the register
80: assign link_state_o = link_state_q;
81:
82: assign line_se0_raw = usb_rx_se0_i;
83: assign line_idle_raw = usb_rx_d_i && !usb_rx_se0_i; // same as J
84:
85: // four ticks is a bit time
86: // Could completely filter out 2-cycle EOP SE0 here but
87: // does not seem needed
88: prim_filter #(.Cycles(6)) filter_se0 (
89: .clk_i (clk_48mhz_i),
90: .rst_ni (rst_ni),
91: .enable_i (1'b1),
92: .filter_i (line_se0_raw),
93: .filter_o (see_se0)
94: );
95:
96: prim_filter #(.Cycles(6)) filter_idle (
97: .clk_i (clk_48mhz_i),
98: .rst_ni (rst_ni),
99: .enable_i (1'b1),
100: .filter_i (line_idle_raw),
101: .filter_o (see_idle)
102: );
103:
104: prim_filter #(.Cycles(6)) filter_pwr_sense (
105: .clk_i (clk_48mhz_i),
106: .rst_ni (rst_ni),
107: .enable_i (1'b1),
108: .filter_i (usb_sense_i),
109: .filter_o (see_pwr_sense)
110: );
111:
112: // Simple events
113: assign ev_bus_active = !see_idle;
114:
115: always_comb begin
116: link_state_d = link_state_q;
117: link_resume_o = 0;
118: monitor_inac = see_pwr_sense ? ((link_state_q == LinkPowered) | (link_state_q == LinkActive)) :
119: 1'b0;
120:
121: // If VBUS ever goes away the link has disconnected
122: if (!see_pwr_sense) begin
123: link_state_d = LinkDisconnect;
124: end else begin
125: unique case (link_state_q)
126: // No USB supply detected (USB spec: Attached)
127: LinkDisconnect: begin
128: if (see_pwr_sense) begin
129: link_state_d = LinkPowered;
130: end
131: end
132:
133: LinkPowered: begin
134: if (ev_reset) begin
135: link_state_d = LinkActive;
136: end else if (ev_bus_inactive) begin
137: link_state_d = LinkPoweredSuspend;
138: end
139: end
140:
141: LinkPoweredSuspend: begin
142: if (ev_reset) begin
143: link_state_d = LinkActive;
144: end else if (ev_bus_active) begin
145: link_resume_o = 1;
146: link_state_d = LinkPowered;
147: end
148: end
149:
150: // Active (USB spec: Default / Address / Configured)
151: LinkActive: begin
152: if (ev_bus_inactive) begin
153: link_state_d = LinkSuspend;
154: end
155: end
156:
157: LinkSuspend: begin
158: if (ev_reset || ev_bus_active) begin
159: link_resume_o = 1;
160: link_state_d = LinkActive;
161: end
162: end
163:
164: default: begin
165: link_state_d = LinkDisconnect;
166: end
167: endcase // case (link_state_q)
168: end
169: end
170:
171: always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
172: if (!rst_ni) begin
173: link_state_q <= LinkDisconnect;
174: end else begin
175: link_state_q <= link_state_d;
176: end
177: end
178:
179: /////////////////////
180: // Reset detection //
181: /////////////////////
182: // Here we clean up the SE0 signal and generate a signle ev_reset at
183: // the end of a valid reset
184:
185: always_comb begin : proc_rst_fsm
186: link_rst_state_d = link_rst_state_q;
187: link_rst_timer_d = link_rst_timer_q;
188: ev_reset = 1'b0;
189: link_reset = 1'b0;
190:
191: unique case (link_rst_state_q)
192: // No reset signal detected
193: NoRst: begin
194: if (see_se0) begin
195: link_rst_state_d = RstCnt;
196: link_rst_timer_d = 0;
197: end
198: end
199:
200: // Reset signal detected -> counting
201: RstCnt: begin
202: if (!see_se0) begin
203: link_rst_state_d = NoRst;
204: end else begin
205: if (us_tick_i) begin
206: if (link_rst_timer_q == RESET_TIMEOUT) begin
207: link_rst_state_d = RstPend;
208: end else begin
209: link_rst_timer_d = link_rst_timer_q + 1;
210: end
211: end
212: end
213: end
214:
215: // Detected reset -> wait for falling edge
216: RstPend: begin
217: if (!see_se0) begin
218: link_rst_state_d = NoRst;
219: ev_reset = 1'b1;
220: end
221: link_reset = 1'b1;
222: end
223:
224: default : link_rst_state_d = NoRst;
225: endcase
226: end
227:
228: assign link_reset_o = link_reset;
229:
230: always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin : proc_reg_rst
231: if (!rst_ni) begin
232: link_rst_state_q <= NoRst;
233: link_rst_timer_q <= 0;
234: end else begin
235: link_rst_state_q <= link_rst_state_d;
236: link_rst_timer_q <= link_rst_timer_d;
237: end
238: end
239:
240: ////////////////////
241: // Idle detection //
242: ////////////////////
243: // Here we clean up the idle signal and generate a signle ev_bus_inactive
244: // after the timer expires
245: always_comb begin : proc_idle_det
246: link_inac_state_d = link_inac_state_q;
247: link_inac_timer_d = link_inac_timer_q;
248: ev_bus_inactive = 0;
249:
250: unique case (link_inac_state_q)
251: // Active or disabled
252: Active: begin
253: link_inac_timer_d = 0;
254: if (see_idle && monitor_inac) begin
255: link_inac_state_d = InactCnt;
256: end
257: end
258:
259: // Got an inactivity signal -> count duration
260: InactCnt: begin
261: if (!see_idle || !monitor_inac) begin
262: link_inac_state_d = Active;
263: end else if (us_tick_i) begin
264: if (link_inac_timer_q == SUSPEND_TIMEOUT) begin
265: link_inac_state_d = InactPend;
266: ev_bus_inactive = 1;
267: end else begin
268: link_inac_timer_d = link_inac_timer_q + 1;
269: end
270: end
271: end
272:
273: // Counter expired & event sent, wait here
274: InactPend: begin
275: if (!see_idle || !monitor_inac) begin
276: link_inac_state_d = Active;
277: end
278: end
279:
280: default : link_inac_state_d = Active;
281: endcase
282: end
283:
284: always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin : proc_reg_idle_det
285: if (!rst_ni) begin
286: link_inac_state_q <= Active;
287: link_inac_timer_q <= 0;
288: end else begin
289: link_inac_state_q <= link_inac_state_d;
290: link_inac_timer_q <= link_inac_timer_d;
291: end
292: end
293:
294: /////////////////////////
295: // Host loss detection //
296: /////////////////////////
297: // host_lost if no sof in 4.096ms (supposed to be every 1ms)
298: // and the link is active
299: logic [12:0] host_presence_timer;
300:
301: assign host_lost_o = host_presence_timer[12];
302: always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
303: if (!rst_ni) begin
304: host_presence_timer <= '0;
305: end else begin
306: if (sof_valid_i || !link_active_o || link_reset) begin
307: host_presence_timer <= '0;
308: end else if (us_tick_i && !host_lost_o) begin
309: host_presence_timer <= host_presence_timer + 1;
310: end
311: end
312: end
313:
314: endmodule
315: