diff --git a/Makefile b/Makefile index 73efe64..b5bba9f 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,7 @@ MODULES += phy_core MODULES += pmd_dp83223 MODULES += pmd_dp83223_rx MODULES += scramble +MODULES += wb_mux .PHONY: test test: $(addsuffix .fst,$(MODULES)) $(addsuffix .synth.fst,$(MODULES)) diff --git a/rtl/wb_mux.v b/rtl/wb_mux.v new file mode 100644 index 0000000..78f481c --- /dev/null +++ b/rtl/wb_mux.v @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2023 Sean Anderson + * + * A wishbone mux with extremely-simple address decoding. + */ + +`include "common.vh" + +module wb_mux ( + /* Wishbone master */ + output reg m_ack, m_err, + input m_cyc, m_stb, m_we, + input [ADDR_WIDTH + SLAVES - 1:0] m_addr, + input [DATA_WIDTH - 1:0] m_data_write, + output reg [DATA_WIDTH - 1:0] m_data_read, + + input [SLAVES - 1:0] s_ack, s_err, + output reg [SLAVES - 1:0] s_cyc, s_stb, s_we, + output reg [ADDR_WIDTH * SLAVES - 1:0] s_addr, + output reg [DATA_WIDTH * SLAVES - 1:0] s_data_write, + input [DATA_WIDTH * SLAVES - 1:0] s_data_read +); + + parameter ADDR_WIDTH = 5; + parameter DATA_WIDTH = 16; + parameter SLAVES = 4; + + integer i; + reg selected; + + always @(*) begin + s_cyc = {SLAVES{m_cyc}}; + s_stb = {SLAVES{1'b0}}; + s_we = {SLAVES{m_we}}; + s_addr = {SLAVES{m_addr[ADDR_WIDTH - 1:0]}}; + s_data_write = {SLAVES{m_data_write}}; + m_ack = 0; + m_err = 0; + m_data_read = {DATA_WIDTH{1'bX}}; + selected = 0; + + for (i = 0; i < SLAVES; i = i + 1) begin + if (m_addr[ADDR_WIDTH + i] && !selected) begin + m_ack = s_ack[i]; + m_err = s_err[i]; + m_data_read = s_data_read[i * DATA_WIDTH +: DATA_WIDTH]; + s_stb[i] = m_stb; + s_we[i] = m_we; + selected = 1; + end + end + + if (m_cyc && m_stb && !selected) begin + m_ack = 0; + m_err = 1; + end + end + +endmodule diff --git a/tb/util.py b/tb/util.py index aec0935..874f4ab 100644 --- a/tb/util.py +++ b/tb/util.py @@ -19,6 +19,9 @@ async def async_iter(it): def BIT(n): return 1 << n +def GENMASK(h, l): + return (-1 << l) & ((1 << h + 1) - 1) + # From https://stackoverflow.com/a/7864317/5086505 class classproperty(property): def __get__(self, cls, owner): diff --git a/tb/wb_mux.py b/tb/wb_mux.py new file mode 100644 index 0000000..f717a96 --- /dev/null +++ b/tb/wb_mux.py @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2023 Sean Anderson + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, Timer +from cocotb.types import LogicArray + +from .util import BIT, GENMASK + +ADDR_WIDTH = 5 +DATA_WIDTH = 16 +SLAVES = 4 + +def get(signal, slave, width): + return (signal.value >> (slave * width)) & GENMASK(width - 1, 0) + +def check(mux): + selected = False + for i in range(SLAVES): + cyc = get(mux.s_cyc, i, 1) + assert cyc == mux.m_cyc.value + if mux.m_addr.value & BIT(i + ADDR_WIDTH) and not selected: + selected = True + stb = bool(mux.s_stb.value & BIT(i)) + assert stb == mux.m_stb.value + if not cyc and stb: + continue + + assert get(mux.s_addr, i, ADDR_WIDTH) == mux.m_addr.value & GENMASK(ADDR_WIDTH - 1, 0) + we = get(mux.s_we, i, 1) + assert we == mux.m_we.value + if we: + assert get(mux.s_data_write, i, DATA_WIDTH) == mux.m_data_write.value + + assert mux.m_ack.value == get(mux.s_ack, i, 1) + assert mux.m_err.value == get(mux.s_err, i, 1) + assert mux.m_data_read.value == get(mux.s_data_read, i, DATA_WIDTH) + else: + assert not get(mux.s_stb, i, 1) + + if not selected and mux.m_cyc.value and mux.m_stb.value: + assert not mux.m_ack.value + assert mux.m_err.value + +@cocotb.test(timeout_time=1, timeout_unit='us') +async def test_mdio(mux): + mux.m_cyc.value = 1 + mux.m_stb.value = 1 + mux.m_we.value = 1 + mux.m_data_write.value = 0x1364 + mux.s_ack.value = 0 + mux.s_err.value = 0 + mux.s_data_read.value = 0x0123456789abcdef + + for i in range(4): + mux.m_addr.value = BIT(i + 5) | 0x15 + mux.s_ack.value = BIT(i) + mux.s_err.value = GENMASK(SLAVES - 1, 0) ^ BIT(i) + await Timer(1) + check(mux) + + mux.s_ack.value = GENMASK(SLAVES - 1, 0) ^ BIT(i) + mux.s_err.value = BIT(i) + await Timer(1) + check(mux) + + mux.m_addr.value = 0 + await Timer(1) + check(mux) + + mux.m_stb.value = 0 + await Timer(1) + check(mux) + + mux.m_cyc.value = 0 + mux.m_stb.value = 1 + await Timer(1) + check(mux) + + mux.m_cyc.value = 1 + mux.m_addr.value = 0x1ff + mux.m_we.value = 0 + await Timer(1) + check(mux)