From cf2a4972f742c87d300305e1a75c08e97e6568a4 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 6 Jan 2023 17:53:11 +0800 Subject: [PATCH] remove WRPLL --- artiq/firmware/libboard_artiq/lib.rs | 2 - artiq/firmware/libboard_artiq/wrpll.rs | 536 --------------------- artiq/firmware/satman/main.rs | 21 - artiq/gateware/drtio/wrpll/__init__.py | 2 - artiq/gateware/drtio/wrpll/core.py | 156 ------- artiq/gateware/drtio/wrpll/ddmtd.py | 221 --------- artiq/gateware/drtio/wrpll/filters.py | 61 --- artiq/gateware/drtio/wrpll/si549.py | 340 -------------- artiq/gateware/drtio/wrpll/thls.py | 618 ------------------------- artiq/gateware/targets/kasli.py | 50 +- artiq/gateware/test/wrpll/__init__.py | 0 artiq/gateware/test/wrpll/test_dsp.py | 158 ------- artiq/gateware/test/wrpll/test_thls.py | 55 --- doc/wrpll_diagram.png | Bin 58653 -> 0 bytes 14 files changed, 13 insertions(+), 2207 deletions(-) delete mode 100644 artiq/firmware/libboard_artiq/wrpll.rs delete mode 100644 artiq/gateware/drtio/wrpll/__init__.py delete mode 100644 artiq/gateware/drtio/wrpll/core.py delete mode 100644 artiq/gateware/drtio/wrpll/ddmtd.py delete mode 100644 artiq/gateware/drtio/wrpll/filters.py delete mode 100644 artiq/gateware/drtio/wrpll/si549.py delete mode 100644 artiq/gateware/drtio/wrpll/thls.py delete mode 100644 artiq/gateware/test/wrpll/__init__.py delete mode 100644 artiq/gateware/test/wrpll/test_dsp.py delete mode 100644 artiq/gateware/test/wrpll/test_thls.py delete mode 100644 doc/wrpll_diagram.png diff --git a/artiq/firmware/libboard_artiq/lib.rs b/artiq/firmware/libboard_artiq/lib.rs index 7436fc8c5..209da3048 100644 --- a/artiq/firmware/libboard_artiq/lib.rs +++ b/artiq/firmware/libboard_artiq/lib.rs @@ -22,8 +22,6 @@ pub mod rpc_queue; #[cfg(has_si5324)] pub mod si5324; -#[cfg(has_wrpll)] -pub mod wrpll; #[cfg(has_grabber)] pub mod grabber; diff --git a/artiq/firmware/libboard_artiq/wrpll.rs b/artiq/firmware/libboard_artiq/wrpll.rs deleted file mode 100644 index a9c9a702f..000000000 --- a/artiq/firmware/libboard_artiq/wrpll.rs +++ /dev/null @@ -1,536 +0,0 @@ -use board_misoc::{csr, clock}; - -mod i2c { - use board_misoc::{csr, clock}; - - #[derive(Debug, Clone, Copy)] - pub enum Dcxo { - Main, - Helper - } - - fn half_period() { clock::spin_us(1) } - const SDA_MASK: u8 = 2; - const SCL_MASK: u8 = 1; - - fn sda_i(dcxo: Dcxo) -> bool { - let reg = match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_in_read() }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_in_read() }, - }; - reg & SDA_MASK != 0 - } - - fn sda_oe(dcxo: Dcxo, oe: bool) { - let reg = match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_oe_read() }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_oe_read() }, - }; - let reg = if oe { reg | SDA_MASK } else { reg & !SDA_MASK }; - match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_oe_write(reg) }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_oe_write(reg) } - } - } - - fn sda_o(dcxo: Dcxo, o: bool) { - let reg = match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_out_read() }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_out_read() }, - }; - let reg = if o { reg | SDA_MASK } else { reg & !SDA_MASK }; - match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_out_write(reg) }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_out_write(reg) } - } - } - - fn scl_oe(dcxo: Dcxo, oe: bool) { - let reg = match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_oe_read() }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_oe_read() }, - }; - let reg = if oe { reg | SCL_MASK } else { reg & !SCL_MASK }; - match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_oe_write(reg) }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_oe_write(reg) } - } - } - - fn scl_o(dcxo: Dcxo, o: bool) { - let reg = match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_out_read() }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_out_read() }, - }; - let reg = if o { reg | SCL_MASK } else { reg & !SCL_MASK }; - match dcxo { - Dcxo::Main => unsafe { csr::wrpll::main_dcxo_gpio_out_write(reg) }, - Dcxo::Helper => unsafe { csr::wrpll::helper_dcxo_gpio_out_write(reg) } - } - } - - pub fn init(dcxo: Dcxo) -> Result<(), &'static str> { - // Set SCL as output, and high level - scl_o(dcxo, true); - scl_oe(dcxo, true); - // Prepare a zero level on SDA so that sda_oe pulls it down - sda_o(dcxo, false); - // Release SDA - sda_oe(dcxo, false); - - // Check the I2C bus is ready - half_period(); - half_period(); - if !sda_i(dcxo) { - // Try toggling SCL a few times - for _bit in 0..8 { - scl_o(dcxo, false); - half_period(); - scl_o(dcxo, true); - half_period(); - } - } - - if !sda_i(dcxo) { - return Err("SDA is stuck low and doesn't get unstuck"); - } - Ok(()) - } - - pub fn start(dcxo: Dcxo) { - // Set SCL high then SDA low - scl_o(dcxo, true); - half_period(); - sda_oe(dcxo, true); - half_period(); - } - - pub fn stop(dcxo: Dcxo) { - // First, make sure SCL is low, so that the target releases the SDA line - scl_o(dcxo, false); - half_period(); - // Set SCL high then SDA high - sda_oe(dcxo, true); - scl_o(dcxo, true); - half_period(); - sda_oe(dcxo, false); - half_period(); - } - - pub fn write(dcxo: Dcxo, data: u8) -> bool { - // MSB first - for bit in (0..8).rev() { - // Set SCL low and set our bit on SDA - scl_o(dcxo, false); - sda_oe(dcxo, data & (1 << bit) == 0); - half_period(); - // Set SCL high ; data is shifted on the rising edge of SCL - scl_o(dcxo, true); - half_period(); - } - // Check ack - // Set SCL low, then release SDA so that the I2C target can respond - scl_o(dcxo, false); - half_period(); - sda_oe(dcxo, false); - // Set SCL high and check for ack - scl_o(dcxo, true); - half_period(); - // returns true if acked (I2C target pulled SDA low) - !sda_i(dcxo) - } - - pub fn read(dcxo: Dcxo, ack: bool) -> u8 { - // Set SCL low first, otherwise setting SDA as input may cause a transition - // on SDA with SCL high which will be interpreted as START/STOP condition. - scl_o(dcxo, false); - half_period(); // make sure SCL has settled low - sda_oe(dcxo, false); - - let mut data: u8 = 0; - - // MSB first - for bit in (0..8).rev() { - scl_o(dcxo, false); - half_period(); - // Set SCL high and shift data - scl_o(dcxo, true); - half_period(); - if sda_i(dcxo) { data |= 1 << bit } - } - // Send ack - // Set SCL low and pull SDA low when acking - scl_o(dcxo, false); - if ack { sda_oe(dcxo, true) } - half_period(); - // then set SCL high - scl_o(dcxo, true); - half_period(); - - data - } -} - -mod si549 { - use board_misoc::clock; - use super::i2c; - - #[cfg(soc_platform = "kasli")] - pub const ADDRESS: u8 = 0x67; - - pub fn write(dcxo: i2c::Dcxo, reg: u8, val: u8) -> Result<(), &'static str> { - i2c::start(dcxo); - if !i2c::write(dcxo, ADDRESS << 1) { - return Err("Si549 failed to ack write address") - } - if !i2c::write(dcxo, reg) { - return Err("Si549 failed to ack register") - } - if !i2c::write(dcxo, val) { - return Err("Si549 failed to ack value") - } - i2c::stop(dcxo); - Ok(()) - } - - pub fn write_no_ack_value(dcxo: i2c::Dcxo, reg: u8, val: u8) -> Result<(), &'static str> { - i2c::start(dcxo); - if !i2c::write(dcxo, ADDRESS << 1) { - return Err("Si549 failed to ack write address") - } - if !i2c::write(dcxo, reg) { - return Err("Si549 failed to ack register") - } - i2c::write(dcxo, val); - i2c::stop(dcxo); - Ok(()) - } - - pub fn read(dcxo: i2c::Dcxo, reg: u8) -> Result { - i2c::start(dcxo); - if !i2c::write(dcxo, ADDRESS << 1) { - return Err("Si549 failed to ack write address") - } - if !i2c::write(dcxo, reg) { - return Err("Si549 failed to ack register") - } - i2c::stop(dcxo); - - i2c::start(dcxo); - if !i2c::write(dcxo, (ADDRESS << 1) | 1) { - return Err("Si549 failed to ack read address") - } - let val = i2c::read(dcxo, false); - i2c::stop(dcxo); - - Ok(val) - } - - pub fn program(dcxo: i2c::Dcxo, hsdiv: u16, lsdiv: u8, fbdiv: u64) -> Result<(), &'static str> { - i2c::init(dcxo)?; - - write(dcxo, 255, 0x00)?; // PAGE - write_no_ack_value(dcxo, 7, 0x80)?; // RESET - clock::spin_us(100_000); // required? not specified in datasheet. - - write(dcxo, 255, 0x00)?; // PAGE - write(dcxo, 69, 0x00)?; // Disable FCAL override. - // Note: Value 0x00 from Table 5.6 is inconsistent with Table 5.7, - // which shows bit 0 as reserved and =1. - write(dcxo, 17, 0x00)?; // Synchronously disable output - - // The Si549 has no ID register, so we check that it responds correctly - // by writing values to a RAM-like register and reading them back. - for test_value in 0..255 { - write(dcxo, 23, test_value)?; - let readback = read(dcxo, 23)?; - if readback != test_value { - return Err("Si549 detection failed"); - } - } - - write(dcxo, 23, hsdiv as u8)?; - write(dcxo, 24, (hsdiv >> 8) as u8 | (lsdiv << 4))?; - write(dcxo, 26, fbdiv as u8)?; - write(dcxo, 27, (fbdiv >> 8) as u8)?; - write(dcxo, 28, (fbdiv >> 16) as u8)?; - write(dcxo, 29, (fbdiv >> 24) as u8)?; - write(dcxo, 30, (fbdiv >> 32) as u8)?; - write(dcxo, 31, (fbdiv >> 40) as u8)?; - - write(dcxo, 7, 0x08)?; // Start FCAL - write(dcxo, 17, 0x01)?; // Synchronously enable output - - Ok(()) - } - - // Si549 digital frequency trim ("all-digital PLL" register) - // ∆ f_out = adpll * 0.0001164e-6 (0.1164 ppb/lsb) - // max trim range is +- 950 ppm - pub fn set_adpll(dcxo: i2c::Dcxo, adpll: i32) -> Result<(), &'static str> { - write(dcxo, 231, adpll as u8)?; - write(dcxo, 232, (adpll >> 8) as u8)?; - write(dcxo, 233, (adpll >> 16) as u8)?; - clock::spin_us(100); - Ok(()) - } - - pub fn get_adpll(dcxo: i2c::Dcxo) -> Result { - let b1 = read(dcxo, 231)? as i32; - let b2 = read(dcxo, 232)? as i32; - let b3 = read(dcxo, 233)? as i8 as i32; - Ok(b3 << 16 | b2 << 8 | b1) - } -} - -// to do: load from gateware config -const DDMTD_COUNTER_N: u32 = 15; -const DDMTD_COUNTER_M: u32 = (1 << DDMTD_COUNTER_N); -const F_SYS: f64 = csr::CONFIG_CLOCK_FREQUENCY as f64; - -const F_MAIN: f64 = 125.0e6; -const F_HELPER: f64 = F_MAIN * DDMTD_COUNTER_M as f64 / (DDMTD_COUNTER_M + 1) as f64; -const F_BEAT: f64 = F_MAIN - F_HELPER; -const TIME_STEP: f32 = 1./F_BEAT as f32; - -fn ddmtd_tag_to_s(mu: f32) -> f32 { - return (mu as f32)*TIME_STEP; -} - -fn get_frequencies() -> (u32, u32, u32) { - unsafe { - csr::wrpll::frequency_counter_update_en_write(1); - // wait for at least one full update cycle (> 2 timer periods) - clock::spin_us(200_000); - csr::wrpll::frequency_counter_update_en_write(0); - let helper = csr::wrpll::frequency_counter_counter_helper_read(); - let main = csr::wrpll::frequency_counter_counter_rtio_read(); - let cdr = csr::wrpll::frequency_counter_counter_rtio_rx0_read(); - (helper, main, cdr) - } -} - -fn log_frequencies() -> (u32, u32, u32) { - let (f_helper, f_main, f_cdr) = get_frequencies(); - let conv_khz = |f| 4*(f as u64)*(csr::CONFIG_CLOCK_FREQUENCY as u64)/(1000*(1 << 23)); - info!("helper clock frequency: {}kHz ({})", conv_khz(f_helper), f_helper); - info!("main clock frequency: {}kHz ({})", conv_khz(f_main), f_main); - info!("CDR clock frequency: {}kHz ({})", conv_khz(f_cdr), f_cdr); - (f_helper, f_main, f_cdr) -} - -fn get_tags() -> (i32, i32, u16, u16) { - unsafe { - csr::wrpll::tag_arm_write(1); - while csr::wrpll::tag_arm_read() != 0 {} - - let main_diff = csr::wrpll::main_diff_tag_read() as i32; - let helper_diff = csr::wrpll::helper_diff_tag_read() as i32; - let ref_tag = csr::wrpll::ref_tag_read(); - let main_tag = csr::wrpll::main_tag_read(); - (main_diff, helper_diff, ref_tag, main_tag) - } -} - -fn print_tags() { - const NUM_TAGS: usize = 30; - let mut main_diffs = [0; NUM_TAGS]; // input to main loop filter - let mut helper_diffs = [0; NUM_TAGS]; // input to helper loop filter - let mut ref_tags = [0; NUM_TAGS]; - let mut main_tags = [0; NUM_TAGS]; - let mut jitter = [0 as f32; NUM_TAGS]; - - for i in 0..NUM_TAGS { - let (main_diff, helper_diff, ref_tag, main_tag) = get_tags(); - main_diffs[i] = main_diff; - helper_diffs[i] = helper_diff; - ref_tags[i] = ref_tag; - main_tags[i] = main_tag; - } - info!("DDMTD ref tags: {:?}", ref_tags); - info!("DDMTD main tags: {:?}", main_tags); - info!("DDMTD main diffs: {:?}", main_diffs); - info!("DDMTD helper diffs: {:?}", helper_diffs); - - // look at the difference between the main DCXO and reference... - let t0 = main_diffs[0]; - main_diffs.iter_mut().for_each(|x| *x -= t0); - - // crude estimate of the max difference across our sample set (assumes no unwrapping issues...) - let delta = main_diffs[main_diffs.len()-1] as f32 / (main_diffs.len()-1) as f32; - info!("detla: {:?} tags", delta); - let delta_f: f32 = delta/DDMTD_COUNTER_M as f32 * F_BEAT as f32; - info!("MAIN <-> ref frequency difference: {:?} Hz ({:?} ppm)", delta_f, delta_f/F_HELPER as f32 * 1e6); - - jitter.iter_mut().enumerate().for_each(|(i, x)| *x = main_diffs[i] as f32 - delta*(i as f32)); - info!("jitter: {:?} tags", jitter); - - let var = jitter.iter().map(|x| x*x).fold(0 as f32, |acc, x| acc + x as f32) / NUM_TAGS as f32; - info!("variance: {:?} tags^2", var); -} - -pub fn init() { - info!("initializing WR PLL..."); - - unsafe { csr::wrpll::helper_reset_write(1); } - - unsafe { - csr::wrpll::helper_dcxo_i2c_address_write(si549::ADDRESS); - csr::wrpll::main_dcxo_i2c_address_write(si549::ADDRESS); - } - - #[cfg(rtio_frequency = "125.0")] - let (h_hsdiv, h_lsdiv, h_fbdiv) = (0x05c, 0, 0x04b5badb98a); - #[cfg(rtio_frequency = "125.0")] - let (m_hsdiv, m_lsdiv, m_fbdiv) = (0x05c, 0, 0x04b5c447213); - - si549::program(i2c::Dcxo::Main, m_hsdiv, m_lsdiv, m_fbdiv) - .expect("cannot initialize main Si549"); - si549::program(i2c::Dcxo::Helper, h_hsdiv, h_lsdiv, h_fbdiv) - .expect("cannot initialize helper Si549"); - // Si549 Settling Time for Large Frequency Change. - // Datasheet said 10ms but it lied. - clock::spin_us(50_000); - - unsafe { csr::wrpll::helper_reset_write(0); } - clock::spin_us(1); -} - -pub fn diagnostics() { - info!("WRPLL diagnostics..."); - info!("Untrimmed oscillator frequencies:"); - log_frequencies(); - - info!("Increase helper DCXO frequency by +10ppm (1.25kHz):"); - si549::set_adpll(i2c::Dcxo::Helper, 85911).expect("ADPLL write failed"); - // to do: add check on frequency? - log_frequencies(); -} - -fn trim_dcxos(f_helper: u32, f_main: u32, f_cdr: u32) -> Result<(i32, i32), &'static str> { - info!("Trimming oscillator frequencies..."); - const DCXO_STEP: i64 = (1.0e6/0.0001164) as i64; - const ADPLL_MAX: i64 = (950.0/0.0001164) as i64; - - const TIMER_WIDTH: u32 = 23; - const COUNTER_DIV: u32 = 2; - - // how many counts we expect to measure - const SYS_COUNTS: i64 = (1 << (TIMER_WIDTH - COUNTER_DIV)) as i64; - const EXP_MAIN_COUNTS: i64 = ((SYS_COUNTS as f64) * (F_MAIN/F_SYS)) as i64; - const EXP_HELPER_COUNTS: i64 = ((SYS_COUNTS as f64) * (F_HELPER/F_SYS)) as i64; - - // calibrate the SYS clock to the CDR clock and correct the measured counts - // assume frequency errors are small so we can make an additive correction - // positive error means sys clock is too fast - let sys_err: i64 = EXP_MAIN_COUNTS - (f_cdr as i64); - let main_err: i64 = EXP_MAIN_COUNTS - (f_main as i64) - sys_err; - let helper_err: i64 = EXP_HELPER_COUNTS - (f_helper as i64) - sys_err; - - info!("sys count err {}", sys_err); - info!("main counts err {}", main_err); - info!("helper counts err {}", helper_err); - - // calculate required adjustment to the ADPLL register see - // https://www.silabs.com/documents/public/data-sheets/si549-datasheet.pdf - // section 5.6 - let helper_adpll: i64 = helper_err*DCXO_STEP/EXP_HELPER_COUNTS; - let main_adpll: i64 = main_err*DCXO_STEP/EXP_MAIN_COUNTS; - if helper_adpll.abs() > ADPLL_MAX { - return Err("helper DCXO offset too large"); - } - if main_adpll.abs() > ADPLL_MAX { - return Err("main DCXO offset too large"); - } - - info!("ADPLL offsets: helper={} main={}", helper_adpll, main_adpll); - Ok((helper_adpll as i32, main_adpll as i32)) -} - -fn statistics(data: &[u16]) -> (f32, f32) { - let sum = data.iter().fold(0 as u32, |acc, x| acc + *x as u32); - let mean = sum as f32 / data.len() as f32; - - let squared_sum = data.iter().fold(0 as u32, |acc, x| acc + (*x as u32).pow(2)); - let variance = (squared_sum as f32 / data.len() as f32) - mean; - return (mean, variance) -} - -fn select_recovered_clock_int(rc: bool) -> Result<(), &'static str> { - info!("Untrimmed oscillator frequencies:"); - let (f_helper, f_main, f_cdr) = log_frequencies(); - if rc { - let (helper_adpll, main_adpll) = trim_dcxos(f_helper, f_main, f_cdr)?; - // to do: add assertion on max frequency shift here? - si549::set_adpll(i2c::Dcxo::Helper, helper_adpll).expect("ADPLL write failed"); - si549::set_adpll(i2c::Dcxo::Main, main_adpll).expect("ADPLL write failed"); - - log_frequencies(); - clock::spin_us(100_000); // TO DO: remove/reduce! - print_tags(); - - info!("increasing main DCXO by 1ppm (125Hz):"); - si549::set_adpll(i2c::Dcxo::Main, main_adpll + 8591).expect("ADPLL write failed"); - clock::spin_us(100_000); - print_tags(); - - si549::set_adpll(i2c::Dcxo::Main, main_adpll).expect("ADPLL write failed"); - - unsafe { - csr::wrpll::adpll_offset_helper_write(helper_adpll as u32); - csr::wrpll::adpll_offset_main_write(main_adpll as u32); - csr::wrpll::helper_dcxo_gpio_enable_write(0); - csr::wrpll::main_dcxo_gpio_enable_write(0); - csr::wrpll::helper_dcxo_errors_write(0xff); - csr::wrpll::main_dcxo_errors_write(0xff); - csr::wrpll::collector_reset_write(0); - } - clock::spin_us(1_000); // wait for the collector to produce meaningful output - unsafe { - csr::wrpll::filter_reset_write(0); - } - - clock::spin_us(100_000); - - print_tags(); -// let mut tags = [0; 10]; -// for i in 0..tags.len() { -// tags[i] = get_ddmtd_helper_tag(); -// } -// info!("DDMTD helper tags: {:?}", tags); - - unsafe { - csr::wrpll::filter_reset_write(1); - csr::wrpll::collector_reset_write(1); - } - clock::spin_us(50_000); - unsafe { - csr::wrpll::helper_dcxo_gpio_enable_write(1); - csr::wrpll::main_dcxo_gpio_enable_write(1); - } - unsafe { - info!("error {} {}", - csr::wrpll::helper_dcxo_errors_read(), - csr::wrpll::main_dcxo_errors_read()); - } - info!("new ADPLL: {} {}", - si549::get_adpll(i2c::Dcxo::Helper)?, - si549::get_adpll(i2c::Dcxo::Main)?); - } else { - si549::set_adpll(i2c::Dcxo::Helper, 0).expect("ADPLL write failed"); - si549::set_adpll(i2c::Dcxo::Main, 0).expect("ADPLL write failed"); - } - Ok(()) -} - -pub fn select_recovered_clock(rc: bool) { - if rc { - info!("switching to recovered clock"); - } else { - info!("switching to local XO clock"); - } - match select_recovered_clock_int(rc) { - Ok(()) => info!("clock transition completed"), - Err(e) => error!("clock transition failed: {}", e) - } -} diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 7d69f7d0d..365131b7a 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -12,8 +12,6 @@ use core::convert::TryFrom; use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp}; #[cfg(has_si5324)] use board_artiq::si5324; -#[cfg(has_wrpll)] -use board_artiq::wrpll; use board_artiq::{spi, drtioaux}; use board_artiq::drtio_routing; use riscv::register::{mcause, mepc, mtval}; @@ -420,8 +418,6 @@ fn sysclk_setup() { else { #[cfg(has_si5324)] si5324::setup(&SI5324_SETTINGS, si5324::Input::Ckin1).expect("cannot initialize Si5324"); - #[cfg(has_wrpll)] - wrpll::init(); info!("Switching sys clock, rebooting..."); // delay for clean UART log, wait until UART FIFO is empty clock::spin_us(1300); @@ -460,17 +456,6 @@ pub extern fn main() -> i32 { io_expander1 = board_misoc::io_expander::IoExpander::new(1); io_expander0.init().expect("I2C I/O expander #0 initialization failed"); io_expander1.init().expect("I2C I/O expander #1 initialization failed"); - #[cfg(has_wrpll)] - { - io_expander0.set_oe(1, 1 << 7).unwrap(); - io_expander0.set(1, 7, true); - io_expander0.service().unwrap(); - io_expander1.set_oe(0, 1 << 7).unwrap(); - io_expander1.set_oe(1, 1 << 7).unwrap(); - io_expander1.set(0, 7, true); - io_expander1.set(1, 7, true); - io_expander1.service().unwrap(); - } // Actively drive TX_DISABLE to false on SFP0..3 io_expander0.set_oe(0, 1 << 1).unwrap(); @@ -490,8 +475,6 @@ pub extern fn main() -> i32 { unsafe { csr::drtio_transceiver::txenable_write(0xffffffffu32 as _); } - #[cfg(has_wrpll)] - wrpll::diagnostics(); init_rtio_crg(); @@ -527,8 +510,6 @@ pub extern fn main() -> i32 { si5324::siphaser::select_recovered_clock(true).expect("failed to switch clocks"); si5324::siphaser::calibrate_skew().expect("failed to calibrate skew"); } - #[cfg(has_wrpll)] - wrpll::select_recovered_clock(true); drtioaux::reset(0); drtiosat_reset(false); @@ -565,8 +546,6 @@ pub extern fn main() -> i32 { info!("uplink is down, switching to local oscillator clock"); #[cfg(has_si5324)] si5324::siphaser::select_recovered_clock(false).expect("failed to switch clocks"); - #[cfg(has_wrpll)] - wrpll::select_recovered_clock(false); } } diff --git a/artiq/gateware/drtio/wrpll/__init__.py b/artiq/gateware/drtio/wrpll/__init__.py deleted file mode 100644 index 25e510f4c..000000000 --- a/artiq/gateware/drtio/wrpll/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from artiq.gateware.drtio.wrpll.core import WRPLL -from artiq.gateware.drtio.wrpll.ddmtd import DDMTDSamplerExtFF, DDMTDSamplerGTP diff --git a/artiq/gateware/drtio/wrpll/core.py b/artiq/gateware/drtio/wrpll/core.py deleted file mode 100644 index 3003d4848..000000000 --- a/artiq/gateware/drtio/wrpll/core.py +++ /dev/null @@ -1,156 +0,0 @@ -from migen import * -from migen.genlib.resetsync import AsyncResetSynchronizer -from migen.genlib.cdc import MultiReg, PulseSynchronizer -from misoc.interconnect.csr import * - -from artiq.gateware.drtio.wrpll.si549 import Si549 -from artiq.gateware.drtio.wrpll.ddmtd import DDMTD, Collector -from artiq.gateware.drtio.wrpll import thls, filters - - -class FrequencyCounter(Module, AutoCSR): - def __init__(self, timer_width=23, counter_width=23, domains=["helper", "sys", "rtio_rx0"]): - for domain in domains: - name = "counter_" + domain - counter = CSRStatus(counter_width, name=name) - setattr(self, name, counter) - self.update_en = CSRStorage() - - timer = Signal(timer_width) - timer_tick = Signal() - self.sync += Cat(timer, timer_tick).eq(timer + 1) - - for domain in domains: - sync_domain = getattr(self.sync, domain) - divider = Signal(2) - sync_domain += divider.eq(divider + 1) - - divided = Signal() - divided.attr.add("no_retiming") - sync_domain += divided.eq(divider[-1]) - divided_sys = Signal() - self.specials += MultiReg(divided, divided_sys) - - divided_sys_r = Signal() - divided_tick = Signal() - self.sync += divided_sys_r.eq(divided_sys) - self.comb += divided_tick.eq(divided_sys & ~divided_sys_r) - - counter = Signal(counter_width) - counter_csr = getattr(self, "counter_" + domain) - self.sync += [ - If(timer_tick, - If(self.update_en.storage, counter_csr.status.eq(counter)), - counter.eq(0), - ).Else( - If(divided_tick, counter.eq(counter + 1)) - ) - ] - - -class WRPLL(Module, AutoCSR): - def __init__(self, helper_clk_pads, main_dcxo_i2c, helper_dxco_i2c, ddmtd_inputs, N=15): - self.helper_reset = CSRStorage(reset=1) - self.collector_reset = CSRStorage(reset=1) - self.filter_reset = CSRStorage(reset=1) - self.adpll_offset_helper = CSRStorage(24) - self.adpll_offset_main = CSRStorage(24) - - self.tag_arm = CSR() - self.main_diff_tag = CSRStatus(32) - self.helper_diff_tag = CSRStatus(32) - self.ref_tag = CSRStatus(N) - self.main_tag = CSRStatus(N) - - main_diff_tag_32 = Signal((32, True)) - helper_diff_tag_32 = Signal((32, True)) - self.comb += [ - self.main_diff_tag.status.eq(main_diff_tag_32), - self.helper_diff_tag.status.eq(helper_diff_tag_32) - ] - - self.clock_domains.cd_helper = ClockDomain() - self.clock_domains.cd_collector = ClockDomain() - self.clock_domains.cd_filter = ClockDomain() - self.helper_reset.storage.attr.add("no_retiming") - self.filter_reset.storage.attr.add("no_retiming") - self.specials += Instance("IBUFGDS", - i_I=helper_clk_pads.p, i_IB=helper_clk_pads.n, - o_O=self.cd_helper.clk) - self.comb += [ - self.cd_collector.clk.eq(self.cd_collector.clk), - self.cd_filter.clk.eq(self.cd_helper.clk), - ] - self.specials += [ - AsyncResetSynchronizer(self.cd_helper, self.helper_reset.storage), - AsyncResetSynchronizer(self.cd_collector, self.collector_reset.storage), - AsyncResetSynchronizer(self.cd_filter, self.filter_reset.storage) - ] - - self.submodules.helper_dcxo = Si549(helper_dxco_i2c) - self.submodules.main_dcxo = Si549(main_dcxo_i2c) - - # for diagnostics and PLL initialization - self.submodules.frequency_counter = FrequencyCounter() - - ddmtd_counter = Signal(N) - self.sync.helper += ddmtd_counter.eq(ddmtd_counter + 1) - self.submodules.ddmtd_ref = DDMTD(ddmtd_counter, ddmtd_inputs.rec_clk) - self.submodules.ddmtd_main = DDMTD(ddmtd_counter, ddmtd_inputs.main_xo) - - collector_cd = ClockDomainsRenamer("collector") - filter_cd = ClockDomainsRenamer("filter") - self.submodules.collector = collector_cd(Collector(N)) - self.submodules.filter_helper = filter_cd( - thls.make(filters.helper, data_width=48)) - self.submodules.filter_main = filter_cd( - thls.make(filters.main, data_width=48)) - - self.comb += [ - self.collector.tag_ref.eq(self.ddmtd_ref.h_tag), - self.collector.ref_stb.eq(self.ddmtd_ref.h_tag_update), - self.collector.tag_main.eq(self.ddmtd_main.h_tag), - self.collector.main_stb.eq(self.ddmtd_main.h_tag_update) - ] - - collector_stb_ps = PulseSynchronizer("helper", "sys") - self.submodules += collector_stb_ps - self.sync.helper += collector_stb_ps.i.eq(self.collector.out_stb) - collector_stb_sys = Signal() - self.sync += collector_stb_sys.eq(collector_stb_ps.o) - - main_diff_tag_sys = Signal((N+2, True)) - helper_diff_tag_sys = Signal((N+2, True)) - ref_tag_sys = Signal(N) - main_tag_sys = Signal(N) - self.specials += MultiReg(self.collector.out_main, main_diff_tag_sys) - self.specials += MultiReg(self.collector.out_helper, helper_diff_tag_sys) - self.specials += MultiReg(self.collector.tag_ref, ref_tag_sys) - self.specials += MultiReg(self.collector.tag_main, main_tag_sys) - - self.sync += [ - If(self.tag_arm.re & self.tag_arm.r, self.tag_arm.w.eq(1)), - If(collector_stb_sys, - self.tag_arm.w.eq(0), - If(self.tag_arm.w, - main_diff_tag_32.eq(main_diff_tag_sys), - helper_diff_tag_32.eq(helper_diff_tag_sys), - self.ref_tag.status.eq(ref_tag_sys), - self.main_tag.status.eq(main_tag_sys) - ) - ) - ] - - self.comb += [ - self.filter_helper.input.eq(self.collector.out_helper << 22), - self.filter_helper.input_stb.eq(self.collector.out_stb), - self.filter_main.input.eq(self.collector.out_main), - self.filter_main.input_stb.eq(self.collector.out_stb) - ] - - self.sync.helper += [ - self.helper_dcxo.adpll_stb.eq(self.filter_helper.output_stb), - self.helper_dcxo.adpll.eq(self.filter_helper.output + self.adpll_offset_helper.storage), - self.main_dcxo.adpll_stb.eq(self.filter_main.output_stb), - self.main_dcxo.adpll.eq(self.filter_main.output + self.adpll_offset_main.storage) - ] diff --git a/artiq/gateware/drtio/wrpll/ddmtd.py b/artiq/gateware/drtio/wrpll/ddmtd.py deleted file mode 100644 index ddeeac54e..000000000 --- a/artiq/gateware/drtio/wrpll/ddmtd.py +++ /dev/null @@ -1,221 +0,0 @@ -from migen import * -from migen.genlib.cdc import PulseSynchronizer, MultiReg -from migen.genlib.fsm import FSM -from misoc.interconnect.csr import * - - -class DDMTDSamplerExtFF(Module): - def __init__(self, ddmtd_inputs): - self.rec_clk = Signal() - self.main_xo = Signal() - - # # # - - # TODO: s/h timing at FPGA pads - if hasattr(ddmtd_inputs, "rec_clk"): - rec_clk_1 = ddmtd_inputs.rec_clk - else: - rec_clk_1 = Signal() - self.specials += Instance("IBUFDS", - i_I=ddmtd_inputs.rec_clk_p, i_IB=ddmtd_inputs.rec_clk_n, - o_O=rec_clk_1) - if hasattr(ddmtd_inputs, "main_xo"): - main_xo_1 = ddmtd_inputs.main_xo - else: - main_xo_1 = Signal() - self.specials += Instance("IBUFDS", - i_I=ddmtd_inputs.main_xo_p, i_IB=ddmtd_inputs.main_xo_n, - o_O=main_xo_1) - self.specials += [ - Instance("FD", i_C=ClockSignal("helper"), - i_D=rec_clk_1, o_Q=self.rec_clk, - attr={("IOB", "TRUE")}), - Instance("FD", i_C=ClockSignal("helper"), - i_D=main_xo_1, o_Q=self.main_xo, - attr={("IOB", "TRUE")}), - ] - - -class DDMTDSamplerGTP(Module): - def __init__(self, gtp, main_xo_pads): - self.rec_clk = Signal() - self.main_xo = Signal() - - # # # - - # Getting the main XO signal from IBUFDS_GTE2 is problematic because - # the transceiver PLL craps out if an improper clock signal is applied, - # so we are disabling the buffer until the clock is stable. - main_xo_se = Signal() - rec_clk_1 = Signal() - main_xo_1 = Signal() - self.specials += [ - Instance("IBUFDS", - i_I=main_xo_pads.p, i_IB=main_xo_pads.n, - o_O=main_xo_se), - Instance("FD", i_C=ClockSignal("helper"), - i_D=gtp.cd_rtio_rx0.clk, o_Q=rec_clk_1, - attr={("DONT_TOUCH", "TRUE")}), - Instance("FD", i_C=ClockSignal("helper"), - i_D=rec_clk_1, o_Q=self.rec_clk, - attr={("DONT_TOUCH", "TRUE")}), - Instance("FD", i_C=ClockSignal("helper"), - i_D=main_xo_se, o_Q=main_xo_1, - attr={("IOB", "TRUE")}), - Instance("FD", i_C=ClockSignal("helper"), - i_D=main_xo_1, o_Q=self.main_xo, - attr={("DONT_TOUCH", "TRUE")}), - ] - - -class DDMTDDeglitcherFirstEdge(Module): - def __init__(self, input_signal, blind_period=128): - self.detect = Signal() - self.tag_correction = 0 - - rising = Signal() - input_signal_r = Signal() - self.sync.helper += [ - input_signal_r.eq(input_signal), - rising.eq(input_signal & ~input_signal_r) - ] - - blind_counter = Signal(max=blind_period) - self.sync.helper += [ - If(blind_counter != 0, blind_counter.eq(blind_counter - 1)), - If(input_signal_r, blind_counter.eq(blind_period - 1)), - self.detect.eq(rising & (blind_counter == 0)) - ] - - -class DDMTD(Module): - def __init__(self, counter, input_signal): - - # in helper clock domain - self.h_tag = Signal(len(counter)) - self.h_tag_update = Signal() - - # # # - - deglitcher = DDMTDDeglitcherFirstEdge(input_signal) - self.submodules += deglitcher - - self.sync.helper += [ - self.h_tag_update.eq(0), - If(deglitcher.detect, - self.h_tag_update.eq(1), - self.h_tag.eq(counter + deglitcher.tag_correction) - ) - ] - - -class Collector(Module): - """Generates loop filter inputs from DDMTD outputs. - - The input to the main DCXO lock loop filter is the difference between the - reference and main tags after unwrapping (see below). - - The input to the helper DCXO lock loop filter is the difference between the - current reference tag and the previous reference tag after unwrapping. - - When the WR PLL is locked, the following ideally (no noise/jitter) obtain: - - f_main = f_ref - - f_helper = f_ref * 2^N/(2^N+1) - - f_beat = f_ref - f_helper = f_ref / (2^N + 1) (cycle time is: dt=1/f_beat) - - the reference and main DCXO tags are equal to each other at every cycle - (the main DCXO lock drives this difference to 0) - - the reference and main DCXO tags both have the same value at each cycle - (the tag difference for each DDMTD is given by - f_helper*dt = f_helper/f_beat = 2^N, which causes the N-bit DDMTD counter - to wrap around and come back to its previous value) - - Note that we currently lock the frequency of the helper DCXO to the - reference clock, not it's phase. As a result, while the tag differences are - controlled, their absolute values are arbitrary. We could consider moving - the helper lock to a phase lock at some point in the future... - - Since the DDMTD counter is only N bits, it is possible for tag values to - wrap around. This will happen frequently if the locked tags happens to be - near the edges of the counter, so that jitter can easily cause a phase wrap. - But, it can also easily happen during lock acquisition or other transients. - To avoid glitches in the output, we unwrap the tag differences. Currently - we do this in hardware, but we should consider extending the processor to - allow us to do it inside the filters. Since the processor uses wider - signals, this would significantly extend the overall glitch-free - range of the PLL and may aid lock acquisition. - """ - def __init__(self, N): - self.ref_stb = Signal() - self.main_stb = Signal() - self.tag_ref = Signal(N) - self.tag_main = Signal(N) - - self.out_stb = Signal() - self.out_main = Signal((N+2, True)) - self.out_helper = Signal((N+2, True)) - self.out_tag_ref = Signal(N) - self.out_tag_main = Signal(N) - - tag_ref_r = Signal(N) - tag_main_r = Signal(N) - main_tag_diff = Signal((N+2, True)) - helper_tag_diff = Signal((N+2, True)) - - # # # - - fsm = FSM(reset_state="IDLE") - self.submodules += fsm - - fsm.act("IDLE", - NextValue(self.out_stb, 0), - If(self.ref_stb & self.main_stb, - NextValue(tag_ref_r, self.tag_ref), - NextValue(tag_main_r, self.tag_main), - NextState("DIFF") - ).Elif(self.ref_stb, - NextValue(tag_ref_r, self.tag_ref), - NextState("WAITMAIN") - ).Elif(self.main_stb, - NextValue(tag_main_r, self.tag_main), - NextState("WAITREF") - ) - ) - fsm.act("WAITREF", - If(self.ref_stb, - NextValue(tag_ref_r, self.tag_ref), - NextState("DIFF") - ) - ) - fsm.act("WAITMAIN", - If(self.main_stb, - NextValue(tag_main_r, self.tag_main), - NextState("DIFF") - ) - ) - fsm.act("DIFF", - NextValue(main_tag_diff, tag_main_r - tag_ref_r), - NextValue(helper_tag_diff, tag_ref_r - self.out_tag_ref), - NextState("UNWRAP") - ) - fsm.act("UNWRAP", - If(main_tag_diff - self.out_main > 2**(N-1), - NextValue(main_tag_diff, main_tag_diff - 2**N) - ).Elif(self.out_main - main_tag_diff > 2**(N-1), - NextValue(main_tag_diff, main_tag_diff + 2**N) - ), - - If(helper_tag_diff - self.out_helper > 2**(N-1), - NextValue(helper_tag_diff, helper_tag_diff - 2**N) - ).Elif(self.out_helper - helper_tag_diff > 2**(N-1), - NextValue(helper_tag_diff, helper_tag_diff + 2**N) - ), - NextState("OUTPUT") - ) - fsm.act("OUTPUT", - NextValue(self.out_tag_ref, tag_ref_r), - NextValue(self.out_tag_main, tag_main_r), - NextValue(self.out_main, main_tag_diff), - NextValue(self.out_helper, helper_tag_diff), - NextValue(self.out_stb, 1), - NextState("IDLE") - ) diff --git a/artiq/gateware/drtio/wrpll/filters.py b/artiq/gateware/drtio/wrpll/filters.py deleted file mode 100644 index 470f17bf3..000000000 --- a/artiq/gateware/drtio/wrpll/filters.py +++ /dev/null @@ -1,61 +0,0 @@ -helper_xn1 = 0 -helper_xn2 = 0 -helper_yn0 = 0 -helper_yn1 = 0 -helper_yn2 = 0 -helper_out = 0 - -main_xn1 = 0 -main_xn2 = 0 -main_yn0 = 0 -main_yn1 = 0 -main_yn2 = 0 - - -def helper(tag_diff): - global helper_xn1, helper_xn2, helper_yn0, \ - helper_yn1, helper_yn2, helper_out - - helper_xn0 = 0 - tag_diff # *(2**22) - - helper_yr = 4294967296 - - helper_yn2 = helper_yn1 - helper_yn1 = helper_yn0 - - helper_yn0 = (284885690 * (helper_xn0 - + (217319150 * helper_xn1 >> 44) - - (17591968725108 * helper_xn2 >> 44) - ) >> 44 - ) + (35184372088832*helper_yn1 >> 44) - helper_yn2 - - helper_xn2 = helper_xn1 - helper_xn1 = helper_xn0 - - helper_out = 268435456*helper_yn0 >> 44 - helper_out = min(helper_out, helper_yr) - helper_out = max(helper_out, 0 - helper_yr) - - return helper_out - - -def main(main_xn0): - global main_xn1, main_xn2, main_yn0, main_yn1, main_yn2 - - main_yr = 4294967296 - - main_yn2 = main_yn1 - main_yn1 = main_yn0 - main_yn0 = ( - ((133450380908*(((35184372088832*main_xn0) >> 44) + - ((17592186044417*main_xn1) >> 44))) >> 44) + - ((29455872930889*main_yn1) >> 44) - - ((12673794781453*main_yn2) >> 44)) - - main_xn2 = main_xn1 - main_xn1 = main_xn0 - - main_yn0 = min(main_yn0, main_yr) - main_yn0 = max(main_yn0, 0 - main_yr) - - return main_yn0 diff --git a/artiq/gateware/drtio/wrpll/si549.py b/artiq/gateware/drtio/wrpll/si549.py deleted file mode 100644 index 46ce0c138..000000000 --- a/artiq/gateware/drtio/wrpll/si549.py +++ /dev/null @@ -1,340 +0,0 @@ -from migen import * -from migen.genlib.fsm import * -from migen.genlib.cdc import MultiReg, PulseSynchronizer, BlindTransfer - -from misoc.interconnect.csr import * - - -class I2CClockGen(Module): - def __init__(self, width): - self.load = Signal(width) - self.clk2x = Signal() - - cnt = Signal.like(self.load) - self.comb += [ - self.clk2x.eq(cnt == 0), - ] - self.sync += [ - If(self.clk2x, - cnt.eq(self.load), - ).Else( - cnt.eq(cnt - 1), - ) - ] - - -class I2CMasterMachine(Module): - def __init__(self, clock_width): - self.scl = Signal(reset=1) - self.sda_o = Signal(reset=1) - self.sda_i = Signal() - - self.submodules.cg = CEInserter()(I2CClockGen(clock_width)) - self.start = Signal() - self.stop = Signal() - self.write = Signal() - self.ack = Signal() - self.data = Signal(8) - self.ready = Signal() - - ### - - bits = Signal(4) - data = Signal(8) - - fsm = CEInserter()(FSM("IDLE")) - self.submodules += fsm - - fsm.act("IDLE", - self.ready.eq(1), - If(self.start, - NextState("START0"), - ).Elif(self.stop, - NextState("STOP0"), - ).Elif(self.write, - NextValue(bits, 8), - NextValue(data, self.data), - NextState("WRITE0") - ) - ) - - fsm.act("START0", - NextValue(self.scl, 1), - NextState("START1") - ) - fsm.act("START1", - NextValue(self.sda_o, 0), - NextState("IDLE") - ) - - fsm.act("STOP0", - NextValue(self.scl, 0), - NextState("STOP1") - ) - fsm.act("STOP1", - NextValue(self.sda_o, 0), - NextState("STOP2") - ) - fsm.act("STOP2", - NextValue(self.scl, 1), - NextState("STOP3") - ) - fsm.act("STOP3", - NextValue(self.sda_o, 1), - NextState("IDLE") - ) - - fsm.act("WRITE0", - NextValue(self.scl, 0), - NextState("WRITE1") - ) - fsm.act("WRITE1", - If(bits == 0, - NextValue(self.sda_o, 1), - NextState("READACK0"), - ).Else( - NextValue(self.sda_o, data[7]), - NextState("WRITE2"), - ) - ) - fsm.act("WRITE2", - NextValue(self.scl, 1), - NextValue(data[1:], data[:-1]), - NextValue(bits, bits - 1), - NextState("WRITE0"), - ) - fsm.act("READACK0", - NextValue(self.scl, 1), - NextState("READACK1"), - ) - fsm.act("READACK1", - NextValue(self.ack, ~self.sda_i), - NextState("IDLE") - ) - - run = Signal() - idle = Signal() - self.comb += [ - run.eq((self.start | self.stop | self.write) & self.ready), - idle.eq(~run & fsm.ongoing("IDLE")), - self.cg.ce.eq(~idle), - fsm.ce.eq(run | self.cg.clk2x), - ] - - -class ADPLLProgrammer(Module): - def __init__(self): - self.i2c_divider = Signal(16) - self.i2c_address = Signal(7) - - self.adpll = Signal(24) - self.stb = Signal() - self.busy = Signal() - self.nack = Signal() - - self.scl = Signal() - self.sda_i = Signal() - self.sda_o = Signal() - - self.scl.attr.add("no_retiming") - self.sda_o.attr.add("no_retiming") - - # # # - - master = I2CMasterMachine(16) - self.submodules += master - - self.comb += [ - master.cg.load.eq(self.i2c_divider), - self.scl.eq(master.scl), - master.sda_i.eq(self.sda_i), - self.sda_o.eq(master.sda_o) - ] - - fsm = FSM() - self.submodules += fsm - - adpll = Signal.like(self.adpll) - - fsm.act("IDLE", - If(self.stb, - NextValue(adpll, self.adpll), - NextState("START") - ) - ) - fsm.act("START", - master.start.eq(1), - If(master.ready, NextState("DEVADDRESS")) - ) - fsm.act("DEVADDRESS", - master.data.eq(self.i2c_address << 1), - master.write.eq(1), - If(master.ready, NextState("REGADRESS")) - ) - fsm.act("REGADRESS", - master.data.eq(231), - master.write.eq(1), - If(master.ready, - If(master.ack, - NextState("DATA0") - ).Else( - self.nack.eq(1), - NextState("STOP") - ) - ) - ) - fsm.act("DATA0", - master.data.eq(adpll[0:8]), - master.write.eq(1), - If(master.ready, - If(master.ack, - NextState("DATA1") - ).Else( - self.nack.eq(1), - NextState("STOP") - ) - ) - ) - fsm.act("DATA1", - master.data.eq(adpll[8:16]), - master.write.eq(1), - If(master.ready, - If(master.ack, - NextState("DATA2") - ).Else( - self.nack.eq(1), - NextState("STOP") - ) - ) - ) - fsm.act("DATA2", - master.data.eq(adpll[16:24]), - master.write.eq(1), - If(master.ready, - If(~master.ack, self.nack.eq(1)), - NextState("STOP") - ) - ) - fsm.act("STOP", - master.stop.eq(1), - If(master.ready, - If(~master.ack, self.nack.eq(1)), - NextState("IDLE") - ) - ) - - self.comb += self.busy.eq(~fsm.ongoing("IDLE")) - - -def simulate_programmer(): - from migen.sim.core import run_simulation - - dut = ADPLLProgrammer() - - def generator(): - yield dut.i2c_divider.eq(4) - yield dut.i2c_address.eq(0x55) - yield - yield dut.adpll.eq(0x123456) - yield dut.stb.eq(1) - yield - yield dut.stb.eq(0) - yield - while (yield dut.busy): - yield - for _ in range(20): - yield - - run_simulation(dut, generator(), vcd_name="tb.vcd") - - -class Si549(Module, AutoCSR): - def __init__(self, pads): - self.gpio_enable = CSRStorage(reset=1) - self.gpio_in = CSRStatus(2) - self.gpio_out = CSRStorage(2) - self.gpio_oe = CSRStorage(2) - - self.i2c_divider = CSRStorage(16, reset=75) - self.i2c_address = CSRStorage(7) - self.errors = CSR(2) - - # in helper clock domain - self.adpll = Signal(24) - self.adpll_stb = Signal() - - # # # - - programmer = ClockDomainsRenamer("helper")(ADPLLProgrammer()) - self.submodules += programmer - - self.i2c_divider.storage.attr.add("no_retiming") - self.i2c_address.storage.attr.add("no_retiming") - self.specials += [ - MultiReg(self.i2c_divider.storage, programmer.i2c_divider, "helper"), - MultiReg(self.i2c_address.storage, programmer.i2c_address, "helper") - ] - self.comb += [ - programmer.adpll.eq(self.adpll), - programmer.stb.eq(self.adpll_stb) - ] - - self.gpio_enable.storage.attr.add("no_retiming") - self.gpio_out.storage.attr.add("no_retiming") - self.gpio_oe.storage.attr.add("no_retiming") - - # SCL GPIO and mux - ts_scl = TSTriple(1) - self.specials += ts_scl.get_tristate(pads.scl) - - status = Signal() - self.comb += self.gpio_in.status[0].eq(status) - - self.specials += MultiReg(ts_scl.i, status) - self.comb += [ - If(self.gpio_enable.storage, - ts_scl.o.eq(self.gpio_out.storage[0]), - ts_scl.oe.eq(self.gpio_oe.storage[0]) - ).Else( - ts_scl.o.eq(0), - ts_scl.oe.eq(~programmer.scl) - ) - ] - - # SDA GPIO and mux - ts_sda = TSTriple(1) - self.specials += ts_sda.get_tristate(pads.sda) - - status = Signal() - self.comb += self.gpio_in.status[1].eq(status) - - self.specials += MultiReg(ts_sda.i, status) - self.comb += [ - If(self.gpio_enable.storage, - ts_sda.o.eq(self.gpio_out.storage[1]), - ts_sda.oe.eq(self.gpio_oe.storage[1]) - ).Else( - ts_sda.o.eq(0), - ts_sda.oe.eq(~programmer.sda_o) - ) - ] - self.specials += MultiReg(ts_sda.i, programmer.sda_i, "helper") - - # Error reporting - collision_cdc = BlindTransfer("helper", "sys") - self.submodules += collision_cdc - self.comb += collision_cdc.i.eq(programmer.stb & programmer.busy) - - nack_cdc = PulseSynchronizer("helper", "sys") - self.submodules += nack_cdc - self.comb += nack_cdc.i.eq(programmer.nack) - - for n, trig in enumerate([collision_cdc.o, nack_cdc.o]): - self.sync += [ - If(self.errors.re & self.errors.r[n], self.errors.w[n].eq(0)), - If(trig, self.errors.w[n].eq(1)) - ] - - -if __name__ == "__main__": - simulate_programmer() diff --git a/artiq/gateware/drtio/wrpll/thls.py b/artiq/gateware/drtio/wrpll/thls.py deleted file mode 100644 index b11459692..000000000 --- a/artiq/gateware/drtio/wrpll/thls.py +++ /dev/null @@ -1,618 +0,0 @@ -import inspect -import ast -from copy import copy -import operator -from functools import reduce -from collections import OrderedDict - -from migen import * -from migen.genlib.fsm import * - - -class Isn: - def __init__(self, immediate=None, inputs=None, outputs=None): - if inputs is None: - inputs = [] - if outputs is None: - outputs = [] - self.immediate = immediate - self.inputs = inputs - self.outputs = outputs - - def __repr__(self): - r = "<" - r += self.__class__.__name__ - if self.immediate is not None: - r += " (" + str(self.immediate) + ")" - for inp in self.inputs: - r += " r" + str(inp) - if self.outputs: - r += " ->" - for outp in self.outputs: - r += " r" + str(outp) - r += ">" - return r - - -class NopIsn(Isn): - opcode = 0 - -class AddIsn(Isn): - opcode = 1 - -class SubIsn(Isn): - opcode = 2 - -class MulShiftIsn(Isn): - opcode = 3 - -# opcode = 4: MulShift with alternate shift - -class MinIsn(Isn): - opcode = 5 - -class MaxIsn(Isn): - opcode = 6 - -class CopyIsn(Isn): - opcode = 7 - -class InputIsn(Isn): - opcode = 8 - -class OutputIsn(Isn): - opcode = 9 - -class EndIsn(Isn): - opcode = 10 - - -class ASTCompiler: - def __init__(self): - self.program = [] - self.data = [] - self.next_ssa_reg = -1 - self.constants = dict() - self.names = dict() - self.globals = OrderedDict() - - def get_ssa_reg(self): - r = self.next_ssa_reg - self.next_ssa_reg -= 1 - return r - - def add_global(self, name): - if name not in self.globals: - r = len(self.data) - self.data.append(0) - self.names[name] = r - self.globals[name] = r - - def input(self, name): - target = self.get_ssa_reg() - self.program.append(InputIsn(outputs=[target])) - self.names[name] = target - - def emit(self, node): - if isinstance(node, ast.BinOp): - if isinstance(node.op, ast.RShift): - if not isinstance(node.left, ast.BinOp) or not isinstance(node.left.op, ast.Mult): - raise NotImplementedError - if not isinstance(node.right, ast.Num): - raise NotImplementedError - left = self.emit(node.left.left) - right = self.emit(node.left.right) - cons = lambda **kwargs: MulShiftIsn(immediate=node.right.n, **kwargs) - else: - left = self.emit(node.left) - right = self.emit(node.right) - if isinstance(node.op, ast.Add): - cons = AddIsn - elif isinstance(node.op, ast.Sub): - cons = SubIsn - elif isinstance(node.op, ast.Mult): - cons = lambda **kwargs: MulShiftIsn(immediate=0, **kwargs) - else: - raise NotImplementedError - output = self.get_ssa_reg() - self.program.append(cons(inputs=[left, right], outputs=[output])) - return output - elif isinstance(node, ast.Call): - if not isinstance(node.func, ast.Name): - raise NotImplementedError - funcname = node.func.id - if node.keywords: - raise NotImplementedError - inputs = [self.emit(x) for x in node.args] - if funcname == "min": - cons = MinIsn - elif funcname == "max": - cons = MaxIsn - else: - raise NotImplementedError - output = self.get_ssa_reg() - self.program.append(cons(inputs=inputs, outputs=[output])) - return output - elif isinstance(node, (ast.Num, ast.UnaryOp)): - if isinstance(node, ast.UnaryOp): - if not isinstance(node.operand, ast.Num): - raise NotImplementedError - if isinstance(node.op, ast.UAdd): - transform = lambda x: x - elif isinstance(node.op, ast.USub): - transform = operator.neg - elif isinstance(node.op, ast.Invert): - transform = operator.invert - else: - raise NotImplementedError - node = node.operand - else: - transform = lambda x: x - n = transform(node.n) - if n in self.constants: - return self.constants[n] - else: - r = len(self.data) - self.data.append(n) - self.constants[n] = r - return r - elif isinstance(node, ast.Name): - return self.names[node.id] - elif isinstance(node, ast.Assign): - output = self.emit(node.value) - for target in node.targets: - assert isinstance(target, ast.Name) - self.names[target.id] = output - elif isinstance(node, ast.Return): - value = self.emit(node.value) - self.program.append(OutputIsn(inputs=[value])) - elif isinstance(node, ast.Global): - pass - else: - raise NotImplementedError - - -class Processor: - def __init__(self, data_width=32, multiplier_stages=2): - self.data_width = data_width - self.multiplier_stages = multiplier_stages - self.multiplier_shifts = [] - self.program_rom_size = None - self.data_ram_size = None - self.opcode_bits = 4 - self.reg_bits = None - - def get_instruction_latency(self, isn): - return { - AddIsn: 2, - SubIsn: 2, - MulShiftIsn: 1 + self.multiplier_stages, - MinIsn: 2, - MaxIsn: 2, - CopyIsn: 1, - InputIsn: 1 - }[isn.__class__] - - def encode_instruction(self, isn, exit): - opcode = isn.opcode - if isn.immediate is not None and not isinstance(isn, MulShiftIsn): - r0 = isn.immediate - if len(isn.inputs) >= 1: - r1 = isn.inputs[0] - else: - r1 = 0 - else: - if len(isn.inputs) >= 1: - r0 = isn.inputs[0] - else: - r0 = 0 - if len(isn.inputs) >= 2: - r1 = isn.inputs[1] - else: - r1 = 0 - r = 0 - for value, bits in ((exit, self.reg_bits), (r1, self.reg_bits), (r0, self.reg_bits), (opcode, self.opcode_bits)): - r <<= bits - r |= value - return r - - def instruction_bits(self): - return 3*self.reg_bits + self.opcode_bits - - def implement(self, program, data): - return ProcessorImpl(self, program, data) - - -class Scheduler: - def __init__(self, processor, reserved_data, program): - self.processor = processor - self.reserved_data = reserved_data - self.used_registers = set(range(self.reserved_data)) - self.exits = dict() - self.program = program - self.remaining = copy(program) - self.output = [] - - def allocate_register(self): - r = min(set(range(max(self.used_registers) + 2)) - self.used_registers) - self.used_registers.add(r) - return r - - def free_register(self, r): - assert r >= self.reserved_data - self.used_registers.discard(r) - - def find_inputs(self, cycle, isn): - mapped_inputs = [] - for inp in isn.inputs: - if inp >= 0: - mapped_inputs.append(inp) - else: - found = False - for i in range(cycle): - if i in self.exits: - r, rm = self.exits[i] - if r == inp: - mapped_inputs.append(rm) - found = True - break - if not found: - return None - return mapped_inputs - - def schedule_one(self, isn): - cycle = len(self.output) - mapped_inputs = self.find_inputs(cycle, isn) - if mapped_inputs is None: - return False - - if isn.outputs: - # check that exit slot is free - latency = self.processor.get_instruction_latency(isn) - exit = cycle + latency - if exit in self.exits: - return False - - # avoid RAW hazard with global writeback - for output in isn.outputs: - if output >= 0: - for risn in self.remaining: - for inp in risn.inputs: - if inp == output: - return False - - # Instruction can be scheduled - - self.remaining.remove(isn) - - for inp, minp in zip(isn.inputs, mapped_inputs): - can_free = inp < 0 and all(inp != rinp for risn in self.remaining for rinp in risn.inputs) - if can_free: - self.free_register(minp) - - if isn.outputs: - assert len(isn.outputs) == 1 - if isn.outputs[0] < 0: - output = self.allocate_register() - else: - output = isn.outputs[0] - self.exits[exit] = (isn.outputs[0], output) - self.output.append(isn.__class__(immediate=isn.immediate, inputs=mapped_inputs)) - - return True - - def schedule(self): - while self.remaining: - success = False - for isn in self.remaining: - if self.schedule_one(isn): - success = True - break - if not success: - self.output.append(NopIsn()) - self.output += [NopIsn()]*(max(self.exits.keys()) - len(self.output) + 1) - return self.output - - -class CompiledProgram: - def __init__(self, processor, program, exits, data, glbs): - self.processor = processor - self.program = program - self.exits = exits - self.data = data - self.globals = glbs - - def pretty_print(self): - for cycle, isn in enumerate(self.program): - l = "{:4d} {:15}".format(cycle, str(isn)) - if cycle in self.exits: - l += " -> r{}".format(self.exits[cycle]) - print(l) - - def dimension_processor(self): - self.processor.program_rom_size = len(self.program) - self.processor.data_ram_size = len(self.data) - self.processor.reg_bits = (self.processor.data_ram_size - 1).bit_length() - for isn in self.program: - if isinstance(isn, MulShiftIsn) and isn.immediate not in self.processor.multiplier_shifts: - self.processor.multiplier_shifts.append(isn.immediate) - - def encode(self): - r = [] - for i, isn in enumerate(self.program): - exit = self.exits.get(i, 0) - r.append(self.processor.encode_instruction(isn, exit)) - return r - - -def compile(processor, function): - node = ast.parse(inspect.getsource(function)) - assert isinstance(node, ast.Module) - assert len(node.body) == 1 - node = node.body[0] - assert isinstance(node, ast.FunctionDef) - assert len(node.args.args) == 1 - arg = node.args.args[0].arg - body = node.body - - astcompiler = ASTCompiler() - for node in body: - if isinstance(node, ast.Global): - for name in node.names: - astcompiler.add_global(name) - arg_r = astcompiler.input(arg) - for node in body: - astcompiler.emit(node) - if isinstance(node, ast.Return): - break - for glbl, location in astcompiler.globals.items(): - new_location = astcompiler.names[glbl] - if new_location != location: - astcompiler.program.append(CopyIsn(inputs=[new_location], outputs=[location])) - - scheduler = Scheduler(processor, len(astcompiler.data), astcompiler.program) - scheduler.schedule() - - program = copy(scheduler.output) - program.append(EndIsn()) - - max_reg = max(max(max(isn.inputs + [0]) for isn in program), max(v[1] for k, v in scheduler.exits.items())) - - return CompiledProgram( - processor=processor, - program=program, - exits={k: v[1] for k, v in scheduler.exits.items()}, - data=astcompiler.data + [0]*(max_reg - len(astcompiler.data) + 1), - glbs=astcompiler.globals) - - -class BaseUnit(Module): - def __init__(self, data_width): - self.stb_i = Signal() - self.i0 = Signal((data_width, True)) - self.i1 = Signal((data_width, True)) - self.stb_o = Signal() - self.o = Signal((data_width, True)) - - -class NopUnit(BaseUnit): - pass - - -class OpUnit(BaseUnit): - def __init__(self, op, data_width, stages, op_data_width=None): - BaseUnit.__init__(self, data_width) - # work around Migen's mishandling of Verilog's cretinous operator width rules - if op_data_width is None: - op_data_width = data_width - - if stages > 1: - # Vivado backward retiming for DSP does not work correctly if DSP inputs - # are not registered. - i0 = Signal.like(self.i0) - i1 = Signal.like(self.i1) - stb_i = Signal() - self.sync += [ - i0.eq(self.i0), - i1.eq(self.i1), - stb_i.eq(self.stb_i) - ] - output_stages = stages - 1 - else: - i0, i1, stb_i = self.i0, self.i1, self.stb_i - output_stages = stages - - o = Signal((op_data_width, True)) - self.comb += o.eq(op(i0, i1)) - stb_o = stb_i - for i in range(output_stages): - n_o = Signal((data_width, True)) - if stages > 1: - n_o.attr.add(("retiming_backward", 1)) - n_stb_o = Signal() - self.sync += [ - n_o.eq(o), - n_stb_o.eq(stb_o) - ] - o = n_o - stb_o = n_stb_o - self.comb += [ - self.o.eq(o), - self.stb_o.eq(stb_o) - ] - - -class SelectUnit(BaseUnit): - def __init__(self, op, data_width): - BaseUnit.__init__(self, data_width) - - self.sync += [ - self.stb_o.eq(self.stb_i), - If(op(self.i0, self.i1), - self.o.eq(self.i0) - ).Else( - self.o.eq(self.i1) - ) - ] - - -class CopyUnit(BaseUnit): - def __init__(self, data_width): - BaseUnit.__init__(self, data_width) - - self.comb += [ - self.stb_o.eq(self.stb_i), - self.o.eq(self.i0) - ] - - -class InputUnit(BaseUnit): - def __init__(self, data_width, input_stb, input): - BaseUnit.__init__(self, data_width) - self.buffer = Signal(data_width) - - self.comb += [ - self.stb_o.eq(self.stb_i), - self.o.eq(self.buffer) - ] - - -class OutputUnit(BaseUnit): - def __init__(self, data_width, output_stb, output): - BaseUnit.__init__(self, data_width) - - self.sync += [ - output_stb.eq(self.stb_i), - output.eq(self.i0) - ] - - -class ProcessorImpl(Module): - def __init__(self, pd, program, data): - self.input_stb = Signal() - self.input = Signal((pd.data_width, True)) - - self.output_stb = Signal() - self.output = Signal((pd.data_width, True)) - - self.busy = Signal() - - # # # - - program_mem = Memory(pd.instruction_bits(), pd.program_rom_size, init=program) - data_mem0 = Memory(pd.data_width, pd.data_ram_size, init=data) - data_mem1 = Memory(pd.data_width, pd.data_ram_size, init=data) - self.specials += program_mem, data_mem0, data_mem1 - - pc = Signal(pd.instruction_bits()) - pc_next = Signal.like(pc) - pc_en = Signal() - self.sync += pc.eq(pc_next) - self.comb += [ - If(pc_en, - pc_next.eq(pc + 1) - ).Else( - pc_next.eq(0) - ) - ] - program_mem_port = program_mem.get_port() - self.specials += program_mem_port - self.comb += program_mem_port.adr.eq(pc_next) - - s = 0 - opcode = Signal(pd.opcode_bits) - self.comb += opcode.eq(program_mem_port.dat_r[s:s+pd.opcode_bits]) - s += pd.opcode_bits - r0 = Signal(pd.reg_bits) - self.comb += r0.eq(program_mem_port.dat_r[s:s+pd.reg_bits]) - s += pd.reg_bits - r1 = Signal(pd.reg_bits) - self.comb += r1.eq(program_mem_port.dat_r[s:s+pd.reg_bits]) - s += pd.reg_bits - exit = Signal(pd.reg_bits) - self.comb += exit.eq(program_mem_port.dat_r[s:s+pd.reg_bits]) - - data_read_port0 = data_mem0.get_port() - data_read_port1 = data_mem1.get_port() - self.specials += data_read_port0, data_read_port1 - self.comb += [ - data_read_port0.adr.eq(r0), - data_read_port1.adr.eq(r1) - ] - - data_write_port = data_mem0.get_port(write_capable=True) - data_write_port_dup = data_mem1.get_port(write_capable=True) - self.specials += data_write_port, data_write_port_dup - self.comb += [ - data_write_port_dup.we.eq(data_write_port.we), - data_write_port_dup.adr.eq(data_write_port.adr), - data_write_port_dup.dat_w.eq(data_write_port.dat_w), - data_write_port.adr.eq(exit) - ] - - nop = NopUnit(pd.data_width) - adder = OpUnit(operator.add, pd.data_width, 1) - subtractor = OpUnit(operator.sub, pd.data_width, 1) - if pd.multiplier_shifts: - if len(pd.multiplier_shifts) != 1: - raise NotImplementedError - multiplier = OpUnit(lambda a, b: a * b >> pd.multiplier_shifts[0], - pd.data_width, pd.multiplier_stages, op_data_width=2*pd.data_width) - else: - multiplier = NopUnit(pd.data_width) - minu = SelectUnit(operator.lt, pd.data_width) - maxu = SelectUnit(operator.gt, pd.data_width) - copier = CopyUnit(pd.data_width) - inu = InputUnit(pd.data_width, self.input_stb, self.input) - outu = OutputUnit(pd.data_width, self.output_stb, self.output) - units = [nop, adder, subtractor, multiplier, minu, maxu, copier, inu, outu] - self.submodules += units - - for unit in units: - self.sync += unit.stb_i.eq(0) - self.comb += [ - unit.i0.eq(data_read_port0.dat_r), - unit.i1.eq(data_read_port1.dat_r), - If(unit.stb_o, - data_write_port.we.eq(1), - data_write_port.dat_w.eq(unit.o) - ) - ] - - decode_table = [ - (NopIsn.opcode, nop), - (AddIsn.opcode, adder), - (SubIsn.opcode, subtractor), - (MulShiftIsn.opcode, multiplier), - (MulShiftIsn.opcode + 1, multiplier), - (MinIsn.opcode, minu), - (MaxIsn.opcode, maxu), - (CopyIsn.opcode, copier), - (InputIsn.opcode, inu), - (OutputIsn.opcode, outu) - ] - for allocated_opcode, unit in decode_table: - self.sync += If(pc_en & (opcode == allocated_opcode), unit.stb_i.eq(1)) - - fsm = FSM() - self.submodules += fsm - fsm.act("IDLE", - pc_en.eq(0), - NextValue(inu.buffer, self.input), - If(self.input_stb, NextState("PROCESSING")) - ) - fsm.act("PROCESSING", - self.busy.eq(1), - pc_en.eq(1), - If(opcode == EndIsn.opcode, - pc_en.eq(0), - NextState("IDLE") - ) - ) - - -def make(function, **kwargs): - proc = Processor(**kwargs) - cp = compile(proc, function) - cp.dimension_processor() - return proc.implement(cp.encode(), cp.data) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 9adfac0a2..448faefd7 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -21,7 +21,6 @@ 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.siphaser import SiPhaser7Series -from artiq.gateware.drtio.wrpll import WRPLL, DDMTDSamplerGTP from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer from artiq.gateware.drtio import * from artiq.build_soc import * @@ -410,7 +409,7 @@ class SatelliteBase(BaseSoC): } mem_map.update(BaseSoC.mem_map) - def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, with_wrpll=False, gateware_identifier_str=None, hw_rev="v2.0", **kwargs): + def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, gateware_identifier_str=None, hw_rev="v2.0", **kwargs): if hw_rev in ("v1.0", "v1.1"): cpu_bus_width = 32 else: @@ -533,35 +532,18 @@ class SatelliteBase(BaseSoC): rtio_clk_period = 1e9/rtio_clk_freq self.config["RTIO_FREQUENCY"] = str(rtio_clk_freq/1e6) - if with_wrpll: - self.submodules.wrpll_sampler = DDMTDSamplerGTP( - self.drtio_transceiver, - platform.request("cdr_clk_clean_fabric")) - helper_clk_pads = platform.request("ddmtd_helper_clk") - self.submodules.wrpll = WRPLL( - helper_clk_pads=helper_clk_pads, - main_dcxo_i2c=platform.request("ddmtd_main_dcxo_i2c"), - helper_dxco_i2c=platform.request("ddmtd_helper_dcxo_i2c"), - ddmtd_inputs=self.wrpll_sampler) - self.csr_devices.append("wrpll") - # note: do not use self.wrpll.cd_helper.clk; otherwise, vivado craps out with: - # critical warning: create_clock attempting to set clock on an unknown port/pin - # command: "create_clock -period 7.920000 -waveform {0.000000 3.960000} -name - # helper_clk [get_xlnx_outside_genome_inst_pin 20 0] - platform.add_period_constraint(helper_clk_pads.p, rtio_clk_period*0.99) - platform.add_false_path_constraints(self.crg.cd_sys.clk, helper_clk_pads.p) - else: - self.submodules.siphaser = SiPhaser7Series( - si5324_clkin=platform.request("cdr_clk") if platform.hw_rev == "v2.0" - else platform.request("si5324_clkin"), - rx_synchronizer=self.rx_synchronizer, - ref_clk=self.crg.clk125_div2, ref_div2=True, - rtio_clk_freq=rtio_clk_freq) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output) - self.csr_devices.append("siphaser") - self.config["HAS_SI5324"] = None - self.config["SI5324_SOFT_RESET"] = None + + self.submodules.siphaser = SiPhaser7Series( + si5324_clkin=platform.request("cdr_clk") if platform.hw_rev == "v2.0" + else platform.request("si5324_clkin"), + rx_synchronizer=self.rx_synchronizer, + ref_clk=self.crg.clk125_div2, ref_div2=True, + rtio_clk_freq=rtio_clk_freq) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output) + self.csr_devices.append("siphaser") + self.config["HAS_SI5324"] = None + self.config["SI5324_SOFT_RESET"] = None gtp = self.drtio_transceiver.gtps[0] txout_buf = Signal() @@ -573,9 +555,6 @@ class SatelliteBase(BaseSoC): platform.add_false_path_constraints( self.crg.cd_sys.clk, gtp.txoutclk, gtp.rxoutclk) - if with_wrpll: - platform.add_false_path_constraints( - helper_clk_pads.p, gtp.rxoutclk) for gtp in self.drtio_transceiver.gtps[1:]: platform.add_period_constraint(gtp.rxoutclk, rtio_clk_period) platform.add_false_path_constraints( @@ -652,7 +631,6 @@ def main(): parser.add_argument("-V", "--variant", default="tester", help="variant: {} (default: %(default)s)".format( "/".join(sorted(VARIANTS.keys())))) - parser.add_argument("--with-wrpll", default=False, action="store_true") parser.add_argument("--tester-dds", default=None, help="Tester variant DDS type: ad9910/ad9912 " "(default: ad9910)") @@ -661,8 +639,6 @@ def main(): args = parser.parse_args() argdict = dict() - if args.with_wrpll: - argdict["with_wrpll"] = True argdict["gateware_identifier_str"] = args.gateware_identifier_str argdict["dds"] = args.tester_dds diff --git a/artiq/gateware/test/wrpll/__init__.py b/artiq/gateware/test/wrpll/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/artiq/gateware/test/wrpll/test_dsp.py b/artiq/gateware/test/wrpll/test_dsp.py deleted file mode 100644 index 033e69853..000000000 --- a/artiq/gateware/test/wrpll/test_dsp.py +++ /dev/null @@ -1,158 +0,0 @@ -import unittest - -import numpy as np - -from migen import * - -from artiq.gateware.drtio.wrpll.ddmtd import Collector -from artiq.gateware.drtio.wrpll import thls, filters - - -class HelperChainTB(Module): - def __init__(self, N): - self.tag_ref = Signal(N) - self.input_stb = Signal() - self.adpll = Signal((24, True)) - self.out_stb = Signal() - - ### - - self.submodules.collector = Collector(N) - self.submodules.loop_filter = thls.make(filters.helper, data_width=48) - - self.comb += [ - self.collector.tag_ref.eq(self.tag_ref), - self.collector.ref_stb.eq(self.input_stb), - self.collector.main_stb.eq(self.input_stb), - self.loop_filter.input.eq(self.collector.out_helper << 22), - self.loop_filter.input_stb.eq(self.collector.out_stb), - self.adpll.eq(self.loop_filter.output), - self.out_stb.eq(self.loop_filter.output_stb), - ] - - -class TestDSP(unittest.TestCase): - def test_main_collector(self): - N = 2 - collector = Collector(N=N) - # check collector phase unwrapping - tags = [(0, 0, 0), - (0, 1, 1), - (2, 1, -1), - (3, 1, -2), - (0, 1, -3), - (1, 1, -4), - (2, 1, -5), - (3, 1, -6), - (3, 3, -4), - (0, 0, -4), - (0, 1, -3), - (0, 2, -2), - (0, 3, -1), - (0, 0, 0)] - for i in range(10): - tags.append((i % (2**N), (i+1) % (2**N), 1)) - - def generator(): - for tag_ref, tag_main, out in tags: - yield collector.tag_ref.eq(tag_ref) - yield collector.tag_main.eq(tag_main) - yield collector.main_stb.eq(1) - yield collector.ref_stb.eq(1) - - yield - - yield collector.main_stb.eq(0) - yield collector.ref_stb.eq(0) - - while not (yield collector.out_stb): - yield - - out_main = yield collector.out_main - self.assertEqual(out_main, out) - - run_simulation(collector, generator()) - - def test_helper_collector(self): - N = 3 - collector = Collector(N=N) - # check collector phase unwrapping - tags = [((2**N - 1 - tag) % (2**N), -1) for tag in range(20)] - tags += [((tags[-1][0] + 1 + tag) % (2**N), 1) for tag in range(20)] - tags += [((tags[-1][0] - 2 - 2*tag) % (2**N), -2) for tag in range(20)] - - def generator(): - for tag_ref, out in tags: - yield collector.tag_ref.eq(tag_ref) - yield collector.main_stb.eq(1) - yield collector.ref_stb.eq(1) - - yield - - yield collector.main_stb.eq(0) - yield collector.ref_stb.eq(0) - - while not (yield collector.out_stb): - yield - - out_helper = yield collector.out_helper - self.assertEqual(out_helper, out) - - run_simulation(collector, generator()) - - # test helper collector + filter against output from MATLAB model - def test_helper_chain(self): - pll = HelperChainTB(15) - - initial_helper_out = -8000 - ref_tags = np.array([ - 24778, 16789, 8801, 814, 25596, 17612, 9628, 1646, - 26433, 18453, 10474, 2496, 27287, 19311, 11337, 3364, 28160, - 20190, 12221, 4253, 29054, 21088, 13124, 5161, 29966, 22005, - 14045, 6087, 30897, 22940, 14985, 7031, 31847, 23895, 15944, - 7995, 47, 24869, 16923, 8978, 1035, 25861, 17920, 9981, - 2042, 26873, 18937, 11002, 3069, 27904, 19973, 12042, 4113, - 28953, 21026, 13100, 5175, 30020, 22098, 14177, 6257, 31106, - 23189, 15273, 7358, 32212, 24300, 16388, 8478, 569, 25429, - 17522, 9617, 1712, 26577, 18675, 10774, 2875, 27745, 19848, - 11951, 4056, 28930, 21038, 13147, 5256, 30135, 22247, 14361, - 6475, 31359, 23476, 15595, 7714, 32603, 24725, 16847, 8971, - 1096 - ]) - adpll_sim = np.array([ - 8, 24, 41, 57, 74, 91, 107, 124, 140, 157, 173, - 190, 206, 223, 239, 256, 273, 289, 306, 322, 339, 355, - 372, 388, 405, 421, 438, 454, 471, 487, 504, 520, 537, - 553, 570, 586, 603, 619, 636, 652, 668, 685, 701, 718, - 734, 751, 767, 784, 800, 817, 833, 850, 866, 882, 899, - 915, 932, 948, 965, 981, 998, 1014, 1030, 1047, 1063, 1080, - 1096, 1112, 1129, 1145, 1162, 1178, 1194, 1211, 1227, 1244, 1260, - 1276, 1293, 1309, 1326, 1342, 1358, 1375, 1391, 1407, 1424, 1440, - 1457, 1473, 1489, 1506, 1522, 1538, 1555, 1571, 1587, 1604, 1620, - 1636]) - - def sim(): - yield pll.collector.out_helper.eq(initial_helper_out) - for ref_tag, adpll_matlab in zip(ref_tags, adpll_sim): - # feed collector - yield pll.tag_ref.eq(int(ref_tag)) - yield pll.input_stb.eq(1) - - yield - - yield pll.input_stb.eq(0) - - while not (yield pll.collector.out_stb): - yield - - tag_diff = yield pll.collector.out_helper - - while not (yield pll.loop_filter.output_stb): - yield - - adpll_migen = yield pll.adpll - self.assertEqual(adpll_migen, adpll_matlab) - - yield - - run_simulation(pll, [sim()]) diff --git a/artiq/gateware/test/wrpll/test_thls.py b/artiq/gateware/test/wrpll/test_thls.py deleted file mode 100644 index c1013de30..000000000 --- a/artiq/gateware/test/wrpll/test_thls.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest - -from migen import * - -from artiq.gateware.drtio.wrpll import thls - - -a = 0 - -def simple_test(x): - global a - a = a + (x*4 >> 1) - return a - - -class TestTHLS(unittest.TestCase): - def test_thls(self): - global a - - proc = thls.Processor() - a = 0 - cp = thls.compile(proc, simple_test) - print("Program:") - cp.pretty_print() - cp.dimension_processor() - print("Encoded program:", cp.encode()) - proc_impl = proc.implement(cp.encode(), cp.data) - - def send_values(values): - for value in values: - yield proc_impl.input.eq(value) - yield proc_impl.input_stb.eq(1) - yield - yield proc_impl.input.eq(0) - yield proc_impl.input_stb.eq(0) - yield - while (yield proc_impl.busy): - yield - @passive - def receive_values(callback): - while True: - while not (yield proc_impl.output_stb): - yield - callback((yield proc_impl.output)) - yield - - send_list = [42, 40, 10, 10] - receive_list = [] - - run_simulation(proc_impl, [send_values(send_list), receive_values(receive_list.append)]) - print("Execution:", send_list, "->", receive_list) - - a = 0 - expected_list = [simple_test(x) for x in send_list] - self.assertEqual(receive_list, expected_list) diff --git a/doc/wrpll_diagram.png b/doc/wrpll_diagram.png deleted file mode 100644 index 1085e0b15335dc913758370250c819caa810a42a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58653 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

25xcf?lM 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