forked from M-Labs/artiq
1
0
Fork 0

add DRTIO-over-EEM PHY

for EFC and perhaps Phaser
This commit is contained in:
occheung 2023-08-09 16:59:40 -07:00 committed by GitHub
parent df662c4262
commit 64d3f867a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 746 additions and 3 deletions

View File

@ -24,3 +24,4 @@ proto_artiq = { path = "../libproto_artiq" }
[features] [features]
uart_console = [] uart_console = []
alloc = []

View File

@ -0,0 +1,219 @@
use board_misoc::{csr, clock, config};
#[cfg(feature = "alloc")]
use alloc::format;
struct SerdesConfig {
pub delay: [u8; 4],
}
impl SerdesConfig {
pub fn as_bytes(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
(self as *const SerdesConfig) as *const u8,
core::mem::size_of::<SerdesConfig>(),
)
}
}
}
fn select_lane(lane_no: u8) {
unsafe {
csr::eem_transceiver::lane_sel_write(lane_no);
}
}
fn apply_delay(tap: u8) {
unsafe {
csr::eem_transceiver::dly_cnt_in_write(tap);
csr::eem_transceiver::dly_ld_write(1);
clock::spin_us(1);
assert!(tap as u8 == csr::eem_transceiver::dly_cnt_out_read());
}
}
fn apply_config(config: &SerdesConfig) {
for lane_no in 0..4 {
select_lane(lane_no as u8);
apply_delay(config.delay[lane_no]);
}
}
unsafe fn assign_delay() -> SerdesConfig {
// Select an appropriate delay for lane 0
select_lane(0);
let read_align = |dly: u8| -> f32 {
apply_delay(dly);
csr::eem_transceiver::counter_reset_write(1);
csr::eem_transceiver::counter_enable_write(1);
clock::spin_us(2000);
csr::eem_transceiver::counter_enable_write(0);
let (high, low) = (
csr::eem_transceiver::counter_high_count_read(),
csr::eem_transceiver::counter_low_count_read(),
);
if csr::eem_transceiver::counter_overflow_read() == 1 {
panic!("Unexpected phase detector counter overflow");
}
low as f32 / (low + high) as f32
};
let mut best_dly = None;
loop {
let mut prev = None;
for curr_dly in 0..32 {
let curr_low_rate = read_align(curr_dly);
if let Some(prev_low_rate) = prev {
// This is potentially a crossover position
if prev_low_rate <= curr_low_rate && curr_low_rate >= 0.5 {
let prev_dev = 0.5 - prev_low_rate;
let curr_dev = curr_low_rate - 0.5;
let selected_idx = if prev_dev < curr_dev {
curr_dly - 1
} else {
curr_dly
};
// The setup setup/hold calibration timing (even with
// tolerance) might be invalid in other lanes due to skew.
// 5 taps is very conservative, generally it is 1 or 2
if selected_idx < 5 {
prev = None;
continue;
} else {
best_dly = Some(selected_idx);
break;
}
}
}
// Only rising slope from <= 0.5 can result in a rising low rate
// crossover at 50%.
if curr_low_rate <= 0.5 {
prev = Some(curr_low_rate);
}
}
if best_dly.is_none() {
error!("setup/hold timing calibration failed, retry in 1s...");
clock::spin_us(1_000_000);
} else {
break;
}
}
let best_dly = best_dly.unwrap();
apply_delay(best_dly);
let mut delay_list = [best_dly; 4];
// Assign delay for other lanes
for lane_no in 1..=3 {
select_lane(lane_no as u8);
let mut min_deviation = 0.5;
let mut min_idx = 0;
for dly_delta in -3..=3 {
let index = (best_dly as isize + dly_delta) as u8;
let low_rate = read_align(index);
// abs() from f32 is not available in core library
let deviation = if low_rate < 0.5 {
0.5 - low_rate
} else {
low_rate - 0.5
};
if deviation < min_deviation {
min_deviation = deviation;
min_idx = index;
}
}
apply_delay(min_idx);
delay_list[lane_no] = min_idx;
}
debug!("setup/hold timing calibration: {:?}", delay_list);
SerdesConfig {
delay: delay_list,
}
}
unsafe fn align_comma() {
loop {
for slip in 1..=10 {
// The soft transceiver has 2 8b10b decoders, which receives lane
// 0/1 and lane 2/3 respectively. The decoder are time-multiplexed
// to decode exactly 1 lane each sysclk cycle.
//
// The decoder decodes lane 0/2 data on odd sysclk cycles, buffer
// on even cycles, and vice versa for lane 1/3. Data/Clock latency
// could change timing. The extend bit flips the decoding timing,
// so lane 0/2 data are decoded on even cycles, and lane 1/3 data
// are decoded on odd cycles.
//
// This is needed because transmitting/receiving a 8b10b character
// takes 2 sysclk cycles. Adjusting bitslip only via ISERDES
// limits the range to 1 cycle. The wordslip bit extends the range
// to 2 sysclk cycles.
csr::eem_transceiver::wordslip_write((slip > 5) as u8);
// Apply a double bitslip since the ISERDES is 2x oversampled.
// Bitslip is used for comma alignment purposes once setup/hold
// timing is met.
csr::eem_transceiver::bitslip_write(1);
csr::eem_transceiver::bitslip_write(1);
clock::spin_us(1);
csr::eem_transceiver::comma_align_reset_write(1);
clock::spin_us(100);
if csr::eem_transceiver::comma_read() == 1 {
debug!("comma alignment completed after {} bitslips", slip);
return;
}
}
error!("comma alignment failed, retrying in 1s...");
clock::spin_us(1_000_000);
}
}
pub fn init() {
for trx_no in 0..csr::CONFIG_EEM_TRANSCEIVERS {
unsafe {
csr::eem_transceiver::transceiver_sel_write(trx_no as u8);
}
let key = format!("eem_drtio_delay{}", trx_no);
config::read(&key, |r| {
match r {
Ok(record) => {
info!("loading calibrated timing values from flash");
unsafe {
apply_config(&*(record.as_ptr() as *const SerdesConfig));
}
},
Err(_) => {
info!("calibrating...");
let config = unsafe { assign_delay() };
config::write(&key, config.as_bytes()).unwrap();
}
}
});
unsafe {
align_comma();
csr::eem_transceiver::rx_ready_write(1);
}
}
}

