From 3f6bf332987c763767952a10630a755927a52978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 7 Oct 2021 13:47:08 +0000 Subject: [PATCH 1/4] fastino: add interpolator support --- artiq/coredevice/fastino.py | 67 +++++++++++++++++++++++++++++- artiq/gateware/rtio/phy/fastino.py | 43 ++++++++++++++----- 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index 73fcfdf38..c60d5d1b1 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -2,7 +2,7 @@ streaming DAC. """ -from artiq.language.core import kernel, portable, delay +from artiq.language.core import kernel, portable, delay, delay_mu from artiq.coredevice.rtio import (rtio_output, rtio_output_wide, rtio_input_data) from artiq.language.units import us @@ -190,3 +190,68 @@ class Fastino: green LED. """ self.write(0x23, leds) + + @kernel + def set_continuous(self, channel_mask): + """Enable continuous DAC updates on channels regardless of new data + being submitted. + """ + self.write(0x25, channel_mask) + + @kernel + def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent): + """Stage machine unit interpolator configuration. + """ + if rate_mantissa < 0 or rate_mantissa >= 1 << 6: + raise ValueError("rate_mantissa out of bounds") + if rate_exponent < 0 or rate_exponent >= 1 << 4: + raise ValueError("rate_exponent out of bounds") + if gain_exponent < 0 or gain_exponent >= 1 << 6: + raise ValueError("gain_exponent out of bounds") + config = rate_mantissa | (rate_exponent << 6) | (gain_exponent << 10) + self.write(0x26, config) + + @kernel + def stage_cic(self, rate) -> TInt32: + """Compute and stage interpolator configuration. + + Approximates rate using 6+4 bit floating point representation, + approximates optimal interpolation gain compensation exponent to avoid + clipping. Gains for rates that are powers of two are accurately + compensated. Other rates lead to overall less than unity gain. + + Returns the actual interpolation rate. + The actual overall interpolation gain including gain compensation is + `actual_rate**order/2**ceil(log2(actual_rate**order))` + where `order = 3`. + """ + if rate <= 0 or rate > 1 << 16: + raise ValueError("rate out of bounds") + rate_mantissa = rate + rate_exponent = 0 + while rate_mantissa > 1 << 6: + rate_exponent += 1 + rate_mantissa >>= 1 + order = 3 + gain = 1 + for i in range(order): + gain *= rate_mantissa + gain_exponent = 0 + while gain > 1 << gain_exponent: + gain_exponent += 1 + gain_exponent += order*rate_exponent + assert gain_exponent <= order*16 + self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent) + return rate_mantissa << rate_exponent + + @kernel + def apply_cic(self, channel_mask): + """Apply the staged interpolator configuration on the specified channels. + + Channels using non-unity interpolation rate and variable data should have + continous DAC updates enabled (see :meth:`set_continuous`). + + This resets and settles the interpolators. There will be no output + updates for the next `order = 3` input samples. + """ + self.write(0x27, channel_mask) diff --git a/artiq/gateware/rtio/phy/fastino.py b/artiq/gateware/rtio/phy/fastino.py index ba8a7b5c9..474bab0f3 100644 --- a/artiq/gateware/rtio/phy/fastino.py +++ b/artiq/gateware/rtio/phy/fastino.py @@ -27,16 +27,16 @@ class Fastino(Module): # dac data words dacs = [Signal(16) for i in range(32)] + header = Record([ ("cfg", 4), ("leds", 8), - ("reserved", 8), + ("typ", 1), + ("reserved", 7), ("addr", 4), ("enable", len(dacs)), ]) - body = Cat(header.raw_bits(), dacs) - assert len(body) == len(self.serializer.payload) - self.comb += self.serializer.payload.eq(body) + assert len(Cat(header.raw_bits(), dacs)) == len(self.serializer.payload) # # # @@ -62,38 +62,61 @@ class Fastino(Module): # address space is sparse. hold = Signal.like(header.enable) + continuous = Signal.like(header.enable) + cic_config = Signal(16) read_regs = Array([Signal.like(self.serializer.readback) for _ in range(1 << len(header.addr))]) cases = { # update - 0x20: header.enable.eq(header.enable | self.rtlink.o.data), + 0x20: [ + header.enable.eq(self.rtlink.o.data), + header.typ.eq(0), + ], # hold 0x21: hold.eq(self.rtlink.o.data), # cfg 0x22: header.cfg.eq(self.rtlink.o.data), # leds 0x23: header.leds.eq(self.rtlink.o.data), - # reserved + # reserved bits 0x24: header.reserved.eq(self.rtlink.o.data), + # force continuous DAC updates + 0x25: continuous.eq(self.rtlink.o.data), + # interpolator configuration stage + 0x26: cic_config.eq(self.rtlink.o.data), + # interpolator update flags + 0x27: [ + header.enable.eq(self.rtlink.o.data), + header.typ.eq(1), + ], } for i in range(0, len(dacs), width): cases[i] = [ Cat(dacs[i:i + width]).eq(self.rtlink.o.data), - [If(~hold[i + j], + [If(~hold[i + j] & (header.typ == 0), header.enable[i + j].eq(1), ) for j in range(width)] ] + self.comb += [ + If(header.typ == 0, + self.serializer.payload.eq(Cat(header.raw_bits(), dacs)), + ).Else( + self.serializer.payload.eq(Cat(header.raw_bits(), Replicate(cic_config, len(dacs)))), + ), + ] + self.sync.rio_phy += [ If(self.serializer.stb, - header.enable.eq(0), + header.typ.eq(0), + header.enable.eq(continuous), read_regs[header.addr].eq(self.serializer.readback), header.addr.eq(header.addr + 1), ), - If(self.rtlink.o.stb & ~self.rtlink.o.address[-1], - Case(self.rtlink.o.address[:-1], cases), + If(self.rtlink.o.stb, + Case(self.rtlink.o.address, cases), ), ] From 10c37b87ecfc8b893bb8e5f8b4acc7e3b674ac54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 27 Oct 2021 20:24:58 +0000 Subject: [PATCH 2/4] fastino: make driver filter order configurable --- artiq/coredevice/fastino.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index c60d5d1b1..588a3d436 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -39,13 +39,15 @@ class Fastino: :param core_device: Core device name (default: "core") :param log2_width: Width of DAC channel group (logarithm base 2). Value must match the corresponding value in the RTIO PHY (gateware). + :param order: CIC filter interpolation order. """ - kernel_invariants = {"core", "channel", "width"} + kernel_invariants = {"core", "channel", "width", "order"} - def __init__(self, dmgr, channel, core_device="core", log2_width=0): + def __init__(self, dmgr, channel, core_device="core", log2_width=0, order=3): self.channel = channel << 8 self.core = dmgr.get(core_device) self.width = 1 << log2_width + self.order = order @kernel def init(self): @@ -222,8 +224,7 @@ class Fastino: Returns the actual interpolation rate. The actual overall interpolation gain including gain compensation is - `actual_rate**order/2**ceil(log2(actual_rate**order))` - where `order = 3`. + `actual_rate**order/2**ceil(log2(actual_rate**order))`. """ if rate <= 0 or rate > 1 << 16: raise ValueError("rate out of bounds") @@ -232,15 +233,14 @@ class Fastino: while rate_mantissa > 1 << 6: rate_exponent += 1 rate_mantissa >>= 1 - order = 3 gain = 1 - for i in range(order): + for i in range(self.order): gain *= rate_mantissa gain_exponent = 0 while gain > 1 << gain_exponent: gain_exponent += 1 - gain_exponent += order*rate_exponent - assert gain_exponent <= order*16 + gain_exponent += self.order*rate_exponent + assert gain_exponent <= self.order*16 self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent) return rate_mantissa << rate_exponent @@ -252,6 +252,6 @@ class Fastino: continous DAC updates enabled (see :meth:`set_continuous`). This resets and settles the interpolators. There will be no output - updates for the next `order = 3` input samples. + updates for the next `order` input samples. """ self.write(0x27, channel_mask) From 1ff474893df1a07399208c8dddbe3dec09879aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 28 Oct 2021 06:29:56 +0000 Subject: [PATCH 3/4] Revert "fastino: make driver filter order configurable" This reverts commit 10c37b87ecfc8b893bb8e5f8b4acc7e3b674ac54. --- artiq/coredevice/fastino.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index 588a3d436..c60d5d1b1 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -39,15 +39,13 @@ class Fastino: :param core_device: Core device name (default: "core") :param log2_width: Width of DAC channel group (logarithm base 2). Value must match the corresponding value in the RTIO PHY (gateware). - :param order: CIC filter interpolation order. """ - kernel_invariants = {"core", "channel", "width", "order"} + kernel_invariants = {"core", "channel", "width"} - def __init__(self, dmgr, channel, core_device="core", log2_width=0, order=3): + def __init__(self, dmgr, channel, core_device="core", log2_width=0): self.channel = channel << 8 self.core = dmgr.get(core_device) self.width = 1 << log2_width - self.order = order @kernel def init(self): @@ -224,7 +222,8 @@ class Fastino: Returns the actual interpolation rate. The actual overall interpolation gain including gain compensation is - `actual_rate**order/2**ceil(log2(actual_rate**order))`. + `actual_rate**order/2**ceil(log2(actual_rate**order))` + where `order = 3`. """ if rate <= 0 or rate > 1 << 16: raise ValueError("rate out of bounds") @@ -233,14 +232,15 @@ class Fastino: while rate_mantissa > 1 << 6: rate_exponent += 1 rate_mantissa >>= 1 + order = 3 gain = 1 - for i in range(self.order): + for i in range(order): gain *= rate_mantissa gain_exponent = 0 while gain > 1 << gain_exponent: gain_exponent += 1 - gain_exponent += self.order*rate_exponent - assert gain_exponent <= self.order*16 + gain_exponent += order*rate_exponent + assert gain_exponent <= order*16 self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent) return rate_mantissa << rate_exponent @@ -252,6 +252,6 @@ class Fastino: continous DAC updates enabled (see :meth:`set_continuous`). This resets and settles the interpolators. There will be no output - updates for the next `order` input samples. + updates for the next `order = 3` input samples. """ self.write(0x27, channel_mask) From 5a5b0cc7c0175bf945c04b44f1aacc504e2f26d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 28 Oct 2021 15:19:48 +0000 Subject: [PATCH 4/4] fastino: expand docs --- artiq/coredevice/fastino.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index c60d5d1b1..193a9d9ba 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -200,7 +200,7 @@ class Fastino: @kernel def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent): - """Stage machine unit interpolator configuration. + """Stage machine unit CIC interpolator configuration. """ if rate_mantissa < 0 or rate_mantissa >= 1 << 6: raise ValueError("rate_mantissa out of bounds") @@ -215,15 +215,18 @@ class Fastino: def stage_cic(self, rate) -> TInt32: """Compute and stage interpolator configuration. - Approximates rate using 6+4 bit floating point representation, - approximates optimal interpolation gain compensation exponent to avoid - clipping. Gains for rates that are powers of two are accurately - compensated. Other rates lead to overall less than unity gain. + This method approximates the desired interpolation rate using a 10 bit + floating point representation (6 bit mantissa, 4 bit exponent) and + then determines an optimal interpolation gain compensation exponent + to avoid clipping. Gains for rates that are powers of two are accurately + compensated. Other rates lead to overall less than unity gain (but more + than 0.5 gain). - Returns the actual interpolation rate. - The actual overall interpolation gain including gain compensation is + The overall gain including gain compensation is `actual_rate**order/2**ceil(log2(actual_rate**order))` where `order = 3`. + + Returns the actual interpolation rate. """ if rate <= 0 or rate > 1 << 16: raise ValueError("rate out of bounds") @@ -248,10 +251,21 @@ class Fastino: def apply_cic(self, channel_mask): """Apply the staged interpolator configuration on the specified channels. - Channels using non-unity interpolation rate and variable data should have - continous DAC updates enabled (see :meth:`set_continuous`). + Each Fastino channel includes a fourth order (cubic) CIC interpolator with + variable rate change and variable output gain compensation (see + :meth:`stage_cic`). - This resets and settles the interpolators. There will be no output - updates for the next `order = 3` input samples. + Channels using non-unity interpolation rate should have + continous DAC updates enabled (see :meth:`set_continuous`) unless + their output is supposed to be constant. + + This method resets and settles the affected interpolators. There will be + no output updates for the next `order = 3` input samples. + Affected channels will only accept one input sample per input sample + period. This method synchronizes the input sample period to the current + frame on the affected channels. + + If application of new interpolator settings results in a change of the + overall gain, there will be a corresponding output step. """ self.write(0x27, channel_mask)