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. +