View File

@ -12,6 +12,8 @@ extern crate log;
extern crate io; extern crate io;
extern crate board_misoc; extern crate board_misoc;
extern crate proto_artiq; extern crate proto_artiq;
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod spi; pub mod spi;
@ -29,3 +31,6 @@ pub mod grabber;
#[cfg(has_drtio)] #[cfg(has_drtio)]
pub mod drtioaux; pub mod drtioaux;
pub mod drtio_routing; pub mod drtio_routing;
#[cfg(all(has_drtio_eem, feature = "alloc"))]
pub mod drtio_eem;

View File

@ -26,7 +26,7 @@ io = { path = "../libio", features = ["byteorder"] }
alloc_list = { path = "../liballoc_list" } alloc_list = { path = "../liballoc_list" }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
logger_artiq = { path = "../liblogger_artiq" } logger_artiq = { path = "../liblogger_artiq" }
board_artiq = { path = "../libboard_artiq" } board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] } proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
riscv = { version = "0.6.0", features = ["inline-asm"] } riscv = { version = "0.6.0", features = ["inline-asm"] }

View File

@ -40,6 +40,8 @@ use board_artiq::drtioaux;
use board_artiq::drtio_routing; use board_artiq::drtio_routing;
use board_artiq::{mailbox, rpc_queue}; use board_artiq::{mailbox, rpc_queue};
use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_proto}; use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_proto};
#[cfg(has_drtio_eem)]
use board_artiq::drtio_eem;
#[cfg(has_rtio_analyzer)] #[cfg(has_rtio_analyzer)]
use proto_artiq::analyzer_proto; use proto_artiq::analyzer_proto;
@ -126,6 +128,9 @@ fn startup() {
} }
rtio_clocking::init(); rtio_clocking::init();
#[cfg(has_drtio_eem)]
drtio_eem::init();
let mut net_device = unsafe { ethmac::EthernetDevice::new() }; let mut net_device = unsafe { ethmac::EthernetDevice::new() };
net_device.reset_phy_if_any(); net_device.reset_phy_if_any();

View File

@ -15,7 +15,7 @@ build_misoc = { path = "../libbuild_misoc" }
[dependencies] [dependencies]
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] }
board_artiq = { path = "../libboard_artiq" } board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
alloc_list = { path = "../liballoc_list" } alloc_list = { path = "../liballoc_list" }
riscv = { version = "0.6.0", features = ["inline-asm"] } riscv = { version = "0.6.0", features = ["inline-asm"] }
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] } proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }

View File

@ -17,6 +17,7 @@ use board_artiq::si5324;
use board_artiq::{spi, drtioaux}; use board_artiq::{spi, drtioaux};
use board_artiq::drtio_routing; use board_artiq::drtio_routing;
use proto_artiq::drtioaux_proto::ANALYZER_MAX_SIZE; use proto_artiq::drtioaux_proto::ANALYZER_MAX_SIZE;
use board_artiq::drtio_eem;
use riscv::register::{mcause, mepc, mtval}; use riscv::register::{mcause, mepc, mtval};
use dma::Manager as DmaManager; use dma::Manager as DmaManager;
use analyzer::Analyzer; use analyzer::Analyzer;
@ -541,13 +542,16 @@ pub extern fn main() -> i32 {
io_expander.service().unwrap(); io_expander.service().unwrap();
} }
#[cfg(not(soc_platform = "efc"))] #[cfg(not(has_drtio_eem))]
unsafe { unsafe {
csr::drtio_transceiver::txenable_write(0xffffffffu32 as _); csr::drtio_transceiver::txenable_write(0xffffffffu32 as _);
} }
init_rtio_crg(); init_rtio_crg();
#[cfg(has_drtio_eem)]
drtio_eem::init();
#[cfg(has_drtio_routing)] #[cfg(has_drtio_routing)]
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()]; let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
#[cfg(not(has_drtio_routing))] #[cfg(not(has_drtio_routing))]

View File

@ -0,0 +1,481 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.cores.code_8b10b import SingleEncoder, Decoder
from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface
class RXSerdes(Module):
def __init__(self, i_pads):
self.rxdata = [ Signal(10) for _ in range(4) ]
self.ld = [ Signal() for _ in range(4) ]
self.cnt_in = [ Signal(5) for _ in range(4) ]
self.cnt_out = [ Signal(5) for _ in range(4) ]
self.bitslip = [ Signal() for _ in range(4) ]
ser_in_no_dly = [ Signal() for _ in range(4) ]
ser_in = [ Signal() for _ in range(4) ]
shifts = [ Signal(2) for _ in range(4) ]
for i in range(4):
self.specials += [
# Master deserializer
Instance("ISERDESE2",
p_DATA_RATE="DDR",
p_DATA_WIDTH=10,
p_INTERFACE_TYPE="NETWORKING",
p_NUM_CE=1,
p_SERDES_MODE="MASTER",
p_IOBDELAY="IFD",
o_Q1=self.rxdata[i][9],
o_Q2=self.rxdata[i][8],
o_Q3=self.rxdata[i][7],
o_Q4=self.rxdata[i][6],
o_Q5=self.rxdata[i][5],
o_Q6=self.rxdata[i][4],
o_Q7=self.rxdata[i][3],
o_Q8=self.rxdata[i][2],
o_SHIFTOUT1=shifts[i][0],
o_SHIFTOUT2=shifts[i][1],
i_DDLY=ser_in[i],
i_BITSLIP=self.bitslip[i],
i_CLK=ClockSignal("sys5x"),
i_CLKB=~ClockSignal("sys5x"),
i_CE1=1,
i_RST=ResetSignal(),
i_CLKDIV=ClockSignal()),
# Slave deserializer
Instance("ISERDESE2",
p_DATA_RATE="DDR",
p_DATA_WIDTH=10,
p_INTERFACE_TYPE="NETWORKING",
p_NUM_CE=1,
p_SERDES_MODE="SLAVE",
p_IOBDELAY="IFD",
o_Q3=self.rxdata[i][1],
o_Q4=self.rxdata[i][0],
i_BITSLIP=self.bitslip[i],
i_CLK=ClockSignal("sys5x"),
i_CLKB=~ClockSignal("sys5x"),
i_CE1=1,
i_RST=ResetSignal(),
i_CLKDIV=ClockSignal(),
i_SHIFTIN1=shifts[i][0],
i_SHIFTIN2=shifts[i][1]),
# Tunable delay
# IDELAYCTRL is with the clocking
Instance("IDELAYE2",
p_DELAY_SRC="IDATAIN",
p_SIGNAL_PATTERN="DATA",
p_CINVCTRL_SEL="FALSE",
p_HIGH_PERFORMANCE_MODE="TRUE",
# REFCLK refers to the clock source of IDELAYCTRL
p_REFCLK_FREQUENCY=200.0,
p_PIPE_SEL="FALSE",
p_IDELAY_TYPE="VAR_LOAD",
p_IDELAY_VALUE=0,
i_C=ClockSignal(),
i_LD=self.ld[i],
i_CE=0,
i_LDPIPEEN=0,
i_INC=1, # Always increment
# Set the optimal delay tap via the aligner
i_CNTVALUEIN=self.cnt_in[i],
# Allow the aligner to check the tap value
o_CNTVALUEOUT=self.cnt_out[i],
i_IDATAIN=ser_in_no_dly[i],
o_DATAOUT=ser_in[i]
),
# IOB
Instance("IBUFDS",
p_DIFF_TERM="TRUE",
i_I=i_pads.p[i],
i_IB=i_pads.n[i],
o_O=ser_in_no_dly[i],
)
]
class TXSerdes(Module):
def __init__(self, o_pads):
self.txdata = [ Signal(5) for _ in range(4) ]
ser_out = [ Signal() for _ in range(4) ]
t_out = [ Signal() for _ in range(4) ]
for i in range(4):
self.specials += [
# Serializer
Instance("OSERDESE2",
p_DATA_RATE_OQ="SDR", p_DATA_RATE_TQ="BUF",
p_DATA_WIDTH=5, p_TRISTATE_WIDTH=1,
p_INIT_OQ=0b00000,
o_OQ=ser_out[i],
o_TQ=t_out[i],
i_RST=ResetSignal(),
i_CLK=ClockSignal("sys5x"),
i_CLKDIV=ClockSignal(),
i_D1=self.txdata[i][0],
i_D2=self.txdata[i][1],
i_D3=self.txdata[i][2],
i_D4=self.txdata[i][3],
i_D5=self.txdata[i][4],
i_TCE=1, i_OCE=1,
i_T1=0
),
# IOB
Instance("OBUFTDS",
i_I=ser_out[i],
o_O=o_pads.p[i],
o_OB=o_pads.n[i],
i_T=t_out[i],
)
]
# This module owns 2 8b10b encoders, each encoder route codewords to 2 lanes,
# through time multiplexing. The scheduler releases 2 bytes every clock cycle,
# and the encoders each encode 1 byte.
#
# Since each lane only transmits 5 bits per sysclk cycle, the encoder selects
# a lane to first transmit the least significant word (LSW, 5 bits), and send
# the rest in the next cycle using the same lane. It takes advantage of the
# arrival sequence of bytes from the scrambler to achieve the transmission
# pattern shown in the MultiDecoder module.
class MultiEncoder(Module):
def __init__(self):
# Keep the link layer interface identical to standard encoders
self.d = [ Signal(8) for _ in range(2) ]
self.k = [ Signal() for _ in range(2) ]
# Output interface
self.output = [ [ Signal(5) for _ in range(2) ] for _ in range(2) ]
# Clock enable signal
# Alternate between sending encoded character to EEM 0/2 and EEM 1/3
# every cycle
self.clk_div2 = Signal()
# Intermediate registers for output and disparity
# More significant bits are buffered due to channel geometry
# Disparity bit is delayed. The same encoder is shared by 2 SERDES
output_bufs = [ Signal(5) for _ in range(2) ]
disp_bufs = [ Signal() for _ in range(2) ]
encoders = [ SingleEncoder() for _ in range(2) ]
self.submodules += encoders
# Encoded characters are routed to the EEM pairs:
# The first character goes through EEM 0/2
# The second character goes through EEM 1/3, and repeat...
# Lower order bits go first, so higher order bits are buffered and
# transmitted in the next cycle.
for d, k, output, output_buf, disp_buf, encoder in \
zip(self.d, self.k, self.output, output_bufs, disp_bufs, encoders):
self.comb += [
encoder.d.eq(d),
encoder.k.eq(k),
If(self.clk_div2,
output[0].eq(encoder.output[0:5]),
output[1].eq(output_buf),
).Else(
output[0].eq(output_buf),
output[1].eq(encoder.output[0:5]),
),
]
# Handle intermediate registers
self.sync += [
disp_buf.eq(encoder.disp_out),
encoder.disp_in.eq(disp_buf),
output_buf.eq(encoder.output[5:10]),
]
# Owns 2 8b10b decoders, each decodes data from lane 0/1 and lane 2/3class
# respectively. The decoders are time multiplexed among the 2 lanes, and
# each decoder decodes exactly 1 lane per sysclk cycle.
#
# The transmitter could send the following data pattern over the 4 lanes.
# Capital letters denote the most significant word (MSW); The lowercase denote
# the least significant word (LSW) of the same 8b10b character.
#
# Cycle \ Lane 0 1 2 3
# 0 a Y b Z
# 1 A c B d
# 2 a' C b' D
# 3 A' c' B' d'
#
# Lane 0/2 and lane 1/3 transmit word of different significance by design (see
# MultiEncoder).
#
# This module buffers the LSW, and immediately send the whole 8b10b character
# to the coresponding decoder once the MSW is also received.
class MultiDecoder(Module):
def __init__(self):
self.raw_input = [ Signal(5) for _ in range(2) ]
self.d = Signal(8)
self.k = Signal()
# Clock enable signal
# Alternate between decoding encoded character from EEM 0/2 and
# EEM 1/3 every cycle
self.clk_div2 = Signal()
# Extended bitslip mechanism. ISERDESE2 bitslip can only adjust bit
# position by 5 bits (1 cycle). However, an encoded character takes 2
# cycles to transmit/receive. The module needs to correctly reassemble
# the 8b10b character. This is useful received waveform is the 1-cycle
# delayed version of the above waveform. The same scheme would
# incorrectly buffer words and create wrong symbols.
#
# Hence, wordslip put LSW as MSW and vice versa, effectively injects
# an additional 5 bit positions worth of bitslips.
self.wordslip = Signal()
# Intermediate register for input
buffer = Signal(5)
self.submodules.decoder = Decoder()
# The decoder does the following actions:
# - Process received characters from EEM 0/2
# - Same, but from EEM 1/3
#
# Wordslipping is equivalent to swapping task between clock cycles.
# (i.e. Swap processing target. Instead of processing EEM 0/2, process
# EEM 1/3, and vice versa on the next cycle.) This effectively shifts
# the processing time of any encoded character by 1 clock cycle (5
# bitslip equivalent without considering oversampling, 10 otherwise).
self.sync += [
If(self.clk_div2 ^ self.wordslip,
buffer.eq(self.raw_input[1])
).Else(
buffer.eq(self.raw_input[0])
)
]
self.comb += [
If(self.clk_div2 ^ self.wordslip,
self.decoder.input.eq(Cat(buffer, self.raw_input[0]))
).Else(
self.decoder.input.eq(Cat(buffer, self.raw_input[1]))
)
]
self.comb += [
self.d.eq(self.decoder.d),
self.k.eq(self.decoder.k),
]
class BangBangPhaseDetector(Module):
def __init__(self):
self.s = Signal(3)
self.high = Signal()
self.low = Signal()
self.comb += If(~self.s[0] & self.s[2],
self.high.eq(self.s[1]),
self.low.eq(~self.s[1]),
).Else(
self.high.eq(0),
self.low.eq(0),
)
class PhaseErrorCounter(Module, AutoCSR):
def __init__(self):
self.high_count = CSRStatus(18)
self.low_count = CSRStatus(18)
# Odd indices are always oversampled bits
self.rxdata = Signal(10)
# Measure setup/hold timing, count phase error in the following
self.submodules.detector = BangBangPhaseDetector()
self.comb += self.detector.s.eq(self.rxdata[:3])
self.reset = CSR()
self.enable = CSRStorage()
self.overflow = CSRStatus()
high_carry = Signal()
low_carry = Signal()
self.sync += [
If(self.reset.re,
self.high_count.status.eq(0),
self.low_count.status.eq(0),
high_carry.eq(0),
low_carry.eq(0),
self.overflow.status.eq(0),
).Elif(self.enable.storage,
Cat(self.high_count.status, high_carry).eq(
self.high_count.status + self.detector.high),
Cat(self.low_count.status, low_carry).eq(
self.low_count.status + self.detector.low),
If(high_carry | low_carry, self.overflow.status.eq(1)),
)
]
class SerdesSingle(Module):
def __init__(self, i_pads, o_pads):
# Serdes modules
self.submodules.rx_serdes = RXSerdes(i_pads)
self.submodules.tx_serdes = TXSerdes(o_pads)
self.lane_sel = Signal(2)
self.bitslip = Signal()
for i in range(4):
self.comb += self.rx_serdes.bitslip[i].eq(self.bitslip)
self.dly_cnt_in = Signal(5)
self.dly_ld = Signal()
for i in range(4):
self.comb += [
self.rx_serdes.cnt_in[i].eq(self.dly_cnt_in),
self.rx_serdes.ld[i].eq((self.lane_sel == i) & self.dly_ld),
]
self.dly_cnt_out = Signal(5)
self.comb += Case(self.lane_sel, {
idx: self.dly_cnt_out.eq(self.rx_serdes.cnt_out[idx]) for idx in range(4)
})
self.wordslip = Signal()
# Encoder/Decoder interfaces
self.submodules.encoder = MultiEncoder()
self.submodules.decoders = decoders = Array(MultiDecoder() for _ in range(2))
self.comb += [
decoders[0].wordslip.eq(self.wordslip),
decoders[1].wordslip.eq(self.wordslip),
]
# Route encoded symbols to TXSerdes, decoded symbols from RXSerdes
for i in range(4):
self.comb += [
self.tx_serdes.txdata[i].eq(self.encoder.output[i//2][i%2]),
decoders[i//2].raw_input[i%2].eq(self.rx_serdes.rxdata[i][0::2]),
]
self.clk_div2 = Signal()
self.comb += [
self.encoder.clk_div2.eq(self.clk_div2),
self.decoders[0].clk_div2.eq(self.clk_div2),
self.decoders[1].clk_div2.eq(self.clk_div2),
]
# Monitor lane 0 decoder output for bitslip alignment
self.comma_align_reset = Signal()
self.comma = Signal()
self.sync += If(self.comma_align_reset,
self.comma.eq(0),
).Elif(~self.comma,
self.comma.eq(
((decoders[0].d == 0x3C) | (decoders[0].d == 0xBC))
& decoders[0].k))
class EEMSerdes(Module, TransceiverInterface, AutoCSR):
def __init__(self, platform, data_pads):
self.rx_ready = CSRStorage()
self.transceiver_sel = CSRStorage(max(1, log2_int(len(data_pads))))
self.lane_sel = CSRStorage(2)
self.bitslip = CSR()
self.dly_cnt_in = CSRStorage(5)
self.dly_ld = CSR()
self.dly_cnt_out = CSRStatus(5)
# Slide a word back/forward by 1 cycle, shared by all lanes of the
# same transceiver. This is to determine if this cycle should decode
# lane 0/2 or lane 1/3. See MultiEncoder/MultiDecoder for the full
# scheme & timing.
self.wordslip = CSRStorage()
# Monitor lane 0 decoder output for bitslip alignment
self.comma_align_reset = CSR()
self.comma = CSRStatus()
clk_div2 = Signal()
self.sync += clk_div2.eq(~clk_div2)
channel_interfaces = []
serdes_list = []
for i_pads, o_pads in data_pads:
serdes = SerdesSingle(i_pads, o_pads)
self.comb += serdes.clk_div2.eq(clk_div2)
serdes_list.append(serdes)
chan_if = ChannelInterface(serdes.encoder, serdes.decoders)
self.comb += chan_if.rx_ready.eq(self.rx_ready.storage)
channel_interfaces.append(chan_if)
# Route CSR signals using transceiver_sel
self.comb += Case(self.transceiver_sel.storage, {
trx_no: [
serdes.bitslip.eq(self.bitslip.re),
serdes.dly_ld.eq(self.dly_ld.re),
self.dly_cnt_out.status.eq(serdes.dly_cnt_out),
self.comma.status.eq(serdes.comma),
] for trx_no, serdes in enumerate(serdes_list)
})
# Wordslip needs to be latched. It needs to hold when calibrating
# other transceivers and/or after calibration.
self.sync += If(self.wordslip.re,
Case(self.transceiver_sel.storage, {
trx_no: [
serdes.wordslip.eq(self.wordslip.storage)
] for trx_no, serdes in enumerate(serdes_list)
})
)
for serdes in serdes_list:
self.comb += [
# Delay counter write only comes into effect after dly_ld
# So, just MUX dly_ld instead.
serdes.dly_cnt_in.eq(self.dly_cnt_in.storage),
# Comma align reset & lane selection can be broadcasted
# without MUXing. Transceivers are aligned one-by-one
serdes.lane_sel.eq(self.lane_sel.storage),
serdes.comma_align_reset.eq(self.comma_align_reset.re),
]
# Setup/hold timing calibration module
self.submodules.counter = PhaseErrorCounter()
self.comb += Case(self.transceiver_sel.storage, {
trx_no: Case(self.lane_sel.storage, {
lane_idx: self.counter.rxdata.eq(serdes.rx_serdes.rxdata[lane_idx])
for lane_idx in range(4)
}) for trx_no, serdes in enumerate(serdes_list)
})
self.submodules += serdes_list
TransceiverInterface.__init__(self, channel_interfaces)
for i in range(len(serdes_list)):
self.comb += [
getattr(self, "cd_rtio_rx" + str(i)).clk.eq(ClockSignal()),
getattr(self, "cd_rtio_rx" + str(i)).rst.eq(ResetSignal())
]

View File

@ -757,3 +757,31 @@ class HVAmp(_EEM):
phy = ttl_out_cls(pads.p, pads.n) phy = ttl_out_cls(pads.p, pads.n)
target.submodules += phy target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy)) target.rtio_channels.append(rtio.Channel.from_phy(phy))
class EFC(_EEM):
@staticmethod
def io(eem, iostandard=default_iostandard):
# Master: Pair 0~3 data IN, 4~7 OUT
data_in = ("efc{}_drtio_rx".format(eem), 0,
Subsignal("p", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "p") for i in range(4)
]))),
Subsignal("n", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "n") for i in range(4)
]))),
iostandard(eem),
Misc("DIFF_TERM=TRUE"),
)
data_out = ("efc{}_drtio_tx".format(eem), 0,
Subsignal("p", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "p") for i in range(4, 8)
]))),
Subsignal("n", Pins("{} {} {} {}".format(*[
_eem_pin(eem, i, "n") for i in range(4, 8)
]))),
iostandard(eem),
)
return [data_in, data_out]