diff --git a/build_CI.sh b/build_CI.sh
new file mode 100755
index 0000000..6e26702
--- /dev/null
+++ b/build_CI.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+set -e
+
+if [ -z "$OPENOCD_ZYNQ" ]; then
+ echo "OPENOCD_ZYNQ environment variable must be set"
+ exit 1
+fi
+if [ -z "$SZL" ]; then
+ echo "SZL environment variable must be set"
+ exit 1
+fi
+
+
+# variant="firmware"
+# variant="gateware"
+# variant="jtag"
+variant="sd"
+
+nix build .#kasli_soc-demo-$variant -L
+nix build .#kasli_soc-master-$variant -L
+nix build .#kasli_soc-satellite-$variant -L
+
+# nix build .#zc706-acpki_nist_clock-$variant -L
+# nix build .#zc706-acpki_nist_clock_master-$variant -L
+# nix build .#zc706-acpki_nist_clock_satellite-$variant -L
+
+# nix build .#zc706-acpki_nist_qc2-$variant -L
+# nix build .#zc706-acpki_nist_qc2_master-$variant -L
+# nix build .#zc706-acpki_nist_qc2_satellite-$variant -L
+
+# nix build .#zc706-nist_clock-$variant -L
+# nix build .#zc706-nist_clock_master-$variant -L
+# nix build .#zc706-nist_clock_satellite-$variant -L
+
+# nix build .#zc706-nist_qc2-$variant -L
+# nix build .#zc706-nist_qc2_master-$variant -L
+# nix build .#zc706-nist_qc2_satellite-$variant -L
\ No newline at end of file
diff --git a/cargofmt.sh b/cargofmt.sh
new file mode 100755
index 0000000..909f74e
--- /dev/null
+++ b/cargofmt.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+nix-shell -p gnumake --command 'make manifests -B'
+cd src
+cargo fmt -- --check
\ No newline at end of file
diff --git a/coaxpress.drawio b/coaxpress.drawio
new file mode 100644
index 0000000..7d5bf7d
--- /dev/null
+++ b/coaxpress.drawio
@@ -0,0 +1,338 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cxp_note.md b/cxp_note.md
new file mode 100644
index 0000000..30bd56e
--- /dev/null
+++ b/cxp_note.md
@@ -0,0 +1,57 @@
+# CXP
+
+## Finished
+- Upconn - Low speed serial
+ [x] Low speed serial PHY
+ [x] 20.833Mbps & 41.666Mbps change
+ [x] 8b10b encoder
+ [x] TX Pipeline with priority transmission
+ [x] Trigger
+ [x] Trigger ack
+ [x] Test & Ctrl packet with DMA
+ [x] CTRL Packet serialize firmware
+ [x] follow DRTIO DMA
+ [x] check crc
+- Downconn - GTX
+ [x] GTX serial PHY
+ [x] QPLL & GTX DRP to config linerate
+ [x] Comma checker & restart rx
+ [x] RX Pipeline with priority decoder
+ [x] Trigger ack
+ [x] CTRL packet DMA with extra buffer
+ [x] Connection test sequence checker
+ [x] CTRL Packet deserialize firmware
+ [x] follow DRTIO DMA
+ [x] check crc
+- Camera boostrap
+
+## TODO
+[] remove ALL debug tools
+ [] flake.nix mod
+ [] local_run.sh mod
+### Gateware
+[] GTX Multilane setup
+[] Region of interest engine
+[] rtio to getting the frame
+ - O: trigger
+ - I: frame
+ - frame crc checker
+
+### Firmware
+[] Camera boostrap
+ - get the CXP version
+ - test connection
+ - discovery other extension (links)
+ - set bitrate
+[] Camera linkdown detection
+[] API programming
+[] add tag handling for api calls
+ - support line reset in kernel using syscall
+[] add heartbeat checking
+
+### Coredevice Driver
+[] support simple GenICam api
+ - camera specific register that hold the same value between reset
+ - support sub-array readout (i.e Region of Interest (ROI))
+
+
diff --git a/flake.lock b/flake.lock
index 4157d84..0fb0bc9 100644
--- a/flake.lock
+++ b/flake.lock
@@ -11,11 +11,11 @@
"src-pythonparser": "src-pythonparser"
},
"locked": {
- "lastModified": 1732066716,
- "narHash": "sha256-krjvt9+RccnAxSEZcFhRpjA2S3CoqE4MSa1JUg421b4=",
+ "lastModified": 1725373154,
+ "narHash": "sha256-fq9EW9fDWrV0v1vNj7ZqDNpNYx8+OxoFdPwpvkPf67g=",
"ref": "refs/heads/master",
- "rev": "270a417a28b516d36983779a1adb6d33a3c55a4a",
- "revCount": 9102,
+ "rev": "0c1ffa9f4f6a3e7864459923ec4b9cc45f16327a",
+ "revCount": 9005,
"type": "git",
"url": "https://github.com/m-labs/artiq.git"
},
@@ -70,11 +70,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1731319897,
- "narHash": "sha256-PbABj4tnbWFMfBp6OcUK5iGy1QY+/Z96ZcLpooIbuEI=",
+ "lastModified": 1724224976,
+ "narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "dc460ec76cbff0e66e269457d7b728432263166c",
+ "rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
"type": "github"
},
"original": {
@@ -175,11 +175,11 @@
"src-migen": {
"flake": false,
"locked": {
- "lastModified": 1727677091,
- "narHash": "sha256-Zg3SQnTwMM/VkOGKogbPyuCC2NhLy8HB2SPEUWWNgCU=",
+ "lastModified": 1724304798,
+ "narHash": "sha256-tQ02N0eXY5W/Z7CrOy3Cu4WjDZDQWb8hYlzsFzr3Mus=",
"owner": "m-labs",
"repo": "migen",
- "rev": "c19ae9f8ae162ffe2d310a92bfce53ac2a821bc8",
+ "rev": "832a7240ba32af9cbd4fdd519ddcb4f912534726",
"type": "github"
},
"original": {
@@ -262,4 +262,4 @@
},
"root": "root",
"version": 7
-}
+}
\ No newline at end of file
diff --git a/notes.drawio b/notes.drawio
new file mode 100644
index 0000000..c351440
--- /dev/null
+++ b/notes.drawio
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/reset.py b/reset.py
new file mode 100644
index 0000000..8a48328
--- /dev/null
+++ b/reset.py
@@ -0,0 +1,17 @@
+from time import sleep
+from pyftdi.ftdi import Ftdi
+
+POR = 1 << 7
+
+def main():
+ dev = Ftdi()
+ dev.open_bitbang_from_url("ftdi://ftdi:4232h/0")
+ dev.set_bitmode(POR, Ftdi.BitMode.BITBANG)
+ dev.write_data(bytes([0]))
+ sleep(0.1)
+ dev.write_data(bytes([POR]))
+ sleep(0.1)
+ dev.close()
+
+if __name__ == "__main__":
+ main()
diff --git a/sat_run.sh b/sat_run.sh
new file mode 100755
index 0000000..3d2c5dc
--- /dev/null
+++ b/sat_run.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+
+python reset.py
+
+set -e
+
+if [ -z "$OPENOCD_ZYNQ" ]; then
+ echo "OPENOCD_ZYNQ environment variable must be set"
+ exit 1
+fi
+if [ -z "$SZL" ]; then
+ echo "SZL environment variable must be set"
+ exit 1
+fi
+
+impure=0
+load_bitstream=1
+board_type="kasli_soc"
+fw_type="satman"
+
+while getopts "ilb:t:f:" opt; do
+ case "$opt" in
+ \?) exit 1
+ ;;
+ i) impure=1
+ ;;
+ l) load_bitstream=0
+ ;;
+ b) board_host=$OPTARG
+ ;;
+ t) board_type=$OPTARG
+ ;;
+ f) fw_type=$OPTARG
+ ;;
+ esac
+done
+
+if [ -z "$board_host" ]; then
+ case $board_type in
+ kasli_soc) board_host="192.168.1.56";;
+ zc706) board_host="192.168.1.52";;
+ *) echo "Unknown board type"; exit 1;;
+ esac
+fi
+
+load_bitstream_cmd=""
+
+build_dir=`pwd`/build
+result_dir=`pwd`/result
+cd $OPENOCD_ZYNQ
+openocd -f $board_type.cfg -c "load_image $SZL/szl-$board_type.elf; resume 0; exit"
+sleep 5
+if [ $impure -eq 1 ]; then
+ if [ $load_bitstream -eq 1 ]; then
+ load_bitstream_cmd="-g $build_dir/gateware/top.bit"
+ fi
+ artiq_netboot $load_bitstream_cmd -f $build_dir/$fw_type.bin -b $board_host
+else
+ if [ $load_bitstream -eq 1 ]; then
+ load_bitstream_cmd="-g $result_dir/top.bit"
+ fi
+ artiq_netboot $load_bitstream_cmd -f $result_dir/$fw_type.bin -b $board_host
+fi
\ No newline at end of file
diff --git a/sim.py b/sim.py
new file mode 100644
index 0000000..5553637
--- /dev/null
+++ b/sim.py
@@ -0,0 +1,55 @@
+from migen import *
+from misoc.interconnect.csr import *
+
+from functools import reduce
+from itertools import combinations
+from operator import or_, and_
+
+class Voter(Module):
+ def __init__(self):
+ self.data_4x = Signal(32)
+ self.k_4x = Signal(4)
+
+ # Section 9.2.2.1 (CXP-001-2021)
+ # decoder should immune to single bit errors when handling duplicated characters
+ self.char = Signal(8)
+ self.k = Signal()
+
+
+ # majority voting
+ char = [[self.data_4x[i*8:(i+1)*8], self.k_4x[i]] for i in range(4)]
+ voter = [Record([("data", 8), ("k", 1)]) for _ in range(4)]
+
+ # stage 1
+ for i, code in enumerate(combinations(char, 3)):
+ self.sync += [
+ voter[i].data.eq(reduce(and_, [c[0] for c in code])),
+ voter[i].k.eq(reduce(and_, [c[1] for c in code])),
+ ]
+
+ # stage 2
+ self.sync += [
+ self.char.eq(reduce(or_, [v.data for v in voter])),
+ self.k.eq(reduce(or_, [v.k for v in voter])),
+ ]
+
+
+
+
+dut = Voter()
+def check_case(data_4x, k_4x, char, k):
+ yield dut.data_4x.eq(data_4x)
+ yield dut.k_4x.eq(k_4x)
+ yield
+ yield
+ yield
+ print(f"char = {yield dut.char:#X} k = {yield dut.k:#X}")
+ assert (yield dut.char) == char and (yield dut.k) == k
+
+def testbench():
+ yield from check_case(0xFFFFFFFF, 0b1111, 0xFF, 1)
+ yield from check_case(0xFFFFFF00, 0b1110, 0xFF, 1)
+ yield from check_case(0xFFFFF00f, 0b0001, 0xFF, 0)
+ yield from check_case(0xFFFFFFFF, 0b1111, 0xFF, 1)
+
+run_simulation(dut, testbench())
diff --git a/sim_comb.py b/sim_comb.py
new file mode 100644
index 0000000..7595098
--- /dev/null
+++ b/sim_comb.py
@@ -0,0 +1,28 @@
+from migen import *
+from misoc.interconnect import stream
+
+class Frame(Module):
+ def __init__(self):
+ self.a = Signal()
+ self.b = Signal()
+ self.comb += [
+ self.a.eq(self.b),
+ # self.b.eq(self.a),
+ ]
+
+dut = Frame()
+
+def check_case():
+ yield dut.a.eq(1)
+ yield
+ yield dut.a.eq(0)
+ yield
+ for i in range(10):
+ yield
+
+
+def testbench():
+ yield from check_case()
+
+
+run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")
diff --git a/sim_crc.py b/sim_crc.py
new file mode 100644
index 0000000..eef9d00
--- /dev/null
+++ b/sim_crc.py
@@ -0,0 +1,58 @@
+from migen import *
+from misoc.interconnect import stream
+from sim_pipeline import *
+from src.gateware.cxp_pipeline import *
+
+dut = StreamData_Generator()
+
+
+def check_case(packet=[], ack=0):
+ print("=================TEST========================")
+ for i, p in enumerate(packet):
+ yield dut.sink.data.eq(p["data"])
+ yield dut.sink.k.eq(p["k"])
+ yield dut.sink.stb.eq(1)
+ if "eop" in p:
+ yield dut.sink.eop.eq(1)
+
+ # CLK
+ yield
+
+ sink = dut.sink
+ source = dut.source
+ crc = dut.crc_inserter.crc
+ print(
+ # f"\n CYCLE#{i} : sink char = {yield sink.data:#X} k = {yield sink.k:#X}"
+ f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X}"
+ f" stb = {yield source.stb} eop = {yield source.eop} ack = {yield source.ack} "
+ f"\nCYCLE#{i} : crc error = {yield crc.error:#X} crc value = {yield crc.value:#X}"
+ f" crc data = {yield crc.data:#X} engine next = {yield crc.engine.next:#X}"
+ f"\nCYCLE#{i} : crc ce = {yield crc.ce:#X} "
+ )
+ # extra clk cycles
+ cyc = i + 1
+ for i in range(cyc, cyc + 11):
+ # yield has memory for some reason
+ yield dut.sink.stb.eq(0)
+ yield dut.source.ack.eq(1)
+ yield
+ print(
+ # f"\n CYCLE#{i} : sink char = {yield sink.data:#X} k = {yield sink.k:#X}"
+ f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X}"
+ f" stb = {yield source.stb} eop = {yield source.eop} ack = {yield source.ack} "
+ f"\nCYCLE#{i} : crc error = {yield crc.error:#X} crc value = {yield crc.value:#X}"
+ f" crc data = {yield crc.data:#X} engine next = {yield crc.engine.next:#X}"
+ f"\nCYCLE#{i} : crc ce = {yield crc.ce:#X} "
+ )
+ assert True
+
+
+def testbench():
+ packet = [
+ {"data": 0x0000_0004, "k": Replicate(0, 4)},
+ {"data": 0x0000_0000, "k": Replicate(0, 4), "eop":1},
+ ]
+ yield from check_case(packet)
+
+
+run_simulation(dut, testbench())
diff --git a/sim_idle.py b/sim_idle.py
new file mode 100644
index 0000000..3b7f2c0
--- /dev/null
+++ b/sim_idle.py
@@ -0,0 +1,149 @@
+from migen import *
+from misoc.interconnect import stream
+
+from src.gateware.cxp_pipeline import Packet_Wrapper
+
+char_width = 8
+word_dw = 32
+word_layout = [("data", word_dw), ("k", word_dw//8)]
+
+
+def K(x, y):
+ return ((y << 5) | x)
+
+KCode = {
+ "pak_start" : C(K(27, 7), char_width),
+ "io_ack" : C(K(28, 6), char_width),
+ "trig_indic_28_2" : C(K(28, 2), char_width),
+ "stream_marker" : C(K(28, 3), char_width),
+ "trig_indic_28_4" : C(K(28, 4), char_width),
+ "pak_end" : C(K(29, 7), char_width),
+ "idle_comma" : C(K(28, 5), char_width),
+ "idle_alignment" : C(K(28, 1), char_width),
+}
+
+class TX_Bootstrap(Module):
+ def __init__(self):
+ self.tx_testseq = Signal()
+
+ # # #
+
+ self.submodules.fsm = fsm = FSM(reset_state="IDLE")
+ self.source = stream.Endpoint(word_layout)
+
+ self.cnt = Signal(max=0xFFF)
+ fsm.act("IDLE",
+ If(self.tx_testseq,
+ NextValue(self.cnt, self.cnt.reset),
+ NextState("WRITE_TEST_PACKET_TYPE"),
+ )
+ )
+
+ fsm.act("WRITE_TEST_PACKET_TYPE",
+ self.source.stb.eq(1),
+ self.source.data.eq(Replicate(C(0x04, char_width), 4)),
+ self.source.k.eq(Replicate(0, 4)),
+ If(self.source.ack,NextState("WRITE_TEST_COUNTER"))
+ )
+
+ # testword = Signal(word_dw)
+ # self.comb += [
+ # testword[:8].eq(self.cnt[:8]),
+ # testword[8:16].eq(self.cnt[:8]+1),
+ # testword[16:24].eq(self.cnt[:8]+2),
+ # testword[24:].eq(self.cnt[:8]+3),
+ # ]
+
+ fsm.act("WRITE_TEST_COUNTER",
+ self.source.stb.eq(1),
+ self.source.data[:8].eq(self.cnt[:8]),
+ self.source.data[8:16].eq(self.cnt[:8]+1),
+ self.source.data[16:24].eq(self.cnt[:8]+2),
+ self.source.data[24:].eq(self.cnt[:8]+3),
+ self.source.k.eq(Cat(0, 0, 0, 0)),
+ If(self.source.ack,
+ If(self.cnt == 0x0FF-3,
+ self.source.eop.eq(1),
+ NextState("IDLE")
+ ).Else(
+ NextValue(self.cnt, self.cnt + 4),
+ )
+
+ )
+ )
+
+class Idle_Word_Inserter(Module):
+ def __init__(self):
+ # Section 9.2.5 (CXP-001-2021)
+ # Send K28.5, K28.1, K28.1, D21.5 as idle word
+ self.submodules.fsm = fsm = FSM(reset_state="WRITE_IDLE")
+
+ self.sink = stream.Endpoint(word_layout)
+ self.source = stream.Endpoint(word_layout)
+
+ cnt = Signal(max=0x10, reset=0xF)
+
+ fsm.act("WRITE_IDLE",
+ self.source.stb.eq(1),
+ self.source.data.eq(Cat(KCode["idle_comma"], KCode["idle_alignment"], KCode["idle_alignment"], C(0xB5, char_width))),
+ self.source.k.eq(Cat(1, 1, 1, 0)),
+
+ self.sink.ack.eq(1),
+ If(self.sink.stb,
+ self.sink.ack.eq(0),
+ If(self.source.ack,
+ NextValue(cnt, cnt.reset),
+ NextState("COPY"),
+ )
+ ),
+ )
+
+ fsm.act("COPY",
+ self.sink.connect(self.source),
+ # increment when upstream has data and got ack
+ If(self.sink.stb & self.source.ack, NextValue(cnt, cnt - 1)),
+ If((((~self.sink.stb) | (self.sink.eop) | (cnt == 0) ) & self.source.ack), NextState("WRITE_IDLE"))
+ )
+
+class Pipeline(Module):
+ def __init__(self):
+ self.submodules.bootstrap = boostrap = TX_Bootstrap()
+ self.submodules.wrapper = wrapper = Packet_Wrapper()
+ # self.submodules.buffer = buffer = stream.SyncFIFO(word_layout, 32)
+ self.submodules.idle_inserter = idle_inserter = Idle_Word_Inserter()
+
+ # # #
+
+ pipeline = [boostrap, wrapper, idle_inserter]
+ for s, d in zip(pipeline, pipeline[1:]):
+ self.comb += s.source.connect(d.sink)
+ # self.sink = pipeline[0].sink
+ self.source = pipeline[-1].source
+
+ # no backpressure
+ # self.comb += self.source.ack.eq(1)
+
+dut = Pipeline()
+
+def check_case():
+ source = dut.source
+ # sink = dut.sink
+ # for i in range(1, 30):
+ # yield sink.data.eq(i)
+ # yield sink.stb.eq(1)
+ # yield
+ yield dut.bootstrap.tx_testseq.eq(1)
+ yield
+ yield dut.bootstrap.tx_testseq.eq(0)
+ for i in range(10):
+ yield
+ for _ in range(100):
+ yield source.ack.eq(1)
+ yield
+
+
+def testbench():
+ yield from check_case()
+
+
+run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")
diff --git a/src/gateware/cxp_rtio.py b/src/gateware/cxp_rtio.py
new file mode 100644
index 0000000..6a1b434
--- /dev/null
+++ b/src/gateware/cxp_rtio.py
@@ -0,0 +1,12 @@
+# Clocking/Reset
+# Create rio and rio_phy domains based on sys
+# with reset controlled by CSR.
+#
+# The `rio` CD contains logic that is reset with `core.reset()`.
+# That's state that could unduly affect subsequent experiments,
+# i.e. input overflows caused by input gates left open, FIFO events far
+# in the future blocking the experiment, pending RTIO or
+# wishbone bus transactions, etc.
+# The `rio_phy` CD contains state that is maintained across
+# `core.reset()`, i.e. TTL output state, OE, DDS state.
+