Merge pull request #1774 from m-labs/fastino-cic

Fastino cic
pull/1675/merge
Robert Jördens 2021-10-28 17:44:20 +02:00 committed by GitHub
commit 591507a7c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 11 deletions

View File

@ -3,7 +3,7 @@ streaming DAC.
"""
from numpy import int32
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
@ -191,3 +191,82 @@ 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 CIC 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.
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).
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")
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.
Each Fastino channel includes a fourth order (cubic) CIC interpolator with
variable rate change and variable output gain compensation (see
:meth:`stage_cic`).
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)

View File

@ -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),
),
]