diff --git a/liteusb/common.py b/liteusb/common.py new file mode 100644 index 000000000..e1cf05aa1 --- /dev/null +++ b/liteusb/common.py @@ -0,0 +1,58 @@ +from migen.fhdl.std import * +from migen.genlib.fsm import * +from migen.actorlib.fifo import * +from migen.flow.actor import EndpointDescription + +user_layout = EndpointDescription( + [ ("dst", 8), + ("length", 4*8), + ("error", 1), + ("d", 8) + ], + packetized=True +) + +phy_layout = [ + ("d", 8) +] + +class FtdiPipe: + def __init__(self, layout): + self.sink = Sink(layout) + self.source = Source(layout) + +class FtdiTimeout(Module): + def __init__(self, clk_freq, length): + cnt_max = int(clk_freq*length) + width = bits_for(cnt_max) + + self.clear = Signal() + self.done = Signal() + + cnt = Signal(width) + self.sync += \ + If(self.clear, + cnt.eq(0) + ).Elif(~self.done, + cnt.eq(cnt+1) + ) + self.comb += self.done.eq(cnt == cnt_max) + +# +# TB +# +import random + +def randn(max_n): + return random.randint(0, max_n-1) + +class RandRun: + def __init__(self, level=0): + self.run = True + self.level = level + + def do_simulation(self, selfp): + self.run = True + n = randn(100) + if n < self.level: + self.run = False diff --git a/liteusb/core/.keep_me b/liteusb/core/.keep_me deleted file mode 100644 index e69de29bb..000000000 diff --git a/liteusb/core/__init__.py b/liteusb/core/__init__.py new file mode 100644 index 000000000..a382591ce --- /dev/null +++ b/liteusb/core/__init__.py @@ -0,0 +1,4 @@ +from liteusb.ftdi.uart import FtdiUART +from liteusb.ftdi.dma import FtdiDMA +from liteusb.ftdi.com import FtdiCom +from liteusb.ftdi.crc import FtdiCRC32 \ No newline at end of file diff --git a/liteusb/core/com.py b/liteusb/core/com.py new file mode 100644 index 000000000..86e49099a --- /dev/null +++ b/liteusb/core/com.py @@ -0,0 +1,28 @@ +from migen.fhdl.std import * +from migen.flow.actor import * + +from liteusb.ftdi.std import * +from liteusb.ftdi.crossbar import FtdiCrossbar +from liteusb.ftdi.packetizer import FtdiPacketizer +from liteusb.ftdi.depacketizer import FtdiDepacketizer +from liteusb.ftdi.phy import FtdiPHY + +class FtdiCom(Module): + def __init__(self, pads, *ports): + # crossbar + self.submodules.crossbar = FtdiCrossbar(list(ports)) + + # packetizer / depacketizer + self.submodules.packetizer = FtdiPacketizer() + self.submodules.depacketizer = FtdiDepacketizer() + self.comb += [ + self.crossbar.slave.source.connect(self.packetizer.sink), + self.depacketizer.source.connect(self.crossbar.slave.sink) + ] + + # phy + self.submodules.phy = FtdiPHY(pads) + self.comb += [ + self.packetizer.source.connect(self.phy.sink), + self.phy.source.connect(self.depacketizer.sink) + ] diff --git a/liteusb/core/crc.py b/liteusb/core/crc.py new file mode 100644 index 000000000..df34837c9 --- /dev/null +++ b/liteusb/core/crc.py @@ -0,0 +1,298 @@ + +from collections import OrderedDict +from migen.fhdl.std import * +from migen.genlib.fsm import FSM, NextState +from migen.genlib.record import * +from migen.genlib.misc import chooser, optree +from migen.flow.actor import Sink, Source +from migen.actorlib.fifo import SyncFIFO + +from liteusb.ftdi.std import * + +class CRCEngine(Module): + """Cyclic Redundancy Check Engine + + Compute next CRC value from last CRC value and data input using + an optimized asynchronous LFSR. + + Parameters + ---------- + dat_width : int + Width of the data bus. + width : int + Width of the CRC. + polynom : int + Polynom of the CRC (ex: 0x04C11DB7 for IEEE 802.3 CRC) + + Attributes + ---------- + d : in + Data input. + last : in + last CRC value. + next : + next CRC value. + """ + def __init__(self, dat_width, width, polynom): + self.d = Signal(dat_width) + self.last = Signal(width) + self.next = Signal(width) + + ### + + def _optimize_eq(l): + """ + Replace even numbers of XORs in the equation + with an equivalent XOR + """ + d = OrderedDict() + for e in l: + if e in d: + d[e] += 1 + else: + d[e] = 1 + r = [] + for key, value in d.items(): + if value%2 != 0: + r.append(key) + return r + + # compute and optimize CRC's LFSR + curval = [[("state", i)] for i in range(width)] + for i in range(dat_width): + feedback = curval.pop() + [("din", i)] + for j in range(width-1): + if (polynom & (1<<(j+1))): + curval[j] += feedback + curval[j] = _optimize_eq(curval[j]) + curval.insert(0, feedback) + + # implement logic + for i in range(width): + xors = [] + for t, n in curval[i]: + if t == "state": + xors += [self.last[n]] + elif t == "din": + xors += [self.d[n]] + self.comb += self.next[i].eq(optree("^", xors)) + +@DecorateModule(InsertReset) +@DecorateModule(InsertCE) +class CRC32(Module): + """IEEE 802.3 CRC + + Implement an IEEE 802.3 CRC generator/checker. + + Parameters + ---------- + dat_width : int + Width of the data bus. + + Attributes + ---------- + d : in + Data input. + value : out + CRC value (used for generator). + error : out + CRC error (used for checker). + """ + width = 32 + polynom = 0x04C11DB7 + init = 2**width-1 + check = 0xC704DD7B + def __init__(self, dat_width): + self.d = Signal(dat_width) + self.value = Signal(self.width) + self.error = Signal() + + ### + + self.submodules.engine = CRCEngine(dat_width, self.width, self.polynom) + reg = Signal(self.width, reset=self.init) + self.sync += reg.eq(self.engine.next) + self.comb += [ + self.engine.d.eq(self.d), + self.engine.last.eq(reg), + + self.value.eq(~reg[::-1]), + self.error.eq(self.engine.next != self.check) + ] + +class CRCInserter(Module): + """CRC Inserter + + Append a CRC at the end of each packet. + + Parameters + ---------- + layout : layout + Layout of the dataflow. + + Attributes + ---------- + sink : in + Packets input without CRC. + source : out + Packets output with CRC. + """ + def __init__(self, crc_class, layout): + self.sink = sink = Sink(layout) + self.source = source = Source(layout) + self.busy = Signal() + + ### + + dw = flen(sink.d) + crc = crc_class(dw) + fsm = FSM(reset_state="IDLE") + self.submodules += crc, fsm + + fsm.act("IDLE", + crc.reset.eq(1), + sink.ack.eq(1), + If(sink.stb & sink.sop, + sink.ack.eq(0), + NextState("COPY"), + ) + ) + fsm.act("COPY", + crc.ce.eq(sink.stb & source.ack), + crc.d.eq(sink.d), + Record.connect(sink, source), + source.eop.eq(0), + If(sink.stb & sink.eop & source.ack, + NextState("INSERT"), + ) + ) + ratio = crc.width//dw + if ratio > 1: + cnt = Signal(max=ratio, reset=ratio-1) + cnt_done = Signal() + fsm.act("INSERT", + source.stb.eq(1), + chooser(crc.value, cnt, source.d, reverse=True), + If(cnt_done, + source.eop.eq(1), + If(source.ack, NextState("IDLE")) + ) + ) + self.comb += cnt_done.eq(cnt == 0) + self.sync += \ + If(fsm.ongoing("IDLE"), + cnt.eq(cnt.reset) + ).Elif(fsm.ongoing("INSERT") & ~cnt_done, + cnt.eq(cnt - source.ack) + ) + else: + fsm.act("INSERT", + source.stb.eq(1), + source.eop.eq(1), + source.d.eq(crc.value), + If(source.ack, NextState("IDLE")) + ) + self.comb += self.busy.eq(~fsm.ongoing("IDLE")) + +class CRC32Inserter(CRCInserter): + def __init__(self, layout): + CRCInserter.__init__(self, CRC32, layout) + +class CRCChecker(Module): + """CRC Checker + + Check CRC at the end of each packet. + + Parameters + ---------- + layout : layout + Layout of the dataflow. + + Attributes + ---------- + sink : in + Packets input with CRC. + source : out + Packets output without CRC and "error" set to 0 + on eop when CRC OK / set to 1 when CRC KO. + """ + def __init__(self, crc_class, layout): + self.sink = sink = Sink(layout) + self.source = source = Source(layout) + self.busy = Signal() + + ### + + dw = flen(sink.d) + crc = crc_class(dw) + self.submodules += crc + ratio = crc.width//dw + + error = Signal() + fifo = InsertReset(SyncFIFO(layout, ratio + 1)) + self.submodules += fifo + + fsm = FSM(reset_state="RESET") + self.submodules += fsm + + fifo_in = Signal() + fifo_out = Signal() + fifo_full = Signal() + + self.comb += [ + fifo_full.eq(fifo.fifo.level == ratio), + fifo_in.eq(sink.stb & (~fifo_full | fifo_out)), + fifo_out.eq(source.stb & source.ack), + + Record.connect(sink, fifo.sink), + fifo.sink.stb.eq(fifo_in), + self.sink.ack.eq(fifo_in), + + source.stb.eq(sink.stb & fifo_full), + source.sop.eq(fifo.source.sop), + source.eop.eq(sink.eop), + fifo.source.ack.eq(fifo_out), + source.payload.eq(fifo.source.payload), + + source.error.eq(sink.error | crc.error), + ] + + fsm.act("RESET", + crc.reset.eq(1), + fifo.reset.eq(1), + NextState("IDLE"), + ) + fsm.act("IDLE", + crc.d.eq(sink.d), + If(sink.stb & sink.sop & sink.ack, + crc.ce.eq(1), + NextState("COPY") + ) + ) + fsm.act("COPY", + crc.d.eq(sink.d), + If(sink.stb & sink.ack, + crc.ce.eq(1), + If(sink.eop, + NextState("RESET") + ) + ) + ) + self.comb += self.busy.eq(~fsm.ongoing("IDLE")) + +class CRC32Checker(CRCChecker): + def __init__(self, layout): + CRCChecker.__init__(self, CRC32, layout) + +class FtdiCRC32(Module): + def __init__(self, tag): + self.tag = tag + + self.submodules.inserter = CRC32Inserter(user_layout) + self.submodules.checker = CRC32Checker(user_layout) + + self.dma_sink = self.inserter.sink + self.dma_source = self.checker.source + + self.sink = self.checker.sink + self.source = self.inserter.source diff --git a/liteusb/core/depacketizer.py b/liteusb/core/depacketizer.py new file mode 100644 index 000000000..d1b75a915 --- /dev/null +++ b/liteusb/core/depacketizer.py @@ -0,0 +1,153 @@ +from migen.fhdl.std import * +from migen.actorlib.structuring import * +from migen.genlib.fsm import FSM, NextState + +from liteusb.ftdi.std import * + +class FtdiDepacketizer(Module): + def __init__(self, timeout=10): + self.sink = sink = Sink(phy_layout) + self.source = source = Source(user_layout) + + # Packet description + # - preamble : 4 bytes + # - dst : 1 byte + # - length : 4 bytes + # - payload + preamble = Array(Signal(8) for i in range(4)) + + header = [ + # dst + source.dst, + # length + source.length[24:32], + source.length[16:24], + source.length[8:16], + source.length[0:8], + ] + + header_pack = InsertReset(Pack(phy_layout, len(header))) + self.submodules += header_pack + + for i, byte in enumerate(header): + chunk = getattr(header_pack.source.payload, "chunk" + str(i)) + self.comb += byte.eq(chunk.d) + + fsm = FSM() + self.submodules += fsm + + self.comb += preamble[0].eq(sink.d) + for i in range(1, 4): + self.sync += If(sink.stb & sink.ack, + preamble[i].eq(preamble[i-1]) + ) + fsm.act("WAIT_SOP", + If( (preamble[3] == 0x5A) & + (preamble[2] == 0xA5) & + (preamble[1] == 0x5A) & + (preamble[0] == 0xA5) & + sink.stb, + NextState("RECEIVE_HEADER") + ), + sink.ack.eq(1), + header_pack.source.ack.eq(1), + ) + + self.submodules.timeout = FtdiTimeout(60000000, timeout) + self.comb += self.timeout.clear.eq(fsm.ongoing("WAIT_SOP")) + + fsm.act("RECEIVE_HEADER", + header_pack.sink.stb.eq(sink.stb), + header_pack.sink.payload.eq(sink.payload), + If(self.timeout.done, NextState("WAIT_SOP")) + .Elif(header_pack.source.stb, NextState("RECEIVE_PAYLOAD")) + .Else(sink.ack.eq(1)) + ) + + self.comb += header_pack.reset.eq(self.timeout.done) + + sop = Signal() + eop = Signal() + cnt = Signal(32) + + fsm.act("RECEIVE_PAYLOAD", + source.stb.eq(sink.stb), + source.sop.eq(sop), + source.eop.eq(eop), + source.d.eq(sink.d), + sink.ack.eq(source.ack), + If((eop & sink.stb & source.ack) | self.timeout.done, NextState("WAIT_SOP")) + ) + + self.sync += \ + If(fsm.ongoing("WAIT_SOP"), + cnt.eq(0) + ).Elif(source.stb & source.ack, + cnt.eq(cnt + 1) + ) + self.comb += sop.eq(cnt == 0) + self.comb += eop.eq(cnt == source.length - 1) + +# +# TB +# +src_data = [ + 0x5A, 0xA5, 0x5A, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03, + 0x5A, 0xA5, 0x5A, 0xA5, 0x12, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +]*4 + +class DepacketizerSourceModel(Module, Source, RandRun): + def __init__(self, data): + Source.__init__(self, phy_layout) + RandRun.__init__(self, 50) + self.data = data + + self._stb = 0 + self._cnt = 0 + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + + if self.run and not self._stb: + self._stb = 1 + + if selfp.stb and selfp.ack: + self._cnt +=1 + + selfp.stb = self._stb + selfp.d = self.data[self._cnt] + + if self._cnt == len(self.data)-1: + raise StopSimulation + + +class DepacketizerSinkModel(Module, Sink, RandRun): + def __init__(self): + Sink.__init__(self, user_layout, True) + RandRun.__init__(self, 50) + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + if self.run: + selfp.ack = 1 + else: + selfp.ack = 0 + + +class TB(Module): + def __init__(self): + self.submodules.source = DepacketizerSourceModel(src_data) + self.submodules.dut = FtdiDepacketizer() + self.submodules.sink = DepacketizerSinkModel() + + self.comb += [ + self.source.connect(self.dut.sink), + self.dut.source.connect(self.sink), + ] + +def main(): + from migen.sim.generic import run_simulation + run_simulation(TB(), ncycles=400, vcd_name="tb_depacketizer.vcd") + +if __name__ == "__main__": + main() diff --git a/liteusb/core/packetizer.py b/liteusb/core/packetizer.py new file mode 100644 index 000000000..bba3ab88c --- /dev/null +++ b/liteusb/core/packetizer.py @@ -0,0 +1,148 @@ +from migen.fhdl.std import * +from migen.actorlib.structuring import * +from migen.genlib.fsm import FSM, NextState + +from liteusb.ftdi.std import * + +class FtdiPacketizer(Module): + def __init__(self): + self.sink = sink = Sink(user_layout) + self.source = source = Source(phy_layout) + + # Packet description + # - preamble : 4 bytes + # - dst : 1 byte + # - length : 4 bytes + # - payload + header = [ + # preamble + 0x5A, + 0xA5, + 0x5A, + 0xA5, + # dst + sink.dst, + # length + sink.length[24:32], + sink.length[16:24], + sink.length[8:16], + sink.length[0:8], + ] + + header_unpack = Unpack(len(header), phy_layout) + self.submodules += header_unpack + + for i, byte in enumerate(header): + chunk = getattr(header_unpack.sink.payload, "chunk" + str(i)) + self.comb += chunk.d.eq(byte) + + fsm = FSM() + self.submodules += fsm + + fsm.act("WAIT_SOP", + If(sink.stb & sink.sop, NextState("SEND_HEADER")) + ) + + fsm.act("SEND_HEADER", + header_unpack.sink.stb.eq(1), + source.stb.eq(1), + source.d.eq(header_unpack.source.d), + header_unpack.source.ack.eq(source.ack), + If(header_unpack.sink.ack, NextState("SEND_DATA")) + ) + + fsm.act("SEND_DATA", + source.stb.eq(sink.stb), + source.d.eq(sink.d), + sink.ack.eq(source.ack), + If(source.ack & sink.eop, NextState("WAIT_SOP")) + ) + +# +# TB +# +src_data = [ + (0x01, 4, + [0x0, 0x1, 0x2, 0x3] + ), + (0x16, 8, + [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7] + ), + (0x22, 16, + [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF] + ), +] + +class PacketizerSourceModel(Module, Source, RandRun): + def __init__(self, data): + Source.__init__(self, user_layout, True) + RandRun.__init__(self, 25) + self.data = data + + self._stb = 0 + self._sop = 0 + self._eop = 0 + self._frame_cnt = 0 + self._payload_cnt = 0 + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + dst, length, payload = self.data[self._frame_cnt] + + if selfp.stb and selfp.ack: + if self._payload_cnt == length-1: + self._frame_cnt += 1 + self._payload_cnt = 0 + else: + self._payload_cnt += 1 + if self.run: + self._stb = 1 + else: + self._stb = 0 + + if self.run and not self._stb: + self._stb = 1 + + self._sop = int((self._payload_cnt == 0)) + self._eop = int((self._payload_cnt == length-1)) + + selfp.stb = self._stb + selfp.sop = self._sop & self._stb + selfp.eop = self._eop & self._stb + selfp.dst = dst + selfp.length = length + selfp.d = payload[self._payload_cnt] + + if self._frame_cnt == len(self.data): + raise StopSimulation + +class PacketizerSinkModel(Module, Sink, RandRun): + def __init__(self): + Sink.__init__(self, phy_layout) + RandRun.__init__(self, 25) + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + if self.run: + selfp.ack = 1 + else: + selfp.ack = 0 + + +class TB(Module): + def __init__(self): + self.submodules.source = PacketizerSourceModel(src_data) + self.submodules.dut = FtdiPacketizer() + self.submodules.sink = PacketizerSinkModel() + + self.comb +=[ + self.source.connect(self.dut.sink), + self.dut.source.connect(self.sink), + ] + +def main(): + from migen.sim.generic import run_simulation + run_simulation(TB(), ncycles=400, vcd_name="tb_packetizer.vcd") + +if __name__ == "__main__": + main() diff --git a/liteusb/frontend/.keep_me b/liteusb/frontend/.keep_me deleted file mode 100644 index e69de29bb..000000000 diff --git a/liteusb/frontend/crossbar.py b/liteusb/frontend/crossbar.py new file mode 100644 index 000000000..d1771c049 --- /dev/null +++ b/liteusb/frontend/crossbar.py @@ -0,0 +1,35 @@ +from migen.fhdl.std import * +from migen.genlib.roundrobin import * +from migen.genlib.record import Record + +from liteusb.ftdi.std import * + +class FtdiCrossbar(Module): + def __init__(self, masters, slave=None): + if slave is None: + slave = FtdiPipe(user_layout) + self.slave = slave + + # masters --> slave arbitration + self.submodules.rr = RoundRobin(len(masters)) + cases = {} + for i, m in enumerate(masters): + sop = Signal() + eop = Signal() + ongoing = Signal() + self.comb += [ + sop.eq(m.source.stb & m.source.sop), + eop.eq(m.source.stb & m.source.eop & m.source.ack), + ] + self.sync += ongoing.eq((sop | ongoing) & ~eop) + self.comb += self.rr.request[i].eq(sop | ongoing) + + cases[i] = [Record.connect(masters[i].source, slave.source)] + self.comb += Case(self.rr.grant, cases) + + # slave --> master demux + cases = {} + for i, m in enumerate(masters): + cases[m.tag] = [Record.connect(slave.sink, masters[i].sink)] + cases["default"] = [slave.sink.ack.eq(1)] + self.comb += Case(slave.sink.dst, cases) diff --git a/liteusb/frontend/dma.py b/liteusb/frontend/dma.py new file mode 100644 index 000000000..a8e1ef99e --- /dev/null +++ b/liteusb/frontend/dma.py @@ -0,0 +1,99 @@ +from migen.fhdl.std import * +from migen.flow.actor import * +from migen.flow.network import * +from migen.actorlib import dma_lasmi, structuring, spi +from migen.bank.description import * +from migen.bank.eventmanager import * +from migen.genlib.record import Record + +from liteusb.ftdi.std import * + +class FtdiDMAWriter(Module, AutoCSR): + def __init__(self, lasmim): + self.sink = sink = Sink(user_layout) + + # Pack data + pack_factor = lasmim.dw//8 + pack = structuring.Pack(phy_layout, pack_factor, reverse=True) + cast = structuring.Cast(pack.source.payload.layout, lasmim.dw) + + # DMA + writer = dma_lasmi.Writer(lasmim) + self._reset = CSR() + self.dma = InsertReset(spi.DMAWriteController(writer, mode=spi.MODE_SINGLE_SHOT)) + self.comb += self.dma.reset.eq(self._reset.r & self._reset.re) + + # Remove sop/eop/length/dst fields from payload + self.comb += [ + pack.sink.stb.eq(sink.stb), + pack.sink.payload.eq(sink.payload), + sink.ack.eq(pack.sink.ack) + ] + + # Graph + g = DataFlowGraph() + g.add_pipeline(pack, cast, self.dma) + self.submodules += CompositeActor(g) + + # IRQ + self.submodules.ev = EventManager() + self.ev.done = EventSourcePulse() + self.ev.finalize() + self.comb += self.ev.done.trigger.eq(sink.stb & sink.eop) + + # CRC + self._crc_failed = CSRStatus() + self.sync += \ + If(sink.stb & sink.eop, + self._crc_failed.status.eq(sink.error) + ) + +class FtdiDMAReader(Module, AutoCSR): + def __init__(self, lasmim, tag): + self.source = source = Source(user_layout) + + reader = dma_lasmi.Reader(lasmim) + self.dma = spi.DMAReadController(reader, mode=spi.MODE_SINGLE_SHOT) + + pack_factor = lasmim.dw//8 + packed_dat = structuring.pack_layout(8, pack_factor) + cast = structuring.Cast(lasmim.dw, packed_dat) + unpack = structuring.Unpack(pack_factor, phy_layout, reverse=True) + + # Graph + cnt = Signal(32) + self.sync += \ + If(self.dma.generator._r_shoot.re, + cnt.eq(0) + ).Elif(source.stb & source.ack, + cnt.eq(cnt + 1) + ) + g = DataFlowGraph() + g.add_pipeline(self.dma, cast, unpack) + self.submodules += CompositeActor(g) + self.comb += [ + source.stb.eq(unpack.source.stb), + source.sop.eq(cnt == 0), + source.eop.eq(cnt == (self.dma.length*pack_factor-1)), + source.length.eq(self.dma.length*pack_factor+4), + source.d.eq(unpack.source.d), + source.dst.eq(tag), + unpack.source.ack.eq(source.ack) + ] + + # IRQ + self.submodules.ev = EventManager() + self.ev.done = EventSourcePulse() + self.ev.finalize() + self.comb += self.ev.done.trigger.eq(source.stb & source.eop) + +class FtdiDMA(Module, AutoCSR): + def __init__(self, lasmim_ftdi_dma_wr, lasmim_ftdi_dma_rd, tag): + self.tag = tag + + self.submodules.writer = FtdiDMAWriter(lasmim_ftdi_dma_wr) + self.submodules.reader = FtdiDMAReader(lasmim_ftdi_dma_rd, self.tag) + self.submodules.ev = SharedIRQ(self.writer.ev, self.reader.ev) + + self.sink = self.writer.sink + self.source = self.reader.source diff --git a/liteusb/frontend/uart.py b/liteusb/frontend/uart.py new file mode 100644 index 000000000..64833f4ce --- /dev/null +++ b/liteusb/frontend/uart.py @@ -0,0 +1,57 @@ +from migen.fhdl.std import * +from migen.bank.description import * +from migen.bank.eventmanager import * +from migen.genlib.fifo import SyncFIFOBuffered + +from liteusb.ftdi.std import * + +class FtdiUART(Module, AutoCSR): + def __init__(self, tag, fifo_depth=64): + self.tag = tag + + self._rxtx = CSR(8) + + self.submodules.ev = EventManager() + self.ev.tx = EventSourcePulse() + self.ev.rx = EventSourceLevel() + self.ev.finalize() + + self.source = source = Source(user_layout) + self.sink = sink = Sink(user_layout) + + ### + + # TX + tx_start = self._rxtx.re + tx_done = self.ev.tx.trigger + + self.sync += \ + If(tx_start, + source.stb.eq(1), + source.d.eq(self._rxtx.r), + ).Elif(tx_done, + source.stb.eq(0) + ) + + self.comb += [ + source.sop.eq(1), + source.eop.eq(1), + source.length.eq(1), + source.dst.eq(self.tag), + tx_done.eq(source.stb & source.ack), + ] + + # RX + rx_available = self.ev.rx.trigger + + rx_fifo = SyncFIFOBuffered(8, fifo_depth) + self.submodules += rx_fifo + self.comb += [ + rx_fifo.we.eq(sink.stb), + sink.ack.eq(sink.stb & rx_fifo.writable), + rx_fifo.din.eq(sink.d), + + rx_available.eq(rx_fifo.readable), + rx_fifo.re.eq(self.ev.rx.clear), + self._rxtx.w.eq(rx_fifo.dout) + ] diff --git a/liteusb/phy/.keep_me b/liteusb/phy/.keep_me deleted file mode 100644 index e69de29bb..000000000 diff --git a/liteusb/phy/ft2232h.py b/liteusb/phy/ft2232h.py new file mode 100644 index 000000000..6f5461b0a --- /dev/null +++ b/liteusb/phy/ft2232h.py @@ -0,0 +1,307 @@ +from migen.fhdl.std import * +from migen.flow.actor import * +from migen.actorlib.fifo import AsyncFIFO +from migen.fhdl.specials import * + +from liteusb.ftdi.std import * + +class FtdiPHY(Module): + def __init__(self, pads, fifo_depth=32, read_time=16, write_time=16): + dw = flen(pads.data) + + # + # Read / Write Fifos + # + + # Read Fifo (Ftdi --> SoC) + read_fifo = RenameClockDomains(AsyncFIFO(phy_layout, fifo_depth), + {"write":"ftdi", "read":"sys"}) + read_buffer = RenameClockDomains(SyncFIFO(phy_layout, 4), + {"sys":"ftdi"}) + self.comb += read_buffer.source.connect(read_fifo.sink) + + # Write Fifo (SoC --> Ftdi) + write_fifo = RenameClockDomains(AsyncFIFO(phy_layout, fifo_depth), + {"write":"sys", "read":"ftdi"}) + + self.submodules += read_fifo, read_buffer, write_fifo + + # + # Sink / Source interfaces + # + self.sink = write_fifo.sink + self.source = read_fifo.source + + # + # Read / Write Arbitration + # + wants_write = Signal() + wants_read = Signal() + + txe_n = Signal() + rxf_n = Signal() + + self.comb += [ + txe_n.eq(pads.txe_n), + rxf_n.eq(pads.rxf_n), + wants_write.eq(~txe_n & write_fifo.source.stb), + wants_read.eq(~rxf_n & read_fifo.sink.ack), + ] + + def anti_starvation(timeout): + en = Signal() + max_time = Signal() + if timeout: + t = timeout - 1 + time = Signal(max=t+1) + self.comb += max_time.eq(time == 0) + self.sync += If(~en, + time.eq(t) + ).Elif(~max_time, + time.eq(time - 1) + ) + else: + self.comb += max_time.eq(0) + return en, max_time + + read_time_en, max_read_time = anti_starvation(read_time) + write_time_en, max_write_time = anti_starvation(write_time) + + data_w_accepted = Signal(reset=1) + + fsm = FSM() + self.submodules += RenameClockDomains(fsm, {"sys": "ftdi"}) + + fsm.act("READ", + read_time_en.eq(1), + If(wants_write, + If(~wants_read | max_read_time, NextState("RTW")) + ) + ) + fsm.act("RTW", + NextState("WRITE") + ) + fsm.act("WRITE", + write_time_en.eq(1), + If(wants_read, + If(~wants_write | max_write_time, NextState("WTR")) + ), + write_fifo.source.ack.eq(wants_write & data_w_accepted) + ) + fsm.act("WTR", + NextState("READ") + ) + + # + # Read / Write Actions + # + + data_w = Signal(dw) + data_r = Signal(dw) + data_oe = Signal() + + pads.oe_n.reset = 1 + pads.rd_n.reset = 1 + pads.wr_n.reset = 1 + + self.sync.ftdi += [ + If(fsm.ongoing("READ"), + data_oe.eq(0), + + pads.oe_n.eq(0), + pads.rd_n.eq(~wants_read), + pads.wr_n.eq(1) + + ).Elif(fsm.ongoing("WRITE"), + data_oe.eq(1), + + pads.oe_n.eq(1), + pads.rd_n.eq(1), + pads.wr_n.eq(~wants_write), + + data_w_accepted.eq(~txe_n) + + ).Else( + data_oe.eq(1), + + pads.oe_n.eq(~fsm.ongoing("WTR")), + pads.rd_n.eq(1), + pads.wr_n.eq(1) + ), + read_buffer.sink.stb.eq(~pads.rd_n & ~rxf_n), + read_buffer.sink.d.eq(data_r), + If(~txe_n & data_w_accepted, + data_w.eq(write_fifo.source.d) + ) + ] + + # + # Databus Tristate + # + self.specials += Tristate(pads.data, data_w, data_oe, data_r) + + self.debug = Signal(8) + self.comb += self.debug.eq(data_r) + + +# +# TB +# +class FtdiModel(Module, RandRun): + def __init__(self, rd_data): + RandRun.__init__(self, 50) + self.rd_data = [0] + rd_data + self.rd_idx = 0 + + # pads + self.data = Signal(8) + self.rxf_n = Signal(reset=1) + self.txe_n = Signal(reset=1) + self.rd_n = Signal(reset=1) + self.wr_n = Signal(reset=1) + self.oe_n = Signal(reset=1) + self.siwua = Signal() + self.pwren_n = Signal(reset=1) + + self.init = True + self.wr_data = [] + self.wait_wr_n = False + self.rd_done = 0 + + def wr_sim(self, selfp): + if not selfp.wr_n and not selfp.txe_n: + self.wr_data.append(selfp.data) + self.wait_wr_n = False + + if not self.wait_wr_n: + if self.run: + selfp.txe_n = 1 + else: + if selfp.txe_n: + self.wait_wr_n = True + selfp.txe_n = 0 + + def rd_sim(self, selfp): + rxf_n = selfp.rxf_n + if self.run: + if self.rd_idx < len(self.rd_data)-1: + self.rd_done = selfp.rxf_n + selfp.rxf_n = 0 + else: + selfp.rxf_n = self.rd_done + else: + selfp.rxf_n = self.rd_done + + if not selfp.rd_n and not selfp.oe_n: + if self.rd_idx < len(self.rd_data)-1: + self.rd_idx += not rxf_n + selfp.data = self.rd_data[self.rd_idx] + self.rd_done = 1 + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + if self.init: + selfp.rxf_n = 0 + self.wr_data = [] + self.init = False + self.wr_sim(selfp) + self.rd_sim(selfp) + +class UserModel(Module, RandRun): + def __init__(self, wr_data): + RandRun.__init__(self, 50) + self.wr_data = wr_data + self.wr_data_idx = 0 + + self.sink = Sink(phy_layout) + self.source = Source(phy_layout) + + self.rd_data = [] + + def wr_sim(self, selfp): + auth = True + if selfp.source.stb and not selfp.source.ack: + auth = False + if auth: + if self.wr_data_idx < len(self.wr_data): + if self.run: + selfp.source.d = self.wr_data[self.wr_data_idx] + selfp.source.stb = 1 + self.wr_data_idx += 1 + else: + selfp.source.stb = 0 + else: + self.source.stb = 0 + + def rd_sim(self, selfp): + if self.run: + selfp.sink.ack = 1 + else: + selfp.sink.ack = 0 + if selfp.sink.stb & selfp.sink.ack: + self.rd_data.append(selfp.sink.d) + + def do_simulation(self, selfp): + RandRun.do_simulation(self, selfp) + self.wr_sim(selfp) + self.rd_sim(selfp) + + +LENGTH = 512 +model_rd_data = [i%256 for i in range(LENGTH)][::-1] +user_wr_data = [i%256 for i in range(LENGTH)] + +class TB(Module): + def __init__(self): + self.submodules.model = FtdiModel(model_rd_data) + self.submodules.phy = FtdiPHY(self.model) + + self.submodules.user = UserModel(user_wr_data) + + self.comb += [ + self.user.source.connect(self.phy.sink), + self.phy.source.connect(self.user.sink) + ] + + # Use sys_clk as ftdi_clk in simulation + self.comb += [ + ClockSignal("ftdi").eq(ClockSignal()), + ResetSignal("ftdi").eq(ResetSignal()) + ] + +def print_results(s, l1, l2): + def comp(l1, l2): + r = True + try: + for i, val in enumerate(l1): + if val != l2[i]: + print(s + " : val : %02X, exp : %02X" %(val, l2[i])) + r = False + except: + return r + return r + + c = comp(l1, l2) + r = s + " " + if c: + r += "[OK]" + else: + r += "[KO]" + print(r) + +def main(): + from migen.sim.generic import run_simulation + tb = TB() + run_simulation(tb, ncycles=8000, vcd_name="tb_phy.vcd") + + ### + #print(tb.user.rd_data) + #print(tb.model.wr_data) + #print(len(tb.user.rd_data)) + #print(len(tb.model.wr_data)) + + print_results("FtdiModel --> UserModel", model_rd_data, tb.user.rd_data) + print_results("UserModel --> FtdiModel", user_wr_data, tb.model.wr_data) + +if __name__ == "__main__": + main()