Compare commits

..

8 Commits

Author SHA1 Message Date
morgan ff68521bfd Firmware: Runtime WRPLL
runtime: drive CLK_SEL to true when si549 is used
runtime & libboard_artiq: allow standalone to use io_expander
si549: add bit bang mmcm dynamic configuration
si549: add frequency counter for refclk
rtio_clocking & si549: add 125Mhz wrpll refclk setup
2024-03-19 10:42:42 +08:00
morgan 712b27153e Firmware: Satman skew calibration & tester
cargo template: add calibrate_wrpll_skew feature
tag collector: add TAG_OFFSET for Satman WRPLL
tag collector: add TAG_OFFSET getter & setter for calibration
tag collector: gate calibrated tag offset behind refclk
tag collector: add tag_offset setter and getter
wrpll: add skew tester and calibration
wrpll: gate calibration behind calibrate_wrpll_skew feature
2024-03-19 10:42:29 +08:00
morgan 82e9fbc6d2 Firmware: Satman WRPLL
satman: drive CLK_SEL to true when si549 is used
satman & si549: add main & helper si549 setup
satman & si549: add WRPLL select_recovered_clock
si549: add tag collector to process gtx & main tags
si549: add tag offset to meet setup/hold constraints for CDR
si549: add frequency counter to set BASE_ADPLL
si549: add set_adpll for main & helper PLL
si549: add main & helper PLL
FIQ & si549: replace dummy with a custom handler for gtx & main tags ISR
2024-03-19 10:41:27 +08:00
morgan 917579f005 Firmware: Si549 and io_expander
io_expander: set CLK_SEL pin to output when si549 is used
io_expander: gate virtual leds for standalone
si549: add bit bang i2c
si549: add si549 programming
si549: add main & helper setup
2024-03-19 10:40:47 +08:00
morgan 74d07a579c Gateware: kasli_soc WRPLL setup
kasli_soc: use enable_wrpll from json to switch between si5324 to si549
kasli_soc: add wrpll for all variants
kasli_soc: add gtx & main tag nFIQ for all variants
kasli_soc: add wrpll_refclk for runtime
kasli_soc: add skewtester for satman
kasli_soc: add WRPLL_REF_CLK config for firmware

kasli soc: move enable_wrpll to json
2024-03-19 10:39:21 +08:00
morgan 303aad20ac Gateware: WRPLL
ddmtd: add DDMTD and deglitcher
wrpll: add helper clockdomain
wrpll: add frequency counter
wrpll: add skewtester
wrpll: add gtx & main tag collection
wrpll: add gtx & main tag eventmanager for shared peripheral interrupt
wrpll: add SMA frequency multiplier to generate 125Mhz refclk
si549: add i2c and adpll programmer
2024-03-19 10:39:11 +08:00
morgan 945a9f1c47 kasli soc: refactor clock synth 2024-03-15 12:11:32 +08:00
morgan 4f34a7c6d0 flake: update artiq 2024-03-15 12:11:32 +08:00
9 changed files with 249 additions and 95 deletions

View File

@ -11,11 +11,11 @@
"src-pythonparser": "src-pythonparser"
},
"locked": {
"lastModified": 1706785107,
"narHash": "sha256-Uj72tqigiOCdewSSBBMg6zUpVKhwjAo1HeLJgvyZ3oc=",
"lastModified": 1710303235,
"narHash": "sha256-0rIfVoL8RInAQSDVfjpLdMqIYdnVsA8DdMk2+aqvwrM=",
"ref": "refs/heads/master",
"rev": "3aaa7e04f26a495e8847e47424bfc16d76d82bf8",
"revCount": 8672,
"rev": "c4323e1179aa0b9c9b4c135f894f267715cf2391",
"revCount": 8727,
"type": "git",
"url": "https://github.com/m-labs/artiq.git"
},
@ -37,11 +37,11 @@
]
},
"locked": {
"lastModified": 1701573753,
"narHash": "sha256-vhEtXjb9AM6/HnsgfVmhJQeqQ9JqysUm7iWNzTIbexs=",
"lastModified": 1707216368,
"narHash": "sha256-ZXoqzG2QsVsybALLYXs473avXcyKSZNh2kIgcPo60XQ=",
"owner": "m-labs",
"repo": "artiq-comtools",
"rev": "199bdabf4de49cb7ada8a4ac7133008e0f8434b7",
"rev": "e5d0204490bccc07ef9141b0d7c405ab01cb8273",
"type": "github"
},
"original": {
@ -118,11 +118,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1706515015,
"narHash": "sha256-eFfY5A7wlYy3jD/75lx6IJRueg4noE+jowl0a8lIlVo=",
"lastModified": 1707347730,
"narHash": "sha256-0etC/exQIaqC9vliKhc3eZE2Mm2wgLa0tj93ZF/egvM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f4a8d6d5324c327dcc2d863eb7f3cc06ad630df4",
"rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6",
"type": "github"
},
"original": {

View File

@ -64,4 +64,4 @@ class DDMTD(Module):
self.h_tag_update.eq(1),
self.h_tag.eq(counter)
)
]
]

