-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
35 changed files
with
4,112 additions
and
242 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# This is an example device database that needs to be adapted to your setup. | ||
# The RTIO channel numbers here are for NIST CLOCK on KC705. | ||
# The list of devices here is not exhaustive. | ||
|
||
{ | ||
"comm": { | ||
"type": "local", | ||
"module": "artiq.coredevice.comm_tcp", | ||
"class": "Comm", | ||
"arguments": {"host": "kc705.lab.m-labs.hk"} | ||
}, | ||
"core": { | ||
"type": "local", | ||
"module": "artiq.coredevice.core", | ||
"class": "Core", | ||
"arguments": {"ref_period": 2e-9} | ||
}, | ||
"core_cache": { | ||
"type": "local", | ||
"module": "artiq.coredevice.cache", | ||
"class": "CoreCache" | ||
}, | ||
|
||
"rled0": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0}, | ||
}, | ||
"rled1": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 1}, | ||
}, | ||
"rled2": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 2}, | ||
}, | ||
"rled3": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 3}, | ||
}, | ||
"rled4": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 4}, | ||
}, | ||
"rled5": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 5}, | ||
}, | ||
"rled6": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 6}, | ||
}, | ||
"rled7": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 7}, | ||
}, | ||
|
||
"rsmap": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 8} | ||
}, | ||
"rsman": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 9} | ||
}, | ||
|
||
"led0": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010000}, | ||
}, | ||
"led1": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010001}, | ||
}, | ||
"led2": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010002}, | ||
}, | ||
"led3": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010003}, | ||
}, | ||
"led4": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010004}, | ||
}, | ||
"led5": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010005}, | ||
}, | ||
"led6": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010006}, | ||
}, | ||
"led7": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010007}, | ||
}, | ||
|
||
"smap": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010008} | ||
}, | ||
"sman": { | ||
"type": "local", | ||
"module": "artiq.coredevice.ttl", | ||
"class": "TTLOut", | ||
"arguments": {"channel": 0x010009} | ||
}, | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from artiq.experiment import * | ||
|
||
|
||
class BlinkForever(EnvExperiment): | ||
def build(self): | ||
self.setattr_device("core") | ||
self.rleds = [self.get_device("rled" + str(i)) for i in range(8)] | ||
self.leds = [self.get_device("led" + str(i)) for i in range(8)] | ||
|
||
@kernel | ||
def run(self): | ||
self.core.reset() | ||
|
||
while True: | ||
with parallel: | ||
for led in self.leds: | ||
led.pulse(250*ms) | ||
for led in self.rleds: | ||
led.pulse(250*ms) | ||
t = now_mu() | ||
for led in self.leds: | ||
at_mu(t) | ||
led.pulse(500*ms) | ||
for led in self.rleds: | ||
at_mu(t) | ||
led.pulse(500*ms) | ||
delay(250*ms) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from artiq.experiment import * | ||
|
||
|
||
class PulseRate(EnvExperiment): | ||
def build(self): | ||
self.setattr_device("core") | ||
self.setattr_device("rsmap") | ||
|
||
@kernel | ||
def run(self): | ||
self.core.reset() | ||
|
||
dt = self.core.seconds_to_mu(300*ns) | ||
while True: | ||
for i in range(10000): | ||
try: | ||
self.rsmap.pulse_mu(dt) | ||
delay_mu(dt) | ||
except RTIOUnderflow: | ||
dt += 1 | ||
self.core.break_realtime() | ||
break | ||
else: | ||
print(self.core.mu_to_seconds(dt)) | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from artiq.gateware.drtio.core import DRTIOSatellite, DRTIOMaster | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
from migen import * | ||
from migen.fhdl.simplify import FullMemoryWE | ||
from migen.genlib.cdc import MultiReg, PulseSynchronizer | ||
|
||
from misoc.interconnect.csr import * | ||
from misoc.interconnect import stream | ||
from misoc.interconnect import wishbone | ||
|
||
|
||
max_packet = 1024 | ||
|
||
|
||
class Transmitter(Module, AutoCSR): | ||
def __init__(self, link_layer, min_mem_dw): | ||
ll_dw = len(link_layer.tx_aux_data) | ||
mem_dw = max(min_mem_dw, ll_dw) | ||
|
||
self.aux_tx_length = CSRStorage(bits_for(max_packet), | ||
alignment_bits=log2_int(mem_dw//8)) | ||
self.aux_tx = CSR() | ||
self.specials.mem = Memory(mem_dw, max_packet//(mem_dw//8)) | ||
|
||
converter = stream.Converter(mem_dw, ll_dw) | ||
self.submodules += converter | ||
|
||
# when continuously fed, the Converter outputs data continuously | ||
self.comb += [ | ||
converter.source.ack.eq(link_layer.tx_aux_ack), | ||
link_layer.tx_aux_frame.eq(converter.source.stb), | ||
link_layer.tx_aux_data.eq(converter.source.data) | ||
] | ||
|
||
seen_eop_rst = Signal() | ||
frame_r = Signal() | ||
seen_eop = Signal() | ||
self.sync.rtio += [ | ||
If(link_layer.tx_aux_ack, | ||
frame_r.eq(link_layer.tx_aux_frame), | ||
If(frame_r & ~link_layer.tx_aux_frame, seen_eop.eq(1)) | ||
), | ||
If(seen_eop_rst, seen_eop.eq(0)) | ||
] | ||
|
||
mem_port = self.mem.get_port(clock_domain="rtio") | ||
self.specials += mem_port | ||
|
||
self.aux_tx_length.storage.attr.add("no_retiming") | ||
tx_length = Signal(bits_for(max_packet)) | ||
self.specials += MultiReg(self.aux_tx_length.storage, tx_length, "rtio") | ||
|
||
frame_counter_nbits = bits_for(max_packet) - log2_int(mem_dw//8) | ||
frame_counter = Signal(frame_counter_nbits) | ||
frame_counter_next = Signal(frame_counter_nbits) | ||
frame_counter_ce = Signal() | ||
frame_counter_rst = Signal() | ||
self.comb += [ | ||
frame_counter_next.eq(frame_counter), | ||
If(frame_counter_rst, | ||
frame_counter_next.eq(0) | ||
).Elif(frame_counter_ce, | ||
frame_counter_next.eq(frame_counter + 1) | ||
), | ||
mem_port.adr.eq(frame_counter_next), | ||
converter.sink.data.eq(mem_port.dat_r) | ||
] | ||
self.sync.rtio += frame_counter.eq(frame_counter_next) | ||
|
||
start_tx = PulseSynchronizer("sys", "rtio") | ||
tx_done = PulseSynchronizer("rtio", "sys") | ||
self.submodules += start_tx, tx_done | ||
self.comb += start_tx.i.eq(self.aux_tx.re) | ||
self.sync += [ | ||
If(tx_done.o, self.aux_tx.w.eq(0)), | ||
If(self.aux_tx.re, self.aux_tx.w.eq(1)) | ||
] | ||
|
||
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="IDLE")) | ||
self.submodules += fsm | ||
|
||
fsm.act("IDLE", | ||
frame_counter_rst.eq(1), | ||
seen_eop_rst.eq(1), | ||
If(start_tx.o, NextState("TRANSMIT")) | ||
) | ||
fsm.act("TRANSMIT", | ||
converter.sink.stb.eq(1), | ||
If(converter.sink.ack, | ||
frame_counter_ce.eq(1) | ||
), | ||
If(frame_counter_next == tx_length, NextState("WAIT_INTERFRAME")) | ||
) | ||
fsm.act("WAIT_INTERFRAME", | ||
If(seen_eop, | ||
tx_done.i.eq(1), | ||
NextState("IDLE") | ||
) | ||
) | ||
|
||
|
||
class Receiver(Module, AutoCSR): | ||
def __init__(self, link_layer, min_mem_dw): | ||
self.aux_rx_length = CSRStatus(bits_for(max_packet)) | ||
self.aux_rx_present = CSR() | ||
self.aux_rx_error = CSR() | ||
|
||
ll_dw = len(link_layer.rx_aux_data) | ||
mem_dw = max(min_mem_dw, ll_dw) | ||
self.specials.mem = Memory(mem_dw, max_packet//(mem_dw//8)) | ||
|
||
converter = stream.Converter(ll_dw, mem_dw) | ||
self.submodules += converter | ||
|
||
# when continuously drained, the Converter accepts data continuously | ||
frame_r = Signal() | ||
self.sync.rtio_rx += [ | ||
If(link_layer.rx_aux_stb, | ||
frame_r.eq(link_layer.rx_aux_frame), | ||
converter.sink.data.eq(link_layer.rx_aux_data) | ||
) | ||
] | ||
self.comb += [ | ||
converter.sink.stb.eq(link_layer.rx_aux_stb & frame_r), | ||
converter.sink.eop.eq(converter.sink.stb & ~link_layer.rx_aux_frame) | ||
] | ||
|
||
mem_port = self.mem.get_port(write_capable=True, clock_domain="rtio_rx") | ||
self.specials += mem_port | ||
|
||
frame_counter_nbits = bits_for(max_packet) - log2_int(mem_dw//8) | ||
frame_counter = Signal(frame_counter_nbits) | ||
self.comb += [ | ||
mem_port.adr.eq(frame_counter), | ||
mem_port.dat_w.eq(converter.source.data), | ||
converter.source.ack.eq(1) | ||
] | ||
|
||
frame_counter.attr.add("no_retiming") | ||
frame_counter_sys = Signal(frame_counter_nbits) | ||
self.specials += MultiReg(frame_counter, frame_counter_sys) | ||
self.comb += self.aux_rx_length.status.eq(frame_counter_sys << log2_int(mem_dw//8)) | ||
|
||
signal_frame = PulseSynchronizer("rtio_rx", "sys") | ||
frame_ack = PulseSynchronizer("sys", "rtio_rx") | ||
signal_error = PulseSynchronizer("rtio_rx", "sys") | ||
self.submodules += signal_frame, frame_ack, signal_error | ||
self.sync += [ | ||
If(self.aux_rx_present.re, self.aux_rx_present.w.eq(0)), | ||
If(signal_frame.o, self.aux_rx_present.w.eq(1)), | ||
If(self.aux_rx_error.re, self.aux_rx_error.w.eq(0)), | ||
If(signal_error.o, self.aux_rx_error.w.eq(1)) | ||
] | ||
self.comb += frame_ack.i.eq(self.aux_rx_present.re) | ||
|
||
fsm = ClockDomainsRenamer("rtio_rx")(FSM(reset_state="IDLE")) | ||
self.submodules += fsm | ||
|
||
sop = Signal(reset=1) | ||
self.sync.rtio_rx += \ | ||
If(converter.source.stb, | ||
If(converter.source.eop, | ||
sop.eq(1) | ||
).Else( | ||
sop.eq(0) | ||
) | ||
) | ||
|
||
fsm.act("IDLE", | ||
If(converter.source.stb & sop, | ||
NextValue(frame_counter, frame_counter + 1), | ||
mem_port.we.eq(1), | ||
If(converter.source.eop, | ||
NextState("SIGNAL_FRAME") | ||
).Else( | ||
NextState("FRAME") | ||
) | ||
).Else( | ||
NextValue(frame_counter, 0) | ||
) | ||
) | ||
fsm.act("FRAME", | ||
If(converter.source.stb, | ||
NextValue(frame_counter, frame_counter + 1), | ||
mem_port.we.eq(1), | ||
If(frame_counter == max_packet, | ||
mem_port.we.eq(0), | ||
signal_error.i.eq(1), | ||
NextState("IDLE") # discard the rest of the frame | ||
), | ||
If(converter.source.eop, | ||
NextState("SIGNAL_FRAME") | ||
) | ||
) | ||
) | ||
fsm.act("SIGNAL_FRAME", | ||
signal_frame.i.eq(1), | ||
NextState("WAIT_ACK"), | ||
If(converter.source.stb, signal_error.i.eq(1)) | ||
) | ||
fsm.act("WAIT_ACK", | ||
If(frame_ack.o, | ||
NextValue(frame_counter, 0), | ||
NextState("IDLE") | ||
), | ||
If(converter.source.stb, signal_error.i.eq(1)) | ||
) | ||
|
||
|
||
# TODO: FullMemoryWE should be applied by migen.build | ||
@FullMemoryWE() | ||
class AuxController(Module): | ||
def __init__(self, link_layer): | ||
self.bus = wishbone.Interface() | ||
self.submodules.transmitter = Transmitter(link_layer, len(self.bus.dat_w)) | ||
self.submodules.receiver = Receiver(link_layer, len(self.bus.dat_w)) | ||
|
||
tx_sdram_if = wishbone.SRAM(self.transmitter.mem, read_only=False) | ||
rx_sdram_if = wishbone.SRAM(self.receiver.mem, read_only=True) | ||
wsb = log2_int(len(self.bus.dat_w)//8) | ||
decoder = wishbone.Decoder(self.bus, | ||
[(lambda a: a[log2_int(max_packet)-wsb] == 0, tx_sdram_if.bus), | ||
(lambda a: a[log2_int(max_packet)-wsb] == 1, rx_sdram_if.bus)], | ||
register=True) | ||
self.submodules += tx_sdram_if, rx_sdram_if, decoder | ||
|
||
def get_csrs(self): | ||
return self.transmitter.get_csrs() + self.receiver.get_csrs() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from types import SimpleNamespace | ||
|
||
from migen import * | ||
|
||
from artiq.gateware.drtio import link_layer, rt_packets, iot, rt_controller, aux_controller | ||
|
||
|
||
class DRTIOSatellite(Module): | ||
def __init__(self, transceiver, rx_synchronizer, channels, fine_ts_width=3, full_ts_width=63): | ||
self.submodules.link_layer = link_layer.LinkLayer( | ||
transceiver.encoder, transceiver.decoders) | ||
self.comb += self.link_layer.rx_ready.eq(transceiver.rx_ready) | ||
|
||
link_layer_sync = SimpleNamespace( | ||
tx_aux_frame=self.link_layer.tx_aux_frame, | ||
tx_aux_data=self.link_layer.tx_aux_data, | ||
tx_aux_ack=self.link_layer.tx_aux_ack, | ||
tx_rt_frame=self.link_layer.tx_rt_frame, | ||
tx_rt_data=self.link_layer.tx_rt_data, | ||
|
||
rx_aux_stb=rx_synchronizer.resync(self.link_layer.rx_aux_stb), | ||
rx_aux_frame=rx_synchronizer.resync(self.link_layer.rx_aux_frame), | ||
rx_aux_data=rx_synchronizer.resync(self.link_layer.rx_aux_data), | ||
rx_rt_frame=rx_synchronizer.resync(self.link_layer.rx_rt_frame), | ||
rx_rt_data=rx_synchronizer.resync(self.link_layer.rx_rt_data) | ||
) | ||
self.submodules.rt_packets = ClockDomainsRenamer("rtio")( | ||
rt_packets.RTPacketSatellite(link_layer_sync)) | ||
|
||
self.submodules.iot = iot.IOT( | ||
self.rt_packets, channels, fine_ts_width, full_ts_width) | ||
|
||
self.clock_domains.cd_rio = ClockDomain() | ||
self.clock_domains.cd_rio_phy = ClockDomain() | ||
self.comb += [ | ||
self.cd_rio.clk.eq(ClockSignal("rtio")), | ||
self.cd_rio.rst.eq(self.rt_packets.reset), | ||
self.cd_rio_phy.clk.eq(ClockSignal("rtio")), | ||
self.cd_rio_phy.rst.eq(self.rt_packets.reset_phy), | ||
] | ||
|
||
self.submodules.aux_controller = aux_controller.AuxController( | ||
self.link_layer) | ||
|
||
def get_csrs(self): | ||
return self.aux_controller.get_csrs() | ||
|
||
|
||
class DRTIOMaster(Module): | ||
def __init__(self, transceiver, channel_count=1024, fine_ts_width=3): | ||
self.submodules.link_layer = link_layer.LinkLayer( | ||
transceiver.encoder, transceiver.decoders) | ||
self.comb += self.link_layer.rx_ready.eq(transceiver.rx_ready) | ||
|
||
self.submodules.rt_packets = rt_packets.RTPacketMaster(self.link_layer) | ||
self.submodules.rt_controller = rt_controller.RTController( | ||
self.rt_packets, channel_count, fine_ts_width) | ||
self.submodules.rt_manager = rt_controller.RTManager(self.rt_packets) | ||
self.cri = self.rt_controller.cri | ||
|
||
self.submodules.aux_controller = aux_controller.AuxController( | ||
self.link_layer) | ||
|
||
def get_csrs(self): | ||
return (self.link_layer.get_csrs() + | ||
self.rt_controller.get_csrs() + | ||
self.rt_manager.get_csrs() + | ||
self.aux_controller.get_csrs()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from migen import * | ||
from migen.genlib.fifo import SyncFIFOBuffered | ||
from migen.genlib.record import * | ||
|
||
from artiq.gateware.rtio import rtlink | ||
|
||
|
||
class IOT(Module): | ||
def __init__(self, rt_packets, channels, max_fine_ts_width, full_ts_width): | ||
tsc = Signal(full_ts_width - max_fine_ts_width) | ||
self.sync.rtio += \ | ||
If(rt_packets.tsc_load, | ||
tsc.eq(rt_packets.tsc_value) | ||
).Else( | ||
tsc.eq(tsc + 1) | ||
) | ||
|
||
for n, channel in enumerate(channels): | ||
interface = channel.interface.o | ||
data_width = rtlink.get_data_width(interface) | ||
address_width = rtlink.get_address_width(interface) | ||
fine_ts_width = rtlink.get_fine_ts_width(interface) | ||
assert fine_ts_width <= max_fine_ts_width | ||
|
||
# FIFO | ||
ev_layout = [] | ||
if data_width: | ||
ev_layout.append(("data", data_width)) | ||
if address_width: | ||
ev_layout.append(("address", address_width)) | ||
ev_layout.append(("timestamp", len(tsc) + fine_ts_width)) | ||
|
||
fifo = ClockDomainsRenamer("rio")( | ||
SyncFIFOBuffered(layout_len(ev_layout), channel.ofifo_depth)) | ||
self.submodules += fifo | ||
fifo_in = Record(ev_layout) | ||
fifo_out = Record(ev_layout) | ||
self.comb += [ | ||
fifo.din.eq(fifo_in.raw_bits()), | ||
fifo_out.raw_bits().eq(fifo.dout) | ||
] | ||
|
||
# FIFO level | ||
self.sync.rio += \ | ||
If(rt_packets.fifo_space_update & | ||
(rt_packets.fifo_space_channel == n), | ||
rt_packets.fifo_space.eq(channel.ofifo_depth - fifo.level)) | ||
|
||
# FIFO write | ||
self.comb += fifo.we.eq(rt_packets.write_stb | ||
& (rt_packets.write_channel == n)) | ||
self.sync.rio += [ | ||
If(rt_packets.write_overflow_ack, | ||
rt_packets.write_overflow.eq(0)), | ||
If(rt_packets.write_underflow_ack, | ||
rt_packets.write_underflow.eq(0)), | ||
If(fifo.we, | ||
If(~fifo.writable, rt_packets.write_overflow.eq(1)), | ||
If(rt_packets.write_timestamp[max_fine_ts_width:] < (tsc + 4), | ||
rt_packets.write_underflow.eq(1) | ||
) | ||
) | ||
] | ||
if data_width: | ||
self.comb += fifo_in.data.eq(rt_packets.write_data) | ||
if address_width: | ||
self.comb += fifo_in.address.eq(rt_packets.write_address) | ||
self.comb += fifo_in.timestamp.eq( | ||
rt_packets.write_timestamp[max_fine_ts_width-fine_ts_width:]) | ||
|
||
# FIFO read | ||
self.sync.rio += [ | ||
fifo.re.eq(0), | ||
interface.stb.eq(0), | ||
If(fifo.readable & | ||
(fifo_out.timestamp[fine_ts_width:] == tsc), | ||
fifo.re.eq(1), | ||
interface.stb.eq(1) | ||
) | ||
] | ||
if data_width: | ||
self.sync.rio += interface.data.eq(fifo_out.data) | ||
if address_width: | ||
self.sync.rio += interface.address.eq(fifo_out.address) | ||
if fine_ts_width: | ||
self.sync.rio += interface.fine_ts.eq(fifo_out.timestamp[:fine_ts_width]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
from functools import reduce | ||
from operator import xor, or_ | ||
|
||
from migen import * | ||
from migen.genlib.fsm import * | ||
from migen.genlib.cdc import MultiReg | ||
from migen.genlib.misc import WaitTimer | ||
|
||
from misoc.interconnect.csr import * | ||
|
||
|
||
class Scrambler(Module): | ||
def __init__(self, n_io1, n_io2, n_state=23, taps=[17, 22]): | ||
self.i1 = Signal(n_io1) | ||
self.o1 = Signal(n_io1) | ||
self.i2 = Signal(n_io2) | ||
self.o2 = Signal(n_io2) | ||
self.sel = Signal() | ||
|
||
# # # | ||
|
||
state = Signal(n_state, reset=1) | ||
|
||
stmts1 = [] | ||
stmts2 = [] | ||
for stmts, si, so in ((stmts1, self.i1, self.o1), | ||
(stmts2, self.i2, self.o2)): | ||
curval = [state[i] for i in range(n_state)] | ||
for i in reversed(range(len(si))): | ||
out = si[i] ^ reduce(xor, [curval[tap] for tap in taps]) | ||
stmts += [so[i].eq(out)] | ||
curval.insert(0, out) | ||
curval.pop() | ||
|
||
stmts += [state.eq(Cat(*curval[:n_state]))] | ||
|
||
self.sync += If(self.sel, stmts2).Else(stmts1) | ||
|
||
|
||
class Descrambler(Module): | ||
def __init__(self, n_io1, n_io2, n_state=23, taps=[17, 22]): | ||
self.i1 = Signal(n_io1) | ||
self.o1 = Signal(n_io1) | ||
self.i2 = Signal(n_io2) | ||
self.o2 = Signal(n_io2) | ||
self.sel = Signal() | ||
|
||
# # # | ||
|
||
state = Signal(n_state, reset=1) | ||
|
||
stmts1 = [] | ||
stmts2 = [] | ||
for stmts, si, so in ((stmts1, self.i1, self.o1), | ||
(stmts2, self.i2, self.o2)): | ||
curval = [state[i] for i in range(n_state)] | ||
for i in reversed(range(len(si))): | ||
flip = reduce(xor, [curval[tap] for tap in taps]) | ||
stmts += [so[i].eq(si[i] ^ flip)] | ||
curval.insert(0, si[i]) | ||
curval.pop() | ||
|
||
stmts += [state.eq(Cat(*curval[:n_state]))] | ||
|
||
self.sync += If(self.sel, stmts2).Else(stmts1) | ||
|
||
|
||
def K(x, y): | ||
return (y << 5) | x | ||
|
||
|
||
aux_coding_comma = [ | ||
K(28, 5), | ||
K(28, 0), | ||
K(28, 1), | ||
K(28, 2), | ||
K(23, 7), | ||
K(27, 7), | ||
K(29, 7), | ||
K(30, 7), | ||
] | ||
|
||
|
||
aux_coding_nocomma = [ | ||
K(28, 0), | ||
K(28, 2), | ||
K(28, 3), | ||
K(28, 4), | ||
K(23, 7), | ||
K(27, 7), | ||
K(29, 7), | ||
K(30, 7), | ||
] | ||
|
||
|
||
class LinkLayerTX(Module): | ||
def __init__(self, encoder): | ||
nwords = len(encoder.k) | ||
# nwords must be a power of 2 | ||
assert nwords & (nwords - 1) == 0 | ||
|
||
self.aux_frame = Signal() | ||
self.aux_data = Signal(2*nwords) | ||
self.aux_ack = Signal() | ||
|
||
self.rt_frame = Signal() | ||
self.rt_data = Signal(8*nwords) | ||
|
||
# # # | ||
|
||
# Idle and auxiliary traffic use special characters defined in the | ||
# aux_coding_* tables. | ||
# The first (or only) character uses aux_coding_comma which guarantees | ||
# that commas appear regularly in the absence of traffic. | ||
# The subsequent characters, if any (depending on the transceiver | ||
# serialization ratio) use aux_coding_nocomma which does not contain | ||
# commas. This permits aligning the comma to the first character at | ||
# the receiver. | ||
# | ||
# A set of 8 special characters is chosen using a 3-bit control word. | ||
# This control word is scrambled to reduce EMI. The control words have | ||
# the following meanings: | ||
# 100 idle/auxiliary framing | ||
# 0AB 2 bits of auxiliary data | ||
# | ||
# RT traffic uses D characters and is also scrambled. The aux and RT | ||
# scramblers are multiplicative and share the same state so that idle | ||
# or aux traffic can synchronize the RT descrambler. | ||
|
||
scrambler = Scrambler(3*nwords, 8*nwords) | ||
self.submodules += scrambler | ||
|
||
# scrambler input | ||
aux_data_ctl = [] | ||
for i in range(nwords): | ||
aux_data_ctl.append(self.aux_data[i*2:i*2+2]) | ||
aux_data_ctl.append(0) | ||
self.comb += [ | ||
If(self.aux_frame, | ||
scrambler.i1.eq(Cat(*aux_data_ctl)) | ||
).Else( | ||
scrambler.i1.eq(Replicate(0b100, nwords)) | ||
), | ||
scrambler.i2.eq(self.rt_data), | ||
scrambler.sel.eq(self.rt_frame), | ||
self.aux_ack.eq(~self.rt_frame) | ||
] | ||
|
||
# compensate for scrambler latency | ||
rt_frame_r = Signal() | ||
self.sync += rt_frame_r.eq(self.rt_frame) | ||
|
||
# scrambler output | ||
for i in range(nwords): | ||
scrambled_ctl = scrambler.o1[i*3:i*3+3] | ||
if i: | ||
aux_coding = aux_coding_nocomma | ||
else: | ||
aux_coding = aux_coding_comma | ||
self.sync += [ | ||
encoder.k[i].eq(1), | ||
encoder.d[i].eq(Array(aux_coding)[scrambled_ctl]) | ||
] | ||
self.sync += \ | ||
If(rt_frame_r, | ||
[k.eq(0) for k in encoder.k], | ||
[d.eq(scrambler.o2[i*8:i*8+8]) for i, d in enumerate(encoder.d)] | ||
) | ||
|
||
|
||
class LinkLayerRX(Module): | ||
def __init__(self, decoders): | ||
nwords = len(decoders) | ||
# nwords must be a power of 2 | ||
assert nwords & (nwords - 1) == 0 | ||
|
||
self.aux_stb = Signal() | ||
self.aux_frame = Signal() | ||
self.aux_data = Signal(2*nwords) | ||
|
||
self.rt_frame = Signal() | ||
self.rt_data = Signal(8*nwords) | ||
|
||
# # # | ||
|
||
descrambler = Descrambler(3*nwords, 8*nwords) | ||
self.submodules += descrambler | ||
|
||
# scrambler input | ||
all_decoded_aux = [] | ||
for i, d in enumerate(decoders): | ||
decoded_aux = Signal(3) | ||
all_decoded_aux.append(decoded_aux) | ||
|
||
if i: | ||
aux_coding = aux_coding_nocomma | ||
else: | ||
aux_coding = aux_coding_comma | ||
|
||
cases = {code: decoded_aux.eq(i) for i, code in enumerate(aux_coding)} | ||
self.comb += Case(d.d, cases).makedefault() | ||
|
||
self.comb += [ | ||
descrambler.i1.eq(Cat(*all_decoded_aux)), | ||
descrambler.i2.eq(Cat(*[d.d for d in decoders])), | ||
descrambler.sel.eq(~decoders[0].k) | ||
] | ||
|
||
# scrambler output | ||
self.comb += [ | ||
self.aux_frame.eq(~descrambler.o1[2]), | ||
self.aux_data.eq( | ||
Cat(*[descrambler.o1[3*i:3*i+2] for i in range(nwords)])), | ||
self.rt_data.eq(descrambler.o2) | ||
] | ||
self.sync += [ | ||
self.aux_stb.eq(decoders[0].k), | ||
self.rt_frame.eq(~decoders[0].k) | ||
] | ||
|
||
|
||
|
||
class LinkLayer(Module, AutoCSR): | ||
def __init__(self, encoder, decoders): | ||
self.link_status = CSRStatus() | ||
|
||
# receiver locked, comma aligned, receiving valid 8b10b symbols | ||
self.rx_ready = Signal() | ||
|
||
tx = ClockDomainsRenamer("rtio")(LinkLayerTX(encoder)) | ||
rx = ClockDomainsRenamer("rtio_rx")(LinkLayerRX(decoders)) | ||
self.submodules += tx, rx | ||
|
||
# in rtio clock domain | ||
self.tx_aux_frame = tx.aux_frame | ||
self.tx_aux_data = tx.aux_data | ||
self.tx_aux_ack = tx.aux_ack | ||
self.tx_rt_frame = tx.rt_frame | ||
self.tx_rt_data = tx.rt_data | ||
|
||
# in rtio_rx clock domain | ||
self.rx_aux_stb = rx.aux_stb | ||
self.rx_aux_frame = Signal() | ||
self.rx_aux_data = rx.aux_data | ||
self.rx_rt_frame = Signal() | ||
self.rx_rt_data = rx.rt_data | ||
|
||
# # # | ||
|
||
ready = Signal() | ||
ready_r = Signal() | ||
self.sync.rtio += ready_r.eq(ready) | ||
ready_rx = Signal() | ||
ready_r.attr.add("no_retiming") | ||
self.specials += MultiReg(ready_r, ready_rx, "rtio_rx") | ||
self.comb += [ | ||
self.rx_aux_frame.eq(rx.aux_frame & ready_rx), | ||
self.rx_rt_frame.eq(rx.rt_frame & ready_rx), | ||
] | ||
self.specials += MultiReg(ready_r, self.link_status.status) | ||
|
||
wait_scrambler = ClockDomainsRenamer("rtio")(WaitTimer(15)) | ||
self.submodules += wait_scrambler | ||
|
||
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="WAIT_RX_READY")) | ||
self.submodules += fsm | ||
|
||
fsm.act("WAIT_RX_READY", | ||
If(self.rx_ready, NextState("WAIT_SCRAMBLER_SYNC")) | ||
) | ||
fsm.act("WAIT_SCRAMBLER_SYNC", | ||
wait_scrambler.wait.eq(1), | ||
If(wait_scrambler.done, NextState("READY")) | ||
) | ||
fsm.act("READY", | ||
ready.eq(1), | ||
If(~self.rx_ready, NextState("WAIT_RX_READY")) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
from migen import * | ||
from migen.genlib.cdc import MultiReg | ||
from migen.genlib.misc import WaitTimer | ||
|
||
from misoc.interconnect.csr import * | ||
|
||
from artiq.gateware.rtio.cdc import RTIOCounter | ||
from artiq.gateware.rtio import cri | ||
|
||
|
||
class _CSRs(AutoCSR): | ||
def __init__(self): | ||
self.chan_sel_override = CSRStorage(16) | ||
self.chan_sel_override_en = CSRStorage() | ||
|
||
self.tsc_correction = CSRStorage(64) | ||
self.set_time = CSR() | ||
self.underflow_margin = CSRStorage(16, reset=200) | ||
|
||
self.o_get_fifo_space = CSR() | ||
self.o_dbg_fifo_space = CSRStatus(16) | ||
self.o_dbg_last_timestamp = CSRStatus(64) | ||
self.o_reset_channel_status = CSR() | ||
self.o_wait = CSRStatus() | ||
self.o_fifo_space_timeout = CSR() | ||
|
||
|
||
class RTController(Module): | ||
def __init__(self, rt_packets, channel_count, fine_ts_width): | ||
self.csrs = _CSRs() | ||
self.cri = cri.Interface() | ||
self.comb += self.cri.arb_gnt.eq(1) | ||
|
||
# channel selection | ||
chan_sel = Signal(16) | ||
self.comb += chan_sel.eq( | ||
Mux(self.csrs.chan_sel_override_en.storage, | ||
self.csrs.chan_sel_override.storage, | ||
self.cri.chan_sel[:16])) | ||
|
||
# master RTIO counter and counter synchronization | ||
self.submodules.counter = RTIOCounter(64-fine_ts_width) | ||
self.comb += self.cri.counter.eq(self.counter.value_sys << fine_ts_width) | ||
tsc_correction = Signal(64) | ||
self.csrs.tsc_correction.storage.attr.add("no_retiming") | ||
self.specials += MultiReg(self.csrs.tsc_correction.storage, tsc_correction) | ||
self.comb += [ | ||
rt_packets.tsc_value.eq( | ||
self.counter.value_rtio + tsc_correction), | ||
self.csrs.set_time.w.eq(rt_packets.set_time_stb) | ||
] | ||
self.sync += [ | ||
If(rt_packets.set_time_ack, rt_packets.set_time_stb.eq(0)), | ||
If(self.csrs.set_time.re, rt_packets.set_time_stb.eq(1)) | ||
] | ||
|
||
# reset | ||
self.sync += [ | ||
If(rt_packets.reset_ack, rt_packets.reset_stb.eq(0)), | ||
If(self.cri.cmd == cri.commands["reset"], | ||
rt_packets.reset_stb.eq(1), | ||
rt_packets.reset_phy.eq(0) | ||
), | ||
If(self.cri.cmd == cri.commands["reset_phy"], | ||
rt_packets.reset_stb.eq(1), | ||
rt_packets.reset_phy.eq(1) | ||
), | ||
] | ||
|
||
# remote channel status cache | ||
fifo_spaces_mem = Memory(16, channel_count) | ||
fifo_spaces = fifo_spaces_mem.get_port(write_capable=True) | ||
self.specials += fifo_spaces_mem, fifo_spaces | ||
last_timestamps_mem = Memory(64, channel_count) | ||
last_timestamps = last_timestamps_mem.get_port(write_capable=True) | ||
self.specials += last_timestamps_mem, last_timestamps | ||
|
||
# common packet fields | ||
rt_packets_fifo_request = Signal() | ||
self.comb += [ | ||
fifo_spaces.adr.eq(chan_sel), | ||
last_timestamps.adr.eq(chan_sel), | ||
last_timestamps.dat_w.eq(self.cri.o_timestamp), | ||
rt_packets.write_channel.eq(chan_sel), | ||
rt_packets.write_address.eq(self.cri.o_address), | ||
rt_packets.write_data.eq(self.cri.o_data), | ||
If(rt_packets_fifo_request, | ||
rt_packets.write_timestamp.eq(0xffff000000000000) | ||
).Else( | ||
rt_packets.write_timestamp.eq(self.cri.o_timestamp) | ||
) | ||
] | ||
|
||
fsm = FSM() | ||
self.submodules += fsm | ||
|
||
status_wait = Signal() | ||
status_underflow = Signal() | ||
status_sequence_error = Signal() | ||
self.comb += [ | ||
self.cri.o_status.eq(Cat( | ||
status_wait, status_underflow, status_sequence_error)), | ||
self.csrs.o_wait.status.eq(status_wait) | ||
] | ||
sequence_error_set = Signal() | ||
underflow_set = Signal() | ||
self.sync += [ | ||
If(self.cri.cmd == cri.commands["o_underflow_reset"], status_underflow.eq(0)), | ||
If(self.cri.cmd == cri.commands["o_sequence_error_reset"], status_sequence_error.eq(0)), | ||
If(underflow_set, status_underflow.eq(1)), | ||
If(sequence_error_set, status_sequence_error.eq(1)), | ||
] | ||
|
||
signal_fifo_space_timeout = Signal() | ||
self.sync += [ | ||
If(self.csrs.o_fifo_space_timeout.re, self.csrs.o_fifo_space_timeout.w.eq(0)), | ||
If(signal_fifo_space_timeout, self.csrs.o_fifo_space_timeout.w.eq(1)) | ||
] | ||
timeout_counter = WaitTimer(8191) | ||
self.submodules += timeout_counter | ||
|
||
# TODO: collision, replace, busy | ||
cond_sequence_error = self.cri.o_timestamp < last_timestamps.dat_r | ||
cond_underflow = ((self.cri.o_timestamp[fine_ts_width:] | ||
- self.csrs.underflow_margin.storage[fine_ts_width:]) < self.counter.value_sys) | ||
cond_fifo_emptied = ((last_timestamps.dat_r[fine_ts_width:] < self.counter.value_sys) | ||
& (last_timestamps.dat_r != 0)) | ||
|
||
fsm.act("IDLE", | ||
If(self.cri.cmd == cri.commands["write"], | ||
If(cond_sequence_error, | ||
sequence_error_set.eq(1) | ||
).Elif(cond_underflow, | ||
underflow_set.eq(1) | ||
).Else( | ||
NextState("WRITE") | ||
) | ||
), | ||
If(self.csrs.o_get_fifo_space.re, | ||
NextState("GET_FIFO_SPACE") | ||
) | ||
) | ||
fsm.act("WRITE", | ||
status_wait.eq(1), | ||
rt_packets.write_stb.eq(1), | ||
If(rt_packets.write_ack, | ||
fifo_spaces.we.eq(1), | ||
If(cond_fifo_emptied, | ||
fifo_spaces.dat_w.eq(1), | ||
).Else( | ||
fifo_spaces.dat_w.eq(fifo_spaces.dat_r - 1) | ||
), | ||
last_timestamps.we.eq(1), | ||
If(~cond_fifo_emptied & (fifo_spaces.dat_r <= 1), | ||
NextState("GET_FIFO_SPACE") | ||
).Else( | ||
NextState("IDLE") | ||
) | ||
) | ||
) | ||
fsm.act("GET_FIFO_SPACE", | ||
status_wait.eq(1), | ||
rt_packets_fifo_request.eq(1), | ||
rt_packets.write_stb.eq(1), | ||
If(rt_packets.write_ack, | ||
NextState("GET_FIFO_SPACE_REPLY") | ||
) | ||
) | ||
fsm.act("GET_FIFO_SPACE_REPLY", | ||
status_wait.eq(1), | ||
fifo_spaces.dat_w.eq(rt_packets.fifo_space), | ||
fifo_spaces.we.eq(1), | ||
rt_packets.fifo_space_not_ack.eq(1), | ||
If(rt_packets.fifo_space_not, | ||
If(rt_packets.fifo_space > 0, | ||
NextState("IDLE") | ||
).Else( | ||
NextState("GET_FIFO_SPACE") | ||
) | ||
), | ||
timeout_counter.wait.eq(1), | ||
If(timeout_counter.done, | ||
signal_fifo_space_timeout.eq(1), | ||
NextState("IDLE") | ||
) | ||
) | ||
|
||
# channel state access | ||
self.comb += [ | ||
self.csrs.o_dbg_fifo_space.status.eq(fifo_spaces.dat_r), | ||
self.csrs.o_dbg_last_timestamp.status.eq(last_timestamps.dat_r), | ||
If(self.csrs.o_reset_channel_status.re, | ||
fifo_spaces.dat_w.eq(0), | ||
fifo_spaces.we.eq(1), | ||
last_timestamps.dat_w.eq(0), | ||
last_timestamps.we.eq(1) | ||
) | ||
] | ||
|
||
def get_csrs(self): | ||
return self.csrs.get_csrs() | ||
|
||
|
||
class RTManager(Module, AutoCSR): | ||
def __init__(self, rt_packets): | ||
self.request_echo = CSR() | ||
|
||
self.packet_err_present = CSR() | ||
self.packet_err_code = CSRStatus(8) | ||
|
||
self.update_packet_cnt = CSR() | ||
self.packet_cnt_tx = CSRStatus(32) | ||
self.packet_cnt_rx = CSRStatus(32) | ||
|
||
# # # | ||
|
||
self.comb += self.request_echo.w.eq(rt_packets.echo_stb) | ||
self.sync += [ | ||
If(rt_packets.echo_ack, rt_packets.echo_stb.eq(0)), | ||
If(self.request_echo.re, rt_packets.echo_stb.eq(1)) | ||
] | ||
|
||
self.comb += [ | ||
self.packet_err_present.w.eq(rt_packets.error_not), | ||
rt_packets.error_not_ack.eq(self.packet_err_present.re), | ||
self.packet_err_code.status.eq(rt_packets.error_code) | ||
] | ||
|
||
self.sync += \ | ||
If(self.update_packet_cnt.re, | ||
self.packet_cnt_tx.status.eq(rt_packets.packet_cnt_tx), | ||
self.packet_cnt_rx.status.eq(rt_packets.packet_cnt_rx) | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
from migen import * | ||
from migen.genlib.resetsync import AsyncResetSynchronizer | ||
|
||
from misoc.cores.code_8b10b import Encoder, Decoder | ||
from misoc.interconnect.csr import * | ||
|
||
from artiq.gateware.drtio.transceiver.gtx_7series_init import * | ||
|
||
|
||
class GTX_20X(Module): | ||
# The transceiver clock on clock_pads must be at the RTIO clock | ||
# frequency when clock_div2=False, and 2x that frequency when | ||
# clock_div2=True. | ||
def __init__(self, clock_pads, tx_pads, rx_pads, sys_clk_freq, | ||
clock_div2=False): | ||
self.submodules.encoder = ClockDomainsRenamer("rtio")( | ||
Encoder(2, True)) | ||
self.decoders = [ClockDomainsRenamer("rtio_rx")( | ||
Decoder(True)) for _ in range(2)] | ||
self.submodules += self.decoders | ||
|
||
self.rx_ready = Signal() | ||
|
||
# transceiver direct clock outputs | ||
# useful to specify clock constraints in a way palatable to Vivado | ||
self.txoutclk = Signal() | ||
self.rxoutclk = Signal() | ||
|
||
# # # | ||
|
||
refclk = Signal() | ||
if clock_div2: | ||
self.specials += Instance("IBUFDS_GTE2", | ||
i_CEB=0, | ||
i_I=clock_pads.p, | ||
i_IB=clock_pads.n, | ||
o_ODIV2=refclk | ||
) | ||
else: | ||
self.specials += Instance("IBUFDS_GTE2", | ||
i_CEB=0, | ||
i_I=clock_pads.p, | ||
i_IB=clock_pads.n, | ||
o_O=refclk | ||
) | ||
|
||
cplllock = Signal() | ||
# TX generates RTIO clock, init must be in system domain | ||
tx_init = GTXInit(sys_clk_freq, False) | ||
# RX receives restart commands from RTIO domain | ||
rx_init = ClockDomainsRenamer("rtio")( | ||
GTXInit(self.rtio_clk_freq, True)) | ||
self.submodules += tx_init, rx_init | ||
self.comb += tx_init.cplllock.eq(cplllock), \ | ||
rx_init.cplllock.eq(cplllock) | ||
|
||
txdata = Signal(20) | ||
rxdata = Signal(20) | ||
self.specials += \ | ||
Instance("GTXE2_CHANNEL", | ||
# PMA Attributes | ||
p_PMA_RSV=0x00018480, | ||
p_PMA_RSV2=0x2050, | ||
p_PMA_RSV3=0, | ||
p_PMA_RSV4=0, | ||
p_RX_BIAS_CFG=0b100, | ||
p_RX_CM_TRIM=0b010, | ||
p_RX_OS_CFG=0b10000000, | ||
p_RX_CLK25_DIV=5, | ||
p_TX_CLK25_DIV=5, | ||
|
||
# Power-Down Attributes | ||
p_PD_TRANS_TIME_FROM_P2=0x3c, | ||
p_PD_TRANS_TIME_NONE_P2=0x3c, | ||
p_PD_TRANS_TIME_TO_P2=0x64, | ||
|
||
# CPLL | ||
p_CPLL_CFG=0xBC07DC, | ||
p_CPLL_FBDIV=4, | ||
p_CPLL_FBDIV_45=5, | ||
p_CPLL_REFCLK_DIV=1, | ||
p_RXOUT_DIV=2, | ||
p_TXOUT_DIV=2, | ||
o_CPLLLOCK=cplllock, | ||
i_CPLLLOCKEN=1, | ||
i_CPLLREFCLKSEL=0b001, | ||
i_TSTIN=2**20-1, | ||
i_GTREFCLK0=refclk, | ||
|
||
# TX clock | ||
p_TXBUF_EN="FALSE", | ||
p_TX_XCLK_SEL="TXUSR", | ||
o_TXOUTCLK=self.txoutclk, | ||
i_TXSYSCLKSEL=0b00, | ||
i_TXOUTCLKSEL=0b11, | ||
|
||
# TX Startup/Reset | ||
i_GTTXRESET=tx_init.gtXxreset, | ||
o_TXRESETDONE=tx_init.Xxresetdone, | ||
i_TXDLYSRESET=tx_init.Xxdlysreset, | ||
o_TXDLYSRESETDONE=tx_init.Xxdlysresetdone, | ||
o_TXPHALIGNDONE=tx_init.Xxphaligndone, | ||
i_TXUSERRDY=tx_init.Xxuserrdy, | ||
|
||
# TX data | ||
p_TX_DATA_WIDTH=20, | ||
p_TX_INT_DATAWIDTH=0, | ||
i_TXCHARDISPMODE=Cat(txdata[9], txdata[19]), | ||
i_TXCHARDISPVAL=Cat(txdata[8], txdata[18]), | ||
i_TXDATA=Cat(txdata[:8], txdata[10:18]), | ||
i_TXUSRCLK=ClockSignal("rtio"), | ||
i_TXUSRCLK2=ClockSignal("rtio"), | ||
|
||
# TX electrical | ||
i_TXBUFDIFFCTRL=0b100, | ||
i_TXDIFFCTRL=0b1000, | ||
|
||
# RX Startup/Reset | ||
i_GTRXRESET=rx_init.gtXxreset, | ||
o_RXRESETDONE=rx_init.Xxresetdone, | ||
i_RXDLYSRESET=rx_init.Xxdlysreset, | ||
o_RXDLYSRESETDONE=rx_init.Xxdlysresetdone, | ||
o_RXPHALIGNDONE=rx_init.Xxphaligndone, | ||
i_RXUSERRDY=rx_init.Xxuserrdy, | ||
|
||
# RX AFE | ||
p_RX_DFE_XYD_CFG=0, | ||
i_RXDFEXYDEN=1, | ||
i_RXDFEXYDHOLD=0, | ||
i_RXDFEXYDOVRDEN=0, | ||
i_RXLPMEN=0, | ||
|
||
# RX clock | ||
p_RXBUF_EN="FALSE", | ||
p_RX_XCLK_SEL="RXUSR", | ||
i_RXDDIEN=1, | ||
i_RXSYSCLKSEL=0b00, | ||
i_RXOUTCLKSEL=0b010, | ||
o_RXOUTCLK=self.rxoutclk, | ||
i_RXUSRCLK=ClockSignal("rtio_rx"), | ||
i_RXUSRCLK2=ClockSignal("rtio_rx"), | ||
p_RXCDR_CFG=0x03000023FF10100020, | ||
|
||
# RX Clock Correction Attributes | ||
p_CLK_CORRECT_USE="FALSE", | ||
p_CLK_COR_SEQ_1_1=0b0100000000, | ||
p_CLK_COR_SEQ_2_1=0b0100000000, | ||
p_CLK_COR_SEQ_1_ENABLE=0b1111, | ||
p_CLK_COR_SEQ_2_ENABLE=0b1111, | ||
|
||
# RX data | ||
p_RX_DATA_WIDTH=20, | ||
p_RX_INT_DATAWIDTH=0, | ||
o_RXDISPERR=Cat(rxdata[9], rxdata[19]), | ||
o_RXCHARISK=Cat(rxdata[8], rxdata[18]), | ||
o_RXDATA=Cat(rxdata[:8], rxdata[10:18]), | ||
|
||
# Pads | ||
i_GTXRXP=rx_pads.p, | ||
i_GTXRXN=rx_pads.n, | ||
o_GTXTXP=tx_pads.p, | ||
o_GTXTXN=tx_pads.n, | ||
) | ||
|
||
tx_reset_deglitched = Signal() | ||
tx_reset_deglitched.attr.add("no_retiming") | ||
self.sync += tx_reset_deglitched.eq(~tx_init.done) | ||
self.clock_domains.cd_rtio = ClockDomain() | ||
self.specials += [ | ||
Instance("BUFG", i_I=self.txoutclk, o_O=self.cd_rtio.clk), | ||
AsyncResetSynchronizer(self.cd_rtio, tx_reset_deglitched) | ||
] | ||
rx_reset_deglitched = Signal() | ||
rx_reset_deglitched.attr.add("no_retiming") | ||
self.sync.rtio += rx_reset_deglitched.eq(~rx_init.done) | ||
self.clock_domains.cd_rtio_rx = ClockDomain() | ||
self.specials += [ | ||
Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_rtio_rx.clk), | ||
AsyncResetSynchronizer(self.cd_rtio_rx, rx_reset_deglitched) | ||
] | ||
|
||
self.comb += [ | ||
txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1])), | ||
self.decoders[0].input.eq(rxdata[:10]), | ||
self.decoders[1].input.eq(rxdata[10:]) | ||
] | ||
|
||
clock_aligner = BruteforceClockAligner(0b0101111100, self.rtio_clk_freq) | ||
self.submodules += clock_aligner | ||
self.comb += [ | ||
clock_aligner.rxdata.eq(rxdata), | ||
rx_init.restart.eq(clock_aligner.restart), | ||
self.rx_ready.eq(clock_aligner.ready) | ||
] | ||
|
||
|
||
class GTX_1000BASE_BX10(GTX_20X): | ||
rtio_clk_freq = 62.5e6 | ||
|
||
|
||
class GTX_3G(GTX_20X): | ||
rtio_clk_freq = 150e6 | ||
|
||
|
||
class RXSynchronizer(Module, AutoCSR): | ||
"""Delays the data received in the rtio_rx by a configurable amount | ||
so that it meets s/h in the rtio domain, and recapture it in the rtio | ||
domain. This has fixed latency. | ||
Since Xilinx doesn't provide decent on-chip delay lines, we implement the | ||
delay with MMCM that provides a clock and a finely configurable phase, used | ||
to resample the data. | ||
The phase has to be determined either empirically or by making sense of the | ||
Xilinx scriptures (when existent) and should be constant for a given design | ||
placement. | ||
""" | ||
def __init__(self, rtio_clk_freq, initial_phase=0.0): | ||
self.phase_shift = CSR() | ||
self.phase_shift_done = CSRStatus() | ||
|
||
self.clock_domains.cd_rtio_delayed = ClockDomain() | ||
|
||
mmcm_output = Signal() | ||
mmcm_fb = Signal() | ||
mmcm_locked = Signal() | ||
# maximize VCO frequency to maximize phase shift resolution | ||
mmcm_mult = 1200e6//rtio_clk_freq | ||
self.specials += [ | ||
Instance("MMCME2_ADV", | ||
p_CLKIN1_PERIOD=1e9/rtio_clk_freq, | ||
i_CLKIN1=ClockSignal("rtio_rx"), | ||
i_RST=ResetSignal("rtio_rx"), | ||
i_CLKINSEL=1, # yes, 1=CLKIN1 0=CLKIN2 | ||
|
||
p_CLKFBOUT_MULT_F=mmcm_mult, | ||
p_CLKOUT0_DIVIDE_F=mmcm_mult, | ||
p_CLKOUT0_PHASE=initial_phase, | ||
p_DIVCLK_DIVIDE=1, | ||
|
||
# According to Xilinx, there is no guarantee of input/output | ||
# phase relationship when using internal feedback. We assume | ||
# here that the input/ouput skew is constant to save BUFGs. | ||
o_CLKFBOUT=mmcm_fb, | ||
i_CLKFBIN=mmcm_fb, | ||
|
||
p_CLKOUT0_USE_FINE_PS="TRUE", | ||
o_CLKOUT0=mmcm_output, | ||
o_LOCKED=mmcm_locked, | ||
|
||
i_PSCLK=ClockSignal(), | ||
i_PSEN=self.phase_shift.re, | ||
i_PSINCDEC=self.phase_shift.r, | ||
o_PSDONE=self.phase_shift_done.status, | ||
), | ||
Instance("BUFR", i_I=mmcm_output, o_O=self.cd_rtio_delayed.clk), | ||
AsyncResetSynchronizer(self.cd_rtio_delayed, ~mmcm_locked) | ||
] | ||
|
||
def resync(self, signal): | ||
delayed = Signal.like(signal, related=signal) | ||
synchronized = Signal.like(signal, related=signal) | ||
self.sync.rtio_delayed += delayed.eq(signal) | ||
self.sync.rtio += synchronized.eq(delayed) | ||
return synchronized |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
from math import ceil | ||
from functools import reduce | ||
from operator import add | ||
|
||
from migen import * | ||
from migen.genlib.cdc import MultiReg, PulseSynchronizer | ||
from migen.genlib.misc import WaitTimer | ||
from migen.genlib.fsm import FSM | ||
|
||
|
||
class GTXInit(Module): | ||
# Based on LiteSATA by Enjoy-Digital | ||
def __init__(self, sys_clk_freq, rx): | ||
self.done = Signal() | ||
self.restart = Signal() | ||
|
||
# GTX signals | ||
self.cplllock = Signal() | ||
self.gtXxreset = Signal() | ||
self.Xxresetdone = Signal() | ||
self.Xxdlysreset = Signal() | ||
self.Xxdlysresetdone = Signal() | ||
self.Xxphaligndone = Signal() | ||
self.Xxuserrdy = Signal() | ||
|
||
# # # | ||
|
||
# Double-latch transceiver asynch outputs | ||
cplllock = Signal() | ||
Xxresetdone = Signal() | ||
Xxdlysresetdone = Signal() | ||
Xxphaligndone = Signal() | ||
self.specials += [ | ||
MultiReg(self.cplllock, cplllock), | ||
MultiReg(self.Xxresetdone, Xxresetdone), | ||
MultiReg(self.Xxdlysresetdone, Xxdlysresetdone), | ||
MultiReg(self.Xxphaligndone, Xxphaligndone), | ||
] | ||
|
||
# Deglitch FSM outputs driving transceiver asynch inputs | ||
gtXxreset = Signal() | ||
Xxdlysreset = Signal() | ||
Xxuserrdy = Signal() | ||
self.sync += [ | ||
self.gtXxreset.eq(gtXxreset), | ||
self.Xxdlysreset.eq(Xxdlysreset), | ||
self.Xxuserrdy.eq(Xxuserrdy) | ||
] | ||
|
||
# After configuration, transceiver resets have to stay low for | ||
# at least 500ns (see AR43482) | ||
startup_cycles = ceil(500*sys_clk_freq/1000000000) | ||
startup_timer = WaitTimer(startup_cycles) | ||
self.submodules += startup_timer | ||
|
||
startup_fsm = FSM(reset_state="INITIAL") | ||
self.submodules += startup_fsm | ||
|
||
if rx: | ||
cdr_stable_timer = WaitTimer(1024) | ||
self.submodules += cdr_stable_timer | ||
|
||
Xxphaligndone_r = Signal(reset=1) | ||
Xxphaligndone_rising = Signal() | ||
self.sync += Xxphaligndone_r.eq(Xxphaligndone) | ||
self.comb += Xxphaligndone_rising.eq(Xxphaligndone & ~Xxphaligndone_r) | ||
|
||
startup_fsm.act("INITIAL", | ||
startup_timer.wait.eq(1), | ||
If(startup_timer.done, NextState("RESET_GTX")) | ||
) | ||
startup_fsm.act("RESET_GTX", | ||
gtXxreset.eq(1), | ||
NextState("WAIT_CPLL") | ||
) | ||
startup_fsm.act("WAIT_CPLL", | ||
gtXxreset.eq(1), | ||
If(cplllock, NextState("RELEASE_RESET")) | ||
) | ||
# Release GTX reset and wait for GTX resetdone | ||
# (from UG476, GTX is reset on falling edge | ||
# of gttxreset) | ||
if rx: | ||
startup_fsm.act("RELEASE_RESET", | ||
Xxuserrdy.eq(1), | ||
cdr_stable_timer.wait.eq(1), | ||
If(Xxresetdone & cdr_stable_timer.done, NextState("ALIGN")) | ||
) | ||
else: | ||
startup_fsm.act("RELEASE_RESET", | ||
Xxuserrdy.eq(1), | ||
If(Xxresetdone, NextState("ALIGN")) | ||
) | ||
# Start delay alignment (pulse) | ||
startup_fsm.act("ALIGN", | ||
Xxuserrdy.eq(1), | ||
Xxdlysreset.eq(1), | ||
NextState("WAIT_ALIGN") | ||
) | ||
# Wait for delay alignment | ||
startup_fsm.act("WAIT_ALIGN", | ||
Xxuserrdy.eq(1), | ||
If(Xxdlysresetdone, NextState("WAIT_FIRST_ALIGN_DONE")) | ||
) | ||
# Wait 2 rising edges of rxphaligndone | ||
# (from UG476 in buffer bypass config) | ||
startup_fsm.act("WAIT_FIRST_ALIGN_DONE", | ||
Xxuserrdy.eq(1), | ||
If(Xxphaligndone_rising, NextState("WAIT_SECOND_ALIGN_DONE")) | ||
) | ||
startup_fsm.act("WAIT_SECOND_ALIGN_DONE", | ||
Xxuserrdy.eq(1), | ||
If(Xxphaligndone_rising, NextState("READY")) | ||
) | ||
startup_fsm.act("READY", | ||
Xxuserrdy.eq(1), | ||
self.done.eq(1), | ||
If(self.restart, NextState("RESET_GTX")) | ||
) | ||
|
||
|
||
# Changes the phase of the transceiver RX clock to align the comma to | ||
# the LSBs of RXDATA, fixing the latency. | ||
# | ||
# This is implemented by repeatedly resetting the transceiver until it | ||
# gives out the correct phase. Each reset gives a random phase. | ||
# | ||
# If Xilinx had designed the GTX transceiver correctly, RXSLIDE_MODE=PMA | ||
# would achieve this faster and in a cleaner way. But: | ||
# * the phase jumps are of 2 UI at every second RXSLIDE pulse, instead | ||
# of 1 UI at every pulse. It is unclear what the latency becomes. | ||
# * RXSLIDE_MODE=PMA cannot be used with the RX buffer bypassed. | ||
# Those design flaws make RXSLIDE_MODE=PMA yet another broken and useless | ||
# transceiver "feature". | ||
# | ||
# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped | ||
# compared to the usual 8b10b binary representation. | ||
class BruteforceClockAligner(Module): | ||
def __init__(self, comma, rtio_clk_freq, check_period=6e-3): | ||
self.rxdata = Signal(20) | ||
self.restart = Signal() | ||
|
||
self.ready = Signal() | ||
|
||
check_max_val = ceil(check_period*rtio_clk_freq) | ||
check_counter = Signal(max=check_max_val+1) | ||
check = Signal() | ||
reset_check_counter = Signal() | ||
self.sync.rtio += [ | ||
check.eq(0), | ||
If(reset_check_counter, | ||
check_counter.eq(check_max_val) | ||
).Else( | ||
If(check_counter == 0, | ||
check.eq(1), | ||
check_counter.eq(check_max_val) | ||
).Else( | ||
check_counter.eq(check_counter-1) | ||
) | ||
) | ||
] | ||
|
||
checks_reset = PulseSynchronizer("rtio", "rtio_rx") | ||
self.submodules += checks_reset | ||
|
||
comma_n = ~comma & 0b1111111111 | ||
comma_seen_rxclk = Signal() | ||
comma_seen = Signal() | ||
comma_seen_rxclk.attr.add("no_retiming") | ||
self.specials += MultiReg(comma_seen_rxclk, comma_seen) | ||
self.sync.rtio_rx += \ | ||
If(checks_reset.o, | ||
comma_seen_rxclk.eq(0) | ||
).Elif((self.rxdata[:10] == comma) | (self.rxdata[:10] == comma_n), | ||
comma_seen_rxclk.eq(1) | ||
) | ||
|
||
error_seen_rxclk = Signal() | ||
error_seen = Signal() | ||
error_seen_rxclk.attr.add("no_retiming") | ||
self.specials += MultiReg(error_seen_rxclk, error_seen) | ||
rx1cnt = Signal(max=11) | ||
self.sync.rtio_rx += [ | ||
rx1cnt.eq(reduce(add, [self.rxdata[i] for i in range(10)])), | ||
If(checks_reset.o, | ||
error_seen_rxclk.eq(0) | ||
).Elif((rx1cnt != 4) & (rx1cnt != 5) & (rx1cnt != 6), | ||
error_seen_rxclk.eq(1) | ||
) | ||
] | ||
|
||
fsm = ClockDomainsRenamer("rtio")(FSM(reset_state="WAIT_COMMA")) | ||
self.submodules += fsm | ||
|
||
fsm.act("WAIT_COMMA", | ||
If(check, | ||
# Errors are still OK at this stage, as the transceiver | ||
# has just been reset and may output garbage data. | ||
If(comma_seen, | ||
NextState("WAIT_NOERROR") | ||
).Else( | ||
self.restart.eq(1) | ||
), | ||
checks_reset.i.eq(1) | ||
) | ||
) | ||
fsm.act("WAIT_NOERROR", | ||
If(check, | ||
If(comma_seen & ~error_seen, | ||
NextState("READY") | ||
).Else( | ||
self.restart.eq(1), | ||
NextState("WAIT_COMMA") | ||
), | ||
checks_reset.i.eq(1) | ||
) | ||
) | ||
fsm.act("READY", | ||
reset_check_counter.eq(1), | ||
self.ready.eq(1), | ||
If(error_seen, | ||
checks_reset.i.eq(1), | ||
self.restart.eq(1), | ||
NextState("WAIT_COMMA") | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from artiq.gateware.rtio.core import Channel, LogChannel, RTIO | ||
from artiq.gateware.rtio.cri import KernelInitiator, CRIInterconnectShared | ||
from artiq.gateware.rtio.core import Channel, LogChannel, Core | ||
from artiq.gateware.rtio.analyzer import Analyzer | ||
from artiq.gateware.rtio.moninj import MonInj | ||
from artiq.gateware.rtio.dma import DMA |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
from migen import * | ||
from migen.genlib.cdc import * | ||
|
||
|
||
__all__ = ["GrayCodeTransfer", "RTIOCounter", "BlindTransfer"] | ||
|
||
|
||
# note: transfer is in rtio/sys domains and not affected by the reset CSRs | ||
class GrayCodeTransfer(Module): | ||
def __init__(self, width): | ||
self.i = Signal(width) # in rtio domain | ||
self.o = Signal(width) # in sys domain | ||
|
||
# # # | ||
|
||
# convert to Gray code | ||
value_gray_rtio = Signal(width) | ||
self.sync.rtio += value_gray_rtio.eq(self.i ^ self.i[1:]) | ||
# transfer to system clock domain | ||
value_gray_sys = Signal(width) | ||
value_gray_rtio.attr.add("no_retiming") | ||
self.specials += MultiReg(value_gray_rtio, value_gray_sys) | ||
# convert back to binary | ||
value_sys = Signal(width) | ||
self.comb += value_sys[-1].eq(value_gray_sys[-1]) | ||
for i in reversed(range(width-1)): | ||
self.comb += value_sys[i].eq(value_sys[i+1] ^ value_gray_sys[i]) | ||
self.sync += self.o.eq(value_sys) | ||
|
||
|
||
class RTIOCounter(Module): | ||
def __init__(self, width): | ||
self.width = width | ||
# Timestamp counter in RTIO domain | ||
self.value_rtio = Signal(width) | ||
# Timestamp counter resynchronized to sys domain | ||
# Lags behind value_rtio, monotonic and glitch-free | ||
self.value_sys = Signal(width) | ||
|
||
# # # | ||
|
||
# note: counter is in rtio domain and never affected by the reset CSRs | ||
self.sync.rtio += self.value_rtio.eq(self.value_rtio + 1) | ||
gt = GrayCodeTransfer(width) | ||
self.submodules += gt | ||
self.comb += gt.i.eq(self.value_rtio), self.value_sys.eq(gt.o) | ||
|
||
|
||
class BlindTransfer(Module): | ||
def __init__(self): | ||
self.i = Signal() | ||
self.o = Signal() | ||
|
||
ps = PulseSynchronizer("rio", "rsys") | ||
ps_ack = PulseSynchronizer("rsys", "rio") | ||
self.submodules += ps, ps_ack | ||
blind = Signal() | ||
self.sync.rio += [ | ||
If(self.i, blind.eq(1)), | ||
If(ps_ack.o, blind.eq(0)) | ||
] | ||
self.comb += [ | ||
ps.i.eq(self.i & ~blind), | ||
ps_ack.i.eq(ps.o), | ||
self.o.eq(ps.o) | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
"""Common RTIO Interface""" | ||
|
||
from migen import * | ||
from migen.genlib.record import * | ||
|
||
from misoc.interconnect.csr import * | ||
|
||
|
||
commands = { | ||
"nop": 0, | ||
"reset": 1, | ||
"reset_phy": 2, | ||
|
||
"write": 3, | ||
"read": 4, | ||
|
||
"o_underflow_reset": 5, | ||
"o_sequence_error_reset": 6, | ||
"o_collision_reset": 7, | ||
"o_busy_reset": 8, | ||
"i_overflow_reset": 9 | ||
} | ||
|
||
|
||
layout = [ | ||
("arb_req", 1, DIR_M_TO_S), | ||
("arb_gnt", 1, DIR_S_TO_M), | ||
|
||
("cmd", 4, DIR_M_TO_S), | ||
# 8 MSBs of chan_sel are used to select core | ||
("chan_sel", 24, DIR_M_TO_S), | ||
|
||
("o_data", 512, DIR_M_TO_S), | ||
("o_address", 16, DIR_M_TO_S), | ||
("o_timestamp", 64, DIR_M_TO_S), | ||
# o_status bits: | ||
# <0:wait> <1:underflow> <2:sequence_error> <3:collision> <4:busy> | ||
("o_status", 5, DIR_S_TO_M), | ||
|
||
("i_data", 32, DIR_S_TO_M), | ||
("i_timestamp", 64, DIR_S_TO_M), | ||
# i_status bits: | ||
# <0:wait> <1:overflow> | ||
("i_status", 2, DIR_S_TO_M), | ||
|
||
("counter", 64, DIR_S_TO_M) | ||
] | ||
|
||
|
||
class Interface(Record): | ||
def __init__(self): | ||
Record.__init__(self, layout) | ||
|
||
|
||
class KernelInitiator(Module, AutoCSR): | ||
def __init__(self, cri=None): | ||
self.arb_req = CSRStorage() | ||
self.arb_gnt = CSRStatus() | ||
|
||
self.reset = CSR() | ||
self.reset_phy = CSR() | ||
self.chan_sel = CSRStorage(24) | ||
|
||
self.o_data = CSRStorage(512, write_from_dev=True) | ||
self.o_address = CSRStorage(16) | ||
self.o_timestamp = CSRStorage(64) | ||
self.o_we = CSR() | ||
self.o_status = CSRStatus(5) | ||
self.o_underflow_reset = CSR() | ||
self.o_sequence_error_reset = CSR() | ||
self.o_collision_reset = CSR() | ||
self.o_busy_reset = CSR() | ||
|
||
self.i_data = CSRStatus(32) | ||
self.i_timestamp = CSRStatus(64) | ||
self.i_re = CSR() | ||
self.i_status = CSRStatus(2) | ||
self.i_overflow_reset = CSR() | ||
|
||
self.counter = CSRStatus(64) | ||
self.counter_update = CSR() | ||
|
||
if cri is None: | ||
cri = Interface() | ||
self.cri = cri | ||
|
||
# # # | ||
|
||
self.comb += [ | ||
self.cri.arb_req.eq(self.arb_req.storage), | ||
self.arb_gnt.status.eq(self.cri.arb_gnt), | ||
|
||
self.cri.cmd.eq(commands["nop"]), | ||
If(self.reset.re, self.cri.cmd.eq(commands["reset"])), | ||
If(self.reset_phy.re, self.cri.cmd.eq(commands["reset_phy"])), | ||
If(self.o_we.re, self.cri.cmd.eq(commands["write"])), | ||
If(self.i_re.re, self.cri.cmd.eq(commands["read"])), | ||
If(self.o_underflow_reset.re, self.cri.cmd.eq(commands["o_underflow_reset"])), | ||
If(self.o_sequence_error_reset.re, self.cri.cmd.eq(commands["o_sequence_error_reset"])), | ||
If(self.o_collision_reset.re, self.cri.cmd.eq(commands["o_collision_reset"])), | ||
If(self.o_busy_reset.re, self.cri.cmd.eq(commands["o_busy_reset"])), | ||
If(self.i_overflow_reset.re, self.cri.cmd.eq(commands["i_overflow_reset"])), | ||
|
||
self.cri.chan_sel.eq(self.chan_sel.storage), | ||
|
||
self.cri.o_data.eq(self.o_data.storage), | ||
self.cri.o_address.eq(self.o_address.storage), | ||
self.cri.o_timestamp.eq(self.o_timestamp.storage), | ||
self.o_status.status.eq(self.cri.o_status), | ||
|
||
self.i_data.status.eq(self.cri.i_data), | ||
self.i_timestamp.status.eq(self.cri.i_timestamp), | ||
self.i_status.status.eq(self.cri.i_status), | ||
|
||
self.o_data.dat_w.eq(0), | ||
self.o_data.we.eq(self.o_timestamp.re), | ||
] | ||
self.sync += If(self.counter_update.re, self.counter.status.eq(self.cri.counter)) | ||
|
||
|
||
class CRIDecoder(Module): | ||
def __init__(self, slaves=2, master=None): | ||
if isinstance(slaves, int): | ||
slaves = [Interface() for _ in range(slaves)] | ||
if master is None: | ||
master = Interface() | ||
self.slaves = slaves | ||
self.master = master | ||
|
||
# # # | ||
|
||
selected = Signal(8) | ||
self.sync += selected.eq(self.master.chan_sel[16:]) | ||
|
||
# master -> slave | ||
for n, slave in enumerate(slaves): | ||
for name, size, direction in layout: | ||
if direction == DIR_M_TO_S and name != "cmd": | ||
self.comb += getattr(slave, name).eq(getattr(master, name)) | ||
self.comb += If(selected == n, slave.cmd.eq(master.cmd)) | ||
|
||
# slave -> master | ||
cases = dict() | ||
for n, slave in enumerate(slaves): | ||
cases[n] = [] | ||
for name, size, direction in layout: | ||
if direction == DIR_S_TO_M: | ||
cases[n].append(getattr(master, name).eq(getattr(slave, name))) | ||
self.comb += Case(selected, cases) | ||
|
||
|
||
class CRIArbiter(Module): | ||
def __init__(self, masters=2, slave=None): | ||
if isinstance(masters, int): | ||
masters = [Interface() for _ in range(masters)] | ||
if slave is None: | ||
slave = Interface() | ||
self.masters = masters | ||
self.slave = slave | ||
|
||
# # # | ||
|
||
if len(masters) == 1: | ||
self.comb += masters[0].connect(slave) | ||
else: | ||
selected = Signal(max=len(masters)) | ||
|
||
# mux master->slave signals | ||
for name, size, direction in layout: | ||
if direction == DIR_M_TO_S: | ||
choices = Array(getattr(m, name) for m in masters) | ||
self.comb += getattr(slave, name).eq(choices[selected]) | ||
|
||
# connect slave->master signals | ||
for name, size, direction in layout: | ||
if direction == DIR_S_TO_M: | ||
source = getattr(slave, name) | ||
for i, m in enumerate(masters): | ||
dest = getattr(m, name) | ||
if name == "arb_gnt": | ||
self.comb += dest.eq(source & (selected == i)) | ||
else: | ||
self.comb += dest.eq(source) | ||
|
||
# select master | ||
self.sync += \ | ||
If(~slave.arb_req, | ||
[If(m.arb_req, selected.eq(i)) for i, m in enumerate(masters)] | ||
) | ||
|
||
|
||
class CRIInterconnectShared(Module): | ||
def __init__(self, masters=2, slaves=2): | ||
shared = Interface() | ||
self.submodules.arbiter = CRIArbiter(masters, shared) | ||
self.submodules.decoder = CRIDecoder(slaves, shared) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
from migen import * | ||
from migen.genlib.record import Record, layout_len | ||
from migen.genlib.fsm import FSM | ||
from misoc.interconnect.csr import * | ||
from misoc.interconnect import stream, wishbone | ||
|
||
from artiq.gateware.rtio import cri | ||
|
||
|
||
class WishboneReader(Module): | ||
def __init__(self, bus=None): | ||
if bus is None: | ||
bus = wishbone.Interface | ||
self.bus = bus | ||
|
||
aw = len(bus.adr) | ||
dw = len(bus.dat_w) | ||
self.sink = stream.Endpoint([("address", aw)]) | ||
self.source = stream.Endpoint([("data", dw)]) | ||
|
||
# # # | ||
|
||
bus_stb = Signal() | ||
data_reg_loaded = Signal() | ||
|
||
self.comb += [ | ||
bus_stb.eq(self.sink.stb & (~data_reg_loaded | self.source.ack)), | ||
bus.cyc.eq(bus_stb), | ||
bus.stb.eq(bus_stb), | ||
bus.adr.eq(self.sink.address), | ||
self.sink.ack.eq(bus.ack), | ||
self.source.stb.eq(data_reg_loaded), | ||
] | ||
self.sync += [ | ||
If(self.source.ack, data_reg_loaded.eq(0)), | ||
If(bus.ack, | ||
data_reg_loaded.eq(1), | ||
self.source.data.eq(bus.dat_r), | ||
self.source.eop.eq(self.sink.eop) | ||
) | ||
] | ||
|
||
|
||
class DMAReader(Module, AutoCSR): | ||
def __init__(self, membus, enable): | ||
aw = len(membus.adr) | ||
data_alignment = log2_int(len(membus.dat_w)//8) | ||
|
||
self.submodules.wb_reader = WishboneReader(membus) | ||
self.source = self.wb_reader.source | ||
|
||
# All numbers in bytes | ||
self.base_address = CSRStorage(aw + data_alignment, | ||
alignment_bits=data_alignment) | ||
self.last_address = CSRStorage(aw + data_alignment, | ||
alignment_bits=data_alignment) | ||
|
||
# # # | ||
|
||
enable_r = Signal() | ||
address = self.wb_reader.sink | ||
self.sync += [ | ||
enable_r.eq(enable), | ||
If(enable & ~enable_r, | ||
address.address.eq(self.base_address.storage), | ||
address.eop.eq(0), | ||
address.stb.eq(1) | ||
), | ||
If(address.stb & address.ack, | ||
If(address.eop, | ||
address.stb.eq(0) | ||
).Else( | ||
address.address.eq(address.address + 1), | ||
If(~enable | (address.address == self.last_address.storage), | ||
address.eop.eq(1) | ||
) | ||
) | ||
) | ||
] | ||
|
||
|
||
class RawSlicer(Module): | ||
def __init__(self, in_size, out_size, granularity): | ||
g = granularity | ||
|
||
self.sink = stream.Endpoint([("data", in_size*g)]) | ||
self.source = Signal(out_size*g) | ||
self.source_stb = Signal() | ||
self.source_consume = Signal(max=out_size+1) | ||
|
||
# # # | ||
|
||
# worst-case buffer space required (when loading): | ||
# <data being shifted out> <new incoming word> <EOP marker> | ||
buf_size = out_size - 1 + in_size + 1 | ||
buf = Signal(buf_size*g) | ||
self.comb += self.source.eq(buf[:out_size]) | ||
|
||
level = Signal(max=buf_size+1) | ||
next_level = Signal(max=buf_size+1) | ||
self.sync += level.eq(next_level) | ||
self.comb += next_level.eq(level) | ||
|
||
load_buf = Signal() | ||
shift_buf = Signal() | ||
|
||
self.sync += [ | ||
If(load_buf, Case(level, | ||
# note how the MSBs of the buffer are set to 0 | ||
# (including the EOP marker position) | ||
{i: buf[i*g:].eq(self.sink.data) | ||
for i in range(out_size)})), | ||
If(shift_buf, buf.eq(buf >> self.source_consume*g)) | ||
] | ||
|
||
fsm = FSM(reset_state="FETCH") | ||
self.submodules += fsm | ||
|
||
fsm.act("FETCH", | ||
self.sink.ack.eq(1), | ||
load_buf.eq(1), | ||
If(self.sink.stb, | ||
If(self.sink.eop, | ||
# insert <granularity> bits of 0 to mark EOP | ||
next_level.eq(level + in_size + 1) | ||
).Else( | ||
next_level.eq(level + in_size) | ||
) | ||
), | ||
If(next_level >= out_size, NextState("OUTPUT")) | ||
) | ||
fsm.act("OUTPUT", | ||
self.source_stb.eq(1), | ||
shift_buf.eq(1), | ||
next_level.eq(level - self.source_consume), | ||
If(next_level < out_size, NextState("FETCH")) | ||
) | ||
|
||
|
||
record_layout = [ | ||
("length", 8), # of whole record (header+data) | ||
("channel", 24), | ||
("timestamp", 64), | ||
("address", 16), | ||
("data", 512) # variable length | ||
] | ||
|
||
|
||
class RecordConverter(Module): | ||
def __init__(self, stream_slicer): | ||
self.source = stream.Endpoint(record_layout) | ||
|
||
hdrlen = layout_len(record_layout) - 512 | ||
record_raw = Record(record_layout) | ||
self.comb += [ | ||
record_raw.raw_bits().eq(stream_slicer.source), | ||
|
||
self.source.channel.eq(record_raw.channel), | ||
self.source.timestamp.eq(record_raw.timestamp), | ||
self.source.address.eq(record_raw.address), | ||
Case(record_raw.length, | ||
{hdrlen+i*8: self.source.data.eq(record_raw.data[:]) | ||
for i in range(512//8)}), | ||
|
||
self.source.stb.eq(stream_slicer.source_stb), | ||
self.source.eop.eq(record_raw.length == 0), | ||
If(self.source.ack, | ||
If(record_raw.length == 0, | ||
stream_slicer.source_consume.eq(1) | ||
).Else( | ||
stream_slicer.source_consume.eq(record_raw.length) | ||
) | ||
) | ||
] | ||
|
||
|
||
class RecordSlicer(Module): | ||
def __init__(self, in_size): | ||
self.submodules.raw_slicer = RawSlicer( | ||
in_size, layout_len(record_layout)//8, 8) | ||
self.submodules.record_converter = RecordConverter(self.raw_slicer) | ||
self.sink = self.raw_slicer.sink | ||
self.source = self.record_converter.source | ||
|
||
|
||
class TimeOffset(Module, AutoCSR): | ||
def __init__(self): | ||
self.time_offset = CSRStorage(64) | ||
self.source = stream.Endpoint(record_layout) | ||
self.sink = stream.Endpoint(record_layout) | ||
|
||
# # # | ||
|
||
pipe_ce = Signal() | ||
self.sync += \ | ||
If(pipe_ce, | ||
self.sink.payload.connect(self.source.payload, | ||
leave_out={"timestamp"}), | ||
self.source.payload.timestamp.eq(self.sink.payload.timestamp | ||
+ self.time_offset.storage), | ||
self.source.stb.eq(self.sink.stb) | ||
) | ||
self.comb += [ | ||
pipe_ce.eq(self.source.ack | ~self.source.stb), | ||
self.sink.ack.eq(pipe_ce) | ||
] | ||
|
||
|
||
class CRIMaster(Module, AutoCSR): | ||
def __init__(self): | ||
self.arb_req = CSRStorage() | ||
self.arb_gnt = CSRStatus() | ||
|
||
self.error_status = CSRStatus(5) # same encoding as RTIO status | ||
self.error_underflow_reset = CSR() | ||
self.error_sequence_error_reset = CSR() | ||
self.error_collision_reset = CSR() | ||
self.error_busy_reset = CSR() | ||
|
||
self.error_channel = CSRStatus(24) | ||
self.error_timestamp = CSRStatus(64) | ||
self.error_address = CSRStatus(16) | ||
|
||
self.sink = stream.Endpoint(record_layout) | ||
self.cri = cri.Interface() | ||
self.busy = Signal() | ||
|
||
# # # | ||
|
||
self.comb += [ | ||
self.cri.arb_req.eq(self.arb_req.storage), | ||
self.arb_gnt.status.eq(self.cri.arb_gnt) | ||
] | ||
|
||
error_set = Signal(4) | ||
for i, rcsr in enumerate([self.error_underflow_reset, self.error_sequence_error_reset, | ||
self.error_collision_reset, self.error_busy_reset]): | ||
# bit 0 is RTIO wait and always 0 here | ||
bit = i + 1 | ||
self.sync += [ | ||
If(error_set[i], | ||
self.error_status.status[bit].eq(1), | ||
self.error_channel.status.eq(self.sink.channel), | ||
self.error_timestamp.status.eq(self.sink.timestamp), | ||
self.error_address.status.eq(self.sink.address) | ||
), | ||
If(rcsr.re, self.error_status.status[bit].eq(0)) | ||
] | ||
|
||
self.comb += [ | ||
self.cri.chan_sel.eq(self.sink.channel), | ||
self.cri.o_timestamp.eq(self.sink.timestamp), | ||
self.cri.o_address.eq(self.sink.address), | ||
] | ||
|
||
fsm = FSM(reset_state="IDLE") | ||
self.submodules += fsm | ||
|
||
fsm.act("IDLE", | ||
If(self.error_status.status == 0, | ||
If(self.sink.stb, NextState("WRITE")) | ||
).Else( | ||
# discard all data until errors are acked | ||
self.sink.ack.eq(1) | ||
) | ||
) | ||
fsm.act("WRITE", | ||
self.busy.eq(1), | ||
self.cri.cmd.eq(cri.commands["write"]), | ||
NextState("CHECK_STATE") | ||
) | ||
fsm.act("CHECK_STATE", | ||
self.busy.eq(1), | ||
If(~self.cri.o_status, | ||
self.sink.ack.eq(1), | ||
NextState("IDLE") | ||
), | ||
If(self.cri.o_status[1], NextState("UNDERFLOW")), | ||
If(self.cri.o_status[2], NextState("SEQUENCE_ERROR")), | ||
If(self.cri.o_status[3], NextState("COLLISION")), | ||
If(self.cri.o_status[4], NextState("BUSY")) | ||
) | ||
for n, name in enumerate(["UNDERFLOW", "SEQUENCE_ERROR", | ||
"COLLISION", "BUSY"]): | ||
fsm.act(name, | ||
self.busy.eq(1), | ||
error_set.eq(1 << n), | ||
self.cri.cmd.eq(cri.commands["o_" + name.lower() + "_reset"]), | ||
self.sink.ack.eq(1), | ||
NextState("IDLE") | ||
) | ||
|
||
|
||
class DMA(Module): | ||
def __init__(self, membus): | ||
# shutdown procedure: set enable to 0, wait until busy=0 | ||
self.enable = CSRStorage() | ||
self.busy = CSRStatus() | ||
|
||
self.submodules.dma = DMAReader(membus, self.enable.storage) | ||
self.submodules.slicer = RecordSlicer(len(membus.dat_w)) | ||
self.submodules.time_offset = TimeOffset() | ||
self.submodules.cri_master = CRIMaster() | ||
self.cri = self.cri_master.cri | ||
|
||
self.comb += [ | ||
self.dma.source.connect(self.slicer.sink), | ||
self.slicer.source.connect(self.time_offset.sink), | ||
self.time_offset.source.connect(self.cri_master.sink) | ||
] | ||
|
||
fsm = FSM(reset_state="IDLE") | ||
self.submodules += fsm | ||
|
||
fsm.act("IDLE", | ||
If(self.enable.storage, NextState("FLOWING")) | ||
) | ||
fsm.act("FLOWING", | ||
self.busy.status.eq(1), | ||
If(self.cri_master.sink.stb & self.cri_master.sink.ack & self.cri_master.sink.eop, | ||
NextState("WAIT_CRI_MASTER") | ||
) | ||
) | ||
fsm.act("WAIT_CRI_MASTER", | ||
self.busy.status.eq(1), | ||
If(~self.cri_master.busy, NextState("IDLE")) | ||
) | ||
|
||
def get_csrs(self): | ||
return ([self.enable, self.busy] + | ||
self.dma.get_csrs() + self.time_offset.get_csrs() + | ||
self.cri_master.get_csrs()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#!/usr/bin/env python3.5 | ||
|
||
import argparse | ||
|
||
from migen import * | ||
from migen.build.generic_platform import * | ||
|
||
from misoc.targets.kc705 import MiniSoC, soc_kc705_args, soc_kc705_argdict | ||
from misoc.integration.builder import builder_args, builder_argdict | ||
|
||
from artiq.gateware.soc import AMPSoC, build_artiq_soc | ||
from artiq.gateware import rtio | ||
from artiq.gateware.rtio.phy import ttl_simple | ||
from artiq.gateware.drtio.transceiver import gtx_7series | ||
from artiq.gateware.drtio import DRTIOMaster | ||
from artiq import __version__ as artiq_version | ||
|
||
|
||
fmc_clock_io = [ | ||
("ad9154_refclk", 0, | ||
Subsignal("p", Pins("HPC:GBTCLK0_M2C_P")), | ||
Subsignal("n", Pins("HPC:GBTCLK0_M2C_N")), | ||
) | ||
] | ||
|
||
|
||
class Master(MiniSoC, AMPSoC): | ||
mem_map = { | ||
"timer_kernel": 0x10000000, | ||
"rtio": 0x20000000, | ||
"rtio_dma": 0x30000000, | ||
"mailbox": 0x70000000 | ||
} | ||
mem_map.update(MiniSoC.mem_map) | ||
|
||
def __init__(self, cfg, medium, **kwargs): | ||
MiniSoC.__init__(self, | ||
cpu_type="or1k", | ||
sdram_controller_type="minicon", | ||
l2_size=128*1024, | ||
with_timer=False, | ||
ident=artiq_version, | ||
**kwargs) | ||
AMPSoC.__init__(self) | ||
|
||
platform = self.platform | ||
|
||
if medium == "sfp": | ||
self.comb += platform.request("sfp_tx_disable_n").eq(1) | ||
tx_pads = platform.request("sfp_tx") | ||
rx_pads = platform.request("sfp_rx") | ||
elif medium == "sma": | ||
tx_pads = platform.request("user_sma_mgt_tx") | ||
rx_pads = platform.request("user_sma_mgt_rx") | ||
else: | ||
raise ValueError | ||
|
||
if cfg == "simple_gbe": | ||
# GTX_1000BASE_BX10 Ethernet compatible, 62.5MHz RTIO clock | ||
# simple TTLs | ||
self.submodules.transceiver = gtx_7series.GTX_1000BASE_BX10( | ||
clock_pads=platform.request("sgmii_clock"), | ||
tx_pads=tx_pads, | ||
rx_pads=rx_pads, | ||
sys_clk_freq=self.clk_freq, | ||
clock_div2=True) | ||
elif cfg == "sawg_3g": | ||
# 3Gb link, 150MHz RTIO clock | ||
# with SAWG on local RTIO and AD9154-FMC-EBZ | ||
platform.register_extension(fmc_clock_io) | ||
self.submodules.transceiver = gtx_7series.GTX_3G( | ||
clock_pads=platform.request("ad9154_refclk"), | ||
tx_pads=tx_pads, | ||
rx_pads=rx_pads, | ||
sys_clk_freq=self.clk_freq) | ||
else: | ||
raise ValueError | ||
self.submodules.drtio = DRTIOMaster(self.transceiver) | ||
self.csr_devices.append("drtio") | ||
|
||
rtio_clk_period = 1e9/self.transceiver.rtio_clk_freq | ||
platform.add_period_constraint(self.transceiver.txoutclk, rtio_clk_period) | ||
platform.add_period_constraint(self.transceiver.rxoutclk, rtio_clk_period) | ||
platform.add_false_path_constraints( | ||
self.crg.cd_sys.clk, | ||
self.transceiver.txoutclk, self.transceiver.rxoutclk) | ||
|
||
rtio_channels = [] | ||
for i in range(8): | ||
phy = ttl_simple.Output(platform.request("user_led", i)) | ||
self.submodules += phy | ||
rtio_channels.append(rtio.Channel.from_phy(phy)) | ||
for sma in "user_sma_gpio_p", "user_sma_gpio_n": | ||
phy = ttl_simple.Inout(platform.request(sma)) | ||
self.submodules += phy | ||
rtio_channels.append(rtio.Channel.from_phy(phy)) | ||
self.submodules.rtio_core = rtio.Core(rtio_channels, 3) | ||
|
||
self.submodules.rtio = rtio.KernelInitiator() | ||
self.submodules.rtio_dma = 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.drtio.cri, self.rtio_core.cri]) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
description="ARTIQ with DRTIO on KC705 - Master") | ||
builder_args(parser) | ||
soc_kc705_args(parser) | ||
parser.add_argument("-c", "--config", default="simple_gbe", | ||
help="configuration: simple_gbe/sawg_3g " | ||
"(default: %(default)s)") | ||
parser.add_argument("--medium", default="sfp", | ||
help="medium to use for transceiver link: sfp/sma " | ||
"(default: %(default)s)") | ||
args = parser.parse_args() | ||
|
||
soc = Master(args.config, args.medium, **soc_kc705_argdict(args)) | ||
build_artiq_soc(soc, builder_argdict(args)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
import argparse | ||
|
||
from migen import * | ||
from migen.build.generic_platform import * | ||
from migen.build.platforms import kc705 | ||
|
||
from misoc.cores.i2c import * | ||
from misoc.cores.sequencer import * | ||
|
||
from artiq.gateware import rtio | ||
from artiq.gateware.rtio.phy import ttl_simple | ||
from artiq.gateware.drtio.transceiver import gtx_7series | ||
from artiq.gateware.drtio import DRTIOSatellite | ||
|
||
|
||
# TODO: parameters for sawg_3g | ||
def get_i2c_program(sys_clk_freq): | ||
# NOTE: the logical parameters DO NOT MAP to physical values written | ||
# into registers. They have to be mapped; see the datasheet. | ||
# DSPLLsim reports the logical parameters in the design summary, not | ||
# the physical register values (but those are present separately). | ||
N1_HS = 6 # 10 | ||
NC1_LS = 7 # 8 | ||
N2_HS = 6 # 10 | ||
N2_LS = 20111 # 20112 | ||
N31 = 2513 # 2514 | ||
N32 = 4596 # 4597 | ||
|
||
i2c_sequence = [ | ||
# PCA9548: select channel 7 | ||
[(0x74 << 1), 1 << 7], | ||
# Si5324: configure | ||
[(0x68 << 1), 0, 0b01010000], # FREE_RUN=1 | ||
[(0x68 << 1), 1, 0b11100100], # CK_PRIOR2=1 CK_PRIOR1=0 | ||
[(0x68 << 1), 2, 0b0010 | (4 << 4)], # BWSEL=4 | ||
[(0x68 << 1), 3, 0b0101 | 0x10], # SQ_ICAL=1 | ||
[(0x68 << 1), 4, 0b10010010], # AUTOSEL_REG=b10 | ||
[(0x68 << 1), 6, 0x07], # SFOUT1_REG=b111 | ||
[(0x68 << 1), 25, (N1_HS << 5 ) & 0xff], | ||
[(0x68 << 1), 31, (NC1_LS >> 16) & 0xff], | ||
[(0x68 << 1), 32, (NC1_LS >> 8 ) & 0xff], | ||
[(0x68 << 1), 33, (NC1_LS) & 0xff], | ||
[(0x68 << 1), 40, (N2_HS << 5 ) & 0xff | | ||
(N2_LS >> 16) & 0xff], | ||
[(0x68 << 1), 41, (N2_LS >> 8 ) & 0xff], | ||
[(0x68 << 1), 42, (N2_LS) & 0xff], | ||
[(0x68 << 1), 43, (N31 >> 16) & 0xff], | ||
[(0x68 << 1), 44, (N31 >> 8) & 0xff], | ||
[(0x68 << 1), 45, (N31) & 0xff], | ||
[(0x68 << 1), 46, (N32 >> 16) & 0xff], | ||
[(0x68 << 1), 47, (N32 >> 8) & 0xff], | ||
[(0x68 << 1), 48, (N32) & 0xff], | ||
[(0x68 << 1), 137, 0x01], # FASTLOCK=1 | ||
[(0x68 << 1), 136, 0x40], # ICAL=1 | ||
] | ||
|
||
program = [ | ||
InstWrite(I2C_CONFIG_ADDR, int(sys_clk_freq/1e3)), | ||
] | ||
for subseq in i2c_sequence: | ||
program += [ | ||
InstWrite(I2C_XFER_ADDR, I2C_START), | ||
InstWait(I2C_XFER_ADDR, I2C_IDLE), | ||
] | ||
for octet in subseq: | ||
program += [ | ||
InstWrite(I2C_XFER_ADDR, I2C_WRITE | octet), | ||
InstWait(I2C_XFER_ADDR, I2C_IDLE), | ||
] | ||
program += [ | ||
InstWrite(I2C_XFER_ADDR, I2C_STOP), | ||
InstWait(I2C_XFER_ADDR, I2C_IDLE), | ||
] | ||
program += [ | ||
InstEnd(), | ||
] | ||
return program | ||
|
||
|
||
class Si5324ResetClock(Module): | ||
def __init__(self, platform, sys_clk_freq): | ||
self.si5324_not_ready = Signal(reset=1) | ||
|
||
# minimum reset pulse 1us | ||
reset_done = Signal() | ||
si5324_rst_n = platform.request("si5324").rst_n | ||
reset_val = int(sys_clk_freq*1.1e-6) | ||
reset_ctr = Signal(max=reset_val+1, reset=reset_val) | ||
self.sync += \ | ||
If(reset_ctr != 0, | ||
reset_ctr.eq(reset_ctr - 1) | ||
).Else( | ||
si5324_rst_n.eq(1), | ||
reset_done.eq(1) | ||
) | ||
# 10ms after reset to microprocessor access ready | ||
ready_val = int(sys_clk_freq*11e-3) | ||
ready_ctr = Signal(max=ready_val+1, reset=ready_val) | ||
self.sync += \ | ||
If(reset_done, | ||
If(ready_ctr != 0, | ||
ready_ctr.eq(ready_ctr - 1) | ||
).Else( | ||
self.si5324_not_ready.eq(0) | ||
) | ||
) | ||
|
||
si5324_clkin = platform.request("si5324_clkin") | ||
self.specials += \ | ||
Instance("OBUFDS", | ||
i_I=ClockSignal("rtio_rx"), | ||
o_O=si5324_clkin.p, o_OB=si5324_clkin.n | ||
) | ||
|
||
|
||
fmc_clock_io = [ | ||
("ad9154_refclk", 0, | ||
Subsignal("p", Pins("HPC:GBTCLK0_M2C_P")), | ||
Subsignal("n", Pins("HPC:GBTCLK0_M2C_N")), | ||
) | ||
] | ||
|
||
|
||
class Satellite(Module): | ||
def __init__(self, cfg, medium, toolchain): | ||
self.platform = platform = kc705.Platform(toolchain=toolchain) | ||
|
||
rtio_channels = [] | ||
for i in range(8): | ||
phy = ttl_simple.Output(platform.request("user_led", i)) | ||
self.submodules += phy | ||
rtio_channels.append(rtio.Channel.from_phy(phy)) | ||
for sma in "user_sma_gpio_p", "user_sma_gpio_n": | ||
phy = ttl_simple.Inout(platform.request(sma)) | ||
self.submodules += phy | ||
rtio_channels.append(rtio.Channel.from_phy(phy)) | ||
|
||
sys_clock_pads = platform.request("clk156") | ||
self.clock_domains.cd_sys = ClockDomain(reset_less=True) | ||
self.specials += Instance("IBUFGDS", | ||
i_I=sys_clock_pads.p, i_IB=sys_clock_pads.n, | ||
o_O=self.cd_sys.clk) | ||
sys_clk_freq = 156000000 | ||
|
||
i2c_master = I2CMaster(platform.request("i2c")) | ||
sequencer = ResetInserter()(Sequencer(get_i2c_program(sys_clk_freq))) | ||
si5324_reset_clock = Si5324ResetClock(platform, sys_clk_freq) | ||
self.submodules += i2c_master, sequencer, si5324_reset_clock | ||
self.comb += [ | ||
sequencer.bus.connect(i2c_master.bus), | ||
sequencer.reset.eq(si5324_reset_clock.si5324_not_ready) | ||
] | ||
|
||
if medium == "sfp": | ||
self.comb += platform.request("sfp_tx_disable_n").eq(1) | ||
tx_pads = platform.request("sfp_tx") | ||
rx_pads = platform.request("sfp_rx") | ||
elif medium == "sma": | ||
tx_pads = platform.request("user_sma_mgt_tx") | ||
rx_pads = platform.request("user_sma_mgt_rx") | ||
else: | ||
raise ValueError | ||
|
||
if cfg == "simple_gbe": | ||
# GTX_1000BASE_BX10 Ethernet compatible, 62.5MHz RTIO clock | ||
# simple TTLs | ||
self.submodules.transceiver = gtx_7series.GTX_1000BASE_BX10( | ||
clock_pads=platform.request("sgmii_clock"), | ||
tx_pads=tx_pads, | ||
rx_pads=rx_pads, | ||
sys_clk_freq=sys_clk_freq, | ||
clock_div2=True) | ||
elif cfg == "sawg_3g": | ||
# 3Gb link, 150MHz RTIO clock | ||
# with SAWG on local RTIO and AD9154-FMC-EBZ | ||
platform.register_extension(fmc_clock_io) | ||
self.submodules.transceiver = gtx_7series.GTX_3G( | ||
clock_pads=platform.request("ad9154_refclk"), | ||
tx_pads=tx_pads, | ||
rx_pads=rx_pads, | ||
sys_clk_freq=sys_clk_freq) | ||
else: | ||
raise ValueError | ||
self.submodules.rx_synchronizer = gtx_7series.RXSynchronizer( | ||
self.transceiver.rtio_clk_freq) | ||
self.submodules.drtio = DRTIOSatellite( | ||
self.transceiver, self.rx_synchronizer, rtio_channels) | ||
|
||
rtio_clk_period = 1e9/self.transceiver.rtio_clk_freq | ||
platform.add_period_constraint(self.transceiver.txoutclk, rtio_clk_period) | ||
platform.add_period_constraint(self.transceiver.rxoutclk, rtio_clk_period) | ||
platform.add_false_path_constraints( | ||
sys_clock_pads, | ||
self.transceiver.txoutclk, self.transceiver.rxoutclk) | ||
|
||
|
||
def build(self, *args, **kwargs): | ||
self.platform.build(self, *args, **kwargs) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser(description="KC705 DRTIO satellite") | ||
parser.add_argument("--toolchain", default="vivado", | ||
help="FPGA toolchain to use: ise, vivado") | ||
parser.add_argument("--output-dir", default="drtiosat_kc705", | ||
help="output directory for generated " | ||
"source files and binaries") | ||
parser.add_argument("-c", "--config", default="simple_gbe", | ||
help="configuration: simple_gbe/sawg_3g " | ||
"(default: %(default)s)") | ||
parser.add_argument("--medium", default="sfp", | ||
help="medium to use for transceiver link: sfp/sma " | ||
"(default: %(default)s)") | ||
args = parser.parse_args() | ||
|
||
top = Satellite(args.config, args.medium, args.toolchain) | ||
top.build(build_dir=args.output_dir) | ||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.