diff --git a/artiq/firmware/libboard/Cargo.toml b/artiq/firmware/libboard/Cargo.toml index 950fbcff2..e3fed9298 100644 --- a/artiq/firmware/libboard/Cargo.toml +++ b/artiq/firmware/libboard/Cargo.toml @@ -8,6 +8,9 @@ build = "build.rs" name = "board" path = "lib.rs" +[build-dependencies] +build_artiq = { path = "../libbuild_artiq" } + [dependencies] log = { version = "0.3", default-features = false } diff --git a/artiq/firmware/libboard/ad9154.rs b/artiq/firmware/libboard/ad9154.rs index 69f5ef547..db74f924b 100644 --- a/artiq/firmware/libboard/ad9154.rs +++ b/artiq/firmware/libboard/ad9154.rs @@ -2,7 +2,7 @@ use csr; use clock; use ad9154_reg; -fn spi_setup() { +fn spi_setup(dacno: u8) { unsafe { csr::converter_spi::offline_write(1); csr::converter_spi::cs_polarity_write(0); @@ -14,7 +14,7 @@ fn spi_setup() { csr::converter_spi::clk_div_read_write(16); csr::converter_spi::xfer_len_write_write(24); csr::converter_spi::xfer_len_read_write(0); - csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_DAC_CS); + csr::converter_spi::cs_write(1 << (csr::CONFIG_CONVERTER_SPI_FIRST_AD9154_CS + dacno as u32)); csr::converter_spi::offline_write(0); } } @@ -35,39 +35,39 @@ fn read(addr: u16) -> u8 { } } -fn jesd_reset(rst: bool) { +fn jesd_reset(dacno: u8, rst: bool) { unsafe { - csr::ad9154::jesd_jreset_write(if rst {1} else {0}) + (csr::AD9154[dacno as usize].jesd_jreset_write)(if rst {1} else {0}) } } -fn jesd_enable(en: bool) { +fn jesd_enable(dacno: u8, en: bool) { unsafe { - csr::ad9154::jesd_control_enable_write(if en {1} else {0}) + (csr::AD9154[dacno as usize].jesd_control_enable_write)(if en {1} else {0}) } } -fn jesd_ready() -> bool { +fn jesd_ready(dacno: u8) -> bool { unsafe { - csr::ad9154::jesd_control_ready_read() != 0 + (csr::AD9154[dacno as usize].jesd_control_ready_read)() != 0 } } -fn jesd_prbs(en: bool) { +fn jesd_prbs(dacno: u8, en: bool) { unsafe { - csr::ad9154::jesd_control_prbs_config_write(if en {1} else {0}) + (csr::AD9154[dacno as usize].jesd_control_prbs_config_write)(if en {1} else {0}) } } -fn jesd_stpl(en: bool) { +fn jesd_stpl(dacno: u8, en: bool) { unsafe { - csr::ad9154::jesd_control_stpl_enable_write(if en {1} else {0}) + (csr::AD9154[dacno as usize].jesd_control_stpl_enable_write)(if en {1} else {0}) } } -fn jesd_jsync() -> bool { +fn jesd_jsync(dacno: u8) -> bool { unsafe { - csr::ad9154::jesd_control_jsync_read() != 0 + (csr::AD9154[dacno as usize].jesd_control_jsync_read)() != 0 } } @@ -421,19 +421,24 @@ fn monitor() { write(ad9154_reg::IRQ_STATUS3, 0x00); } -fn cfg() -> Result<(), &'static str> { - jesd_enable(false); - jesd_prbs(false); - jesd_stpl(false); +fn cfg(dacno: u8) -> Result<(), &'static str> { + spi_setup(dacno); + // Release the JESD clock domain reset late, as we need to + // set up clock chips before. + jesd_reset(dacno, false); + + jesd_enable(dacno, false); + jesd_prbs(dacno, false); + jesd_stpl(dacno, false); clock::spin_us(10000); - jesd_enable(true); + jesd_enable(dacno, true); dac_setup()?; - jesd_enable(false); + jesd_enable(dacno, false); clock::spin_us(10000); - jesd_enable(true); + jesd_enable(dacno, true); monitor(); let t = clock::get_ms(); - while !jesd_ready() { + while !jesd_ready(dacno) { if clock::get_ms() > t + 200 { return Err("JESD ready timeout"); } @@ -442,7 +447,7 @@ fn cfg() -> Result<(), &'static str> { if read(ad9154_reg::CODEGRPSYNCFLG) != 0x0f { return Err("bad CODEGRPSYNCFLG") } - if !jesd_jsync() { + if !jesd_jsync(dacno) { return Err("bad SYNC") } if read(ad9154_reg::FRAMESYNCFLG) != 0x0f { @@ -458,18 +463,10 @@ fn cfg() -> Result<(), &'static str> { } pub fn init() -> Result<(), &'static str> { - spi_setup(); - - // Release the JESD clock domain reset late, as we need to - // set up clock chips before. - jesd_reset(false); - - for i in 0..99 { - let outcome = cfg(); - match outcome { - Ok(_) => return outcome, - Err(e) => warn!("config attempt #{} failed ({}), retrying", i, e) - } + for dacno in 0..csr::AD9154.len() { + let dacno = dacno as u8; + debug!("setting up DAC #{}", dacno); + cfg(dacno)?; } - cfg() + Ok(()) } diff --git a/artiq/firmware/libboard/ad9516.rs b/artiq/firmware/libboard/ad9516.rs index e6f582050..93d3e1e1e 100644 --- a/artiq/firmware/libboard/ad9516.rs +++ b/artiq/firmware/libboard/ad9516.rs @@ -13,7 +13,7 @@ fn spi_setup() { csr::converter_spi::clk_div_read_write(16); csr::converter_spi::xfer_len_write_write(24); csr::converter_spi::xfer_len_read_write(0); - csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_CLK_CS); + csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_AD9516_CS); csr::converter_spi::offline_write(0); } } diff --git a/artiq/firmware/libboard/build.rs b/artiq/firmware/libboard/build.rs index a7b0335e5..26a3aa1bc 100644 --- a/artiq/firmware/libboard/build.rs +++ b/artiq/firmware/libboard/build.rs @@ -1,15 +1,29 @@ +extern crate build_artiq; + use std::env; -use std::path::Path; -use std::io::{BufRead, BufReader}; use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +use std::process::Command; + +fn gen_hmc7043_writes() { + println!("cargo:rerun-if-changed=hmc7043_gen_writes.py"); + println!("cargo:rerun-if-changed=hmc7043_guiexport_10gbps.py"); + + let hmc7043_writes = + Command::new("python3") + .arg("hmc7043_gen_writes.py") + .arg("hmc7043_guiexport_10gbps.py") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let mut f = File::create(out_dir.join("hmc7043_writes.rs")).unwrap(); + write!(f, "{}", hmc7043_writes).unwrap(); +} fn main() { - let out_dir = env::var("BUILDINC_DIRECTORY").unwrap(); - let cfg_path = Path::new(&out_dir).join("generated").join("rust-cfg"); - println!("cargo:rerun-if-changed={}", cfg_path.to_str().unwrap()); - - let f = BufReader::new(File::open(&cfg_path).unwrap()); - for line in f.lines() { - println!("cargo:rustc-cfg={}", line.unwrap()); - } + build_artiq::misoc_cfg(); + gen_hmc7043_writes(); } diff --git a/artiq/firmware/libboard/hmc7043_gen_writes.py b/artiq/firmware/libboard/hmc7043_gen_writes.py new file mode 100755 index 000000000..3f0a8cb47 --- /dev/null +++ b/artiq/firmware/libboard/hmc7043_gen_writes.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# The HMC7043 GUI exports register write lists into Python files. +# This script converts them into Rust arrays. + +import sys +import runpy + + +class DUT: + def __init__(self): + self.writes = [] + + def write(self, address, value): + self.writes.append((address, value)) + + +def main(): + dut = DUT() + runpy.run_path(sys.argv[1], {"dut": dut}) + + print("// This file was autogenerated by hmc7043_gen_writes.py") + print("const HMC7043_WRITES: [(u16, u8); {}] = [".format(len(dut.writes))) + for address, value in dut.writes: + print(" (0x{:04x}, 0x{:02x}),".format(address, value)) + print("];") + + +if __name__ == "__main__": + main() diff --git a/artiq/firmware/libboard/hmc7043_guiexport_10gbps.py b/artiq/firmware/libboard/hmc7043_guiexport_10gbps.py new file mode 100644 index 000000000..de3275ba0 --- /dev/null +++ b/artiq/firmware/libboard/hmc7043_guiexport_10gbps.py @@ -0,0 +1,697 @@ +# glbl_cfg1_swrst[0:0] = 0x0 +dut.write(0x0, 0x0) + +# glbl_cfg1_sleep[0:0] = 0x0 +# glbl_cfg1_restart[1:1] = 0x0 +# sysr_cfg1_pulsor_req[2:2] = 0x0 +# grpx_cfg1_mute[3:3] = 0x0 +# dist_cfg1_perf_floor[6:6] = 0x0 +# sysr_cfg1_reseed_req[7:7] = 0x0 +dut.write(0x1, 0x0) + +# sysr_cfg1_rev[0:0] = 0x0 +# sysr_cfg1_slipN_req[1:1] = 0x0 +dut.write(0x2, 0x0) + +# glbl_cfg1_ena_sysr[2:2] = 0x1 +# glbl_cfg2_ena_vcos[4:3] = 0x0 +# glbl_cfg1_ena_sysri[5:5] = 0x1 +dut.write(0x3, 0x24) + +# glbl_cfg7_ena_clkgr[6:0] = 0x3B +dut.write(0x4, 0x3B) + +# glbl_cfg1_clear_alarms[0:0] = 0x0 +dut.write(0x6, 0x0) + +# glbl_reserved[0:0] = 0x0 +dut.write(0x7, 0x0) + +# glbl_cfg5_ibuf0_en[0:0] = 0x0 +# glbl_cfg5_ibuf0_mode[4:1] = 0x7 +dut.write(0xA, 0xE) + +# glbl_cfg5_ibuf1_en[0:0] = 0x1 +# glbl_cfg5_ibuf1_mode[4:1] = 0x7 +dut.write(0xB, 0xF) + +# glbl_cfg5_gpi1_en[0:0] = 0x0 +# glbl_cfg5_gpi1_sel[4:1] = 0x0 +dut.write(0x46, 0x0) + +# glbl_cfg8_gpo1_en[0:0] = 0x1 +# glbl_cfg8_gpo1_mode[1:1] = 0x1 +# glbl_cfg8_gpo1_sel[7:2] = 0x7 +dut.write(0x50, 0x1F) + +# glbl_cfg2_sdio_en[0:0] = 0x1 +# glbl_cfg2_sdio_mode[1:1] = 0x1 +dut.write(0x54, 0x3) + +# sysr_cfg3_pulsor_mode[2:0] = 0x1 +dut.write(0x5A, 0x1) + +# sysr_cfg1_synci_invpol[0:0] = 0x0 +# sysr_cfg1_ext_sync_retimemode[2:2] = 0x1 +dut.write(0x5B, 0x4) + +# sysr_cfg16_divrat_lsb[7:0] = 0x0 +dut.write(0x5C, 0x0) + +# sysr_cfg16_divrat_msb[3:0] = 0x6 +dut.write(0x5D, 0x6) + +# dist_cfg1_extvco_islowfreq_sel[0:0] = 0x0 +# dist_cfg1_extvco_div2_sel[1:1] = 0x1 +dut.write(0x64, 0x2) + +# clkgrpx_cfg1_alg_dly_lowpwr_sel[0:0] = 0x0 +dut.write(0x65, 0x0) + +# alrm_cfg1_sysr_unsyncd_allow[1:1] = 0x0 +# alrm_cfg1_clkgrpx_validph_allow[2:2] = 0x0 +# alrm_cfg1_sync_req_allow[4:4] = 0x1 +dut.write(0x71, 0x10) + +# glbl_ro8_chipid_lob[7:0] = 0x1 +dut.write(0x78, 0x1) + +# glbl_ro8_chipid_mid[7:0] = 0x52 +dut.write(0x79, 0x52) + +# glbl_ro8_chipid_hib[7:0] = 0x4 +dut.write(0x7A, 0x4) + +# alrm_ro1_sysr_unsyncd_now[1:1] = 0x1 +# alrm_ro1_clkgrpx_validph_now[2:2] = 0x0 +# alrm_ro1_sync_req_now[4:4] = 0x1 +dut.write(0x7D, 0x12) + +# sysr_ro4_fsmstate[3:0] = 0x2 +# grpx_ro1_outdivfsm_busy[4:4] = 0x0 +dut.write(0x91, 0x2) + +# reg_98[7:0] = 0x0 +dut.write(0x98, 0x0) + +# reg_99[7:0] = 0x0 +dut.write(0x99, 0x0) + +# reg_9A[7:0] = 0x0 +dut.write(0x9A, 0x0) + +# reg_9B[7:0] = 0xAA +dut.write(0x9B, 0xAA) + +# reg_9C[7:0] = 0xAA +dut.write(0x9C, 0xAA) + +# reg_9D[7:0] = 0xAA +dut.write(0x9D, 0xAA) + +# reg_9E[7:0] = 0xAA +dut.write(0x9E, 0xAA) + +# reg_9F[7:0] = 0x4D +dut.write(0x9F, 0x4D) + +# reg_A0[7:0] = 0xDF +dut.write(0xA0, 0xDF) + +# reg_A1[7:0] = 0x97 +dut.write(0xA1, 0x97) + +# reg_A2[7:0] = 0x3 +dut.write(0xA2, 0x3) + +# reg_A3[7:0] = 0x0 +dut.write(0xA3, 0x0) + +# reg_A4[7:0] = 0x0 +dut.write(0xA4, 0x0) + +# reg_AD[7:0] = 0x0 +dut.write(0xAD, 0x0) + +# reg_AE[7:0] = 0x8 +dut.write(0xAE, 0x8) + +# reg_AF[7:0] = 0x50 +dut.write(0xAF, 0x50) + +# reg_B0[7:0] = 0x4 +dut.write(0xB0, 0x4) + +# reg_B1[7:0] = 0xD +dut.write(0xB1, 0xD) + +# reg_B2[7:0] = 0x0 +dut.write(0xB2, 0x0) + +# reg_B3[7:0] = 0x0 +dut.write(0xB3, 0x0) + +# reg_B5[7:0] = 0x0 +dut.write(0xB5, 0x0) + +# reg_B6[7:0] = 0x0 +dut.write(0xB6, 0x0) + +# reg_B7[7:0] = 0x0 +dut.write(0xB7, 0x0) + +# reg_B8[7:0] = 0x0 +dut.write(0xB8, 0x0) + +# clkgrp1_div1_cfg1_en[0:0] = 0x1 +# clkgrp1_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp1_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp1_div1_cfg1_rev[4:4] = 0x1 +# clkgrp1_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp1_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp1_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xC8, 0x73) + +# clkgrp1_div1_cfg12_divrat_lsb[7:0] = 0x1 +dut.write(0xC9, 0x1) + +# clkgrp1_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xCA, 0x0) + +# clkgrp1_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xCB, 0x0) + +# clkgrp1_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xCC, 0x0) + +# clkgrp1_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xCD, 0x0) + +# clkgrp1_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0xCE, 0x0) + +# clkgrp1_div1_cfg2_sel_outmux[1:0] = 0x3 +# clkgrp1_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0xCF, 0x3) + +# clkgrp1_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp1_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp1_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp1_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp1_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0xD0, 0x8) + +# clkgrp1_div2_cfg1_en[0:0] = 0x1 +# clkgrp1_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp1_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp1_div2_cfg1_rev[4:4] = 0x1 +# clkgrp1_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp1_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp1_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xD2, 0x71) + +# clkgrp1_div2_cfg12_divrat_lsb[7:0] = 0x40 +dut.write(0xD3, 0x40) + +# clkgrp1_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xD4, 0x0) + +# clkgrp1_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xD5, 0x0) + +# clkgrp1_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xD6, 0x0) + +# clkgrp1_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xD7, 0x0) + +# clkgrp1_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0xD8, 0x0) + +# clkgrp1_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp1_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0xD9, 0x0) + +# clkgrp1_div2_cfg5_drvr_res[1:0] = 0x1 +# clkgrp1_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp1_div2_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp1_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp1_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0xDA, 0x9) + +# clkgrp2_div1_cfg1_en[0:0] = 0x1 +# clkgrp2_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp2_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp2_div1_cfg1_rev[4:4] = 0x1 +# clkgrp2_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp2_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp2_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xDC, 0x73) + +# clkgrp2_div1_cfg12_divrat_lsb[7:0] = 0x1 +dut.write(0xDD, 0x1) + +# clkgrp2_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xDE, 0x0) + +# clkgrp2_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xDF, 0x0) + +# clkgrp2_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xE0, 0x0) + +# clkgrp2_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xE1, 0x0) + +# clkgrp2_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0xE2, 0x0) + +# clkgrp2_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp2_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0xE3, 0x0) + +# clkgrp2_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp2_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp2_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp2_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp2_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0xE4, 0x8) + +# clkgrp2_div2_cfg1_en[0:0] = 0x1 +# clkgrp2_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp2_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp2_div2_cfg1_rev[4:4] = 0x1 +# clkgrp2_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp2_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp2_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xE6, 0x71) + +# clkgrp2_div2_cfg12_divrat_lsb[7:0] = 0x40 +dut.write(0xE7, 0x40) + +# clkgrp2_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xE8, 0x0) + +# clkgrp2_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xE9, 0x0) + +# clkgrp2_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xEA, 0x0) + +# clkgrp2_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xEB, 0x0) + +# clkgrp2_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0xEC, 0x0) + +# clkgrp2_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp2_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0xED, 0x0) + +# clkgrp2_div2_cfg5_drvr_res[1:0] = 0x1 +# clkgrp2_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp2_div2_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp2_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp2_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0xEE, 0x9) + +# clkgrp3_div1_cfg1_en[0:0] = 0x0 +# clkgrp3_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp3_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp3_div1_cfg1_rev[4:4] = 0x1 +# clkgrp3_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp3_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp3_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xF0, 0x72) + +# clkgrp3_div1_cfg12_divrat_lsb[7:0] = 0x2 +dut.write(0xF1, 0x2) + +# clkgrp3_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xF2, 0x0) + +# clkgrp3_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xF3, 0x0) + +# clkgrp3_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xF4, 0x0) + +# clkgrp3_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xF5, 0x0) + +# clkgrp3_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0xF6, 0x0) + +# clkgrp3_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp3_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0xF7, 0x0) + +# clkgrp3_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp3_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp3_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp3_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp3_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0xF8, 0x8) + +# clkgrp3_div2_cfg1_en[0:0] = 0x0 +# clkgrp3_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp3_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp3_div2_cfg1_rev[4:4] = 0x1 +# clkgrp3_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp3_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp3_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0xFA, 0x70) + +# clkgrp3_div2_cfg12_divrat_lsb[7:0] = 0x80 +dut.write(0xFB, 0x80) + +# clkgrp3_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0xFC, 0x0) + +# clkgrp3_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0xFD, 0x0) + +# clkgrp3_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0xFE, 0x0) + +# clkgrp3_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0xFF, 0x0) + +# clkgrp3_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x100, 0x0) + +# clkgrp3_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp3_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x101, 0x0) + +# clkgrp3_div2_cfg5_drvr_res[1:0] = 0x3 +# clkgrp3_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp3_div2_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp3_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp3_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0x102, 0xB) + +# clkgrp4_div1_cfg1_en[0:0] = 0x1 +# clkgrp4_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp4_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp4_div1_cfg1_rev[4:4] = 0x1 +# clkgrp4_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp4_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp4_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x104, 0x73) + +# clkgrp4_div1_cfg12_divrat_lsb[7:0] = 0x4 +dut.write(0x105, 0x4) + +# clkgrp4_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x106, 0x0) + +# clkgrp4_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x107, 0x0) + +# clkgrp4_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x108, 0x0) + +# clkgrp4_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x109, 0x0) + +# clkgrp4_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x10A, 0x0) + +# clkgrp4_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp4_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x10B, 0x0) + +# clkgrp4_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp4_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp4_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp4_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp4_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0x10C, 0x8) + +# clkgrp4_div2_cfg1_en[0:0] = 0x1 +# clkgrp4_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp4_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp4_div2_cfg1_rev[4:4] = 0x1 +# clkgrp4_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp4_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp4_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x10E, 0x71) + +# clkgrp4_div2_cfg12_divrat_lsb[7:0] = 0x40 +dut.write(0x10F, 0x40) + +# clkgrp4_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x110, 0x0) + +# clkgrp4_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x111, 0x0) + +# clkgrp4_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x112, 0x0) + +# clkgrp4_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x113, 0x0) + +# clkgrp4_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x114, 0x0) + +# clkgrp4_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp4_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x115, 0x0) + +# clkgrp4_div2_cfg5_drvr_res[1:0] = 0x3 +# clkgrp4_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp4_div2_cfg5_drvr_mode[4:3] = 0x2 +# clkgrp4_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp4_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0x116, 0x13) + +# clkgrp5_div1_cfg1_en[0:0] = 0x1 +# clkgrp5_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp5_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp5_div1_cfg1_rev[4:4] = 0x1 +# clkgrp5_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp5_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp5_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x118, 0x73) + +# clkgrp5_div1_cfg12_divrat_lsb[7:0] = 0x4 +dut.write(0x119, 0x4) + +# clkgrp5_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x11A, 0x0) + +# clkgrp5_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x11B, 0x0) + +# clkgrp5_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x11C, 0x0) + +# clkgrp5_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x11D, 0x0) + +# clkgrp5_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x11E, 0x0) + +# clkgrp5_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp5_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x11F, 0x0) + +# clkgrp5_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp5_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp5_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp5_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp5_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0x120, 0x8) + +# clkgrp5_div2_cfg1_en[0:0] = 0x1 +# clkgrp5_div2_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp5_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp5_div2_cfg1_rev[4:4] = 0x1 +# clkgrp5_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp5_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp5_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x122, 0x73) + +# clkgrp5_div2_cfg12_divrat_lsb[7:0] = 0x4 +dut.write(0x123, 0x4) + +# clkgrp5_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x124, 0x0) + +# clkgrp5_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x125, 0x0) + +# clkgrp5_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x126, 0x0) + +# clkgrp5_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x127, 0x0) + +# clkgrp5_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x128, 0x0) + +# clkgrp5_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp5_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x129, 0x0) + +# clkgrp5_div2_cfg5_drvr_res[1:0] = 0x3 +# clkgrp5_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp5_div2_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp5_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp5_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0x12A, 0xB) + +# clkgrp6_div1_cfg1_en[0:0] = 0x1 +# clkgrp6_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp6_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp6_div1_cfg1_rev[4:4] = 0x1 +# clkgrp6_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp6_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp6_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x12C, 0x73) + +# clkgrp6_div1_cfg12_divrat_lsb[7:0] = 0x4 +dut.write(0x12D, 0x4) + +# clkgrp6_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x12E, 0x0) + +# clkgrp6_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x12F, 0x0) + +# clkgrp6_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x130, 0x0) + +# clkgrp6_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x131, 0x0) + +# clkgrp6_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x132, 0x0) + +# clkgrp6_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp6_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x133, 0x0) + +# clkgrp6_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp6_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp6_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp6_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp6_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0x134, 0x8) + +# clkgrp6_div2_cfg1_en[0:0] = 0x1 +# clkgrp6_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp6_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp6_div2_cfg1_rev[4:4] = 0x1 +# clkgrp6_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp6_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp6_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x136, 0x71) + +# clkgrp6_div2_cfg12_divrat_lsb[7:0] = 0x80 +dut.write(0x137, 0x80) + +# clkgrp6_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x138, 0x0) + +# clkgrp6_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x139, 0x0) + +# clkgrp6_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x13A, 0x0) + +# clkgrp6_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x13B, 0x0) + +# clkgrp6_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x13C, 0x0) + +# clkgrp6_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp6_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x13D, 0x0) + +# clkgrp6_div2_cfg5_drvr_res[1:0] = 0x1 +# clkgrp6_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp6_div2_cfg5_drvr_mode[4:3] = 0x2 +# clkgrp6_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp6_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0x13E, 0x11) + +# clkgrp7_div1_cfg1_en[0:0] = 0x0 +# clkgrp7_div1_cfg1_phdelta_mslip[1:1] = 0x1 +# clkgrp7_div1_cfg2_startmode[3:2] = 0x0 +# clkgrp7_div1_cfg1_rev[4:4] = 0x1 +# clkgrp7_div1_cfg1_slipmask[5:5] = 0x1 +# clkgrp7_div1_cfg1_reseedmask[6:6] = 0x1 +# clkgrp7_div1_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x140, 0x72) + +# clkgrp7_div1_cfg12_divrat_lsb[7:0] = 0x2 +dut.write(0x141, 0x2) + +# clkgrp7_div1_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x142, 0x0) + +# clkgrp7_div1_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x143, 0x0) + +# clkgrp7_div1_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x144, 0x0) + +# clkgrp7_div1_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x145, 0x0) + +# clkgrp7_div1_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x146, 0x0) + +# clkgrp7_div1_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp7_div1_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x147, 0x0) + +# clkgrp7_div1_cfg5_drvr_res[1:0] = 0x0 +# clkgrp7_div1_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp7_div1_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp7_div1_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp7_div1_cfg2_mutesel[7:6] = 0x0 +dut.write(0x148, 0x8) + +# clkgrp7_div2_cfg1_en[0:0] = 0x0 +# clkgrp7_div2_cfg1_phdelta_mslip[1:1] = 0x0 +# clkgrp7_div2_cfg2_startmode[3:2] = 0x0 +# clkgrp7_div2_cfg1_rev[4:4] = 0x1 +# clkgrp7_div2_cfg1_slipmask[5:5] = 0x1 +# clkgrp7_div2_cfg1_reseedmask[6:6] = 0x1 +# clkgrp7_div2_cfg1_hi_perf[7:7] = 0x0 +dut.write(0x14A, 0x70) + +# clkgrp7_div2_cfg12_divrat_lsb[7:0] = 0x80 +dut.write(0x14B, 0x80) + +# clkgrp7_div2_cfg12_divrat_msb[3:0] = 0x0 +dut.write(0x14C, 0x0) + +# clkgrp7_div2_cfg5_fine_delay[4:0] = 0x0 +dut.write(0x14D, 0x0) + +# clkgrp7_div2_cfg5_sel_coarse_delay[4:0] = 0x0 +dut.write(0x14E, 0x0) + +# clkgrp7_div2_cfg12_mslip_lsb[7:0] = 0x0 +dut.write(0x14F, 0x0) + +# clkgrp7_div2_cfg12_mslip_msb[3:0] = 0x0 +dut.write(0x150, 0x0) + +# clkgrp7_div2_cfg2_sel_outmux[1:0] = 0x0 +# clkgrp7_div2_cfg1_drvr_sel_testclk[2:2] = 0x0 +dut.write(0x151, 0x0) + +# clkgrp7_div2_cfg5_drvr_res[1:0] = 0x3 +# clkgrp7_div2_cfg5_drvr_spare[2:2] = 0x0 +# clkgrp7_div2_cfg5_drvr_mode[4:3] = 0x1 +# clkgrp7_div2_cfg_outbuf_dyn[5:5] = 0x0 +# clkgrp7_div2_cfg2_mutesel[7:6] = 0x0 +dut.write(0x152, 0xB) + diff --git a/artiq/firmware/libboard/hmc830_7043.rs b/artiq/firmware/libboard/hmc830_7043.rs new file mode 100644 index 000000000..3b6a2fae6 --- /dev/null +++ b/artiq/firmware/libboard/hmc830_7043.rs @@ -0,0 +1,151 @@ +/* + * HMC830 config: + * 100MHz input, 1GHz output + * fvco = (refclk / r_divider) * n_divider + * fout = fvco/2 + * + * HMC7043 config: + * dac clock: 1GHz (div=1) + * fpga clock: 250MHz (div=4) + * sysref clock: 15.625MHz (div=64) + */ + +mod hmc830 { + use csr; + + const HMC830_WRITES: [(u8, u32); 16] = [ + (0x0, 0x20), + (0x1, 0x2), + (0x2, 0x2), // r_divider + (0x5, 0x1628), + (0x5, 0x60a0), + (0x5, 0xe110), + (0x5, 0x2818), + (0x5, 0x0), + (0x6, 0x303ca), + (0x7, 0x14d), + (0x8, 0xc1beff), + (0x9, 0x153fff), + (0xa, 0x2046), + (0xb, 0x7c061), + (0xf, 0x81), + (0x3, 0x28), // n_divider + ]; + + fn spi_setup() { + unsafe { + csr::converter_spi::offline_write(1); + csr::converter_spi::cs_polarity_write(0); + csr::converter_spi::clk_polarity_write(0); + csr::converter_spi::clk_phase_write(0); + csr::converter_spi::lsb_first_write(0); + csr::converter_spi::half_duplex_write(0); + csr::converter_spi::clk_div_write_write(8); + csr::converter_spi::clk_div_read_write(8); + csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_HMC830_CS); + csr::converter_spi::offline_write(0); + } + } + + fn write(addr: u8, data: u32) { + let cmd = (0 << 6) | addr; + let val = ((cmd as u32) << 24) | data; + unsafe { + csr::converter_spi::xfer_len_write_write(32); + csr::converter_spi::xfer_len_read_write(0); + csr::converter_spi::data_write_write(val << (32-31)); + while csr::converter_spi::pending_read() != 0 {} + while csr::converter_spi::active_read() != 0 {} + } + } + + fn read(addr: u8) -> u32 { + let cmd = (1 << 6) | addr; + let val = (cmd as u32) << 24; + unsafe { + csr::converter_spi::xfer_len_write_write(7); + csr::converter_spi::xfer_len_read_write(25); + csr::converter_spi::data_write_write(val << (32-31)); + while csr::converter_spi::pending_read() != 0 {} + while csr::converter_spi::active_read() != 0 {} + csr::converter_spi::data_read_read() + } + } + + pub fn init() -> Result<(), &'static str> { + spi_setup(); + let id = read(0); + if id != 0xa7975 { + error!("invalid HMC830 ID: 0x{:08x}", id); + return Err("invalid HMC830 identification"); + } + for &(addr, data) in HMC830_WRITES.iter() { + write(addr, data); + } + Ok(()) + } +} + +mod hmc7043 { + use csr; + + include!(concat!(env!("OUT_DIR"), "/hmc7043_writes.rs")); + + fn spi_setup() { + unsafe { + csr::converter_spi::offline_write(1); + csr::converter_spi::cs_polarity_write(0); + csr::converter_spi::clk_polarity_write(0); + csr::converter_spi::clk_phase_write(0); + csr::converter_spi::lsb_first_write(0); + csr::converter_spi::half_duplex_write(1); + csr::converter_spi::clk_div_write_write(8); + csr::converter_spi::clk_div_read_write(8); + csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_HMC7043_CS); + csr::converter_spi::offline_write(0); + } + } + + fn write(addr: u16, data: u8) { + let cmd = (0 << 15) | addr; + let val = ((cmd as u32) << 8) | data as u32; + unsafe { + csr::converter_spi::xfer_len_write_write(24); + csr::converter_spi::xfer_len_read_write(0); + csr::converter_spi::data_write_write(val << (32-24)); + while csr::converter_spi::pending_read() != 0 {} + while csr::converter_spi::active_read() != 0 {} + } + } + + fn read(addr: u16) -> u8 { + let cmd = (0 << 15) | addr; + let val = (cmd as u32) << 8; + unsafe { + csr::converter_spi::xfer_len_write_write(16); + csr::converter_spi::xfer_len_read_write(8); + csr::converter_spi::data_write_write(val << (32-24)); + while csr::converter_spi::pending_read() != 0 {} + while csr::converter_spi::active_read() != 0 {} + csr::converter_spi::data_read_read() as u8 + } + } + + pub fn init() -> Result<(), &'static str> { + spi_setup(); + let id = (read(0x78) as u32) << 16 | (read(0x79) as u32) << 8 | read(0x7a) as u32; + if id != 0xf17904 { + error!("invalid HMC7043 ID: 0x{:08x}", id); + return Err("invalid HMC7043 identification"); + } + for &(addr, data) in HMC7043_WRITES.iter() { + write(addr, data); + } + Ok(()) + } +} + +pub fn init() -> Result<(), &'static str> { + hmc830::init()?; + hmc7043::init() +} diff --git a/artiq/firmware/libboard/lib.rs b/artiq/firmware/libboard/lib.rs index d0c6ecc77..738740273 100644 --- a/artiq/firmware/libboard/lib.rs +++ b/artiq/firmware/libboard/lib.rs @@ -25,11 +25,15 @@ pub mod spi; #[cfg(has_si5324)] pub mod si5324; +#[cfg(has_serwb_phy)] +pub mod serwb; #[cfg(has_ad9516)] #[allow(dead_code)] mod ad9516_reg; #[cfg(has_ad9516)] pub mod ad9516; +#[cfg(has_hmc830_7043)] +pub mod hmc830_7043; #[cfg(has_ad9154)] #[allow(dead_code)] mod ad9154_reg; diff --git a/artiq/firmware/libboard/serwb.rs b/artiq/firmware/libboard/serwb.rs new file mode 100644 index 000000000..ce7fbf2cf --- /dev/null +++ b/artiq/firmware/libboard/serwb.rs @@ -0,0 +1,18 @@ +use csr; + +pub fn wait_init() { + info!("waiting for AMC/RTM serwb bridge to be ready..."); + unsafe { + while csr::serwb_phy::control_ready_read() != 0 {} + } + info!("done."); + + // Try reading the identifier register on the other side of the bridge. + let rtm_identifier = unsafe { + csr::rtm_identifier::identifier_read() + }; + if rtm_identifier != 0x5352544d { + error!("incorrect RTM identifier: 0x{:08x}", rtm_identifier); + // proceed anyway + } +} diff --git a/artiq/firmware/runtime/lib.rs b/artiq/firmware/runtime/lib.rs index c9b9acb1f..6f8b9d170 100644 --- a/artiq/firmware/runtime/lib.rs +++ b/artiq/firmware/runtime/lib.rs @@ -62,6 +62,9 @@ fn startup() { info!("software version {}", include_str!(concat!(env!("OUT_DIR"), "/git-describe"))); info!("gateware version {}", board::ident(&mut [0; 64])); + #[cfg(has_serwb_phy)] + board::serwb::wait_init(); + let t = board::clock::get_ms(); info!("press 'e' to erase startup and idle kernels..."); while board::clock::get_ms() < t + 1000 { @@ -77,9 +80,11 @@ fn startup() { #[cfg(has_i2c)] board::i2c::init(); #[cfg(has_ad9516)] - board::ad9516::init().expect("cannot initialize ad9516"); + board::ad9516::init().expect("cannot initialize AD9516"); + #[cfg(has_hmc830_7043)] + board::hmc830_7043::init().expect("cannot initialize HMC830/7043"); #[cfg(has_ad9154)] - board::ad9154::init().expect("cannot initialize ad9154"); + board::ad9154::init().expect("cannot initialize AD9154"); let hardware_addr; match config::read_str("mac", |r| r?.parse()) { diff --git a/artiq/firmware/satman/lib.rs b/artiq/firmware/satman/lib.rs index 5ffb0d6a2..95fbb6e59 100644 --- a/artiq/firmware/satman/lib.rs +++ b/artiq/firmware/satman/lib.rs @@ -195,10 +195,15 @@ fn startup() { info!("software version {}", include_str!(concat!(env!("OUT_DIR"), "/git-describe"))); info!("gateware version {}", board::ident(&mut [0; 64])); + #[cfg(has_serwb_phy)] + board::serwb::wait_init(); + #[cfg(has_ad9516)] - board::ad9516::init().expect("cannot initialize ad9516"); + board::ad9516::init().expect("cannot initialize AD9516"); + #[cfg(has_hmc830_7043)] + board::hmc830_7043::init().expect("cannot initialize HMC830/7043"); board::i2c::init(); - board::si5324::setup(&SI5324_SETTINGS).expect("cannot initialize si5324"); + board::si5324::setup(&SI5324_SETTINGS).expect("cannot initialize Si5324"); loop { while !drtio_link_is_up() { diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index 819c2c8a1..5536565cd 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# Copyright (C) 2015 Robert Jordens import argparse import os @@ -11,16 +10,6 @@ from artiq import __artiq_dir__ as artiq_dir from artiq.frontend.bit2bin import bit2bin -def scripts_path(): - p = ["share", "openocd", "scripts"] - if os.name == "nt": - p.insert(0, "Library") - p = os.path.abspath(os.path.join( - os.path.dirname(shutil.which("openocd")), - "..", *p)) - return p - - def get_argparser(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, @@ -47,8 +36,8 @@ Prerequisites: """) parser.add_argument("-t", "--target", default="kc705", help="target board, default: %(default)s") - parser.add_argument("-m", "--adapter", default="nist_clock", - help="target adapter, default: %(default)s") + parser.add_argument("-m", "--adapter", default=None, + help="target adapter, default: board-dependent") parser.add_argument("--target-file", default=None, help="use alternative OpenOCD target file") parser.add_argument("-f", "--storage", help="write file to storage area") @@ -59,84 +48,210 @@ Prerequisites: return parser +def scripts_path(): + p = ["share", "openocd", "scripts"] + if os.name == "nt": + p.insert(0, "Library") + p = os.path.abspath(os.path.join( + os.path.dirname(shutil.which("openocd")), + "..", *p)) + return p + + +class Programmer: + def __init__(self, target_file): + self.target_file = target_file + self.prog = [] + + def load(self, bitfile): + raise NotImplementedError + + def proxy(self, proxy_bitfile): + raise NotImplementedError + + def flash_binary(self, flashno, address, filename): + raise NotImplementedError + + def start(self): + raise NotImplementedError + + def do(self): + self.prog.append("exit") + cmdline = [ + "openocd", + "-s", scripts_path() + ] + if self.target_file is not None: + cmdline += ["-f", self.target_file] + cmdline += ["-c", "; ".join(self.prog)] + subprocess.check_call(cmdline) + + +class ProgrammerJtagSpi7(Programmer): + def __init__(self, target_file): + Programmer.__init__(self, target_file) + self.prog.append("init") + + def load(self, bitfile): + self.prog.append("pld load 0 " + bitfile) + + def proxy(self, proxy_bitfile): + self.prog.append("jtagspi_init 0 {{{}}}".format(proxy_bitfile)) + + def flash_binary(self, flashno, address, filename): + # jtagspi_program supports only one flash + assert flashno == 0 + self.prog.append("jtagspi_program {{{}}} 0x{:x}".format( + filename, address)) + + def start(self): + self.prog.append("xc7_program xc7.tap") + + +class ProgrammerSayma(Programmer): + def __init__(self, target_file): + # TODO: use target_file + # TODO: support Sayma RTM + Programmer.__init__(self, None) + self.proxy_loaded = False + self.prog += [ + "interface ftdi", + "ftdi_device_desc \"Quad RS232-HS\"", + "ftdi_vid_pid 0x0403 0x6011", + "ftdi_channel 0", + # EN_USB_JTAG on ADBUS7: out, high + # nTRST on ADBUS4: out, high, but R46 is DNP + "ftdi_layout_init 0x0098 0x008b", + "ftdi_tdo_sample_edge falling", + "ftdi_layout_signal nSRST -data 0x0080", + "reset_config srst_only srst_pulls_trst srst_gates_jtag srst_push_pull", + + "adapter_khz 25000", + + "transport select jtag", + + "jtag newtap amc_xcu tap -irlen 6 -ignore-version -expected-id 0x03822093", + + "pld device virtex2 amc_xcu.tap 1", + + "set XILINX_USER1 0x02", + "set XILINX_USER2 0x03", + "set AMC_DR_LEN 1", + + "target create amc_xcu.proxy testee -chain-position amc_xcu.tap", + "flash bank amc_xcu.spi0 jtagspi 0 0 0 0 amc_xcu.proxy $XILINX_USER1 $AMC_DR_LEN", + "flash bank amc_xcu.spi1 jtagspi 0 0 0 0 amc_xcu.proxy $XILINX_USER2 $AMC_DR_LEN", + + "init" + ] + + def load(self, bitfile): + self.prog.append("pld load 0 " + bitfile) + + def proxy(self, proxy_bitfile): + self.prog += [ + "pld load 0 " + proxy_bitfile, + "reset halt" + ] + + def flash_binary(self, flashno, address, filename): + self.prog += [ + "flash probe amc_xcu.spi{}".format(flashno), + "irscan amc_xcu.tap $XILINX_USER{}".format(flashno+1), + "flash write_bank {} {} 0x{:x}".format(flashno, filename, address) + ] + + def start(self): + self.proxy_loaded = False + self.prog.append("xcu_program xcu.tap") + + def main(): parser = get_argparser() opts = parser.parse_args() config = { "kc705": { - "chip": "xc7k325t", - "start": "xc7_program xc7.tap", - "gateware": 0x000000, - "bios": 0xaf0000, - "runtime": 0xb00000, - "storage": 0xb80000, + "programmer_factory": ProgrammerJtagSpi7, + "proxy_bitfile": "bscan_spi_xc7k325t.bit", + "adapters": ["nist_clock", "nist_qc2"], + "gateware": (0, 0x000000), + "bios": (0, 0xaf0000), + "runtime": (0, 0xb00000), + "storage": (0, 0xb80000), + }, + "sayma": { + "programmer_factory": ProgrammerSayma, + "proxy_bitfile": "bscan_spi_xcku040_sayma.bit", + "adapters": [], + "gateware": (0, 0x000000), + "bios": (1, 0x000000), + "runtime": (1, 0x010000), + "storage": (1, 0x090000), }, }[opts.target] - if opts.dir is None: - opts.dir = os.path.join(artiq_dir, "binaries", - "{}-{}".format(opts.target, opts.adapter)) - if not os.path.exists(opts.dir) and opts.action != ["start"]: + adapter = opts.adapter + if adapter is not None and adapter not in config["adapters"]: + raise SystemExit("Invalid adapter for this board") + if adapter is None and config["adapters"]: + adapter = config["adapters"][0] + bin_dir = opts.dir + if bin_dir is None: + if adapter is None: + bin_dir = os.path.join(artiq_dir, "binaries", + "{}".format(opts.target)) + else: + bin_dir = os.path.join(artiq_dir, "binaries", + "{}-{}".format(opts.target, adapter)) + if not os.path.exists(bin_dir) and opts.action != ["start"]: raise SystemExit("Binaries directory '{}' does not exist" - .format(opts.dir)) + .format(bin_dir)) + + if opts.target_file is None: + target_file = os.path.join("board", opts.target + ".cfg") + else: + target_file = opts.target_file + programmer = config["programmer_factory"](target_file) conv = False - - prog = [] - prog.append("init") for action in opts.action: if action == "proxy": - proxy_base = "bscan_spi_{}.bit".format(config["chip"]) - proxy = None - for p in [opts.dir, os.path.expanduser("~/.migen"), + proxy_found = False + for p in [bin_dir, os.path.expanduser("~/.migen"), "/usr/local/share/migen", "/usr/share/migen"]: - proxy_ = os.path.join(p, proxy_base) - if os.access(proxy_, os.R_OK): - proxy = "jtagspi_init 0 {{{}}}".format(proxy_) + proxy_bitfile = os.path.join(p, config["proxy_bitfile"]) + if os.access(proxy_bitfile, os.R_OK): + programmer.proxy(proxy_bitfile) + proxy_found = True break - if not proxy: + if not proxy_found: raise SystemExit( - "proxy gateware bitstream {} not found".format(proxy_base)) - prog.append(proxy) + "proxy gateware bitstream {} not found".format(config["proxy_bitfile"])) elif action == "gateware": - bin = os.path.join(opts.dir, "top.bin") + bin = os.path.join(bin_dir, "top.bin") if not os.access(bin, os.R_OK): bin_handle, bin = tempfile.mkstemp() - bit = os.path.join(opts.dir, "top.bit") + bit = os.path.join(bin_dir, "top.bit") conv = True - prog.append("jtagspi_program {{{}}} 0x{:x}".format( - bin, config["gateware"])) + programmer.flash_binary(*config["gateware"], bin) elif action == "bios": - prog.append("jtagspi_program {{{}}} 0x{:x}".format( - os.path.join(opts.dir, "bios.bin"), config["bios"])) + programmer.flash_binary(*config["bios"], os.path.join(bin_dir, "bios.bin")) elif action == "runtime": - prog.append("jtagspi_program {{{}}} 0x{:x}".format( - os.path.join(opts.dir, "runtime.fbi"), config["runtime"])) + programmer.flash_binary(*config["runtime"], os.path.join(bin_dir, "runtime.fbi")) elif action == "storage": - prog.append("jtagspi_program {{{}}} 0x{:x}".format( - opts.storage, config["storage"])) + programmer.flash_binary(*config["storage"], opts.storage) elif action == "load": - prog.append("pld load 0 {{{}}}".format( - os.path.join(opts.dir, "top.bit"))) + programmer.load(os.path.join(bin_dir, "top.bit")) elif action == "start": - prog.append(config["start"]) + programmer.start() else: raise ValueError("invalid action", action) - prog.append("exit") + + if conv: + bit2bin(bit, bin_handle) try: - if conv: - bit2bin(bit, bin_handle) - if opts.target_file is None: - target_file = os.path.join("board", opts.target + ".cfg") - else: - target_file = opts.target_file - subprocess.check_call([ - "openocd", - "-s", scripts_path(), - "-f", target_file, - "-c", "; ".join(prog), - ]) + programmer.do() finally: if conv: os.unlink(bin) diff --git a/artiq/gateware/remote_csr.py b/artiq/gateware/remote_csr.py new file mode 100644 index 000000000..7acbba798 --- /dev/null +++ b/artiq/gateware/remote_csr.py @@ -0,0 +1,42 @@ +from collections import OrderedDict +from operator import itemgetter +import csv + +from misoc.interconnect.csr import CSRStatus, CSRStorage + + +def _get_csr_data(csv_file): + csr_data = OrderedDict() + with open(csv_file) as csv_file_f: + csv_reader = csv.reader(csv_file_f) + for name, address, length, ro in csv_reader: + region_name, csr_name = name.split(".") + address = int(address, 0) + length = int(length, 0) + ro = ro == "ro" + if region_name not in csr_data: + csr_data[region_name] = [] + csr_data[region_name].append((csr_name, address, length, ro)) + return csr_data + + +def get_remote_csr_regions(offset, csv_file): + busword = 32 + regions = [] + for region_name, csrs_info in _get_csr_data(csv_file).items(): + csrs_info = sorted(csrs_info, key=itemgetter(1)) + origin = csrs_info[0][1] + next_address = origin + csrs = [] + for csr_name, address, length, ro in csrs_info: + if address != next_address: + raise ValueError("CSRs are not contiguous") + nr = (length + busword - 1)//busword + next_address += nr*busword//8 + if ro: + csr = CSRStatus(length, name=csr_name) + else: + csr = CSRStorage(length, name=csr_name) + csrs.append(csr) + regions.append((region_name, offset + origin, busword, csrs)) + return regions diff --git a/artiq/gateware/serwb/__init__.py b/artiq/gateware/serwb/__init__.py new file mode 100644 index 000000000..3ebf3f028 --- /dev/null +++ b/artiq/gateware/serwb/__init__.py @@ -0,0 +1 @@ +from artiq.gateware.serwb import s7phy, kusphy, phy, core, packet, etherbone diff --git a/artiq/gateware/serwb/core.py b/artiq/gateware/serwb/core.py new file mode 100644 index 000000000..4435c3e40 --- /dev/null +++ b/artiq/gateware/serwb/core.py @@ -0,0 +1,37 @@ +from migen import * + +from misoc.interconnect import stream + +from artiq.gateware.serwb.packet import Depacketizer, Packetizer +from artiq.gateware.serwb.etherbone import Etherbone + + +class SERWBCore(Module): + def __init__(self, phy, clk_freq, mode): + self.submodules.etherbone = etherbone = Etherbone(mode) + depacketizer = Depacketizer(clk_freq) + packetizer = Packetizer() + self.submodules += depacketizer, packetizer + tx_cdc = stream.AsyncFIFO([("data", 32)], 8) + tx_cdc = ClockDomainsRenamer({"write": "sys", "read": "serwb_serdes"})(tx_cdc) + self.submodules += tx_cdc + rx_cdc = stream.AsyncFIFO([("data", 32)], 8) + rx_cdc = ClockDomainsRenamer({"write": "serwb_serdes", "read": "sys"})(rx_cdc) + self.submodules += rx_cdc + self.comb += [ + # core <--> etherbone + depacketizer.source.connect(etherbone.sink), + etherbone.source.connect(packetizer.sink), + + # core --> serdes + packetizer.source.connect(tx_cdc.sink), + If(tx_cdc.source.stb & phy.init.ready, + phy.serdes.tx_data.eq(tx_cdc.source.data) + ), + tx_cdc.source.ack.eq(phy.init.ready), + + # serdes --> core + rx_cdc.sink.stb.eq(phy.init.ready), + rx_cdc.sink.data.eq(phy.serdes.rx_data), + rx_cdc.source.connect(depacketizer.sink), + ] diff --git a/artiq/gateware/serwb/etherbone.py b/artiq/gateware/serwb/etherbone.py new file mode 100644 index 000000000..231299bed --- /dev/null +++ b/artiq/gateware/serwb/etherbone.py @@ -0,0 +1,740 @@ +""" +Etherbone + +CERN's Etherbone protocol is initially used to run a Wishbone bus over an +ethernet network. This re-implementation is meant to be run over serdes +and introduces some limitations: +- no probing (pf/pr) +- no address spaces (rca/bca/wca/wff) +- 32bits data and address +- 1 record per frame +""" + +from migen import * + +from misoc.interconnect import stream +from misoc.interconnect import wishbone + +from artiq.gateware.serwb.packet import * + + +class _Packetizer(Module): + def __init__(self, sink_description, source_description, header): + self.sink = sink = stream.Endpoint(sink_description) + self.source = source = stream.Endpoint(source_description) + self.header = Signal(header.length*8) + + # # # + + dw = len(self.sink.data) + + header_reg = Signal(header.length*8, reset_less=True) + header_words = (header.length*8)//dw + load = Signal() + shift = Signal() + counter = Signal(max=max(header_words, 2)) + counter_reset = Signal() + counter_ce = Signal() + self.sync += \ + If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + 1) + ) + + self.comb += header.encode(sink, self.header) + if header_words == 1: + self.sync += [ + If(load, + header_reg.eq(self.header) + ) + ] + else: + self.sync += [ + If(load, + header_reg.eq(self.header) + ).Elif(shift, + header_reg.eq(Cat(header_reg[dw:], Signal(dw))) + ) + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + if header_words == 1: + idle_next_state = "COPY" + else: + idle_next_state = "SEND_HEADER" + + fsm.act("IDLE", + sink.ack.eq(1), + counter_reset.eq(1), + If(sink.stb, + sink.ack.eq(0), + source.stb.eq(1), + source.eop.eq(0), + source.data.eq(self.header[:dw]), + If(source.stb & source.ack, + load.eq(1), + NextState(idle_next_state) + ) + ) + ) + if header_words != 1: + fsm.act("SEND_HEADER", + source.stb.eq(1), + source.eop.eq(0), + source.data.eq(header_reg[dw:2*dw]), + If(source.stb & source.ack, + shift.eq(1), + counter_ce.eq(1), + If(counter == header_words-2, + NextState("COPY") + ) + ) + ) + if hasattr(sink, "error"): + self.comb += source.error.eq(sink.error) + fsm.act("COPY", + source.stb.eq(sink.stb), + source.eop.eq(sink.eop), + source.data.eq(sink.data), + If(source.stb & source.ack, + sink.ack.eq(1), + If(source.eop, + NextState("IDLE") + ) + ) + ) + + +class _Depacketizer(Module): + def __init__(self, sink_description, source_description, header): + self.sink = sink = stream.Endpoint(sink_description) + self.source = source = stream.Endpoint(source_description) + self.header = Signal(header.length*8) + + # # # + + dw = len(sink.data) + + header_reg = Signal(header.length*8, reset_less=True) + header_words = (header.length*8)//dw + + shift = Signal() + counter = Signal(max=max(header_words, 2)) + counter_reset = Signal() + counter_ce = Signal() + self.sync += \ + If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + 1) + ) + + if header_words == 1: + self.sync += \ + If(shift, + header_reg.eq(sink.data) + ) + else: + self.sync += \ + If(shift, + header_reg.eq(Cat(header_reg[dw:], sink.data)) + ) + self.comb += self.header.eq(header_reg) + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + if header_words == 1: + idle_next_state = "COPY" + else: + idle_next_state = "RECEIVE_HEADER" + + fsm.act("IDLE", + sink.ack.eq(1), + counter_reset.eq(1), + If(sink.stb, + shift.eq(1), + NextState(idle_next_state) + ) + ) + if header_words != 1: + fsm.act("RECEIVE_HEADER", + sink.ack.eq(1), + If(sink.stb, + counter_ce.eq(1), + shift.eq(1), + If(counter == header_words-2, + NextState("COPY") + ) + ) + ) + no_payload = Signal() + self.sync += \ + If(fsm.before_entering("COPY"), + no_payload.eq(sink.eop) + ) + + if hasattr(sink, "error"): + self.comb += source.error.eq(sink.error) + self.comb += [ + source.eop.eq(sink.eop | no_payload), + source.data.eq(sink.data), + header.decode(self.header, source) + ] + fsm.act("COPY", + sink.ack.eq(source.ack), + source.stb.eq(sink.stb | no_payload), + If(source.stb & source.ack & source.eop, + NextState("IDLE") + ) + ) + + +etherbone_magic = 0x4e6f +etherbone_version = 1 +etherbone_packet_header_length = 8 +etherbone_packet_header_fields = { + "magic": HeaderField(0, 0, 16), + + "version": HeaderField(2, 4, 4), + "nr": HeaderField(2, 2, 1), + "pr": HeaderField(2, 1, 1), # unused + "pf": HeaderField(2, 0, 1), # unused + + "addr_size": HeaderField(3, 4, 4), # static + "port_size": HeaderField(3, 0, 4) # static +} +etherbone_packet_header = Header(etherbone_packet_header_fields, + etherbone_packet_header_length, + swap_field_bytes=True) + +etherbone_record_header_length = 4 +etherbone_record_header_fields = { + "bca": HeaderField(0, 0, 1), # unused + "rca": HeaderField(0, 1, 1), # unused + "rff": HeaderField(0, 2, 1), # unused + "cyc": HeaderField(0, 4, 1), # unused + "wca": HeaderField(0, 5, 1), # unused + "wff": HeaderField(0, 6, 1), # unused + + "byte_enable": HeaderField(1, 0, 8), + + "wcount": HeaderField(2, 0, 8), + + "rcount": HeaderField(3, 0, 8) +} +etherbone_record_header = Header(etherbone_record_header_fields, + etherbone_record_header_length, + swap_field_bytes=True) + +def _remove_from_layout(layout, *args): + r = [] + for f in layout: + remove = False + for arg in args: + if f[0] == arg: + remove = True + if not remove: + r.append(f) + return r + +def etherbone_packet_description(dw): + layout = etherbone_packet_header.get_layout() + layout += [("data", dw)] + return stream.EndpointDescription(layout) + +def etherbone_packet_user_description(dw): + layout = etherbone_packet_header.get_layout() + layout = _remove_from_layout(layout, + "magic", + "portsize", + "addrsize", + "version") + layout += user_description(dw).payload_layout + return stream.EndpointDescription(layout) + +def etherbone_record_description(dw): + layout = etherbone_record_header.get_layout() + layout += [("data", dw)] + return stream.EndpointDescription(layout) + +def etherbone_mmap_description(dw): + layout = [ + ("we", 1), + ("count", 8), + ("base_addr", 32), + ("be", dw//8), + ("addr", 32), + ("data", dw) + ] + return stream.EndpointDescription(layout) + + +# etherbone packet + +class _EtherbonePacketPacketizer(_Packetizer): + def __init__(self): + _Packetizer.__init__(self, + etherbone_packet_description(32), + user_description(32), + etherbone_packet_header) + + +class _EtherbonePacketTX(Module): + def __init__(self): + self.sink = sink = stream.Endpoint(etherbone_packet_user_description(32)) + self.source = source = stream.Endpoint(user_description(32)) + + # # # + + self.submodules.packetizer = packetizer = _EtherbonePacketPacketizer() + self.comb += [ + packetizer.sink.stb.eq(sink.stb), + packetizer.sink.eop.eq(sink.eop), + sink.ack.eq(packetizer.sink.ack), + + packetizer.sink.magic.eq(etherbone_magic), + packetizer.sink.port_size.eq(32//8), + packetizer.sink.addr_size.eq(32//8), + packetizer.sink.nr.eq(sink.nr), + packetizer.sink.version.eq(etherbone_version), + + packetizer.sink.data.eq(sink.data) + ] + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + packetizer.source.ack.eq(1), + If(packetizer.source.stb, + packetizer.source.ack.eq(0), + NextState("SEND") + ) + ) + fsm.act("SEND", + packetizer.source.connect(source), + source.length.eq(sink.length + etherbone_packet_header.length), + If(source.stb & source.eop & source.ack, + NextState("IDLE") + ) + ) + + +class _EtherbonePacketDepacketizer(_Depacketizer): + def __init__(self): + _Depacketizer.__init__(self, + user_description(32), + etherbone_packet_description(32), + etherbone_packet_header) + + +class _EtherbonePacketRX(Module): + def __init__(self): + self.sink = sink = stream.Endpoint(user_description(32)) + self.source = source = stream.Endpoint(etherbone_packet_user_description(32)) + + # # # + + self.submodules.depacketizer = depacketizer = _EtherbonePacketDepacketizer() + self.comb += sink.connect(depacketizer.sink) + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + depacketizer.source.ack.eq(1), + If(depacketizer.source.stb, + depacketizer.source.ack.eq(0), + NextState("CHECK") + ) + ) + stb = Signal() + self.sync += stb.eq( + depacketizer.source.stb & + (depacketizer.source.magic == etherbone_magic) + ) + fsm.act("CHECK", + If(stb, + NextState("PRESENT") + ).Else( + NextState("DROP") + ) + ) + self.comb += [ + source.eop.eq(depacketizer.source.eop), + + source.nr.eq(depacketizer.source.nr), + + source.data.eq(depacketizer.source.data), + + source.length.eq(sink.length - etherbone_packet_header.length) + ] + fsm.act("PRESENT", + source.stb.eq(depacketizer.source.stb), + depacketizer.source.ack.eq(source.ack), + If(source.stb & source.eop & source.ack, + NextState("IDLE") + ) + ) + fsm.act("DROP", + depacketizer.source.ack.eq(1), + If(depacketizer.source.stb & + depacketizer.source.eop & + depacketizer.source.ack, + NextState("IDLE") + ) + ) + + +class _EtherbonePacket(Module): + def __init__(self, port_sink, port_source): + self.submodules.tx = tx = _EtherbonePacketTX() + self.submodules.rx = rx = _EtherbonePacketRX() + self.comb += [ + tx.source.connect(port_sink), + port_source.connect(rx.sink) + ] + self.sink, self.source = self.tx.sink, self.rx.source + +# etherbone record + +class _EtherboneRecordPacketizer(_Packetizer): + def __init__(self): + _Packetizer.__init__(self, + etherbone_record_description(32), + etherbone_packet_user_description(32), + etherbone_record_header) + + +class _EtherboneRecordDepacketizer(_Depacketizer): + def __init__(self): + _Depacketizer.__init__(self, + etherbone_packet_user_description(32), + etherbone_record_description(32), + etherbone_record_header) + + +class _EtherboneRecordReceiver(Module): + def __init__(self, buffer_depth=256): + self.sink = sink = stream.Endpoint(etherbone_record_description(32)) + self.source = source = stream.Endpoint(etherbone_mmap_description(32)) + + # # # + + fifo = stream.SyncFIFO(etherbone_record_description(32), buffer_depth, + buffered=True) + self.submodules += fifo + self.comb += sink.connect(fifo.sink) + + base_addr = Signal(32) + base_addr_update = Signal() + self.sync += If(base_addr_update, base_addr.eq(fifo.source.data)) + + counter = Signal(max=512) + counter_reset = Signal() + counter_ce = Signal() + self.sync += \ + If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + 1) + ) + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + fifo.source.ack.eq(1), + counter_reset.eq(1), + If(fifo.source.stb, + base_addr_update.eq(1), + If(fifo.source.wcount, + NextState("RECEIVE_WRITES") + ).Elif(fifo.source.rcount, + NextState("RECEIVE_READS") + + ) + ) + ) + fsm.act("RECEIVE_WRITES", + source.stb.eq(fifo.source.stb), + source.eop.eq(counter == fifo.source.wcount-1), + source.count.eq(fifo.source.wcount), + source.be.eq(fifo.source.byte_enable), + source.addr.eq(base_addr[2:] + counter), + source.we.eq(1), + source.data.eq(fifo.source.data), + fifo.source.ack.eq(source.ack), + If(source.stb & source.ack, + counter_ce.eq(1), + If(source.eop, + If(fifo.source.rcount, + NextState("RECEIVE_BASE_RET_ADDR") + ).Else( + NextState("IDLE") + ) + ) + ) + ) + fsm.act("RECEIVE_BASE_RET_ADDR", + counter_reset.eq(1), + If(fifo.source.stb, + base_addr_update.eq(1), + NextState("RECEIVE_READS") + ) + ) + fsm.act("RECEIVE_READS", + source.stb.eq(fifo.source.stb), + source.eop.eq(counter == fifo.source.rcount-1), + source.count.eq(fifo.source.rcount), + source.base_addr.eq(base_addr), + source.addr.eq(fifo.source.data[2:]), + fifo.source.ack.eq(source.ack), + If(source.stb & source.ack, + counter_ce.eq(1), + If(source.eop, + NextState("IDLE") + ) + ) + ) + + +class _EtherboneRecordSender(Module): + def __init__(self, buffer_depth=256): + self.sink = sink = stream.Endpoint(etherbone_mmap_description(32)) + self.source = source = stream.Endpoint(etherbone_record_description(32)) + + # # # + + pbuffer = stream.SyncFIFO(etherbone_mmap_description(32), buffer_depth) + self.submodules += pbuffer + self.comb += sink.connect(pbuffer.sink) + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + pbuffer.source.ack.eq(1), + If(pbuffer.source.stb, + pbuffer.source.ack.eq(0), + NextState("SEND_BASE_ADDRESS") + ) + ) + self.comb += [ + source.byte_enable.eq(pbuffer.source.be), + If(pbuffer.source.we, + source.wcount.eq(pbuffer.source.count) + ).Else( + source.rcount.eq(pbuffer.source.count) + ) + ] + + fsm.act("SEND_BASE_ADDRESS", + source.stb.eq(pbuffer.source.stb), + source.eop.eq(0), + source.data.eq(pbuffer.source.base_addr), + If(source.ack, + NextState("SEND_DATA") + ) + ) + fsm.act("SEND_DATA", + source.stb.eq(pbuffer.source.stb), + source.eop.eq(pbuffer.source.eop), + source.data.eq(pbuffer.source.data), + If(source.stb & source.ack, + pbuffer.source.ack.eq(1), + If(source.eop, + NextState("IDLE") + ) + ) + ) + + +class _EtherboneRecord(Module): + def __init__(self): + self.sink = sink = stream.Endpoint(etherbone_packet_user_description(32)) + self.source = source = stream.Endpoint(etherbone_packet_user_description(32)) + + # # # + + # receive record, decode it and generate mmap stream + self.submodules.depacketizer = depacketizer = _EtherboneRecordDepacketizer() + self.submodules.receiver = receiver = _EtherboneRecordReceiver() + self.comb += [ + sink.connect(depacketizer.sink), + depacketizer.source.connect(receiver.sink) + ] + + # receive mmap stream, encode it and send records + self.submodules.sender = sender = _EtherboneRecordSender() + self.submodules.packetizer = packetizer = _EtherboneRecordPacketizer() + self.comb += [ + sender.source.connect(packetizer.sink), + packetizer.source.connect(source), + source.length.eq(etherbone_record_header.length + + (sender.source.wcount != 0)*4 + sender.source.wcount*4 + + (sender.source.rcount != 0)*4 + sender.source.rcount*4) + ] + + +# etherbone wishbone + +class _EtherboneWishboneMaster(Module): + def __init__(self): + self.sink = sink = stream.Endpoint(etherbone_mmap_description(32)) + self.source = source = stream.Endpoint(etherbone_mmap_description(32)) + self.bus = bus = wishbone.Interface() + + # # # + + data = Signal(32) + data_update = Signal() + self.sync += If(data_update, data.eq(bus.dat_r)) + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + sink.ack.eq(1), + If(sink.stb, + sink.ack.eq(0), + If(sink.we, + NextState("WRITE_DATA") + ).Else( + NextState("READ_DATA") + ) + ) + ) + fsm.act("WRITE_DATA", + bus.adr.eq(sink.addr), + bus.dat_w.eq(sink.data), + bus.sel.eq(sink.be), + bus.stb.eq(sink.stb), + bus.we.eq(1), + bus.cyc.eq(1), + If(bus.stb & bus.ack, + sink.ack.eq(1), + If(sink.eop, + NextState("IDLE") + ) + ) + ) + fsm.act("READ_DATA", + bus.adr.eq(sink.addr), + bus.sel.eq(sink.be), + bus.stb.eq(sink.stb), + bus.cyc.eq(1), + If(bus.stb & bus.ack, + data_update.eq(1), + NextState("SEND_DATA") + ) + ) + fsm.act("SEND_DATA", + source.stb.eq(sink.stb), + source.eop.eq(sink.eop), + source.base_addr.eq(sink.base_addr), + source.addr.eq(sink.addr), + source.count.eq(sink.count), + source.be.eq(sink.be), + source.we.eq(1), + source.data.eq(data), + If(source.stb & source.ack, + sink.ack.eq(1), + If(source.eop, + NextState("IDLE") + ).Else( + NextState("READ_DATA") + ) + ) + ) + + +class _EtherboneWishboneSlave(Module): + def __init__(self): + self.bus = bus = wishbone.Interface() + self.ready = Signal(reset=1) + self.sink = sink = stream.Endpoint(etherbone_mmap_description(32)) + self.source = source = stream.Endpoint(etherbone_mmap_description(32)) + + # # # + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + sink.ack.eq(1), + If(bus.stb & bus.cyc, + If(self.ready, + If(bus.we, + NextState("SEND_WRITE") + ).Else( + NextState("SEND_READ") + ) + ).Else( + NextState("SEND_ERROR") + ) + ) + ) + fsm.act("SEND_WRITE", + If(~self.ready, + NextState("SEND_ERROR") + ).Else( + source.stb.eq(1), + source.eop.eq(1), + source.base_addr[2:].eq(bus.adr), + source.count.eq(1), + source.be.eq(bus.sel), + source.we.eq(1), + source.data.eq(bus.dat_w), + If(source.stb & source.ack, + bus.ack.eq(1), + NextState("IDLE") + ) + ) + ) + fsm.act("SEND_READ", + If(~self.ready, + NextState("SEND_ERROR") + ).Else( + source.stb.eq(1), + source.eop.eq(1), + source.base_addr.eq(0), + source.count.eq(1), + source.be.eq(bus.sel), + source.we.eq(0), + source.data[2:].eq(bus.adr), + If(source.stb & source.ack, + NextState("WAIT_READ") + ) + ) + ) + fsm.act("WAIT_READ", + sink.ack.eq(1), + If(~self.ready, + NextState("SEND_ERROR") + ).Elif(sink.stb & sink.we, + bus.ack.eq(1), + bus.dat_r.eq(sink.data), + NextState("IDLE") + ) + ) + fsm.act("SEND_ERROR", + bus.ack.eq(1), + bus.err.eq(1) + ) + +# etherbone + +class Etherbone(Module): + def __init__(self, mode="master"): + self.sink = sink = stream.Endpoint(user_description(32)) + self.source = source = stream.Endpoint(user_description(32)) + + # # # + + self.submodules.packet = _EtherbonePacket(source, sink) + self.submodules.record = _EtherboneRecord() + if mode == "master": + self.submodules.wishbone = _EtherboneWishboneMaster() + elif mode == "slave": + self.submodules.wishbone = _EtherboneWishboneSlave() + else: + raise ValueError + + self.comb += [ + self.packet.source.connect(self.record.sink), + self.record.source.connect(self.packet.sink), + self.record.receiver.source.connect(self.wishbone.sink), + self.wishbone.source.connect(self.record.sender.sink) + ] diff --git a/artiq/gateware/serwb/kusphy.py b/artiq/gateware/serwb/kusphy.py new file mode 100644 index 000000000..74cb16fbf --- /dev/null +++ b/artiq/gateware/serwb/kusphy.py @@ -0,0 +1,224 @@ +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer +from migen.genlib.cdc import MultiReg, PulseSynchronizer, Gearbox +from migen.genlib.misc import BitSlip + +from misoc.cores.code_8b10b import Encoder, Decoder + + +class KUSSerdes(Module): + def __init__(self, pll, pads, mode="master"): + self.tx_data = Signal(32) + self.rx_data = Signal(32) + + self.tx_idle = Signal() + self.tx_comma = Signal() + self.rx_idle = Signal() + self.rx_comma = Signal() + + self.rx_bitslip_value = Signal(6) + self.rx_delay_rst = Signal() + self.rx_delay_inc = Signal() + self.rx_delay_ce = Signal() + self.rx_delay_en_vtc = Signal() + + # # # + + self.submodules.encoder = ClockDomainsRenamer("serwb_serdes")( + Encoder(4, True)) + self.decoders = [ClockDomainsRenamer("serwb_serdes")( + Decoder(True)) for _ in range(4)] + self.submodules += self.decoders + + # clocking: + + # In master mode: + # - linerate/10 pll refclk provided by user + # - linerate/10 slave refclk generated on clk_pads + # In Slave mode: + # - linerate/10 pll refclk provided by clk_pads + self.clock_domains.cd_serwb_serdes = ClockDomain() + self.clock_domains.cd_serwb_serdes_5x = ClockDomain() + self.clock_domains.cd_serwb_serdes_20x = ClockDomain(reset_less=True) + self.comb += [ + self.cd_serwb_serdes.clk.eq(pll.serwb_serdes_clk), + self.cd_serwb_serdes_5x.clk.eq(pll.serwb_serdes_5x_clk), + self.cd_serwb_serdes_20x.clk.eq(pll.serwb_serdes_20x_clk) + ] + self.specials += AsyncResetSynchronizer(self.cd_serwb_serdes, ~pll.lock) + self.comb += self.cd_serwb_serdes_5x.rst.eq(self.cd_serwb_serdes.rst) + + # control/status cdc + tx_idle = Signal() + tx_comma = Signal() + rx_idle = Signal() + rx_comma = Signal() + rx_bitslip_value = Signal(6) + rx_delay_rst = Signal() + rx_delay_inc = Signal() + rx_delay_en_vtc = Signal() + rx_delay_ce = Signal() + self.specials += [ + MultiReg(self.tx_idle, tx_idle, "serwb_serdes"), + MultiReg(self.tx_comma, tx_comma, "serwb_serdes"), + MultiReg(rx_idle, self.rx_idle, "sys"), + MultiReg(rx_comma, self.rx_comma, "sys"), + MultiReg(self.rx_bitslip_value, rx_bitslip_value, "serwb_serdes"), + MultiReg(self.rx_delay_inc, rx_delay_inc, "serwb_serdes_5x"), + MultiReg(self.rx_delay_en_vtc, rx_delay_en_vtc, "serwb_serdes_5x") + ] + self.submodules.do_rx_delay_rst = PulseSynchronizer("sys", "serwb_serdes_5x") + self.comb += [ + rx_delay_rst.eq(self.do_rx_delay_rst.o), + self.do_rx_delay_rst.i.eq(self.rx_delay_rst) + ] + self.submodules.do_rx_delay_ce = PulseSynchronizer("sys", "serwb_serdes_5x") + self.comb += [ + rx_delay_ce.eq(self.do_rx_delay_ce.o), + self.do_rx_delay_ce.i.eq(self.rx_delay_ce) + ] + + # tx clock (linerate/10) + if mode == "master": + self.submodules.tx_clk_gearbox = Gearbox(40, "serwb_serdes", 8, "serwb_serdes_5x") + self.comb += self.tx_clk_gearbox.i.eq((0b1111100000 << 30) | + (0b1111100000 << 20) | + (0b1111100000 << 10) | + (0b1111100000 << 0)) + clk_o = Signal() + self.specials += [ + Instance("OSERDESE3", + p_DATA_WIDTH=8, p_INIT=0, + p_IS_CLK_INVERTED=0, p_IS_CLKDIV_INVERTED=0, p_IS_RST_INVERTED=0, + + o_OQ=clk_o, + i_RST=ResetSignal("serwb_serdes"), + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLKDIV=ClockSignal("serwb_serdes_5x"), + i_D=self.tx_clk_gearbox.o + ), + Instance("OBUFDS", + i_I=clk_o, + o_O=pads.clk_p, + o_OB=pads.clk_n + ) + ] + + # tx datapath + # tx_data -> encoders -> gearbox -> serdes + self.submodules.tx_gearbox = Gearbox(40, "serwb_serdes", 8, "serwb_serdes_5x") + self.comb += [ + If(tx_comma, + self.encoder.k[0].eq(1), + self.encoder.d[0].eq(0xbc) + ).Else( + self.encoder.d[0].eq(self.tx_data[0:8]), + self.encoder.d[1].eq(self.tx_data[8:16]), + self.encoder.d[2].eq(self.tx_data[16:24]), + self.encoder.d[3].eq(self.tx_data[24:32]) + ) + ] + self.sync.serwb_serdes += \ + If(tx_idle, + self.tx_gearbox.i.eq(0) + ).Else( + self.tx_gearbox.i.eq(Cat(*[self.encoder.output[i] for i in range(4)])) + ) + + serdes_o = Signal() + self.specials += [ + Instance("OSERDESE3", + p_DATA_WIDTH=8, p_INIT=0, + p_IS_CLK_INVERTED=0, p_IS_CLKDIV_INVERTED=0, p_IS_RST_INVERTED=0, + + o_OQ=serdes_o, + i_RST=ResetSignal("serwb_serdes"), + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLKDIV=ClockSignal("serwb_serdes_5x"), + i_D=self.tx_gearbox.o + ), + Instance("OBUFDS", + i_I=serdes_o, + o_O=pads.tx_p, + o_OB=pads.tx_n + ) + ] + + # rx clock + use_bufr = True + if mode == "slave": + clk_i = Signal() + clk_i_bufg = Signal() + self.specials += [ + Instance("IBUFDS", + i_I=pads.clk_p, + i_IB=pads.clk_n, + o_O=clk_i + ) + ] + if use_bufr: + clk_i_bufr = Signal() + self.specials += [ + Instance("BUFR", i_I=clk_i, o_O=clk_i_bufr), + Instance("BUFG", i_I=clk_i_bufr, o_O=clk_i_bufg) + ] + else: + self.specials += Instance("BUFG", i_I=clk_i, o_O=clk_i_bufg) + self.comb += pll.refclk.eq(clk_i_bufg) + + # rx datapath + # serdes -> gearbox -> bitslip -> decoders -> rx_data + self.submodules.rx_gearbox = Gearbox(8, "serwb_serdes_5x", 40, "serwb_serdes") + self.submodules.rx_bitslip = ClockDomainsRenamer("serwb_serdes")(BitSlip(40)) + + serdes_i_nodelay = Signal() + self.specials += [ + Instance("IBUFDS_DIFF_OUT", + i_I=pads.rx_p, + i_IB=pads.rx_n, + o_O=serdes_i_nodelay + ) + ] + + serdes_i_delayed = Signal() + serdes_q = Signal(8) + self.specials += [ + Instance("IDELAYE3", + p_CASCADE="NONE", p_UPDATE_MODE="ASYNC", p_REFCLK_FREQUENCY=200.0, + p_IS_CLK_INVERTED=0, p_IS_RST_INVERTED=0, + p_DELAY_FORMAT="COUNT", p_DELAY_SRC="IDATAIN", + p_DELAY_TYPE="VARIABLE", p_DELAY_VALUE=0, + + i_CLK=ClockSignal("serwb_serdes_5x"), + i_RST=rx_delay_rst, i_LOAD=0, + i_INC=rx_delay_inc, i_EN_VTC=rx_delay_en_vtc, + i_CE=rx_delay_ce, + + i_IDATAIN=serdes_i_nodelay, o_DATAOUT=serdes_i_delayed + ), + Instance("ISERDESE3", + p_DATA_WIDTH=8, + + i_D=serdes_i_delayed, + i_RST=ResetSignal("serwb_serdes"), + i_FIFO_RD_CLK=0, i_FIFO_RD_EN=0, + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLK_B=~ClockSignal("serwb_serdes_20x"), + i_CLKDIV=ClockSignal("serwb_serdes_5x"), + o_Q=serdes_q + ) + ] + + self.comb += [ + self.rx_gearbox.i.eq(serdes_q), + self.rx_bitslip.value.eq(rx_bitslip_value), + self.rx_bitslip.i.eq(self.rx_gearbox.o), + self.decoders[0].input.eq(self.rx_bitslip.o[0:10]), + self.decoders[1].input.eq(self.rx_bitslip.o[10:20]), + self.decoders[2].input.eq(self.rx_bitslip.o[20:30]), + self.decoders[3].input.eq(self.rx_bitslip.o[30:40]), + self.rx_data.eq(Cat(*[self.decoders[i].d for i in range(4)])), + rx_idle.eq(self.rx_bitslip.o == 0), + rx_comma.eq(((self.decoders[0].d == 0xbc) & (self.decoders[0].k == 1)) & + ((self.decoders[1].d == 0x00) & (self.decoders[1].k == 0)) & + ((self.decoders[2].d == 0x00) & (self.decoders[2].k == 0)) & + ((self.decoders[3].d == 0x00) & (self.decoders[3].k == 0))) + + ] diff --git a/artiq/gateware/serwb/packet.py b/artiq/gateware/serwb/packet.py new file mode 100644 index 000000000..a1d2b00fb --- /dev/null +++ b/artiq/gateware/serwb/packet.py @@ -0,0 +1,175 @@ +from math import ceil +from copy import copy +from collections import OrderedDict + +from migen import * +from migen.genlib.misc import WaitTimer + +from misoc.interconnect import stream +from misoc.interconnect.stream import EndpointDescription + + +def reverse_bytes(signal): + n = ceil(len(signal)/8) + return Cat(iter([signal[i*8:(i+1)*8] for i in reversed(range(n))])) + + +class HeaderField: + def __init__(self, byte, offset, width): + self.byte = byte + self.offset = offset + self.width = width + + +class Header: + def __init__(self, fields, length, swap_field_bytes=True): + self.fields = fields + self.length = length + self.swap_field_bytes = swap_field_bytes + + def get_layout(self): + layout = [] + for k, v in sorted(self.fields.items()): + layout.append((k, v.width)) + return layout + + def get_field(self, obj, name, width): + if "_lsb" in name: + field = getattr(obj, name.replace("_lsb", ""))[:width] + elif "_msb" in name: + field = getattr(obj, name.replace("_msb", ""))[width:2*width] + else: + field = getattr(obj, name) + if len(field) != width: + raise ValueError("Width mismatch on " + name + " field") + return field + + def encode(self, obj, signal): + r = [] + for k, v in sorted(self.fields.items()): + start = v.byte*8 + v.offset + end = start + v.width + field = self.get_field(obj, k, v.width) + if self.swap_field_bytes: + field = reverse_bytes(field) + r.append(signal[start:end].eq(field)) + return r + + def decode(self, signal, obj): + r = [] + for k, v in sorted(self.fields.items()): + start = v.byte*8 + v.offset + end = start + v.width + field = self.get_field(obj, k, v.width) + if self.swap_field_bytes: + r.append(field.eq(reverse_bytes(signal[start:end]))) + else: + r.append(field.eq(signal[start:end])) + return r + +def phy_description(dw): + layout = [("data", dw)] + return stream.EndpointDescription(layout) + + +def user_description(dw): + layout = [ + ("data", 32), + ("length", 32) + ] + return stream.EndpointDescription(layout) + + +class Packetizer(Module): + def __init__(self): + self.sink = sink = stream.Endpoint(user_description(32)) + self.source = source = stream.Endpoint(phy_description(32)) + + # # # + + # Packet description + # - preamble : 4 bytes + # - length : 4 bytes + # - payload + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + If(sink.stb, + NextState("INSERT_PREAMBLE") + ) + ) + fsm.act("INSERT_PREAMBLE", + source.stb.eq(1), + source.data.eq(0x5aa55aa5), + If(source.ack, + NextState("INSERT_LENGTH") + ) + ) + fsm.act("INSERT_LENGTH", + source.stb.eq(1), + source.data.eq(sink.length), + If(source.ack, + NextState("COPY") + ) + ) + fsm.act("COPY", + source.stb.eq(sink.stb), + source.data.eq(sink.data), + sink.ack.eq(source.ack), + If(source.ack & sink.eop, + NextState("IDLE") + ) + ) + + +class Depacketizer(Module): + def __init__(self, clk_freq, timeout=10): + self.sink = sink = stream.Endpoint(phy_description(32)) + self.source = source = stream.Endpoint(user_description(32)) + + # # # + + # Packet description + # - preamble : 4 bytes + # - length : 4 bytes + # - payload + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + self.submodules.timer = WaitTimer(clk_freq*timeout) + self.comb += self.timer.wait.eq(~fsm.ongoing("IDLE")) + + fsm.act("IDLE", + sink.ack.eq(1), + If(sink.stb & (sink.data == 0x5aa55aa5), + NextState("RECEIVE_LENGTH") + ) + ) + fsm.act("RECEIVE_LENGTH", + sink.ack.eq(1), + If(sink.stb, + NextValue(source.length, sink.data), + NextState("COPY") + ) + ) + eop = Signal() + cnt = Signal(32) + fsm.act("COPY", + source.stb.eq(sink.stb), + source.eop.eq(eop), + source.data.eq(sink.data), + sink.ack.eq(source.ack), + If((source.stb & source.ack & eop) | self.timer.done, + NextState("IDLE") + ) + ) + self.sync += \ + If(fsm.ongoing("IDLE"), + cnt.eq(0) + ).Elif(source.stb & source.ack, + cnt.eq(cnt + 1) + ) + self.comb += eop.eq(cnt == source.length[2:] - 1) diff --git a/artiq/gateware/serwb/phy.py b/artiq/gateware/serwb/phy.py new file mode 100644 index 000000000..49beb9240 --- /dev/null +++ b/artiq/gateware/serwb/phy.py @@ -0,0 +1,394 @@ +from migen import * +from migen.genlib.cdc import MultiReg, PulseSynchronizer +from migen.genlib.misc import WaitTimer + +from misoc.interconnect.csr import * + +from artiq.gateware.serwb.kusphy import KUSSerdes +from artiq.gateware.serwb.s7phy import S7Serdes + + +# Master <--> Slave synchronization: +# 1) Master sends idle pattern (zeroes) to reset Slave. +# 2) Master sends K28.5 commas to allow Slave to calibrate, Slave sends idle pattern. +# 3) Slave sends K28.5 commas to allow Master to calibrate, Master sends K28.5 commas. +# 4) Master stops sending K28.5 commas. +# 5) Slave stops sending K25.5 commas. +# 6) Link is ready. + +class _SerdesMasterInit(Module): + def __init__(self, serdes, taps, timeout=1024): + self.reset = Signal() + self.ready = Signal() + self.error = Signal() + + # # # + + self.delay = delay = Signal(max=taps) + self.delay_min = delay_min = Signal(max=taps) + self.delay_min_found = delay_min_found = Signal() + self.delay_max = delay_max = Signal(max=taps) + self.delay_max_found = delay_max_found = Signal() + self.bitslip = bitslip = Signal(max=40) + + timer = WaitTimer(timeout) + self.submodules += timer + + self.submodules.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) + self.comb += self.fsm.reset.eq(self.reset) + + fsm.act("IDLE", + NextValue(delay, 0), + NextValue(delay_min, 0), + NextValue(delay_min_found, 0), + NextValue(delay_max, 0), + NextValue(delay_max_found, 0), + serdes.rx_delay_rst.eq(1), + NextValue(bitslip, 0), + NextState("RESET_SLAVE"), + serdes.tx_idle.eq(1) + ) + fsm.act("RESET_SLAVE", + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("SEND_PATTERN") + ), + serdes.tx_idle.eq(1) + ) + fsm.act("SEND_PATTERN", + If(~serdes.rx_idle, + NextState("WAIT_STABLE") + ), + serdes.tx_comma.eq(1) + ) + fsm.act("WAIT_STABLE", + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("CHECK_PATTERN") + ), + serdes.tx_comma.eq(1) + ) + fsm.act("CHECK_PATTERN", + If(~delay_min_found, + If(serdes.rx_comma, + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextValue(delay_min, delay), + NextValue(delay_min_found, 1) + ) + ).Else( + NextState("INC_DELAY_BITSLIP") + ), + ).Else( + If(~serdes.rx_comma, + NextValue(delay_max, delay), + NextValue(delay_max_found, 1), + NextState("CHECK_SAMPLING_WINDOW") + ).Else( + NextState("INC_DELAY_BITSLIP") + ) + ), + serdes.tx_comma.eq(1) + ) + self.comb += serdes.rx_bitslip_value.eq(bitslip) + fsm.act("INC_DELAY_BITSLIP", + NextState("WAIT_STABLE"), + If(delay == (taps - 1), + If(bitslip == (40 - 1), + NextState("ERROR") + ).Else( + NextValue(delay_min_found, 0), + NextValue(bitslip, bitslip + 1) + ), + NextValue(delay, 0), + serdes.rx_delay_rst.eq(1) + ).Else( + NextValue(delay, delay + 1), + serdes.rx_delay_inc.eq(1), + serdes.rx_delay_ce.eq(1) + ), + serdes.tx_comma.eq(1) + ) + fsm.act("CHECK_SAMPLING_WINDOW", + If((delay_min == 0) | + (delay_max == (taps - 1)) | + ((delay_max - delay_min) < taps//16), + NextValue(delay_min_found, 0), + NextValue(delay_max_found, 0), + NextState("WAIT_STABLE") + ).Else( + NextState("RESET_SAMPLING_WINDOW") + ) + ) + fsm.act("RESET_SAMPLING_WINDOW", + NextValue(delay, 0), + serdes.rx_delay_rst.eq(1), + NextState("WAIT_SAMPLING_WINDOW"), + serdes.tx_comma.eq(1) + ) + fsm.act("CONFIGURE_SAMPLING_WINDOW", + If(delay == (delay_min + (delay_max - delay_min)[1:]), + NextState("READY") + ).Else( + NextValue(delay, delay + 1), + serdes.rx_delay_inc.eq(1), + serdes.rx_delay_ce.eq(1), + NextState("WAIT_SAMPLING_WINDOW") + ), + serdes.tx_comma.eq(1) + ) + fsm.act("WAIT_SAMPLING_WINDOW", + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("CONFIGURE_SAMPLING_WINDOW") + ), + serdes.tx_comma.eq(1) + ) + fsm.act("READY", + self.ready.eq(1) + ) + fsm.act("ERROR", + self.error.eq(1) + ) + + +class _SerdesSlaveInit(Module, AutoCSR): + def __init__(self, serdes, taps, timeout=1024): + self.reset = Signal() + self.ready = Signal() + self.error = Signal() + + # # # + + self.delay = delay = Signal(max=taps) + self.delay_min = delay_min = Signal(max=taps) + self.delay_min_found = delay_min_found = Signal() + self.delay_max = delay_max = Signal(max=taps) + self.delay_max_found = delay_max_found = Signal() + self.bitslip = bitslip = Signal(max=40) + + timer = WaitTimer(timeout) + self.submodules += timer + + self.comb += self.reset.eq(serdes.rx_idle) + + self.submodules.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) + fsm.act("IDLE", + NextValue(delay, 0), + NextValue(delay_min, 0), + NextValue(delay_min_found, 0), + NextValue(delay_max, 0), + NextValue(delay_max_found, 0), + serdes.rx_delay_rst.eq(1), + NextValue(bitslip, 0), + NextState("WAIT_STABLE"), + serdes.tx_idle.eq(1) + ) + fsm.act("WAIT_STABLE", + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("CHECK_PATTERN") + ), + serdes.tx_idle.eq(1) + ) + fsm.act("CHECK_PATTERN", + If(~delay_min_found, + If(serdes.rx_comma, + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextValue(delay_min, delay), + NextValue(delay_min_found, 1) + ) + ).Else( + NextState("INC_DELAY_BITSLIP") + ), + ).Else( + If(~serdes.rx_comma, + NextValue(delay_max, delay), + NextValue(delay_max_found, 1), + NextState("CHECK_SAMPLING_WINDOW") + ).Else( + NextState("INC_DELAY_BITSLIP") + ) + ), + serdes.tx_idle.eq(1) + ) + self.comb += serdes.rx_bitslip_value.eq(bitslip) + fsm.act("INC_DELAY_BITSLIP", + NextState("WAIT_STABLE"), + If(delay == (taps - 1), + If(bitslip == (40 - 1), + NextState("ERROR") + ).Else( + NextValue(delay_min_found, 0), + NextValue(bitslip, bitslip + 1) + ), + NextValue(delay, 0), + serdes.rx_delay_rst.eq(1) + ).Else( + NextValue(delay, delay + 1), + serdes.rx_delay_inc.eq(1), + serdes.rx_delay_ce.eq(1) + ), + serdes.tx_idle.eq(1) + ) + fsm.act("CHECK_SAMPLING_WINDOW", + If((delay_min == 0) | + (delay_max == (taps - 1)) | + ((delay_max - delay_min) < taps//16), + NextValue(delay_min_found, 0), + NextValue(delay_max_found, 0), + NextState("WAIT_STABLE") + ).Else( + NextState("RESET_SAMPLING_WINDOW") + ) + ) + fsm.act("RESET_SAMPLING_WINDOW", + NextValue(delay, 0), + serdes.rx_delay_rst.eq(1), + NextState("WAIT_SAMPLING_WINDOW") + ) + fsm.act("CONFIGURE_SAMPLING_WINDOW", + If(delay == (delay_min + (delay_max - delay_min)[1:]), + NextState("SEND_PATTERN") + ).Else( + NextValue(delay, delay + 1), + serdes.rx_delay_inc.eq(1), + serdes.rx_delay_ce.eq(1), + NextState("WAIT_SAMPLING_WINDOW") + ) + ) + fsm.act("WAIT_SAMPLING_WINDOW", + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("CONFIGURE_SAMPLING_WINDOW") + ) + ) + fsm.act("SEND_PATTERN", + timer.wait.eq(1), + If(timer.done, + If(~serdes.rx_comma, + NextState("READY") + ) + ), + serdes.tx_comma.eq(1) + ) + fsm.act("READY", + self.ready.eq(1) + ) + fsm.act("ERROR", + self.error.eq(1) + ) + + +class _SerdesControl(Module, AutoCSR): + def __init__(self, init, mode="master"): + if mode == "master": + self.reset = CSR() + self.ready = CSRStatus() + self.error = CSRStatus() + + self.delay = CSRStatus(9) + self.delay_min_found = CSRStatus() + self.delay_min = CSRStatus(9) + self.delay_max_found = CSRStatus() + self.delay_max = CSRStatus(9) + self.bitslip = CSRStatus(6) + + # # # + + if mode == "master": + self.comb += init.reset.eq(self.reset.re) + self.comb += [ + self.ready.status.eq(init.ready), + self.error.status.eq(init.error), + self.delay.status.eq(init.delay), + self.delay_min_found.status.eq(init.delay_min_found), + self.delay_min.status.eq(init.delay_min), + self.delay_max_found.status.eq(init.delay_max_found), + self.delay_max.status.eq(init.delay_max), + self.bitslip.status.eq(init.bitslip) + ] + + +class SERWBPLL(Module): + def __init__(self, refclk_freq, linerate, vco_div=1): + assert refclk_freq == 125e6 + assert linerate == 1.25e9 + + self.lock = Signal() + self.refclk = Signal() + self.serwb_serdes_clk = Signal() + self.serwb_serdes_20x_clk = Signal() + self.serwb_serdes_5x_clk = Signal() + + # # # + + #---------------------------- + # refclk: 125MHz + # vco: 1250MHz + #---------------------------- + # serwb_serdes: 31.25MHz + # serwb_serdes_20x: 625MHz + # serwb_serdes_5x: 156.25MHz + #---------------------------- + self.linerate = linerate + + pll_locked = Signal() + pll_fb = Signal() + pll_serwb_serdes_clk = Signal() + pll_serwb_serdes_20x_clk = Signal() + pll_serwb_serdes_5x_clk = Signal() + self.specials += [ + Instance("PLLE2_BASE", + p_STARTUP_WAIT="FALSE", o_LOCKED=pll_locked, + + # VCO @ 1.25GHz / vco_div + p_REF_JITTER1=0.01, p_CLKIN1_PERIOD=8.0, + p_CLKFBOUT_MULT=10, p_DIVCLK_DIVIDE=vco_div, + i_CLKIN1=self.refclk, i_CLKFBIN=pll_fb, + o_CLKFBOUT=pll_fb, + + # 31.25MHz: serwb_serdes + p_CLKOUT0_DIVIDE=40//vco_div, p_CLKOUT0_PHASE=0.0, + o_CLKOUT0=pll_serwb_serdes_clk, + + # 625MHz: serwb_serdes_20x + p_CLKOUT1_DIVIDE=2//vco_div, p_CLKOUT1_PHASE=0.0, + o_CLKOUT1=pll_serwb_serdes_20x_clk, + + # 156.25MHz: serwb_serdes_5x + p_CLKOUT2_DIVIDE=8//vco_div, p_CLKOUT2_PHASE=0.0, + o_CLKOUT2=pll_serwb_serdes_5x_clk + ), + Instance("BUFG", i_I=pll_serwb_serdes_clk, o_O=self.serwb_serdes_clk), + Instance("BUFG", i_I=pll_serwb_serdes_20x_clk, o_O=self.serwb_serdes_20x_clk), + Instance("BUFG", i_I=pll_serwb_serdes_5x_clk, o_O=self.serwb_serdes_5x_clk) + ] + self.specials += MultiReg(pll_locked, self.lock) + + + +class SERWBPHY(Module, AutoCSR): + def __init__(self, device, pll, pads, mode="master"): + assert mode in ["master", "slave"] + if device[:4] == "xcku": + taps = 512 + self.submodules.serdes = KUSSerdes(pll, pads, mode) + elif device[:4] == "xc7a": + taps = 32 + self.submodules.serdes = S7Serdes(pll, pads, mode) + else: + raise NotImplementedError + if mode == "master": + self.submodules.init = _SerdesMasterInit(self.serdes, taps) + else: + self.submodules.init = _SerdesSlaveInit(self.serdes, taps) + self.submodules.control = _SerdesControl(self.init, mode) diff --git a/artiq/gateware/serwb/s7phy.py b/artiq/gateware/serwb/s7phy.py new file mode 100644 index 000000000..d64f5bbb5 --- /dev/null +++ b/artiq/gateware/serwb/s7phy.py @@ -0,0 +1,223 @@ +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer +from migen.genlib.cdc import MultiReg, Gearbox +from migen.genlib.misc import BitSlip + +from misoc.cores.code_8b10b import Encoder, Decoder + + +class S7Serdes(Module): + def __init__(self, pll, pads, mode="master"): + self.tx_data = Signal(32) + self.rx_data = Signal(32) + + self.tx_idle = Signal() + self.tx_comma = Signal() + self.rx_idle = Signal() + self.rx_comma = Signal() + + self.rx_bitslip_value = Signal(6) + self.rx_delay_rst = Signal() + self.rx_delay_inc = Signal() + self.rx_delay_ce = Signal() + + # # # + + self.submodules.encoder = ClockDomainsRenamer("serwb_serdes")( + Encoder(4, True)) + self.decoders = [ClockDomainsRenamer("serwb_serdes")( + Decoder(True)) for _ in range(4)] + self.submodules += self.decoders + + # clocking: + + # In master mode: + # - linerate/10 pll refclk provided by user + # - linerate/10 slave refclk generated on clk_pads + # In Slave mode: + # - linerate/10 pll refclk provided by clk_pads + self.clock_domains.cd_serwb_serdes = ClockDomain() + self.clock_domains.cd_serwb_serdes_5x = ClockDomain() + self.clock_domains.cd_serwb_serdes_20x = ClockDomain(reset_less=True) + self.comb += [ + self.cd_serwb_serdes.clk.eq(pll.serwb_serdes_clk), + self.cd_serwb_serdes_5x.clk.eq(pll.serwb_serdes_5x_clk), + self.cd_serwb_serdes_20x.clk.eq(pll.serwb_serdes_20x_clk) + ] + self.specials += AsyncResetSynchronizer(self.cd_serwb_serdes, ~pll.lock) + self.comb += self.cd_serwb_serdes_5x.rst.eq(self.cd_serwb_serdes.rst) + + # control/status cdc + tx_idle = Signal() + tx_comma = Signal() + rx_idle = Signal() + rx_comma = Signal() + rx_bitslip_value = Signal(6) + self.specials += [ + MultiReg(self.tx_idle, tx_idle, "serwb_serdes"), + MultiReg(self.tx_comma, tx_comma, "serwb_serdes"), + MultiReg(rx_idle, self.rx_idle, "sys"), + MultiReg(rx_comma, self.rx_comma, "sys") + ] + self.specials += MultiReg(self.rx_bitslip_value, rx_bitslip_value, "serwb_serdes"), + + # tx clock (linerate/10) + if mode == "master": + self.submodules.tx_clk_gearbox = Gearbox(40, "serwb_serdes", 8, "serwb_serdes_5x") + self.comb += self.tx_clk_gearbox.i.eq((0b1111100000 << 30) | + (0b1111100000 << 20) | + (0b1111100000 << 10) | + (0b1111100000 << 0)) + clk_o = Signal() + self.specials += [ + Instance("OSERDESE2", + p_DATA_WIDTH=8, p_TRISTATE_WIDTH=1, + p_DATA_RATE_OQ="DDR", p_DATA_RATE_TQ="BUF", + p_SERDES_MODE="MASTER", + + o_OQ=clk_o, + i_OCE=1, + i_RST=ResetSignal("serwb_serdes"), + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLKDIV=ClockSignal("serwb_serdes_5x"), + i_D1=self.tx_clk_gearbox.o[0], i_D2=self.tx_clk_gearbox.o[1], + i_D3=self.tx_clk_gearbox.o[2], i_D4=self.tx_clk_gearbox.o[3], + i_D5=self.tx_clk_gearbox.o[4], i_D6=self.tx_clk_gearbox.o[5], + i_D7=self.tx_clk_gearbox.o[6], i_D8=self.tx_clk_gearbox.o[7] + ), + Instance("OBUFDS", + i_I=clk_o, + o_O=pads.clk_p, + o_OB=pads.clk_n + ) + ] + + # tx datapath + # tx_data -> encoders -> gearbox -> serdes + self.submodules.tx_gearbox = Gearbox(40, "serwb_serdes", 8, "serwb_serdes_5x") + self.comb += [ + If(tx_comma, + self.encoder.k[0].eq(1), + self.encoder.d[0].eq(0xbc) + ).Else( + self.encoder.d[0].eq(self.tx_data[0:8]), + self.encoder.d[1].eq(self.tx_data[8:16]), + self.encoder.d[2].eq(self.tx_data[16:24]), + self.encoder.d[3].eq(self.tx_data[24:32]) + ) + ] + self.sync.serwb_serdes += \ + If(tx_idle, + self.tx_gearbox.i.eq(0) + ).Else( + self.tx_gearbox.i.eq(Cat(*[self.encoder.output[i] for i in range(4)])) + ) + + serdes_o = Signal() + self.specials += [ + Instance("OSERDESE2", + p_DATA_WIDTH=8, p_TRISTATE_WIDTH=1, + p_DATA_RATE_OQ="DDR", p_DATA_RATE_TQ="BUF", + p_SERDES_MODE="MASTER", + + o_OQ=serdes_o, + i_OCE=1, + i_RST=ResetSignal("serwb_serdes"), + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLKDIV=ClockSignal("serwb_serdes_5x"), + i_D1=self.tx_gearbox.o[0], i_D2=self.tx_gearbox.o[1], + i_D3=self.tx_gearbox.o[2], i_D4=self.tx_gearbox.o[3], + i_D5=self.tx_gearbox.o[4], i_D6=self.tx_gearbox.o[5], + i_D7=self.tx_gearbox.o[6], i_D8=self.tx_gearbox.o[7] + ), + Instance("OBUFDS", + i_I=serdes_o, + o_O=pads.tx_p, + o_OB=pads.tx_n + ) + ] + + # rx clock + use_bufr = True + if mode == "slave": + clk_i = Signal() + clk_i_bufg = Signal() + self.specials += [ + Instance("IBUFDS", + i_I=pads.clk_p, + i_IB=pads.clk_n, + o_O=clk_i + ) + ] + if use_bufr: + clk_i_bufr = Signal() + self.specials += [ + Instance("BUFR", i_I=clk_i, o_O=clk_i_bufr), + Instance("BUFG", i_I=clk_i_bufr, o_O=clk_i_bufg) + ] + else: + self.specials += Instance("BUFG", i_I=clk_i, o_O=clk_i_bufg) + self.comb += pll.refclk.eq(clk_i_bufg) + + # rx datapath + # serdes -> gearbox -> bitslip -> decoders -> rx_data + self.submodules.rx_gearbox = Gearbox(8, "serwb_serdes_5x", 40, "serwb_serdes") + self.submodules.rx_bitslip = ClockDomainsRenamer("serwb_serdes")(BitSlip(40)) + + serdes_i_nodelay = Signal() + self.specials += [ + Instance("IBUFDS_DIFF_OUT", + i_I=pads.rx_p, + i_IB=pads.rx_n, + o_O=serdes_i_nodelay + ) + ] + + serdes_i_delayed = Signal() + serdes_q = Signal(8) + self.specials += [ + Instance("IDELAYE2", + p_DELAY_SRC="IDATAIN", p_SIGNAL_PATTERN="DATA", + p_CINVCTRL_SEL="FALSE", p_HIGH_PERFORMANCE_MODE="TRUE", + p_REFCLK_FREQUENCY=200.0, p_PIPE_SEL="FALSE", + p_IDELAY_TYPE="VARIABLE", p_IDELAY_VALUE=0, + + i_C=ClockSignal(), + i_LD=self.rx_delay_rst, + i_CE=self.rx_delay_ce, + i_LDPIPEEN=0, i_INC=self.rx_delay_inc, + + i_IDATAIN=serdes_i_nodelay, o_DATAOUT=serdes_i_delayed + ), + Instance("ISERDESE2", + p_DATA_WIDTH=8, p_DATA_RATE="DDR", + p_SERDES_MODE="MASTER", p_INTERFACE_TYPE="NETWORKING", + p_NUM_CE=1, p_IOBDELAY="IFD", + + i_DDLY=serdes_i_delayed, + i_CE1=1, + i_RST=ResetSignal("serwb_serdes"), + i_CLK=ClockSignal("serwb_serdes_20x"), i_CLKB=~ClockSignal("serwb_serdes_20x"), + i_CLKDIV=ClockSignal("serwb_serdes_5x"), + i_BITSLIP=0, + o_Q8=serdes_q[0], o_Q7=serdes_q[1], + o_Q6=serdes_q[2], o_Q5=serdes_q[3], + o_Q4=serdes_q[4], o_Q3=serdes_q[5], + o_Q2=serdes_q[6], o_Q1=serdes_q[7] + ) + ] + + self.comb += [ + self.rx_gearbox.i.eq(serdes_q), + self.rx_bitslip.value.eq(rx_bitslip_value), + self.rx_bitslip.i.eq(self.rx_gearbox.o), + self.decoders[0].input.eq(self.rx_bitslip.o[0:10]), + self.decoders[1].input.eq(self.rx_bitslip.o[10:20]), + self.decoders[2].input.eq(self.rx_bitslip.o[20:30]), + self.decoders[3].input.eq(self.rx_bitslip.o[30:40]), + self.rx_data.eq(Cat(*[self.decoders[i].d for i in range(4)])), + rx_idle.eq(self.rx_bitslip.o == 0), + rx_comma.eq(((self.decoders[0].d == 0xbc) & (self.decoders[0].k == 1)) & + ((self.decoders[1].d == 0x00) & (self.decoders[1].k == 0)) & + ((self.decoders[2].d == 0x00) & (self.decoders[2].k == 0)) & + ((self.decoders[3].d == 0x00) & (self.decoders[3].k == 0))) + + ] diff --git a/artiq/gateware/targets/kc705_drtio_master.py b/artiq/gateware/targets/kc705_drtio_master.py index 889258965..0257b2e40 100755 --- a/artiq/gateware/targets/kc705_drtio_master.py +++ b/artiq/gateware/targets/kc705_drtio_master.py @@ -59,7 +59,7 @@ class Master(MiniSoC, AMPSoC): self.add_wb_slave(self.mem_map["drtio_aux"], 0x800, self.drtio0.aux_controller.bus) self.add_memory_region("drtio0_aux", self.mem_map["drtio_aux"] | self.shadow_base, 0x800) - self.config["has_drtio"] = None + self.config["HAS_DRTIO"] = None self.add_csr_group("drtio", ["drtio0"]) self.add_memory_group("drtio_aux", ["drtio0_aux"]) diff --git a/artiq/gateware/targets/kc705_drtio_satellite.py b/artiq/gateware/targets/kc705_drtio_satellite.py index e81756491..cd882b3d5 100755 --- a/artiq/gateware/targets/kc705_drtio_satellite.py +++ b/artiq/gateware/targets/kc705_drtio_satellite.py @@ -66,7 +66,7 @@ class Satellite(BaseSoC): self.add_wb_slave(self.mem_map["drtio_aux"], 0x800, self.drtio0.aux_controller.bus) self.add_memory_region("drtio0_aux", self.mem_map["drtio_aux"] | self.shadow_base, 0x800) - self.config["has_drtio"] = None + self.config["HAS_DRTIO"] = None self.add_csr_group("drtio", ["drtio0"]) self.add_memory_group("drtio_aux", ["drtio0_aux"]) diff --git a/artiq/gateware/targets/phaser.py b/artiq/gateware/targets/phaser.py index fe141837f..735604063 100755 --- a/artiq/gateware/targets/phaser.py +++ b/artiq/gateware/targets/phaser.py @@ -192,12 +192,14 @@ class Phaser(MiniSoC, AMPSoC): self.comb += ad9154_spi.en.eq(1) self.submodules.converter_spi = spi_csr.SPIMaster(ad9154_spi) self.csr_devices.append("converter_spi") - self.config["CONVERTER_SPI_DAC_CS"] = 0 - self.config["CONVERTER_SPI_CLK_CS"] = 1 self.config["HAS_AD9516"] = None + self.config["CONVERTER_SPI_AD9516_CS"] = 1 + self.config["CONVERTER_SPI_FIRST_AD9154_CS"] = 0 - self.submodules.ad9154 = AD9154(platform) - self.csr_devices.append("ad9154") + self.submodules.ad9154_0 = AD9154(platform) + self.csr_devices.append("ad9154_0") + self.config["HAS_AD9154"] = None + self.add_csr_group("ad9154", ["ad9154_0"]) rtio_channels = [] @@ -218,7 +220,7 @@ class Phaser(MiniSoC, AMPSoC): self.config["RTIO_FIRST_SAWG_CHANNEL"] = len(rtio_channels) rtio_channels.extend(rtio.Channel.from_phy(phy) - for sawg in self.ad9154.sawgs + for sawg in self.ad9154_0.sawgs for phy in sawg.phys) self.config["HAS_RTIO_LOG"] = None @@ -226,7 +228,7 @@ class Phaser(MiniSoC, AMPSoC): rtio_channels.append(rtio.LogChannel()) self.submodules.rtio_crg = _PhaserCRG( - platform, self.ad9154.jesd.cd_jesd.clk) + platform, self.ad9154_0.jesd.cd_jesd.clk) self.csr_devices.append("rtio_crg") self.submodules.rtio_core = rtio.Core(rtio_channels) self.csr_devices.append("rtio_core") @@ -248,8 +250,8 @@ class Phaser(MiniSoC, AMPSoC): platform.add_false_path_constraints( self.crg.cd_sys.clk, self.rtio_crg.cd_rtio.clk) platform.add_false_path_constraints( - self.crg.cd_sys.clk, self.ad9154.jesd.cd_jesd.clk) - for phy in self.ad9154.jesd.phys: + self.crg.cd_sys.clk, self.ad9154_0.jesd.cd_jesd.clk) + for phy in self.ad9154_0.jesd.phys: platform.add_false_path_constraints( self.crg.cd_sys.clk, phy.transmitter.cd_tx.clk) diff --git a/artiq/gateware/targets/sayma_amc_standalone.py b/artiq/gateware/targets/sayma_amc_standalone.py new file mode 100755 index 000000000..4100541dc --- /dev/null +++ b/artiq/gateware/targets/sayma_amc_standalone.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 + +import argparse +import os + +from migen import * + +from misoc.cores import gpio +from misoc.integration.soc_sdram import soc_sdram_args, soc_sdram_argdict +from misoc.integration.builder import builder_args, builder_argdict +from misoc.interconnect import stream +from misoc.targets.sayma_amc import MiniSoC + +from artiq.gateware.amp import AMPSoC, build_artiq_soc +from artiq.gateware import serwb +from artiq.gateware import remote_csr +from artiq.gateware import rtio +from artiq.gateware.rtio.phy import ttl_simple +from artiq import __version__ as artiq_version + + +class SaymaAMCStandalone(MiniSoC, AMPSoC): + mem_map = { + "cri_con": 0x10000000, + "rtio": 0x11000000, + "rtio_dma": 0x12000000, + "serwb": 0x13000000, + "mailbox": 0x70000000 + } + mem_map.update(MiniSoC.mem_map) + + def __init__(self, cpu_type="or1k", **kwargs): + MiniSoC.__init__(self, + cpu_type=cpu_type, + sdram_controller_type="minicon", + l2_size=128*1024, + ident=artiq_version, + ethmac_nrxslots=4, + ethmac_ntxslots=4, + **kwargs) + AMPSoC.__init__(self) + platform = self.platform + platform.toolchain.bitstream_commands.append( + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]") + + self.submodules.leds = gpio.GPIOOut(Cat( + platform.request("user_led", 0), + platform.request("user_led", 1))) + self.csr_devices.append("leds") + + # forward RTM UART to second FTDI UART channel + serial_1 = platform.request("serial", 1) + serial_rtm = platform.request("serial_rtm") + self.comb += [ + serial_1.tx.eq(serial_rtm.rx), + serial_rtm.tx.eq(serial_1.rx) + ] + + # AMC/RTM serwb + serwb_pll = serwb.phy.SERWBPLL(125e6, 1.25e9, vco_div=2) + self.comb += serwb_pll.refclk.eq(self.crg.cd_sys.clk) + self.submodules += serwb_pll + + serwb_pads = platform.request("amc_rtm_serwb") + serwb_phy = serwb.phy.SERWBPHY(platform.device, serwb_pll, serwb_pads, mode="master") + self.submodules.serwb_phy = serwb_phy + self.csr_devices.append("serwb_phy") + + serwb_phy.serdes.cd_serwb_serdes.clk.attr.add("keep") + serwb_phy.serdes.cd_serwb_serdes_20x.clk.attr.add("keep") + serwb_phy.serdes.cd_serwb_serdes_5x.clk.attr.add("keep") + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes.clk, 32.0), + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes_20x.clk, 1.6), + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes_5x.clk, 6.4) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, + serwb_phy.serdes.cd_serwb_serdes.clk, + serwb_phy.serdes.cd_serwb_serdes_5x.clk) + + serwb_core = serwb.core.SERWBCore(serwb_phy, int(self.clk_freq), mode="slave") + self.submodules += serwb_core + self.add_wb_slave(self.mem_map["serwb"], 8192, serwb_core.etherbone.wishbone.bus) + + # RTIO + rtio_channels = [] + for i in (2, 3): + phy = ttl_simple.Output(platform.request("user_led", i)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + for i in (0, 1): + sma_io = platform.request("sma_io", i) + self.comb += sma_io.direction.eq(1) + phy = ttl_simple.Output(sma_io.level) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + self.config["HAS_RTIO_LOG"] = None + self.config["RTIO_LOG_CHANNEL"] = len(rtio_channels) + rtio_channels.append(rtio.LogChannel()) + + self.clock_domains.cd_rtio = ClockDomain() + self.comb += [ + self.cd_rtio.clk.eq(ClockSignal()), + self.cd_rtio.rst.eq(ResetSignal()) + ] + self.submodules.rtio_core = rtio.Core(rtio_channels) + self.csr_devices.append("rtio_core") + self.submodules.rtio = rtio.KernelInitiator() + self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( + rtio.DMA(self.get_native_sdram_if())) + self.register_kernel_cpu_csrdevice("rtio") + self.register_kernel_cpu_csrdevice("rtio_dma") + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.rtio.cri, self.rtio_dma.cri], + [self.rtio_core.cri]) + self.register_kernel_cpu_csrdevice("cri_con") + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_core.cri, + self.get_native_sdram_if()) + self.csr_devices.append("rtio_analyzer") + + +def main(): + parser = argparse.ArgumentParser( + description="ARTIQ device binary builder / Sayma AMC stand-alone") + builder_args(parser) + soc_sdram_args(parser) + parser.add_argument("--rtm-csr-csv", + default=os.path.join("artiq_sayma_rtm", "sayma_rtm_csr.csv"), + help="CSV file listing remote CSRs on RTM (default: %(default)s)") + args = parser.parse_args() + + soc = SaymaAMCStandalone(**soc_sdram_argdict(args)) + + remote_csr_regions = remote_csr.get_remote_csr_regions( + soc.mem_map["serwb"] | soc.shadow_base, + args.rtm_csr_csv) + for name, origin, busword, csrs in remote_csr_regions: + soc.add_csr_region(name, origin, busword, csrs) + # Configuration for RTM peripherals. Keep in sync with sayma_rtm.py! + soc.config["HAS_HMC830_7043"] = None + soc.config["CONVERTER_SPI_HMC830_CS"] = 0 + soc.config["CONVERTER_SPI_HMC7043_CS"] = 1 + soc.config["CONVERTER_SPI_FIRST_AD9154_CS"] = 2 + + build_artiq_soc(soc, builder_argdict(args)) + + +if __name__ == "__main__": + main() diff --git a/artiq/gateware/targets/sayma_rtm.py b/artiq/gateware/targets/sayma_rtm.py new file mode 100755 index 000000000..a8449bb81 --- /dev/null +++ b/artiq/gateware/targets/sayma_rtm.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 + +import os + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer +from migen.build.platforms.sinara import sayma_rtm + +from misoc.interconnect import wishbone, stream +from misoc.interconnect.csr import * +from misoc.cores import spi +from misoc.integration.wb_slaves import WishboneSlaveManager +from misoc.integration.cpu_interface import get_csr_csv + +from artiq.gateware import serwb + + +class CRG(Module): + def __init__(self, platform): + self.clock_domains.cd_sys = ClockDomain() + self.clock_domains.cd_clk200 = ClockDomain() + + clk50 = platform.request("clk50") + self.reset = Signal() + + pll_locked = Signal() + pll_fb = Signal() + pll_sys = Signal() + pll_clk200 = Signal() + self.specials += [ + Instance("PLLE2_BASE", + p_STARTUP_WAIT="FALSE", o_LOCKED=pll_locked, + + # VCO @ 1GHz + p_REF_JITTER1=0.01, p_CLKIN1_PERIOD=20.0, + p_CLKFBOUT_MULT=20, p_DIVCLK_DIVIDE=1, + i_CLKIN1=clk50, i_CLKFBIN=pll_fb, o_CLKFBOUT=pll_fb, + + # 125MHz + p_CLKOUT0_DIVIDE=8, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=pll_sys, + + # 200MHz + p_CLKOUT3_DIVIDE=5, p_CLKOUT3_PHASE=0.0, o_CLKOUT3=pll_clk200 + ), + Instance("BUFG", i_I=pll_sys, o_O=self.cd_sys.clk), + Instance("BUFG", i_I=pll_clk200, o_O=self.cd_clk200.clk), + AsyncResetSynchronizer(self.cd_sys, ~pll_locked | self.reset), + AsyncResetSynchronizer(self.cd_clk200, ~pll_locked | self.reset) + ] + + reset_counter = Signal(4, reset=15) + ic_reset = Signal(reset=1) + self.sync.clk200 += \ + If(reset_counter != 0, + reset_counter.eq(reset_counter - 1) + ).Else( + ic_reset.eq(0) + ) + self.specials += Instance("IDELAYCTRL", i_REFCLK=ClockSignal("clk200"), i_RST=ic_reset) + + +class RTMIdentifier(Module, AutoCSR): + def __init__(self): + self.identifier = CSRStatus(32) + self.comb += self.identifier.status.eq(0x5352544d) # "SRTM" + + +CSR_RANGE_SIZE = 0x800 + + +class SaymaRTM(Module): + def __init__(self, platform): + csr_devices = [] + + self.submodules.crg = CRG(platform) + self.crg.cd_sys.clk.attr.add("keep") + clk_freq = 125e6 + platform.add_period_constraint(self.crg.cd_sys.clk, 8.0) + + self.submodules.rtm_identifier = RTMIdentifier() + csr_devices.append("rtm_identifier") + + # clock mux: 125MHz ext SMA clock to HMC830 input + self.comb += [ + platform.request("clk_src_ext_sel").eq(1), # use ext clk from sma + platform.request("ref_clk_src_sel").eq(1), + platform.request("dac_clk_src_sel").eq(0), # use clk from dac_clk + ] + + self.comb += [ + platform.request("ad9154_rst_n").eq(1), + platform.request("ad9154_txen", 0).eq(0b11), + platform.request("ad9154_txen", 1).eq(0b11) + ] + + self.submodules.converter_spi = spi.SPIMaster([ + platform.request("hmc_spi"), + platform.request("ad9154_spi", 0), + platform.request("ad9154_spi", 1)]) + csr_devices.append("converter_spi") + self.comb += platform.request("hmc7043_reset").eq(0) + + # AMC/RTM serwb + serwb_pll = serwb.phy.SERWBPLL(125e6, 1.25e9, vco_div=1) + self.submodules += serwb_pll + + serwb_pads = platform.request("amc_rtm_serwb") + serwb_phy = serwb.phy.SERWBPHY(platform.device, serwb_pll, serwb_pads, mode="slave") + self.submodules.serwb_phy = serwb_phy + self.comb += self.crg.reset.eq(serwb_phy.init.reset) + + serwb_phy.serdes.cd_serwb_serdes.clk.attr.add("keep") + serwb_phy.serdes.cd_serwb_serdes_20x.clk.attr.add("keep") + serwb_phy.serdes.cd_serwb_serdes_5x.clk.attr.add("keep") + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes.clk, 32.0), + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes_20x.clk, 1.6), + platform.add_period_constraint(serwb_phy.serdes.cd_serwb_serdes_5x.clk, 6.4) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, + serwb_phy.serdes.cd_serwb_serdes.clk, + serwb_phy.serdes.cd_serwb_serdes_5x.clk) + + serwb_core = serwb.core.SERWBCore(serwb_phy, int(clk_freq), mode="master") + self.submodules += serwb_core + + # process CSR devices and connect them to serwb + self.csr_regions = [] + wb_slaves = WishboneSlaveManager(0x10000000) + for i, name in enumerate(csr_devices): + origin = i*CSR_RANGE_SIZE + module = getattr(self, name) + csrs = module.get_csrs() + + bank = wishbone.CSRBank(csrs) + self.submodules += bank + + wb_slaves.add(origin, CSR_RANGE_SIZE, bank.bus) + self.csr_regions.append((name, origin, 32, csrs)) + + self.submodules += wishbone.Decoder(serwb_core.etherbone.wishbone.bus, + wb_slaves.get_interconnect_slaves(), + register=True) + + +def main(): + build_dir = "artiq_sayma_rtm" + platform = sayma_rtm.Platform() + top = SaymaRTM(platform) + with open(os.path.join(build_dir, "sayma_rtm_csr.csv"), "w") as f: + f.write(get_csr_csv(top.csr_regions)) + platform.build(top, build_dir=build_dir) + + +if __name__ == "__main__": + main() diff --git a/artiq/gateware/test/serwb/__init__.py b/artiq/gateware/test/serwb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/artiq/gateware/test/serwb/test_etherbone.py b/artiq/gateware/test/serwb/test_etherbone.py new file mode 100644 index 000000000..963769a1f --- /dev/null +++ b/artiq/gateware/test/serwb/test_etherbone.py @@ -0,0 +1,70 @@ +import unittest +import random + +from migen import * + +from misoc.interconnect.wishbone import SRAM +from misoc.interconnect.stream import Converter + +from artiq.gateware.serwb import packet +from artiq.gateware.serwb import etherbone + + +class DUT(Module): + def __init__(self): + # wishbone slave + slave_depacketizer = packet.Depacketizer(int(100e6)) + slave_packetizer = packet.Packetizer() + self.submodules += slave_depacketizer, slave_packetizer + slave_etherbone = etherbone.Etherbone(mode="slave") + self.submodules += slave_etherbone + self.comb += [ + slave_depacketizer.source.connect(slave_etherbone.sink), + slave_etherbone.source.connect(slave_packetizer.sink) + ] + + # wishbone master + master_depacketizer = packet.Depacketizer(int(100e6)) + master_packetizer = packet.Packetizer() + self.submodules += master_depacketizer, master_packetizer + master_etherbone = etherbone.Etherbone(mode="master") + master_sram = SRAM(64, bus=master_etherbone.wishbone.bus) + self.submodules += master_etherbone, master_sram + self.comb += [ + master_depacketizer.source.connect(master_etherbone.sink), + master_etherbone.source.connect(master_packetizer.sink) + ] + + # connect core directly with converters in the loop + s2m_downconverter = Converter(32, 16) + s2m_upconverter = Converter(16, 32) + self.submodules += s2m_downconverter, s2m_upconverter + m2s_downconverter = Converter(32, 16) + m2s_upconverter = Converter(16, 32) + self.submodules += m2s_upconverter, m2s_downconverter + self.comb += [ + slave_packetizer.source.connect(s2m_downconverter.sink), + s2m_downconverter.source.connect(s2m_upconverter.sink), + s2m_upconverter.source.connect(master_depacketizer.sink), + + master_packetizer.source.connect(m2s_downconverter.sink), + m2s_downconverter.source.connect(m2s_upconverter.sink), + m2s_upconverter.source.connect(slave_depacketizer.sink) + ] + + # expose wishbone slave + self.wishbone = slave_etherbone.wishbone.bus + + +class TestEtherbone(unittest.TestCase): + def test_write_read_sram(self): + dut = DUT() + prng = random.Random(1) + def generator(dut): + datas = [prng.randrange(0, 2**32-1) for i in range(16)] + for i in range(16): + yield from dut.wishbone.write(i, datas[i]) + for i in range(16): + data = (yield from dut.wishbone.read(i)) + self.assertEqual(data, datas[i]) + run_simulation(dut, generator(dut)) diff --git a/artiq/gateware/test/serwb/test_serwb_phy_init.py b/artiq/gateware/test/serwb/test_serwb_phy_init.py new file mode 100644 index 000000000..ea807f97d --- /dev/null +++ b/artiq/gateware/test/serwb/test_serwb_phy_init.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +import unittest + +from migen import * + +from artiq.gateware.serwb import packet +from artiq.gateware.serwb import etherbone +from artiq.gateware.serwb.phy import _SerdesMasterInit, _SerdesSlaveInit + + +class SerdesModel(Module): + def __init__(self, taps, mode="slave"): + self.tx_idle = Signal() + self.tx_comma = Signal() + self.rx_idle = Signal() + self.rx_comma = Signal() + + self.rx_bitslip_value = Signal(6) + self.rx_delay_rst = Signal() + self.rx_delay_inc = Signal() + self.rx_delay_ce = Signal() + + self.valid_bitslip = Signal(6) + self.valid_delays = Signal(taps) + + # # # + + delay = Signal(max=taps) + bitslip = Signal(6) + + valid_delays = Array(Signal() for i in range(taps)) + for i in range(taps): + self.comb += valid_delays[taps-1-i].eq(self.valid_delays[i]) + + self.sync += [ + bitslip.eq(self.rx_bitslip_value), + If(self.rx_delay_rst, + delay.eq(0) + ).Elif(self.rx_delay_inc & self.rx_delay_ce, + delay.eq(delay + 1) + ) + ] + + if mode == "master": + self.submodules.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) + self.comb += self.fsm.reset.eq(self.tx_idle) + fsm.act("IDLE", + If(self.tx_comma, + NextState("SEND_COMMA") + ), + self.rx_idle.eq(1) + ) + fsm.act("SEND_COMMA", + If(valid_delays[delay] & + (bitslip == self.valid_bitslip), + self.rx_comma.eq(1) + ), + If(~self.tx_comma, + NextState("READY") + ) + ) + fsm.act("READY") + elif mode == "slave": + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + self.rx_idle.eq(1), + NextState("SEND_COMMA") + ) + fsm.act("SEND_COMMA", + If(valid_delays[delay] & + (bitslip == self.valid_bitslip), + self.rx_comma.eq(1) + ), + If(~self.tx_idle, + NextState("READY") + ) + ) + fsm.act("READY") + + +class DUTMaster(Module): + def __init__(self, taps=32): + self.submodules.serdes = SerdesModel(taps, mode="master") + self.submodules.init = _SerdesMasterInit(self.serdes, taps, timeout=1) + + +class DUTSlave(Module): + def __init__(self, taps=32): + self.submodules.serdes = SerdesModel(taps, mode="slave") + self.submodules.init = _SerdesSlaveInit(self.serdes, taps, timeout=1) + + +def generator(test, dut, valid_bitslip, valid_delays, check_success): + yield dut.serdes.valid_bitslip.eq(valid_bitslip) + yield dut.serdes.valid_delays.eq(valid_delays) + while not ((yield dut.init.ready) or + (yield dut.init.error)): + yield + if check_success: + ready = (yield dut.init.ready) + error = (yield dut.init.error) + delay_min = (yield dut.init.delay_min) + delay_max = (yield dut.init.delay_max) + delay = (yield dut.init.delay) + bitslip = (yield dut.init.bitslip) + test.assertEqual(ready, 1) + test.assertEqual(error, 0) + test.assertEqual(delay_min, 4) + test.assertEqual(delay_max, 9) + test.assertEqual(delay, 6) + test.assertEqual(bitslip, valid_bitslip) + else: + ready = (yield dut.init.ready) + error = (yield dut.init.error) + test.assertEqual(ready, 0) + test.assertEqual(error, 1) + + +class TestPHYInit(unittest.TestCase): + def test_master_init_success(self): + dut = DUTMaster() + valid_bitslip = 2 + valid_delays = 0b10001111100000111110000011111000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, True)) + + def test_master_init_failure(self): + # partial window at the beginning + dut = DUTMaster() + valid_bitslip = 2 + valid_delays = 0b11000000000000000000000000000000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False)) + # partial window at the end + dut = DUTMaster() + valid_bitslip = 2 + valid_delays = 0b00000000000000000000000000000011 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False)) + # too small window + dut = DUTMaster() + valid_bitslip = 2 + valid_delays = 0b00000000000000010000000000000000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False)) + + def test_slave_init_success(self): + dut = DUTSlave() + valid_bitslip = 2 + valid_delays = 0b10001111100000111110000011111000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, True)) + + def test_slave_init_failure(self): + # partial window at the beginning + dut = DUTSlave() + valid_bitslip = 2 + valid_delays = 0b11000000000000000000000000000000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False)) + # partial window at the end + dut = DUTSlave() + valid_bitslip = 2 + valid_delays = 0b00000000000000000000000000000011 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False)) + # too small window + dut = DUTSlave() + valid_bitslip = 2 + valid_delays = 0b00000000000000010000000000000000 + run_simulation(dut, generator(self, dut, valid_bitslip, valid_delays, False))