From 512ed2b3d60694998a4d905a9d7c130e257e9981 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Tue, 20 Feb 2018 21:27:51 +0000 Subject: [PATCH 1/2] Preliminary AXI4Lite CSR bridge support This change introduces an AXI4Lite to CSR bridge. Hopefully it will become extended in the future with full AXI support and more structures (Wishbone bridge, interconnect, ...). For now this will do. The bridge has been simulated (and includes an FHDL testbench) and tested in hardware (on a Zynq 7020). --- litex/soc/interconnect/axi.py | 260 ++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 litex/soc/interconnect/axi.py diff --git a/litex/soc/interconnect/axi.py b/litex/soc/interconnect/axi.py new file mode 100644 index 000000000..1ffaa8b2b --- /dev/null +++ b/litex/soc/interconnect/axi.py @@ -0,0 +1,260 @@ +"""AXI4Lite support for LiteX""" + +# Copyright (C) 2018 by Sergiusz Bazanski +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted. + +import math + +from litex.gen import * +from litex.gen.genlib.record import * +from litex.soc.interconnect import csr_bus + +# Layout of AXI4 Lite Bus +_layout = [ + # Write Address + ("awaddr", "address_width", DIR_M_TO_S), + ("awprot", 3, DIR_M_TO_S), + ("awvalid", 1, DIR_M_TO_S), + ("awready", 1, DIR_S_TO_M), + + # Write Data + ("wdata", "data_width", DIR_M_TO_S), + ("wstrb", "strb_width", DIR_M_TO_S), + ("wvalid", 1, DIR_M_TO_S), + ("wready", 1, DIR_S_TO_M), + + # Write Response + ("bresp", 2, DIR_S_TO_M), + ("bvalid", 1, DIR_S_TO_M), + ("bready", 1, DIR_M_TO_S), + + # Read Address + ("araddr", "address_width", DIR_M_TO_S), + ("arprot", 3, DIR_M_TO_S), + ("arvalid", 1, DIR_M_TO_S), + ("arready", 1, DIR_S_TO_M), + + # Read Data + ("rdata", "data_width", DIR_S_TO_M), + ("rresp", 2, DIR_S_TO_M), + ("rvalid", 1, DIR_S_TO_M), + ("rready", 1, DIR_M_TO_S), +] + +class Interface(Record): + """AXI4Lite Bus Interface""" + def __init__(self, data_width=32, address_width=6): + super().__init__(set_layout_parameters(_layout, + data_width=data_width, + address_width=address_width, + strb_width=data_width//8)) + + +class AXILite2CSR(Module): + """ + A bridge between AXI4Lite and a CSR bus. + + This bridge will let you connect an CSR bus to an AXI4 Lite master. Please + bear in mind that CSR is word-addressed but AXI4 is byte-addressed. This + bridge performs translation, so your AXI bus should be at least two bits + wider then your CSR bus. + + The bridge does not support unaligned reads/writes - it will round down + every access to the nearest word. If it tries to access unmapped memory, + it will return whaterver word is currently active on the CSR bus - + including writes. + """ + + def __init__(self, bus_interface_axi, bus_interface_csr): + self.axi = bus_interface_axi + self.csr = bus_interface_csr + + ### + + # Machine is currently busy talking to CSR, hold your horses. + busy = Signal() + + # A write transaction is happening on the bus. + write_transaction = Signal() + # A read transaction is happening on the bus. + read_transaction = Signal() + self.comb += [ + write_transaction.eq(self.axi.awvalid & self.axi.awready & self.axi.wvalid & self.axi.wready), + read_transaction.eq(self.axi.arvalid & self.axi.arready), + ] + + # Write transaction generation. + self.sync += [ + self.axi.awready.eq(0), + self.axi.wready.eq(0), + If(self.axi.awvalid & self.axi.wvalid, + If(~self.axi.awready & ~busy & ~self.axi.arvalid, + self.axi.awready.eq(1), + self.axi.wready.eq(1) + ) + ) + ] + # Write response generation. + self.sync += [ + self.axi.bvalid.eq(0), + If(write_transaction, + If(self.axi.bready & ~self.axi.bvalid, + self.axi.bvalid.eq(1), + # Response 0 -> OKAY + self.axi.bresp.eq(0), + ) + ) + ] + # Read transaction generation. + self.sync += [ + self.axi.arready.eq(0), + If(self.axi.arvalid & ~self.axi.arready & ~busy, + self.axi.arready.eq(1), + ) + ] + + + # Registered data to be written to CSR, set by FSM. + wdata = Signal(self.csr.dat_w.nbits) + # Combinatorial byte address to assert on CSR bus, driven by FSM. + addr = Signal(self.axi.araddr.nbits) + # Drive AXI & CSR combinatorial signals. + self.comb += [ + self.csr.adr.eq(addr >> + int(math.log(self.axi.rdata.nbits//8, 2.0))), + self.csr.dat_w.eq(wdata), + + self.axi.rdata.eq(self.csr.dat_r), + self.axi.rresp.eq(0), + ] + + # CSR interaction FSM. + self.submodules.fsm = FSM(reset_state='IDLE') + self.comb += [ + busy.eq(~self.fsm.ongoing('IDLE')), + self.axi.rvalid.eq(self.fsm.ongoing('READING')), + self.csr.we.eq(self.fsm.ongoing('WRITING')), + ] + + # Idle state - wait for a transaction to happen on AXI. Immediately + # assert read/write address on CSR if such an transaction is occuring. + self.fsm.act('IDLE', + If(read_transaction, + addr.eq(self.axi.araddr), + NextState('READING'), + ).Elif(write_transaction, + addr.eq(self.axi.awaddr), + # Register data from AXI. + NextValue(wdata, self.axi.wdata), + NextState('WRITING'), + ) + ) + + # Perform write to CSR. + self.fsm.act('WRITING', + addr.eq(self.axi.awaddr), + # CSR writes are single cycle, go back to IDLE. + NextState('IDLE'), + ) + + # Respond to read to AXI. + self.fsm.act('READING', + addr.eq(self.axi.araddr), + # If AXI master is ready to receive data, go back to IDLE. + If(self.axi.rready, + NextState('IDLE'), + ) + ) + + +from litex.gen.sim import run_simulation +from litex.soc.interconnect import csr, csr_bus + +def test_axilite2csr(): + class CSRHolder(Module, csr.AutoCSR): + def __init__(self): + self.foo = csr.CSRStorage(32, reset=1) + self.bar = csr.CSRStorage(32, reset=1) + + class Fixture(Module): + def __init__(self): + self.csr = csr_bus.Interface(data_width=32, address_width=12) + self.axi = Interface(data_width=32, address_width=14) + self.submodules.holder = CSRHolder() + self.submodules.dut = AXILite2CSR(self.axi, self.csr) + self.submodules.csrbankarray = csr_bus.CSRBankArray(self, self.map_csr, data_width=32, address_width=12) + self.submodules.csrcon = csr_bus.Interconnect(self.csr, self.csrbankarray.get_buses()) + + def map_csr(self, name, memory): + return { + 'holder': 0, + }[name] + + def testbench_write_read(dut): + for _ in range(8): + yield + + # Write test + yield dut.axi.awvalid.eq(1) + yield dut.axi.awaddr.eq(4) + yield dut.axi.wvalid.eq(1) + yield dut.axi.bready.eq(1) + yield dut.axi.wdata.eq(0x2137) + + while (yield dut.axi.awready) != 1: + yield + while (yield dut.axi.wready) != 1: + yield + yield dut.axi.awvalid.eq(0) + yield dut.axi.wvalid.eq(0) + + for _ in range(8): + yield + + # Read test + yield dut.axi.arvalid.eq(1) + yield dut.axi.rready.eq(1) + yield dut.axi.araddr.eq(4) + + while (yield dut.axi.arready != 1): + yield + yield dut.axi.arvalid.eq(0) + while (yield dut.axi.rvalid != 1): + yield + yield dut.axi.rready.eq(0) + + read = yield dut.axi.rdata + assert read == 0x2137 + + for _ in range(8): + yield + + def testbench_simultaneous(dut): + for _ in range(8): + yield + + # Write + yield dut.axi.awvalid.eq(1) + yield dut.axi.awaddr.eq(2) + yield dut.axi.wvalid.eq(1) + yield dut.axi.bready.eq(1) + yield dut.axi.wdata.eq(0x2137) + # Read + yield dut.axi.arvalid.eq(1) + yield dut.axi.rready.eq(1) + yield dut.axi.araddr.eq(2) + + yield + yield + + is_reading = yield dut.axi.arready + is_writing = yield dut.axi.awready + + assert is_reading + assert not is_writing + + fixture = Fixture() + run_simulation(fixture, testbench_write_read(fixture.dut), vcd_name='axi-write-read.vcd') + fixture = Fixture() + run_simulation(fixture, testbench_simultaneous(fixture.dut), vcd_name='axi-simultaneous.vcd') From 688f26cc323700e0c82ff637bf7aa0c28e048ec1 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 21 Feb 2018 00:00:58 +0000 Subject: [PATCH 2/2] Change AXI interface and tidy code Inspired by parts of https://github.com/peteut/migen-misc/ --- litex/soc/interconnect/axi.py | 189 ++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 86 deletions(-) diff --git a/litex/soc/interconnect/axi.py b/litex/soc/interconnect/axi.py index 1ffaa8b2b..3d3570b7d 100644 --- a/litex/soc/interconnect/axi.py +++ b/litex/soc/interconnect/axi.py @@ -13,33 +13,43 @@ from litex.soc.interconnect import csr_bus # Layout of AXI4 Lite Bus _layout = [ # Write Address - ("awaddr", "address_width", DIR_M_TO_S), - ("awprot", 3, DIR_M_TO_S), - ("awvalid", 1, DIR_M_TO_S), - ("awready", 1, DIR_S_TO_M), + ("aw", [ + ("addr", "address_width", DIR_M_TO_S), + ("prot", 3, DIR_M_TO_S), + ("valid", 1, DIR_M_TO_S), + ("ready", 1, DIR_S_TO_M), + ]), # Write Data - ("wdata", "data_width", DIR_M_TO_S), - ("wstrb", "strb_width", DIR_M_TO_S), - ("wvalid", 1, DIR_M_TO_S), - ("wready", 1, DIR_S_TO_M), + ("w", [ + ("data", "data_width", DIR_M_TO_S), + ("strb", "strb_width", DIR_M_TO_S), + ("valid", 1, DIR_M_TO_S), + ("ready", 1, DIR_S_TO_M), + ]), # Write Response - ("bresp", 2, DIR_S_TO_M), - ("bvalid", 1, DIR_S_TO_M), - ("bready", 1, DIR_M_TO_S), + ("b", [ + ("resp", 2, DIR_S_TO_M), + ("valid", 1, DIR_S_TO_M), + ("ready", 1, DIR_M_TO_S), + ]), # Read Address - ("araddr", "address_width", DIR_M_TO_S), - ("arprot", 3, DIR_M_TO_S), - ("arvalid", 1, DIR_M_TO_S), - ("arready", 1, DIR_S_TO_M), + ("ar", [ + ("addr", "address_width", DIR_M_TO_S), + ("prot", 3, DIR_M_TO_S), + ("valid", 1, DIR_M_TO_S), + ("ready", 1, DIR_S_TO_M), + ]), # Read Data - ("rdata", "data_width", DIR_S_TO_M), - ("rresp", 2, DIR_S_TO_M), - ("rvalid", 1, DIR_S_TO_M), - ("rready", 1, DIR_M_TO_S), + ("r", [ + ("data", "data_width", DIR_S_TO_M), + ("resp", 2, DIR_S_TO_M), + ("valid", 1, DIR_S_TO_M), + ("ready", 1, DIR_M_TO_S), + ]), ] class Interface(Record): @@ -66,12 +76,14 @@ class AXILite2CSR(Module): including writes. """ - def __init__(self, bus_interface_axi, bus_interface_csr): - self.axi = bus_interface_axi - self.csr = bus_interface_csr + def __init__(self, bus_axi, bus_csr): + self.axi = axi = bus_axi + self.csr = csr = bus_csr ### + ar, r, aw, w, b = axi.ar, axi.r, axi.aw, axi.w, axi.b + # Machine is currently busy talking to CSR, hold your horses. busy = Signal() @@ -80,89 +92,88 @@ class AXILite2CSR(Module): # A read transaction is happening on the bus. read_transaction = Signal() self.comb += [ - write_transaction.eq(self.axi.awvalid & self.axi.awready & self.axi.wvalid & self.axi.wready), - read_transaction.eq(self.axi.arvalid & self.axi.arready), + write_transaction.eq(aw.valid & aw.ready & w.valid & w.ready), + read_transaction.eq(ar.valid & ar.ready), ] # Write transaction generation. self.sync += [ - self.axi.awready.eq(0), - self.axi.wready.eq(0), - If(self.axi.awvalid & self.axi.wvalid, - If(~self.axi.awready & ~busy & ~self.axi.arvalid, - self.axi.awready.eq(1), - self.axi.wready.eq(1) + aw.ready.eq(0), + w.ready.eq(0), + If(aw.valid & w.valid, + If(~aw.ready & ~busy & ~ar.valid, + aw.ready.eq(1), + w.ready.eq(1) ) ) ] # Write response generation. self.sync += [ - self.axi.bvalid.eq(0), + b.valid.eq(0), If(write_transaction, - If(self.axi.bready & ~self.axi.bvalid, - self.axi.bvalid.eq(1), + If(b.ready & ~b.valid, + b.valid.eq(1), # Response 0 -> OKAY - self.axi.bresp.eq(0), + b.resp.eq(0), ) ) ] # Read transaction generation. self.sync += [ - self.axi.arready.eq(0), - If(self.axi.arvalid & ~self.axi.arready & ~busy, - self.axi.arready.eq(1), + ar.ready.eq(0), + If(ar.valid & ~ar.ready & ~busy, + ar.ready.eq(1), ) ] # Registered data to be written to CSR, set by FSM. - wdata = Signal(self.csr.dat_w.nbits) + wdata = Signal(csr.dat_w.nbits) # Combinatorial byte address to assert on CSR bus, driven by FSM. - addr = Signal(self.axi.araddr.nbits) + addr = Signal(ar.addr.nbits) # Drive AXI & CSR combinatorial signals. self.comb += [ - self.csr.adr.eq(addr >> - int(math.log(self.axi.rdata.nbits//8, 2.0))), - self.csr.dat_w.eq(wdata), + csr.adr.eq(addr >> int(math.log(r.data.nbits//8, 2.0))), + csr.dat_w.eq(wdata), - self.axi.rdata.eq(self.csr.dat_r), - self.axi.rresp.eq(0), + r.data.eq(csr.dat_r), + r.resp.eq(0), ] # CSR interaction FSM. - self.submodules.fsm = FSM(reset_state='IDLE') + self.submodules.fsm = fsm = FSM(reset_state='IDLE') self.comb += [ - busy.eq(~self.fsm.ongoing('IDLE')), - self.axi.rvalid.eq(self.fsm.ongoing('READING')), - self.csr.we.eq(self.fsm.ongoing('WRITING')), + busy.eq(~fsm.ongoing('IDLE')), + r.valid.eq(fsm.ongoing('READING')), + csr.we.eq(fsm.ongoing('WRITING')), ] # Idle state - wait for a transaction to happen on AXI. Immediately # assert read/write address on CSR if such an transaction is occuring. - self.fsm.act('IDLE', + fsm.act('IDLE', If(read_transaction, - addr.eq(self.axi.araddr), + addr.eq(ar.addr), NextState('READING'), ).Elif(write_transaction, - addr.eq(self.axi.awaddr), + addr.eq(aw.addr), # Register data from AXI. - NextValue(wdata, self.axi.wdata), + NextValue(wdata, w.data), NextState('WRITING'), ) ) # Perform write to CSR. - self.fsm.act('WRITING', - addr.eq(self.axi.awaddr), + fsm.act('WRITING', + addr.eq(aw.addr), # CSR writes are single cycle, go back to IDLE. NextState('IDLE'), ) # Respond to read to AXI. - self.fsm.act('READING', - addr.eq(self.axi.araddr), + fsm.act('READING', + addr.eq(ar.addr), # If AXI master is ready to receive data, go back to IDLE. - If(self.axi.rready, + If(r.ready, NextState('IDLE'), ) ) @@ -183,8 +194,10 @@ def test_axilite2csr(): self.axi = Interface(data_width=32, address_width=14) self.submodules.holder = CSRHolder() self.submodules.dut = AXILite2CSR(self.axi, self.csr) - self.submodules.csrbankarray = csr_bus.CSRBankArray(self, self.map_csr, data_width=32, address_width=12) - self.submodules.csrcon = csr_bus.Interconnect(self.csr, self.csrbankarray.get_buses()) + self.submodules.csrbankarray = csr_bus.CSRBankArray( + self, self.map_csr, data_width=32, address_width=12) + self.submodules.csrcon = csr_bus.Interconnect( + self.csr, self.csrbankarray.get_buses()) def map_csr(self, name, memory): return { @@ -192,64 +205,68 @@ def test_axilite2csr(): }[name] def testbench_write_read(dut): + axi = dut.axi + for _ in range(8): yield # Write test - yield dut.axi.awvalid.eq(1) - yield dut.axi.awaddr.eq(4) - yield dut.axi.wvalid.eq(1) - yield dut.axi.bready.eq(1) - yield dut.axi.wdata.eq(0x2137) + yield axi.aw.valid.eq(1) + yield axi.aw.addr.eq(4) + yield axi.w.valid.eq(1) + yield axi.b.ready.eq(1) + yield axi.w.data.eq(0x2137) - while (yield dut.axi.awready) != 1: + while (yield axi.aw.ready) != 1: yield - while (yield dut.axi.wready) != 1: + while (yield axi.w.ready) != 1: yield - yield dut.axi.awvalid.eq(0) - yield dut.axi.wvalid.eq(0) + yield axi.aw.valid.eq(0) + yield axi.w.valid.eq(0) for _ in range(8): yield # Read test - yield dut.axi.arvalid.eq(1) - yield dut.axi.rready.eq(1) - yield dut.axi.araddr.eq(4) + yield axi.ar.valid.eq(1) + yield axi.r.ready.eq(1) + yield axi.ar.addr.eq(4) - while (yield dut.axi.arready != 1): + while (yield axi.ar.ready != 1): yield - yield dut.axi.arvalid.eq(0) - while (yield dut.axi.rvalid != 1): + yield axi.ar.valid.eq(0) + while (yield axi.r.valid != 1): yield - yield dut.axi.rready.eq(0) + yield axi.r.ready.eq(0) - read = yield dut.axi.rdata + read = yield axi.r.data assert read == 0x2137 for _ in range(8): yield def testbench_simultaneous(dut): + axi = dut.axi + for _ in range(8): yield # Write - yield dut.axi.awvalid.eq(1) - yield dut.axi.awaddr.eq(2) - yield dut.axi.wvalid.eq(1) - yield dut.axi.bready.eq(1) - yield dut.axi.wdata.eq(0x2137) + yield axi.aw.valid.eq(1) + yield axi.aw.addr.eq(2) + yield axi.w.valid.eq(1) + yield axi.b.ready.eq(1) + yield axi.w.data.eq(0x2137) # Read - yield dut.axi.arvalid.eq(1) - yield dut.axi.rready.eq(1) - yield dut.axi.araddr.eq(2) + yield axi.ar.valid.eq(1) + yield axi.r.ready.eq(1) + yield axi.ar.addr.eq(2) yield yield - is_reading = yield dut.axi.arready - is_writing = yield dut.axi.awready + is_reading = yield axi.ar.ready + is_writing = yield axi.aw.ready assert is_reading assert not is_writing