-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheth_phy_88e1111_controller.sv
390 lines (332 loc) · 13.9 KB
/
eth_phy_88e1111_controller.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// Marvel 88E1111 Controller
// Copyright ⓒ 2023 Douglas P. Fields, Jr. All Rights Reserved
`ifdef IS_QUARTUS // Defined in Assignments -> Settings -> ... -> Verilog HDL input logic
// This doesn't work in Questa for some reason. vlog-2892 errors.
`default_nettype none // Disable implicit creation of undeclared nets
`endif
// Simulation: Tick at 1ns (using #<num>) to a precision of 0.1ns (100ps)
`timescale 1 ns / 100 ps
/* See README.md, section "88E1111 Controller," for instructions on what
* this is supposed to be doing.
*/
module eth_phy_88e1111_controller #(
// Pass to mii_management_interface - see that for description
parameter CLK_DIV = 32,
parameter PHY_MII_ADDRESS = 5'b0_0000,
// 50MHz = 50,000 cycles per ms
`ifdef IS_QUARTUS
parameter CLOCKS_FOR_5ms = 250_000
`else
// Set a faster number for simulation
parameter CLOCKS_FOR_5ms = 50
`endif
) (
input logic clk,
input logic reset, // This is the global system reset
// MDIO Bus - pass through to MII management interface
// These need to be connected to a tristate buffered output pin
input logic mdio_i, // MDIO input
output logic mdio_o, // MDIO output
output logic mdio_e, // MDIO output enabled
output logic mdc, // MDC clock (generated from 1/4 of 1/CLK_DIV of system clk)
output logic phy_reset, // Positive ETH PHY reset signal
// Outputs about what's going on
output logic busy, // This controller is busy
output logic success, // When busy -> !busy, did we complete the last request successfully?
// Passthrough to MII management interface
// (when the controller & the underlying is not busy)
// This adds 1 cycle latency on all requests to MII
input logic mii_activate, // True to begin when !busy
input logic mii_read, // True to do a read operation, false for write
input logic [4:0] mii_register, // Register to read/write
input logic [15:0] mii_data_out, // Data to send when !read
output logic [15:0] mii_data_in, // Data read when read
// Outputs from our PHY
// FIXME: Code these and output from the controller
output logic speed_10,
output logic speed_100,
output logic speed_1000,
output logic full_duplex,
output logic connected,
// Assert this anytime we failed to get expected response in configuration
output logic configured, // When RX and TX are ready to be used
output logic config_error,
// Debugging outputs
output logic [5:0] d_state,
output logic [15:0] d_reg0,
output logic [15:0] d_reg20,
output logic [15:0] d_seen_states,
output logic [15:0] d_soft_reset_checks
);
`ifdef IS_QUARTUS
// QuestaSim does not like these initial conditions with always_ff:
// # ** Error (suppressible): eth_phy_88e1111_controller.sv(171): (vlog-7061) Variable 'phy_reset' driven in an always_ff block, may not be driven by any other process. See eth_phy_88e1111_controller.sv(73).
initial phy_reset = '1;
initial config_error = '0;
initial connected = '0;
initial d_seen_states = '0;
initial d_soft_reset_checks = '0;
initial configured = '0;
`endif
logic reset_into_mii = '0;
logic c_mii_busy;
logic c_mii_success;
// Controller internal versions of these signals,
// which can also be sent in by end-user using non-c_ just-mii_ versions
logic c_mii_activate; // True to begin when !busy
logic c_mii_read; // True to do a read operation, false for write
logic [4:0] c_mii_register; // Register to read/write
logic [15:0] c_mii_data_out; // Data to send when !read
logic [15:0] c_mii_data_in; // Data read when read
// Instantiate one MII Management Interface
mii_management_interface #(
// Leave all parameters at default, usually
.CLK_DIV(CLK_DIV)
) mii_management_interface1 (
// Controller clock & reset
.clk(clk),
.reset(reset || reset_into_mii),
// External management bus connections
.mdc(mdc),
.mdio_e(mdio_e), .mdio_i(mdio_i), .mdio_o(mdio_o),
// Status
.busy(c_mii_busy),
.success(c_mii_success),
// Management interface inputs
.activate (c_mii_activate),
.read (c_mii_read),
.phy_address(PHY_MII_ADDRESS),
.register (c_mii_register),
.data_out (c_mii_data_out),
.data_in (c_mii_data_in)
);
// Main controller state machine states
localparam S_POWER_ON = 6'd0,
S_POST_RESET_WAIT = 6'd1,
S_STARTUP_READ_20 = 6'd2,
S_STARTUP_WRITE_20 = 6'd3,
S_STARTUP_REREAD_20 = 6'd4,
S_STARTUP_VERIFY_20 = 6'd5,
S_REGISTER_READ_START = 6'd6,
S_REGISTER_WRITE_START = 6'd7,
S_REGISTER_READ_AWAIT = 6'd8,
S_REGISTER_WRITE_AWAIT = 6'd9,
S_SOFT_RESET_BEGIN = 6'd10,
S_SOFT_RESET_WRITE_0 = 6'd11,
S_SOFT_RESET_WAIT = 6'd12,
S_SOFT_RESET_VERIFY = 6'd13,
S_CUSTOMER_IDLE = 6'd14;
// Register IDs
localparam R_CONTROL = 5'd0,
R_STATUS = 5'd1,
R_PHY_ID_1 = 5'd2,
R_PHY_ID_2 = 5'd3,
R_PHY_EXT_SPEC_CTRL = 5'd20; // Extended PHY Specific Control Register (Table 95)
// Register bits
localparam REG20_ADJ_CLKs = 16'b0000_0000__1000_0010,
REG0_SOFT_RESET = 16'b1000_0000__0000_0000;
// Delays
localparam DELAY_AFTER_POWER_ON = CLOCKS_FOR_5ms * 3, // Also for minimum reset
DELAY_AFTER_DEASSERT_RESET = CLOCKS_FOR_5ms * 3 / 2,
DELAY_AFTER_RUN_MDC = CLK_DIV * 8, // Let the MDC "spin up"
DELAY_AFTER_SOFT_RESET = CLK_DIV * 32;
logic [31:0] delay_counter = DELAY_AFTER_POWER_ON;
logic [5:0] state = S_POWER_ON;
assign d_state = state;
logic [5:0] state_after_rw; // What state after a read/write to MII?
logic [5:0] state_after_soft_reset; // What state after we do soft reset?
logic success_after_rw; // Did a read/write to MII return success?
logic [4:0] reg_to_rw; // Which register to read/write in MII
logic [15:0] saved_read; // What we just read from MII
logic [15:0] val_to_write; // What we should write to MII
logic was_busy; // Track when MII newly becomes busy
always_ff @(posedge clk) begin
if (reset) begin
// Must give a minimum PHY reset of 10ms (4.8.1)
phy_reset <= '1;
// So handle that by doing the full power on cycle after reset
state <= S_POWER_ON;
delay_counter <= DELAY_AFTER_POWER_ON;
reset_into_mii <= '1;
busy <= '1;
success <= '0;
configured <= '0;
config_error <= '0;
d_seen_states <= '0;
d_reg0 <= '0;
d_reg20 <= '0;
end else begin
d_seen_states <= d_seen_states | (16'b1 << state);
case (state)
////////////////////////////////////////////////////////////////////
// Power on routine
S_POWER_ON: begin ///////////////////////////////////////////////////
// We need to wait 10ms before we deassert reset after power on
// (Technically also must ensure 10 clocks of ETH clock but we don't see that.)
busy <= '1;
success <= '0;
config_error <= '0;
configured <= '0;
if (delay_counter == '0) begin
phy_reset <= '0;
reset_into_mii <= '1; // We don't start MDIO/MDC until after PHY reset is down a while
state <= S_POST_RESET_WAIT;
delay_counter <= DELAY_AFTER_DEASSERT_RESET;
end else begin
delay_counter <= delay_counter - 1'd1;
phy_reset <= '1;
reset_into_mii <= '1;
end
end // S_POWER_ON
S_POST_RESET_WAIT: begin ////////////////////////////////////////////
// We need to wait 5ms before we enable MDIO,
// then wait a few moments for MDC to get reestablished (I guess,
// probably not strictly necessary)
if (delay_counter == '0) begin
if (reset_into_mii) begin
// We can start running our MII MDC now (remove the MII reset)
reset_into_mii <= '0;
delay_counter <= DELAY_AFTER_RUN_MDC;
end else begin
// We can begin our startup sequence
state <= S_STARTUP_READ_20;
end
end else begin
delay_counter <= delay_counter - 1'd1;
end
end // S_POST_RESET_WAIT
////////////////////////////////////////////////////////////////////
// Startup configuration routine
S_STARTUP_REREAD_20,
S_STARTUP_READ_20: begin //////////////////////////////////////////////
// We need to read (or re-read) register 20
config_error <= '0;
reg_to_rw <= R_PHY_EXT_SPEC_CTRL;
state <= S_REGISTER_READ_START;
state_after_rw <= (state == S_STARTUP_READ_20) ? S_STARTUP_WRITE_20 : S_STARTUP_VERIFY_20;
end // S_STARTUP_READ_20
S_STARTUP_WRITE_20: begin ///////////////////////////////////////////
// Set Reg 20 bits 7 and 1 to adjust RX and TX clocks in PHY
d_reg20 <= saved_read;
reg_to_rw <= R_PHY_EXT_SPEC_CTRL; // Should already be set
val_to_write <= saved_read | REG20_ADJ_CLKs;
state <= S_REGISTER_WRITE_START;
state_after_rw <= S_STARTUP_REREAD_20;
end // S_STARTUP_WRITE_20
S_STARTUP_VERIFY_20: begin //////////////////////////////////////////
// Verify that bits 7 & 1 are set in register 20
d_reg20 <= saved_read;
if ((saved_read & REG20_ADJ_CLKs) != REG20_ADJ_CLKs) begin
config_error <= '1;
$warning("Could not configure register 20");
// FIXME: Should we try a few more times?
end
state <= S_SOFT_RESET_BEGIN;
state_after_soft_reset <= S_CUSTOMER_IDLE;
end // S_STARTUP_VERIFY_20
////////////////////////////////////////////////////////////////////
// End user interactions
S_CUSTOMER_IDLE: begin ///////////////////////////////////////////
// Pass through everything we get from outside, but just one
// cycle delayed
configured <= '1; // Clearly we're done by now since we're in pass-thru mode
busy <= c_mii_busy;
success <= c_mii_success;
mii_data_in <= c_mii_data_in; // data IN is read IN from the PHY
c_mii_activate <= mii_activate;
c_mii_read <= mii_read;
c_mii_register <= mii_register;
c_mii_data_out <= mii_data_out; // data OUT is data written OUT to the PHY
end
////////////////////////////////////////////////////////////////////
// Register read/write subroutines
S_REGISTER_WRITE_START,
S_REGISTER_READ_START: begin ///////////////////////////////////////
// (TODO: Refactor to use a r/w flag instead of two states?)
// Send a read request to MII once it's not busy
if (c_mii_busy) begin
// Do nothing
end else begin
c_mii_activate <= '1;
c_mii_read <= (state == S_REGISTER_READ_START);
c_mii_register <= reg_to_rw;
if (state == S_REGISTER_WRITE_START)
c_mii_data_out <= val_to_write; // data OUT is data written OUT to the PHY
state <= (state == S_REGISTER_READ_START) ? S_REGISTER_READ_AWAIT : S_REGISTER_WRITE_AWAIT;
was_busy <= '0;
end
end // S_REGISTER_READ_START
S_REGISTER_WRITE_AWAIT,
S_REGISTER_READ_AWAIT: begin ///////////////////////////////////////
// Wait for it to be busy, then wait for it to be not busy.
was_busy <= c_mii_busy;
if (!was_busy && c_mii_busy) begin
// Just became busy
c_mii_activate <= '0; // We no longer need an activation, it's working
end else if (was_busy && c_mii_busy) begin
// It's processing, nothing to do
end else if (was_busy && !c_mii_busy) begin
// Just finished being busy
if (state == S_REGISTER_READ_AWAIT)
saved_read <= c_mii_data_in;
state <= state_after_rw;
success_after_rw <= c_mii_success;
end else if (!c_mii_busy) begin
// Should only happen if MII takes a short time to respond to activate request
if (c_mii_activate)
$warning("Slow MII response");
else
$error("Impossible state");
end
end // S_REGISTER_READ_AWAIT
////////////////////////////////////////////////////////////////////
// Soft reset subroutine: read 0, write 0, wait N ms, read until not in reset
S_SOFT_RESET_BEGIN: begin ////////////////////////////////////////
reg_to_rw <= R_CONTROL;
state <= S_REGISTER_READ_START;
state_after_rw <= S_SOFT_RESET_WRITE_0;
d_soft_reset_checks <= '0;
end // S_SOFT_RESET_BEGIN
S_SOFT_RESET_WRITE_0: begin ///////////////////////////////////////////
d_reg0 <= saved_read;
// Set Reg 0 bit 15 to do a soft reset
reg_to_rw <= R_CONTROL; // Should already be set
val_to_write <= saved_read | REG0_SOFT_RESET;
state <= S_REGISTER_WRITE_START;
state_after_rw <= S_SOFT_RESET_WAIT;
delay_counter <= DELAY_AFTER_SOFT_RESET;
end // S_STARTUP_WRITE_20
S_SOFT_RESET_WAIT: begin ////////////////////////////////////////////
// We read every now and then and then check if software
if (delay_counter == '0) begin
state <= S_REGISTER_READ_START;
reg_to_rw <= R_CONTROL;
state_after_rw <= S_SOFT_RESET_VERIFY;
end else begin
delay_counter <= delay_counter - 1'd1;
end
end // S_SOFT_RESET_WAIT
S_SOFT_RESET_VERIFY: begin //////////////////////////////////////////
// Ensure that reset is done
d_reg0 <= saved_read;
if ((saved_read & REG0_SOFT_RESET) == '0) begin
state <= state_after_soft_reset;
end else begin
// Need to try again
delay_counter <= DELAY_AFTER_SOFT_RESET;
state <= S_SOFT_RESET_WAIT;
d_soft_reset_checks <= d_soft_reset_checks + 1'd1;
end
end // S_SOFT_RESET_VERIFY
endcase
end // reset or not?
end // Main state machine
endmodule
`ifdef IS_QUARTUS // Defined in Assignments -> Settings -> ... -> Verilog HDL Input
// Restore the default_nettype to prevent side effects
// See: https://front-end-verification.blogspot.com/2010/10/implicit-net-declartions-in-verilog-and.html
// and: https://sutherland-hdl.com/papers/2006-SNUG-Boston_standard_gotchas_presentation.pdf
`default_nettype wire // turn implicit nets on again to avoid side-effects
`endif
// `default_nettype none // This causes problems in Questa