Merge branch 'master' into nac3

This commit is contained in:
Sebastien Bourdeauducq 2023-09-13 19:21:10 +08:00
commit 7cffa3f8ff
57 changed files with 2061 additions and 228 deletions

View File

@ -68,6 +68,9 @@ Old syntax should be replaced with the form shown on the right.
data[key][1] ==> value[key]
data[key][2] ==> metadata[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
ARTIQ-7

View File

@ -44,8 +44,8 @@ class AppletControlRPC:
self.dataset_ctl = dataset_ctl
self.background_tasks = set()
def _background(self, coro, *args):
task = self.loop.create_task(coro(*args))
def _background(self, coro, *args, **kwargs):
task = self.loop.create_task(coro(*args, **kwargs))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)

View File

@ -27,7 +27,7 @@ class AlmaznyLegacy:
This applies to Almazny hardware v1.1 and earlier.
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny - Mirny device Almazny is connected to
:param host_mirny: Mirny device Almazny is connected to
"""
core: KernelInvariant[Core]
@ -74,9 +74,10 @@ class AlmaznyLegacy:
def set_att(self, channel: int32, att: float, rf_switch: bool = True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in dBm [0-31.5]
:param rf_switch - rf switch (bool)
:param channel: index of the register [0-3]
:param att: attenuation setting in dBm [0-31.5]
:param rf_switch: rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@ -84,9 +85,10 @@ class AlmaznyLegacy:
def set_att_mu(self, channel: int32, att_mu: int32, rf_switch: bool = True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in machine units [0-63]
:param rf_switch - rf switch (bool)
:param channel: index of the register [0-3]
:param att_mu: attenuation setting in machine units [0-63]
:param rf_switch: rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
@ -96,7 +98,8 @@ class AlmaznyLegacy:
def output_toggle(self, oe: bool):
"""
Toggles output on all shift registers on or off.
:param oe - toggle output enable (bool)
:param oe: toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
@ -142,6 +145,7 @@ class AlmaznyChannel:
This driver requires Almazny hardware revision v1.2 or later
and Mirny CPLD gateware v0.3 or later.
Use :class:`artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
:param host_mirny: Mirny CPLD device name
:param channel: channel index (0-3)
"""
@ -160,6 +164,7 @@ class AlmaznyChannel:
"""
Convert an attenuation in dB, RF switch state and LED state to machine
units.
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
@ -181,6 +186,7 @@ class AlmaznyChannel:
def set_mu(self, mu: int32):
"""
Set channel state (machine units).
:param mu: channel state in machine units.
"""
self.mirny_cpld.write_ext(
@ -190,6 +196,7 @@ class AlmaznyChannel:
def set(self, att: float, enable: bool, led: bool = False):
"""
Set attenuation, RF switch, and LED state (SI units).
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)

View File

@ -130,7 +130,7 @@
},
"hw_rev": {
"type": "string",
"enum": ["v1.0"]
"enum": ["v1.0", "v1.1"]
}
}
}
@ -142,7 +142,7 @@
"properties": {
"type": {
"type": "string",
"enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp"]
"enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "efc"]
},
"board": {
"type": "string"
@ -562,6 +562,28 @@
},
"required": ["ports"]
}
},{
"title": "EFC",
"if": {
"properties": {
"type": {
"const": "efc"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
}
},
"required": ["ports"]
}
}]
}
}

View File

@ -168,6 +168,7 @@ class I2CSwitch:
@kernel
def set(self, channel: int32):
"""Enable one channel.
:param channel: channel number (0-7)
"""
i2c_switch_select(self.busno, self.address >> 1, 1 << channel)

View File

@ -0,0 +1,91 @@
import numpy
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output
class Config:
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def set_config(self, config):
rtio_output(self.target_o, config)
class Volt:
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
pdq_words = [
a0,
a1,
a1 >> 16,
a2 & 0xFFFF,
(a2 >> 16) & 0xFFFF,
(a2 >> 32) & 0xFFFF,
a3 & 0xFFFF,
(a3 >> 16) & 0xFFFF,
(a3 >> 32) & 0xFFFF,
]
for i in range(len(pdq_words)):
rtio_output(self.target_o | i, pdq_words[i])
delay_mu(int64(self.core.ref_multiplier))
class Dds:
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
c0: TInt32, c1: TInt32, c2: TInt32):
pdq_words = [
b0,
b1,
b1 >> 16,
b2 & 0xFFFF,
(b2 >> 16) & 0xFFFF,
(b2 >> 32) & 0xFFFF,
b3 & 0xFFFF,
(b3 >> 16) & 0xFFFF,
(b3 >> 32) & 0xFFFF,
c0,
c1,
c1 >> 16,
c2,
c2 >> 16,
]
for i in range(len(pdq_words)):
rtio_output(self.target_o | i, pdq_words[i])
delay_mu(int64(self.core.ref_multiplier))
class Trigger:
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def trigger(self, trig_out):
rtio_output(self.target_o, trig_out)

View File

@ -286,9 +286,8 @@ class NRTSPIMaster:
def set_config_mu(self, flags: int32 = 0, length: int32 = 8, div: int32 = 6, cs: int32 = 1):
"""Set the ``config`` register.
Note that the non-realtime SPI cores are usually clocked by the system
clock and not the RTIO clock. In many cases, the SPI configuration is
already set by the firmware and you do not need to call this method.
In many cases, the SPI configuration is already set by the firmware
and you do not need to call this method.
"""
spi_set_config(self.busno, flags, length, div, cs)

View File

@ -5,7 +5,7 @@ import numpy as np
from PyQt5 import QtCore, QtWidgets
from sipyco import pyon
from artiq.tools import short_format, exc_to_warning
from artiq.tools import scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.scientific_spinbox import ScientificSpinBox
@ -77,6 +77,12 @@ class CreateEditDialog(QtWidgets.QDialog):
self.value_widget.setText(value)
if metadata is not None:
scale = scale_from_metadata(metadata)
decoded_value = pyon.decode(value)
if scale == 1:
self.value_widget.setText(value)
else:
self.value_widget.setText(pyon.encode(decoded_value / scale))
self.unit_widget.setText(metadata.get('unit', ''))
self.scale_widget.setText(str(metadata.get('scale', '')))
self.precision_widget.setText(str(metadata.get('precision', '')))
@ -97,10 +103,16 @@ class CreateEditDialog(QtWidgets.QDialog):
metadata['scale'] = float(scale)
if precision != "":
metadata['precision'] = int(precision)
scale = scale_from_metadata(metadata)
value = pyon.decode(value)
t = value.dtype if value is np.ndarray else type(value)
is_floating = scale != 1 or np.issubdtype(t, np.floating)
if is_floating:
value = value * scale
if self.key and self.key != key:
asyncio.ensure_future(exc_to_warning(rename(self.key, key, pyon.decode(value), metadata, persist, self.dataset_ctl)))
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist, self.dataset_ctl)))
else:
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, pyon.decode(value), metadata=metadata, persist=persist)))
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata, persist=persist)))
self.key = key
QtWidgets.QDialog.accept(self)
@ -197,12 +209,6 @@ class DatasetsDock(QtWidgets.QDockWidget):
key = self.table_model.index_to_key(idx)
if key is not None:
persist, value, metadata = self.table_model.backing_store[key]
t = type(value)
if np.issubdtype(t, np.number) or np.issubdtype(t, np.bool_):
value = str(value)
elif np.issubdtype(t, np.unicode_):
value = '"{}"'.format(str(value))
else:
value = pyon.encode(value)
CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open()

View File

@ -26,7 +26,7 @@ class DDSSetter(EnvExperiment):
"driver": self.get_device(k),
"frequency": self.get_argument(
"{}_frequency".format(k),
NumberValue(100e6, scale=1e6, unit="MHz", ndecimals=6))
NumberValue(100e6, scale=1e6, unit="MHz", precision=6))
}
@kernel

View File

@ -31,8 +31,8 @@ class PhotonHistogram(EnvExperiment):
self.setattr_device("bdd_sw")
self.setattr_device("pmt")
self.setattr_argument("nbins", NumberValue(100, ndecimals=0, step=1))
self.setattr_argument("repeats", NumberValue(100, ndecimals=0, step=1))
self.setattr_argument("nbins", NumberValue(100, precision=0, step=1))
self.setattr_argument("repeats", NumberValue(100, precision=0, step=1))
self.setattr_dataset("cool_f", 230*MHz)
self.setattr_dataset("detect_f", 220*MHz)

View File

@ -97,7 +97,7 @@ class SpeedBenchmark(EnvExperiment):
"CoreSend1MB",
"CorePrimes"]))
self.setattr_argument("nruns", NumberValue(10, min=1, max=1000,
ndecimals=0, step=1))
precision=0, step=1))
self.setattr_device("core")
self.setattr_device("scheduler")

View File

@ -45,13 +45,13 @@ class ArgumentsDemo(EnvExperiment):
PYONValue(self.get_dataset("foo", default=42)))
self.setattr_argument("number", NumberValue(42e-6,
unit="us",
ndecimals=4))
precision=4))
self.setattr_argument("integer", NumberValue(42,
step=1, ndecimals=0))
step=1, precision=0))
self.setattr_argument("string", StringValue("Hello World"))
self.setattr_argument("scan", Scannable(global_max=400,
default=NoScan(325),
ndecimals=6))
precision=6))
self.setattr_argument("boolean", BooleanValue(True), "Group")
self.setattr_argument("enum", EnumerationValue(
["foo", "bar", "quux"], "foo"), "Group")

View File

@ -320,6 +320,7 @@ dependencies = [
"build_misoc",
"byteorder",
"cslice",
"dyld",
"eh",
"failure",
"failure_derive",

View File

@ -17,7 +17,7 @@ void send_to_rtio_log(struct slice data);
#define KERNELCPU_EXEC_ADDRESS 0x45000000
#define KERNELCPU_PAYLOAD_ADDRESS 0x45060000
#define KERNELCPU_LAST_ADDRESS 0x4fffffff
#define KSUPPORT_HEADER_SIZE 0x80
#define KSUPPORT_HEADER_SIZE 0x74
FILE *stderr;

View File

@ -35,16 +35,6 @@ SECTIONS
*(.text .text.*)
} :text
/* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */
.got : {
PROVIDE(_GLOBAL_OFFSET_TABLE_ = .);
*(.got)
} :text
.got.plt : {
*(.got.plt)
} :text
.rodata :
{
*(.rodata .rodata.*)

View File

@ -24,3 +24,4 @@ proto_artiq = { path = "../libproto_artiq" }
[features]
uart_console = []
alloc = []

View File

@ -0,0 +1,70 @@
use spi;
use board_misoc::{csr, clock};
const DATA_CTRL_REG : u8 = 0x02;
const IRCML_REG : u8 = 0x05;
const QRCML_REG : u8 = 0x08;
const CLKMODE_REG : u8 = 0x14;
const VERSION_REG : u8 = 0x1F;
fn hard_reset() {
unsafe {
// Min Pulse Width: 50ns
csr::dac_rst::out_write(1);
clock::spin_us(1);
csr::dac_rst::out_write(0);
}
}
fn spi_setup(dac_sel: u8, half_duplex: bool, end: bool) -> Result<(), &'static str> {
// Clear the cs_polarity and cs config
spi::set_config(0, 0, 8, 64, 0b1111)?;
spi::set_config(0, 1 << 3, 8, 64, (7 - dac_sel) << 1)?;
spi::set_config(0, (half_duplex as u8) << 7 | (end as u8) << 1, 8, 64, 0b0001)?;
Ok(())
}
fn write(dac_sel: u8, reg: u8, val: u8) -> Result<(), &'static str> {
spi_setup(dac_sel, false, false)?;
spi::write(0, (reg as u32) << 24)?;
spi_setup(dac_sel, false, true)?;
spi::write(0, (val as u32) << 24)?;
Ok(())
}
fn read(dac_sel: u8, reg: u8) -> Result<u8, &'static str> {
spi_setup(dac_sel, false, false)?;
spi::write(0, ((reg | 1 << 7) as u32) << 24)?;
spi_setup(dac_sel, true, true)?;
spi::write(0, 0)?;
Ok(spi::read(0)? as u8)
}
pub fn init() -> Result<(), &'static str> {
hard_reset();
for channel in 0..8 {
let reg = read(channel, VERSION_REG)?;
if reg != 0x0A {
debug!("DAC AD9117 Channel {} has incorrect hardware version. VERSION reg: {:02x}", channel, reg);
return Err("DAC AD9117 hardware version is not equal to 0x0A");
}
let reg = read(channel, CLKMODE_REG)?;
if reg >> 4 & 1 != 0 {
debug!("DAC AD9117 Channel {} retiming fails. CLKMODE reg: {:02x}", channel, reg);
return Err("DAC AD9117 retiming failure");
}
// Set the DACs input data format to be twos complement
// Set IFIRST and IRISING to True
write(channel, DATA_CTRL_REG, 1 << 7 | 1 << 5 | 1 << 4)?;
// Enable internal common mode resistors of both channels
write(channel, IRCML_REG, 1 << 7)?;
write(channel, QRCML_REG, 1 << 7)?;
}
Ok(())
}

View File

@ -0,0 +1,219 @@
use board_misoc::{csr, clock, config};
#[cfg(feature = "alloc")]
use alloc::format;
struct SerdesConfig {
pub delay: [u8; 4],
}
impl SerdesConfig {
pub fn as_bytes(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
(self as *const SerdesConfig) as *const u8,
core::mem::size_of::<SerdesConfig>(),
)
}
}
}
fn select_lane(lane_no: u8) {
unsafe {
csr::eem_transceiver::lane_sel_write(lane_no);
}
}
fn apply_delay(tap: u8) {
unsafe {
csr::eem_transceiver::dly_cnt_in_write(tap);
csr::eem_transceiver::dly_ld_write(1);
clock::spin_us(1);
assert!(tap as u8 == csr::eem_transceiver::dly_cnt_out_read());
}
}
fn apply_config(config: &SerdesConfig) {
for lane_no in 0..4 {
select_lane(lane_no as u8);
apply_delay(config.delay[lane_no]);
}
}
unsafe fn assign_delay() -> SerdesConfig {
// Select an appropriate delay for lane 0
select_lane(0);
let read_align = |dly: u8| -> f32 {
apply_delay(dly);
csr::eem_transceiver::counter_reset_write(1);
csr::eem_transceiver::counter_enable_write(1);
clock::spin_us(2000);
csr::eem_transceiver::counter_enable_write(0);
let (high, low) = (
csr::eem_transceiver::counter_high_count_read(),
csr::eem_transceiver::counter_low_count_read(),
);
if csr::eem_transceiver::counter_overflow_read() == 1 {
panic!("Unexpected phase detector counter overflow");
}
low as f32 / (low + high) as f32
};
let mut best_dly = None;
loop {
let mut prev = None;
for curr_dly in 0..32 {
let curr_low_rate = read_align(curr_dly);
if let Some(prev_low_rate) = prev {
// This is potentially a crossover position
if prev_low_rate <= curr_low_rate && curr_low_rate >= 0.5 {
let prev_dev = 0.5 - prev_low_rate;
let curr_dev = curr_low_rate - 0.5;
let selected_idx = if prev_dev < curr_dev {
curr_dly - 1
} else {
curr_dly
};
// The setup setup/hold calibration timing (even with
// tolerance) might be invalid in other lanes due to skew.
// 5 taps is very conservative, generally it is 1 or 2
if selected_idx < 5 {
prev = None;
continue;
} else {
best_dly = Some(selected_idx);
break;
}
}
}
// Only rising slope from <= 0.5 can result in a rising low rate
// crossover at 50%.
if curr_low_rate <= 0.5 {
prev = Some(curr_low_rate);
}
}
if best_dly.is_none() {
error!("setup/hold timing calibration failed, retry in 1s...");
clock::spin_us(1_000_000);
} else {
break;
}
}
let best_dly = best_dly.unwrap();
apply_delay(best_dly);
let mut delay_list = [best_dly; 4];
// Assign delay for other lanes
for lane_no in 1..=3 {
select_lane(lane_no as u8);
let mut min_deviation = 0.5;
let mut min_idx = 0;
for dly_delta in -3..=3 {
let index = (best_dly as isize + dly_delta) as u8;
let low_rate = read_align(index);
// abs() from f32 is not available in core library
let deviation = if low_rate < 0.5 {
0.5 - low_rate
} else {
low_rate - 0.5
};
if deviation < min_deviation {
min_deviation = deviation;
min_idx = index;
}
}
apply_delay(min_idx);
delay_list[lane_no] = min_idx;
}
debug!("setup/hold timing calibration: {:?}", delay_list);
SerdesConfig {
delay: delay_list,
}
}
unsafe fn align_comma() {
loop {
for slip in 1..=10 {
// The soft transceiver has 2 8b10b decoders, which receives lane
// 0/1 and lane 2/3 respectively. The decoder are time-multiplexed
// to decode exactly 1 lane each sysclk cycle.
//
// The decoder decodes lane 0/2 data on odd sysclk cycles, buffer
// on even cycles, and vice versa for lane 1/3. Data/Clock latency
// could change timing. The extend bit flips the decoding timing,
// so lane 0/2 data are decoded on even cycles, and lane 1/3 data
// are decoded on odd cycles.
//
// This is needed because transmitting/receiving a 8b10b character
// takes 2 sysclk cycles. Adjusting bitslip only via ISERDES
// limits the range to 1 cycle. The wordslip bit extends the range
// to 2 sysclk cycles.
csr::eem_transceiver::wordslip_write((slip > 5) as u8);
// Apply a double bitslip since the ISERDES is 2x oversampled.
// Bitslip is used for comma alignment purposes once setup/hold
// timing is met.
csr::eem_transceiver::bitslip_write(1);
csr::eem_transceiver::bitslip_write(1);
clock::spin_us(1);
csr::eem_transceiver::comma_align_reset_write(1);
clock::spin_us(100);
if csr::eem_transceiver::comma_read() == 1 {
debug!("comma alignment completed after {} bitslips", slip);
return;
}
}
error!("comma alignment failed, retrying in 1s...");
clock::spin_us(1_000_000);
}
}
pub fn init() {
for trx_no in 0..csr::CONFIG_EEM_DRTIO_COUNT {
unsafe {
csr::eem_transceiver::transceiver_sel_write(trx_no as u8);
}
let key = format!("eem_drtio_delay{}", trx_no);
config::read(&key, |r| {
match r {
Ok(record) => {
info!("loading calibrated timing values from flash");
unsafe {
apply_config(&*(record.as_ptr() as *const SerdesConfig));
}
},
Err(_) => {
info!("calibrating...");
let config = unsafe { assign_delay() };
config::write(&key, config.as_bytes()).unwrap();
}
}
});
unsafe {
align_comma();
csr::eem_transceiver::rx_ready_write(1);
}
}
}

View File

@ -12,6 +12,8 @@ extern crate log;
extern crate io;
extern crate board_misoc;
extern crate proto_artiq;
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod spi;
@ -29,3 +31,9 @@ pub mod grabber;
#[cfg(has_drtio)]
pub mod drtioaux;
pub mod drtio_routing;
#[cfg(all(has_drtio_eem, feature = "alloc"))]
pub mod drtio_eem;
#[cfg(soc_platform = "efc")]
pub mod ad9117;

View File

@ -2,9 +2,11 @@
mod imp {
use board_misoc::csr;
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), ()> {
const INVALID_BUS: &'static str = "Invalid SPI bus";
pub fn set_config(busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), &'static str> {
if busno != 0 {
return Err(())
return Err(INVALID_BUS)
}
unsafe {
while csr::converter_spi::writable_read() == 0 {}
@ -31,9 +33,9 @@ mod imp {
Ok(())
}
pub fn write(busno: u8, data: u32) -> Result<(), ()> {
pub fn write(busno: u8, data: u32) -> Result<(), &'static str> {
if busno != 0 {
return Err(())
return Err(INVALID_BUS)
}
unsafe {
while csr::converter_spi::writable_read() == 0 {}
@ -42,9 +44,9 @@ mod imp {
Ok(())
}
pub fn read(busno: u8) -> Result<u32, ()> {
pub fn read(busno: u8) -> Result<u32, &'static str> {
if busno != 0 {
return Err(())
return Err(INVALID_BUS)
}
Ok(unsafe {
while csr::converter_spi::writable_read() == 0 {}

View File

@ -81,6 +81,31 @@ impl IoExpander {
Ok(io_expander)
}
#[cfg(soc_platform = "efc")]
pub fn new() -> Result<Self, &'static str> {
const VIRTUAL_LED_MAPPING: [(u8, u8, u8); 2] = [(0, 0, 5), (1, 0, 6)];
let io_expander = IoExpander {
busno: 0,
port: 1,
address: 0x40,
virtual_led_mapping: &VIRTUAL_LED_MAPPING,
iodir: [0xff; 2],
out_current: [0; 2],
out_target: [0; 2],
registers: Registers {
iodira: 0x00,
iodirb: 0x01,
gpioa: 0x12,
gpiob: 0x13,
},
};
if !io_expander.check_ack()? {
return Err("MCP23017 io expander not found.");
};
Ok(io_expander)
}
#[cfg(soc_platform = "kasli")]
fn select(&self) -> Result<(), &'static str> {
let mask: u16 = 1 << self.port;
@ -89,6 +114,13 @@ impl IoExpander {
Ok(())
}
#[cfg(soc_platform = "efc")]
fn select(&self) -> Result<(), &'static str> {
let mask: u16 = 1 << self.port;
i2c::switch_select(self.busno, 0x70, mask as u8)?;
Ok(())
}
fn write(&self, addr: u8, value: u8) -> Result<(), &'static str> {
i2c::start(self.busno)?;
i2c::write(self.busno, self.address)?;

View File

@ -40,7 +40,7 @@ pub mod ethmac;
pub mod i2c;
#[cfg(soc_platform = "kasli")]
pub mod i2c_eeprom;
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
#[cfg(any(all(soc_platform = "kasli", hw_rev = "v2.0"), soc_platform = "efc"))]
pub mod io_expander;
#[cfg(all(has_ethmac, feature = "smoltcp"))]
pub mod net_settings;

View File

@ -8,9 +8,6 @@ mod ddr {
DFII_COMMAND_WRDATA, DFII_COMMAND_RDDATA};
use sdram_phy::{DFII_NPHASES, DFII_PIX_DATA_SIZE, DFII_PIX_WRDATA_ADDR, DFII_PIX_RDDATA_ADDR};
#[cfg(kusddrphy)]
const DDRPHY_MAX_DELAY: u16 = 512;
#[cfg(not(kusddrphy))]
const DDRPHY_MAX_DELAY: u16 = 32;
const DQS_SIGNAL_COUNT: usize = DFII_PIX_DATA_SIZE / 2;
@ -35,17 +32,12 @@ mod ddr {
#[cfg(ddrphy_wlevel)]
unsafe fn write_level_scan(logger: &mut Option<&mut dyn fmt::Write>) {
#[cfg(kusddrphy)]
log!(logger, "DQS initial delay: {} taps\n", ddrphy::wdly_dqs_taps_read());
log!(logger, "Write leveling scan:\n");
enable_write_leveling(true);
spin_cycles(100);
#[cfg(not(kusddrphy))]
let ddrphy_max_delay : u16 = DDRPHY_MAX_DELAY;
#[cfg(kusddrphy)]
let ddrphy_max_delay : u16 = DDRPHY_MAX_DELAY - ddrphy::wdly_dqs_taps_read();
for n in 0..DQS_SIGNAL_COUNT {
let dq_addr = dfii::PI0_RDDATA_ADDR
@ -57,10 +49,6 @@ mod ddr {
ddrphy::wdly_dq_rst_write(1);
ddrphy::wdly_dqs_rst_write(1);
#[cfg(kusddrphy)]
for _ in 0..ddrphy::wdly_dqs_taps_read() {
ddrphy::wdly_dqs_inc_write(1);
}
let mut dq;
for _ in 0..ddrphy_max_delay {
@ -88,17 +76,12 @@ mod ddr {
unsafe fn write_level(logger: &mut Option<&mut dyn fmt::Write>,
delay: &mut [u16; DQS_SIGNAL_COUNT],
high_skew: &mut [bool; DQS_SIGNAL_COUNT]) -> bool {
#[cfg(kusddrphy)]
log!(logger, "DQS initial delay: {} taps\n", ddrphy::wdly_dqs_taps_read());
log!(logger, "Write leveling: ");
enable_write_leveling(true);
spin_cycles(100);
#[cfg(not(kusddrphy))]
let ddrphy_max_delay : u16 = DDRPHY_MAX_DELAY;
#[cfg(kusddrphy)]
let ddrphy_max_delay : u16 = DDRPHY_MAX_DELAY - ddrphy::wdly_dqs_taps_read();
let mut failed = false;
for n in 0..DQS_SIGNAL_COUNT {
@ -112,10 +95,6 @@ mod ddr {
ddrphy::wdly_dq_rst_write(1);
ddrphy::wdly_dqs_rst_write(1);
#[cfg(kusddrphy)]
for _ in 0..ddrphy::wdly_dqs_taps_read() {
ddrphy::wdly_dqs_inc_write(1);
}
ddrphy::wlevel_strobe_write(1);
spin_cycles(10);
@ -146,11 +125,6 @@ mod ddr {
dq = ptr::read_volatile(dq_addr);
}
// Get a bit further into the 0 zone
#[cfg(kusddrphy)]
for _ in 0..32 {
incr_delay();
}
}
while dq == 0 {
@ -191,9 +165,6 @@ mod ddr {
if delay[n] > threshold {
ddrphy::dly_sel_write(1 << n);
#[cfg(kusddrphy)]
ddrphy::rdly_dq_bitslip_write(1);
#[cfg(not(kusddrphy))]
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
}
@ -246,7 +217,7 @@ mod ddr {
ddrphy::dly_sel_write(1 << (DQS_SIGNAL_COUNT - n - 1));
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
#[cfg(any(soc_platform = "kasli", soc_platform = "efc"))]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
@ -337,7 +308,7 @@ mod ddr {
let mut max_seen_valid = 0;
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
#[cfg(any(soc_platform = "kasli", soc_platform = "efc"))]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
@ -400,7 +371,7 @@ mod ddr {
// Set delay to the middle
ddrphy::rdly_dq_rst_write(1);
#[cfg(soc_platform = "kasli")]
#[cfg(any(soc_platform = "kasli", soc_platform = "efc"))]
{
for _ in 0..3 {
ddrphy::rdly_dq_bitslip_write(1);
@ -451,13 +422,9 @@ pub unsafe fn init(mut _logger: Option<&mut dyn fmt::Write>) -> bool {
#[cfg(has_ddrphy)]
{
#[cfg(kusddrphy)]
csr::ddrphy::en_vtc_write(0);
if !ddr::level(&mut _logger) {
return false
}
#[cfg(kusddrphy)]
csr::ddrphy::en_vtc_write(1);
}
csr::dfii::control_write(sdram_phy::DFII_CONTROL_SEL);

View File

@ -5,7 +5,7 @@ use elf::*;
pub mod elf;
fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> {
pub fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> {
if data.len() < offset + mem::size_of::<T>() {
Err(())
} else {
@ -75,6 +75,25 @@ impl<'a> fmt::Display for Error<'a> {
}
}
pub fn is_elf_for_current_arch(ehdr: &Elf32_Ehdr, e_type: u16) -> bool {
const IDENT: [u8; EI_NIDENT] = [
ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE,
/* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0
];
if ehdr.e_ident != IDENT { return false; }
if ehdr.e_type != e_type { return false; }
#[cfg(target_arch = "riscv32")]
const ARCH: u16 = EM_RISCV;
#[cfg(not(target_arch = "riscv32"))]
const ARCH: u16 = EM_NONE;
if ehdr.e_machine != ARCH { return false; }
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arch {
RiscV,
@ -268,25 +287,16 @@ impl<'a> Library<'a> {
let ehdr = read_unaligned::<Elf32_Ehdr>(data, 0)
.map_err(|()| "cannot read ELF header")?;
const IDENT: [u8; EI_NIDENT] = [
ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE,
/* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0
];
#[cfg(target_arch = "riscv32")]
const ARCH: u16 = EM_RISCV;
#[cfg(not(target_arch = "riscv32"))]
const ARCH: u16 = EM_NONE;
if !is_elf_for_current_arch(&ehdr, ET_DYN) {
return Err("not a shared library for current architecture")?
}
#[cfg(all(target_feature = "f", target_feature = "d"))]
const FLAGS: u32 = EF_RISCV_FLOAT_ABI_DOUBLE;
#[cfg(not(all(target_feature = "f", target_feature = "d")))]
const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT;
if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS {
return Err("not a shared library for current architecture")?
if ehdr.e_flags != FLAGS {
return Err("unexpected flags for shared library (wrong floating point ABI?)")?
}
let mut dyn_off = None;

View File

@ -5,7 +5,10 @@ use dyld;
pub const KERNELCPU_EXEC_ADDRESS: usize = 0x45000000;
pub const KERNELCPU_PAYLOAD_ADDRESS: usize = 0x45060000;
pub const KERNELCPU_LAST_ADDRESS: usize = 0x4fffffff;
pub const KSUPPORT_HEADER_SIZE: usize = 0x80;
// Must match the offset of the first (starting at KERNELCPU_EXEC_ADDRESS)
// section in ksupport.elf.
pub const KSUPPORT_HEADER_SIZE: usize = 0x74;
#[derive(Debug)]
pub enum Message<'a> {

View File

@ -112,8 +112,12 @@ unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
Tag::String | Tag::Bytes | Tag::ByteArray => {
consume_value!(CMutSlice<u8>, |ptr| {
let length = reader.read_u32()? as usize;
if length > 0 {
*ptr = CMutSlice::new(alloc(length)? as *mut u8, length);
reader.read_exact((*ptr).as_mut())?;
} else {
*ptr = CMutSlice::new(core::ptr::NonNull::<u8>::dangling().as_ptr(), 0);
}
Ok(())
})
}

View File

@ -19,13 +19,14 @@ byteorder = { version = "1.0", default-features = false }
cslice = { version = "0.3" }
log = { version = "0.4", default-features = false }
managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] }
dyld = { path = "../libdyld" }
eh = { path = "../libeh" }
unwind_backtrace = { path = "../libunwind_backtrace" }
io = { path = "../libio", features = ["byteorder"] }
alloc_list = { path = "../liballoc_list" }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
logger_artiq = { path = "../liblogger_artiq" }
board_artiq = { path = "../libboard_artiq" }
board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
riscv = { version = "0.6.0", features = ["inline-asm"] }

View File

@ -1,5 +1,5 @@
use core::ptr;
use board_misoc::csr;
use core::{ptr, slice};
use mailbox;
use rpc_queue;
@ -13,15 +13,20 @@ pub unsafe fn start() {
stop();
extern {
extern "C" {
static _binary____ksupport_ksupport_elf_start: u8;
static _binary____ksupport_ksupport_elf_end: u8;
}
let ksupport_start = &_binary____ksupport_ksupport_elf_start as *const _;
let ksupport_end = &_binary____ksupport_ksupport_elf_end as *const _;
ptr::copy_nonoverlapping(ksupport_start,
(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as *mut u8,
ksupport_end as usize - ksupport_start as usize);
let ksupport_elf_start = &_binary____ksupport_ksupport_elf_start as *const u8;
let ksupport_elf_end = &_binary____ksupport_ksupport_elf_end as *const u8;
let ksupport_elf = slice::from_raw_parts(
ksupport_elf_start,
ksupport_elf_end as usize - ksupport_elf_start as usize,
);
if let Err(msg) = load_image(&ksupport_elf) {
panic!("failed to load kernel CPU image (ksupport.elf): {}", msg);
}
csr::kernel_cpu::reset_write(0);
@ -41,6 +46,44 @@ pub unsafe fn stop() {
rpc_queue::init();
}
/// Loads the given image for execution on the kernel CPU.
///
/// The entire image including the headers is copied into memory for later use by libunwind, but
/// placed such that the text section ends up at the right location in memory. Currently, we just
/// hard-code the address range, but at least verify that this matches the ELF program header given
/// in the image (avoids loading the non-relocatable code at the wrong address on toolchain/…
/// changes).
unsafe fn load_image(image: &[u8]) -> Result<(), &'static str> {
use dyld::elf::*;
use dyld::{is_elf_for_current_arch, read_unaligned};
let ehdr = read_unaligned::<Elf32_Ehdr>(image, 0).map_err(|()| "could not read ELF header")?;
// The check assumes the two CPUs share the same architecture. This is just to avoid inscrutable
// errors; we do not functionally rely on this.
if !is_elf_for_current_arch(&ehdr, ET_EXEC) {
return Err("not an executable for kernel CPU architecture");
}
// First program header should be the main text/… LOAD (see ksupport.ld).
let phdr = read_unaligned::<Elf32_Phdr>(image, ehdr.e_phoff as usize)
.map_err(|()| "could not read program header")?;
if phdr.p_type != PT_LOAD {
return Err("unexpected program header type");
}
if phdr.p_vaddr + phdr.p_memsz > KERNELCPU_LAST_ADDRESS as u32 {
// This is a weak sanity check only; we also need to fit in the stack, etc.
return Err("too large for kernel CPU address range");
}
const TARGET_ADDRESS: u32 = (KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE) as _;
if phdr.p_vaddr - phdr.p_offset != TARGET_ADDRESS {
return Err("unexpected load address/offset");
}
ptr::copy_nonoverlapping(image.as_ptr(), TARGET_ADDRESS as *mut u8, image.len());
Ok(())
}
pub fn validate(ptr: usize) -> bool {
ptr >= KERNELCPU_EXEC_ADDRESS && ptr <= KERNELCPU_LAST_ADDRESS
}

View File

@ -1,6 +1,7 @@
#![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by)]
#![no_std]
extern crate dyld;
extern crate eh;
#[macro_use]
extern crate alloc;
@ -39,6 +40,8 @@ use board_artiq::drtioaux;
use board_artiq::drtio_routing;
use board_artiq::{mailbox, rpc_queue};
use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_proto};
#[cfg(has_drtio_eem)]
use board_artiq::drtio_eem;
#[cfg(has_rtio_analyzer)]
use proto_artiq::analyzer_proto;
@ -125,6 +128,9 @@ fn startup() {
}
rtio_clocking::init();
#[cfg(has_drtio_eem)]
drtio_eem::init();
let mut net_device = unsafe { ethmac::EthernetDevice::new() };
net_device.reset_phy_if_any();

View File

@ -259,14 +259,17 @@ pub fn init() {
unsafe {
// clock switch and reboot will begin after TX is initialized
// and TX will be initialized after this
csr::drtio_transceiver::stable_clkin_write(1);
csr::gt_drtio::stable_clkin_write(1);
}
loop {}
}
else {
// enable TX after the reboot, with stable clock
unsafe {
csr::drtio_transceiver::txenable_write(0xffffffffu32 as _);
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
#[cfg(has_drtio_eem)]
csr::eem_transceiver::txenable_write(0xffffffffu32 as _);
}
}
}

View File

@ -15,7 +15,7 @@ build_misoc = { path = "../libbuild_misoc" }
[dependencies]
log = { version = "0.4", default-features = false }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] }
board_artiq = { path = "../libboard_artiq" }
board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
alloc_list = { path = "../liballoc_list" }
riscv = { version = "0.6.0", features = ["inline-asm"] }
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }

View File

@ -15,8 +15,12 @@ use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp};
#[cfg(has_si5324)]
use board_artiq::si5324;
use board_artiq::{spi, drtioaux};
#[cfg(soc_platform = "efc")]
use board_artiq::ad9117;
use board_artiq::drtio_routing;
use proto_artiq::drtioaux_proto::ANALYZER_MAX_SIZE;
#[cfg(has_drtio_eem)]
use board_artiq::drtio_eem;
use riscv::register::{mcause, mepc, mtval};
use dma::Manager as DmaManager;
use analyzer::Analyzer;
@ -461,6 +465,7 @@ const SI5324_SETTINGS: si5324::FrequencySettings
crystal_as_ckin2: true
};
#[cfg(not(soc_platform = "efc"))]
fn sysclk_setup() {
let switched = unsafe {
csr::crg::switch_done_read()
@ -476,7 +481,7 @@ fn sysclk_setup() {
// delay for clean UART log, wait until UART FIFO is empty
clock::spin_us(1300);
unsafe {
csr::drtio_transceiver::stable_clkin_write(1);
csr::gt_drtio::stable_clkin_write(1);
}
loop {}
}
@ -527,14 +532,42 @@ pub extern fn main() -> i32 {
io_expander1.service().unwrap();
}
#[cfg(not(soc_platform = "efc"))]
sysclk_setup();
#[cfg(soc_platform = "efc")]
let mut io_expander;
#[cfg(soc_platform = "efc")]
{
io_expander = board_misoc::io_expander::IoExpander::new().unwrap();
// Enable LEDs
io_expander.set_oe(0, 1 << 5 | 1 << 6 | 1 << 7).unwrap();
// Enable VADJ and P3V3_FMC
io_expander.set_oe(1, 1 << 0 | 1 << 1).unwrap();
io_expander.set(1, 0, true);
io_expander.set(1, 1, true);
io_expander.service().unwrap();
}
#[cfg(not(has_drtio_eem))]
unsafe {
csr::drtio_transceiver::txenable_write(0xffffffffu32 as _);
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
}
#[cfg(has_drtio_eem)]
unsafe {
csr::eem_transceiver::txenable_write(0xffffffffu32 as _);
}
init_rtio_crg();
#[cfg(has_drtio_eem)]
drtio_eem::init();
#[cfg(has_drtio_routing)]
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
#[cfg(not(has_drtio_routing))]
@ -547,6 +580,9 @@ pub extern fn main() -> i32 {
let mut hardware_tick_ts = 0;
#[cfg(soc_platform = "efc")]
ad9117::init().expect("AD9117 initialization failed");
loop {
while !drtiosat_link_rx_up() {
drtiosat_process_errors();
@ -558,6 +594,8 @@ pub extern fn main() -> i32 {
io_expander0.service().expect("I2C I/O expander #0 service failed");
io_expander1.service().expect("I2C I/O expander #1 service failed");
}
#[cfg(soc_platform = "efc")]
io_expander.service().expect("I2C I/O expander service failed");
hardware_tick(&mut hardware_tick_ts);
}
@ -591,6 +629,8 @@ pub extern fn main() -> i32 {
io_expander0.service().expect("I2C I/O expander #0 service failed");
io_expander1.service().expect("I2C I/O expander #1 service failed");
}
#[cfg(soc_platform = "efc")]
io_expander.service().expect("I2C I/O expander service failed");
hardware_tick(&mut hardware_tick_ts);
if drtiosat_tsc_loaded() {
info!("TSC loaded from uplink");
@ -621,6 +661,24 @@ pub extern fn main() -> i32 {
}
}
#[cfg(soc_platform = "efc")]
fn enable_error_led() {
let mut io_expander = board_misoc::io_expander::IoExpander::new().unwrap();
// Keep LEDs enabled
io_expander.set_oe(0, 1 << 5 | 1 << 6 | 1 << 7).unwrap();
// Enable Error LED
io_expander.set(0, 7, true);
// Keep VADJ and P3V3_FMC enabled
io_expander.set_oe(1, 1 << 0 | 1 << 1).unwrap();
io_expander.set(1, 0, true);
io_expander.set(1, 1, true);
io_expander.service().unwrap();
}
#[no_mangle]
pub extern fn exception(_regs: *const u32) {
let pc = mepc::read();
@ -660,8 +718,16 @@ pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
if let Some(location) = info.location() {
print!("panic at {}:{}:{}", location.file(), location.line(), location.column());
#[cfg(soc_platform = "efc")]
{
if location.file() != "libboard_misoc/io_expander.rs" {
enable_error_led();
}
}
} else {
print!("panic at unknown location");
#[cfg(soc_platform = "efc")]
enable_error_led();
}
if let Some(message) = info.message() {
println!(": {}", message);

View File

@ -14,6 +14,7 @@ import sys
import os
from operator import itemgetter
from dateutil.parser import parse as parse_date
import numpy as np
from prettytable import PrettyTable
@ -22,7 +23,7 @@ from sipyco.sync_struct import Subscriber
from sipyco.broadcast import Receiver
from sipyco import common_args, pyon
from artiq.tools import short_format, parse_arguments
from artiq.tools import scale_from_metadata, short_format, parse_arguments
from artiq import __version__ as artiq_version
@ -187,7 +188,12 @@ def _action_set_dataset(remote, args):
metadata["scale"] = args.scale
if args.precision is not None:
metadata["precision"] = args.precision
remote.set(args.name, pyon.decode(args.value), persist, metadata)
scale = scale_from_metadata(metadata)
value = pyon.decode(args.value)
t = type(value)
if np.issubdtype(t, np.number) or t is np.ndarray:
value = value * scale
remote.set(args.name, value, persist, metadata)
def _action_del_dataset(remote, args):

View File

@ -59,7 +59,7 @@ Prerequisites:
help="SSH host to jump through")
parser.add_argument("-t", "--target", default="kasli",
help="target board, default: %(default)s, one of: "
"kasli kc705")
"kasli efc kc705")
parser.add_argument("-I", "--preinit-command", default=[], action="append",
help="add a pre-initialization OpenOCD command. "
"Useful for selecting a board when several are connected.")
@ -215,9 +215,28 @@ class ProgrammerXC7(Programmer):
Programmer.__init__(self, client, preinit_script)
self._proxy = proxy
if board != "efc":
add_commands(self._board_script,
"source {boardfile}",
boardfile=self._transfer_script("board/{}.cfg".format(board)))
else:
add_commands(self._board_script,
# OpenOCD does not have the efc board file so custom script is included.
# To be used with Digilent-HS2 Programming Cable but the config in digilent-hs2.cfg is wrong
# See digilent_jtag_smt2_nc.cfg for details
"source [find interface/ftdi/digilent_jtag_smt2_nc.cfg]",
"ftdi tdo_sample_edge falling",
"reset_config none",
"transport select jtag",
"adapter speed 25000",
"source [find cpld/xilinx-xc7.cfg]",
"source [find cpld/jtagspi.cfg]",
"source [find fpga/xilinx-xadc.cfg]",
"source [find fpga/xilinx-dna.cfg]"
)
self.add_flash_bank("spi0", "xc7", index=0)
add_commands(self._script, "xadc_report xc7.tap")
@ -242,6 +261,13 @@ def main():
"storage": ("spi0", 0x440000),
"firmware": ("spi0", 0x450000),
},
"efc": {
"programmer": partial(ProgrammerXC7, board="efc", proxy="bscan_spi_xc7a100t.bit"),
"gateware": ("spi0", 0x000000),
"bootloader": ("spi0", 0x400000),
"storage": ("spi0", 0x440000),
"firmware": ("spi0", 0x450000),
},
"kc705": {
"programmer": partial(ProgrammerXC7, board="kc705", proxy="bscan_spi_xc7k325t.bit"),
"gateware": ("spi0", 0x000000),

View File

@ -27,9 +27,10 @@ class ChannelInterface:
class TransceiverInterface(AutoCSR):
def __init__(self, channel_interfaces):
def __init__(self, channel_interfaces, *, async_rx=True):
self.stable_clkin = CSRStorage()
self.txenable = CSRStorage(len(channel_interfaces))
if async_rx:
for i in range(len(channel_interfaces)):
name = "rtio_rx" + str(i)
setattr(self.clock_domains, "cd_"+name, ClockDomain(name=name))

View File

@ -2,6 +2,12 @@ from migen import *
from migen.genlib.cdc import ElasticBuffer
class NoRXSynchronizer:
"""To be used when RX is already synchronous (e.g. IOSERDES based PHY)."""
def resync(self, signal):
return signal
class GenericRXSynchronizer(Module):
"""Simple RX synchronizer based on the portable Migen elastic buffer.

View File

@ -0,0 +1,536 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.cores.code_8b10b import SingleEncoder, Decoder
from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface
class RXSerdes(Module):
def __init__(self, i_pads):
self.rxdata = [ Signal(10) for _ in range(4) ]
self.ld = [ Signal() for _ in range(4) ]
self.cnt_in = [ Signal(5) for _ in range(4) ]
self.cnt_out = [ Signal(5) for _ in range(4) ]
self.bitslip = [ Signal() for _ in range(4) ]
self.o = [ Signal() for _ in range(4) ]
ser_in_no_dly = [ Signal() for _ in range(4) ]
ser_in = [ Signal() for _ in range(4) ]
shifts = [ Signal(2) for _ in range(4) ]
for i in range(4):
self.specials += [
# Master deserializer
Instance("ISERDESE2",
p_DATA_RATE="DDR",
p_DATA_WIDTH=10,
p_INTERFACE_TYPE="NETWORKING",
p_NUM_CE=1,
p_SERDES_MODE="MASTER",
p_IOBDELAY="IFD",
o_Q1=self.rxdata[i][9],
o_Q2=self.rxdata[i][8],
o_Q3=self.rxdata[i][7],
o_Q4=self.rxdata[i][6],
o_Q5=self.rxdata[i][5],
o_Q6=self.rxdata[i][4],
o_Q7=self.rxdata[i][3],
o_Q8=self.rxdata[i][2],
o_O=self.o[i],
o_SHIFTOUT1=shifts[i][0],
o_SHIFTOUT2=shifts[i][1],
i_DDLY=ser_in[i],
i_BITSLIP=self.bitslip[i],
i_CLK=ClockSignal("sys5x"),
i_CLKB=~ClockSignal("sys5x"),
i_CE1=1,
i_RST=ResetSignal(),
i_CLKDIV=ClockSignal()),
# Slave deserializer
Instance("ISERDESE2",
p_DATA_RATE="DDR",
p_DATA_WIDTH=10,
p_INTERFACE_TYPE="NETWORKING",
p_NUM_CE=1,
p_SERDES_MODE="SLAVE",
p_IOBDELAY="IFD",
o_Q3=self.rxdata[i][1],
o_Q4=self.rxdata[i][0],
i_BITSLIP=self.bitslip[i],
i_CLK=ClockSignal("sys5x"),
i_CLKB=~ClockSignal("sys5x"),
i_CE1=1,
i_RST=ResetSignal(),
i_CLKDIV=ClockSignal(),
i_SHIFTIN1=shifts[i][0],
i_SHIFTIN2=shifts[i][1]),
# Tunable delay
# IDELAYCTRL is with the clocking
Instance("IDELAYE2",
p_DELAY_SRC="IDATAIN",
p_SIGNAL_PATTERN="DATA",
p_CINVCTRL_SEL="FALSE",
p_HIGH_PERFORMANCE_MODE="TRUE",
# REFCLK refers to the clock source of IDELAYCTRL
p_REFCLK_FREQUENCY=200.0,
p_PIPE_SEL="FALSE",
p_IDELAY_TYPE="VAR_LOAD",
p_IDELAY_VALUE=0,
i_C=ClockSignal(),
i_LD=self.ld[i],
i_CE=0,
i_LDPIPEEN=0,
i_INC=1, # Always increment
# Set the optimal delay tap via the aligner
i_CNTVALUEIN=self.cnt_in[i],
# Allow the aligner to check the tap value
o_CNTVALUEOUT=self.cnt_out[i],
i_IDATAIN=ser_in_no_dly[i],
o_DATAOUT=ser_in[i]
),
# IOB
Instance("IBUFDS",
p_DIFF_TERM="TRUE",
i_I=i_pads.p[i],
i_IB=i_pads.n[i],
o_O=ser_in_no_dly[i],
)
]
class TXSerdes(Module):
def __init__(self, o_pads):
self.txdata = [ Signal(5) for _ in range(4) ]
ser_out = [ Signal() for _ in range(4) ]
t_out = [ Signal() for _ in range(4) ]
self.ext_rst = Signal()
for i in range(4):
self.specials += [
# Serializer
Instance("OSERDESE2",
p_DATA_RATE_OQ="SDR", p_DATA_RATE_TQ="BUF",
p_DATA_WIDTH=5, p_TRISTATE_WIDTH=1,
p_INIT_OQ=0b00000,
o_OQ=ser_out[i],
o_TQ=t_out[i],
i_RST=ResetSignal() | self.ext_rst,
i_CLK=ClockSignal("sys5x"),
i_CLKDIV=ClockSignal(),
i_D1=self.txdata[i][0],
i_D2=self.txdata[i][1],
i_D3=self.txdata[i][2],
i_D4=self.txdata[i][3],
i_D5=self.txdata[i][4],
i_TCE=1, i_OCE=1,
i_T1=0
),
# IOB
Instance("OBUFTDS",
i_I=ser_out[i],
o_O=o_pads.p[i],
o_OB=o_pads.n[i],
i_T=t_out[i],
)
]
# This module owns 2 8b10b encoders, each encoder route codewords to 2 lanes,
# through time multiplexing. The scheduler releases 2 bytes every clock cycle,
# and the encoders each encode 1 byte.
#
# Since each lane only transmits 5 bits per sysclk cycle, the encoder selects
# a lane to first transmit the least significant word (LSW, 5 bits), and send
# the rest in the next cycle using the same lane. It takes advantage of the
# arrival sequence of bytes from the scrambler to achieve the transmission
# pattern shown in the MultiDecoder module.
class MultiEncoder(Module):
def __init__(self):
# Keep the link layer interface identical to standard encoders
self.d = [ Signal(8) for _ in range(2) ]
self.k = [ Signal() for _ in range(2) ]
# Output interface
self.output = [ [ Signal(5) for _ in range(2) ] for _ in range(2) ]
# Clock enable signal
# Alternate between sending encoded character to EEM 0/2 and EEM 1/3
# every cycle
self.clk_div2 = Signal()
# Intermediate registers for output and disparity
# More significant bits are buffered due to channel geometry
# Disparity bit is delayed. The same encoder is shared by 2 SERDES
output_bufs = [ Signal(5) for _ in range(2) ]
disp_bufs = [ Signal() for _ in range(2) ]
encoders = [ SingleEncoder() for _ in range(2) ]
self.submodules += encoders
# Encoded characters are routed to the EEM pairs:
# The first character goes through EEM 0/2
# The second character goes through EEM 1/3, and repeat...
# Lower order bits go first, so higher order bits are buffered and
# transmitted in the next cycle.
for d, k, output, output_buf, disp_buf, encoder in \
zip(self.d, self.k, self.output, output_bufs, disp_bufs, encoders):
self.comb += [
encoder.d.eq(d),
encoder.k.eq(k),
If(self.clk_div2,
output[0].eq(encoder.output[0:5]),
output[1].eq(output_buf),
).Else(
output[0].eq(output_buf),
output[1].eq(encoder.output[0:5]),
),
]
# Handle intermediate registers
self.sync += [
disp_buf.eq(encoder.disp_out),
encoder.disp_in.eq(disp_buf),
output_buf.eq(encoder.output[5:10]),
]
# Owns 2 8b10b decoders, each decodes data from lane 0/1 and lane 2/3class
# respectively. The decoders are time multiplexed among the 2 lanes, and
# each decoder decodes exactly 1 lane per sysclk cycle.
#
# The transmitter could send the following data pattern over the 4 lanes.
# Capital letters denote the most significant word (MSW); The lowercase denote
# the least significant word (LSW) of the same 8b10b character.
#
# Cycle \ Lane 0 1 2 3
# 0 a Y b Z
# 1 A c B d
# 2 a' C b' D
# 3 A' c' B' d'
#
# Lane 0/2 and lane 1/3 transmit word of different significance by design (see
# MultiEncoder).
#
# This module buffers the LSW, and immediately send the whole 8b10b character
# to the coresponding decoder once the MSW is also received.
class MultiDecoder(Module):
def __init__(self):
self.raw_input = [ Signal(5) for _ in range(2) ]
self.d = Signal(8)
self.k = Signal()
# Clock enable signal
# Alternate between decoding encoded character from EEM 0/2 and
# EEM 1/3 every cycle
self.clk_div2 = Signal()
# Extended bitslip mechanism. ISERDESE2 bitslip can only adjust bit
# position by 5 bits (1 cycle). However, an encoded character takes 2
# cycles to transmit/receive. The module needs to correctly reassemble
# the 8b10b character. This is useful received waveform is the 1-cycle
# delayed version of the above waveform. The same scheme would
# incorrectly buffer words and create wrong symbols.
#
# Hence, wordslip put LSW as MSW and vice versa, effectively injects
# an additional 5 bit positions worth of bitslips.
self.wordslip = Signal()
# Intermediate register for input
buffer = Signal(5)
self.submodules.decoder = Decoder()
# The decoder does the following actions:
# - Process received characters from EEM 0/2
# - Same, but from EEM 1/3
#
# Wordslipping is equivalent to swapping task between clock cycles.
# (i.e. Swap processing target. Instead of processing EEM 0/2, process
# EEM 1/3, and vice versa on the next cycle.) This effectively shifts
# the processing time of any encoded character by 1 clock cycle (5
# bitslip equivalent without considering oversampling, 10 otherwise).
self.sync += [
If(self.clk_div2 ^ self.wordslip,
buffer.eq(self.raw_input[1])
).Else(
buffer.eq(self.raw_input[0])
)
]
self.comb += [
If(self.clk_div2 ^ self.wordslip,
self.decoder.input.eq(Cat(buffer, self.raw_input[0]))
).Else(
self.decoder.input.eq(Cat(buffer, self.raw_input[1]))
)
]
self.comb += [
self.d.eq(self.decoder.d),
self.k.eq(self.decoder.k),
]
class BangBangPhaseDetector(Module):
def __init__(self):
self.s = Signal(3)
self.high = Signal()
self.low = Signal()
self.comb += If(~self.s[0] & self.s[2],
self.high.eq(self.s[1]),
self.low.eq(~self.s[1]),
).Else(
self.high.eq(0),
self.low.eq(0),
)
class PhaseErrorCounter(Module, AutoCSR):
def __init__(self):
self.high_count = CSRStatus(18)
self.low_count = CSRStatus(18)
# Odd indices are always oversampled bits
self.rxdata = Signal(10)
# Measure setup/hold timing, count phase error in the following
self.submodules.detector = BangBangPhaseDetector()
self.comb += self.detector.s.eq(self.rxdata[:3])
self.reset = CSR()
self.enable = CSRStorage()
self.overflow = CSRStatus()
high_carry = Signal()
low_carry = Signal()
self.sync += [
If(self.reset.re,
self.high_count.status.eq(0),
self.low_count.status.eq(0),
high_carry.eq(0),
low_carry.eq(0),
self.overflow.status.eq(0),
).Elif(self.enable.storage,
Cat(self.high_count.status, high_carry).eq(
self.high_count.status + self.detector.high),
Cat(self.low_count.status, low_carry).eq(
self.low_count.status + self.detector.low),
If(high_carry | low_carry, self.overflow.status.eq(1)),
)
]
class SerdesSingle(Module):
def __init__(self, i_pads, o_pads):
# Serdes modules
self.submodules.rx_serdes = RXSerdes(i_pads)
self.submodules.tx_serdes = TXSerdes(o_pads)
self.lane_sel = Signal(2)
self.bitslip = Signal()
for i in range(4):
self.comb += self.rx_serdes.bitslip[i].eq(self.bitslip)
self.dly_cnt_in = Signal(5)
self.dly_ld = Signal()
for i in range(4):
self.comb += [
self.rx_serdes.cnt_in[i].eq(self.dly_cnt_in),
self.rx_serdes.ld[i].eq((self.lane_sel == i) & self.dly_ld),
]
self.dly_cnt_out = Signal(5)
self.comb += Case(self.lane_sel, {
idx: self.dly_cnt_out.eq(self.rx_serdes.cnt_out[idx]) for idx in range(4)
})
self.wordslip = Signal()
# Encoder/Decoder interfaces
self.submodules.encoder = MultiEncoder()
self.submodules.decoders = decoders = Array(MultiDecoder() for _ in range(2))
self.comb += [
decoders[0].wordslip.eq(self.wordslip),
decoders[1].wordslip.eq(self.wordslip),
]
# Route encoded symbols to TXSerdes, decoded symbols from RXSerdes
for i in range(4):
self.comb += [
self.tx_serdes.txdata[i].eq(self.encoder.output[i//2][i%2]),
decoders[i//2].raw_input[i%2].eq(self.rx_serdes.rxdata[i][0::2]),
]
self.clk_div2 = Signal()
self.comb += [
self.encoder.clk_div2.eq(self.clk_div2),
self.decoders[0].clk_div2.eq(self.clk_div2),
self.decoders[1].clk_div2.eq(self.clk_div2),
]
# Monitor lane 0 decoder output for bitslip alignment
self.comma_align_reset = Signal()
self.comma = Signal()
self.sync += If(self.comma_align_reset,
self.comma.eq(0),
).Elif(~self.comma,
self.comma.eq(
((decoders[0].d == 0x3C) | (decoders[0].d == 0xBC))
& decoders[0].k))
class OOBReset(Module):
def __init__(self, iserdes_o):
ce_counter = Signal(13)
activity_ce = Signal()
transition_ce = Signal()
self.sync.clk200 += Cat(ce_counter, activity_ce).eq(ce_counter + 1)
self.comb += transition_ce.eq(ce_counter[0])
idle_low_meta = Signal()
idle_high_meta = Signal()
idle_low = Signal()
idle_high = Signal()
idle = Signal()
self.rst = Signal(reset=1)
# Detect the lack of transitions (idle) within 2 clk200 cycles
self.specials += [
Instance("FDCE", p_INIT=1, i_D=1, i_CLR=iserdes_o,
i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_low_meta,
attr={"async_reg", "ars_ff1"}),
Instance("FDCE", p_INIT=1, i_D=idle_low_meta, i_CLR=0,
i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_low,
attr={"async_reg", "ars_ff2"}),
Instance("FDCE", p_INIT=1, i_D=1, i_CLR=~iserdes_o,
i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_high_meta,
attr={"async_reg", "ars_ff1"}),
Instance("FDCE", p_INIT=1, i_D=idle_high_meta, i_CLR=0,
i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_high,
attr={"async_reg", "ars_ff2"}),
]
# Detect activity for the last 2**13 clk200 cycles
# The 2**13 cycles are fully partitioned into 2**12 time segments of 2
# cycles in duration. If there exists 2-cycle time segment without
# signal level transition, rst is asserted.
self.sync.clk200 += [
If(activity_ce,
idle.eq(0),
self.rst.eq(idle),
),
If(idle_low | idle_high,
idle.eq(1),
self.rst.eq(1),
),
]
class EEMSerdes(Module, TransceiverInterface, AutoCSR):
def __init__(self, platform, data_pads):
self.rx_ready = CSRStorage()
self.transceiver_sel = CSRStorage(max(1, log2_int(len(data_pads))))
self.lane_sel = CSRStorage(2)
self.bitslip = CSR()
self.dly_cnt_in = CSRStorage(5)
self.dly_ld = CSR()
self.dly_cnt_out = CSRStatus(5)
# Slide a word back/forward by 1 cycle, shared by all lanes of the
# same transceiver. This is to determine if this cycle should decode
# lane 0/2 or lane 1/3. See MultiEncoder/MultiDecoder for the full
# scheme & timing.
self.wordslip = CSRStorage()
# Monitor lane 0 decoder output for bitslip alignment
self.comma_align_reset = CSR()
self.comma = CSRStatus()
clk_div2 = Signal()
self.sync += clk_div2.eq(~clk_div2)
channel_interfaces = []
serdes_list = []
for i_pads, o_pads in data_pads:
serdes = SerdesSingle(i_pads, o_pads)
self.comb += serdes.clk_div2.eq(clk_div2)
serdes_list.append(serdes)
chan_if = ChannelInterface(serdes.encoder, serdes.decoders)
self.comb += chan_if.rx_ready.eq(self.rx_ready.storage)
channel_interfaces.append(chan_if)
# Route CSR signals using transceiver_sel
self.comb += Case(self.transceiver_sel.storage, {
trx_no: [
serdes.bitslip.eq(self.bitslip.re),
serdes.dly_ld.eq(self.dly_ld.re),
self.dly_cnt_out.status.eq(serdes.dly_cnt_out),
self.comma.status.eq(serdes.comma),
] for trx_no, serdes in enumerate(serdes_list)
})
# Wordslip needs to be latched. It needs to hold when calibrating
# other transceivers and/or after calibration.
self.sync += If(self.wordslip.re,
Case(self.transceiver_sel.storage, {
trx_no: [
serdes.wordslip.eq(self.wordslip.storage)
] for trx_no, serdes in enumerate(serdes_list)
})
)
for serdes in serdes_list:
self.comb += [
# Delay counter write only comes into effect after dly_ld
# So, just MUX dly_ld instead.
serdes.dly_cnt_in.eq(self.dly_cnt_in.storage),
# Comma align reset & lane selection can be broadcasted
# without MUXing. Transceivers are aligned one-by-one
serdes.lane_sel.eq(self.lane_sel.storage),
serdes.comma_align_reset.eq(self.comma_align_reset.re),
]
# Setup/hold timing calibration module
self.submodules.counter = PhaseErrorCounter()
self.comb += Case(self.transceiver_sel.storage, {
trx_no: Case(self.lane_sel.storage, {
lane_idx: self.counter.rxdata.eq(serdes.rx_serdes.rxdata[lane_idx])
for lane_idx in range(4)
}) for trx_no, serdes in enumerate(serdes_list)
})
self.submodules += serdes_list
self.submodules.oob_reset = OOBReset(serdes_list[0].rx_serdes.o[0])
self.rst = self.oob_reset.rst
self.rst.attr.add("no_retiming")
TransceiverInterface.__init__(self, channel_interfaces, async_rx=False)
for tx_en, serdes in zip(self.txenable.storage, serdes_list):
self.comb += serdes.tx_serdes.ext_rst.eq(~tx_en)

View File

@ -25,9 +25,13 @@ def default_iostandard(eem):
class _EEM:
@classmethod
def add_extension(cls, target, eem, *args, **kwargs):
def add_extension(cls, target, eem, *args, is_drtio_over_eem=False, **kwargs):
name = cls.__name__
target.platform.add_extension(cls.io(eem, *args, **kwargs))
if is_drtio_over_eem:
print("{} (EEM{}) starting at DRTIO channel 0x{:06x}"
.format(name, eem, (len(target.gt_drtio.channels) + len(target.eem_drtio_channels) + 1) << 16))
else:
print("{} (EEM{}) starting at RTIO channel 0x{:06x}"
.format(name, eem, len(target.rtio_channels)))
@ -710,3 +714,36 @@ class HVAmp(_EEM):
phy = ttl_out_cls(pads.p, pads.n)
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy))
class EFC(_EEM):
@staticmethod
def io(eem, iostandard=default_iostandard):
# Master: Pair 0~3 data IN, 4~7 OUT
data_in = ("efc{}_drtio_rx".format(eem), 0,
Subsignal("p", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "p") for i in range(4)
]))),
Subsignal("n", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "n") for i in range(4)
]))),
iostandard(eem),
Misc("DIFF_TERM=TRUE"),
)
data_out = ("efc{}_drtio_tx".format(eem), 0,
Subsignal("p", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "p") for i in range(4, 8)
]))),
Subsignal("n", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "n") for i in range(4, 8)
]))),
iostandard(eem),
)
return [data_in, data_out]
@classmethod
def add_std(cls, target, eem, eem_aux, iostandard=default_iostandard):
cls.add_extension(target, eem, is_drtio_over_eem=True, iostandard=iostandard)
target.eem_drtio_channels.append((target.platform.request("efc{}_drtio_rx".format(eem), 0), target.platform.request("efc{}_drtio_tx".format(eem), 0)))

View File

@ -126,6 +126,15 @@ def peripheral_hvamp(module, peripheral, **kwargs):
eem.HVAmp.add_std(module, peripheral["ports"][0],
ttl_simple.Output, **kwargs)
def peripheral_efc(module, peripheral, **kwargs):
if len(peripheral["ports"]) == 1:
port = peripheral["ports"][0]
port_aux = None
elif len(peripheral["ports"]) == 2:
port, port_aux = peripheral["ports"]
else:
raise ValueError("wrong number of ports")
eem.EFC.add_std(module, port, port_aux)
peripheral_processors = {
"dio": peripheral_dio,
@ -139,6 +148,7 @@ peripheral_processors = {
"fastino": peripheral_fastino,
"phaser": peripheral_phaser,
"hvamp": peripheral_hvamp,
"efc": peripheral_efc,
}

298
artiq/gateware/shuttler.py Normal file
View File

@ -0,0 +1,298 @@
# Copyright 2013-2017 Robert Jordens <jordens@gmail.com>
#
# 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 collections import namedtuple
from operator import add
from migen import *
from misoc.interconnect.stream import Endpoint
from misoc.interconnect.csr import *
from misoc.cores.cordic import Cordic
from artiq.gateware.rtio import rtlink
class DacInterface(Module, AutoCSR):
def __init__(self, pads):
bit_width = len(pads[0].data)
self.data = [[Signal(bit_width) for _ in range(2)] for _ in range(8)]
self.ddr_clk_phase_shift = CSR()
self.ddr_clk_phase_shift_done = CSRStatus(reset=1)
mmcm_ps_fb = Signal()
mmcm_ps_output = Signal()
mmcm_ps_psdone = Signal()
ddr_clk = Signal()
# Generate DAC DDR CLK
# 125MHz to 125MHz with controllable phase shift,
# VCO @ 1000MHz.
# Phase is shifted by 45 degree by default
self.specials += \
Instance("MMCME2_ADV",
p_CLKIN1_PERIOD=8.0,
i_CLKIN1=ClockSignal(),
i_RST=ResetSignal(),
i_CLKINSEL=1,
p_CLKFBOUT_MULT_F=8.0,
p_CLKOUT0_DIVIDE_F=8.0,
p_DIVCLK_DIVIDE=1,
p_CLKOUT0_PHASE=45.0,
o_CLKFBOUT=mmcm_ps_fb, i_CLKFBIN=mmcm_ps_fb,
p_CLKOUT0_USE_FINE_PS="TRUE",
o_CLKOUT0=mmcm_ps_output,
i_PSCLK=ClockSignal(),
i_PSEN=self.ddr_clk_phase_shift.re,
i_PSINCDEC=self.ddr_clk_phase_shift.r,
o_PSDONE=mmcm_ps_psdone,
)
self.sync += [
If(self.ddr_clk_phase_shift.re, self.ddr_clk_phase_shift_done.status.eq(0)),
If(mmcm_ps_psdone, self.ddr_clk_phase_shift_done.status.eq(1))
]
# din.clk pads locate at multiple clock regions/IO banks
self.specials += [
Instance("BUFG", i_I=mmcm_ps_output, o_O=ddr_clk),
]
for i, din in enumerate(pads):
self.specials += Instance("ODDR",
i_C=ddr_clk,
i_CE=1,
i_D1=1,
i_D2=0,
o_Q=din.clk,
p_DDR_CLK_EDGE="SAME_EDGE")
self.specials += [
Instance("ODDR",
i_C=ClockSignal(),
i_CE=1,
i_D1=self.data[i][0][bit], # DDR CLK Rising Edge
i_D2=self.data[i][1][bit], # DDR CLK Falling Edge
o_Q=din.data[bit],
p_DDR_CLK_EDGE="SAME_EDGE")
for bit in range(bit_width)]
class Dac(Module):
"""Output module.
Holds the two output line executors.
Attributes:
data (Signal[16]): Output value to be send to the DAC.
clear (Signal): Clear accumulated phase offset when loading a new
waveform. Input.
i (Endpoint[]): Coefficients of the output lines.
"""
def __init__(self):
self.clear = Signal()
self.data = Signal(16)
###
subs = [
Volt(),
Dds(self.clear),
]
self.sync.rio += [
self.data.eq(reduce(add, [sub.data for sub in subs])),
]
self.i = [ sub.i for sub in subs ]
self.submodules += subs
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
Attributes:
data (Signal[16]): Output data from this spline.
i (Endpoint): Coefficients of the DC bias spline, along with its
latency compensation.
"""
def __init__(self):
self.data = Signal(16)
self.i = Endpoint([("data", 144)])
self.i.latency = 17
###
v = [Signal(48) for i in range(4)] # amp, damp, ddamp, dddamp
# Increase latency of stb by 17 cycles to compensate CORDIC latency
stb_r = [ Signal() for _ in range(17) ]
self.sync.rio += [
stb_r[0].eq(self.i.stb),
]
for idx in range(16):
self.sync.rio += stb_r[idx+1].eq(stb_r[idx])
self.sync.rio += [
v[0].eq(v[0] + v[1]),
v[1].eq(v[1] + v[2]),
v[2].eq(v[2] + v[3]),
If(stb_r[16],
v[0].eq(0),
v[1].eq(0),
Cat(v[0][32:], v[1][16:], v[2], v[3]).eq(self.i.payload.raw_bits()),
)
]
self.comb += self.data.eq(v[0][32:])
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
* 32 bit chirp
Args:
line (Record[line_layout]): Next line to be executed. Input.
clear (Signal): Clear accumulated phase offset when loading a new
waveform. Input.
Attributes:
data (Signal[16]): Output data from this spline.
i (Endpoint): Coefficients of the DDS spline, along with its latency
compensation.
"""
def __init__(self, clear):
self.data = Signal(16)
self.i = Endpoint([("data", 224)])
###
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.rio += [
za.eq(za + z[1]),
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(self.i.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(self.i.payload.raw_bits()),
If(clear,
za.eq(0),
)
)
]
class Config(Module):
def __init__(self):
self.clr = Signal(16, reset=0xFFFF)
self.i = Endpoint([("data", 16)])
# This introduces 1 extra latency to everything in config
# See the latency/delay attributes in Volt & DDS Endpoints/rtlinks
self.sync.rio += If(self.i.stb, self.clr.eq(self.i.data))
Phy = namedtuple("Phy", "rtlink probes overrides")
class Shuttler(Module, AutoCSR):
"""Shuttler module.
Used both in functional simulation and final gateware.
Holds the DACs and the configuration register. The DAC and Config are
collected and adapted into RTIO interface.
Attributes:
phys (list): List of Endpoints.
"""
def __init__(self, pads):
NUM_OF_DACS = 16
self.submodules.dac_interface = DacInterface(pads)
self.phys = []
self.submodules.cfg = Config()
cfg_rtl_iface = rtlink.Interface(rtlink.OInterface(
data_width=len(self.cfg.i.data),
enable_replace=False))
self.comb += [
self.cfg.i.stb.eq(cfg_rtl_iface.o.stb),
self.cfg.i.data.eq(cfg_rtl_iface.o.data),
]
self.phys.append(Phy(cfg_rtl_iface, [], []))
trigger_iface = rtlink.Interface(rtlink.OInterface(
data_width=NUM_OF_DACS,
enable_replace=False))
self.phys.append(Phy(trigger_iface, [], []))
for idx in range(NUM_OF_DACS):
dac = Dac()
self.comb += [
dac.clear.eq(self.cfg.clr[idx]),
self.dac_interface.data[idx // 2][idx % 2].eq(dac.data)
]
for i in dac.i:
delay = getattr(i, "latency", 0)
rtl_iface = rtlink.Interface(rtlink.OInterface(
data_width=16, address_width=4, delay=delay))
array = Array(i.data[wi: wi+16] for wi in range(0, len(i.data), 16))
self.sync.rio += [
i.stb.eq(trigger_iface.o.data[idx] & trigger_iface.o.stb),
If(rtl_iface.o.stb,
array[rtl_iface.o.address].eq(rtl_iface.o.data),
),
]
self.phys.append(Phy(rtl_iface, [], []))
self.submodules += dac

View File

@ -0,0 +1,223 @@
#!/usr/bin/env python3
import argparse
from migen import *
from migen.build.generic_platform import *
from misoc.cores import gpio, spi2
from misoc.targets.efc import BaseSoC
from misoc.integration.builder import builder_args, builder_argdict
from artiq.gateware.amp import AMPSoC
from artiq.gateware import rtio
from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
from artiq.gateware.rtio.phy import ttl_simple
from artiq.gateware.drtio.transceiver import eem_serdes
from artiq.gateware.drtio.rx_synchronizer import NoRXSynchronizer
from artiq.gateware.drtio import *
from artiq.gateware.shuttler import Shuttler
from artiq.build_soc import *
class Satellite(BaseSoC, AMPSoC):
mem_map = {
"rtio": 0x20000000,
"drtioaux": 0x50000000,
"mailbox": 0x70000000
}
mem_map.update(BaseSoC.mem_map)
def __init__(self, gateware_identifier_str=None, **kwargs):
BaseSoC.__init__(self,
cpu_type="vexriscv",
cpu_bus_width=64,
sdram_controller_type="minicon",
l2_size=128*1024,
clk_freq=125e6,
**kwargs)
AMPSoC.__init__(self)
add_identifier(self, gateware_identifier_str=gateware_identifier_str)
platform = self.platform
drtio_eem_io = [
("drtio_tx", 0,
Subsignal("p", Pins("eem0:d0_cc_p eem0:d1_p eem0:d2_p eem0:d3_p")),
Subsignal("n", Pins("eem0:d0_cc_n eem0:d1_n eem0:d2_n eem0:d3_n")),
IOStandard("LVDS_25"),
),
("drtio_rx", 0,
Subsignal("p", Pins("eem0:d4_p eem0:d5_p eem0:d6_p eem0:d7_p")),
Subsignal("n", Pins("eem0:d4_n eem0:d5_n eem0:d6_n eem0:d7_n")),
IOStandard("LVDS_25"), Misc("DIFF_TERM=TRUE"),
),
]
platform.add_extension(drtio_eem_io)
data_pads = [
(platform.request("drtio_rx"), platform.request("drtio_tx"))
]
# Disable SERVMOD, hardwire it to ground to enable EEM 0
servmod = self.platform.request("servmod")
self.comb += servmod.eq(0)
self.submodules.eem_transceiver = eem_serdes.EEMSerdes(self.platform, data_pads)
self.csr_devices.append("eem_transceiver")
self.config["HAS_DRTIO_EEM"] = None
self.config["EEM_DRTIO_COUNT"] = 1
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
cdr = ClockDomainsRenamer({"rtio_rx": "sys"})
core = cdr(DRTIOSatellite(
self.rtio_tsc, self.eem_transceiver.channels[0],
NoRXSynchronizer()))
self.submodules.drtiosat = core
self.csr_devices.append("drtiosat")
self.submodules.drtioaux0 = cdr(DRTIOAuxController(
core.link_layer, self.cpu_dw))
self.csr_devices.append("drtioaux0")
memory_address = self.mem_map["drtioaux"]
self.add_wb_slave(memory_address, 0x800, self.drtioaux0.bus)
self.add_memory_region("drtioaux0_mem", memory_address | self.shadow_base, 0x800)
self.config["HAS_DRTIO"] = None
self.add_csr_group("drtioaux", ["drtioaux0"])
self.add_memory_group("drtioaux_mem", ["drtioaux0_mem"])
# Async reset gateware if data lane is idle
self.comb += self.crg.reset.eq(self.eem_transceiver.rst)
i2c = self.platform.request("fpga_i2c")
self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda])
self.csr_devices.append("i2c")
self.config["I2C_BUS_COUNT"] = 1
# Enable I2C
i2c_reset = self.platform.request("i2c_mux_rst_n")
self.comb += i2c_reset.eq(1)
fix_serdes_timing_path(platform)
self.config["DRTIO_ROLE"] = "satellite"
self.config["RTIO_FREQUENCY"] = "125.0"
shuttler_io = [
('dac_spi', 0,
Subsignal('clk', Pins('fmc0:HB16_N')),
Subsignal('mosi', Pins('fmc0:HB06_CC_N')),
Subsignal('cs_n', Pins('fmc0:LA31_N fmc0:LA31_P fmc0:HB19_P fmc0:LA30_P')),
IOStandard("LVCMOS18")),
('dac_rst', 0, Pins('fmc0:HB16_P'), IOStandard("LVCMOS18")),
('dac_din', 0,
Subsignal('data', Pins('fmc0:HA06_N fmc0:HA06_P fmc0:HA07_N fmc0:HA02_N fmc0:HA07_P fmc0:HA02_P fmc0:HA03_N fmc0:HA03_P fmc0:HA04_N fmc0:HA04_P fmc0:HA05_N fmc0:HA05_P fmc0:HA00_CC_N fmc0:HA01_CC_N')),
Subsignal('clk', Pins('fmc0:HA00_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 1,
Subsignal('data', Pins('fmc0:LA09_P fmc0:LA09_N fmc0:LA07_N fmc0:LA08_N fmc0:LA07_P fmc0:LA08_P fmc0:LA05_N fmc0:LA04_N fmc0:LA05_P fmc0:LA06_N fmc0:LA04_P fmc0:LA03_N fmc0:LA03_P fmc0:LA06_P')),
Subsignal('clk', Pins('fmc0:LA00_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 2,
Subsignal('data', Pins('fmc0:HA14_N fmc0:HA14_P fmc0:HA12_N fmc0:HA12_P fmc0:HA13_N fmc0:HA10_N fmc0:HA10_P fmc0:HA11_N fmc0:HA11_P fmc0:HA13_P fmc0:HA08_N fmc0:HA08_P fmc0:HA09_N fmc0:HA09_P')),
Subsignal('clk', Pins('fmc0:HA01_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 3,
Subsignal('data', Pins('fmc0:LA14_N fmc0:LA15_N fmc0:LA16_N fmc0:LA15_P fmc0:LA14_P fmc0:LA13_N fmc0:LA16_P fmc0:LA13_P fmc0:LA11_N fmc0:LA12_N fmc0:LA11_P fmc0:LA12_P fmc0:LA10_N fmc0:LA10_P')),
Subsignal('clk', Pins('fmc0:LA01_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 4,
Subsignal('data', Pins('fmc0:HA22_N fmc0:HA19_N fmc0:HA22_P fmc0:HA21_N fmc0:HA21_P fmc0:HA19_P fmc0:HA18_CC_N fmc0:HA20_N fmc0:HA20_P fmc0:HA18_CC_P fmc0:HA15_N fmc0:HA15_P fmc0:HA16_N fmc0:HA16_P')),
Subsignal('clk', Pins('fmc0:HA17_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 5,
Subsignal('data', Pins('fmc0:LA24_N fmc0:LA25_N fmc0:LA24_P fmc0:LA25_P fmc0:LA21_N fmc0:LA21_P fmc0:LA22_N fmc0:LA22_P fmc0:LA23_N fmc0:LA23_P fmc0:LA19_N fmc0:LA19_P fmc0:LA20_N fmc0:LA20_P')),
Subsignal('clk', Pins('fmc0:LA17_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 6,
Subsignal('data', Pins('fmc0:HB08_N fmc0:HB08_P fmc0:HB07_N fmc0:HB07_P fmc0:HB04_N fmc0:HB04_P fmc0:HB01_N fmc0:HB05_N fmc0:HB01_P fmc0:HB05_P fmc0:HB02_N fmc0:HB02_P fmc0:HB03_N fmc0:HB03_P')),
Subsignal('clk', Pins('fmc0:HB00_CC_P')),
IOStandard('LVCMOS18')),
('dac_din', 7,
Subsignal('data', Pins('fmc0:HB13_N fmc0:HB12_N fmc0:HB13_P fmc0:HB12_P fmc0:HB15_N fmc0:HB15_P fmc0:HB11_N fmc0:HB09_N fmc0:HB09_P fmc0:HB14_N fmc0:HB14_P fmc0:HB10_N fmc0:HB10_P fmc0:HB11_P')),
Subsignal('clk', Pins('fmc0:HB06_CC_P')),
IOStandard('LVCMOS18')),
]
platform.add_extension(shuttler_io)
self.submodules.converter_spi = spi2.SPIMaster(spi2.SPIInterface(self.platform.request("dac_spi", 0)))
self.csr_devices.append("converter_spi")
self.config["HAS_CONVERTER_SPI"] = None
self.submodules.dac_rst = gpio.GPIOOut(self.platform.request("dac_rst"))
self.csr_devices.append("dac_rst")
self.rtio_channels = []
for i in range(2):
phy = ttl_simple.Output(self.virtual_leds.get(i))
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
self.submodules.shuttler = Shuttler([platform.request("dac_din", i) for i in range(8)])
self.csr_devices.append("shuttler")
self.rtio_channels.extend(rtio.Channel.from_phy(phy) for phy in self.shuttler.phys)
self.config["HAS_RTIO_LOG"] = None
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels)
def add_rtio(self, rtio_channels, sed_lanes=8):
# Only add MonInj core if there is anything to monitor
if any([len(c.probes) for c in rtio_channels]):
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
self.csr_devices.append("rtio_moninj")
# satellite (master-controlled) RTIO
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors)
# subkernel RTIO
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)
self.register_kernel_cpu_csrdevice("rtio")
self.submodules.rtio_dma = rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)
self.csr_devices.append("rtio_dma")
self.submodules.cri_con = rtio.CRIInterconnectShared(
[self.drtiosat.cri, self.rtio_dma.cri],
[self.local_io.cri],
enable_routing=True)
self.csr_devices.append("cri_con")
self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con)
self.csr_devices.append("routing_table")
self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_tsc, self.local_io.cri,
self.get_native_sdram_if(), cpu_dw=self.cpu_dw)
self.csr_devices.append("rtio_analyzer")
def main():
parser = argparse.ArgumentParser(
description="ARTIQ device binary builder for EEM FMC Carrier systems")
builder_args(parser)
parser.set_defaults(output_dir="artiq_efc")
parser.add_argument("-V", "--variant", default="shuttler")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args()
argdict = dict()
argdict["gateware_identifier_str"] = args.gateware_identifier_str
soc = Satellite(**argdict)
build_artiq_soc(soc, builder_argdict(args))
if __name__ == "__main__":
main()

View File

@ -19,7 +19,7 @@ from artiq.gateware import rtio
from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, edge_counter
from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
from artiq.gateware import eem
from artiq.gateware.drtio.transceiver import gtp_7series
from artiq.gateware.drtio.transceiver import gtp_7series, eem_serdes
from artiq.gateware.drtio.siphaser import SiPhaser7Series
from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer
from artiq.gateware.drtio import *
@ -268,17 +268,17 @@ class MasterBase(MiniSoC, AMPSoC):
sfp_ctls = [platform.request("sfp_ctl", i) for i in range(1, 3)]
self.comb += [sc.tx_disable.eq(0) for sc in sfp_ctls]
self.submodules.drtio_transceiver = gtp_7series.GTP(
self.submodules.gt_drtio = gtp_7series.GTP(
qpll_channel=self.drtio_qpll_channel,
data_pads=drtio_data_pads,
sys_clk_freq=self.clk_freq,
rtio_clk_freq=rtio_clk_freq)
self.csr_devices.append("drtio_transceiver")
self.csr_devices.append("gt_drtio")
if enable_sata:
sfp_channels = self.drtio_transceiver.channels[1:]
sfp_channels = self.gt_drtio.channels[1:]
else:
sfp_channels = self.drtio_transceiver.channels
sfp_channels = self.gt_drtio.channels
if self.platform.hw_rev in ("v1.0", "v1.1"):
self.comb += [sfp_ctl.led.eq(channel.rx_ready)
for sfp_ctl, channel in zip(sfp_ctls, sfp_channels)]
@ -288,21 +288,21 @@ class MasterBase(MiniSoC, AMPSoC):
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
drtio_csr_group = []
drtioaux_csr_group = []
drtioaux_memory_group = []
self.drtio_csr_group = []
self.drtioaux_csr_group = []
self.drtioaux_memory_group = []
self.drtio_cri = []
for i in range(len(self.drtio_transceiver.channels)):
for i in range(len(self.gt_drtio.channels)):
core_name = "drtio" + str(i)
coreaux_name = "drtioaux" + str(i)
memory_name = "drtioaux" + str(i) + "_mem"
drtio_csr_group.append(core_name)
drtioaux_csr_group.append(coreaux_name)
drtioaux_memory_group.append(memory_name)
self.drtio_csr_group.append(core_name)
self.drtioaux_csr_group.append(coreaux_name)
self.drtioaux_memory_group.append(memory_name)
cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)})
core = cdr(DRTIOMaster(self.rtio_tsc, self.drtio_transceiver.channels[i]))
core = cdr(DRTIOMaster(self.rtio_tsc, self.gt_drtio.channels[i]))
setattr(self.submodules, core_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(core_name)
@ -317,12 +317,9 @@ class MasterBase(MiniSoC, AMPSoC):
self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800)
self.config["HAS_DRTIO"] = None
self.config["HAS_DRTIO_ROUTING"] = None
self.add_csr_group("drtio", drtio_csr_group)
self.add_csr_group("drtioaux", drtioaux_csr_group)
self.add_memory_group("drtioaux_mem", drtioaux_memory_group)
rtio_clk_period = 1e9/rtio_clk_freq
gtp = self.drtio_transceiver.gtps[0]
gtp = self.gt_drtio.gtps[0]
txout_buf = Signal()
self.specials += Instance("BUFG", i_I=gtp.txoutclk, o_O=txout_buf)
@ -334,7 +331,7 @@ class MasterBase(MiniSoC, AMPSoC):
platform.add_false_path_constraints(
self.crg.cd_sys.clk,
gtp.txoutclk, gtp.rxoutclk)
for gtp in self.drtio_transceiver.gtps[1:]:
for gtp in self.gt_drtio.gtps[1:]:
platform.add_period_constraint(gtp.rxoutclk, rtio_clk_period)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, gtp.rxoutclk)
@ -367,6 +364,43 @@ class MasterBase(MiniSoC, AMPSoC):
self.get_native_sdram_if(), cpu_dw=self.cpu_dw)
self.csr_devices.append("rtio_analyzer")
def add_eem_drtio(self, eem_drtio_channels):
# Must be called before invoking add_rtio() to construct the CRI
# interconnect properly
self.submodules.eem_transceiver = eem_serdes.EEMSerdes(self.platform, eem_drtio_channels)
self.csr_devices.append("eem_transceiver")
self.config["HAS_DRTIO_EEM"] = None
self.config["EEM_DRTIO_COUNT"] = len(eem_drtio_channels)
cdr = ClockDomainsRenamer({"rtio_rx": "sys"})
for i in range(len(self.eem_transceiver.channels)):
channel = i + len(self.gt_drtio.channels)
core_name = "drtio" + str(channel)
coreaux_name = "drtioaux" + str(channel)
memory_name = "drtioaux" + str(channel) + "_mem"
self.drtio_csr_group.append(core_name)
self.drtioaux_csr_group.append(coreaux_name)
self.drtioaux_memory_group.append(memory_name)
core = cdr(DRTIOMaster(self.rtio_tsc, self.eem_transceiver.channels[i]))
setattr(self.submodules, core_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(core_name)
coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw))
setattr(self.submodules, coreaux_name, coreaux)
self.csr_devices.append(coreaux_name)
memory_address = self.mem_map["drtioaux"] + 0x800*channel
self.add_wb_slave(memory_address, 0x800,
coreaux.bus)
self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800)
def add_drtio_cpuif_groups(self):
self.add_csr_group("drtio", self.drtio_csr_group)
self.add_csr_group("drtioaux", self.drtioaux_csr_group)
self.add_memory_group("drtioaux_mem", self.drtioaux_memory_group)
# Never running out of stupid features, GTs on A7 make you pack
# unrelated transceiver PLLs into one GTPE2_COMMON yourself.
def create_qpll(self):
@ -472,17 +506,17 @@ class SatelliteBase(BaseSoC, AMPSoC):
if self.platform.hw_rev in ("v1.0", "v1.1"):
sfp_ctls = [platform.request("sfp_ctl", i) for i in range(3)]
self.comb += [sc.tx_disable.eq(0) for sc in sfp_ctls]
self.submodules.drtio_transceiver = gtp_7series.GTP(
self.submodules.gt_drtio = gtp_7series.GTP(
qpll_channel=qpll.channels[0],
data_pads=drtio_data_pads,
sys_clk_freq=self.clk_freq,
rtio_clk_freq=rtio_clk_freq)
self.csr_devices.append("drtio_transceiver")
self.csr_devices.append("gt_drtio")
if enable_sata:
sfp_channels = self.drtio_transceiver.channels[1:]
sfp_channels = self.gt_drtio.channels[1:]
else:
sfp_channels = self.drtio_transceiver.channels
sfp_channels = self.gt_drtio.channels
if self.platform.hw_rev in ("v1.0", "v1.1"):
self.comb += [sfp_ctl.led.eq(channel.rx_ready)
for sfp_ctl, channel in zip(sfp_ctls, sfp_channels)]
@ -496,7 +530,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
drtioaux_memory_group = []
drtiorep_csr_group = []
self.drtio_cri = []
for i in range(len(self.drtio_transceiver.channels)):
for i in range(len(self.gt_drtio.channels)):
coreaux_name = "drtioaux" + str(i)
memory_name = "drtioaux" + str(i) + "_mem"
drtioaux_csr_group.append(coreaux_name)
@ -507,7 +541,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
if i == 0:
self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer())
core = cdr(DRTIOSatellite(
self.rtio_tsc, self.drtio_transceiver.channels[i],
self.rtio_tsc, self.gt_drtio.channels[i],
self.rx_synchronizer))
self.submodules.drtiosat = core
self.csr_devices.append("drtiosat")
@ -516,7 +550,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
drtiorep_csr_group.append(corerep_name)
core = cdr(DRTIORepeater(
self.rtio_tsc, self.drtio_transceiver.channels[i]))
self.rtio_tsc, self.gt_drtio.channels[i]))
setattr(self.submodules, corerep_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(corerep_name)
@ -555,7 +589,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
self.config["HAS_SI5324"] = None
self.config["SI5324_SOFT_RESET"] = None
gtp = self.drtio_transceiver.gtps[0]
gtp = self.gt_drtio.gtps[0]
txout_buf = Signal()
self.specials += Instance("BUFG", i_I=gtp.txoutclk, o_O=txout_buf)
self.crg.configure(txout_buf, clk_sw=gtp.tx_init.done)
@ -565,7 +599,7 @@ class SatelliteBase(BaseSoC, AMPSoC):
platform.add_false_path_constraints(
self.crg.cd_sys.clk,
gtp.txoutclk, gtp.rxoutclk)
for gtp in self.drtio_transceiver.gtps[1:]:
for gtp in self.gt_drtio.gtps[1:]:
platform.add_period_constraint(gtp.rxoutclk, rtio_clk_period)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, gtp.rxoutclk)

View File

@ -71,10 +71,12 @@ class GenericMaster(MasterBase):
if hw_rev is None:
hw_rev = description["hw_rev"]
self.class_name_override = description["variant"]
has_drtio_over_eem = any(peripheral["type"] == "efc" for peripheral in description["peripherals"])
MasterBase.__init__(self,
hw_rev=hw_rev,
rtio_clk_freq=description["rtio_frequency"],
enable_sata=description["enable_sata_drtio"],
enable_sys5x=has_drtio_over_eem,
**kwargs)
self.config["DRTIO_ROLE"] = description["drtio_role"]
if "ext_ref_frequency" in description:
@ -85,6 +87,8 @@ class GenericMaster(MasterBase):
# EEM clock fan-out from Si5324, not MMCX
self.comb += self.platform.request("clk_sel").eq(1)
if has_drtio_over_eem:
self.eem_drtio_channels = []
has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"])
if has_grabber:
self.grabber_csr_group = []
@ -95,13 +99,18 @@ class GenericMaster(MasterBase):
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
if has_drtio_over_eem:
self.add_eem_drtio(self.eem_drtio_channels)
self.add_drtio_cpuif_groups()
self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"])
if has_grabber:
self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group)
for grabber in self.grabber_csr_group:
self.platform.add_false_path_constraints(
self.drtio_transceiver.gtps[0].txoutclk, getattr(self, grabber).deserializer.cd_cl.clk)
self.gt_drtio.gtps[0].txoutclk, getattr(self, grabber).deserializer.cd_cl.clk)
class GenericSatellite(SatelliteBase):
@ -135,7 +144,7 @@ class GenericSatellite(SatelliteBase):
self.add_csr_group("grabber", self.grabber_csr_group)
for grabber in self.grabber_csr_group:
self.platform.add_false_path_constraints(
self.drtio_transceiver.gtps[0].txoutclk, getattr(self, grabber).deserializer.cd_cl.clk)
self.gt_drtio.gtps[0].txoutclk, getattr(self, grabber).deserializer.cd_cl.clk)
def main():
@ -168,6 +177,10 @@ def main():
else:
raise ValueError("Invalid DRTIO role")
has_efc = any(peripheral["type"] == "efc" for peripheral in description["peripherals"])
if has_efc and (description["drtio_role"] == "standalone"):
raise ValueError("EFC requires DRTIO, please switch role to master")
soc = cls(description, gateware_identifier_str=args.gateware_identifier_str, **soc_kasli_argdict(args))
args.variant = description["variant"]
build_artiq_soc(soc, builder_argdict(args))

View File

@ -215,11 +215,11 @@ class _MasterBase(MiniSoC, AMPSoC):
]
# 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock
self.submodules.drtio_transceiver = gtx_7series.GTX(
self.submodules.gt_drtio = gtx_7series.GTX(
clock_pads=platform.request("si5324_clkout"),
pads=data_pads,
clk_freq=self.clk_freq)
self.csr_devices.append("drtio_transceiver")
self.csr_devices.append("gt_drtio")
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
@ -227,7 +227,7 @@ class _MasterBase(MiniSoC, AMPSoC):
drtioaux_csr_group = []
drtioaux_memory_group = []
self.drtio_cri = []
for i in range(len(self.drtio_transceiver.channels)):
for i in range(len(self.gt_drtio.channels)):
core_name = "drtio" + str(i)
coreaux_name = "drtioaux" + str(i)
memory_name = "drtioaux" + str(i) + "_mem"
@ -238,7 +238,7 @@ class _MasterBase(MiniSoC, AMPSoC):
cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)})
core = cdr(DRTIOMaster(
self.rtio_tsc, self.drtio_transceiver.channels[i]))
self.rtio_tsc, self.gt_drtio.channels[i]))
setattr(self.submodules, core_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(core_name)
@ -257,7 +257,7 @@ class _MasterBase(MiniSoC, AMPSoC):
self.add_csr_group("drtioaux", drtioaux_csr_group)
self.add_memory_group("drtioaux_mem", drtioaux_memory_group)
self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6)
self.config["RTIO_FREQUENCY"] = str(self.gt_drtio.rtio_clk_freq/1e6)
self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324_33").rst_n, reset_out=1)
self.csr_devices.append("si5324_rst_n")
i2c = self.platform.request("i2c")
@ -266,10 +266,10 @@ class _MasterBase(MiniSoC, AMPSoC):
self.config["I2C_BUS_COUNT"] = 1
self.config["HAS_SI5324"] = None
rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq
rtio_clk_period = 1e9/self.gt_drtio.rtio_clk_freq
# Constrain TX & RX timing for the first transceiver channel
# (First channel acts as master for phase alignment for all channels' TX)
gtx0 = self.drtio_transceiver.gtxs[0]
gtx0 = self.gt_drtio.gtxs[0]
txout_buf = Signal()
self.specials += Instance("BUFG", i_I=gtx0.txoutclk, o_O=txout_buf)
@ -286,7 +286,7 @@ class _MasterBase(MiniSoC, AMPSoC):
self.crg.cd_sys.clk, gtx0.rxoutclk)
# Constrain RX timing for the each transceiver channel
# (Each channel performs single-lane phase alignment for RX)
for gtx in self.drtio_transceiver.gtxs[1:]:
for gtx in self.gt_drtio.gtxs[1:]:
platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, gtx.rxoutclk)
@ -363,11 +363,11 @@ class _SatelliteBase(BaseSoC, AMPSoC):
rtio_clk_freq = clk_freq
# 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock
self.submodules.drtio_transceiver = gtx_7series.GTX(
self.submodules.gt_drtio = gtx_7series.GTX(
clock_pads=platform.request("si5324_clkout"),
pads=data_pads,
clk_freq=self.clk_freq)
self.csr_devices.append("drtio_transceiver")
self.csr_devices.append("gt_drtio")
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
@ -375,7 +375,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
drtioaux_memory_group = []
drtiorep_csr_group = []
self.drtio_cri = []
for i in range(len(self.drtio_transceiver.channels)):
for i in range(len(self.gt_drtio.channels)):
coreaux_name = "drtioaux" + str(i)
memory_name = "drtioaux" + str(i) + "_mem"
drtioaux_csr_group.append(coreaux_name)
@ -387,7 +387,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
if i == 0:
self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer())
core = cdr(DRTIOSatellite(
self.rtio_tsc, self.drtio_transceiver.channels[0], self.rx_synchronizer))
self.rtio_tsc, self.gt_drtio.channels[0], self.rx_synchronizer))
self.submodules.drtiosat = core
self.csr_devices.append("drtiosat")
# Repeaters
@ -395,7 +395,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
corerep_name = "drtiorep" + str(i-1)
drtiorep_csr_group.append(corerep_name)
core = cdr(DRTIORepeater(
self.rtio_tsc, self.drtio_transceiver.channels[i]))
self.rtio_tsc, self.gt_drtio.channels[i]))
setattr(self.submodules, corerep_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(corerep_name)
@ -414,14 +414,14 @@ class _SatelliteBase(BaseSoC, AMPSoC):
self.add_memory_group("drtioaux_mem", drtioaux_memory_group)
self.add_csr_group("drtiorep", drtiorep_csr_group)
self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6)
self.config["RTIO_FREQUENCY"] = str(self.gt_drtio.rtio_clk_freq/1e6)
# Si5324 Phaser
self.submodules.siphaser = SiPhaser7Series(
si5324_clkin=platform.request("si5324_clkin_33"),
rx_synchronizer=self.rx_synchronizer,
ref_clk=ClockSignal("bootstrap"),
ultrascale=False,
rtio_clk_freq=self.drtio_transceiver.rtio_clk_freq)
rtio_clk_freq=self.gt_drtio.rtio_clk_freq)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output)
self.csr_devices.append("siphaser")
@ -433,10 +433,10 @@ class _SatelliteBase(BaseSoC, AMPSoC):
self.config["I2C_BUS_COUNT"] = 1
self.config["HAS_SI5324"] = None
rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq
rtio_clk_period = 1e9/self.gt_drtio.rtio_clk_freq
# Constrain TX & RX timing for the first transceiver channel
# (First channel acts as master for phase alignment for all channels' TX)
gtx0 = self.drtio_transceiver.gtxs[0]
gtx0 = self.gt_drtio.gtxs[0]
txout_buf = Signal()
self.specials += Instance("BUFG", i_I=gtx0.txoutclk, o_O=txout_buf)
@ -451,7 +451,7 @@ class _SatelliteBase(BaseSoC, AMPSoC):
platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period)
# Constrain RX timing for the each transceiver channel
# (Each channel performs single-lane phase alignment for RX)
for gtx in self.drtio_transceiver.gtxs[1:]:
for gtx in self.gt_drtio.gtxs[1:]:
platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period)
platform.add_false_path_constraints(
self.crg.cd_sys.clk, gtx.rxoutclk)

View File

@ -120,8 +120,8 @@ class NumberEntryFloat(ScientificSpinBox):
disable_scroll_wheel(self)
procdesc = argument["desc"]
scale = procdesc["scale"]
self.setDecimals(procdesc["ndecimals"])
self.setPrecision()
self.setDecimals(procdesc["precision"])
self.setSigFigs()
self.setSingleStep(procdesc["step"]/scale)
self.setRelativeStep()
if procdesc["min"] is not None:
@ -159,8 +159,8 @@ class _NoScan(LayoutWidget):
scale = procdesc["scale"]
self.value = ScientificSpinBox()
disable_scroll_wheel(self.value)
self.value.setDecimals(procdesc["ndecimals"])
self.value.setPrecision()
self.value.setDecimals(procdesc["precision"])
self.value.setSigFigs()
if procdesc["global_min"] is not None:
self.value.setMinimum(procdesc["global_min"]/scale)
else:
@ -202,7 +202,7 @@ class _RangeScan(LayoutWidget):
scale = procdesc["scale"]
def apply_properties(widget):
widget.setDecimals(procdesc["ndecimals"])
widget.setDecimals(procdesc["precision"])
if procdesc["global_min"] is not None:
widget.setMinimum(procdesc["global_min"]/scale)
else:
@ -244,10 +244,10 @@ class _RangeScan(LayoutWidget):
self.addWidget(randomize, 3, 1)
apply_properties(start)
start.setPrecision()
start.setSigFigs()
start.setRelativeStep()
apply_properties(stop)
stop.setPrecision()
stop.setSigFigs()
stop.setRelativeStep()
apply_properties(scanner)
@ -293,7 +293,7 @@ class _CenterScan(LayoutWidget):
scale = procdesc["scale"]
def apply_properties(widget):
widget.setDecimals(procdesc["ndecimals"])
widget.setDecimals(procdesc["precision"])
if procdesc["global_min"] is not None:
widget.setMinimum(procdesc["global_min"]/scale)
else:
@ -310,7 +310,7 @@ class _CenterScan(LayoutWidget):
center = ScientificSpinBox()
disable_scroll_wheel(center)
apply_properties(center)
center.setPrecision()
center.setSigFigs()
center.setRelativeStep()
center.setValue(state["center"]/scale)
self.addWidget(center, 0, 1)
@ -319,7 +319,7 @@ class _CenterScan(LayoutWidget):
span = ScientificSpinBox()
disable_scroll_wheel(span)
apply_properties(span)
span.setPrecision()
span.setSigFigs()
span.setRelativeStep()
span.setMinimum(0)
span.setValue(state["span"]/scale)
@ -329,7 +329,7 @@ class _CenterScan(LayoutWidget):
step = ScientificSpinBox()
disable_scroll_wheel(step)
apply_properties(step)
step.setPrecision()
step.setSigFigs()
step.setRelativeStep()
step.setMinimum(0)
step.setValue(state["step"]/scale)
@ -469,7 +469,7 @@ class ScanEntry(LayoutWidget):
def procdesc_to_entry(procdesc):
ty = procdesc["ty"]
if ty == "NumberValue":
is_int = (procdesc["ndecimals"] == 0
is_int = (procdesc["precision"] == 0
and int(procdesc["step"]) == procdesc["step"]
and procdesc["scale"] == 1)
if is_int:

View File

@ -20,21 +20,21 @@ class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
self.setCorrectionMode(self.CorrectToPreviousValue)
# singleStep: resolution for step, buttons, accelerators
# decimals: absolute rounding granularity
# precision: number of significant digits shown, %g precision
self.setPrecision()
# sigFigs: number of significant digits shown
self.setSigFigs()
self.setRelativeStep()
self.setRange(-inf, inf)
self.setValue(0)
# self.setKeyboardTracking(False)
def setPrecision(self, d=None):
def setSigFigs(self, d=None):
if d is None:
d = self.decimals() + 3
self._precision = max(1, int(d))
self._fmt = "{{:.{}g}}".format(self._precision)
self._sig_figs = max(1, int(d))
self._fmt = "{{:.{}g}}".format(self._sig_figs)
def precision(self):
return self._precision
def sigFigs(self):
return self._sig_figs
def setRelativeStep(self, s=None):
if s is None:

View File

@ -100,7 +100,7 @@ class NumberValue(_SimpleArgProcessor):
"""An argument that can take a numerical value.
If ``type=="auto"``, the result will be a ``float`` unless
ndecimals = 0, scale = 1 and step is an integer. Setting ``type`` to
precision = 0, scale = 1 and step is an integer. Setting ``type`` to
``int`` will also result in an error unless these conditions are met.
When ``scale`` is not specified, and the unit is a common one (i.e.
@ -123,14 +123,17 @@ class NumberValue(_SimpleArgProcessor):
buttons in a UI. The default is the scale divided by 10.
:param min: The minimum value of the argument.
:param max: The maximum value of the argument.
:param ndecimals: The number of decimals a UI should use.
:param precision: The maximum number of decimals a UI should use.
:param type: Type of this number. Accepts ``"float"``, ``"int"`` or
``"auto"``. Defaults to ``"auto"``.
"""
valid_types = ["auto", "float", "int"]
def __init__(self, default=NoDefault, unit="", scale=None,
step=None, min=None, max=None, ndecimals=2, type="auto"):
def __init__(self, default=NoDefault, unit="", *, scale=None,
step=None, min=None, max=None, precision=2, type="auto", ndecimals=None):
if ndecimals is not None:
print("DeprecationWarning: 'ndecimals' is deprecated. Please use 'precision' instead.")
precision = ndecimals
if scale is None:
if unit == "":
scale = 1.0
@ -147,7 +150,7 @@ class NumberValue(_SimpleArgProcessor):
self.step = step
self.min = min
self.max = max
self.ndecimals = ndecimals
self.precision = precision
self.type = type
if self.type not in NumberValue.valid_types:
@ -155,7 +158,7 @@ class NumberValue(_SimpleArgProcessor):
if self.type == "int" and not self._is_int_compatible():
raise ValueError(("Value marked as integer but settings are "
"not compatible. Please set ndecimals = 0, "
"not compatible. Please set precision = 0, "
"scale = 1 and step to an integer"))
super().__init__(default)
@ -165,7 +168,7 @@ class NumberValue(_SimpleArgProcessor):
Are the settings other than `type` compatible with this being
an integer?
'''
return (self.ndecimals == 0
return (self.precision == 0
and int(self.step) == self.step
and self.scale == 1)
@ -191,7 +194,7 @@ class NumberValue(_SimpleArgProcessor):
d["step"] = self.step
d["min"] = self.min
d["max"] = self.max
d["ndecimals"] = self.ndecimals
d["precision"] = self.precision
d["type"] = self.type
return d

View File

@ -83,8 +83,7 @@ class RangeScan(ScanObject):
self.sequence = [i*dx + start for i in range(npoints)]
if randomize:
rng = random.Random(seed)
random.shuffle(self.sequence, rng.random)
random.Random(seed).shuffle(self.sequence)
def __iter__(self):
return iter(self.sequence)
@ -120,8 +119,7 @@ class CenterScan(ScanObject):
for i in range(n) for sign in [-1, 1]][1:]
if randomize:
rng = random.Random(seed)
random.shuffle(self.sequence, rng.random)
random.Random(seed).shuffle(self.sequence)
def __iter__(self):
return iter(self.sequence)
@ -191,11 +189,14 @@ class Scannable:
:param unit: A string representing the unit of the scanned variable.
:param scale: A numerical scaling factor by which the displayed values
are multiplied when referenced in the experiment.
:param ndecimals: The number of decimals a UI should use.
:param precision: The maximum number of decimals a UI should use.
"""
def __init__(self, default=NoDefault, unit="", scale=None,
def __init__(self, default=NoDefault, unit="", *, scale=None,
global_step=None, global_min=None, global_max=None,
ndecimals=2):
precision=2, ndecimals=None):
if ndecimals is not None:
print("DeprecationWarning: 'ndecimals' is deprecated. Please use 'precision' instead.")
precision = ndecimals
if scale is None:
if unit == "":
scale = 1.0
@ -216,7 +217,7 @@ class Scannable:
self.global_step = global_step
self.global_min = global_min
self.global_max = global_max
self.ndecimals = ndecimals
self.precision = precision
def default(self):
if not hasattr(self, "default_values"):
@ -240,7 +241,7 @@ class Scannable:
d["global_step"] = self.global_step
d["global_min"] = self.global_min
d["global_max"] = self.global_max
d["ndecimals"] = self.ndecimals
d["precision"] = self.precision
return d

View File

@ -0,0 +1,15 @@
# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t
# RUN: OutputCheck %s --file-to-check=%t
from artiq.language.core import *
from artiq.language.types import *
@kernel
def modify(x):
# CHECK-L: ${LINE:+1}: error: cannot assign to a tuple element
x[0] = 2
@kernel
def entrypoint():
modify((1, "foo", True))
modify((2, "bar", False))

View File

@ -0,0 +1,16 @@
# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s
# RUN: OutputCheck %s --file-to-check=%t.ll
from artiq.language.core import *
from artiq.language.types import *
# CHECK-L: void @_Z16testbench.unpackzz({ i32, { i8*, i32 }, i1 } %ARG.x)
@kernel
def unpack(x):
print(x[0])
@kernel
def entrypoint():
unpack((1, "foo", True))
unpack((2, "bar", False))

View File

@ -0,0 +1,14 @@
# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t
i = 0
x = (1, "foo", True)
# CHECK-L: ${LINE:+1}: error: tuples can only be indexed by a constant
x[i]
# CHECK-L: ${LINE:+1}: error: tuples can only be indexed by a constant
x[0:2]
# CHECK-L: ${LINE:+1}: error: index 3 is out of range for tuple of size 3
x[3]

View File

@ -0,0 +1,22 @@
# RUN: %python -m artiq.compiler.testbench.jit %s
# RUN: %python %s
# Basic indexing
a = (1, "xyz", True)
assert a[0] == 1
assert a[1] == "xyz"
assert a[2] == True
# Nested indexing
b = (a, 2, (3, "abc", a))
assert b[0][0] == 1
assert b[1] == 2
assert b[2][2][0] == 1
# Usage on the LHS of an assignment
c = (1, 2, [1, 2, 3])
c[2][0] = 456
assert c[2][0] == 456

View File

@ -9,7 +9,7 @@ from artiq.language.environment import BooleanValue, EnumerationValue, \
class NumberValueCase(unittest.TestCase):
def setUp(self):
self.default_value = NumberValue()
self.int_value = NumberValue(42, step=1, ndecimals=0)
self.int_value = NumberValue(42, step=1, precision=0)
self.float_value = NumberValue(42)
def test_invalid_default(self):

View File

@ -18,7 +18,8 @@ from artiq.language.environment import is_public_experiment
from artiq.language import units
__all__ = ["parse_arguments", "elide", "short_format", "file_import",
__all__ = ["parse_arguments", "elide", "scale_from_metadata",
"short_format", "file_import",
"get_experiment",
"exc_to_warning", "asyncio_wait_or_cancel",
"get_windows_drives", "get_user_config_dir"]
@ -54,12 +55,15 @@ def elide(s, maxlen):
s += "..."
return s
def scale_from_metadata(metadata):
unit = metadata.get("unit", "")
default_scale = getattr(units, unit, 1)
return metadata.get("scale", default_scale)
def short_format(v, metadata={}):
m = metadata
unit = m.get("unit", "")
default_scale = getattr(units, unit, 1)
scale = m.get("scale", default_scale)
scale = scale_from_metadata(m)
precision = m.get("precision", None)
if v is None:
return "None"
@ -68,6 +72,7 @@ def short_format(v, metadata={}):
v_t = np.divide(v, scale)
v_str = np.format_float_positional(v_t,
precision=precision,
trim='-',
unique=True)
v_str += " " + unit if unit else ""
return v_str

View File

@ -46,6 +46,8 @@ Datasets are values (possibly arrays) that are read and written by experiments a
A dataset may be broadcasted, that is, distributed to all clients connected to the master. For example, the ARTIQ GUI may plot it while the experiment is in progress to give rapid feedback to the user. Broadcasted datasets live in a global key-value store; experiments should use distinctive real-time result names in order to avoid conflicts. Broadcasted datasets may be used to communicate values across experiments; for example, a periodic calibration experiment may update a dataset read by payload experiments. Broadcasted datasets are replaced when a new dataset with the same key (name) is produced.
Datasets can attach metadata for numerical formatting with the ``unit``, ``scale`` and ``precision`` parameters. In experiment code, values are assumed to be in the SI unit. In code, setting a dataset with a value of `1000` and the unit `kV` represents the quantity `1 kV`. It is recommended to use the globals defined by `artiq.language.units` and write `1*kV` instead of `1000` for the value. In dashboards and clients these globals are not available. There, setting a dataset with a value of `1` and the unit `kV` simply represents the quantity `1 kV`. ``precision`` refers to the max number of decimal places to display. This parameter does not affect the underlying value, and is only used for display purposes.
Broadcasted datasets may be persistent: the master stores them in a file typically called ``dataset_db.pyon`` so they are saved across master restarts.
Datasets produced by an experiment run may be archived in the HDF5 output for that run.

View File

@ -53,7 +53,7 @@ Experiments may have arguments whose values can be set in the dashboard and used
def build(self):
self.setattr_argument("count", NumberValue(ndecimals=0, step=1))
self.setattr_argument("count", NumberValue(precision=0, step=1))
def run(self):
for i in range(self.count):

View File

@ -126,6 +126,8 @@
migen = pkgs.python3Packages.buildPythonPackage rec {
name = "migen";
src = src-migen;
format = "pyproject";
nativeBuildInputs = [ pkgs.python3Packages.setuptools ];
propagatedBuildInputs = [ pkgs.python3Packages.colorama ];
};
@ -211,16 +213,20 @@
'';
installPhase =
''
TARGET_DIR=$out
mkdir -p $TARGET_DIR
cp artiq_${target}/${variant}/gateware/top.bit $TARGET_DIR
mkdir $out
cp artiq_${target}/${variant}/gateware/top.bit $out
if [ -e artiq_${target}/${variant}/software/bootloader/bootloader.bin ]
then cp artiq_${target}/${variant}/software/bootloader/bootloader.bin $TARGET_DIR
then cp artiq_${target}/${variant}/software/bootloader/bootloader.bin $out
fi
if [ -e artiq_${target}/${variant}/software/runtime ]
then cp artiq_${target}/${variant}/software/runtime/runtime.{elf,fbi} $TARGET_DIR
else cp artiq_${target}/${variant}/software/satman/satman.{elf,fbi} $TARGET_DIR
then cp artiq_${target}/${variant}/software/runtime/runtime.{elf,fbi} $out
else cp artiq_${target}/${variant}/software/satman/satman.{elf,fbi} $out
fi
mkdir $out/nix-support
for i in $out/*.*; do
echo file binary-dist $i >> $out/nix-support/hydra-build-products
done
'';
# don't mangle ELF files as they are not for NixOS
dontFixup = true;
@ -291,6 +297,10 @@
target = "kc705";
variant = "nist_clock";
};
artiq-board-efc-shuttler = makeArtiqBoardPackage {
target = "efc";
variant = "shuttler";
};
inherit sphinxcontrib-wavedrom latex-artiq-manual;
artiq-manual-html = pkgs.stdenvNoCC.mkDerivation rec {
name = "artiq-manual-html-${version}";
@ -374,9 +384,9 @@
(pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ migen misoc artiq ps.packaging ]))
rust
pkgs.cargo-xbuild
pkgs.llvmPackages_11.clang-unwrapped
pkgs.llvm_11
pkgs.lld_11
pkgs.llvmPackages_14.clang-unwrapped
pkgs.llvm_14
pkgs.lld_14
packages.x86_64-linux.vivado
packages.x86_64-linux.openocd-bscanspi
];
@ -387,7 +397,7 @@
};
hydraJobs = {
inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi;
inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock artiq-board-efc-shuttler openocd-bscanspi;
sipyco-msys2-pkg = packages.x86_64-w64-mingw32.sipyco-pkg;
artiq-comtools-msys2-pkg = packages.x86_64-w64-mingw32.artiq-comtools-pkg;
artiq-msys2-pkg = packages.x86_64-w64-mingw32.artiq-pkg;