From 7c9ac42988a5a2792f0911c4cec2d3a4e868edd6 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Sat, 18 Feb 2023 22:32:12 -0500 Subject: [PATCH] Add wishbone mux This adds a simple wishbone mux. The idea is that each slave gets its own address bit. This lends itself to extemely simple address decoding, but uses up address space quickly. In theory, we could also give larger addres space to some slaves, but currently lower bits have priority. The testbench is also very simple. Since everything is combinatorial, we can determine the outputs from the inputs exactly. Signed-off-by: Sean Anderson --- Makefile | 1 + rtl/wb_mux.v | 60 +++++++++++++++++++++++++++++++++++++ tb/util.py | 3 ++ tb/wb_mux.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 rtl/wb_mux.v create mode 100644 tb/wb_mux.py 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)