Source code for gateware.dac

# Copyright 2013-2015 Robert Jordens <jordens@gmail.com>
#
# This file is part of pdq2.
#
# pdq2 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.
#
# pdq2 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 pdq2.  If not, see <http://www.gnu.org/licenses/>.

from migen.fhdl.std import *
from migen.genlib.record import Record
from migen.genlib.misc import optree
from migen.flow.actor import Source, Sink
from migen.actorlib.fifo import SyncFIFO
from migen.genlib.fsm import FSM, NextState

from .cordic import Cordic


line_layout = [
        ("header", [
            ("length", 4), # length in shorts
            ("typ", 2), # volt, dds
            ("trigger", 1), # wait for trigger before
            ("silence", 1), # shut down clock
            ("aux", 1), # aux channel value
            ("shift", 4), # time shift
            ("end", 1), # return to jump table after
            ("clear", 1), # clear persistent state (phase accu)
            ("wait", 1), # wait for trigger after
        ]),
        ("dt", 16),
        ("data", 14*16),
]


[docs]class Parser(Module): """Memory parser. Reads memory controlled by TTL signals, builds lines, and submits them to its output. Args: mem_depth (int): Memory depth in 16 bit entries. Attributes: mem (Memory): Memory to read from. source (Source[line_layout]): Source of lines read from memory. Output. arm (Signal): Allow triggers. If disarmed, the next line will not be read. Instead, the Parser will return to the frame address table. Input. start (Signal): Allow leaving the frame address table. Input. frame (Signal[3]): Values of the frame selection lines. Input. """ def __init__(self, mem_depth=4*(1<<10)): # XC3S500E: 20x18bx1024 self.specials.mem = Memory(width=16, depth=mem_depth) self.specials.read = read = self.mem.get_port() self.source = Source(line_layout) self.arm = Signal() self.start = Signal() self.frame = Signal(3) ### adr = Signal.like(read.adr) inc = Signal() lp = self.source.payload raw = Signal.like(lp.raw_bits()) self.comb += lp.raw_bits().eq(raw) lpa = Array([raw[i:i + flen(read.dat_r)] for i in range(0, flen(raw), flen(read.dat_r))]) data_read = Signal.like(lp.header.length) self.submodules.fsm = fsm = FSM(reset_state="JUMP") fsm.act("JUMP", read.adr.eq(self.frame), If(self.start, NextState("FRAME") ) ) fsm.act("FRAME", read.adr.eq(read.dat_r), inc.eq(1), If(read.dat_r == 0, NextState("JUMP") ).Else( NextState("HEADER") ) ) fsm.act("HEADER", read.adr.eq(adr), inc.eq(1), NextState("LINE") ) fsm.act("LINE", read.adr.eq(adr), If(data_read == lp.header.length, NextState("STB") ).Else( inc.eq(1), ) ) fsm.act("STB", read.adr.eq(adr), self.source.stb.eq(1), If(self.source.ack, inc.eq(1), If(lp.header.end, NextState("JUMP") ).Else( NextState("HEADER") ) ), If(~self.arm, NextState("JUMP") ) ) self.sync += [ If(inc, adr.eq(read.adr + 1), ), If(fsm.ongoing("HEADER"), raw.eq(read.dat_r), data_read.eq(1), ), If(fsm.ongoing("LINE"), lpa[data_read].eq(read.dat_r), data_read.eq(data_read + 1), ) ]
[docs]class Sequencer(Module): """Line sequencer. Controls execution of a line. Owns the executors that evolve the line data and sums them together to generate the output. Also manages the line duration counter, the ``2**shift`` counter and the acknowledgment of new line data when the previous is finished. Attributes: sink (Sink[line_layout]): Line data sink. trigger (Signal): Trigger input. arm (Signal): Arm input. aux (Signal): TTL AUX (F5) output. silence (Signal): Silence DAC clocks output. data (Signal[16]): Output value to be send to the DAC. """ def __init__(self): self.sink = Sink(line_layout) self.trigger = Signal() self.aux = Signal() self.silence = Signal() self.arm = Signal() self.data = Signal(16) ### line = Record(line_layout) dt_dec = Signal(16) dt_end = Signal(16) dt = Signal(16) adv = Signal() tic = Signal() toc = Signal() stb = Signal() toc0 = Signal() inc = Signal() lp = self.sink.payload self.comb += [ adv.eq(self.arm & self.sink.stb & (self.trigger | ~(line.header.wait | lp.header.trigger)) ), tic.eq(dt_dec == dt_end), toc.eq(dt == line.dt), stb.eq(tic & toc & adv), self.sink.ack.eq(stb), inc.eq(self.arm & tic & (~toc | (~toc0 & ~adv))), ] subs = [ Volt(lp, stb & (lp.header.typ == 0), inc), Dds(lp, stb & (lp.header.typ == 1), inc), ] for i, sub in enumerate(subs): self.submodules += sub self.sync += [ toc0.eq(toc), self.data.eq(optree("+", [sub.data for sub in subs])), self.aux.eq(line.header.aux), self.silence.eq(line.header.silence), If(~tic, dt_dec.eq(dt_dec + 1), ).Elif(~toc, dt_dec.eq(0), dt.eq(dt + 1), ).Elif(stb, line.header.eq(lp.header), line.dt.eq(lp.dt - 1), dt_end.eq((1<<lp.header.shift) - 1), dt_dec.eq(0), dt.eq(0), ) ]
[docs]class Volt(Module): """DC bias spline interpolator. The line data is interpreted as a concatenation of: * 16 bit amplitude offset * 32 bit amplitude first order derivative * 48 bit amplitude second order derivative * 48 bit amplitude third order derivative Args: line (Record[line_layout]): Next line to be executed. Input. stb (Signal): Load data from next line. Input. inc (Signal): Evolve clock enable. Input. Attributes: data (Signal[16]): Output data from this spline. """ def __init__(self, line, stb, inc): self.data = Signal(16) ### v = [Signal(48) for i in range(4)] # amp, damp, ddamp, dddamp self.comb += self.data.eq(v[0][32:]) self.sync += [ If(inc, v[0].eq(v[0] + v[1]), v[1].eq(v[1] + v[2]), v[2].eq(v[2] + v[3]), ), If(stb, v[0].eq(0), v[1].eq(0), Cat(v[0][32:], v[1][16:], v[2], v[3]).eq(line.data), ) ]
[docs]class Dds(Module): """DDS spline interpolator. The line data is interpreted as: * 16 bit amplitude offset * 32 bit amplitude first order derivative * 48 bit amplitude second order derivative * 48 bit amplitude third order derivative * 16 bit phase offset * 32 bit frequency word * 48 bit chirp Args: line (Record[line_layout]): Next line to be executed. Input. stb (Signal): Load data from next line. Input. inc (Signal): Evolve clock enable. Input. Attributes: data (Signal[16]): Output data from this spline. """ def __init__(self, line, stb, inc): self.data = Signal(16) ### self.submodules.cordic = Cordic(width=16, eval_mode="pipelined", guard=None) za = Signal(32) z = [Signal(32) for i in range(3)] # phase, dphase, ddphase x = [Signal(48) for i in range(4)] # amp, damp, ddamp, dddamp self.comb += [ self.cordic.xi.eq(x[0][32:]), self.cordic.yi.eq(0), self.cordic.zi.eq(za[16:] + z[0][16:]), self.data.eq(self.cordic.xo), ] self.sync += [ za.eq(za + z[1]), If(inc, x[0].eq(x[0] + x[1]), x[1].eq(x[1] + x[2]), x[2].eq(x[2] + x[3]), z[1].eq(z[1] + z[2]), ), If(stb, x[0].eq(0), x[1].eq(0), Cat(x[0][32:], x[1][16:], x[2], x[3], z[0][16:], z[1], z[2] ).eq(line.data), If(line.header.clear, za.eq(0), ) ) ]
[docs]class Dac(Module): """Output module. Holds the Memory, the :class:`Parser`, the :class:`Sequencer`, and its two output line executors. Args: fifo (int): Number of lines to buffer between :class:`Parser` and :class:`Sequencer`. **kwargs: Passed to :class:`Parser`. Attributes: parser: The memory :class:`Parser`. out: The :class:`Sequencer` and output executor. Connect its ``data`` to the DAC. """ def __init__(self, fifo=0, **kwargs): self.submodules.parser = Parser(**kwargs) self.submodules.out = Sequencer() if fifo: self.submodules.fifo = SyncFIFO(line_layout, fifo) self.comb += [ self.fifo.sink.connect(self.parser.source), self.out.sink.connect(self.fifo.source) ] else: self.comb += self.out.sink.connect(self.parser.source)