View File

@ -119,9 +119,10 @@ class GTPBootstrapClock(Module):
class GenericStandalone(SoCCore):
def __init__(self, description, acpki=False, with_wrpll=False):
def __init__(self, description, acpki=False):
self.acpki = acpki
clk_freq = description["rtio_frequency"]
with_wrpll = description["enable_wrpll"]
platform = kasli_soc.Platform()
platform.toolchain.bitstream_commands.extend([
@ -222,8 +223,9 @@ class GenericStandalone(SoCCore):
class GenericMaster(SoCCore):
def __init__(self, description, acpki=False, with_wrpll=False):
def __init__(self, description, acpki=False):
clk_freq = description["rtio_frequency"]
with_wrpll = description["enable_wrpll"]
has_drtio_over_eem = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"])
self.acpki = acpki
@ -427,8 +429,9 @@ class GenericMaster(SoCCore):
class GenericSatellite(SoCCore):
def __init__(self, description, acpki=False, with_wrpll=False):
def __init__(self, description, acpki=False):
clk_freq = description["rtio_frequency"]
with_wrpll = description["enable_wrpll"]
self.acpki = acpki
@ -586,6 +589,8 @@ class GenericSatellite(SoCCore):
platform=self.platform,
cd_ref=self.gt_drtio.cd_rtio_rx0,
main_clk_se=self.clk_synth.se)
self.submodules.wrpll_skewtester = wrpll.SkewTester(self.rx_synchronizer)
self.csr_devices.append("wrpll_skewtester")
self.csr_devices.append("wrpll")
self.comb += self.ps7.core.core0.nfiq.eq(self.wrpll.ev.irq)
self.config["HAS_SI549"] = None
@ -628,8 +633,6 @@ def main():
help="build gateware into the specified directory")
parser.add_argument("--acpki", default=False, action="store_true",
help="enable ACPKI")
parser.add_argument("--with-wrpll", default=False, action="store_true",
help="enable WRPLL")
parser.add_argument("description", metavar="DESCRIPTION",
help="JSON system description file")
args = parser.parse_args()
@ -647,7 +650,7 @@ def main():
else:
raise ValueError("Invalid DRTIO role")
soc = cls(description, acpki=args.acpki, with_wrpll=args.with_wrpll)
soc = cls(description, acpki=args.acpki)
soc.finalize()
if args.r is not None:

View File

@ -44,6 +44,28 @@ class FrequencyCounter(Module, AutoCSR):
)
]
class SkewTester(Module, AutoCSR):
def __init__(self, rx_synchronizer):
self.error = CSR()
# # #
# The RX synchronizer is tested for setup/hold violations by feeding it a
# toggling pattern and checking that the same toggling pattern comes out.
toggle_in = Signal()
self.sync.rtio_rx0 += toggle_in.eq(~toggle_in)
toggle_out = rx_synchronizer.resync(toggle_in)
toggle_out_expected = Signal()
self.sync += toggle_out_expected.eq(~toggle_out)
error = Signal()
self.sync += [
If(toggle_out != toggle_out_expected, error.eq(1)),
If(self.error.re, error.eq(0))
]
self.specials += MultiReg(error, self.error.w)
class WRPLL(Module, AutoCSR):
def __init__(self, platform, cd_ref, main_clk_se, COUNTER_BIT=32):

View File

@ -10,6 +10,7 @@ name = "libboard_artiq"
[features]
target_zc706 = ["libboard_zynq/target_zc706", "libconfig/target_zc706"]
target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libconfig/target_kasli_soc"]
calibrate_wrpll_skew = []
[build-dependencies]
build_zynq = { path = "../libbuild_zynq" }

View File

