# Copyright 2013-2017 Robert Jordens <jordens@gmail.com>
#
# This file is part of pdq.
#
# pdq is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pdq is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pdq. If not, see <http://www.gnu.org/licenses/>.
from migen import *
from migen.genlib.cdc import MultiReg
from misoc.interconnect.stream import Endpoint
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine
from .ft245r import bus_layout
from .escape import Unescaper
from .spi import SPISlave
mem_layout = [("data", 16)]
[docs]class ResetGen(Module):
"""Reset generator.
Asserts :attr:`reset` for a given number of cycles when triggered.
Args:
n (int): number of cycles.
Attributes:
trigger (Signal): Trigger input.
reset (Signal): Reset output. Active high.
"""
def __init__(self, n=1<<7):
self.trigger = Signal()
self.reset = Signal()
###
self.clock_domains.cd_no_rst = ClockDomain(reset_less=True)
counter = Signal(max=n)
self.comb += [
self.cd_no_rst.clk.eq(ClockSignal()),
self.reset.eq(counter != n - 1)
]
self.sync.no_rst += [
If(self.trigger,
counter.eq(0)
).Elif(self.reset,
counter.eq(counter + 1)
),
]
[docs]class FTDI2SPI(Module):
"""Converts parallel data stream from FTDI chip into framed
SPI-like data.
It uses the :class:`Unescaper` to to detect escaped start-of-frame SOF
and EOF characters.
Attributes:
sink (Endpoint): Raw data from FTDI parallel bus.
source (Endpoint): Framed data stream (eop asserted when there is no
active frame).
"""
def __init__(self):
self.sink = Endpoint(bus_layout)
self.source = Endpoint(bus_layout)
self.source.eop.reset = 1
###
unesc = Unescaper(bus_layout)
self.submodules += unesc
self.sync += [
If(unesc.source1.stb,
Case(unesc.source1.data, {
0x02: self.source.eop.eq(0),
0x03: self.source.eop.eq(1),
}),
)
]
self.comb += [
self.sink.connect(unesc.sink),
self.source.data.eq(unesc.source0.data),
self.source.stb.eq(unesc.source0.stb),
unesc.source0.ack.eq(self.source.ack),
unesc.source1.ack.eq(1),
]
[docs]class Arbiter(Module):
"""Simple arbiter for two framed data streams.
Uses end-of-packet (eop) to detect that :attr:`sink0` is inactive
and yields to :attr:`sink1`.
"""
def __init__(self, width=8):
self.sink0 = Endpoint(bus_layout)
self.sink1 = Endpoint(bus_layout)
self.source = Endpoint(bus_layout)
###
self.comb += [
If(~self.sink0.eop, # has priority
self.sink0.connect(self.source),
).Else(
self.sink1.connect(self.source),
)
]
[docs]class Protocol(Module):
"""Handles the register and memory protocols and
reads/writes data in the channel memories.
Args:
mems (list): List of memories from :mod:`gateware.dac.Dac`.
Attributes:
sink (Endpoint): 8 bit data sink.
source (Endpoint): 8 bit data source for SPI MISO read-back.
board (Signal(4)): Board address.
config (Record): Configuration register.
frame (Signal(max=32)): Selected frame.
"""
def __init__(self, mems):
self.sink = Endpoint(bus_layout)
self.source = Endpoint(bus_layout)
self.board = Signal(4)
# mapped registers
self.config = Record([
("reset", 1),
("clk2x", 1),
("enable", 1),
("trigger", 1),
("aux_miso", 1),
("aux_dac", 3),
])
self.checksum = Signal(8)
self.frame = Signal(max=32)
###
# CRC8-CCIT
crc = LiteEthMACCRCEngine(data_width=8, width=8, polynom=0x07)
self.submodules += crc
self.comb += [
crc.data.eq(self.sink.data[::-1]),
crc.last.eq(self.checksum),
]
self.sync += [
If(self.sink.stb & ~self.sink.eop & ~self.source.stb,
self.checksum.eq(crc.next),
),
]
cmd_layout = [
("adr", 2), # reg or mem
("is_mem", 1), # is_mem/is_reg_n
("board", 4), # 0xf: broadcast
("we", 1), # write/read_n
]
cmd_cur = Record(cmd_layout)
self.comb += cmd_cur.raw_bits().eq(self.sink.data)
cmd = Record(cmd_layout)
reg_map = Array([self.config.raw_bits(), self.checksum, self.frame])
reg_we = Signal()
self.sync += [
If(reg_we,
reg_map[cmd.adr].eq(self.sink.data),
)
]
mems = [mem.get_port(write_capable=True, we_granularity=8)
for mem in mems]
self.specials += mems
mem_adr = Signal(16)
mem_we = Signal()
mem_dat_r = Signal(16)
self.comb += [
self.sink.ack.eq(1),
[[
mem.adr.eq(mem_adr[1:]),
mem.dat_w.eq(Replicate(self.sink.data, 2)),
] for mem in mems],
If(mem_we,
Array([mem.we for mem in mems])[cmd.adr].eq(
Mux(mem_adr[0], 0b10, 0b01)),
),
mem_dat_r.eq(Array([mem.dat_r for mem in mems])[cmd.adr]),
]
fsm = ResetInserter()(CEInserter()(FSM(reset_state="CMD")))
self.submodules += fsm
self.comb += [
fsm.reset.eq(self.sink.eop),
fsm.ce.eq(self.sink.stb),
]
fsm.act("CMD",
If((cmd_cur.board == self.board) | (cmd_cur.board == 0xf),
If(cmd_cur.is_mem,
NextState("MEM_ADRL"),
).Else(
NextState("REG_DO"),
),
).Else(
NextState("IGNORE"),
),
)
fsm.act("IGNORE",
NextState("IGNORE"))
fsm.act("REG_DO",
self.source.stb.eq(self.sink.stb & ~cmd.we),
reg_we.eq(self.sink.stb & cmd.we),
self.source.data.eq(reg_map[cmd.adr]),
NextState("IGNORE"),
)
fsm.act("MEM_ADRL",
NextState("MEM_ADRH"),
)
fsm.act("MEM_ADRH",
NextState("MEM_DO"),
)
fsm.act("MEM_DO",
mem_we.eq(self.sink.stb & cmd.we),
self.source.stb.eq(self.sink.stb & ~cmd.we),
self.source.data.eq(Mux(mem_adr[0],
mem_dat_r[8:], mem_dat_r[:8])),
)
self.sync += [
If(fsm.before_leaving("CMD"),
cmd.raw_bits().eq(self.sink.data),
),
If(fsm.before_leaving("MEM_ADRL"),
mem_adr[:8].eq(self.sink.data),
),
If(fsm.before_leaving("MEM_ADRH"),
mem_adr[8:].eq(self.sink.data),
),
If(fsm.ongoing("MEM_DO") & self.sink.stb,
mem_adr.eq(mem_adr + 1),
),
]
[docs]class Comm(Module):
"""USB Protocol handler.
Args:
ctrl_pads (Record): Control signal pads.
dacs (list): List of :mod:`gateware.dac.Dac`.
Attributes:
sink (Endpoint[bus_layout]): 8 bit data sink containing both the control
sequencences and the data stream.
Control command handler.
Controls the input and output TTL signals, handles the excaped control
commands.
Args:
pads (Record): Pads containing the TTL input and output control signals
dacs (list): List of :mod:`gateware.dac.Dac`.
Attributes:
rg: :class:`ResetGen`
proto: :class:`Protocol`
spi: :class:`SPISlave`
"""
def __init__(self, ctrl_pads, dacs):
rg = ResetGen()
spi = SPISlave(width=8)
f2s = FTDI2SPI()
arb = Arbiter()
proto = Protocol([dac.parser.mem for dac in dacs])
self.submodules += proto, rg, spi, f2s, arb
self.spi = spi
self.proto = proto
self.rg = rg
self.ftdi_bus = f2s.sink
self.comb += [
spi.spi.cs_n.eq(ctrl_pads.frame[0]),
spi.spi.clk.eq(ctrl_pads.frame[1]),
spi.spi.mosi.eq(ctrl_pads.frame[2]),
spi.mosi.connect(arb.sink0),
proto.source.connect(spi.miso),
spi.reset.eq(spi.cs_n),
f2s.source.connect(arb.sink1),
arb.source.connect(proto.sink),
]
trigger = Signal()
self.specials += MultiReg(ctrl_pads.trigger, trigger)
aux_dac = Signal()
self.comb += [
proto.board.eq(~ctrl_pads.board), # pcb inverted
rg.trigger.eq(proto.config.reset),
ctrl_pads.aux.eq(Mux(proto.config.aux_miso,
spi.spi.miso, aux_dac)),
aux_dac.eq(proto.config.aux_dac &
Cat([dac.out.aux for dac in dacs]) != 0),
]
for dac in dacs:
self.comb += [
dac.parser.frame.eq(proto.frame),
dac.out.trigger.eq(proto.config.enable &
(trigger | proto.config.trigger)),
dac.out.arm.eq(proto.config.enable),
dac.parser.arm.eq(proto.config.enable),
dac.parser.start.eq(proto.config.enable),
]