diff --git a/artiq/coredevice/sawg.py b/artiq/coredevice/sawg.py index ba7f9ef15..5f81bf146 100644 --- a/artiq/coredevice/sawg.py +++ b/artiq/coredevice/sawg.py @@ -10,12 +10,10 @@ _SAWG_DIV = 0 _SAWG_CLR = 1 _SAWG_IQ_EN = 2 # _SAWF_PAD = 3 # reserved -_SAWG_DUC_I_MIN = 4 -_SAWG_DUC_I_MAX = 5 -_SAWG_DUC_Q_MIN = 6 -_SAWG_DUC_Q_MAX = 7 -_SAWG_OUT_MIN = 8 -_SAWG_OUT_MAX = 9 +_SAWG_OUT_MIN = 4 +_SAWG_OUT_MAX = 5 +_SAWG_DUC_MIN = 6 +_SAWG_DUC_MAX = 7 class Config: @@ -92,8 +90,8 @@ class Config: :param clr2: Auto-clear phase accumulator of the ``phase2``/ ``frequency2`` DDS. Default: ``True`` """ - rtio_output(now_mu(), self.channel, _SAWG_CLR, clr1 | - (clr2 << 1) | (clr0 << 2)) + rtio_output(now_mu(), self.channel, _SAWG_CLR, clr0 | + (clr1 << 1) | (clr2 << 2)) @kernel def set_iq_en(self, i_enable: TInt32, q_enable: TInt32): @@ -122,48 +120,38 @@ class Config: (q_enable << 1)) @kernel - def set_duc_i_max_mu(self, limit: TInt32): - """Set the digital up-converter (DUC) I data summing junction upper - limit. In machine units. + def set_duc_max_mu(self, limit: TInt32): + """Set the digital up-converter (DUC) I and Q data summing junctions + upper limit. In machine units. The default limits are chosen to reach maximum and minimum DAC output amplitude. For a description of the limiter functions in normalized units see: - .. seealso:: :meth:`set_duc_i_max` + .. seealso:: :meth:`set_duc_max` """ - rtio_output(now_mu(), self.channel, _SAWG_DUC_I_MAX, limit) + rtio_output(now_mu(), self.channel, _SAWG_DUC_MAX, limit) @kernel - def set_duc_i_min_mu(self, limit: TInt32): - """.. seealso:: :meth:`set_duc_i_max_mu`""" - rtio_output(now_mu(), self.channel, _SAWG_DUC_I_MIN, limit) - - @kernel - def set_duc_q_max_mu(self, limit: TInt32): - """.. seealso:: :meth:`set_duc_i_max_mu`""" - rtio_output(now_mu(), self.channel, _SAWG_DUC_Q_MAX, limit) - - @kernel - def set_duc_q_min_mu(self, limit: TInt32): - """.. seealso:: :meth:`set_duc_i_max_mu`""" - rtio_output(now_mu(), self.channel, _SAWG_DUC_Q_MIN, limit) + def set_duc_min_mu(self, limit: TInt32): + """.. seealso:: :meth:`set_duc_max_mu`""" + rtio_output(now_mu(), self.channel, _SAWG_DUC_MIN, limit) @kernel def set_out_max_mu(self, limit: TInt32): - """.. seealso:: :meth:`set_duc_i_max_mu`""" + """.. seealso:: :meth:`set_duc_max_mu`""" rtio_output(now_mu(), self.channel, _SAWG_OUT_MAX, limit) @kernel def set_out_min_mu(self, limit: TInt32): - """.. seealso:: :meth:`set_duc_i_max_mu`""" + """.. seealso:: :meth:`set_duc_max_mu`""" rtio_output(now_mu(), self.channel, _SAWG_OUT_MIN, limit) @kernel - def set_duc_i_max(self, limit: TFloat): - """Set the digital up-converter (DUC) I data summing junction upper - limit. + def set_duc_max(self, limit: TFloat): + """Set the digital up-converter (DUC) I and Q data summing junctions + upper limit. Each of the three summing junctions has a saturating adder with configurable upper and lower limits. The three summing junctions are: @@ -171,7 +159,8 @@ class Config: * At the in-phase input to the ``phase0``/``frequency0`` fast DUC, after the anti-aliasing FIR filter. * At the quadrature input to the ``phase0``/``frequency0`` - fast DUC, after the anti-aliasing FIR filter. + fast DUC, after the anti-aliasing FIR filter. The in-phase and + quadrature data paths both use the same limits. * Before the DAC, where the following three data streams are added together: @@ -190,42 +179,28 @@ class Config: ``[-1, 1]``. .. seealso:: - * :meth:`set_duc_i_max`: Upper limit of the in-phase input to - the DUC. - * :meth:`set_duc_i_min`: Lower limit of the in-phase input to - the DUC. - * :meth:`set_duc_q_max`: Upper limit of the quadrature input to - the DUC. - * :meth:`set_duc_q_min`: Lower limit of the quadrature input to - the DUC. + * :meth:`set_duc_max`: Upper limit of the in-phase and quadrature + inputs to the DUC. + * :meth:`set_duc_min`: Lower limit of the in-phase and quadrature + inputs to the DUC. * :meth:`set_out_max`: Upper limit of the DAC output. * :meth:`set_out_min`: Lower limit of the DAC output. """ - self.set_duc_i_max_mu(int32(round(limit*self._duc_scale))) + self.set_duc_max_mu(int32(round(limit*self._duc_scale))) @kernel - def set_duc_i_min(self, limit: TFloat): - """.. seealso:: :meth:`set_duc_i_max`""" - self.set_duc_i_min_mu(int32(round(limit*self._duc_scale))) - - @kernel - def set_duc_q_max(self, limit: TFloat): - """.. seealso:: :meth:`set_duc_i_max`""" - self.set_duc_q_max_mu(int32(round(limit*self._duc_scale))) - - @kernel - def set_duc_q_min(self, limit: TFloat): - """.. seealso:: :meth:`set_duc_i_max`""" - self.set_duc_q_min_mu(int32(round(limit*self._duc_scale))) + def set_duc_min(self, limit: TFloat): + """.. seealso:: :meth:`set_duc_max`""" + self.set_duc_min_mu(int32(round(limit*self._duc_scale))) @kernel def set_out_max(self, limit: TFloat): - """.. seealso:: :meth:`set_duc_i_max`""" + """.. seealso:: :meth:`set_duc_max`""" self.set_out_max_mu(int32(round(limit*self._out_scale))) @kernel def set_out_min(self, limit: TFloat): - """.. seealso:: :meth:`set_duc_i_max`""" + """.. seealso:: :meth:`set_duc_max`""" self.set_out_min_mu(int32(round(limit*self._out_scale))) @@ -306,6 +281,7 @@ class SAWG: width = 16 time_width = 16 cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain + cordic_gain *= 1.01 # leave a bit of headroom self.config = Config(channel_base, self.core, cordic_gain) self.offset = Spline(width, time_width, channel_base + 1, self.core, 2.) @@ -335,7 +311,7 @@ class SAWG: settings. This method advances the timeline by the time required to perform all - eight writes to the configuration channel. + six writes to the configuration channel. """ self.frequency0.set_mu(0) self.frequency1.set_mu(0) @@ -350,13 +326,9 @@ class SAWG: delay_mu(self.config._rtio_interval) self.config.set_iq_en(1, 0) delay_mu(self.config._rtio_interval) - self.config.set_duc_i_min(-1.) + self.config.set_duc_min(-1.) delay_mu(self.config._rtio_interval) - self.config.set_duc_i_max(1.) - delay_mu(self.config._rtio_interval) - self.config.set_duc_q_min(-1.) - delay_mu(self.config._rtio_interval) - self.config.set_duc_q_max(1.) + self.config.set_duc_max(1.) delay_mu(self.config._rtio_interval) self.config.set_out_min(-1.) delay_mu(self.config._rtio_interval) diff --git a/artiq/gateware/dsp/sawg.py b/artiq/gateware/dsp/sawg.py index c60bcdb00..7b8d40b4a 100644 --- a/artiq/gateware/dsp/sawg.py +++ b/artiq/gateware/dsp/sawg.py @@ -83,35 +83,43 @@ class SplineParallelDDS(SplineParallelDUC): class Config(Module): - def __init__(self, width): + def __init__(self, width, cordic_gain): self.clr = Signal(3, reset=0b111) self.iq_en = Signal(2, reset=0b01) - self.limits = [[Signal((width, True), reset=-(1 << width - 1)), - Signal((width, True), reset=(1 << width - 1) - 1)] - for i in range(3)] - self.clipped = [Signal(2) for i in range(3)] # TODO - self.i = Endpoint([("addr", bits_for(4 + 2*len(self.limits))), - ("data", 16)]) + self.limits = [[Signal((width, True)), Signal((width, True))] + for i in range(2)] + limit = 1 << width - 1 + self.limits[0][0].reset = -limit + self.limits[0][1].reset = limit - 1 + self.limits[1][0].reset = int(-limit/cordic_gain) + self.limits[1][1].reset = int((limit - 1)/cordic_gain) + # TODO make persistent, add read-out/notification/clear + self.clipped = [Signal(2) for i in range(2)] + self.i = Endpoint([("addr", bits_for(4 + 2*len(self.limits) - 1)), + ("data", width)]) + assert len(self.i.addr) == 3 self.ce = Signal() ### div = Signal(16, reset=0) n = Signal.like(div) - pad = Signal() - reg = Array([Cat(div, n), self.clr, self.iq_en, pad] + - sum(self.limits, [])) - - self.comb += [ - self.i.ack.eq(1), - self.ce.eq(n == 0), - ] + self.comb += self.ce.eq(n == 0) self.sync += [ n.eq(n - 1), If(self.ce, n.eq(div), - ), + ) + ] + + pad = Signal() + + reg = Array(sum(self.limits, + [Cat(div, n), self.clr, self.iq_en, pad])) + + self.comb += self.i.ack.eq(1) + self.sync += [ If(self.i.stb, reg[self.i.addr].eq(self.i.data), ), @@ -131,26 +139,26 @@ class Channel(Module, SatAddMixin): coeff = halfgen4_cascade(parallelism, width=.4, order=8) hbf = [ParallelHBFUpsampler(coeff, width=width + 1) for i in range(2)] self.submodules.b = b = SplineParallelDUC( - widths._replace(a=len(hbf[0].o[0]), f=widths.f - width), orders, + widths._replace(a=width + 1, f=widths.f - width), orders, parallelism=parallelism) - cfg = Config(width) + cfg = Config(width, b.gain) u = Spline(width=widths.a, order=orders.a) self.submodules += cfg, u, hbf self.u = u.tri(widths.t) self.i = [cfg.i, self.u, a1.a, a1.f, a1.p, a2.a, a2.f, a2.p, b.f, b.p] self.i_names = "cfg u a1 f1 p1 a2 f2 p2 f0 p0".split() self.i_named = dict(zip(self.i_names, self.i)) - self.y_in = [Signal((width, True)) for i in range(parallelism)] + self.y_in = [Signal.like(b.yo[0]) for i in range(parallelism)] self.o = [Signal((width, True)) for i in range(parallelism)] self.widths = widths self.orders = orders self.parallelism = parallelism - self.cordic_gain = a1.gain*b.gain + self.cordic_gain = a2.gain*b.gain self.u.latency += 1 b.p.latency += 2 b.f.latency += 2 - a_latency_delta = hbf[0].latency + b.latency + 3 + a_latency_delta = hbf[0].latency + b.latency + 2 for a in a1, a2: a.a.latency += a_latency_delta a.p.latency += a_latency_delta @@ -169,26 +177,19 @@ class Channel(Module, SatAddMixin): a2.ce.eq(cfg.ce), b.ce.eq(cfg.ce), u.o.ack.eq(cfg.ce), - Cat(a1.clr, a2.clr, b.clr).eq(cfg.clr), + Cat(b.clr, a1.clr, a2.clr).eq(cfg.clr), + ] + self.sync += [ + hbf[0].i.eq(self.sat_add(a1.xo[0], a2.xo[0], + limits=cfg.limits[1], clipped=cfg.clipped[1])), + hbf[1].i.eq(self.sat_add(a1.yo[0], a2.yo[0], + limits=cfg.limits[1], clipped=None)), # ignore Q clip data ] for i in range(parallelism): - self.sync += [ - b.xi[i].eq(self.sat_add(hbf[0].o[i], - limits=cfg.limits[0], - clipped=cfg.clipped[0])), - b.yi[i].eq(self.sat_add(hbf[1].o[i], - limits=cfg.limits[1], - clipped=cfg.clipped[1])), + self.comb += [ + b.xi[i].eq(hbf[0].o[i]), + b.yi[i].eq(hbf[1].o[i]), ] - for i in range(2): - for j in range(2): - # correct pre-DUC limiter by cordic gain - v = cfg.limits[i][j].reset.value - cfg.limits[i][j].reset.value = int(v / b.gain) - self.sync += [ - hbf[0].i.eq(a1.xo[0] + a2.xo[0]), - hbf[1].i.eq(a1.yo[0] + a2.yo[0]) - ] # wire up outputs and q_{i,o} exchange for o, x, y in zip(self.o, b.xo, self.y_in): self.sync += [ @@ -196,8 +197,8 @@ class Channel(Module, SatAddMixin): u.o.a0[-len(o):], Mux(cfg.iq_en[0], x, 0), Mux(cfg.iq_en[1], y, 0), - limits=cfg.limits[2], - clipped=cfg.clipped[2])), + limits=cfg.limits[0], + clipped=cfg.clipped[0])), ] def connect_y(self, buddy):