@ -7,6 +7,8 @@ use crate::pl::csr;
#[cfg(feature = "target_kasli_soc")]
const ADDRESS: u8 = 0x67;
const ADPLL_MAX: i32 = (950.0 / 0.0001164) as i32;
pub struct DividerConfig {
pub hsdiv: u16,
pub lsdiv: u8,
@ -262,17 +264,83 @@ pub fn main_setup(timer: &mut GlobalTimer, settings: &FrequencySetting) -> Resul
Ok(())
}
pub fn helper_setup(timer: &mut GlobalTimer, settings: &FrequencySetting) -> Result<(), &'static str> {
unsafe {
csr::wrpll::helper_reset_write(1);
csr::wrpll::helper_dcxo_bitbang_enable_write(1);
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
}
setup(i2c::DCXO::Helper, &settings.helper, timer)?;
// Si549 maximum settling time for large frequency change.
timer.delay_us(40_000);
unsafe {
csr::wrpll::helper_reset_write(0);
csr::wrpll::helper_dcxo_bitbang_enable_write(0);
}
info!("Helper Si549 started");
Ok(())
}
/// set adpll using gateware i2c
/// Note: disable main/helper i2c bitbang before using this function
fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> {
if adpll.abs() > ADPLL_MAX {
return Err("adpll is too large");
}
match dcxo {
i2c::DCXO::Main => unsafe {
if csr::wrpll::main_dcxo_bitbang_enable_read() == 1 {
return Err("Main si549 bitbang mode is active when using gateware i2c");
}
while csr::wrpll::main_dcxo_adpll_busy_read() == 1 {}
csr::wrpll::main_dcxo_i2c_address_write(ADDRESS);
csr::wrpll::main_dcxo_adpll_write(adpll as u32);
csr::wrpll::main_dcxo_adpll_stb_write(1);
csr::wrpll::main_dcxo_adpll_stb_write(0);
if csr::wrpll::main_dcxo_nack_read() == 1 {
return Err("Main si549 failed to ack adpll write");
}
},
i2c::DCXO::Helper => unsafe {
if csr::wrpll::helper_dcxo_bitbang_enable_read() == 1 {
return Err("Helper si549 bitbang mode is active when using gateware i2c");
}
while csr::wrpll::helper_dcxo_adpll_busy_read() == 1 {}
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
csr::wrpll::helper_dcxo_adpll_write(adpll as u32);
csr::wrpll::helper_dcxo_adpll_stb_write(1);
csr::wrpll::helper_dcxo_adpll_stb_write(0);
if csr::wrpll::helper_dcxo_nack_read() == 1 {
return Err("Helper si549 failed to ack adpll write");
}
},
};
Ok(())
}
#[cfg(has_wrpll)]
pub mod wrpll {
use libcortex_a9::mutex::Mutex;
use super::*;
const BEATING_PERIOD: i32 = 0x8000;
const BEATING_HALFPERIOD: i32 = 0x4000;
const TIMER_WIDTH: u32 = 24;
const COUNTER_DIV: u32 = 2;
const ADPLL_MAX: i32 = (950.0 / 0.0001164) as i32;
const KP: i32 = 6;
const KI: i32 = 2;
@ -289,9 +357,10 @@ pub mod wrpll {
mod tag_collector {
use super::*;
const BEATING_PERIOD: i32 = 0x8000;
const BEATING_HALFPERIOD: i32 = 0x4000;
#[cfg(wrpll_ref_clk = "GTX_CDR")]
static TAG_OFFSET: Mutex<u32> = Mutex::new(19050);
#[cfg(wrpll_ref_clk = "SMA_CLKIN")]
static TAG_OFFSET: Mutex<u32> = Mutex::new(0);
static REF_TAG: Mutex<u32> = Mutex::new(0);
static REF_TAG_READY: Mutex<bool> = Mutex::new(false);
static MAIN_TAG: Mutex<u32> = Mutex::new(0);
@ -325,6 +394,16 @@ pub mod wrpll {
*REF_TAG_READY.lock() && *MAIN_TAG_READY.lock()
}
#[cfg(feature = "calibrate_wrpll_skew")]
pub fn set_tag_offset(offset: u32) {
*TAG_OFFSET.lock() = offset;
}
#[cfg(feature = "calibrate_wrpll_skew")]
pub fn get_tag_offset() -> u32 {
*TAG_OFFSET.lock()
}
pub fn get_period_error() -> i32 {
// n * BEATING_PERIOD - REF_TAG(n) mod BEATING_PERIOD
let mut period_error = (*REF_TAG.lock()).overflowing_neg().0.rem_euclid(BEATING_PERIOD as u32) as i32;
@ -337,9 +416,9 @@ pub mod wrpll {
}
pub fn get_phase_error() -> i32 {
// MAIN_TAG(n) - REF_TAG(n) mod BEATING_PERIOD
// MAIN_TAG(n) - REF_TAG(n) - TAG_OFFSET mod BEATING_PERIOD
let mut phase_error = (*MAIN_TAG.lock())
.overflowing_sub(*REF_TAG.lock())
.overflowing_sub(*REF_TAG.lock() + *TAG_OFFSET.lock())
.0
.rem_euclid(BEATING_PERIOD as u32) as i32;
@ -351,26 +430,6 @@ pub mod wrpll {
}
}
pub fn helper_setup(timer: &mut GlobalTimer, settings: &FrequencySetting) -> Result<(), &'static str> {
unsafe {
csr::wrpll::helper_reset_write(1);
csr::wrpll::helper_dcxo_bitbang_enable_write(1);
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
}
setup(i2c::DCXO::Helper, &settings.helper, timer)?;
// Si549 maximum settling time for large frequency change.
timer.delay_us(40_000);
unsafe {
csr::wrpll::helper_reset_write(0);
csr::wrpll::helper_dcxo_bitbang_enable_write(0);
}
info!("Helper Si549 started");
Ok(())
}
fn set_isr(en: bool) {
let val = if en { 1 } else { 0 };
unsafe {
@ -379,51 +438,6 @@ pub mod wrpll {
}
}
/// set adpll using gateware i2c
/// Note: disable main/helper i2c bitbang before using this function
fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> {
if adpll.abs() > ADPLL_MAX {
return Err("adpll is too large");
}
match dcxo {
i2c::DCXO::Main => unsafe {
if csr::wrpll::main_dcxo_bitbang_enable_read() == 1 {
return Err("Main si549 bitbang mode is active when using gateware i2c");
}
while csr::wrpll::main_dcxo_adpll_busy_read() == 1 {}
csr::wrpll::main_dcxo_i2c_address_write(ADDRESS);
csr::wrpll::main_dcxo_adpll_write(adpll as u32);
csr::wrpll::main_dcxo_adpll_stb_write(1);
csr::wrpll::main_dcxo_adpll_stb_write(0);
if csr::wrpll::main_dcxo_nack_read() == 1 {
return Err("Main si549 failed to ack adpll write");
}
},
i2c::DCXO::Helper => unsafe {
if csr::wrpll::helper_dcxo_bitbang_enable_read() == 1 {
return Err("Helper si549 bitbang mode is active when using gateware i2c");
}
while csr::wrpll::helper_dcxo_adpll_busy_read() == 1 {}
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
csr::wrpll::helper_dcxo_adpll_write(adpll as u32);
csr::wrpll::helper_dcxo_adpll_stb_write(1);
csr::wrpll::helper_dcxo_adpll_stb_write(0);
if csr::wrpll::helper_dcxo_nack_read() == 1 {
return Err("Helper si549 failed to ack adpll write");
}
},
};
Ok(())
}
/// To get within capture range
fn set_base_adpll(timer: &mut GlobalTimer) -> Result<(), &'static str> {
let count2adpll =
@ -520,6 +534,113 @@ pub mod wrpll {
Ok(())
}
#[cfg(wrpll_ref_clk = "GTX_CDR")]
fn test_skew(timer: &mut GlobalTimer) -> Result<(), &'static str> {
// wait for PLL to stabilize
timer.delay_us(20_000);
info!("testing the skew of SYS CLK...");
if has_timing_error(timer) {
return Err("the skew cannot satisfy setup/hold time constraint of RX synchronizer");
}
info!("the skew of SYS CLK met the timing constraint");
Ok(())
}
#[cfg(wrpll_ref_clk = "GTX_CDR")]
fn has_timing_error(timer: &mut GlobalTimer) -> bool {
unsafe {
csr::wrpll_skewtester::error_write(1);
}
timer.delay_us(5_000);
unsafe { csr::wrpll_skewtester::error_read() == 1 }
}
#[cfg(feature = "calibrate_wrpll_skew")]
fn find_edge(target: bool, timer: &mut GlobalTimer) -> Result<u32, &'static str> {
const STEP: u32 = 8;
const STABLE_THRESHOLD: u32 = 10;
enum FSM {
Init,
WaitEdge,
GotEdge,
}
let mut state: FSM = FSM::Init;
let mut offset: u32 = tag_collector::get_tag_offset();
let mut median_edge: u32 = 0;
let mut stable_counter: u32 = 0;
for _ in 0..(BEATING_PERIOD as u32 / STEP) as usize {
tag_collector::set_tag_offset(offset);
offset += STEP;
// wait for PLL to stabilize
timer.delay_us(20_000);
let error = has_timing_error(timer);
// A median edge deglitcher
match state {
FSM::Init => {
if error != target {
stable_counter += 1;
} else {
stable_counter = 0;
}
if stable_counter >= STABLE_THRESHOLD {
state = FSM::WaitEdge;
stable_counter = 0;
}
}
FSM::WaitEdge => {
if error == target {
state = FSM::GotEdge;
median_edge = offset;
}
}
FSM::GotEdge => {
if error != target {
median_edge += STEP;
stable_counter = 0;
} else {
stable_counter += 1;
}
if stable_counter >= STABLE_THRESHOLD {
return Ok(median_edge);
}
}
}
}
return Err("failed to find timing error edge");
}
#[cfg(feature = "calibrate_wrpll_skew")]
pub fn calibrate_skew(timer: &mut GlobalTimer) -> Result<(), &'static str> {
info!("calibrating skew to meet timing constraint...");
// clear calibrated value
tag_collector::set_tag_offset(0);
let rising = find_edge(true, timer)? as i32;
let falling = find_edge(false, timer)? as i32;
let width = BEATING_PERIOD - (falling - rising);
let result = falling + width / 2;
tag_collector::set_tag_offset(result as u32);
info!(
"calibration successful, error zone: {} -> {}, width: {} ({}deg), middle of working region: {}",
rising,
falling,
width,
360 * width / BEATING_PERIOD,
result,
);
Ok(())
}
pub fn select_recovered_clock(rc: bool, timer: &mut GlobalTimer) {
set_isr(false);
@ -539,6 +660,12 @@ pub mod wrpll {
// use nFIQ to avoid IRQ being disabled by mutex lock and mess up PLL
set_isr(true);
info!("WRPLL interrupt enabled");
#[cfg(feature = "calibrate_wrpll_skew")]
calibrate_skew(timer).expect("failed to set the correct skew");
#[cfg(wrpll_ref_clk = "GTX_CDR")]
test_skew(timer).expect("skew test failed");
}
}
}

