diff --git a/src/libboard_artiq/src/cxp_downconn.rs b/src/libboard_artiq/src/cxp_downconn.rs index 41acbd3..fb5f52f 100644 --- a/src/libboard_artiq/src/cxp_downconn.rs +++ b/src/libboard_artiq/src/cxp_downconn.rs @@ -4,115 +4,440 @@ use log::info; use crate::pl::csr; -pub fn main(timer: &mut GlobalTimer) { +pub struct CXP_DownConn_Settings { + pub rxdiv: u8, + pub qpll_fbdiv: u8, +} + +#[derive(Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum CXP_SPEED { + CXP_1, + CXP_2, + CXP_3, + CXP_5, + CXP_6, + CXP_10, + CXP_12, +} + +fn loopback_testing(timer: &mut GlobalTimer, data: u8, control_bit: u8) { unsafe { - info!("turning on pmc loopback mode..."); - csr::cxp::downconn_loopback_mode_write(0b010); // Near-End PMA Loopback + // send K28_5 for CDR to align + const K28_5: u8 = 0xBC; + const K28_1: u8 = 0x3C; + const D21_5: u8 = 21 | 5 << 5; + // brute force aligner only align K28_5 on rxdata[:10] + // don't send too much K28_5 in case phase is locked on the wrong stuff + // seems like I need at least 2 K28_5 to work - loopback_testing(timer, 0x00, 0); - } + // NOTE: testing with IDLE word + buildin comma alignment + // 62.5MHz OK + // 125MHz OK + // 156.25MHz OK + // 250MHz + // 312.5MHz + const LEN: usize = 4; + const DATA: [[u8; LEN]; 2] = [ + // [K28_5, K28_5, K28_5, K28_1, K28_5, K28_5, K28_5, K28_5], + // [1, 1, 1, 1, 1, 1, 1, 1], + [K28_5, K28_1, K28_1, D21_5], + [1, 1, 1, 0], + ]; - fn loopback_testing(timer: &mut GlobalTimer, data: u8, control_bit: u8) { - unsafe { - // send K28_5 for CDR to align - const K28_5: u8 = 0xBC; - const K28_1: u8 = 0x3C; - const D21_5: u8 = 21 | 5 << 5; - // brute force aligner only align K28_5 on rxdata[:10] - // don't send too much K28_5 in case phase is locked on the wrong stuff - // seems like I need at least 2 K28_5 to work + // // STEP 1: reset QPLL + // csr::cxp::downconn_qpll_reset_write(1); + // info!("waiting for QPLL/CPLL to lock..."); + // while csr::cxp::downconn_qpll_locked_read() != 1 {} + // info!("QPLL locked"); - // NOTE: testing with IDLE word + buildin comma alignment - // 62.5MHz OK - // 125MHz OK - // 156.25MHz OK - // 250MHz - // 312.5MHz - const LEN: usize = 4; - const DATA: [[u8; LEN]; 2] = [ - // [K28_5, K28_5, K28_5, K28_1, K28_5, K28_5, K28_5, K28_5], - // [1, 1, 1, 1, 1, 1, 1, 1], - [K28_5, K28_1, K28_1, D21_5], - [1, 1, 1, 0], - ]; + // STEP 2: setup tx/rx gtx + csr::cxp::downconn_data_0_write(DATA[0][0]); + csr::cxp::downconn_data_1_write(DATA[0][1]); + csr::cxp::downconn_data_2_write(DATA[0][2]); + csr::cxp::downconn_data_3_write(DATA[0][3]); - // STEP 1: reset QPLL - csr::cxp::downconn_qpll_reset_write(1); - info!("waiting for QPLL/CPLL to lock..."); - while csr::cxp::downconn_qpll_locked_read() != 1 {} - info!("QPLL locked"); + csr::cxp::downconn_control_bit_0_write(DATA[1][0]); + csr::cxp::downconn_control_bit_1_write(DATA[1][1]); + csr::cxp::downconn_control_bit_2_write(DATA[1][2]); + csr::cxp::downconn_control_bit_3_write(DATA[1][3]); - // STEP 2: setup tx/rx gtx - csr::cxp::downconn_data_0_write(DATA[0][0]); - csr::cxp::downconn_data_1_write(DATA[0][1]); - csr::cxp::downconn_data_2_write(DATA[0][2]); - csr::cxp::downconn_data_3_write(DATA[0][3]); + // TEST: change rx linerate + // works great - csr::cxp::downconn_control_bit_0_write(DATA[1][0]); - csr::cxp::downconn_control_bit_1_write(DATA[1][1]); - csr::cxp::downconn_control_bit_2_write(DATA[1][2]); - csr::cxp::downconn_control_bit_3_write(DATA[1][3]); + // enable cxp gtx clock domains - // enable cxp gtx clock domains - csr::cxp::downconn_tx_start_init_write(1); - csr::cxp::downconn_rx_start_init_write(1); + info!("waiting for tx setup..."); + timer.delay_us(50_000); + info!( + "tx_phaligndone = {} | rx_phaligndone = {}", + csr::cxp::downconn_txinit_phaligndone_read(), + csr::cxp::downconn_rxinit_phaligndone_read(), + ); - info!("waiting for tx setup..."); - timer.delay_us(50_000); - info!( - "tx_phaligndone = {} | rx_phaligndone = {}", - csr::cxp::downconn_txinit_phaligndone_read(), - csr::cxp::downconn_rxinit_phaligndone_read(), - ); + // enable txdata tranmission thought MGTXTXP, required by PMA loopback + csr::cxp::downconn_txenable_write(1); - // enable txdata tranmission thought MGTXTXP, required by PMA loopback - csr::cxp::downconn_txenable_write(1); + info!("waiting for rx to align..."); + while csr::cxp::downconn_rx_ready_read() != 1 {} + timer.delay_us(50_000); + info!("rx ready!"); - info!("waiting for rx to align..."); - while csr::cxp::downconn_rx_ready_read() != 1 {} - timer.delay_us(50_000); - info!("rx ready!"); + // csr::cxp::data_3_write(data); + // csr::cxp::control_bit_3_write(control_bit); + println!( + "data[0] = {:#04x} control bit = {:#b} encoded = {:#012b}", + csr::cxp::downconn_data_0_read(), + csr::cxp::downconn_control_bit_0_read(), + csr::cxp::downconn_encoded_0_read(), + ); + println!( + "data[1] = {:#04x} control bit = {:#b} encoded = {:#012b}", + csr::cxp::downconn_data_1_read(), + csr::cxp::downconn_control_bit_1_read(), + csr::cxp::downconn_encoded_1_read(), + ); - // csr::cxp::data_3_write(data); - // csr::cxp::control_bit_3_write(control_bit); + for _ in 0..20 { + timer.delay_us(100); + // println!( + // "data[0] = {:#012b} data[1] = {:#012b}", + // csr::cxp::rxdata_0_read(), + // csr::cxp::rxdata_1_read(), + // ); println!( - "data[0] = {:#04x} control bit = {:#b} encoded = {:#012b}", - csr::cxp::downconn_data_0_read(), - csr::cxp::downconn_control_bit_0_read(), - csr::cxp::downconn_encoded_0_read(), + "decoded_data[0] = {:#04x} decoded_k[0] = {:#b} decoded_data[1] = {:#04x} decoded_k[1] = {:#b}", + csr::cxp::downconn_decoded_data_0_read(), + csr::cxp::downconn_decoded_k_0_read(), + csr::cxp::downconn_decoded_data_1_read(), + csr::cxp::downconn_decoded_k_1_read(), ); - println!( - "data[1] = {:#04x} control bit = {:#b} encoded = {:#012b}", - csr::cxp::downconn_data_1_read(), - csr::cxp::downconn_control_bit_1_read(), - csr::cxp::downconn_encoded_1_read(), - ); - - for _ in 0..20 { - timer.delay_us(100); - // println!( - // "data[0] = {:#012b} data[1] = {:#012b}", - // csr::cxp::rxdata_0_read(), - // csr::cxp::rxdata_1_read(), - // ); - println!( - "decoded_data[0] = {:#04x} decoded_k[0] = {:#b} decoded_data[1] = {:#04x} decoded_k[1] = {:#b}", - csr::cxp::downconn_decoded_data_0_read(), - csr::cxp::downconn_decoded_k_0_read(), - csr::cxp::downconn_decoded_data_1_read(), - csr::cxp::downconn_decoded_k_1_read(), - ); - } } } } -pub fn change_linerate() { +pub fn setup(timer: &mut GlobalTimer, speed: CXP_SPEED) { + unsafe { + info!("turning on pmc loopback mode..."); + csr::cxp::downconn_loopback_mode_write(0b010); // Near-End PMA Loopback + + // QPLL setup + csr::cxp::downconn_qpll_reset_write(1); + info!("waiting for QPLL/CPLL to lock..."); + while csr::cxp::downconn_qpll_locked_read() != 1 {} + info!("QPLL locked"); + + // tx/rx setup + csr::cxp::downconn_tx_start_init_write(1); + csr::cxp::downconn_rx_start_init_write(1); + + info!("waiting for tx setup..."); + timer.delay_us(50_000); + info!( + "tx_phaligndone = {} | rx_phaligndone = {}", + csr::cxp::downconn_txinit_phaligndone_read(), + csr::cxp::downconn_rxinit_phaligndone_read(), + ); + } + + change_linerate(timer, speed); + + loopback_testing(timer, 0x00, 0); +} + +pub fn change_linerate(timer: &mut GlobalTimer, speed: CXP_SPEED) { // TODO: switch QPLL divider for RXUSRCLK - // TODO: switch TX/RXDIV via TX/RXRATE - // no need for DRP for this + // TODO: set TX/RXDIV via TX/RXRATE + change_qpll_settings(speed); - // TODO: switch pll for TXUSRCLK = freq(linerate)/20 - // TODO: reset tx&rx for phase alignment + unsafe { + csr::cxp::downconn_qpll_reset_write(1); + info!("waiting for QPLL/CPLL to lock..."); + while csr::cxp::downconn_qpll_locked_read() != 1 {} + info!("QPLL locked"); + } + + // DEBUG: DRP pll for TXUSRCLK = freq(linerate)/20 + let settings = txusrclk::get_txusrclk_config(speed); + txusrclk::setup(timer, settings); + + // reset tx&rx for phase alignment + unsafe { + // csr::cxp::downconn_tx_restart_write(1); // <--- NOTE: changing TXRATE will do reset automatically, no need to manually reset + csr::cxp::downconn_rx_restart_write(1); // <--- NOTE: this doesn't do anything atm + } +} + +fn change_qpll_settings(speed: CXP_SPEED) { + let divider = match speed { + CXP_SPEED::CXP_1 => 0b100, // Divided by 8 + CXP_SPEED::CXP_2 | CXP_SPEED::CXP_3 => 0b011, // Divided by 4 + CXP_SPEED::CXP_5 | CXP_SPEED::CXP_6 => 0b010, // Divided by 2 + CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => 0b010, // Divided by 1 + }; + + unsafe { + csr::cxp::downconn_rx_div_write(divider); + csr::cxp::downconn_tx_div_write(divider); + } +} + +pub mod txusrclk { + use super::*; + + pub struct PLLSetting { + pub clkout0_reg1: u16, //0x08 + pub clkout0_reg2: u16, //0x09 + pub clkfbout_reg1: u16, //0x14 + pub clkfbout_reg2: u16, //0x15 + pub div_reg: u16, //0x16 + pub lock_reg1: u16, //0x18 + pub lock_reg2: u16, //0x19 + pub lock_reg3: u16, //0x1A + pub power_reg: u16, //0x28 + pub filt_reg1: u16, //0x4E + pub filt_reg2: u16, //0x4F + } + + fn one_clock_cycle() { + unsafe { + csr::cxp::downconn_pll_dclk_write(1); + csr::cxp::downconn_pll_dclk_write(0); + } + } + + fn set_addr(address: u8) { + unsafe { + csr::cxp::downconn_pll_daddr_write(address); + } + } + + fn set_data(value: u16) { + unsafe { + csr::cxp::downconn_pll_din_write(value); + } + } + + fn set_enable(en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + csr::cxp::downconn_pll_den_write(val); + } + } + + fn set_write_enable(en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + csr::cxp::downconn_pll_dwen_write(val); + } + } + + fn get_data() -> u16 { + unsafe { csr::cxp::downconn_pll_dout_read() } + } + + fn drp_ready() -> bool { + unsafe { csr::cxp::downconn_pll_dready_read() == 1 } + } + + #[allow(dead_code)] + fn read(address: u8) -> u16 { + set_addr(address); + set_enable(true); + // Set DADDR on the mmcm and assert DEN for one clock cycle + one_clock_cycle(); + + set_enable(false); + while !drp_ready() { + // keep the clock signal until data is ready + one_clock_cycle(); + } + get_data() + } + + fn write(address: u8, value: u16) { + set_addr(address); + set_data(value); + set_write_enable(true); + set_enable(true); + // Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle + one_clock_cycle(); + + set_write_enable(false); + set_enable(false); + while !drp_ready() { + // keep the clock signal until write is finished + one_clock_cycle(); + } + } + + fn reset(rst: bool) { + unsafe { + let val = if rst { 1 } else { 0 }; + csr::cxp::downconn_txpll_reset_write(val) + } + } + + pub fn setup(timer: &mut GlobalTimer, settings: PLLSetting) { + if false { + info!("0x08 = {:#06x}", read(0x08)); + info!("0x09 = {:#06x}", read(0x09)); + info!("0x14 = {:#06x}", read(0x14)); + info!("0x15 = {:#06x}", read(0x15)); + info!("0x16 = {:#06x}", read(0x16)); + info!("0x18 = {:#06x}", read(0x18)); + info!("0x19 = {:#06x}", read(0x19)); + info!("0x1A = {:#06x}", read(0x1A)); + info!("0x28 = {:#06x}", read(0x28)); + info!("0x4E = {:#06x}", read(0x4E)); + info!("0x4F = {:#06x}", read(0x4F)); + } else { + // Based on "DRP State Machine" from XAPP888 + // hold reset HIGH during pll config + reset(true); + write(0x08, settings.clkout0_reg1); + write(0x09, settings.clkout0_reg2); + write(0x14, settings.clkfbout_reg1); + write(0x15, settings.clkfbout_reg2); + write(0x16, settings.div_reg); + write(0x18, settings.lock_reg1); + write(0x19, settings.lock_reg2); + write(0x1A, settings.lock_reg3); + write(0x28, settings.power_reg); + write(0x4E, settings.filt_reg1); + write(0x4F, settings.filt_reg2); + reset(false); + + // wait for the pll to lock + timer.delay_us(100); + + let locked = unsafe { csr::cxp::downconn_txpll_locked_read() == 1 }; + info!("txusrclk locked = {}", locked); + } + } + + pub fn get_txusrclk_config(speed: CXP_SPEED) -> PLLSetting { + match speed { + CXP_SPEED::CXP_1 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 16 + // TXUSRCLK=62.5MHz + PLLSetting { + clkout0_reg1: 0x1208, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_2 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8 + // TXUSRCLK=125MHz + PLLSetting { + clkout0_reg1: 0x1104, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_3 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8 + // TXUSRCLK=125MHz + PLLSetting { + clkout0_reg1: 0x1104, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + CXP_SPEED::CXP_5 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4 + // TXUSRCLK=250MHz + PLLSetting { + clkout0_reg1: 0x1082, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_6 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4 + // TXUSRCLK=312.5MHz + PLLSetting { + clkout0_reg1: 0x1082, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + CXP_SPEED::CXP_10 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 2 + // TXUSRCLK=500MHz + PLLSetting { + clkout0_reg1: 0x1041, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_12 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 2 + // TXUSRCLK=625MHz + PLLSetting { + clkout0_reg1: 0x1041, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + } + } }