2022-08-27 12:10:59 -05:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-Only
|
|
|
|
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
|
|
|
|
|
|
|
import random
|
|
|
|
|
|
|
|
import cocotb
|
|
|
|
from cocotb.clock import Clock
|
|
|
|
from cocotb.triggers import ClockCycles, Edge, FallingEdge, First, RisingEdge, Timer
|
|
|
|
from cocotb.types import LogicArray
|
|
|
|
|
|
|
|
from .util import ClockEnable
|
|
|
|
|
|
|
|
def to_bits(val, width):
|
|
|
|
for bit in range(width - 1, -1, -1):
|
|
|
|
yield (val >> bit) & 1
|
|
|
|
|
|
|
|
def frame(phyad, regad, data=None, *, st=0b01, op=None, preamble_bits=32):
|
|
|
|
for _ in range(preamble_bits):
|
|
|
|
yield 1
|
|
|
|
|
|
|
|
yield from to_bits(st, 2)
|
|
|
|
if op is None:
|
|
|
|
op = 0b10 if data is None else 0b01
|
|
|
|
yield from to_bits(op, 2)
|
|
|
|
|
|
|
|
yield from to_bits(phyad, 5)
|
|
|
|
yield from to_bits(regad, 5)
|
|
|
|
if data is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
yield 1
|
|
|
|
yield 0
|
|
|
|
yield from to_bits(data, 16)
|
|
|
|
|
|
|
|
# Should be 50, but reduced to simulate faster
|
|
|
|
MDIO_RATIO = 3
|
|
|
|
|
|
|
|
async def mdio_read(mdio, phyad, regad, **kwargs):
|
|
|
|
ret = 0
|
|
|
|
|
|
|
|
for bit in frame(phyad, regad, **kwargs):
|
|
|
|
await RisingEdge(mdio.ce)
|
|
|
|
mdio.mdi.value = bit
|
|
|
|
await FallingEdge(mdio.ce)
|
|
|
|
mdio.mdi.value = LogicArray('X')
|
|
|
|
|
|
|
|
for bit in range(19):
|
|
|
|
await RisingEdge(mdio.ce)
|
|
|
|
await RisingEdge(mdio.clk)
|
|
|
|
|
|
|
|
if bit < 2:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if bit == 2:
|
|
|
|
if not mdio.mdo_valid.value:
|
|
|
|
ret = None
|
|
|
|
continue
|
|
|
|
|
|
|
|
if ret is None:
|
|
|
|
assert not mdio.mdo_valid.value
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
assert mdio.mdo_valid.value
|
|
|
|
|
|
|
|
ret <<= 1
|
|
|
|
ret |= mdio.mdo.value
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
async def mdio_write(mdio, phyad, regad, data, **kwargs):
|
|
|
|
for bit in frame(phyad, regad, data, **kwargs):
|
|
|
|
await RisingEdge(mdio.ce)
|
|
|
|
mdio.mdi.value = bit
|
|
|
|
await FallingEdge(mdio.ce)
|
|
|
|
mdio.mdi.value = LogicArray('X')
|
|
|
|
|
|
|
|
async def wb_read(mdio, addr, data):
|
|
|
|
while not (mdio.cyc.value and mdio.stb.value):
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
|
|
|
|
assert not mdio.we.value
|
|
|
|
assert mdio.addr.value == addr
|
|
|
|
mdio.data_read.value = data
|
|
|
|
mdio.ack.value = 1
|
|
|
|
|
|
|
|
await RisingEdge(mdio.clk)
|
|
|
|
mdio.ack.value = 0
|
|
|
|
mdio.data_read.value = LogicArray('X' * 16)
|
|
|
|
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
assert not mdio.stb.value
|
|
|
|
|
|
|
|
async def wb_write(mdio, addr, data):
|
|
|
|
while not (mdio.cyc.value and mdio.stb.value):
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
|
|
|
|
mdio.ack.value = 1
|
|
|
|
|
|
|
|
await RisingEdge(mdio.clk)
|
|
|
|
assert mdio.we.value
|
|
|
|
assert mdio.addr.value == addr
|
|
|
|
assert mdio.data_write.value == data
|
|
|
|
mdio.ack.value = 0
|
|
|
|
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
assert not mdio.stb.value
|
|
|
|
|
|
|
|
async def setup(mdio):
|
|
|
|
mdio.mdi.value = 0
|
|
|
|
mdio.ack.value = 0
|
2022-08-29 20:25:25 -05:00
|
|
|
mdio.err.value = 0
|
2022-08-27 12:10:59 -05:00
|
|
|
mdio.data_read.value = LogicArray('X' * 16)
|
|
|
|
await cocotb.start(ClockEnable(mdio.clk, mdio.ce, MDIO_RATIO))
|
|
|
|
await Timer(1)
|
|
|
|
await cocotb.start(Clock(mdio.clk, 8, units='ns').start())
|
|
|
|
|
|
|
|
@cocotb.test(timeout_time=50, timeout_unit='us')
|
|
|
|
async def test_mdio(mdio):
|
|
|
|
await setup(mdio)
|
|
|
|
|
|
|
|
reads = [(i, random.randrange(0, 0xFFFF)) for i in range(16)]
|
|
|
|
writes = [(i, random.randrange(0, 0xFFFF)) for i in range(16)]
|
|
|
|
random.shuffle(reads)
|
|
|
|
random.shuffle(writes)
|
|
|
|
|
|
|
|
async def rw_mdio():
|
|
|
|
for (read, write) in zip(reads, writes):
|
|
|
|
assert await mdio_read(mdio, 0, read[0]) == read[1]
|
|
|
|
await mdio_write(mdio, 0, write[0], write[1])
|
|
|
|
await cocotb.start(rw_mdio())
|
|
|
|
|
|
|
|
for (read, write) in zip(reads, writes):
|
|
|
|
await wb_read(mdio, read[0], read[1])
|
|
|
|
await wb_write(mdio, write[0], write[1])
|
|
|
|
|
|
|
|
@cocotb.test(timeout_time=20, timeout_unit='us')
|
|
|
|
async def test_badmdio(mdio):
|
|
|
|
await setup(mdio)
|
|
|
|
|
|
|
|
async def nowb():
|
|
|
|
await First(RisingEdge(mdio.cyc), RisingEdge(mdio.stb))
|
|
|
|
assert False, "Unexpected wishbone transaction"
|
|
|
|
await cocotb.start(nowb())
|
|
|
|
|
|
|
|
# Force mdi low to ensure we get exactly 31 bits in the preamble
|
|
|
|
async def rw_mdio(phyad, **kwargs):
|
|
|
|
mdio.mdi.value = 0
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
assert await mdio_read(mdio, phyad, 0, **kwargs) is None
|
|
|
|
mdio.mdi.value = 0
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
await mdio_write(mdio, phyad, 0, 0, **kwargs)
|
|
|
|
|
|
|
|
await rw_mdio(0, preamble_bits=31)
|
|
|
|
await rw_mdio(0, st=0b00)
|
|
|
|
await rw_mdio(0, op=0b00)
|
|
|
|
await rw_mdio(1)
|
|
|
|
await rw_mdio(0x10)
|
|
|
|
|
|
|
|
@cocotb.test(timeout_time=20, timeout_unit='us')
|
|
|
|
async def test_badwb(mdio):
|
|
|
|
await setup(mdio)
|
|
|
|
|
2022-08-29 20:25:25 -05:00
|
|
|
async def bad_resp():
|
|
|
|
# No ack
|
|
|
|
await ClockCycles(mdio.stb, 2, False)
|
|
|
|
# Error response
|
|
|
|
for _ in range(2):
|
|
|
|
while not (mdio.cyc.value and mdio.stb.value):
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
|
|
|
|
mdio.err.value = 1
|
|
|
|
await RisingEdge(mdio.clk)
|
|
|
|
mdio.err.value = 0
|
|
|
|
await FallingEdge(mdio.clk)
|
|
|
|
assert not mdio.stb.value
|
|
|
|
await cocotb.start(bad_resp())
|
|
|
|
|
|
|
|
for _ in range(2):
|
2022-08-27 12:10:59 -05:00
|
|
|
assert await mdio_read(mdio, 0, 0) is None
|
|
|
|
await mdio_write(mdio, 0, 0, 0)
|