soc/interconnect/axi: implement AXILite down-converter
This commit is contained in:
parent
0be607dad9
commit
93bcc94b53
|
@ -172,13 +172,14 @@ class AXILiteInterface:
|
||||||
yield
|
yield
|
||||||
while not (yield self.aw.ready):
|
while not (yield self.aw.ready):
|
||||||
yield
|
yield
|
||||||
|
yield self.aw.valid.eq(0)
|
||||||
while not (yield self.w.ready):
|
while not (yield self.w.ready):
|
||||||
yield
|
yield
|
||||||
|
yield self.w.valid.eq(0)
|
||||||
|
yield self.b.ready.eq(1)
|
||||||
while not (yield self.b.valid):
|
while not (yield self.b.valid):
|
||||||
yield
|
yield
|
||||||
yield self.b.ready.eq(1)
|
|
||||||
resp = (yield self.b.resp)
|
resp = (yield self.b.resp)
|
||||||
yield
|
|
||||||
yield self.b.ready.eq(0)
|
yield self.b.ready.eq(0)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@ -188,12 +189,12 @@ class AXILiteInterface:
|
||||||
yield
|
yield
|
||||||
while not (yield self.ar.ready):
|
while not (yield self.ar.ready):
|
||||||
yield
|
yield
|
||||||
|
yield self.ar.valid.eq(0)
|
||||||
|
yield self.r.ready.eq(1)
|
||||||
while not (yield self.r.valid):
|
while not (yield self.r.valid):
|
||||||
yield
|
yield
|
||||||
yield self.r.ready.eq(1)
|
|
||||||
data = (yield self.r.data)
|
data = (yield self.r.data)
|
||||||
resp = (yield self.r.resp)
|
resp = (yield self.r.resp)
|
||||||
yield
|
|
||||||
yield self.r.ready.eq(0)
|
yield self.r.ready.eq(0)
|
||||||
return (data, resp)
|
return (data, resp)
|
||||||
|
|
||||||
|
@ -679,6 +680,152 @@ class AXILiteSRAM(Module):
|
||||||
|
|
||||||
# AXILite Data Width Converter ---------------------------------------------------------------------
|
# AXILite Data Width Converter ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
class AXILiteDownConverter(Module):
|
||||||
|
def __init__(self, master, slave):
|
||||||
|
assert isinstance(master, AXILiteInterface) and isinstance(slave, AXILiteInterface)
|
||||||
|
dw_from = len(master.r.data)
|
||||||
|
dw_to = len(slave.r.data)
|
||||||
|
ratio = dw_from//dw_to
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
skip = Signal()
|
||||||
|
counter = Signal(max=ratio)
|
||||||
|
do_read = Signal()
|
||||||
|
do_write = Signal()
|
||||||
|
last_was_read = Signal()
|
||||||
|
aw_ready = Signal()
|
||||||
|
w_ready = Signal()
|
||||||
|
|
||||||
|
# Slave address counter
|
||||||
|
master_align = log2_int(master.data_width//8)
|
||||||
|
slave_align = log2_int(slave.data_width//8)
|
||||||
|
addr_counter = Signal(master_align)
|
||||||
|
self.comb += addr_counter[slave_align:].eq(counter)
|
||||||
|
|
||||||
|
# Write path
|
||||||
|
self.comb += [
|
||||||
|
slave.aw.addr.eq(Cat(addr_counter, master.aw.addr[master_align:])),
|
||||||
|
Case(counter, {i: slave.w.data.eq(master.w.data[i*dw_to:]) for i in range(ratio)}),
|
||||||
|
Case(counter, {i: slave.w.strb.eq(master.w.strb[i*dw_to//8:]) for i in range(ratio)}),
|
||||||
|
master.b.resp.eq(RESP_OKAY), # FIXME: error handling?
|
||||||
|
]
|
||||||
|
|
||||||
|
# Read path
|
||||||
|
# shift the data word
|
||||||
|
r_data = Signal(dw_from, reset_less=True)
|
||||||
|
self.sync += If(slave.r.ready, r_data.eq(master.r.data))
|
||||||
|
self.comb += master.r.data.eq(Cat(r_data[dw_to:], slave.r.data))
|
||||||
|
# address, resp
|
||||||
|
self.comb += [
|
||||||
|
slave.ar.addr.eq(Cat(addr_counter, master.ar.addr[master_align:])),
|
||||||
|
master.r.resp.eq(RESP_OKAY), # FIXME: error handling?
|
||||||
|
]
|
||||||
|
|
||||||
|
# Control Path
|
||||||
|
fsm = FSM(reset_state="IDLE")
|
||||||
|
fsm = ResetInserter()(fsm)
|
||||||
|
self.submodules.fsm = fsm
|
||||||
|
self.comb += fsm.reset.eq(~(master.aw.valid | master.ar.valid))
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
NextValue(counter, 0),
|
||||||
|
# If the last access was a read, do a write, and vice versa
|
||||||
|
If(master.aw.valid & master.ar.valid,
|
||||||
|
do_write.eq(last_was_read),
|
||||||
|
do_read.eq(~last_was_read),
|
||||||
|
).Else(
|
||||||
|
do_write.eq(master.aw.valid),
|
||||||
|
do_read.eq(master.ar.valid),
|
||||||
|
),
|
||||||
|
# Start reading/writing immediately not to waste a cycle
|
||||||
|
If(do_write & master.w.valid,
|
||||||
|
NextValue(last_was_read, 0),
|
||||||
|
NextState("WRITE")
|
||||||
|
).Elif(do_read,
|
||||||
|
NextValue(last_was_read, 1),
|
||||||
|
NextState("READ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write conversion
|
||||||
|
fsm.act("WRITE",
|
||||||
|
skip.eq(slave.w.strb == 0),
|
||||||
|
slave.aw.valid.eq(~skip & ~aw_ready),
|
||||||
|
slave.w.valid.eq(~skip & ~w_ready),
|
||||||
|
If(slave.aw.ready,
|
||||||
|
NextValue(aw_ready, 1)
|
||||||
|
),
|
||||||
|
If(slave.w.ready,
|
||||||
|
NextValue(w_ready, 1)
|
||||||
|
),
|
||||||
|
# When skipping, we just increment the counter
|
||||||
|
If(skip,
|
||||||
|
NextValue(counter, counter + 1),
|
||||||
|
# Corner-case: when the last word is being skipped, we must send the response
|
||||||
|
If(counter == (ratio - 1),
|
||||||
|
master.aw.ready.eq(1),
|
||||||
|
master.w.ready.eq(1),
|
||||||
|
NextState("WRITE-RESPONSE-MASTER")
|
||||||
|
)
|
||||||
|
# Write current word and wait for write response
|
||||||
|
).Elif((slave.aw.ready | aw_ready) & (slave.w.ready | w_ready),
|
||||||
|
NextState("WRITE-RESPONSE-SLAVE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WRITE-RESPONSE-SLAVE",
|
||||||
|
NextValue(aw_ready, 0),
|
||||||
|
NextValue(w_ready, 0),
|
||||||
|
If(slave.b.valid,
|
||||||
|
slave.b.ready.eq(1),
|
||||||
|
If(counter == (ratio - 1),
|
||||||
|
master.aw.ready.eq(1),
|
||||||
|
master.w.ready.eq(1),
|
||||||
|
NextState("WRITE-RESPONSE-MASTER")
|
||||||
|
).Else(
|
||||||
|
NextValue(counter, counter + 1),
|
||||||
|
NextState("WRITE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WRITE-RESPONSE-MASTER",
|
||||||
|
NextValue(aw_ready, 0),
|
||||||
|
NextValue(w_ready, 0),
|
||||||
|
master.b.valid.eq(1),
|
||||||
|
If(master.b.ready,
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read conversion
|
||||||
|
fsm.act("READ",
|
||||||
|
slave.ar.valid.eq(1),
|
||||||
|
If(slave.ar.ready,
|
||||||
|
NextState("READ-RESPONSE-SLAVE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("READ-RESPONSE-SLAVE",
|
||||||
|
If(slave.r.valid,
|
||||||
|
# On last word acknowledge ar and hold slave.r.valid until we get master.r.ready
|
||||||
|
If(counter == (ratio - 1),
|
||||||
|
master.ar.ready.eq(1),
|
||||||
|
NextState("READ-RESPONSE-MASTER")
|
||||||
|
# Acknowledge the response and continue conversion
|
||||||
|
).Else(
|
||||||
|
slave.r.ready.eq(1),
|
||||||
|
NextValue(counter, counter + 1),
|
||||||
|
NextState("READ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("READ-RESPONSE-MASTER",
|
||||||
|
master.r.valid.eq(1),
|
||||||
|
If(master.r.ready,
|
||||||
|
slave.r.ready.eq(1),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class AXILiteConverter(Module):
|
class AXILiteConverter(Module):
|
||||||
"""AXILite data width converter"""
|
"""AXILite data width converter"""
|
||||||
def __init__(self, master, slave):
|
def __init__(self, master, slave):
|
||||||
|
@ -690,8 +837,10 @@ class AXILiteConverter(Module):
|
||||||
dw_from = len(master.r.data)
|
dw_from = len(master.r.data)
|
||||||
dw_to = len(slave.r.data)
|
dw_to = len(slave.r.data)
|
||||||
if dw_from > dw_to:
|
if dw_from > dw_to:
|
||||||
raise NotImplementedError
|
print("AXILiteConverter (Down): {} -> {}".format(master.data_width, slave.data_width))
|
||||||
|
self.submodules += AXILiteDownConverter(master, slave)
|
||||||
elif dw_from < dw_to:
|
elif dw_from < dw_to:
|
||||||
|
print("AXILiteConverter (Up): {} -> {}".format(master.data_width, slave.data_width))
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
else:
|
else:
|
||||||
self.comb += master.connect(slave)
|
self.comb += master.connect(slave)
|
||||||
|
|
200
test/test_axi.py
200
test/test_axi.py
|
@ -51,7 +51,7 @@ class Write(Access):
|
||||||
class Read(Access):
|
class Read(Access):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Tests --------------------------------------------------------------------------------------------
|
# TestAXI ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class TestAXI(unittest.TestCase):
|
class TestAXI(unittest.TestCase):
|
||||||
def test_burst2beat(self):
|
def test_burst2beat(self):
|
||||||
|
@ -327,6 +327,71 @@ class TestAXI(unittest.TestCase):
|
||||||
r_ready_random = 90
|
r_ready_random = 90
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TestAXILite --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class AXILiteChecker:
|
||||||
|
def __init__(self, latency=None, rdata_generator=None):
|
||||||
|
self.latency = latency or (lambda: 0)
|
||||||
|
self.rdata_generator = rdata_generator or (lambda adr: 0xbaadc0de)
|
||||||
|
self.writes = []
|
||||||
|
self.reads = []
|
||||||
|
|
||||||
|
def delay(self):
|
||||||
|
for _ in range(self.latency()):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def handle_write(self, axi_lite):
|
||||||
|
while not (yield axi_lite.aw.valid):
|
||||||
|
yield
|
||||||
|
yield from self.delay()
|
||||||
|
addr = (yield axi_lite.aw.addr)
|
||||||
|
yield axi_lite.aw.ready.eq(1)
|
||||||
|
yield
|
||||||
|
yield axi_lite.aw.ready.eq(0)
|
||||||
|
while not (yield axi_lite.w.valid):
|
||||||
|
yield
|
||||||
|
yield from self.delay()
|
||||||
|
data = (yield axi_lite.w.data)
|
||||||
|
strb = (yield axi_lite.w.strb)
|
||||||
|
yield axi_lite.w.ready.eq(1)
|
||||||
|
yield
|
||||||
|
yield axi_lite.w.ready.eq(0)
|
||||||
|
yield axi_lite.b.valid.eq(1)
|
||||||
|
yield axi_lite.b.resp.eq(RESP_OKAY)
|
||||||
|
yield
|
||||||
|
while not (yield axi_lite.b.ready):
|
||||||
|
yield
|
||||||
|
yield axi_lite.b.valid.eq(0)
|
||||||
|
self.writes.append((addr, data, strb))
|
||||||
|
|
||||||
|
def handle_read(self, axi_lite):
|
||||||
|
while not (yield axi_lite.ar.valid):
|
||||||
|
yield
|
||||||
|
yield from self.delay()
|
||||||
|
addr = (yield axi_lite.ar.addr)
|
||||||
|
yield axi_lite.ar.ready.eq(1)
|
||||||
|
yield
|
||||||
|
yield axi_lite.ar.ready.eq(0)
|
||||||
|
data = self.rdata_generator(addr)
|
||||||
|
yield axi_lite.r.valid.eq(1)
|
||||||
|
yield axi_lite.r.resp.eq(RESP_OKAY)
|
||||||
|
yield axi_lite.r.data.eq(data)
|
||||||
|
yield
|
||||||
|
while not (yield axi_lite.r.ready):
|
||||||
|
yield
|
||||||
|
yield axi_lite.r.valid.eq(0)
|
||||||
|
self.reads.append((addr, data))
|
||||||
|
|
||||||
|
@passive
|
||||||
|
def handler(self, axi_lite):
|
||||||
|
while True:
|
||||||
|
if (yield axi_lite.aw.valid):
|
||||||
|
yield from self.handle_write(axi_lite)
|
||||||
|
if (yield axi_lite.ar.valid):
|
||||||
|
yield from self.handle_read(axi_lite)
|
||||||
|
yield
|
||||||
|
|
||||||
|
class TestAXILite(unittest.TestCase):
|
||||||
def test_wishbone2axi2wishbone(self):
|
def test_wishbone2axi2wishbone(self):
|
||||||
class DUT(Module):
|
class DUT(Module):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -439,3 +504,136 @@ class TestAXI(unittest.TestCase):
|
||||||
dut = DUT(size=len(init)*4, init=[v for v in init])
|
dut = DUT(size=len(init)*4, init=[v for v in init])
|
||||||
run_simulation(dut, [generator(dut, init)])
|
run_simulation(dut, [generator(dut, init)])
|
||||||
self.assertEqual(dut.errors, 0)
|
self.assertEqual(dut.errors, 0)
|
||||||
|
|
||||||
|
def converter_test(self, width_from, width_to,
|
||||||
|
write_pattern=None, write_expected=None,
|
||||||
|
read_pattern=None, read_expected=None):
|
||||||
|
assert not (write_pattern is None and read_pattern is None)
|
||||||
|
|
||||||
|
if write_pattern is None:
|
||||||
|
write_pattern = []
|
||||||
|
write_expected = []
|
||||||
|
elif len(write_pattern[0]) == 2:
|
||||||
|
# add w.strb
|
||||||
|
write_pattern = [(adr, data, 2**(width_from//8)-1) for adr, data in write_pattern]
|
||||||
|
|
||||||
|
if read_pattern is None:
|
||||||
|
read_pattern = []
|
||||||
|
read_expected = []
|
||||||
|
|
||||||
|
class DUT(Module):
|
||||||
|
def __init__(self, width_from, width_to):
|
||||||
|
self.master = AXILiteInterface(data_width=width_from)
|
||||||
|
self.slave = AXILiteInterface(data_width=width_to)
|
||||||
|
self.submodules.converter = AXILiteConverter(self.master, self.slave)
|
||||||
|
|
||||||
|
def generator(axi_lite):
|
||||||
|
for addr, data, strb in write_pattern or []:
|
||||||
|
resp = (yield from axi_lite.write(addr, data, strb))
|
||||||
|
self.assertEqual(resp, RESP_OKAY)
|
||||||
|
for _ in range(16):
|
||||||
|
yield
|
||||||
|
|
||||||
|
for addr, refdata in read_pattern or []:
|
||||||
|
data, resp = (yield from axi_lite.read(addr))
|
||||||
|
self.assertEqual(resp, RESP_OKAY)
|
||||||
|
self.assertEqual(data, refdata)
|
||||||
|
for _ in range(4):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def rdata_generator(adr):
|
||||||
|
for a, v in read_expected:
|
||||||
|
if a == adr:
|
||||||
|
return v
|
||||||
|
return 0xbaadc0de
|
||||||
|
|
||||||
|
_latency = 0
|
||||||
|
def latency():
|
||||||
|
nonlocal _latency
|
||||||
|
_latency = (_latency + 1) % 3
|
||||||
|
return _latency
|
||||||
|
|
||||||
|
dut = DUT(width_from=width_from, width_to=width_to)
|
||||||
|
checker = AXILiteChecker(latency, rdata_generator)
|
||||||
|
run_simulation(dut, [generator(dut.master), checker.handler(dut.slave)], vcd_name='sim.vcd')
|
||||||
|
self.assertEqual(checker.writes, write_expected)
|
||||||
|
self.assertEqual(checker.reads, read_expected)
|
||||||
|
|
||||||
|
def test_axilite_down_converter_32to16(self):
|
||||||
|
write_pattern = [
|
||||||
|
(0x00000000, 0x22221111),
|
||||||
|
(0x00000004, 0x44443333),
|
||||||
|
(0x00000008, 0x66665555),
|
||||||
|
(0x00000100, 0x88887777),
|
||||||
|
]
|
||||||
|
write_expected = [
|
||||||
|
(0x00000000, 0x1111, 0b11),
|
||||||
|
(0x00000002, 0x2222, 0b11),
|
||||||
|
(0x00000004, 0x3333, 0b11),
|
||||||
|
(0x00000006, 0x4444, 0b11),
|
||||||
|
(0x00000008, 0x5555, 0b11),
|
||||||
|
(0x0000000a, 0x6666, 0b11),
|
||||||
|
(0x00000100, 0x7777, 0b11),
|
||||||
|
(0x00000102, 0x8888, 0b11),
|
||||||
|
]
|
||||||
|
read_pattern = write_pattern
|
||||||
|
read_expected = [(adr, data) for (adr, data, _) in write_expected]
|
||||||
|
self.converter_test(width_from=32, width_to=16,
|
||||||
|
write_pattern=write_pattern, write_expected=write_expected,
|
||||||
|
read_pattern=read_pattern, read_expected=read_expected)
|
||||||
|
|
||||||
|
def test_axilite_down_converter_32to8(self):
|
||||||
|
write_pattern = [
|
||||||
|
(0x00000000, 0x44332211),
|
||||||
|
(0x00000004, 0x88776655),
|
||||||
|
]
|
||||||
|
write_expected = [
|
||||||
|
(0x00000000, 0x11, 0b1),
|
||||||
|
(0x00000001, 0x22, 0b1),
|
||||||
|
(0x00000002, 0x33, 0b1),
|
||||||
|
(0x00000003, 0x44, 0b1),
|
||||||
|
(0x00000004, 0x55, 0b1),
|
||||||
|
(0x00000005, 0x66, 0b1),
|
||||||
|
(0x00000006, 0x77, 0b1),
|
||||||
|
(0x00000007, 0x88, 0b1),
|
||||||
|
]
|
||||||
|
read_pattern = write_pattern
|
||||||
|
read_expected = [(adr, data) for (adr, data, _) in write_expected]
|
||||||
|
self.converter_test(width_from=32, width_to=8,
|
||||||
|
write_pattern=write_pattern, write_expected=write_expected,
|
||||||
|
read_pattern=read_pattern, read_expected=read_expected)
|
||||||
|
|
||||||
|
def test_axilite_down_converter_64to32(self):
|
||||||
|
write_pattern = [
|
||||||
|
(0x00000000, 0x2222222211111111),
|
||||||
|
(0x00000008, 0x4444444433333333),
|
||||||
|
]
|
||||||
|
write_expected = [
|
||||||
|
(0x00000000, 0x11111111, 0b1111),
|
||||||
|
(0x00000004, 0x22222222, 0b1111),
|
||||||
|
(0x00000008, 0x33333333, 0b1111),
|
||||||
|
(0x0000000c, 0x44444444, 0b1111),
|
||||||
|
]
|
||||||
|
read_pattern = write_pattern
|
||||||
|
read_expected = [(adr, data) for (adr, data, _) in write_expected]
|
||||||
|
self.converter_test(width_from=64, width_to=32,
|
||||||
|
write_pattern=write_pattern, write_expected=write_expected,
|
||||||
|
read_pattern=read_pattern, read_expected=read_expected)
|
||||||
|
|
||||||
|
def test_axilite_down_converter_strb(self):
|
||||||
|
write_pattern = [
|
||||||
|
(0x00000000, 0x22221111, 0b1100),
|
||||||
|
(0x00000004, 0x44443333, 0b1111),
|
||||||
|
(0x00000008, 0x66665555, 0b1011),
|
||||||
|
(0x00000100, 0x88887777, 0b0011),
|
||||||
|
]
|
||||||
|
write_expected = [
|
||||||
|
(0x00000002, 0x2222, 0b11),
|
||||||
|
(0x00000004, 0x3333, 0b11),
|
||||||
|
(0x00000006, 0x4444, 0b11),
|
||||||
|
(0x00000008, 0x5555, 0b11),
|
||||||
|
(0x0000000a, 0x6666, 0b10),
|
||||||
|
(0x00000100, 0x7777, 0b11),
|
||||||
|
]
|
||||||
|
self.converter_test(width_from=32, width_to=16,
|
||||||
|
write_pattern=write_pattern, write_expected=write_expected)
|
||||||
|
|
Loading…
Reference in New Issue