forked from M-Labs/artiq
remove WRPLL
This commit is contained in:
parent
668997a451
commit
cf2a4972f7
|
@ -22,8 +22,6 @@ pub mod rpc_queue;
|
||||||
|
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
pub mod si5324;
|
pub mod si5324;
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
pub mod wrpll;
|
|
||||||
|
|
||||||
#[cfg(has_grabber)]
|
#[cfg(has_grabber)]
|
||||||
pub mod grabber;
|
pub mod grabber;
|
||||||
|
|
|
@ -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<u8, &'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::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<i32, &'static str> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,6 @@ use core::convert::TryFrom;
|
||||||
use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp};
|
use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp};
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
use board_artiq::si5324;
|
use board_artiq::si5324;
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
use board_artiq::wrpll;
|
|
||||||
use board_artiq::{spi, drtioaux};
|
use board_artiq::{spi, drtioaux};
|
||||||
use board_artiq::drtio_routing;
|
use board_artiq::drtio_routing;
|
||||||
use riscv::register::{mcause, mepc, mtval};
|
use riscv::register::{mcause, mepc, mtval};
|
||||||
|
@ -420,8 +418,6 @@ fn sysclk_setup() {
|
||||||
else {
|
else {
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
si5324::setup(&SI5324_SETTINGS, si5324::Input::Ckin1).expect("cannot initialize Si5324");
|
si5324::setup(&SI5324_SETTINGS, si5324::Input::Ckin1).expect("cannot initialize Si5324");
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
wrpll::init();
|
|
||||||
info!("Switching sys clock, rebooting...");
|
info!("Switching sys clock, rebooting...");
|
||||||
// delay for clean UART log, wait until UART FIFO is empty
|
// delay for clean UART log, wait until UART FIFO is empty
|
||||||
clock::spin_us(1300);
|
clock::spin_us(1300);
|
||||||
|
@ -460,17 +456,6 @@ pub extern fn main() -> i32 {
|
||||||
io_expander1 = board_misoc::io_expander::IoExpander::new(1);
|
io_expander1 = board_misoc::io_expander::IoExpander::new(1);
|
||||||
io_expander0.init().expect("I2C I/O expander #0 initialization failed");
|
io_expander0.init().expect("I2C I/O expander #0 initialization failed");
|
||||||
io_expander1.init().expect("I2C I/O expander #1 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
|
// Actively drive TX_DISABLE to false on SFP0..3
|
||||||
io_expander0.set_oe(0, 1 << 1).unwrap();
|
io_expander0.set_oe(0, 1 << 1).unwrap();
|
||||||
|
@ -490,8 +475,6 @@ pub extern fn main() -> i32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
csr::drtio_transceiver::txenable_write(0xffffffffu32 as _);
|
csr::drtio_transceiver::txenable_write(0xffffffffu32 as _);
|
||||||
}
|
}
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
wrpll::diagnostics();
|
|
||||||
|
|
||||||
init_rtio_crg();
|
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::select_recovered_clock(true).expect("failed to switch clocks");
|
||||||
si5324::siphaser::calibrate_skew().expect("failed to calibrate skew");
|
si5324::siphaser::calibrate_skew().expect("failed to calibrate skew");
|
||||||
}
|
}
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
wrpll::select_recovered_clock(true);
|
|
||||||
|
|
||||||
drtioaux::reset(0);
|
drtioaux::reset(0);
|
||||||
drtiosat_reset(false);
|
drtiosat_reset(false);
|
||||||
|
@ -565,8 +546,6 @@ pub extern fn main() -> i32 {
|
||||||
info!("uplink is down, switching to local oscillator clock");
|
info!("uplink is down, switching to local oscillator clock");
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
si5324::siphaser::select_recovered_clock(false).expect("failed to switch clocks");
|
si5324::siphaser::select_recovered_clock(false).expect("failed to switch clocks");
|
||||||
#[cfg(has_wrpll)]
|
|
||||||
wrpll::select_recovered_clock(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
from artiq.gateware.drtio.wrpll.core import WRPLL
|
|
||||||
from artiq.gateware.drtio.wrpll.ddmtd import DDMTDSamplerExtFF, DDMTDSamplerGTP
|
|
|
@ -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)
|
|
||||||
]
|
|
|
@ -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")
|
|
||||||
)
|
|
|
@ -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
|
|
|
@ -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()
|
|
|
@ -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)
|
|
|
@ -21,7 +21,6 @@ from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
|
||||||
from artiq.gateware import eem
|
from artiq.gateware import eem
|
||||||
from artiq.gateware.drtio.transceiver import gtp_7series
|
from artiq.gateware.drtio.transceiver import gtp_7series
|
||||||
from artiq.gateware.drtio.siphaser import SiPhaser7Series
|
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.rx_synchronizer import XilinxRXSynchronizer
|
||||||
from artiq.gateware.drtio import *
|
from artiq.gateware.drtio import *
|
||||||
from artiq.build_soc import *
|
from artiq.build_soc import *
|
||||||
|
@ -410,7 +409,7 @@ class SatelliteBase(BaseSoC):
|
||||||
}
|
}
|
||||||
mem_map.update(BaseSoC.mem_map)
|
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"):
|
if hw_rev in ("v1.0", "v1.1"):
|
||||||
cpu_bus_width = 32
|
cpu_bus_width = 32
|
||||||
else:
|
else:
|
||||||
|
@ -533,24 +532,7 @@ class SatelliteBase(BaseSoC):
|
||||||
|
|
||||||
rtio_clk_period = 1e9/rtio_clk_freq
|
rtio_clk_period = 1e9/rtio_clk_freq
|
||||||
self.config["RTIO_FREQUENCY"] = str(rtio_clk_freq/1e6)
|
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(
|
self.submodules.siphaser = SiPhaser7Series(
|
||||||
si5324_clkin=platform.request("cdr_clk") if platform.hw_rev == "v2.0"
|
si5324_clkin=platform.request("cdr_clk") if platform.hw_rev == "v2.0"
|
||||||
else platform.request("si5324_clkin"),
|
else platform.request("si5324_clkin"),
|
||||||
|
@ -573,9 +555,6 @@ class SatelliteBase(BaseSoC):
|
||||||
platform.add_false_path_constraints(
|
platform.add_false_path_constraints(
|
||||||
self.crg.cd_sys.clk,
|
self.crg.cd_sys.clk,
|
||||||
gtp.txoutclk, gtp.rxoutclk)
|
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:]:
|
for gtp in self.drtio_transceiver.gtps[1:]:
|
||||||
platform.add_period_constraint(gtp.rxoutclk, rtio_clk_period)
|
platform.add_period_constraint(gtp.rxoutclk, rtio_clk_period)
|
||||||
platform.add_false_path_constraints(
|
platform.add_false_path_constraints(
|
||||||
|
@ -652,7 +631,6 @@ def main():
|
||||||
parser.add_argument("-V", "--variant", default="tester",
|
parser.add_argument("-V", "--variant", default="tester",
|
||||||
help="variant: {} (default: %(default)s)".format(
|
help="variant: {} (default: %(default)s)".format(
|
||||||
"/".join(sorted(VARIANTS.keys()))))
|
"/".join(sorted(VARIANTS.keys()))))
|
||||||
parser.add_argument("--with-wrpll", default=False, action="store_true")
|
|
||||||
parser.add_argument("--tester-dds", default=None,
|
parser.add_argument("--tester-dds", default=None,
|
||||||
help="Tester variant DDS type: ad9910/ad9912 "
|
help="Tester variant DDS type: ad9910/ad9912 "
|
||||||
"(default: ad9910)")
|
"(default: ad9910)")
|
||||||
|
@ -661,8 +639,6 @@ def main():
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
argdict = dict()
|
argdict = dict()
|
||||||
if args.with_wrpll:
|
|
||||||
argdict["with_wrpll"] = True
|
|
||||||
argdict["gateware_identifier_str"] = args.gateware_identifier_str
|
argdict["gateware_identifier_str"] = args.gateware_identifier_str
|
||||||
argdict["dds"] = args.tester_dds
|
argdict["dds"] = args.tester_dds
|
||||||
|
|
||||||
|
|
|
@ -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()])
|
|
|
@ -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)
|
|
Binary file not shown.
Before Width: | Height: | Size: 57 KiB |
Loading…
Reference in New Issue