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 <seanga2@gmail.com>
This commit is contained in:
parent
afbb64023e
commit
7c9ac42988
1
Makefile
1
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))
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||
/*
|
||||
* Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
|
||||
*
|
||||
* 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
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-Only
|
||||
# Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
|
||||
|
||||
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)
|
Loading…
Reference in New Issue