View File

@ -262,7 +262,7 @@ fn setup_si5324(i2c: &mut I2c, timer: &mut GlobalTimer, clk: RtioClock) {
si5324::setup(i2c, &si5324_settings, si5324_ref_input, timer).expect("cannot initialize Si5324");
}
#[cfg(has_si549)]
#[cfg(all(has_si549, has_wrpll))]
fn wrpll_setup(timer: &mut GlobalTimer, clk: RtioClock, si549_settings: &si549::FrequencySetting) {
// register values are directly copied from preconfigured mmcm
let (mmcm_setting, mmcm_bypass) = match clk {
@ -337,7 +337,7 @@ fn wrpll_setup(timer: &mut GlobalTimer, clk: RtioClock, si549_settings: &si549::
_ => unreachable!(),
};
si549::wrpll::helper_setup(timer, &si549_settings).expect("cannot initialize helper Si549");
si549::helper_setup(timer, &si549_settings).expect("cannot initialize helper Si549");
si549::wrpll_refclk::setup(timer, mmcm_setting, mmcm_bypass).expect("cannot initialize ref clk for wrpll");
si549::wrpll::select_recovered_clock(true, timer);
}
@ -432,7 +432,7 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
#[cfg(not(has_drtio))]
init_rtio(timer);
#[cfg(has_si549)]
#[cfg(all(has_si549, has_wrpll))]
{
// SYS CLK switch will reset CSRs that are used by WRPLL
match clk {

View File

@ -7,6 +7,7 @@ build = "build.rs"
[features]
target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"]
target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"]
calibrate_wrpll_skew = ["libboard_artiq/calibrate_wrpll_skew"]
default = ["target_zc706", ]
[build-dependencies]

View File

@ -932,7 +932,7 @@ pub extern "C" fn main_core0() -> i32 {
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
}
#[cfg(has_si549)]
si549::wrpll::helper_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize helper Si549");
si549::helper_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize helper Si549");
#[cfg(has_drtio_routing)]
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
@ -978,7 +978,7 @@ pub extern "C" fn main_core0() -> i32 {
si5324::siphaser::calibrate_skew(&mut timer).expect("failed to calibrate skew");
}
#[cfg(has_si549)]
#[cfg(has_wrpll)]
si549::wrpll::select_recovered_clock(true, &mut timer);
// Various managers created here, so when link is dropped, all DMA traces
@ -1078,7 +1078,7 @@ pub extern "C" fn main_core0() -> i32 {
info!("uplink is down, switching to local oscillator clock");
#[cfg(has_siphaser)]
si5324::siphaser::select_recovered_clock(&mut i2c, false, &mut timer).expect("failed to switch clocks");
#[cfg(has_si549)]
#[cfg(has_wrpll)]
si549::wrpll::select_recovered_clock(false, &mut timer);
}
}