frontend/axi: add burst support (fixed/incr)

This commit is contained in:
Florent Kermarrec 2018-08-27 16:21:12 +02:00
parent 3fa77c8417
commit c846b8b1c7
2 changed files with 222 additions and 82 deletions

View File

@ -6,11 +6,20 @@ from migen.genlib.roundrobin import *
from litex.soc.interconnect import stream from litex.soc.interconnect import stream
burst_types = {
"fixed": 0b00,
"incr": 0b01,
"wrap": 0b10, # FIXME: Not implemented
"reserved": 0b11
}
def aw_description(address_width, id_width): def ax_description(address_width, id_width):
return [ return [
("addr", address_width), ("addr", address_width),
("id", id_width) ("burst", 2), # burst type
("len", 8), # number of data transfers (up to 256)
("size", 4), # number of bytes of each data transfer (up to 1024 bits)
("id", id_width)
] ]
def w_description(data_width): def w_description(data_width):
@ -24,12 +33,6 @@ def b_description(id_width):
("id", id_width) ("id", id_width)
] ]
def ar_description(address_width, id_width):
return [
("addr", address_width),
("id", id_width)
]
def r_description(data_width, id_width): def r_description(data_width, id_width):
return [ return [
("data", data_width), ("data", data_width),
@ -44,13 +47,64 @@ class LiteDRAMAXIPort(Record):
self.id_width = id_width self.id_width = id_width
self.clock_domain = clock_domain self.clock_domain = clock_domain
self.aw = stream.Endpoint(aw_description(address_width, id_width)) self.aw = stream.Endpoint(ax_description(address_width, id_width))
self.w = stream.Endpoint(w_description(data_width)) self.w = stream.Endpoint(w_description(data_width))
self.b = stream.Endpoint(b_description(id_width)) self.b = stream.Endpoint(b_description(id_width))
self.ar = stream.Endpoint(ar_description(address_width, id_width)) self.ar = stream.Endpoint(ax_description(address_width, id_width))
self.r = stream.Endpoint(r_description(data_width, id_width)) self.r = stream.Endpoint(r_description(data_width, id_width))
class LiteDRAMAXIBurst2Beat(Module):
def __init__(self, ax_burst, ax_beat):
# # #
count = Signal(8)
size = Signal(8+4)
offset = Signal(8+4)
# convert burst size to bytes
cases = {}
for i in range(11):
cases[i] = size.eq(2**i)
self.comb += Case(ax_burst.size, cases)
# fsm
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
ax_beat.valid.eq(ax_burst.valid),
ax_beat.addr.eq(ax_burst.addr),
ax_beat.id.eq(ax_burst.id),
If(ax_beat.valid & ax_beat.ready,
If(ax_burst.len != 0,
NextValue(count, 0),
NextValue(offset, size),
NextState("BURST2BEAT")
).Else(
ax_burst.ready.eq(1)
)
)
)
fsm.act("BURST2BEAT",
ax_beat.valid.eq(1),
If(ax_burst.burst == burst_types["incr"],
ax_beat.addr.eq(ax_burst.addr + offset)
).Else(
ax_beat.addr.eq(ax_burst.addr)
),
ax_beat.id.eq(ax_burst.id),
If(ax_beat.valid & ax_beat.ready,
If(count == (ax_burst.len - 1),
ax_burst.ready.eq(1),
NextState("IDLE")
).Else(
NextValue(count, count + 1),
NextValue(offset, offset + size)
)
)
)
class LiteDRAMAXI2Native(Module): class LiteDRAMAXI2Native(Module):
def __init__(self, axi, port, w_buffer_depth=8, r_buffer_depth=8): def __init__(self, axi, port, w_buffer_depth=8, r_buffer_depth=8):
@ -61,6 +115,13 @@ class LiteDRAMAXI2Native(Module):
can_write = Signal() can_write = Signal()
can_read = Signal() can_read = Signal()
# Burst to beat
aw = stream.Endpoint(ax_description(axi.address_width, axi.id_width))
ar = stream.Endpoint(ax_description(axi.address_width, axi.id_width))
aw_burst2beat = LiteDRAMAXIBurst2Beat(axi.aw, aw)
ar_burst2beat = LiteDRAMAXIBurst2Beat(axi.ar, ar)
self.submodules += aw_burst2beat, ar_burst2beat
# Write / Read buffers # Write / Read buffers
w_buffer = stream.SyncFIFO(w_description(axi.data_width), w_buffer_depth) w_buffer = stream.SyncFIFO(w_description(axi.data_width), w_buffer_depth)
r_buffer = stream.SyncFIFO(r_description(axi.data_width, axi.id_width), r_buffer_depth) r_buffer = stream.SyncFIFO(r_description(axi.data_width, axi.id_width), r_buffer_depth)
@ -73,8 +134,8 @@ class LiteDRAMAXI2Native(Module):
w_buffer_id = stream.SyncFIFO([("id", axi.id_width)], w_buffer_depth) w_buffer_id = stream.SyncFIFO([("id", axi.id_width)], w_buffer_depth)
self.submodules += w_buffer_id self.submodules += w_buffer_id
self.comb += [ self.comb += [
w_buffer_id.sink.valid.eq(axi.aw.valid & axi.aw.ready), w_buffer_id.sink.valid.eq(aw.valid & aw.ready),
w_buffer_id.sink.id.eq(axi.aw.id), w_buffer_id.sink.id.eq(aw.id),
axi.b.valid.eq(axi.w.valid & axi.w.ready), # FIXME: axi.b always supposed to be ready axi.b.valid.eq(axi.w.valid & axi.w.ready), # FIXME: axi.b always supposed to be ready
axi.b.id.eq(w_buffer_id.source.id), axi.b.id.eq(w_buffer_id.source.id),
w_buffer_id.source.ready.eq(axi.b.valid & axi.b.ready) w_buffer_id.source.ready.eq(axi.b.valid & axi.b.ready)
@ -103,8 +164,8 @@ class LiteDRAMAXI2Native(Module):
r_buffer_id = stream.SyncFIFO([("id", axi.id_width)], r_buffer_depth) r_buffer_id = stream.SyncFIFO([("id", axi.id_width)], r_buffer_depth)
self.submodules += r_buffer_id self.submodules += r_buffer_id
self.comb += [ self.comb += [
r_buffer_id.sink.valid.eq(axi.ar.valid & axi.ar.ready), r_buffer_id.sink.valid.eq(ar.valid & ar.ready),
r_buffer_id.sink.id.eq(axi.ar.id), r_buffer_id.sink.id.eq(ar.id),
axi.r.id.eq(r_buffer_id.source.id), axi.r.id.eq(r_buffer_id.source.id),
r_buffer_id.source.ready.eq(axi.r.valid & axi.r.ready) r_buffer_id.source.ready.eq(axi.r.valid & axi.r.ready)
] ]
@ -113,22 +174,22 @@ class LiteDRAMAXI2Native(Module):
arbiter = RoundRobin(2, SP_CE) arbiter = RoundRobin(2, SP_CE)
self.submodules += arbiter self.submodules += arbiter
self.comb += [ self.comb += [
arbiter.request[0].eq(axi.aw.valid & can_write), arbiter.request[0].eq(aw.valid & can_write),
arbiter.request[1].eq(axi.ar.valid & can_read), arbiter.request[1].eq(ar.valid & can_read),
arbiter.ce.eq(~port.cmd.valid | port.cmd.ready) arbiter.ce.eq(~port.cmd.valid | port.cmd.ready)
] ]
self.comb += [ self.comb += [
If(arbiter.grant, If(arbiter.grant,
port.cmd.valid.eq(axi.ar.valid & can_read), port.cmd.valid.eq(ar.valid & can_read),
axi.ar.ready.eq(port.cmd.ready & can_read), ar.ready.eq(port.cmd.ready & can_read),
port.cmd.we.eq(0), port.cmd.we.eq(0),
port.cmd.adr.eq(axi.ar.addr >> ashift) port.cmd.adr.eq(ar.addr >> ashift)
).Else( ).Else(
port.cmd.valid.eq(axi.aw.valid & can_write), port.cmd.valid.eq(aw.valid & can_write),
axi.aw.ready.eq(port.cmd.ready & can_write), aw.ready.eq(port.cmd.ready & can_write),
port.cmd.we.eq(1), port.cmd.we.eq(1),
port.cmd.adr.eq(axi.aw.addr >> ashift) port.cmd.adr.eq(aw.addr >> ashift)
) )
] ]

View File

@ -9,64 +9,143 @@ from litedram.frontend.axi import *
from litex.gen.sim import * from litex.gen.sim import *
def main_generator(axi_port, dram_port, dut):
prng = random.Random(42)
# axi_port always accepting wresps/rdatas
yield axi_port.b.ready.eq(1)
yield axi_port.r.ready.eq(1)
yield
# test writes
for i in range(16):
# write command
yield axi_port.aw.valid.eq(1)
yield axi_port.aw.addr.eq(i)
while (yield dram_port.cmd.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.cmd.ready.eq(1)
yield
yield axi_port.aw.valid.eq(0)
yield dram_port.cmd.ready.eq(0)
yield
# write data
yield axi_port.w.valid.eq(1)
yield axi_port.w.data.eq(i)
while (yield dram_port.wdata.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.wdata.ready.eq(1)
yield
if (yield axi_port.w.ready) == 1:
yield axi_port.w.valid.eq(0)
yield axi_port.aw.valid.eq(0)
yield dram_port.wdata.ready.eq(0)
yield
# test reads
for i in range(16):
# read command
yield axi_port.ar.valid.eq(1)
yield axi_port.ar.addr.eq(i)
while (yield dram_port.cmd.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.cmd.ready.eq(1)
yield
yield axi_port.ar.valid.eq(0)
yield dram_port.cmd.ready.eq(0)
yield
# read data
yield dram_port.rdata.valid.eq(1)
yield dram_port.rdata.data.eq(i)
while (yield dram_port.rdata.valid) == 0:
if prng.randrange(100) < 20:
yield dram_port.rdata.valid.eq(1)
yield
yield axi_port.ar.valid.eq(0)
yield dram_port.rdata.valid.eq(0)
yield
for i in range(128):
yield
class TestAXI(unittest.TestCase): class TestAXI(unittest.TestCase):
def test(self): def test_axi2native(self):
axi_port = LiteDRAMAXIPort(32, 24, 32) def main_generator(axi_port, dram_port, dut):
dram_port = LiteDRAMNativePort("both", 24, 32) prng = random.Random(42)
# axi_port always accepting wresps/rdatas
yield axi_port.b.ready.eq(1)
yield axi_port.r.ready.eq(1)
yield
# test writes
for i in range(16):
# write command
yield axi_port.aw.valid.eq(1)
yield axi_port.aw.addr.eq(i)
while (yield dram_port.cmd.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.cmd.ready.eq(1)
yield
yield axi_port.aw.valid.eq(0)
yield dram_port.cmd.ready.eq(0)
yield
# write data
yield axi_port.w.valid.eq(1)
yield axi_port.w.data.eq(i)
while (yield dram_port.wdata.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.wdata.ready.eq(1)
yield
if (yield axi_port.w.ready) == 1:
yield axi_port.w.valid.eq(0)
yield axi_port.aw.valid.eq(0)
yield dram_port.wdata.ready.eq(0)
yield
# test reads
for i in range(16):
# read command
yield axi_port.ar.valid.eq(1)
yield axi_port.ar.addr.eq(i)
while (yield dram_port.cmd.ready) == 0:
if prng.randrange(100) < 20:
yield dram_port.cmd.ready.eq(1)
yield
yield axi_port.ar.valid.eq(0)
yield dram_port.cmd.ready.eq(0)
yield
# read data
yield dram_port.rdata.valid.eq(1)
yield dram_port.rdata.data.eq(i)
while (yield dram_port.rdata.valid) == 0:
if prng.randrange(100) < 20:
yield dram_port.rdata.valid.eq(1)
yield
yield axi_port.ar.valid.eq(0)
yield dram_port.rdata.valid.eq(0)
yield
for i in range(128):
yield
axi_port = LiteDRAMAXIPort(32, 32, 32)
dram_port = LiteDRAMNativePort("both", 32, 32)
dut = LiteDRAMAXI2Native(axi_port, dram_port) dut = LiteDRAMAXI2Native(axi_port, dram_port)
run_simulation(dut, main_generator(axi_port, dram_port, dut), vcd_name="axi.vcd") run_simulation(dut, main_generator(axi_port, dram_port, dut), vcd_name="axi2native.vcd")
def test_burst2beat(self):
class Beat:
def __init__(self, addr):
self.addr = addr
class Burst:
def __init__(self, type, addr, len, size):
self.type = type
self.addr = addr
self.len = len
self.size = size
def to_beats(self):
r = []
for i in range(self.len + 1):
if self.type == burst_types["incr"]:
r += [Beat(self.addr + i*2**(self.size))]
else:
r += [Beat(self.addr)]
return r
def bursts_generator(ax, bursts, valid_rand=50):
prng = random.Random(42)
for burst in bursts:
yield ax.valid.eq(1)
yield ax.addr.eq(burst.addr)
yield ax.burst.eq(burst.type)
yield ax.len.eq(burst.len)
yield ax.size.eq(burst.size)
while (yield ax.ready) == 0:
yield
yield ax.valid.eq(0)
while prng.randrange(100) < valid_rand:
yield
yield
@passive
def beats_checker(ax, beats, ready_rand=50):
self.errors = 0
yield ax.ready.eq(0)
prng = random.Random(42)
for beat in beats:
while ((yield ax.valid) and (yield ax.ready)) == 0:
if prng.randrange(100) > ready_rand:
yield ax.ready.eq(1)
else:
yield ax.ready.eq(0)
yield
ax_addr = (yield ax.addr)
if ax_addr != beat.addr:
self.errors += 1
yield
# dut
ax_burst = stream.Endpoint(ax_description(32, 32))
ax_beat = stream.Endpoint(ax_description(32, 32))
dut = LiteDRAMAXIBurst2Beat(ax_burst, ax_beat)
# generate dut input (bursts)
prng = random.Random(42)
bursts = []
for i in range(32):
bursts.append(Burst(burst_types["fixed"], prng.randrange(2**32), prng.randrange(256), log2_int(32//8)))
bursts.append(Burst(burst_types["incr"], prng.randrange(2**32), prng.randrange(256), log2_int(32//8)))
# generate expexted dut output (beats for reference)
beats = []
for burst in bursts:
beats += burst.to_beats()
# simulation
generators = [
bursts_generator(ax_burst, bursts),
beats_checker(ax_beat, beats)
]
run_simulation(dut, generators, vcd_name="burst2beat.vcd")
self.assertEqual(self.errors, 0)