mdio_regs: Add support for counters

This adds support for counters for interesting conditions (disconnects,
PMD phase wraparounds, errors, etc). All the counters are 15 bits
instead of 16, because 16-bit counters have an fmax of 110MHz or so. All
the counters live behind a condition because they ~double the number of
resources used.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2022-11-02 16:38:19 -04:00
parent ec08287853
commit 7d35e07401
2 changed files with 133 additions and 6 deletions

View File

@ -16,6 +16,10 @@ module mdio_regs (
/* Control signals */ /* Control signals */
input link_status, input link_status,
input positive_wraparound,
input negative_wraparound,
input false_carrier,
input symbol_error,
output reg loopback, output reg loopback,
output reg pdown, output reg pdown,
output reg isolate, output reg isolate,
@ -28,19 +32,39 @@ module mdio_regs (
parameter [3:0] REVISION = 0; parameter [3:0] REVISION = 0;
/* /*
* Normally, this module will assert err when read/writing to an * Normally, this module will assert err when read/writing to an
* unknown register. The master will detect this and won't drive MDIO * unknown register. The master will detect this and won't drive the
* line. However, this might be undesirable if there is no external * MDIO line. However, this might be undesirable if there is no
* MDIO bus. Setting this parameter to 0 will cause it to ack all * external MDIO bus. Setting this parameter to 0 will cause it to ack
* transactions. Writes to unknown registers will be ignored, and * all transactions. Writes to unknown registers will be ignored, and
* reads from unknown registers will yield 16'hffff, emulating * reads from unknown registers will yield 16'hffff, emulating
* a pull-up on MDIO. * a pull-up on MDIO.
*/ */
parameter EMULATE_PULLUP = 0; parameter EMULATE_PULLUP = 0;
/* Enable counter registers */
parameter ENABLE_COUNTERS = 1;
/*
* Number of bits in counters; we can't meet timing with 16 bits, so
* use a smaller default.
*/
parameter COUNTER_WIDTH = 15;
/* c22 Basic Mode Control Register */
localparam BMCR = 0; localparam BMCR = 0;
/* c22 Basic Mode Status Register */
localparam BMSR = 1; localparam BMSR = 1;
/* c22 Phy Identifier */
localparam ID1 = 2; localparam ID1 = 2;
localparam ID2 = 3; localparam ID2 = 3;
/* Negative Wraparound Counter Register */
localparam NWCR = 16;
/* Positive Wraparound Counter Register */
localparam PWCR = 17;
/* Disconnect Counter Register */
localparam DCR = 18;
/* False Carrier Counter Register */
localparam FCCR = 19;
/* Symbol Error Counter Register */
localparam SECR = 21;
localparam BMCR_RESET = 15; localparam BMCR_RESET = 15;
localparam BMCR_LOOPBACK = 14; localparam BMCR_LOOPBACK = 14;
@ -57,10 +81,13 @@ module mdio_regs (
localparam BMSR_EXTCAP = 0; localparam BMSR_EXTCAP = 0;
integer i; integer i;
reg duplex, link_status_latched; reg duplex, false_carrier_last, false_carrier_event;
reg link_status_latched, link_status_latched_next, link_status_last, disconnect;
reg loopback_next, pdown_next, isolate_next, duplex_next, coltest_next; reg loopback_next, pdown_next, isolate_next, duplex_next, coltest_next;
reg link_status_latched_next;
reg [15:0] data_read_next; reg [15:0] data_read_next;
/* Can't meet timing at 16 bits wide */
reg [COUNTER_WIDTH-1:0] nwc, pwc, dc, fcc, sec;
reg [COUNTER_WIDTH-1:0] nwc_next, pwc_next, dc_next, fcc_next, sec_next;
initial begin initial begin
loopback = 0; loopback = 0;
@ -69,6 +96,14 @@ module mdio_regs (
duplex = 0; duplex = 0;
coltest = 0; coltest = 0;
link_status_latched = 0; link_status_latched = 0;
link_status_last = 0;
if (ENABLE_COUNTERS) begin
nwc = 0;
pwc = 0;
dc = 0;
fcc = 0;
sec = 0;
end
end end
always @(*) begin always @(*) begin
@ -78,6 +113,22 @@ module mdio_regs (
duplex_next = duplex; duplex_next = duplex;
coltest_next = coltest; coltest_next = coltest;
link_status_latched_next = link_status_latched && link_status; link_status_latched_next = link_status_latched && link_status;
disconnect = link_status_last && !link_status;
false_carrier_event = false_carrier && !false_carrier_last;
if (ENABLE_COUNTERS) begin
nwc_next = nwc;
pwc_next = pwc;
dc_next = dc;
fcc_next = fcc;
sec_next = sec;
if (!(&nwc)) nwc_next = nwc + negative_wraparound;
if (!(&pwc)) pwc_next = pwc + positive_wraparound;
if (!(&dc)) dc_next = dc + disconnect;
if (!(&fcc)) fcc_next = fcc + false_carrier_event;
if (!(&sec)) sec_next = sec + symbol_error;
end
data_read_next = 0; data_read_next = 0;
ack = cyc && stb; ack = cyc && stb;
@ -105,6 +156,13 @@ module mdio_regs (
duplex_next = 0; duplex_next = 0;
coltest_next = 0; coltest_next = 0;
link_status_latched_next = link_status; link_status_latched_next = link_status;
if (ENABLE_COUNTERS) begin
nwc_next = negative_wraparound;
pwc_next = positive_wraparound;
dc_next = disconnect;
fcc_next = false_carrier_event;
sec_next = symbol_error;
end
end end
end end
end end
@ -127,6 +185,36 @@ module mdio_regs (
for (i = 0; i < 6; i = i + 1) for (i = 0; i < 6; i = i + 1)
data_read_next[i + 4] = OUI[23 - i]; data_read_next[i + 4] = OUI[23 - i];
end end
NWCR: if (ENABLE_COUNTERS) begin
data_read_next = nwc;
if (cyc && stb)
nwc_next = we ? data_write : negative_wraparound;
end
PWCR: if (ENABLE_COUNTERS) begin
data_read_next = pwc;
if (cyc && stb)
pwc_next = we ? data_write : positive_wraparound;
end
DCR: if (ENABLE_COUNTERS) begin
data_read_next = dc;
if (cyc && stb)
dc_next = we ? data_write : disconnect;
end
FCCR: if (ENABLE_COUNTERS) begin
data_read_next = fcc;
if (cyc && stb)
fcc_next = we ? data_write : false_carrier_event;
end
SECR: if (ENABLE_COUNTERS) begin
data_read_next = sec;
if (cyc && stb)
sec_next = we ? data_write : symbol_error;
end
default: begin default: begin
if (EMULATE_PULLUP) begin if (EMULATE_PULLUP) begin
data_read_next = 16'hFFFF; data_read_next = 16'hFFFF;
@ -146,7 +234,16 @@ module mdio_regs (
duplex <= duplex_next; duplex <= duplex_next;
coltest <= coltest_next; coltest <= coltest_next;
link_status_latched <= link_status_latched_next; link_status_latched <= link_status_latched_next;
link_status_last <= link_status;
false_carrier_last <= false_carrier;
data_read <= data_read_next; data_read <= data_read_next;
if (ENABLE_COUNTERS) begin
nwc <= nwc_next;
pwc <= pwc_next;
dc <= dc_next;
fcc <= fcc_next;
sec <= sec_next;
end
end end
endmodule endmodule

View File

@ -14,6 +14,11 @@ BMSR = 1
PHYID1 = 2 PHYID1 = 2
PHYID2 = 3 PHYID2 = 3
EXTSTATUS = 15 EXTSTATUS = 15
NWCR = 16
PWCR = 17
DCR = 18
FCCR = 19
SECR = 21
BMCR_RESET = BIT(15) BMCR_RESET = BIT(15)
BMCR_LOOPBACK = BIT(14) BMCR_LOOPBACK = BIT(14)
@ -34,6 +39,10 @@ async def test_mdio(regs):
regs.cyc.value = 1 regs.cyc.value = 1
regs.stb.value = 0 regs.stb.value = 0
regs.link_status.value = 1 regs.link_status.value = 1
regs.positive_wraparound.value = 0
regs.negative_wraparound.value = 0
regs.false_carrier.value = 0
regs.symbol_error.value = 0
await Timer(1) await Timer(1)
await cocotb.start(Clock(regs.clk, 8, units='ns').start()) await cocotb.start(Clock(regs.clk, 8, units='ns').start())
@ -93,3 +102,24 @@ async def test_mdio(regs):
# I'm pretty sure this register will never be implemented # I'm pretty sure this register will never be implemented
assert await xfer(EXTSTATUS) is None assert await xfer(EXTSTATUS) is None
assert await xfer(EXTSTATUS, 0) is None assert await xfer(EXTSTATUS, 0) is None
async def counter_test(reg, signal, edge_triggered=False, active_high=True):
signal.value = 1 if active_high else 0
assert await xfer(reg) == 1
await xfer(reg, 0xfffe)
if edge_triggered:
signal.value = 0 if active_high else 1
await FallingEdge(regs.clk)
if edge_triggered:
signal.value = 1 if active_high else 0
await FallingEdge(regs.clk)
signal.value = 0 if active_high else 1
assert await xfer(reg) == 0x7fff
assert await xfer(reg) == 0
await counter_test(NWCR, regs.negative_wraparound)
await counter_test(PWCR, regs.positive_wraparound)
await xfer(DCR) # Clear DCR from the BMSR testing
await counter_test(DCR, regs.link_status, True, False)
await counter_test(FCCR, regs.false_carrier, True)
await counter_test(SECR, regs.symbol_error)