Compare commits

...

101 Commits

Author SHA1 Message Date
f6b13f271d sim: prototyping frame decoding pipeine
sim: add double buffer
sim: add eop marker for crc checker in double buffer
sim: add KCode, pak type & CRC generator
sim: add Stream crossbar
sim: add stream pipeline with parser & buffer
sim: add frame generator & image viewer
sim: add arbiter
sim: add broadcaster, double buffer & eop tester
2025-01-22 13:42:07 +08:00
3c44c66ab2 cxp upconn & downconn firmware: packet testing
downconn FW: fix compile err
2025-01-22 13:42:07 +08:00
5b4878f156 cxp GW: add cxp frame routing pipeline
cxp GW: use roi pipeline

cxp GW: add register for cnt to imprve timinig

cxp GW: add cdr for roi pipeline

cxp GW: add back rx debug buffer

cxp GW: move router pipeline to frame pipeline
cxp GW: refactor to combine rx/tx into phy
cxp GW: fix compilation err
cxp GW: remove unused memorywe
cxp GW: update to use word_width
cxp GW: add rtio interface for frame pipeline
cxp GW: fix extra 1 bits issue
cxp GW: add crc error counter
cxp GW: add fifo for rx pipeline
2025-01-22 13:42:07 +08:00
492d7888c3 cxp coredevice driver: init
driver: add api init
driver: add cxp syscall
driver: add xml download
driver: change to using linktrigger (hw trig)
driver: add docs on explaining trigger -> packet receive delay
driver: add roi & gate drivers
driver: save power after trig & add rio timeout
2025-01-22 13:42:07 +08:00
7562e5b9c5 api: add cxp api support for CTRL packet
api: DEBUG print in correct endianese
api: add bytes read for XML download
api: add more debug print
2025-01-22 13:42:07 +08:00
98ed2904a7 libboard_artiq: setup
libboard_artiq: add cxp_downconn & cxp_upconn
libboard_artiq: compile mem with cxp
libboard_artiq: add cxp_proto
libboard_artiq: add cxp_phys
libboard_artiq: add cxp
libboard artiq: add cxp_ctrl
2025-01-22 13:42:07 +08:00
6a063daa49 runtime main: add cxp phys setup 2025-01-22 13:42:07 +08:00
cbeee38ab0 CXP firmware: camera setup init
cxp FW: add camera discovery
cxp FW: add multi channel discovery
cxp FW: add CXP version negotiate
cxp FW: add CXP operation linerate setter
cxp FW: add ConnectReset, CXP version readout
cxp FW: add connection tester
cxp FW: add master channel & topology checker
cxp FW: set packet max size to 2KiB
cxp FW: add frame buffer cfg & hostconnid

cxp fw: add docs explaining stream pak size issue
2025-01-22 13:42:07 +08:00
eceef9a3cd cxp ctrl: Control packet handler
ctrl: add buildin delay_ms
ctrl: add proper RX Downconn ACK & REPLAY packet handling
ctrl: add send, receive & receive with timeout
ctrl: add write/read u32 u64
ctrl: add read bytes
2025-01-22 13:41:19 +08:00
601be17a46 cxp protocol: init
testing: add packet printing helper function
proto FW: use memory buffer for tx and rx
proto FW: use byteoder crate to handle endianness
proto FW: add event packet reader and writer
proto FW: add error correction for 4x char
proto FW: add pending packet
2025-01-22 13:41:19 +08:00
16910ff425 Cargo: add byteorder
Cargo lock: add byteorder

Cargo: update
2025-01-22 13:41:19 +08:00
8c972f4732 cxp_phys: low speed serial & GTX setup
downconn: add QPLL & GTX setup
downconn: add DRP to change linerate up to 12.5Gbps
downconn testing: add txuserclk config
upconn: add low speed serital setup
upconn & downconn: add linerate changer
2025-01-22 13:41:19 +08:00
baab7f92e8 zc706: add CXP_DEMO variant
zc706: add fmc pads
zc706: add constraint to fix comma alignment & setup/hold time issue
zc706: add csr & mem group for cxp
zc706: add CXP to rtio_channel
zc706: add frame buffer pipeline
2025-01-22 13:41:19 +08:00
0d13231eef cxp: add PHY and pipeline
testing: add loopback tx for rx testing
testing: add trigger, trigger ack for testing
cxp: add upconn & downconn phy
cxp: add upconn & downconn pipeline
cxp: add rtlink
cxp: add test packet & error counter CSR
cxp: fix ch1 rx mem cannot be read
cxp: add frame buffer to use KiB instead of KB
2025-01-22 13:41:19 +08:00
89e9a438e7 cxp frame pipeline: frame handling pipeline
pipeline: add eop marker, cxp_crc32 checker
frame: add stream crossbar, double buffer, parser
frame: add metadata parser, frame extractor
frame: add stream arbiter, crc checker & broadcaster
frame: add 8, 10, 12, 14, 16bit pixel gearbox support
frame: add pixel coordinate tracker
2025-01-22 13:41:19 +08:00
86ae02187b cxp pipeline: packet handling pipeline
tx pipeline: add CRC32 inserter
tx pipeline: add start & end of packet code inserter
tx pipeline: add packet wrapper for start & stop packet indication
tx pipeline: add code source for trigger & trigger ack packet
tx pipeline: add packet for trigger & trigger ack
tx pipeline: add test packet generator
tx pipeline: add tx_command_packet for firmware
tx command packet: add dma to store control packet
rx pipeline: add reciever path
rx pipeline: add duplicate char decoder
rx pipeline: add trig ack checker
rx pipeline: add packet decoder
decoder: add test packet checher
decoder: add packet DMA
2025-01-22 13:41:19 +08:00
8c34921799 cxp upconn gw: add low speed serial PHY
testing: add debug fifo output b4 encoder
cxp upconn: add low speed serial
cxp upconn: add reset, tx_busy, tx_enable
cxp upconn: add clockgen module for 20.83Mbps & 41.66Mbps using counters
cxp upconn: add oserdes using CEInserter
2025-01-22 13:41:19 +08:00
f158aaf975 cxp downconn gw: add gtx up to 12.5Gbps
testing: add txusrclk mmcm & loopback mode
testing: add debug output
testing: send comma in the middle of long packet to maintain lock
downconn: don't put IDLE into fifo
downconn: add GTX and QPLL support
downconn: add DRP for GTX and QPLL to support all CXP linerates
GTX: add gtx with mmcm for TXUSRCLK freq requirement
GTX: add loopback mode parameter for testing
GTX: add gtx with 40bits internal width
GTX: use built-in comma aligner
GTX: add comma checker to ensure comma is aligner on highest linerate
GTX: set QPLL as CLK source for GTX
GTX: add multilane rx support with the same rx reseter
2025-01-22 13:41:19 +08:00
f93ab3a02a fmc: add cxp_4r_fmc adepter io 2025-01-22 13:41:19 +08:00
3748bff2e1 temp: flake,libunwind,scripts,notes,diagram
flake: download llvm11 binary instead of compiling
local_run: preset cxp zc706 dev board ip addr
flake: add pillow for sim
libunwind build: suppress libunwind warning
2025-01-22 13:41:19 +08:00
3070c00238 flake: add CXP_DEMO variant build options 2024-12-04 16:08:15 +08:00
e1f493f3ca drtio: add InjectionRequest to expects_response 2024-11-26 14:58:24 +08:00
1f5ea41934 flake: update dependencies 2024-11-20 19:58:55 +08:00
7f83d56ef5 cargo fmt 2024-11-20 09:42:49 +08:00
1d431456f4 Fix DWARF parser treating catch blocks as unconditional
Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2024-11-20 09:32:38 +08:00
b03e380c1e flake: update dependencies 2024-11-20 09:07:00 +08:00
47fc53c4bf drtio_tuple -> drtio_context 2024-11-18 13:13:10 +08:00
42eaecf9e1 remove debug message 2024-11-18 12:19:37 +08:00
beb7e6f994 cargo fmt 2024-11-18 12:19:37 +08:00
4502a47aa6 drtio_proto: add allocate step for flashing
This avoids reallocation while transfering binaries.
2024-11-18 12:19:37 +08:00
ac6b7d5cf0 satman: fix checksum error message 2024-11-18 12:19:37 +08:00
3019bc6123 runtime: check crc when flashing 2024-11-18 12:19:37 +08:00
95b8562812 cargo fmt 2024-11-18 12:19:37 +08:00
a13f5d02fa mgmt: supplementary tuple -> tuple struct 2024-11-18 12:19:37 +08:00
e52aa77068 cargo fmt 2024-11-18 12:19:37 +08:00
8e28d12ad0 runtime mgmt: avoid pull_log resource hog 2024-11-18 12:19:37 +08:00
47cddae04f runtime mgmt: avoid passing incomplete log to core_log 2024-11-18 12:19:37 +08:00
27a65df40e satman mgmt: fix uart log level change message 2024-11-18 12:19:37 +08:00
759cca3bfd satman mgmt: allow sliceable to consume log source 2024-11-18 12:19:37 +08:00
aadb6fc22d satman mgmt: get logger unconditionally 2024-11-18 12:19:37 +08:00
ae4d5a4228 mgmt: minor fix 2024-11-18 12:19:37 +08:00
6f1d727ca2 drtio-proto: avoid expecting response to drop link ack 2024-11-18 12:19:37 +08:00
7da5061f7e coremgmt: fix import/uses 2024-11-18 12:19:37 +08:00
47d418c69e coremgmt: remove unnecsaary cursors 2024-11-18 12:19:37 +08:00
d2979e8894 runtime coremgmt: implement firmware rewrite 2024-11-18 12:19:37 +08:00
3884c14a19 satman coremgmt: code after reboot is unreachable 2024-11-18 12:19:37 +08:00
c5b00d8e4e cargo fmt 2024-11-18 12:19:37 +08:00
2985875f9a satman: implement boot file rewrite sequence 2024-11-18 12:19:37 +08:00
5cb565a7e0 coremgr: current_payload -> config_payload 2024-11-18 12:19:37 +08:00
59954829a2 drtio-proto: (N)ACK -> Reply { succeeded } 2024-11-18 12:19:37 +08:00
960864c847 drtio-proto: add coremgmt-over-drtio messages 2024-11-18 12:19:37 +08:00
bdc29e5709 runtime: support coremgmt on satellites 2024-11-18 12:19:37 +08:00
332732dc44 satman: implement cfg/mgmt operations 2024-11-18 12:19:37 +08:00
244c7396d9 runtime: handle drtio-eem satellite disconnection 2024-11-18 12:08:44 +08:00
2c633409b8 Set FCLK0 for EBAZ4205
EBAZ4205 uses FCLK0 as the RTIO clock.

If the user modifies the gateware to use an external clock, FCLK0 is not used.
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-11-17 10:08:43 +08:00
9774b39fd8 flake: update zynq-rs 2024-11-16 17:32:05 +08:00
9054e4a7cb flake: update zynq-rs, switch to oxalica rust overlay 2024-11-16 17:22:01 +08:00
d79bf8d54a gateware: Add default TTLs to EBAZ4205 (#335)
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-11-16 10:40:45 +08:00
75e7fc55a3 flake: update dependencies 2024-11-16 10:39:39 +08:00
24a4d79f0f README: general update 2024-11-07 19:07:38 +01:00
9ce3aadb15 cargo fmt 2024-10-18 17:43:39 +08:00
3390abd5a1 subkernels: pass now_mu when calling subkernels 2024-10-18 13:51:48 +08:00
a410c40b50 ADD SPI to EBAZ4205 for AD9834 (#331)
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-10-17 15:06:11 +08:00
030247be18 add pre-commit hooks for code formatting
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-10-08 15:19:07 +08:00
61df939c87 ebaz4205: add variant and hydra job
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-10-08 11:35:31 +08:00
aba97175c6 Fix formatting 2024-10-05 16:30:45 -07:00
81790257a5 Add ebaz4205 support (#327)
Co-authored-by: newell <newell.jensen@gmail.com>
Co-committed-by: newell <newell.jensen@gmail.com>
2024-10-05 15:05:49 +08:00
1f81d038e0 update dependencies 2024-10-05 14:50:13 +08:00
1e42228aac flake: remove deprecated pytest-runner 2024-09-30 16:19:56 +08:00
c84653b500 flake: update dependencies 2024-09-30 16:01:11 +08:00
6585b9b441 flake: update dependencies 2024-09-30 14:17:25 +08:00
873dd86b4d runtime: cargo fmt (NFC) 2024-09-19 10:23:31 +08:00
e7614d2e8e rerun idle kernel on finish 2024-09-13 09:35:38 +08:00
491e426222 run idle kernel on flash 2024-09-12 16:12:57 +08:00
ccd3bf3003 runtime: fix drtio inject lock 2024-09-02 17:19:20 +08:00
3fdb7e80a8 flake: update dependencies 2024-08-23 19:14:08 +08:00
bd1de933fb cargo fmt 2024-08-23 17:49:14 +08:00
e8d77fca3e firmware: add UnwrapNoneError exception 2024-08-23 16:50:47 +08:00
85e8a3fc44 firmware: add LinAlgError exception 2024-08-22 10:42:28 +08:00
04078b3d89 flake: update dependencies 2024-08-21 18:51:19 +08:00
d508c5c6f8 firmware: add unit tests for exception sync 2024-08-21 16:35:03 +08:00
bae41253e4 firmware: sync exception names and ids 2024-08-21 16:34:25 +08:00
20181e9915 fix nalgebra url 2024-08-07 13:49:03 +08:00
a835149619 kernel/linalg: remove redundant unsafe blocks 2024-08-07 13:48:21 +08:00
78d6b7ddcf cargo fmt 2024-08-05 19:37:55 +08:00
fad1db9796 comms: remove idle kernel DRTIO error case 2024-08-05 19:28:09 +08:00
fee30033ec comms: run idle kernel on start-up 2024-08-05 19:28:09 +08:00
fe6f259d48 kernel: add linalg functions 2024-08-01 18:20:32 +08:00
e4d7ce114f flake: update fastnumbers 2024-08-01 07:41:32 +08:00
63f4783687 subkernels: support exceptions from subkernels 2024-07-31 17:22:29 +08:00
69a0b1bfb7 subkernels: raise exceptions to kernel 2024-07-31 17:22:29 +08:00
f6bff80105 flake: update dependencies 2024-07-31 17:20:54 +08:00
57fd327ecb rustfmt 2024-07-22 18:55:17 +08:00
69d5b11ebf kernel/api: add nalgebra::linalg methods 2024-07-22 11:57:58 +08:00
bab938c563 add nalgebra dependency
Co-authored-by: abdul124 <ar@m-labs.hk>
Co-committed-by: abdul124 <ar@m-labs.hk>
2024-07-22 11:13:45 +08:00
d51e5e60c3 repeater: handle async messages 2024-07-09 23:04:34 +08:00
23857eef63 allow toggling SED spread with flash config key 2024-07-09 18:11:20 +08:00
d0615bf965 flake: update dependencies 2024-07-09 10:37:37 +02:00
3a789889cf kernel/api: add rint api 2024-07-05 14:53:09 +08:00
72b814f7fd repeater: clear buffer after ping 2024-07-04 17:30:04 +08:00
ead20a66a5 flake: update dependencies 2024-06-06 10:05:55 +08:00
77 changed files with 11172 additions and 454 deletions

8
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,8 @@
repos:
- repo: local
hooks:
- id: cargo_format
name: cargo_format
entry: cargofmt.sh
language: script
pass_filenames: false

View File

@ -4,60 +4,102 @@ ARTIQ on Zynq
How to use
----------
1. Install the ARTIQ version that corresponds to the artiq-zynq version you are targeting.
2. To obtain firmware binaries, select the latest successful build on [Hydra](https://nixbld.m-labs.hk/) for the targeted artiq-zynq version, or use AFWS. If using Hydra, search for the job named ``<board>-<variant>-sd`` (for example: ``zc706-nist_clock-sd`` or ``zc706-nist_qc2-sd``).
3. Place the ``boot.bin`` file, obtained from Hydra's "binary distribution" download link or from AFWS, at the root of a FAT-formatted SD card.
4. Optionally, create a ``config.txt`` configuration file at the root of the SD card containing ``key=value`` pairs on each line. Use the ``ip``, ``ip6`` and ``mac`` keys to respectively set the IPv4, IPv6 and MAC address of the board. Configuring an IPv6 address is entirely optional. If these keys are not found, the firmware will use default values that may or may not be compatible with your network.
5. Insert the SD card into the board and set up the board to boot from the SD card. For the ZC706, this is achieved by placing the large DIP switch SW11 in the 00110 position.
6. Power up the board. After the firmware starts successfully, it should respond to ping at its IP addresses, and boot messages can be observed from its UART at 115200bps.
7. Create and use an ARTIQ device database as usual, but set ``"target": "cortexa9"`` in the arguments of the core device.
1. [Install ARTIQ](https://m-labs.hk/artiq/manual/installing.html). Get the corresponding version to the ``artiq-zynq`` version you are targeting.
2. To obtain firmware binaries, use AFWS or build your own; see [the ARTIQ manual](https://m-labs.hk/artiq/manual/building_developing.html) for detailed instructions or skip to "Development" below. ZC706 variants only can also be downloaded from latest successful build on [Hydra](https://nixbld.m-labs.hk/).
3. Place ``boot.bin`` file at the root ``/`` of a FAT-formatted SD card.
4. Optionally, create a ``config.txt`` configuration file containing ``key=value`` pairs on each line and place it at the root of the SD card. See below for valid keys. The ``ip``, ``ip6`` and ``mac`` keys can be used to set networking information. If these keys are not found, the firmware will use default values which may or may not be compatible with your network.
5. Insert the SD card into the board and set the board to boot from the SD card. For ZC706, this is achieved by placing the large DIP switch SW11 into the 00110 position. On Kasli-SoC, place the BOOT MODE switches to SD.
6. Power up the board. After successful boot the firmware should respond to ping at its IP addresses. Boot output can be observed from UART at 115200bps 8-N-1.
7. Create and use an ARTIQ device database as usual.
Configuration
-------------
Configuring the device is done using the ``config.txt`` text file at the root of the SD card, plus the contents of the ``config`` folder. When searching for a configuration key, the firmware first looks for a file named ``/config/[key].bin`` and, if it exists, returns the contents of that file. If not, it looks into ``/config.txt``, which contains a list of ``key=value`` pairs, one per line. The ``config`` folder allows configuration values that consist in binary data, such as the startup kernel.
Configuring the device is done using the ``config.txt`` text file at the root of the SD card plus optionally a ``config`` folder. When searching for a configuration key, the firmware first looks for a file named ``/config/[key].bin`` and, if it exists, returns the contents of that file. If not, it looks into ``/config.txt``, which should contain a list of ``key=value`` pairs, one per line. ``config.txt`` should be used for most keys but the ``config`` folder allows for setting configuration values which consist of binary data, such as the startup kernel.
The following configuration keys are available:
The following configuration keys are available among others:
- ``mac``: Ethernet MAC address.
- ``ip``: IPv4 address.
- ``ip6``: IPv6 address.
- ``startup``: startup kernel in ELF format (as produced by ``artiq_compile``).
- ``idle_kernel``: idle kernel in ELF format (as produced by ``artiq_compile``).
- ``startup_kernel``: startup kernel in ELF format (as produced by ``artiq_compile``).
- ``rtio_clock``: source of RTIO clock; valid values are ``ext0_bypass`` and ``int_125``.
- ``boot``: SD card "boot.bin" file, for replacing the boot firmware/gateware. Write only.
Configurations can be read/written/removed via ``artiq_coremgmt``. Config erase is
not implemented as it seems not very useful.
See [ARTIQ manual](https://m-labs.hk/artiq/manual-beta/core_device.html#configuration-storage) for full list. Configurations can be read/written/removed with ``artiq_coremgmt``. Config erase is not implemented, as it isn't particularly useful.
For convenience, the ``boot`` key can be used with ``artiq_coremgmt`` and a ``boot.bin`` file to replace firmware/gateware in a running system. This key is read-only. When loading ``boot.bin`` onto the SD card directly, place it at the root and not in the ``config`` folder.
Development instructions
------------------------
ARTIQ on Zynq is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.8+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
ARTIQ on Zynq is packaged using [Nix](https://nixos.org) Flakes. Install Nix 2.8+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
Pure build with Nix and execution on a remote JTAG server:
**Pure build with Nix:**
```shell
nix build .#zc706-nist_clock-jtag # or zc706-nist_qc2-jtag or zc706-nist_clock_satellite-jtag etc.
./remote_run.sh
nix build .#zc706-nist_clock-jtag # or zc706-nist_qc2-jtag or zc706-nist_clock-sd or etc
```
Impure incremental build and execution on a remote JTAG server:
Run ``nix flake show`` to see all valid build targets. Targets suffixed with ``-jtag`` produce separate firmware and gateware files, intended for use in booting via JTAG server/Ethernet, e.g. ``./remote_run.sh -i`` with a remote JTAG server. Targets suffixed with ``-sd`` will produce ``boot.bin`` file suitable for SD card boot. ``-firmware`` and ``-gateware`` respectively build firmware and gateware only.
The Kasli-SoC target requires a system description file as input. See ARTIQ manual for exact instructions or use incremental build.
**Impure incremental build:**
For boards with fixed variants, i.e. ZC706, etc. :
```shell
nix develop
cd src
gateware/zc706.py -g ../build/gateware -V <variant> # build gateware
make GWARGS="-V <variant>" <runtime/satman> # build firmware
cd ..
./remote_run.sh -i
gateware/<board>.py -g ../build/gateware -V <variant> # gateware
make GWARGS="-V <variant>" <runtime/satman> # firmware
```
For boards with system descriptions, i.e. Kasli-SoC, etc. :
```shell
nix develop
cd src
gateware/<board>.py -g ../build/gateware <description.json> # gateware
make TARGET=<board> GWARGS="path/to/description.json" <runtime/satman> # firmware
```
``szl.elf`` can be obtained with:
```shell
nix build git+https://git.m-labs.hk/m-labs/zynq-rs#<board>-szl
```
To generate ``boot.bin`` use ``mkbootimage``, e.g.:
```shell
echo "the_ROM_image:
{
[bootloader]result/szl.elf
gateware/top.bit
firmware/armv7-none-eabihf/release/<runtime/satman>
}
EOF" >> boot.bif
mkbootimage boot.bif boot.bin
```
Notes:
- The impure build process is also compatible with non-Nix systems.
- When calling make, you need to specify both the variant and firmware type.
- Firmware type must be either ``runtime`` for DRTIO-less or DRTIO master variants, or ``satman`` for DRTIO satellite.
- If the board is connected to the local machine, use the ``local_run.sh`` script.
- If the board is connected to the local machine by JTAG, use the ``local_run.sh`` script.
- A known Xilinx hardware bug prevents repeatedly loading the bootloader over JTAG without a POR reset. If booting over JTAG, install a jumper on ``PS_POR_B`` and use the POR reset script [here](https://git.m-labs.hk/M-Labs/zynq-rs/src/branch/master/kasli_soc_por.py).
Pre-Commit Hooks
----------------
You are strongly recommended to use the provided pre-commit hooks to automatically reformat files and check for non-optimal Rust/C/C++ practices. Run `pre-commit install` to install the hook and `pre-commit` will automatically run `cargo fmt`, `cargo clippy`, and `clang-format` for you.
Several things to note:
- If `cargo fmt`, `cargo clippy`, or `clang-format` returns an error, the pre-commit hook will fail. You should fix all errors before trying to commit again.
- If `cargo fmt` or `clang-format` reformats some files, the pre-commit hook will also fail. You should review the changes and, if satisfied, try to commit again.
License
-------

38
build_CI.sh Executable file
View File

@ -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

5
cargofmt.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
nix-shell -p gnumake --command 'make manifests -B'
cd src
cargo fmt -- --check

338
coaxpress.drawio Normal file
View File

@ -0,0 +1,338 @@
<mxfile host="65bd71144e">
<diagram id="en7HUHNV3kVsTTCxeEt8" name="Page-1">
<mxGraphModel dx="1155" dy="1481" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="7" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;rounded=0;" parent="1" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="320" y="280" as="targetPoint"/>
<mxPoint x="240" y="280.0000000000001" as="sourcePoint"/>
</mxGeometry>
</mxCell>
<mxCell id="109" value="32" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="7" vertex="1" connectable="0">
<mxGeometry x="-0.3138" y="2" relative="1" as="geometry">
<mxPoint x="12" y="-8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="14" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;rounded=0;dashed=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="390" y="600" as="sourcePoint"/>
<mxPoint x="240" y="599.76" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="15" value="CTRL/Trig" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=0;" parent="14" vertex="1" connectable="0">
<mxGeometry x="-0.375" y="4" relative="1" as="geometry">
<mxPoint x="-15" y="-14" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="21" value="TX&lt;br style=&quot;border-color: var(--border-color);&quot;&gt;Low speed" style="shape=offPageConnector;whiteSpace=wrap;html=1;rotation=0;size=0.3333333333333333;direction=south;fillColor=#DAE8FC;strokeColor=#6c8ebf;rounded=0;" parent="1" vertex="1">
<mxGeometry x="120" y="400" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="24" value="RX GTX&lt;br style=&quot;border-color: var(--border-color);&quot;&gt;High Speed&lt;br&gt;Master" style="shape=offPageConnector;whiteSpace=wrap;html=1;rotation=0;size=0.3333333333333333;direction=north;fillColor=#f8cecc;strokeColor=#b85450;rounded=0;" parent="1" vertex="1">
<mxGeometry x="120" y="240" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="25" value="TX High Speed&lt;br&gt;(optional)" style="shape=offPageConnector;whiteSpace=wrap;html=1;rotation=0;size=0.3333333333333333;direction=south;fillColor=#f8cecc;strokeColor=#b85450;dashed=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="120" y="560" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="26" value="RX GTX&lt;br style=&quot;border-color: var(--border-color);&quot;&gt;High Speed&amp;nbsp;&lt;br&gt;Extension&lt;br&gt;(Optional)" style="shape=offPageConnector;whiteSpace=wrap;html=1;rotation=0;size=0.4166666666666667;direction=north;fillColor=#f8cecc;strokeColor=#b85450;rounded=0;dashed=1;" parent="1" vertex="1">
<mxGeometry x="120" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="30" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;rounded=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="439.58" as="sourcePoint"/>
<mxPoint x="240" y="440" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="CTRL/Trig &lt;br&gt;DATA PACKET" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=0;" parent="30" vertex="1" connectable="0">
<mxGeometry x="-0.375" y="4" relative="1" as="geometry">
<mxPoint x="-30" y="-24" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="33" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;TX pipeline&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- priority transmission&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- IDLE&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;" parent="1" vertex="1">
<mxGeometry x="400" y="400" width="160" height="80" as="geometry"/>
</mxCell>
<mxCell id="38" value="Red: clocked by cxp_gtx_rx/tx (31.25MHz - 312.5MHz)&amp;nbsp;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;strokeColor=#b85450;fillColor=#f8cecc;align=left;" parent="1" vertex="1">
<mxGeometry x="120" y="680" width="360" height="40" as="geometry"/>
</mxCell>
<mxCell id="39" value="Blue: clocked by sys with CEInserter (20.83MHz - 41.66MHz)&amp;nbsp;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;strokeColor=#6c8ebf;fillColor=#dae8fc;align=left;" parent="1" vertex="1">
<mxGeometry x="120" y="720" width="360" height="40" as="geometry"/>
</mxCell>
<mxCell id="40" value="White: clocked by sys (125MHz)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;strokeColor=default;fontColor=#000000;fillColor=default;align=left;" parent="1" vertex="1">
<mxGeometry x="120" y="800" width="360" height="40" as="geometry"/>
</mxCell>
<mxCell id="43" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&lt;b&gt;&lt;u&gt;CXP Bootstrap FW&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- handle GTX speed&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- test connection&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- hand over to RTIO after init&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&#9;&lt;/span&gt;- cannot access CTRL PAK&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- with camera specific .rs file&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- compare heatbeat to check connection status&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- handle event ack??&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- don't use tag?&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="800" y="240" width="240" height="240" as="geometry"/>
</mxCell>
<mxCell id="53" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&lt;u&gt;&lt;b&gt;CXP Camera specific prog&lt;/b&gt;&lt;/u&gt;&lt;br&gt;GenICam interface @&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;RTIO coredevice&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- handle frame programming&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- handle event as well??&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- two interface&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&amp;nbsp; &amp;nbsp;- IO CTRL packet via API!&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&amp;nbsp; &amp;nbsp;- on master ch only&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;&amp;nbsp; &amp;nbsp;- O Frame data&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1120" y="80" width="160" height="480" as="geometry"/>
</mxCell>
<mxCell id="54" value="RX pipeline" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="400" width="160" height="80" as="geometry"/>
</mxCell>
<mxCell id="55" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;rounded=0;" parent="1" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="400" y="39.660000000000025" as="targetPoint"/>
<mxPoint x="240" y="39.660000000000025" as="sourcePoint"/>
</mxGeometry>
</mxCell>
<mxCell id="63" value="Green: clocked by rio / rio_phy" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;strokeColor=#82b366;fillColor=#d5e8d4;align=left;" parent="1" vertex="1">
<mxGeometry x="120" y="760" width="360" height="40" as="geometry"/>
</mxCell>
<mxCell id="64" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1040" y="279.65999999999997" as="sourcePoint"/>
<mxPoint x="1120" y="280.03999999999996" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="69" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="640" y="439.71" as="sourcePoint"/>
<mxPoint x="560" y="439.71" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="71" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;TX Bootstrap&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- testseq&lt;/span&gt;&lt;br&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- DMA&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;" parent="1" vertex="1">
<mxGeometry x="640" y="400" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="72" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="800" y="439.7100000000002" as="sourcePoint"/>
<mxPoint x="720" y="439.7100000000002" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="93" value="CTRL/Event&lt;br&gt;Packet (DMA)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="72" vertex="1" connectable="0">
<mxGeometry x="-0.4276" y="-1" relative="1" as="geometry">
<mxPoint x="-17" y="-19" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="77" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;RX Bootstrap&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- testseq&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- DMA&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="640" y="240" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="78" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="720" y="279.71" as="sourcePoint"/>
<mxPoint x="800" y="279.71" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="82" value="CTRL/Event&lt;br style=&quot;border-color: var(--border-color);&quot;&gt;Packet (DMA)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="78" vertex="1" connectable="0">
<mxGeometry x="-0.4143" y="2" relative="1" as="geometry">
<mxPoint x="17" y="-18" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="85" value="32+8" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry y="10" width="100" relative="1" as="geometry">
<mxPoint x="560" y="279.71000000000004" as="sourcePoint"/>
<mxPoint x="640" y="279.71000000000004" as="targetPoint"/>
<Array as="points"/>
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="94" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="77" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="720" y="119.66000000000003" as="sourcePoint"/>
<mxPoint x="800" y="119.66000000000003" as="targetPoint"/>
<Array as="points">
<mxPoint x="680" y="120"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="97" value="4x Frame data" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="94" vertex="1" connectable="0">
<mxGeometry x="0.5972" y="-1" relative="1" as="geometry">
<mxPoint x="-1" y="-11" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="95" value="CTRL Packet" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry y="-10" width="100" relative="1" as="geometry">
<mxPoint x="1120" y="439.66000000000025" as="sourcePoint"/>
<mxPoint x="1040" y="439.66000000000025" as="targetPoint"/>
<Array as="points"/>
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="96" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1040" y="119.57999999999984" as="sourcePoint"/>
<mxPoint x="1120" y="119.95999999999995" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="98" value="&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;RX Bootstrap&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- testseq&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- DMA&lt;/span&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="640" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="99" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="560" y="39.58000000000004" as="sourcePoint"/>
<mxPoint x="640" y="39.58000000000004" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="100" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" target="33" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1120" y="520.0000000000003" as="sourcePoint"/>
<mxPoint x="1040" y="520.0000000000003" as="targetPoint"/>
<Array as="points">
<mxPoint x="480" y="520"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="101" value="Trigger" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="100" vertex="1" connectable="0">
<mxGeometry x="0.7255" y="-2" relative="1" as="geometry">
<mxPoint x="-53" y="-18" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="103" value="RX EC" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="320" y="240" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="104" value="RX pipeline" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="480" y="240" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="105" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;rounded=0;" parent="1" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="480" y="310" as="targetPoint"/>
<mxPoint x="400" y="310" as="sourcePoint"/>
</mxGeometry>
</mxCell>
<mxCell id="107" value="32" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="105" vertex="1" connectable="0">
<mxGeometry x="-0.2448" y="-2" relative="1" as="geometry">
<mxPoint x="10" y="-12" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="106" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;rounded=0;" parent="1" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="480" y="250" as="targetPoint"/>
<mxPoint x="400" y="250" as="sourcePoint"/>
</mxGeometry>
</mxCell>
<mxCell id="108" value="8 dchar" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="106" vertex="1" connectable="0">
<mxGeometry x="-0.5034" y="1" relative="1" as="geometry">
<mxPoint x="20" y="-9" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="180" value="Streams&lt;br&gt;Crossbar" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="760" y="-480" width="80" height="320" as="geometry"/>
</mxCell>
<mxCell id="181" value="ROI Engine" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1560" y="-360" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="182" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1480" y="-320.0000000000001" as="sourcePoint"/>
<mxPoint x="1560" y="-319.62" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="185" value="&lt;div style=&quot;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- 32bit pixel data&lt;/span&gt;&lt;/div&gt;- frame valid (new frame)&lt;br&gt;- line break" style="text;html=1;align=left;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="1520" y="-430" width="160" height="60" as="geometry"/>
</mxCell>
<mxCell id="186" value="CRC Checker" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="920" y="-360.42" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="187" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1000" y="-321.25000000000017" as="sourcePoint"/>
<mxPoint x="1080" y="-320.87000000000006" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="188" value="Double&lt;br&gt;Buffered&lt;br&gt;Memory" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="1080" y="-360.42" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="194" value="Pixel Decoder" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad9d5;strokeColor=#ae4132;" parent="1" vertex="1">
<mxGeometry x="1400" y="-360" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="195" value="&lt;div style=&quot;&quot;&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;- extract line break &amp;amp; new frame&lt;/span&gt;&lt;/div&gt;" style="text;html=1;align=left;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
<mxGeometry x="1400" y="-270" width="190" height="30" as="geometry"/>
</mxCell>
<mxCell id="203" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1160" y="-319.9999999999999" as="sourcePoint"/>
<mxPoint x="1240" y="-319.6199999999998" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="205" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="840" y="-320.4200000000002" as="sourcePoint"/>
<mxPoint x="920" y="-320.0400000000001" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="206" value="CRC Checker" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;dashed=1;" parent="1" vertex="1">
<mxGeometry x="920" y="-480" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="207" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1000" y="-440.83000000000027" as="sourcePoint"/>
<mxPoint x="1080" y="-440.45000000000016" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="208" value="Double&lt;br&gt;Buffered&lt;br&gt;Memory" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;dashed=1;" parent="1" vertex="1">
<mxGeometry x="1080" y="-480" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="209" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="680" y="-440.4200000000003" as="sourcePoint"/>
<mxPoint x="760" y="-440.0400000000002" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="210" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="680" y="-280.00000000000017" as="sourcePoint"/>
<mxPoint x="760" y="-279.62000000000006" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="211" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="680" y="-360.00000000000017" as="sourcePoint"/>
<mxPoint x="760" y="-359.62000000000006" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="212" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="680" y="-200.49000000000018" as="sourcePoint"/>
<mxPoint x="760" y="-200.11000000000007" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="213" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="840" y="-440.42000000000036" as="sourcePoint"/>
<mxPoint x="920" y="-440.04000000000025" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="214" value="Stream&lt;br&gt;Parser" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="1240" y="-360.00000000000006" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="215" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="1320" y="-320.41999999999996" as="sourcePoint"/>
<mxPoint x="1400" y="-320.03999999999985" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="216" value="Streams&lt;br&gt;Crossbar" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="800" y="-40" width="80" height="200" as="geometry"/>
</mxCell>
<mxCell id="217" value="Stream pipeline #1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="960" y="80" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="218" value="" style="edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1">
<mxGeometry width="100" relative="1" as="geometry">
<mxPoint x="880" y="119.57999999999993" as="sourcePoint"/>
<mxPoint x="960" y="119.96000000000004" as="targetPoint"/>
<Array as="points"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

364
cxp_kernel.py Normal file
View File

@ -0,0 +1,364 @@
"""
Non-realtime drivers for CXP.
"""
# TODO: add api calls for CTRL packet similar i2c
# TODO: add timing critical trigger ack
from artiq.language.core import syscall, kernel
from artiq.language.types import TBool, TInt32, TNone
from artiq.coredevice.rtio import rtio_output,rtio_input_timestamped_data
from artiq.experiment import *
import numpy as np
import math
# TODO: change this to read bytes and accept TBytearray
@syscall(flags={"nounwind", "nowrite"})
def cxp_read_words(addr: TInt32, val: TList(TInt32), with_tag: TBool) -> TInt32:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_readu32(addr: TInt32, with_tag: TBool) -> TInt32:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_readu64(addr: TInt32, with_tag: TBool) -> TInt64:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_writeu32(addr: TInt32, val: TInt32, with_tag: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_writeu64(addr: TInt32, val: TInt64, with_tag: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_setup() -> TBool:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_debug_frame_print() -> TNone:
raise NotImplementedError("syscall not simulated")
# Bootstrap register
_XML_MANIFEST_SIZE = 0x0008
_XML_MANIFEST_SEL = 0x000C
_XML_VER = 0x0010
_XML_SCHEMA_VER = 0x0014
_XML_URL_ADDR = 0x0018
_WIDTH_ADDR = 0x3000
_HEIGHT_ADDR = 0x3004
_ACQUISITION_MODE_ADDR = 0x3008
_ACQUISITION_START_ADDR = 0x300C
_ACQUISITION_STOP_ADDR = 0x3010
_PIXEL_FORMAT_ADDR = 0x3014
_DEVICE_TAP_GEOG_ADDR = 0x3018
_IMG_1_STREAMID_ADDR = 0x301C
_MAX_BYTE_SIZE = 100 # in bytes
_MAX_WORD_SIZE = _MAX_BYTE_SIZE // 4 # in bytes
class CoaXPress:
def __init__(self, channel_base, core_device="core", xml_url_len=_MAX_WORD_SIZE):
# __device_mgr is private
# self.core = dmgr.get(core_device)
# you can get the channel via `print(len(rtio_channels))` before calling
# `rtio_channels.append(rtio.Channel.from_phy(cxp_interface))`
self.channel_base = channel_base
# the first 8 bits is reserved for the rtlink.OInterface.addr not for channel no.
self.target_o = channel_base << 8
self.with_tag = False
self.xml_addr = 0
self.width_addr = 0
self.height_addr = 0
self.acq_mode_addr = 0
self.acq_start_addr = 0
self.acq_stop_addr = 0
self.pixel_fmt_addr = 0
self.device_tap_geog_addr = 0
self.img_1_streamid_addr = 0
self.xml_url = [0] * xml_url_len
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def trigger(self, linktrig, trigdelay):
rtio_output(self.channel_base << 8, linktrig | trigdelay << 1)
@kernel
def setup_roi(self, n, x0, y0, x1, y1):
# DEBUG:
# c = int64(self.core.ref_multiplier)
c = int64(8)
rtio_output(((self.channel_base + 1) << 8) | (4*n+0), x0)
delay_mu(c)
rtio_output(((self.channel_base + 1) << 8) | (4*n+1), y0)
delay_mu(c)
rtio_output(((self.channel_base + 1) << 8) | (4*n+2), x1)
delay_mu(c)
rtio_output(((self.channel_base + 1) << 8) | (4*n+3), y1)
delay_mu(c)
# TODO: add gate
@kernel
def input_mu(self, data, tt, timeout_mu=-1):
assert len(data) == len(tt)
channel = self.channel_base + 2
for i in range(len(data)):
timestamp, roi_output = rtio_input_timestamped_data(timeout_mu, channel)
data[i] = roi_output
tt[i] = timestamp
@kernel
def init(self):
self.with_tag = cxp_setup()
self.xml_addr = self.read_u32(_XML_URL_ADDR)
# self.width_addr = self.read_u32(_WIDTH_ADDR)
# self.height_addr = self.read_u32(_HEIGHT_ADDR)
self.acq_mode_addr = self.read_u32(_ACQUISITION_MODE_ADDR)
self.acq_start_addr = self.read_u32(_ACQUISITION_START_ADDR)
self.acq_stop_addr = self.read_u32(_ACQUISITION_STOP_ADDR)
self.pixel_fmt_addr = self.read_u32(_PIXEL_FORMAT_ADDR)
# self.device_tap_geog_addr = self.read_u32(_DEVICE_TAP_GEOG_ADDR)
self.img_1_streamid_addr = self.read_u32(_IMG_1_STREAMID_ADDR)
self.read_words(self.xml_addr, self.xml_url)
@kernel
def read_u32(self, addr: TInt32) -> TInt32:
return cxp_readu32(addr, self.with_tag)
@kernel
def read_u64(self, addr: TInt32) -> TInt64:
return cxp_readu64(addr, self.with_tag)
@kernel
def read_words(self, addr: TInt32, val: TList(TInt32)):
cxp_read_words(addr, val, self.with_tag)
@kernel
def write_u32(self, addr: TInt32, val: TInt32):
cxp_writeu32(addr, val, self.with_tag)
@kernel
def write_u64(self, addr: TInt32, val: TInt64):
cxp_writeu64(addr, val, self.with_tag)
@kernel
def write_wide(self, addr: TInt32, vals: TList(TInt32)):
for i in range(len(vals)):
cxp_writeu32(addr + i * 4, vals[i], self.with_tag)
@kernel
def read_width(self) -> TInt32:
return self.read_u32(self.width_addr)
@kernel
def read_height(self) -> TInt32:
return self.read_u32(self.height_addr)
@kernel
def read_acq_mode(self) -> TInt64:
return self.read_u64(self.acq_mode_addr)
@kernel
def write_acq_mode(self, val: TInt64):
self.write_u64(self.acq_mode_addr, val)
@kernel
def start(self):
self.write_u32(self.acq_start_addr, 0x00000001)
@kernel
def stop(self):
self.write_u32(self.acq_stop_addr, 0x00000001)
@kernel
def get_frameid(self) -> TInt32:
return self.read_u32(self.img_1_streamid_addr)
@kernel
def get_pixel_format(self) -> TInt32:
return self.read_u32(self.pixel_fmt_addr)
@host_only
def print_xml_url(self):
url = ""
for x in self.xml_url:
url += x.to_bytes(4, byteorder="big").decode("ascii")
print(f"url = {url}")
if "Local:" in url:
file_name, start_addr, size = url.split(";", 2)
print(
f"file name: {file_name.replace("Local:", "")}, start addr: 0x{start_addr}, size; 0x{size.split("?", 1)[0]} bytes"
)
@kernel
def get_xml_data(self, xml_start_addr, xml_data):
i = -1
addr_offset = 0
for i in range(len(xml_data) // _MAX_WORD_SIZE):
buf = [0] * _MAX_WORD_SIZE
self.read_words(xml_start_addr + addr_offset, buf)
for j in range(len(buf)):
xml_data[j+i*_MAX_WORD_SIZE] = buf[j]
addr_offset += _MAX_WORD_SIZE * 4
buf = [0]*(len(xml_data) % _MAX_WORD_SIZE)
self.read_words(xml_start_addr + addr_offset, buf)
for j in range(len(buf)):
xml_data[j+(i+1)*_MAX_WORD_SIZE] = buf[j]
@host_only
def write_xml_data(self, xml_data, file_path):
byte_arr = bytearray()
for d in xml_data:
byte_arr += d.to_bytes(4, "big", signed=True)
with open(file_path, "wb") as binary_file:
binary_file.write(byte_arr)
# From the camera XML files
_USER_SET_SELECTOR = 0x10000050
_REAL_ACQ_MODE = 0x10000bb4
_REAL_ACQ_START = 0x10000498
_REAL_ACQ_STOP = 0x100004a4
_REAL_ACQ_ABORT = 0x100004b0 #stop all acq immediately
_BSL_SENSOR_STAND_BY = 0x100004c8 # put sensor in standby mode, certain parameter can only be change during standby
_BSL_POWER_MODE = 0x100000b4
# strange d_470 -> d_469 (= 3) perhaps a obscure security trick? So I guess looking @ Index"3" is fine?
_TRIG_MODE_INDEX_3 = 0x10001424 # d_87
_TRIG_SRC_INDEX_3 = 0x100081ac # d_479
_TRIG_ACT_INDEX_3 = 0x1000293c # d_502
_TRIG_SOFTWARE_INDEX_3 = 0x10000c34 # d_525
CXP_TRIG = True
class IdleKernel(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("led0")
# declare the class before using it in kernel
self.cxp = CoaXPress(0x0)
# self.vals = [0]*0x11ab3
xml_word_size = math.ceil(0x11ab3/4)
self.vals = [0] * xml_word_size
self.cnt = [0]
self.timestamp = [np.int64(0)]
@kernel
def camera_init(self):
self.cxp.init()
@kernel
def camera_trigger_setup(self):
# self.cxp.init()
# DEBUG: get xml data
# self.cxp.get_xml_data(0xc0000000, self.vals)
# DEBUG: Try to setup trigger
# All address below is from the XML, what's the point of bootstrap anyway?
# NOTE: setting is persistent over ConnectionReset but NOT power cycle
# self.cxp.write_u32(_REAL_ACQ_MODE, 1) # single frame mode
# self.cxp.write_u32(_REAL_ACQ_START, 1) # single acq start
# self.cxp.write_u32(_REAL_ACQ_STOP, 1) # single acq end
# self.cxp.write_u32(_REAL_ACQ_ABORT, 1) # single acq ABORT
# boA2448-250cm is area scan camera:
# see https://docs.baslerweb.com/triggered-image-acquisition#hardware-and-software-triggering to setup triggering properly
# TRIGGER: setup
# self.cxp.write_u32(_TRIG_SELECTOR, 3) # FrameStart by default, boA xml b_469 don't have an address for some reason
self.cxp.write_u32(_TRIG_MODE_INDEX_3, 1) # ON
if CXP_TRIG:
self.cxp.write_u32(_TRIG_SRC_INDEX_3, 7) # CXPTrigger0
else:
self.cxp.write_u32(_TRIG_SRC_INDEX_3, 0) # use software trigger
self.cxp.write_u32(_TRIG_ACT_INDEX_3, 2) # trig on anyedge
cxp_debug_frame_print()
# TAKING PICTURE
self.cxp.write_u32(_REAL_ACQ_MODE, 1) # single frame mode
self.cxp.write_u32(_REAL_ACQ_START, 1) # single acq start
# STOP acq
# self.cxp.write_u32(_REAL_ACQ_STOP, 1) # single acq end
# self.cxp.write_u32(_REAL_ACQ_ABORT, 1) # single acq ABORT
# self.cxp.write_u32(_BSL_SENSOR_STAND_BY, 1)
return self.cxp.read_u32(_BSL_POWER_MODE)
@kernel
def camera_trigger(self):
# reset mu for rtio
self.core.reset()
self.core.break_realtime()
self.cxp.setup_roi(0, 1, 1, 10, 10)
delay_mu(1000000)
if CXP_TRIG:
self.cxp.trigger(0 ,0x00)
else:
self.cxp.write_u32(_TRIG_SOFTWARE_INDEX_3, 0) # software trigger via register write
delay_mu(100)
# self.cxp.write_u32(_REAL_ACQ_STOP, 1) # single acq end
# self.cxp.write_u32(_REAL_ACQ_ABORT, 1) # single acq ABORT
# self.cxp.write_u32(_BSL_SENSOR_STAND_BY, 1)
# NOTE: This may not print when using CXP hardware TRIG
# As the write_u32 trigger a packet printout that delays the CPU enough that the frame arrive
# But using hw trigger, the print is not necessory i.e. not enough time delay for the zc706 to receive the frame
# cxp_debug_frame_print()
self.cxp.input_mu(self.cnt, self.timestamp, 10000)
for _ in range(4):
cxp_debug_frame_print()
# reduce power draw & temperature
# overtemperature can cause unstable connection
self.cxp.write_u32(_BSL_SENSOR_STAND_BY, 1)
return self.cxp.read_u32(_BSL_POWER_MODE)
def print_hex(self, arr: TList(TInt32)):
print("[{}]".format(", ".join(hex(np.uint32(x)) for x in arr)))
def run(self):
self.camera_init()
print(f"power mode before trigger = {self.camera_trigger_setup()}")
print(f"power mode after trigger = {self.camera_trigger()}")
print(f"count = {np.uint32(self.cnt)} | timestamp = {self.timestamp}")
# self.cxp.print_xml_url()
# self.cxp.write_xml_data(self.vals, "genicam_16e13898.zip")

78
cxp_note.md Normal file
View File

@ -0,0 +1,78 @@
# 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
[x] GTX Multilane setup
- Camera boostrap
[x] get the CXP version
[x] test connection
[x] discovery other extension (links)
[x] set bitrate
- Camera frame pipeline
[x] CXP frame packet routing (maybe no need to routing non zero streaming id (we have ROI buildin anyways)?)
[x] CXP CRC32 detection
## TODO
[] remove ALL debug tools
[] flake.nix mod
[] local_run.sh mod
### Gateware
[x] rename word_dw to word_width
[x] Test out CXP trigger
[] add __init__.py for cxp??
[] Try to fix tight s/h time pins
[] refactor error_cnt
[] Heartbeat (is it useful?? lol)
[] rename circular buffer to slots
[] remove pmod/debug_sma in fns args
[] add enum for gtx mode (e.g. tx only, rx only, both)
[] use if tx_mode / rx_mode instead
[] Region of interest engine
[x] pixel gearbox
[] pixel parser (xy pos)
[] rtio to getting the frame
- O: trigger
- I: frame
[] add a packet parser module that mux the packet?
### Firmware (design with driver)
[] Camera linkdown detection
[] Camera auto linkup/linkdown using threads
[] API programming
[] add tag handling for api calls
- support line reset in kernel using syscall
[] add heartbeat checking
### Coredevice Driver
[] support simple camera programming interface (Not real time)
- basic i2c-like interface with read/write u32
[] add grabber like fns & docs
[] use camera test pattern black/white to verify roi https://docs.baslerweb.com/test-patterns
### PR
1. push the gtx init fix
2. push the cxp core to misoc
3. push the cxp rtio core to artiq-zynq & artiq

30
device_db.py Normal file
View File

@ -0,0 +1,30 @@
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {
"host": "192.168.1.14",
"ref_period": 1e-9,
"ref_multiplier": 8,
"target": "cortexa9"
}
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"led0": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 0x000001},
},
}

View File

@ -0,0 +1,78 @@
core_addr = "192.168.1.57"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"target": "cortexa9",
},
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr,
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} "
+ core_addr,
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} "
+ core_addr,
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache",
},
"core_dma": {"type": "local", "module": "artiq.coredevice.dma", "class": "CoreDMA"},
"led0": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 0},
},
"led1": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 1},
},
}
# TTLs starting at RTIO channel 2, ending at RTIO channel 15
for i in range(2, 16):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut",
"arguments": {"channel": i},
}
device_db.update(
spi0={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 16},
},
dds0={
"type": "local",
"module": "artiq.coredevice.ad9834",
"class": "AD9834",
"arguments": {"spi_device": "spi0"},
},
)

176
flake.lock generated
View File

@ -3,25 +3,25 @@
"artiq": {
"inputs": {
"artiq-comtools": "artiq-comtools",
"mozilla-overlay": "mozilla-overlay",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"sipyco": "sipyco",
"src-migen": "src-migen",
"src-misoc": "src-misoc",
"src-pythonparser": "src-pythonparser"
},
"locked": {
"lastModified": 1716972728,
"narHash": "sha256-88J+eckZamtwhcCQkPpKLu6R1hmgj5+C9n2U5i+sHUE=",
"lastModified": 1733297384,
"narHash": "sha256-sJbrxy86EDGibQE5YZaKk2/m9YftYeTkYHhZ3ylnRKc=",
"ref": "refs/heads/master",
"rev": "49e402780bebba437c6098047ab1dc68eaf5a17c",
"revCount": 8808,
"rev": "c780bb3742cdadc6d4dc1cb3012115a88cb84968",
"revCount": 9111,
"type": "git",
"url": "https://github.com/m-labs/artiq.git"
"url": "file:///home/morgan/dev/artiq-master"
},
"original": {
"type": "git",
"url": "https://github.com/m-labs/artiq.git"
"url": "file:///home/morgan/dev/artiq-master"
}
},
"artiq-comtools": {
@ -37,11 +37,11 @@
]
},
"locked": {
"lastModified": 1707216368,
"narHash": "sha256-ZXoqzG2QsVsybALLYXs473avXcyKSZNh2kIgcPo60XQ=",
"lastModified": 1720768567,
"narHash": "sha256-3VoK7o5MtHtbHLrc6Pv+eQWFtaz5Gd/YWyV5TD3c5Ss=",
"owner": "m-labs",
"repo": "artiq-comtools",
"rev": "e5d0204490bccc07ef9141b0d7c405ab01cb8273",
"rev": "f93570d8f2ed5a3cfb3e1c16ab00f2540551e994",
"type": "github"
},
"original": {
@ -55,11 +55,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@ -68,66 +68,34 @@
"type": "github"
}
},
"mozilla-overlay": {
"flake": false,
"locked": {
"lastModified": 1704373101,
"narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"mozilla-overlay_2": {
"flake": false,
"locked": {
"lastModified": 1704373101,
"narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"mozilla-overlay_3": {
"flake": false,
"locked": {
"lastModified": 1704373101,
"narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1716542732,
"narHash": "sha256-0Y9fRr0CUqWT4KgBITmaGwlnNIGMYuydu2L8iLTfHU4=",
"lastModified": 1731319897,
"narHash": "sha256-PbABj4tnbWFMfBp6OcUK5iGy1QY+/Z96ZcLpooIbuEI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d12251ef6e8e6a46e05689eeccd595bdbd3c9e60",
"rev": "dc460ec76cbff0e66e269457d7b728432263166c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_old": {
"locked": {
"lastModified": 1720535198,
"narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
@ -135,10 +103,54 @@
"root": {
"inputs": {
"artiq": "artiq",
"mozilla-overlay": "mozilla-overlay_2",
"nixpkgs_old": "nixpkgs_old",
"zynq-rs": "zynq-rs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"artiq",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719454714,
"narHash": "sha256-MojqG0lyUINkEk0b3kM2drsU5vyaF8DFZe/FAlZVOGs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d1c527659cf076ecc4b96a91c702d080b213801e",
"type": "github"
},
"original": {
"owner": "oxalica",
"ref": "snapshot/2024-08-01",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"nixpkgs": [
"zynq-rs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719454714,
"narHash": "sha256-MojqG0lyUINkEk0b3kM2drsU5vyaF8DFZe/FAlZVOGs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d1c527659cf076ecc4b96a91c702d080b213801e",
"type": "github"
},
"original": {
"owner": "oxalica",
"ref": "snapshot/2024-08-01",
"repo": "rust-overlay",
"type": "github"
}
},
"sipyco": {
"inputs": {
"nixpkgs": [
@ -147,11 +159,11 @@
]
},
"locked": {
"lastModified": 1708937641,
"narHash": "sha256-Hkb9VYFzFgkYxfbh4kYcDSn7DbMUYehoQDeTALrxo2Q=",
"lastModified": 1728371104,
"narHash": "sha256-PPnAyDedUQ7Og/Cby9x5OT9wMkNGTP8GS53V6N/dk4w=",
"owner": "m-labs",
"repo": "sipyco",
"rev": "4a28b311ce0069454b4e8fe1e6049db11b9f1296",
"rev": "094a6cd63ffa980ef63698920170e50dc9ba77fd",
"type": "github"
},
"original": {
@ -163,11 +175,11 @@
"src-migen": {
"flake": false,
"locked": {
"lastModified": 1715484909,
"narHash": "sha256-4DCHBUBfc/VA+7NW2Hr0+JP4NnKPru2uVJyZjCCk0Ws=",
"lastModified": 1727677091,
"narHash": "sha256-Zg3SQnTwMM/VkOGKogbPyuCC2NhLy8HB2SPEUWWNgCU=",
"owner": "m-labs",
"repo": "migen",
"rev": "4790bb577681a8c3a8d226bc196a4e5deb39e4df",
"rev": "c19ae9f8ae162ffe2d310a92bfce53ac2a821bc8",
"type": "github"
},
"original": {
@ -179,11 +191,11 @@
"src-misoc": {
"flake": false,
"locked": {
"lastModified": 1715647536,
"narHash": "sha256-q+USDcaKHABwW56Jzq8u94iGPWlyLXMyVt0j/Gyg+IE=",
"lastModified": 1729234629,
"narHash": "sha256-TLsTCXV5AC2xh+bS7EhBVBKqdqIU3eKrnlWcFF9LtAM=",
"ref": "refs/heads/master",
"rev": "fea9de558c730bc394a5936094ae95bb9d6fa726",
"revCount": 2455,
"rev": "6085a312bca26adeca6584e37d08c8ba2e1d6e38",
"revCount": 2460,
"submodules": true,
"type": "git",
"url": "https://github.com/m-labs/misoc.git"
@ -227,18 +239,18 @@
},
"zynq-rs": {
"inputs": {
"mozilla-overlay": "mozilla-overlay_3",
"nixpkgs": [
"artiq",
"nixpkgs"
]
],
"rust-overlay": "rust-overlay_2"
},
"locked": {
"lastModified": 1716519432,
"narHash": "sha256-vgKBJCQRPCutJ4n+FtJNczMZULWW7J3B8icf/PUothw=",
"lastModified": 1731749494,
"narHash": "sha256-WGigAhvVCGN5YZ1dHPyvoqAh47W1Gtph036O1aKFlLE=",
"ref": "refs/heads/master",
"rev": "46dc25b89e46b9043129d77e3c9348916748e325",
"revCount": 645,
"rev": "12975de2e110d7948bf47b768559f727d0abc8fc",
"revCount": 655,
"type": "git",
"url": "https://git.m-labs.hk/m-labs/zynq-rs"
},

View File

@ -1,28 +1,31 @@
{
description = "ARTIQ port to the Zynq-7000 platform";
inputs.artiq.url = git+https://github.com/m-labs/artiq.git;
inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
inputs.artiq.url = git+file:///home/morgan/dev/artiq-master;
inputs.zynq-rs.url = git+https://git.m-labs.hk/m-labs/zynq-rs;
inputs.zynq-rs.inputs.nixpkgs.follows = "artiq/nixpkgs";
outputs = { self, mozilla-overlay, zynq-rs, artiq }:
inputs.nixpkgs_old.url = "github:nixos/nixpkgs?ref=nixos-23.11";
outputs = { self, zynq-rs, artiq, nixpkgs_old }:
let
pkgs = import artiq.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
pkgs_old = import nixpkgs_old { system = "x86_64-linux";};
pkgs = import artiq.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import zynq-rs.inputs.rust-overlay) ]; };
zynqpkgs = zynq-rs.packages.x86_64-linux;
artiqpkgs = artiq.packages.x86_64-linux;
llvmPackages_11 = zynq-rs.llvmPackages_11;
# llvmPackages_11 = zynq-rs.llvmPackages_11;
llvmPackages_11 = pkgs_old.llvmPackages_11;
rust = zynq-rs.rust;
rustPlatform = zynq-rs.rustPlatform;
fastnumbers = pkgs.python3Packages.buildPythonPackage rec {
pname = "fastnumbers";
version = "2.2.1";
version = "5.1.0";
src = pkgs.python3Packages.fetchPypi {
inherit pname version;
sha256 = "0j15i54p7nri6hkzn1wal9pxri4pgql01wgjccig6ar0v5jjbvsy";
sha256 = "sha256-4JLTP4uVwxcaL7NOV57+DFSwKQ3X+W/6onYkN2AdkKc=";
};
};
@ -75,7 +78,7 @@
propagatedBuildInputs = with pkgs.python3Packages; [ setuptools click numpy toolz jinja2 ramda artiqpkgs.migen artiqpkgs.misoc ];
checkInputs = with pkgs.python3Packages; [ pytest-runner pytestCheckHook pytest-timeout ];
checkInputs = with pkgs.python3Packages; [ pytestCheckHook pytest-timeout ];
# migen/misoc version checks are broken with pyproject for some reason
postPatch = ''
@ -126,6 +129,7 @@
lockFile = src/Cargo.lock;
outputHashes = {
"tar-no-std-0.1.8" = "sha256-xm17108v4smXOqxdLvHl9CxTCJslmeogjm4Y87IXFuM=";
"nalgebra-0.32.6" = "sha256-L/YudkVOtfGYoNQKBD7LMk/sMYgRDzPDdpGL5rO7G2I=";
};
};
@ -341,6 +345,7 @@
{
inherit fastnumbers artiq-netboot ramda migen-axi binutils-arm;
} //
(board-package-set { target = "zc706"; variant = "cxp_demo"; }) //
(board-package-set { target = "zc706"; variant = "nist_clock"; }) //
(board-package-set { target = "zc706"; variant = "nist_clock_master"; }) //
(board-package-set { target = "zc706"; variant = "nist_clock_master_100mhz"; }) //
@ -363,7 +368,8 @@
(board-package-set { target = "zc706"; variant = "acpki_nist_qc2_satellite_100mhz"; }) //
(board-package-set { target = "kasli_soc"; variant = "demo"; json = ./demo.json; }) //
(board-package-set { target = "kasli_soc"; variant = "master"; json = ./kasli-soc-master.json; }) //
(board-package-set { target = "kasli_soc"; variant = "satellite"; json = ./kasli-soc-satellite.json; });
(board-package-set { target = "kasli_soc"; variant = "satellite"; json = ./kasli-soc-satellite.json; }) //
(board-package-set { target = "ebaz4205"; variant = "base"; });
hydraJobs = packages.x86_64-linux // { inherit zc706-hitl-tests; inherit gateware-sim; inherit fmt-check; };
@ -379,10 +385,11 @@
zynqpkgs.mkbootimage
openocd
openssh rsync
(python3.withPackages(ps: (with artiqpkgs; [ migen migen-axi misoc artiq artiq-netboot ps.jsonschema ps.pyftdi ])))
(python3.withPackages(ps: (with artiqpkgs; [ migen migen-axi misoc artiq artiq-netboot ps.jsonschema ps.pyftdi ps.pillow ])))
artiqpkgs.artiq
artiqpkgs.vivado
binutils-arm
pre-commit
];
XARGO_RUST_SRC = "${rust}/lib/rustlib/src/rust/library";
CLANG_EXTRA_INCLUDE_DIR = "${llvmPackages_11.clang-unwrapped.lib}/lib/clang/11.1.0/include";

View File

@ -13,7 +13,7 @@ fi
impure=0
load_bitstream=1
board_type="kasli_soc"
board_type="zc706"
fw_type="runtime"
while getopts "ilb:t:f:" opt; do
@ -36,7 +36,7 @@ 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";;
zc706) board_host="192.168.1.14";;
*) echo "Unknown board type"; exit 1;;
esac
fi
@ -58,4 +58,4 @@ else
load_bitstream_cmd="-g $result_dir/top.bit"
fi
artiq_netboot $load_bitstream_cmd -f $result_dir/$fw_type.bin -b $board_host
fi
fi

82
notes.drawio Normal file
View File

@ -0,0 +1,82 @@
<mxfile host="65bd71144e">
<diagram id="en7HUHNV3kVsTTCxeEt8" name="Page-1">
<mxGraphModel dx="924" dy="545" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="50" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="200" as="sourcePoint"/>
<mxPoint x="440" y="200" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="52" value="Payload (layout)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="50">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="-63" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="53" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="239.81" as="sourcePoint"/>
<mxPoint x="440" y="239.81" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="54" value="stb (valid/readable)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="53">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="-63" y="-1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="55" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="440" y="279.80999999999995" as="sourcePoint"/>
<mxPoint x="280" y="279.80999999999995" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="56" value="ack (ready)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="55">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="-197" y="1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="48" value="Source" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="320" y="160" width="80" height="160" as="geometry"/>
</mxCell>
<mxCell id="57" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="520" y="200" as="sourcePoint"/>
<mxPoint x="680" y="200" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="58" value="Payload (layout)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="57">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="197" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="59" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="520" y="239.80999999999995" as="sourcePoint"/>
<mxPoint x="680" y="239.80999999999995" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="60" value="stb (valid)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="59">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="197" y="-1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="61" value="" style="shape=flexArrow;endArrow=classic;html=1;fillColor=#647687;strokeColor=#314354;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="680" y="279.99999999999994" as="sourcePoint"/>
<mxPoint x="520" y="279.99999999999994" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="62" value="ack (ready/writeable)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="61">
<mxGeometry x="-0.8404" y="-1" relative="1" as="geometry">
<mxPoint x="73" y="1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="49" value="Sink" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="560" y="160" width="80" height="160" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

17
reset.py Normal file
View File

@ -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()

63
sat_run.sh Executable file
View File

@ -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

55
sim.py Normal file
View File

@ -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())

149
sim_arbiter.py Normal file
View File

@ -0,0 +1,149 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from sim_generator import CXPCRC32Inserter
from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
CXP_CHANNELS = 2
class Frame_Pipeline(Module):
def __init__(self, n_downconn):
# to construct correct crc and ack/stb signal
self.submodules.arbiter = arbiter = Stream_Arbiter(n_downconn)
self.submodules.broadcaster = broadcaster = Stream_Broadcaster(1)
self.submodules.buffer = buffer = Buffer(word_layout_dchar)
self.sinks = []
for i in range(n_downconn):
# generating the packet
dchar_decoder = Duplicated_Char_Decoder()
eop_marker = EOP_Marker()
pipeline = [dchar_decoder, eop_marker]
self.submodules += pipeline
for s, d in zip(pipeline, pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.sinks.append(pipeline[0].sink)
self.comb += pipeline[-1].source.connect(arbiter.sinks[i])
# self.comb += arbiter.source.ack.eq(1)
self.comb += [
arbiter.source.connect(broadcaster.sink),
broadcaster.sources[0].connect(buffer.sink),
]
self.comb += buffer.source.ack.eq(1)
dut = Frame_Pipeline(CXP_CHANNELS)
def packet_sim(packets=[], active_ch=2):
assert active_ch <= CXP_CHANNELS
print("=================TEST========================")
# yield dut.arbiter.active_channels.eq((2**active_ch) - 1)
yield dut.arbiter.active_channels.eq((2**active_ch) - 1)
sinks = dut.sinks
for p in packets:
for i in range(CXP_CHANNELS):
if p["sink_id"] == i:
yield sinks[p["sink_id"]].data.eq(p["data"])
yield sinks[p["sink_id"]].k.eq(p["k"])
yield sinks[p["sink_id"]].stb.eq(1)
if "eop" in p:
yield sinks[p["sink_id"]].eop.eq(1)
else:
yield sinks[p["sink_id"]].eop.eq(0)
else:
yield sinks[i].data.eq(0)
yield sinks[i].k.eq(0)
yield sinks[i].stb.eq(0)
yield sinks[i].eop.eq(0)
yield
# extra clk cycles
for _ in range(100):
for i in range(CXP_CHANNELS):
yield sinks[i].data.eq(0)
yield sinks[i].k.eq(0)
yield sinks[i].stb.eq(0)
yield sinks[i].eop.eq(0)
yield
assert True
def testbench(n_downconn, with_header=True, with_crc=True):
paks = []
stream_id = 0
pix_len = 10
for p in range(20):
n_conn = p % n_downconn
if with_header:
frame_pak_len = pix_len if with_crc else pix_len - 1
frame = [
{
"sink_id": n_conn,
"data": Replicate(C(stream_id, char_width), 4),
"k": Replicate(0, 4),
},
{
"sink_id": n_conn,
"data": Replicate(C(0, char_width), 4),
"k": Replicate(0, 4),
},
{
"sink_id": n_conn,
"data": Replicate(C(frame_pak_len, 2 * char_width)[8:], 4),
"k": Replicate(0, 4),
},
{
"sink_id": n_conn,
"data": Replicate(C(frame_pak_len, 2 * char_width)[:8], 4),
"k": Replicate(0, 4),
},
]
else:
frame = []
for n_pix in range(pix_len):
if n_pix < pix_len - 1:
frame.append(
{
"sink_id": n_conn,
# "data": C((n_conn + 1) << 28 | p << 24 | n_pix, word_width),
"data": C( p << 24 | n_pix, word_width),
"k": Replicate(0, 4),
}
)
else:
frame.append(
{
"sink_id": n_conn,
# "data": C((n_conn + 1) << 28 | p << 24 | n_pix, word_width),
"data": C( p << 24 | n_pix, word_width),
"k": Replicate(0, 4),
}
)
frame.append(
{
"sink_id": n_conn,
"data": Replicate(KCode["pak_end"], 4),
"k": Replicate(1, 4),
}
)
paks += frame
yield from packet_sim(paks)
run_simulation(dut, testbench(CXP_CHANNELS,with_crc=False), vcd_name="sim-cxp.vcd")

91
sim_broadcaster.py Normal file
View File

@ -0,0 +1,91 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from sim_generator import CXPCRC32Inserter
from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
class double_buffer_pipeline(Module):
def __init__(self):
fifo = stream.SyncFIFO(word_layout, 32)
dchar_decoder = Duplicated_Char_Decoder()
broadcaster = Stream_Broadcaster(1)
pipeline = [fifo, dchar_decoder, broadcaster]
self.submodules += pipeline
for s, d in zip(pipeline, pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.sink = pipeline[0].sink
self.submodules.buffer = buffer = Buffer(word_layout_dchar)
self.comb += broadcaster.sources[0].connect(buffer.sink)
self.source = buffer.source
# for sim, no backpressure
self.comb += self.source.ack.eq(1)
dut = double_buffer_pipeline()
def packet_sim(packets=[]):
print("=================TEST========================")
sink = dut.sink
cyc = len(packets)
pak = packets
for c in range(cyc):
yield sink.data.eq(pak[c]["data"])
yield sink.k.eq(pak[c]["k"])
yield sink.stb.eq(1)
if "eop" in pak[c]:
yield sink.eop.eq(1)
else:
yield sink.eop.eq(0)
yield
# extra clk cycles
for _ in range(cyc, cyc + 20):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield
assert True
def testbench():
paks = [
{"data": Replicate(C(0, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(0, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(4, 2 * char_width)[8:], 4), "k": Replicate(0, 4)},
{"data": Replicate(C(4, 2 * char_width)[:8], 4), "k": Replicate(0, 4)}, # CRC doesn't count
{"data": C(0x7C7C7C7C, word_width), "k": Replicate(1, 4)},
{"data": C(0x01010101, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0xF6ACEF6A, word_width), "k": Replicate(0, 4)},
{"data": Replicate(C(0, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(1, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(8, 2 * char_width)[8:], 4), "k": Replicate(0, 4)},
{"data": Replicate(C(8, 2 * char_width)[:8], 4), "k": Replicate(0, 4)}, # CRC doesn't count
{"data": C(0x19191919, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x09090909, word_width), "k": Replicate(0, 4)},
{"data": C(0x90909090, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x985EFDB2, word_width), "k": Replicate(0, 4)},
]
yield from packet_sim(paks)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

93
sim_buffer.py Normal file
View File

@ -0,0 +1,93 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
# from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
from types import SimpleNamespace
class DUT(Module):
def __init__(self):
# PHY
phy = SimpleNamespace()
phy.sink = stream.Endpoint(word_layout)
phy.source = stream.Endpoint(word_layout)
self.sync += [
phy.source.stb.eq(0),
If(~((phy.sink.data[:8] == 0xBC) & (phy.sink.k[0] == 1)),
phy.source.stb.eq(1),
phy.source.data.eq(phy.sink.data),
phy.source.k.eq(phy.sink.k),
),
]
# # #
dchar_decoder = Duplicated_Char_Decoder()
eop_marker = EOP_Marker()
pipeline = [phy, dchar_decoder, eop_marker]
self.submodules += pipeline[1:] #phy is not a submodules
for s, d in zip(pipeline, pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.sink, self.source = pipeline[0].sink, pipeline[-1].source
dut = DUT()
def packet_sim(packets=[]):
print("=================TEST========================")
sink = dut.sink
source = dut.source
for i, p in enumerate(packets):
yield sink.data.eq(p["data"])
yield sink.k.eq(p["k"])
yield sink.stb.eq(p["stb"])
yield source.ack.eq(1)
# if i % 2 == 0:
# yield source.ack.eq(1)
# else:
# yield source.ack.eq(0)
yield
for _ in range(10):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield source.ack.eq(1)
yield
assert True
def testbench():
paks = []
for i in range(1, 10):
paks += [
{"data": C(i << 8 | 1, word_width), "k" : Replicate(0, 4), "stb":1},
{"data": C(i << 8 | 2, word_width), "k" : Replicate(0, 4), "stb":1},
{"data": C(i << 8 | 3, word_width), "k" : Replicate(0, 4), "stb":1},
{"data": C(i << 8 | 4, word_width), "k" : Replicate(0, 4), "stb":1},
{"data": C(0xB53C3CBC, word_width), "k" : C(0b0111, 4), "stb":0},
{
"data": Replicate(KCode["pak_end"], 4),
"k": Replicate(1, 4),
"stb": 1,
},
]
yield from packet_sim(paks)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

28
sim_comb.py Normal file
View File

@ -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")

58
sim_crc.py Normal file
View File

@ -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())

80
sim_double_buffer.py Normal file
View File

@ -0,0 +1,80 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from sim_generator import CXPCRC32Inserter
from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
class double_buffer_pipeline(Module):
def __init__(self):
fifo = stream.SyncFIFO(word_layout_dchar, 32)
double_buffer = CXPCRC32_Checker(0x100)
dchar_dropper = DChar_Dropper()
pipeline = [double_buffer, dchar_dropper]
self.submodules += pipeline
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
# for sim, no backpressure
self.comb += self.source.ack.eq(1)
dut = double_buffer_pipeline()
def packet_sim(packets=[]):
print("=================TEST========================")
sink = dut.sink
cyc = len(packets)
pak = packets
for c in range(cyc):
yield sink.data.eq(pak[c]["data"])
yield sink.k.eq(pak[c]["k"])
yield sink.stb.eq(1)
if "eop" in pak[c]:
yield sink.eop.eq(1)
else:
yield sink.eop.eq(0)
yield
# extra clk cycles
for _ in range(cyc, cyc + 20):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield
assert True
def testbench():
paks = [
{"data": C(0x7C7C7C7C, word_width), "k": Replicate(1, 4)},
{"data": C(0x01010101, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
# {"data": C(0xF6ACEF6B, word_dw), "k": Replicate(0, 4), "eop":1},
{"data": C(0x6AEFACF6, word_width), "k": Replicate(0, 4), "eop":1},
{"data": C(0x19191919, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x09090909, word_width), "k": Replicate(0, 4)},
{"data": C(0x90909090, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k": Replicate(0, 4)},
{"data": C(0xB2FD5E98, word_width), "k": Replicate(0, 4), "eop":1},
]
yield from packet_sim(paks)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

72
sim_eop.py Normal file
View File

@ -0,0 +1,72 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from sim_generator import CXPCRC32Inserter
from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
class EOP_Pipeline(Module):
def __init__(self):
dchar_decoder = Duplicated_Char_Decoder()
eop_inserter = EOP_Marker()
buffer = stream.SyncFIFO(word_layout_dchar, 32)
pipeline = [dchar_decoder, eop_inserter, buffer]
self.submodules += pipeline
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
# for sim, no backpressure
self.comb += self.source.ack.eq(1)
dut = EOP_Pipeline()
def packet_sim(packets=[]):
print("=================TEST========================")
sink = dut.sink
cyc = len(packets)
pak = packets
for c in range(cyc):
yield sink.data.eq(pak[c]["data"])
yield sink.k.eq(pak[c]["k"])
yield sink.stb.eq(1)
yield
# extra clk cycles
for _ in range(cyc, cyc + 10):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield
assert True
def testbench():
paks = [
{"data": Replicate(KCode["pak_start"], 4), "k": Replicate(1, 4)},
{"data": C(0x1111, word_width), "k": Replicate(1, 4)},
{"data": C(0x2222, word_width), "k": Replicate(0, 4)},
{"data": C(0x3333, word_width), "k": Replicate(0, 4)},
{"data": C(0x4444, word_width), "k": Replicate(0, 4)},
{"data": Replicate(KCode["pak_end"], 4), "k": Replicate(1, 4)},
{"data": Replicate(KCode["pak_start"], 4), "k": Replicate(1, 4)},
{"data": C(0xAAAA, word_width), "k": Replicate(1, 4)},
{"data": C(0xBBBB, word_width), "k": Replicate(0, 4)},
{"data": C(0xCCCC, word_width), "k": Replicate(0, 4)},
{"data": C(0xDDDD, word_width), "k": Replicate(0, 4)},
{"data": Replicate(KCode["pak_end"], 4), "k": Replicate(1, 4)},
]
yield from packet_sim(paks)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

134
sim_frame.py Normal file
View File

@ -0,0 +1,134 @@
from migen import *
from misoc.interconnect import stream
from sim_generator import CXPCRC32Inserter
from sim_frame_gen import get_frame_packet
from src.gateware.cxp_pipeline import *
from src.gateware.cxp_frame_pipeline import *
import numpy as np
from PIL import Image
class Frame(Module):
def __init__(self):
# to construct correct crc and ack/stb signal
self.submodules.buffer = buffer = stream.SyncFIFO(word_layout, 32)
self.submodules.crc_inserter = crc_inserter = CXPCRC32Inserter()
self.submodules.dchar_decoder = dchar_decoder = Duplicated_Char_Decoder()
# NOTE: eop is needed for crc to work correctly and RX_Bootstrap need to be followed by a EOP marker anyway
self.submodules.eop_marker = eop_marker = EOP_Marker()
self.submodules.stream_pipe = stream_pipe = Pixel_Pipeline()
pipeline = [buffer, crc_inserter, dchar_decoder, eop_marker, stream_pipe]
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 for sim
self.sync += self.source.ack.eq(1)
dut = Frame()
def check_case(packet=[]):
print("=================TEST========================")
sink = dut.sink
stream_pipe = dut.stream_pipe
for i, p in enumerate(packet):
yield sink.data.eq(p["data"])
yield sink.k.eq(p["k"])
yield sink.stb.eq(1)
if "eop" in p:
yield sink.eop.eq(1)
else:
yield sink.eop.eq(0)
# check cycle result
yield
# source = dut.dchar_decoder.source
# source = dut.stream_pipe.frame_extractor.sink
source = dut.sink
# print(
# f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}"
# )
# extra clk cycles
cyc = i + 1
img = []
line = -1
total_pixel = 1000
for i in range(cyc, cyc + total_pixel):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield
# print(
# f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}"
# )
frame_extractoer = dut.stream_pipe.frame_extractor
new_line = yield frame_extractoer.new_line
if new_line:
img.append([])
line += 1
stb = yield frame_extractoer.source.stb
data = yield frame_extractoer.source.data
if stb:
# CXP use MSB
img[line].append(np.uint16(data & 0xFFFF))
img[line].append(np.uint16(data >> 16))
# metadata = dut.stream_pipe.frame_extractor.metadata
# img_header_layout = [
# "stream_id",
# "source_tag",
# "x_size",
# "x_offset",
# "y_size",
# "y_offset",
# "l_size", # number of data words per image line
# "pixel_format",
# "tap_geo",
# "flag",
# ]
# for name in img_header_layout:
# print(f"{name} = {yield getattr(metadata, name):#04X} ", end="")
# print()
Image.fromarray(np.array(img, dtype=np.uint8)).show()
assert True
def testbench():
stream_id = 0x69
packet_tag = 0
frame_packet = get_frame_packet(stream_id)
packet = [
{"data": Replicate(C(stream_id, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(packet_tag, char_width), 4), "k": Replicate(0, 4)},
{
"data": Replicate(C(len(frame_packet), 2*char_width)[8:], 4),
"k": Replicate(0, 4),
},
{
"data": Replicate(C(len(frame_packet), 2*char_width)[:8], 4),
"k": Replicate(0, 4),
},
]
packet += frame_packet
# NOTE: for crc inserter!!!!
packet[-1]["eop"] = 0
yield from check_case(packet)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

148
sim_frame_gen.py Normal file
View File

@ -0,0 +1,148 @@
from migen import *
from misoc.interconnect import stream
from src.gateware.cxp_pipeline import *
from src.gateware.cxp_frame_pipeline import *
from PIL import Image
import numpy as np
def get_image_header(
stream_id, source_tag, xsize, xoffset, ysize, yoffset, dsize, pixelF, tag_geo, flag
):
stream_id = C(stream_id, char_width)
source_tag = C(source_tag, 2 * char_width)
xsize = C(xsize, 3 * char_width)
xoffset = C(xoffset, 3 * char_width)
ysize = C(ysize, 3 * char_width)
yoffset = C(yoffset, 3 * char_width)
dsize = C(dsize, 3 * char_width)
pixelF = C(pixelF, 2 * char_width)
tag_geo = C(tag_geo, 2 * char_width)
flag = C(flag, char_width)
assert len(stream_id) == len(flag) == char_width
assert len(source_tag) == len(pixelF) == len(tag_geo) == 2 * char_width
assert len(xsize) == len(xoffset) == len(ysize) == len(yoffset) == 3 * char_width
return [
{"data": Replicate(KCode["stream_marker"], 4), "k": Replicate(1, 4)},
{"data": Replicate(C(0x01, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(stream_id, 4), "k": Replicate(0, 4)},
{"data": Replicate(source_tag[8:], 4), "k": Replicate(0, 4)},
{"data": Replicate(source_tag[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(xsize[16:], 4), "k": Replicate(0, 4)},
{"data": Replicate(xsize[8:16], 4), "k": Replicate(0, 4)},
{"data": Replicate(xsize[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(xoffset[16:], 4), "k": Replicate(0, 4)},
{"data": Replicate(xoffset[8:16], 4), "k": Replicate(0, 4)},
{"data": Replicate(xoffset[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(ysize[16:], 4), "k": Replicate(0, 4)},
{"data": Replicate(ysize[8:16], 4), "k": Replicate(0, 4)},
{"data": Replicate(ysize[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(yoffset[16:], 4), "k": Replicate(0, 4)},
{"data": Replicate(yoffset[8:16], 4), "k": Replicate(0, 4)},
{"data": Replicate(yoffset[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(dsize[16:], 4), "k": Replicate(0, 4)},
{"data": Replicate(dsize[8:16], 4), "k": Replicate(0, 4)},
{"data": Replicate(dsize[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(pixelF[8:], 4), "k": Replicate(0, 4)},
{"data": Replicate(pixelF[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(tag_geo[8:], 4), "k": Replicate(0, 4)},
{"data": Replicate(tag_geo[:8], 4), "k": Replicate(0, 4)},
{"data": Replicate(flag, 4), "k": Replicate(0, 4)},
]
def get_line_marker():
return [
{"data": Replicate(KCode["stream_marker"], 4), "k": Replicate(1, 4)},
{"data": Replicate(C(0x02, char_width), 4), "k": Replicate(0, 4)},
]
def get_frame_packet(stream_id, pixel_format="mono16"):
assert pixel_format in ["mono16"]
arr = [
[204, 200, 203, 205, 190, 187, 189, 205, 214, 197, 188, 185, 181, 178, 193, 209, 211, 207, 211, 192, 168, 168, 171, 199, 210, 212, 203, 196],
[218, 205, 199, 190, 192, 197, 196, 195, 184, 178, 182, 173, 166, 132, 122, 114, 154, 184, 187, 188, 171, 168, 170, 180, 192, 196, 202, 198],
[223, 222, 222, 224, 216, 199, 199, 207, 205, 189, 183, 182, 144, 66, 61, 66, 80, 148, 181, 175, 169, 170, 174, 177, 196, 206, 223, 218],
[221, 226, 225, 222, 211, 200, 202, 208, 215, 201, 187, 180, 133, 116, 113, 118, 96, 111, 206, 193, 170, 169, 186, 211, 218, 224, 231, 223],
[219, 216, 206, 197, 210, 201, 206, 203, 191, 190, 185, 145, 134, 140, 159, 170, 150, 116, 180, 173, 179, 170, 172, 185, 201, 218, 227, 227],
[203, 198, 194, 208, 227, 201, 201, 201, 215, 221, 209, 170, 136, 113, 141, 139, 141, 145, 188, 170, 180, 169, 184, 173, 174, 192, 215, 230],
[206, 224, 213, 213, 233, 207, 204, 226, 233, 227, 214, 166, 145, 123, 145, 155, 147, 186, 213, 187, 171, 169, 193, 193, 171, 178, 186, 207],
[212, 228, 216, 205, 214, 205, 204, 230, 235, 225, 219, 187, 143, 122, 146, 163, 158, 195, 209, 203, 174, 168, 190, 185, 187, 202, 180, 174],
[197, 206, 201, 223, 213, 201, 203, 231, 234, 225, 218, 206, 147, 125, 149, 155, 190, 208, 206, 203, 175, 168, 171, 179, 184, 206, 189, 176],
[213, 202, 209, 235, 223, 200, 202, 202, 227, 227, 202, 176, 138, 122, 144, 153, 190, 209, 207, 191, 172, 167, 179, 204, 190, 191, 180, 193],
[225, 225, 207, 231, 219, 197, 215, 200, 194, 199, 181, 172, 131, 129, 147, 159, 113, 175, 196, 179, 184, 169, 181, 210, 202, 204, 200, 177],
[208, 222, 204, 223, 210, 191, 195, 198, 203, 167, 171, 168, 135, 129, 149, 175, 66, 57, 90, 121, 147, 165, 181, 205, 195, 217, 209, 173],
[188, 216, 201, 206, 199, 180, 185, 180, 129, 75, 139, 166, 124, 146, 189, 135, 51, 41, 38, 40, 45, 63, 131, 201, 189, 215, 193, 170],
[188, 194, 195, 192, 182, 180, 134, 68, 45, 41, 96, 130, 116, 156, 163, 64, 46, 41, 43, 41, 42, 42, 74, 181, 177, 198, 175, 193],
[179, 179, 209, 224, 198, 182, 99, 42, 44, 41, 44, 100, 116, 125, 100, 46, 45, 42, 42, 37, 44, 43, 49, 150, 183, 170, 172, 198],
[175, 177, 208, 223, 197, 180, 94, 40, 42, 40, 41, 99, 134, 117, 80, 43, 46, 43, 37, 37, 44, 42, 35, 129, 195, 170, 170, 180],
[179, 181, 187, 217, 193, 175, 91, 38, 41, 41, 42, 106, 151, 107, 62, 43, 45, 41, 33, 38, 42, 34, 33, 77, 188, 175, 173, 208],
[190, 191, 180, 213, 194, 175, 78, 38, 40, 40, 40, 98, 134, 97, 51, 44, 59, 50, 37, 40, 36, 26, 36, 44, 100, 178, 192, 206],
[199, 191, 184, 204, 196, 176, 78, 33, 38, 38, 39, 80, 102, 83, 43, 44, 112, 130, 122, 63, 33, 24, 29, 34, 33, 74, 162, 195],
[191, 170, 196, 193, 186, 177, 88, 27, 34, 37, 36, 74, 101, 70, 36, 37, 81, 127, 137, 113, 40, 28, 30, 32, 36, 29, 69, 173],
[164, 189, 190, 180, 176, 172, 83, 26, 28, 33, 32, 68, 97, 62, 32, 30, 44, 97, 123, 136, 58, 42, 44, 43, 43, 40, 58, 162],
[177, 202, 205, 181, 174, 163, 78, 38, 35, 47, 54, 67, 92, 51, 28, 29, 26, 21, 39, 85, 47, 46, 52, 47, 46, 45, 48, 141],
[181, 193, 199, 192, 171, 163, 91, 67, 121, 123, 91, 63, 89, 45, 25, 25, 23, 20, 15, 13, 20, 48, 54, 35, 34, 34, 68, 146],
[175, 192, 195, 179, 165, 163, 100, 64, 99, 94, 82, 58, 83, 37, 23, 22, 22, 27, 21, 15, 14, 44, 98, 83, 94, 118, 164, 157],
[153, 184, 171, 163, 161, 157, 140, 70, 58, 89, 61, 53, 76, 30, 20, 20, 20, 31, 24, 19, 16, 47, 159, 163, 160, 171, 160, 142],
[142, 150, 161, 168, 154, 154, 164, 138, 76, 55, 26, 37, 62, 24, 19, 19, 20, 21, 23, 27, 31, 46, 142, 156, 151, 153, 147, 145],
[153, 147, 174, 171, 151, 150, 169, 158, 142, 92, 28, 60, 59, 20, 20, 18, 20, 26, 27, 29, 33, 38, 125, 153, 150, 147, 147, 148],
[138, 141, 166, 164, 146, 144, 164, 149, 132, 72, 34, 88, 72, 24, 19, 18, 18, 23, 25, 28, 31, 30, 98, 150, 146, 144, 146, 144]
]
source_tag = 0
xsize, ysize = len(arr[0]), len(arr)
xoffset, yoffset = 0, 0
if pixel_format == "mono16":
dsize = xsize // 2
pixelF = 0x0105
tag_geo = 0
flag = 0
packet = []
# Image header
packet += get_image_header(
stream_id,
source_tag,
xsize,
xoffset,
ysize,
yoffset,
dsize,
pixelF,
tag_geo,
flag,
)
for line in arr:
packet += get_line_marker()
if pixel_format == "mono16":
for i in range(len(line)):
if (i % 2) == 0:
if i == len(line) - 1:
# print(C(line[i]))
packet += [
{
"data": C(line[i], 4 * char_width),
"k": Replicate(0, 4),
},
]
else:
# print(C(line[i], 2 * char_width), C(line[i + 1]))
# CXP use MSB
packet += [
{
"data": Cat(
C(line[i], 2 * char_width),
C(line[i + 1], 2 * char_width),
),
"k": Replicate(0, 4),
},
]
return packet

122
sim_generator.py Normal file
View File

@ -0,0 +1,122 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from src.gateware.cxp_frame_pipeline import CXPCRC32
from src.gateware.cxp_pipeline import *
class CXPCRC32Inserter(Module):
def __init__(self, insert_eop=True):
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout)
# # #
self.submodules.crc = crc = CXPCRC32(word_width)
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
# WARNING: this will eat data if the source don't care about ack
# NOTE: need one cycle to turn itself on
fsm.act(
"IDLE",
crc.reset.eq(1),
self.sink.ack.eq(1),
If(
self.sink.stb,
self.sink.ack.eq(0),
NextState("COPY"),
),
)
fsm.act(
"COPY",
crc.ce.eq(self.sink.stb & self.source.ack),
crc.data.eq(self.sink.data),
self.sink.connect(self.source),
self.source.eop.eq(0),
If(
self.sink.stb & self.sink.eop & self.source.ack,
NextState("INSERT"),
),
)
fsm.act(
"INSERT",
self.source.stb.eq(1),
self.source.eop.eq(1) if insert_eop else self.source.eop.eq(0),
self.source.data.eq(crc.value),
If(self.source.ack, NextState("IDLE")),
)
class StreamPacket_Wrapper(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout)
# # #
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act(
"IDLE",
self.sink.ack.eq(1),
If(
self.sink.stb,
self.sink.ack.eq(0),
NextState("INSERT_HEADER_0"),
),
)
fsm.act(
"INSERT_HEADER_0",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(KCode["pak_start"], 4)),
self.source.k.eq(Replicate(1, 4)),
If(self.source.ack, NextState("INSERT_HEADER_1")),
)
fsm.act(
"INSERT_HEADER_1",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(C(0x01, char_width), 4)),
self.source.k.eq(Replicate(0, 4)),
If(self.source.ack, NextState("COPY")),
)
fsm.act(
"COPY",
self.sink.connect(self.source),
self.source.eop.eq(0),
If(
self.sink.stb & self.sink.eop & self.source.ack,
NextState("INSERT_FOOTER"),
),
)
fsm.act(
"INSERT_FOOTER",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(KCode["pak_end"], 4)),
self.source.k.eq(Replicate(1, 4)),
# Simulate RX don't have eop tagged
# self.source.eop.eq(1),
If(self.source.ack, NextState("IDLE")),
)
# With KCode & 0x01*4
class StreamData_Generator(Module):
def __init__(self):
# should be big enough for all test
self.submodules.buffer = buffer = stream.SyncFIFO(word_layout, 32)
self.submodules.crc_inserter = crc_inserter = CXPCRC32Inserter()
self.submodules.wrapper = wrapper = StreamPacket_Wrapper()
# # #
pipeline = [buffer, crc_inserter, wrapper]
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

149
sim_idle.py Normal file
View File

@ -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")

323
sim_roi.py Normal file
View File

@ -0,0 +1,323 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from src.gateware.cxp_frame_pipeline import *
from src.gateware.cxp_pipeline import *
from math import ceil
class ROI(Module):
def __init__(self):
fifo = stream.SyncFIFO(word_layout, 32) # to avoid data getting eaten and act as delay between eop
dchar_decoder = Duplicated_Char_Decoder()
# self.crc = CXPCRC32_Checker()
self.pipeline = Pixel_Pipeline(24, 32)
pipeline = [fifo, dchar_decoder, self.pipeline]
self.submodules += pipeline
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
# DEBUG: test roi
cfg = self.pipeline.roi.cfg
self.comb += [
cfg.x0.eq(1),
cfg.x1.eq(2),
cfg.y0.eq(1),
cfg.y1.eq(2),
]
dut = ROI()
def packet_sim(packets=[]):
print("=================TEST========================")
sink = dut.sink
cyc = len(packets)
pak = packets
for c in range(cyc):
yield sink.data.eq(pak[c]["data"])
yield sink.k.eq(pak[c]["k"])
yield sink.stb.eq(1)
if "eop" in pak[c]:
yield sink.eop.eq(1)
else:
yield sink.eop.eq(0)
yield
# extra clk cycles
for _ in range(cyc, cyc + 20):
yield sink.data.eq(0)
yield sink.k.eq(0)
yield sink.stb.eq(0)
yield sink.eop.eq(0)
yield
metadata = dut.pipeline.header_decoder.metadata
img_header_layout = [
"stream_id",
"source_tag", # image index since powering on
"x_size",
"x_offset",
"y_size",
"y_offset",
"l_size", # number of data words per image line
"pixel_format",
"tap_geo",
"flag",
]
for name in img_header_layout:
print(f"{name} = {yield getattr(metadata, name):#04X} ", end="")
print()
roi = dut.pipeline.roi
print(f"x0 = {yield roi.cfg.x0} y0 = {yield roi.cfg.y0} | x1 = {yield roi.cfg.x1} y1 = {yield roi.cfg.y1}")
print(f"out count = {yield roi.out.count}")
assert True
def testbench():
paks = [
{"data": C(0x7C7C7C7C, word_width), "k" : Replicate(1, 4)},
{"data": C(0x01010101, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # stream id
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x6AEFACF6, word_width), "k" : Replicate(0, 4), "eop":0}, # crc
{"data": C(0x02020202, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # Xsize[23:16]
{"data": C(0x09090909, word_width), "k" : Replicate(0, 4)}, # Xsize[15:8]
{"data": C(0x90909090, word_width), "k" : Replicate(0, 4)}, # Xsize[7:0]
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x8EE1DAA1, word_width), "k" : Replicate(0, 4), "eop":0}, # crc
{"data": C(0x08080808, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x08080808, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # DsizeL[23:16]
{"data": C(0x02020202, word_width), "k" : Replicate(0, 4)}, # DsizeL[15:8]
{"data": C(0x64646464, word_width), "k" : Replicate(0, 4)}, # DsizeL[7:0]
{"data": C(0x01010101, word_width), "k" : Replicate(0, 4)},
{"data": C(0x01010101, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x51C243EA, word_width), "k" : Replicate(0, 4), "eop":0}, # crc
# the new line + pixel data
{"data": C(0x7C7C7C7C, word_width), "k" : Replicate(1, 4)},
{"data": C(0x02020202, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0D0D0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0B0D, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0D0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0C0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0A0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0C0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0C0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0C0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0C0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0D0A, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0A0B0D, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0D0C0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0A0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0D0D0B0A, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0C0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0C0B0B0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0C0C0B, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0x0B0B0B0C, word_width), "k" : Replicate(0, 4)},
{"data": C(0xCB5DCDD6, word_width), "k" : Replicate(0, 4), "eop": 0}, # crc
# {"data": C(0x0C0B0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0B0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0B0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0D0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0A, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0D0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0D, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0A0C0C0D, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0B0A, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0D0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0B0D0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0D0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0B0A, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0A0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0D0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0D0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0D0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0D0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0D0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0C0D0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0D0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0D0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0B0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0B0C0B, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0B0C0C0D, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0D0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0B0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0C0C0C0C, word_dw), "k" : Replicate(0, 4)},
# {"data": C(0x0BADF020, word_dw), "k" : Replicate(0, 4), "eop":0}, # crc
]
yield from packet_sim(paks)
def testbench_fake_data():
# config
pix_size = 8
# x_size = 2448
x_size = 10
y_size = 2
l_size = ceil(x_size*pix_size/32)
pix_fmt = {
8: 0x0101,
10: 0x0102,
12: 0x0103,
14: 0x0104,
16: 0x0105,
}
# frame header
paks = [
{"data": C(0x7C7C7C7C, word_width), "k" : Replicate(1, 4)},
{"data": C(0x01010101, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # stream id
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x6AEFACF6, word_width), "k" : Replicate(0, 4), "eop":0}, # fake crc
{"data": C(0x02020202, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # Xsize[23:16]
{"data": Replicate(C(x_size >> 8, char_width), 4), "k" : Replicate(0, 4)}, # Xsize[15:8]
{"data": Replicate(C(x_size & 0xFF, char_width), 4), "k" : Replicate(0, 4)}, # Xsize[7:0]
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # Ysize[23:16]
{"data": C(0x8EE1DAA1, word_width), "k" : Replicate(0, 4), "eop":0}, # fake crc
{"data": Replicate(C(y_size >> 8, char_width), 4), "k" : Replicate(0, 4)}, # Ysize[15:8]
{"data": Replicate(C(y_size & 0xFF, char_width), 4), "k" : Replicate(0, 4)}, # Ysize[7:0]
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x08080808, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)}, # DsizeL[23:16]
{"data": Replicate(C(l_size >> 8, char_width), 4), "k" : Replicate(0, 4)}, # DsizeL[15:8]
{"data": Replicate(C(l_size & 0xFF, char_width), 4), "k" : Replicate(0, 4)}, # DsizeL[7:0]
{"data": Replicate(C(pix_fmt[pix_size] >> 8, char_width), 4), "k" : Replicate(0, 4)}, # PixelF[15:8]
{"data": Replicate(C(pix_fmt[pix_size] & 0xFF, char_width), 4), "k" : Replicate(0, 4)}, # PixelF[7:0]
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x00000000, word_width), "k" : Replicate(0, 4)},
{"data": C(0x51C243EA, word_width), "k" : Replicate(0, 4), "eop":0}, # fake crc
]
# pixel data
base = 0
for _ in range(y_size):
pix = []
for _ in range(x_size):
base += 1
pix.append(base)
print(pix)
packed = 0
for i, p in enumerate(pix):
packed += p << i*pix_size
# print(f"{packed:08X}")
# new line indicator
paks += [
{"data": C(0x7C7C7C7C, word_width), "k" : Replicate(1, 4)},
{"data": C(0x02020202, word_width), "k" : Replicate(0, 4)},
]
for i in range(l_size):
serialized = (packed & (0xFFFF_FFFF << i*word_width)) >> i*word_width
print(f"{serialized:08X}")
paks.append({"data": C(serialized, word_width), "k" : Replicate(0, 4)})
paks.append({"data": C(0xCB5DCDD6, word_width), "k" : Replicate(0, 4), "eop": 0}) # fake crc
yield from packet_sim(paks)
run_simulation(dut, testbench_fake_data(), vcd_name="sim-cxp.vcd")

154
sim_stream.py Normal file
View File

@ -0,0 +1,154 @@
from migen import *
from misoc.interconnect import stream
from sim_pipeline import *
from sim_generator import StreamData_Generator
from src.gateware.cxp_pipeline import *
class CXP_Links(Module):
def __init__(self):
# TODO: select the correct buffer to read from
# NOTE: although there are double buffer in each connect, the reading must be faster than writing to avoid data loss
self.downconn_sources = []
self.stream_sinks = []
for i in range(2):
downconn = Pipeline()
setattr(self.submodules, "cxp_conn"+str(i), downconn)
self.downconn_sources.append(downconn)
stream_pipeline = Stream_Pipeline()
setattr(self.submodules, "stream_pipeline"+str(i), stream_pipeline)
self.stream_sinks.append(stream_pipeline)
self.submodules.crossbar = Streams_Crossbar(self.downconn_sources, self.stream_sinks)
class Pipeline(Module):
def __init__(self):
self.submodules.generator = generator = StreamData_Generator()
self.submodules.dchar_decoder = dchar_decoder = Duplicated_Char_Decoder()
self.submodules.data_decoder = data_decoder = Control_Packet_Reader()
self.submodules.eop_marker = eop_marker = EOP_Marker()
# # #
pipeline = [generator, dchar_decoder, data_decoder, eop_marker]
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
# self.comb += self.source.ack.eq(1)
dut = CXP_Links()
def check_case(packet=[]):
print("=================TEST========================")
downconns = dut.downconn_sources
stream_buffers = dut.stream_sinks
ch = 0
for i, p in enumerate(packet):
for x in range(len(downconns)):
if x == ch:
yield downconns[x].sink.data.eq(p["data"])
yield downconns[x].sink.k.eq(p["k"])
yield downconns[x].sink.stb.eq(1)
else:
yield downconns[x].sink.data.eq(0)
yield downconns[x].sink.k.eq(0)
yield downconns[x].sink.stb.eq(0)
yield downconns[x].sink.eop.eq(0)
if "eop" in p:
yield downconns[ch].sink.eop.eq(1)
# compensate for delay
# yield
# yield downconns[ch].sink.data.eq(0)
# yield downconns[ch].sink.k.eq(0)
# yield downconns[ch].sink.stb.eq(0)
# yield downconns[ch].sink.eop.eq(0)
# yield
# yield
# yield
ch = (ch + 1) % len(downconns)
else:
yield downconns[ch].sink.eop.eq(0)
# check cycle result
yield
# source = dut.stream_pipeline_sinks[0].source
source = dut.stream_sinks[0].double_buffer.source
print(
f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}"
# f" source dchar = {yield source.dchar:#X} dchar_k = {yield source.dchar_k:#X}"
f"\nCYCLE#{i} : read mask = {yield dut.crossbar.mux.sel}"
# f"\nCYCLE#{i} : stream id = {yield decoder.stream_id:#X} pak_tag = {yield decoder.pak_tag:#X}"
# f" stream_pak_size = {yield decoder.stream_pak_size:#X}"
)
# crc = downconns[1].generator.crc_inserter.crc
# crc = dut.double_buffer.crc
# print(
# f"CYCLE#{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} ce = {yield crc.ce}"
# )
# extra clk cycles
cyc = i + 1
for i in range(cyc, cyc + 30):
for x in range(len(downconns)):
# yield won't reset every cycle
yield downconns[x].sink.data.eq(0)
yield downconns[x].sink.k.eq(0)
yield downconns[x].sink.stb.eq(0)
yield downconns[x].sink.eop.eq(0)
yield
print(
f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}"
# f" source dchar = {yield source.dchar:#X} dchar_k = {yield source.dchar_k:#X}"
f"\nCYCLE#{i} : read mask = {yield dut.crossbar .mux.sel}"
# f"\nCYCLE#{i} : stream id = {yield decoder.stream_id:#X} pak_tag = {yield decoder.pak_tag:#X}"
# f" stream_pak_size = {yield decoder.stream_pak_size:#X}"
)
assert True
def testbench():
# stream_id = 0x01
streams = [
[
{"data": 0x11111111, "k": Replicate(0, 4)},
{"data": 0xB105F00D, "k": Replicate(0, 4)},
],
[
{"data": 0x22222222, "k": Replicate(0, 4)},
{"data": 0xC001BEA0, "k": Replicate(0, 4)},
],
[
{"data": 0x33333333, "k": Replicate(0, 4)},
{"data": 0xC0A79AE5, "k": Replicate(0, 4)},
],
]
packet = []
for i, s in enumerate(streams):
s[-1]["eop"] = 0
packet += [
{"data": Replicate(C(i % 2, char_width), 4), "k": Replicate(0, 4)},
{"data": Replicate(C(i, char_width), 4), "k": Replicate(0, 4)},
{
"data": Replicate(C(len(s) >> 8 & 0xFF, char_width), 4),
"k": Replicate(0, 4),
},
{"data": Replicate(C(len(s) & 0xFF, char_width), 4), "k": Replicate(0, 4)},
*s,
]
yield from check_case(packet)
run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")

32
src/.clang-format Normal file
View File

@ -0,0 +1,32 @@
BasedOnStyle: LLVM
Language: Cpp
Standard: Cpp11
AccessModifierOffset: -1
AlignEscapedNewlines: Left
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: Inline
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
ColumnLimit: 120
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ContinuationIndentWidth: 4
DerivePointerAlignment: false
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
MaxEmptyLinesToKeep: 1
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterTemplateKeyword: false
SpacesBeforeTrailingComments: 2
TabWidth: 4
UseTab: Never

1
src/.clippy.toml Normal file
View File

@ -0,0 +1 @@
doc-valid-idents = ["CPython", "NumPy", ".."]

View File

@ -0,0 +1,32 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [commit]
repos:
- repo: local
hooks:
- id: cargo-fmt
name: artiq-zynq cargo format
entry: nix
language: system
types: [file, rust]
pass_filenames: false
description: Runs cargo fmt on the codebase.
args: [develop, -c, cargo, fmt, --manifest-path, src/Cargo.toml, --all]
- id: cargo-clippy
name: artiq-zynq cargo clippy
entry: nix
language: system
types: [file, rust]
pass_filenames: false
description: Runs cargo clippy on the codebase.
args: [develop, -c, cargo, clippy, --manifest-path, src/Cargo.toml, --tests]
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.0
hooks:
- id: clang-format
name: artiq-zynq clang-format
description: Runs clang-format on the codebase.
files: \.(cpp|h|hpp|c)$
args: [-style=file, -fallback-style=none, -assume-filename=src/.clang-format]

78
src/Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -246,6 +255,7 @@ dependencies = [
"libsupport_zynq",
"log",
"log_buffer",
"nalgebra",
"nb 0.1.3",
"unwind",
"vcell",
@ -268,6 +278,7 @@ name = "libboard_artiq"
version = "0.0.0"
dependencies = [
"build_zynq",
"byteorder",
"core_io",
"crc",
"embedded-hal",
@ -382,6 +393,19 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577"
[[package]]
name = "nalgebra"
version = "0.32.6"
source = "git+https://git.m-labs.hk/M-Labs/nalgebra.git?rev=dd00f9b#dd00f9b46046e0b931d1b470166db02fd29591be"
dependencies = [
"approx",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nb"
version = "0.1.3"
@ -397,6 +421,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
[[package]]
name = "num-complex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.3.3"
@ -408,6 +441,26 @@ dependencies = [
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
@ -415,8 +468,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -523,6 +583,18 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
[[package]]
name = "simba"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
]
[[package]]
name = "smoltcp"
version = "0.7.5"
@ -555,6 +627,12 @@ dependencies = [
"log",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.5"

484
src/gateware/cxp.py Normal file
View File

@ -0,0 +1,484 @@
from migen import *
from migen.genlib.cdc import MultiReg, PulseSynchronizer
from misoc.interconnect.csr import *
from artiq.gateware.rtio import rtlink
from cxp_downconn import CXP_RXPHYs
from cxp_upconn import CXP_TXPHYs
from cxp_pipeline import *
from cxp_frame_pipeline import *
from functools import reduce
from operator import add
from types import SimpleNamespace
class CXP_PHYS(Module, AutoCSR):
def __init__(self, refclk, upconn_pads, downconn_pads, sys_clk_freq, master=0):
assert len(upconn_pads) == len(downconn_pads)
self.submodules.tx = CXP_TXPHYs(upconn_pads, sys_clk_freq)
self.submodules.rx = CXP_RXPHYs(refclk, downconn_pads, sys_clk_freq, master)
self.phys = []
for tx, rx in zip(self.tx.phys, self.rx.phys):
phy = SimpleNamespace()
phy.tx, phy.rx = tx, rx
self.phys.append(phy)
class CXP_Core(Module, AutoCSR):
def __init__(self, phy):
self.submodules.tx = TX_Pipeline(phy.tx)
self.submodules.rx = RX_Pipeline(phy.rx)
def get_tx_port(self):
return self.tx.writer.mem.get_port(write_capable=True)
def get_tx_mem_size(self):
# TODO: remove this
# FIXME: if tx mem size is NOT same as rx, for some reason when rx mem is writen, tx mem cannot be access anymore
# and each time tx mem is read, CPU will return rx mem instead (fixed by reordering the mem allocation order)
# FIXME: seems like there are address alignment issue, if tx mem size is 0x800, the mem following the tx mem cannot be read correctly
# However, if tx mem is 0x2000 (same size as rx mem) the following rx mem can be read correctly
return self.tx.writer.mem.depth*self.upconn.bootstrap.mem.width // 8 # 0x800
# return self.downconn.bootstrap.mem.depth*self.downconn.bootstrap.mem.width // 8 # 0x2000
def get_mem_size(self):
return word_width * buffer_count * buffer_depth // 8
def get_rx_port(self):
return self.rx.reader.mem.get_port(write_capable=False)
def get_rx_mem_size(self):
# TODO: remove this
return self.rx.reader.mem.depth*self.downconn.bootstrap.mem.width // 8
class RX_Pipeline(Module, AutoCSR):
def __init__(self, phy):
self.ready = CSRStatus()
# # #
gtx = phy.gtx
# GTX status
self.sync += self.ready.status.eq(gtx.rx_ready)
# DEBUG: init status
self.rxinit_phaligndone = CSRStatus()
self.comb += [
self.rxinit_phaligndone.status.eq(gtx.rx_init.Xxphaligndone),
]
# Connect all GTX connections' DRP
self.gtx_daddr = CSRStorage(9)
self.gtx_dread = CSR()
self.gtx_din_stb = CSR()
self.gtx_din = CSRStorage(16)
self.gtx_dout = CSRStatus(16)
self.gtx_dready = CSR()
self.comb += gtx.dclk.eq(ClockSignal("sys"))
self.sync += [
gtx.daddr.eq(self.gtx_daddr.storage),
gtx.den.eq(self.gtx_dread.re | self.gtx_din_stb.re),
gtx.dwen.eq(self.gtx_din_stb.re),
gtx.din.eq(self.gtx_din.storage),
If(gtx.dready,
self.gtx_dready.w.eq(1),
self.gtx_dout.status.eq(gtx.dout),
).Elif(self.gtx_dready.re,
self.gtx_dready.w.eq(0),
),
]
# Receiver Pipeline WIP
#
# 32 32+8(dchar)
# PHY ---/---> dchar -----/-----> trigger ack ------> packet ------> EOP Marker ------> stream data packet
# decoder checker decoder with CRC
#
cdr = ClockDomainsRenamer("cxp_gtx_rx")
# decode all incoming data as duplicate char and inject the result into the bus for downstream modules
self.submodules.dchar_decoder = dchar_decoder = cdr(Duplicated_Char_Decoder())
# Priority level 1 packet - Trigger ack packet
self.submodules.trig_ack_checker = trig_ack_checker = cdr(Trigger_Ack_Checker())
self.submodules.trig_ack_ps = trig_ack_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
self.sync.cxp_gtx_rx += trig_ack_ps.i.eq(trig_ack_checker.ack)
self.trig_ack = Signal()
self.trig_clr = Signal()
# Error are latched
self.sync += [
If(trig_ack_ps.o,
self.trig_ack.eq(1),
).Elif(self.trig_clr,
self.trig_ack.eq(0),
),
]
# Priority level 2 packet - data, test packet
self.submodules.reader = reader = cdr(Control_Packet_Reader())
self.reader_decode_err = CSR()
self.reader_buffer_err = CSR()
decode_err_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
buffer_err_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
self.submodules += decode_err_ps, buffer_err_ps
self.sync.cxp_gtx_rx += [
decode_err_ps.i.eq(reader.decode_err),
buffer_err_ps.i.eq(reader.buffer_err),
]
self.sync += [
If(decode_err_ps.o,
self.reader_decode_err.w.eq(1),
).Elif(self.reader_decode_err.re,
self.reader_decode_err.w.eq(0),
),
If(buffer_err_ps.o,
self.reader_buffer_err.w.eq(1),
).Elif(self.reader_buffer_err.re,
self.reader_buffer_err.w.eq(0),
),
]
# TODO: rewrite this to follow crc_error_cnt from frameline
# test packet error & packet counters
self.test_error_counter = CSRStatus(len(reader.test_err_cnt))
self.test_packet_counter = CSRStatus(len(reader.test_pak_cnt))
self.test_counts_reset = CSR()
test_reset_ps = PulseSynchronizer("sys", "cxp_gtx_rx")
self.submodules += test_reset_ps
self.sync += test_reset_ps.i.eq(self.test_counts_reset.re),
test_err_cnt_r = Signal.like(reader.test_err_cnt)
test_pak_cnt_r = Signal.like(reader.test_pak_cnt)
self.sync.cxp_gtx_rx += [
reader.test_cnt_reset.eq(test_reset_ps.o),
test_err_cnt_r.eq(reader.test_err_cnt),
test_pak_cnt_r.eq(reader.test_pak_cnt),
]
self.specials += [
MultiReg(test_err_cnt_r, self.test_error_counter.status),
MultiReg(test_pak_cnt_r, self.test_packet_counter.status),
]
# reader cicular memory control interface
self.packet_type = CSRStatus(8)
self.pending_packet = CSR()
self.read_ptr = CSRStatus(log2_int(buffer_count))
self.specials += [
MultiReg(reader.packet_type, self.packet_type.status),
MultiReg(self.read_ptr.status, reader.read_ptr_rx, odomain="cxp_gtx_rx"),
]
self.sync += [
self.pending_packet.w.eq(self.read_ptr.status != reader.write_ptr_sys),
If(~gtx.rx_ready,
self.read_ptr.status.eq(0),
).Elif(self.pending_packet.re & self.pending_packet.w,
self.read_ptr.status.eq(self.read_ptr.status + 1),
)
]
self.submodules.fifo = fifo = cdr(stream.SyncFIFO(word_layout_dchar, 32, True))
# Drop the K29.7 and mark the EOP for arbiter and crc cheker
self.submodules.eop_marker = eop_marker = cdr(EOP_Marker())
rx_pipeline = [phy, dchar_decoder, trig_ack_checker, reader, fifo, eop_marker]
for s, d in zip(rx_pipeline, rx_pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.source = rx_pipeline[-1].source
# DEBUG: CSR
self.trigger_ack = CSR()
self.sync += [
self.trig_clr.eq(self.trigger_ack.re),
self.trigger_ack.w.eq(self.trig_ack),
]
class TX_Pipeline(Module, AutoCSR):
def __init__(self, phy):
# Transmission Pipeline
#
# 32 32 8
# ctrl/test ---/---> packet -----> idle word -----> trigger ack ---/--> conv ---/---> trigger -----> PHY
# packet wrapper inserter inserter inserter
#
# Equivalent transmission priority:
# trigger > trigger ack > idle > test/data packet
# To maintain the trigger performance, idle word should not be inserted into trigger or trigger ack.
#
# In low speed CoaXpress, the higher priority packet can be inserted in two types of boundary
# Insertion @ char boundary: Trigger packets
# Insertion @ word boundary: Trigger ack & IDLE packets
# The 32 bit part of the pipeline handles the word boundary insertion while the 8 bit part handles the char boundary insertion
# Packet FIFOs with transmission priority
# 0: Trigger packet
self.submodules.trig = trig = Trigger_Inserter()
# 1: IO acknowledgment for trigger packet
self.submodules.trig_ack = trig_ack = Trigger_ACK_Inserter()
# 2: All other packets (data & test packet)
# Control is not timing dependent, all the data packets are handled in firmware
self.submodules.writer = writer = Control_Packet_Writer()
# writer memory control interface
self.writer_word_len = CSRStorage(log2_int(buffer_depth))
self.writer_stb = CSR()
self.writer_stb_testseq = CSR()
self.writer_busy = CSRStatus()
self.sync += [
writer.word_len.eq(self.writer_word_len.storage),
writer.stb.eq(self.writer_stb.re),
writer.stb_testseq.eq(self.writer_stb_testseq.re),
self.writer_busy.status.eq(writer.busy),
]
# Misc
self.submodules.pak_wrp = pak_wrp = Packet_Wrapper()
self.submodules.idle = idle = Idle_Word_Inserter()
self.submodules.converter = converter = stream.StrideConverter(word_layout, char_layout)
tx_pipeline = [writer, pak_wrp, idle, trig_ack, converter, trig, phy]
for s, d in zip(tx_pipeline, tx_pipeline[1:]):
self.comb += s.source.connect(d.sink)
class CXP_Frame_Pipeline(Module, AutoCSR):
# optimal stream packet size is 2 KiB - Section 9.5.2 (CXP-001-2021)
def __init__(self, pipelines, pmod_pads, roi_engine_count=1, res_width=16, count_width=31, master=0, packet_size=16384):
n_channels = len(pipelines)
assert n_channels > 0
assert count_width <= 31
# Trigger rtio
nbit_trigdelay = 8
nbit_linktrig = 1
self.trigger = rtlink.Interface(
rtlink.OInterface(nbit_trigdelay + nbit_linktrig),
rtlink.IInterface(word_width, timestamped=False)
)
self.sync.rio += [
If(self.trigger.o.stb,
pipelines[master].tx.trig.delay.eq(self.trigger.o.data[nbit_linktrig:]),
pipelines[master].tx.trig.linktrig_mode.eq(self.trigger.o.data[:nbit_linktrig]),
),
pipelines[master].tx.trig.stb.eq(self.trigger.o.stb),
]
# ROI rtio
# 4 cfg (x0, y0, x1, y1) per roi_engine
self.config = rtlink.Interface(rtlink.OInterface(res_width, bits_for(4*roi_engine_count-1)))
# select which roi engine can output rtio_input signal
self.gate_data = rtlink.Interface(
rtlink.OInterface(roi_engine_count),
# the 32th bits is for sentinel (gate detection)
rtlink.IInterface(count_width+1, timestamped=False)
)
self.crc_error_cnt = CSRStatus(16)
self.crc_error_reset = CSR()
# DEBUG: csr
self.arbiter_active_ch = CSRStatus(n_channels)
self.roi_counter = CSRStatus(count_width)
self.roi_update = CSR()
self.pix_y = CSRStatus(res_width)
self.header_l_size = CSRStatus(3*char_width)
self.header_x_size = CSRStatus(3*char_width)
self.header_y_size = CSRStatus(3*char_width)
self.header_new_line = CSRStatus(3*char_width)
# # #
cdr = ClockDomainsRenamer("cxp_gtx_rx")
debug_out = False
if not debug_out:
self.submodules.pixel_pipeline = pixel_pipeline = cdr(Pixel_Pipeline(res_width, count_width, packet_size))
# CRC error counter
self.submodules.crc_reset_ps = crc_reset_ps = PulseSynchronizer("sys", "cxp_gtx_rx")
self.sync += crc_reset_ps.i.eq(self.crc_error_reset.re)
crc_error_r = Signal()
crc_error_cnt_rx = Signal.like(self.crc_error_cnt.status)
self.sync.cxp_gtx_rx += [
# to improve timinig
crc_error_r.eq(pixel_pipeline.crc_checker.error),
If(crc_error_r,
crc_error_cnt_rx.eq(crc_error_cnt_rx + 1),
).Elif(crc_reset_ps.o,
crc_error_cnt_rx.eq(crc_error_cnt_rx + 1),
),
]
self.specials += MultiReg(crc_error_cnt_rx, self.crc_error_cnt.status)
# RTIO interface
n = 0
cfg = pixel_pipeline.roi.cfg
for offset, target in enumerate([cfg.x0, cfg.y0, cfg.x1, cfg.y1]):
roi_boundary = Signal.like(target)
self.sync.rio += If(self.config.o.stb & (self.config.o.address == 4*n+offset),
roi_boundary.eq(self.config.o.data))
self.specials += MultiReg(roi_boundary, target, "cxp_gtx_rx")
roi_out = pixel_pipeline.roi.out
update = Signal()
self.submodules.ps = ps = PulseSynchronizer("cxp_gtx_rx", "sys")
self.sync.cxp_gtx_rx += ps.i.eq(roi_out.update)
self.sync += update.eq(ps.o)
sentinel = 2**count_width
count_sys = Signal.like(roi_out.count)
self.specials += MultiReg(roi_out.count, count_sys),
self.sync.rio += [
# TODO: add gating
self.gate_data.i.stb.eq(update),
# without the slice, unspecified bits will be 1 for some reason
# i.e. data[count_wdith:] = 0b111111... when using data.eq(count_sys)
self.gate_data.i.data[:count_width].eq(count_sys),
]
# TODO: fix count_sys seems like end of frame is broken
# BUG: it maybe related to the KCode bug that only happens after frameheader decoder COPY (i.e. when sending pixel data)
# DEBUG:
new_line_cnt_rx, new_line_cnt_sys = Signal(3*char_width), Signal(3*char_width)
l_size_rx, l_size_sys = Signal(3*char_width), Signal(3*char_width)
x_size_rx, x_size_sys = Signal(3*char_width), Signal(3*char_width)
y_size_rx, y_size_sys = Signal(3*char_width), Signal(3*char_width)
y_pix_rx, y_pix_sys = Signal(res_width), Signal(res_width)
self.sync.cxp_gtx_rx += [
If(pixel_pipeline.header_decoder.new_line,
new_line_cnt_rx.eq(new_line_cnt_rx + 1),
),
l_size_rx.eq(pixel_pipeline.header_decoder.metadata.l_size),
x_size_rx.eq(pixel_pipeline.header_decoder.metadata.x_size),
y_size_rx.eq(pixel_pipeline.header_decoder.metadata.y_size),
y_pix_rx.eq(pixel_pipeline.parser.pixel4x[0].y),
]
self.specials += [
MultiReg(new_line_cnt_rx, new_line_cnt_sys),
MultiReg(l_size_rx, l_size_sys),
MultiReg(x_size_rx, x_size_sys),
MultiReg(y_size_rx, y_size_sys),
MultiReg(y_pix_rx, y_pix_sys),
]
self.sync += [
self.header_new_line.status.eq(new_line_cnt_sys),
self.pix_y.status.eq(y_pix_sys),
self.header_l_size.status.eq(l_size_sys),
self.header_x_size.status.eq(x_size_sys),
self.header_y_size.status.eq(y_size_sys),
self.roi_counter.status.eq(count_sys),
If(update,
self.roi_update.w.eq(1),
).Elif(self.roi_update.re,
self.roi_update.w.eq(0),
),
]
else:
# DEBUG:
crc_checker = cdr(CXPCRC32_Checker())
# TODO: handle full buffer gracefully
# TODO: investigate why there is a heartbeat message in the middle of the frame with k27.7 code too???
# NOTE: sometimes there are 0xFBFBFBFB K=0b1111
# perhaps the buffer is full overflowing and doing strange stuff
# it should be mem block not "cycle buffer"
# self.submodules.dropper = dropper = cdr(DChar_Dropper())
buffer = cdr(Buffer(word_layout_dchar)) # crcchecker timinig is bad
buffer_cdc_fifo = cdr(Buffer(word_layout_dchar)) # to improve timing
cdc_fifo = stream.AsyncFIFO(word_layout_dchar, 2**log2_int(packet_size//word_width))
self.submodules += buffer, crc_checker, buffer_cdc_fifo
self.submodules += ClockDomainsRenamer({"write": "cxp_gtx_rx", "read": "sys"})(cdc_fifo)
self.submodules.debug_out = debug_out = RX_Debug_Buffer(word_layout_dchar, 2**log2_int(packet_size//word_width))
pipeline = [buffer, crc_checker, buffer_cdc_fifo, cdc_fifo, debug_out]
for s, d in zip(pipeline, pipeline[1:]):
self.comb += s.source.connect(d.sink)
#
# +---------+ +-------------+
# downconn pipline ----->| | | |------> crc checker ------> raw stream data
# | arbiter |---->| broadcaster |
# downconn pipline ----->| | | |------> crc checker ------> raw stream data
# +---------+ +-------------+
#
self.submodules.arbiter = arbiter = cdr(Stream_Arbiter(n_channels))
self.submodules.broadcaster = broadcaster = cdr(Stream_Broadcaster())
# Connect pipeline
for i, p in enumerate(pipelines):
# Assume downconns pipeline already marks the eop
self.comb += p.rx.source.connect(arbiter.sinks[i])
self.comb += arbiter.source.connect(broadcaster.sink)
if not debug_out:
self.comb += broadcaster.sources[0].connect(pixel_pipeline.sink),
else:
self.comb += broadcaster.sources[0].connect(pipeline[0].sink),
# Control interface
# only the simple topology MASTER:ch0, extension:ch1,2,3 is supported right now
active_channels_sys = Signal(n_channels)
for i, p in enumerate(pipelines):
# TODO: change this to non csr signal?
self.sync += active_channels_sys[i].eq(p.rx.ready.status)
self.specials += MultiReg(active_channels_sys, arbiter.active_channels, odomain="cxp_gtx_rx"),
# DEBUG:
self.sync += self.arbiter_active_ch.status.eq(active_channels_sys)
for i, p in enumerate(pipelines):
# self.comb += p.rx.source.ack.eq(1)
rx_stb = Signal()
self.sync.cxp_gtx_rx += rx_stb.eq(p.rx.source.stb)
self.specials += [
Instance("OBUF", i_I=rx_stb, o_O=pmod_pads[i]),
# Instance("OBUF", i_I=arbiter.sinks[i].stb, o_O=pmod_pads[i]),
]

View File

@ -0,0 +1,87 @@
from migen.build.generic_platform import *
fmc_adapter_io = [
# CoaXPress high speed link
("CXP_HS", 0,
Subsignal("txp", Pins("HPC:DP0_C2M_P")),
Subsignal("txn", Pins("HPC:DP0_C2M_N")),
Subsignal("rxp", Pins("HPC:DP0_M2C_P")),
Subsignal("rxn", Pins("HPC:DP0_M2C_N")),
),
("CXP_HS", 1,
Subsignal("txp", Pins("HPC:DP1_C2M_P")),
Subsignal("txn", Pins("HPC:DP1_C2M_N")),
Subsignal("rxp", Pins("HPC:DP1_M2C_P")),
Subsignal("rxn", Pins("HPC:DP1_M2C_N")),
),
("CXP_HS", 2,
Subsignal("txp", Pins("HPC:DP2_C2M_P")),
Subsignal("txn", Pins("HPC:DP2_C2M_N")),
Subsignal("rxp", Pins("HPC:DP2_M2C_P")),
Subsignal("rxn", Pins("HPC:DP2_M2C_N")),
),
("CXP_HS", 3,
Subsignal("txp", Pins("HPC:DP3_C2M_P")),
Subsignal("txn", Pins("HPC:DP3_C2M_N")),
Subsignal("rxp", Pins("HPC:DP3_M2C_P")),
Subsignal("rxn", Pins("HPC:DP3_M2C_N")),
),
# CoaXPress low speed link
("CXP_LS", 0, Pins("HPC:LA00_CC_P"), IOStandard("LVCMOS33")),
("CXP_LS", 1, Pins("HPC:LA01_CC_N"), IOStandard("LVCMOS33")),
("CXP_LS", 2, Pins("HPC:LA01_CC_P"), IOStandard("LVCMOS33")),
("CXP_LS", 3, Pins("HPC:LA02_N"), IOStandard("LVCMOS33")),
# CoaXPress green and red LED
("CXP_LED", 0,
Subsignal("green", Pins("HPC:LA11_P"), IOStandard("LVCMOS33")),
Subsignal("red", Pins("HPC:LA11_N"), IOStandard("LVCMOS33")),
),
("CXP_LED", 1,
Subsignal("green", Pins("HPC:LA12_P"), IOStandard("LVCMOS33")),
Subsignal("red", Pins("HPC:LA12_N"), IOStandard("LVCMOS33")),
),
("CXP_LED", 2,
Subsignal("green", Pins("HPC:LA13_P"), IOStandard("LVCMOS33")),
Subsignal("red", Pins("HPC:LA13_N"), IOStandard("LVCMOS33")),
),
("CXP_LED", 3,
Subsignal("green", Pins("HPC:LA14_P"), IOStandard("LVCMOS33")),
Subsignal("red", Pins("HPC:LA14_N"), IOStandard("LVCMOS33")),
),
# Power over CoaXPress
("PoCXP", 0,
Subsignal("enable", Pins("HPC:LA21_N"), IOStandard("LVCMOS33")),
Subsignal("alert", Pins("HPC:LA18_CC_P"), IOStandard("LVCMOS33")),
),
("PoCXP", 1,
Subsignal("enable", Pins("HPC:LA21_P"), IOStandard("LVCMOS33")),
Subsignal("alert", Pins("HPC:LA19_N"), IOStandard("LVCMOS33")),
),
("PoCXP", 2,
Subsignal("enable", Pins("HPC:LA22_N"), IOStandard("LVCMOS33")),
Subsignal("alert", Pins("HPC:LA19_P"), IOStandard("LVCMOS33")),
),
("PoCXP", 3,
Subsignal("enable", Pins("HPC:LA22_P"), IOStandard("LVCMOS33")),
Subsignal("alert", Pins("HPC:LA20_N"), IOStandard("LVCMOS33")),
),
("i2c_fmc", 0,
Subsignal("scl", Pins("HPC:IIC_SCL")),
Subsignal("sda", Pins("HPC:IIC_SDA")),
IOStandard("LVCMOS33")
),
("3V3", 0, Pins("HPC:PG_M2C")),
("GND", 0, Pins("HPC:PRSNT_M2C_L HPC:CLK0_M2C_P")),
("VADJ", 0, Pins("HPC:GBTCLK1_M2C_N HPC:CLK0_M2C_N")),
("clk125_fmc", 0,
Subsignal("p", Pins("HPC:GBTCLK0_M2C_P")),
Subsignal("n", Pins("HPC:GBTCLK0_M2C_N")),
),
]

View File

@ -0,0 +1,662 @@
from migen import *
from migen.genlib.cdc import MultiReg
from migen.genlib.resetsync import AsyncResetSynchronizer
from misoc.cores.code_8b10b import Encoder, Decoder
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from artiq.gateware.drtio.transceiver.gtx_7series_init import *
from cxp_pipeline import word_layout
from functools import reduce
from operator import add
class CXP_RXPHYs(Module, AutoCSR):
def __init__(self, refclk, pads, sys_clk_freq, master):
self.qpll_reset = CSR()
self.qpll_locked = CSRStatus()
self.gtx_start_init = CSRStorage()
self.gtx_restart = CSR()
self.phys = []
# # #
# For speed higher than 6.6Gbps, QPLL need to be used instead of CPLL - DS191 (v1.18.1) Table 9.1
self.submodules.qpll = qpll = QPLL(refclk, sys_clk_freq)
self.sync += [
qpll.reset.eq(self.qpll_reset.re),
self.qpll_locked.status.eq(qpll.lock),
]
self.submodules.rx_resetter = rx_resetter = RX_Resetter()
for i, pad in enumerate(pads):
if len(pads) == 1:
rx_mode = "single"
else:
rx_mode = "master" if i == master else "slave"
rx = Receiver(qpll, pad, sys_clk_freq, rx_mode)
self.phys.append(rx)
setattr(self.submodules, "rx"+str(i), rx)
for i, phy in enumerate(self.phys):
if i == master:
self.comb += rx_resetter.rx_ready.eq(phy.gtx.rx_ready)
self.comb += [
phy.gtx.rx_manual_restart.eq(self.gtx_restart.re | rx_resetter.rx_reset),
phy.gtx.rx_init.clk_path_ready.eq(self.gtx_start_init.storage),
]
# master rx_init will lock up when slaves_phaligndone signal is not connected
self.submodules.rx_phase_alignment = GTXInitPhaseAlignment([rx_phy.gtx.rx_init for rx_phy in self.phys])
class Receiver(Module):
def __init__(self, qpll, pad, sys_clk_freq, rx_mode):
self.submodules.gtx = gtx = GTX(qpll, pad, sys_clk_freq, None, rx_mode)
self.source = stream.Endpoint(word_layout)
data_valid = Signal()
self.sync.cxp_gtx_rx += [
data_valid.eq(gtx.comma_aligner.rxfsm.ongoing("READY")),
self.source.stb.eq(0),
If(data_valid & ~((gtx.decoders[0].d == 0xBC) & (gtx.decoders[0].k == 1)),
self.source.stb.eq(1),
self.source.data.eq(Cat(gtx.decoders[i].d for i in range(4))),
self.source.k.eq(Cat(gtx.decoders[i].k for i in range(4))),
)
]
class QPLL(Module, AutoCSR):
def __init__(self, refclk, sys_clk_freq):
self.clk = Signal()
self.refclk = Signal()
self.lock = Signal()
self.reset = Signal()
self.daddr = CSRStorage(8)
self.dread = CSR()
self.din_stb = CSR()
self.din = CSRStorage(16)
self.dout = CSRStatus(16)
self.dready = CSR()
# # #
# VCO @ 10GHz, linerate = 1.25Gbps
# feedback divider = 80
qpll_fbdiv = 0b0100100000
qpll_fbdiv_ratio = 1
refclk_div = 1
self.Xxout_div = 8
# used for txuserclk pll
fbdiv_real = 80
self.tx_usrclk_freq = (sys_clk_freq*fbdiv_real/self.Xxout_div)/40
dready = Signal()
self.specials += [
Instance("GTXE2_COMMON",
i_QPLLREFCLKSEL=0b001,
i_GTREFCLK0=refclk,
i_QPLLPD=0,
i_QPLLRESET=self.reset,
i_QPLLLOCKEN=1,
o_QPLLLOCK=self.lock,
o_QPLLOUTCLK=self.clk,
o_QPLLOUTREFCLK=self.refclk,
# See UG476 (v1.12.1) Table 2-16
p_QPLL_FBDIV=qpll_fbdiv,
p_QPLL_FBDIV_RATIO=qpll_fbdiv_ratio,
p_QPLL_REFCLK_DIV=refclk_div,
# From 7 Series FPGAs Transceivers Wizard
p_BIAS_CFG=0x0000040000001000,
p_COMMON_CFG=0x00000000,
p_QPLL_CFG=0x0680181,
p_QPLL_CLKOUT_CFG=0b0000,
p_QPLL_COARSE_FREQ_OVRD=0b010000,
p_QPLL_COARSE_FREQ_OVRD_EN=0b0,
p_QPLL_CP=0b0000011111,
p_QPLL_CP_MONITOR_EN=0b0,
p_QPLL_DMONITOR_SEL=0b0,
p_QPLL_FBDIV_MONITOR_EN= 0b0,
p_QPLL_INIT_CFG=0x000006,
p_QPLL_LOCK_CFG=0x21E8,
p_QPLL_LPF=0b1111,
# Reserved, values cannot be modified
i_BGBYPASSB=0b1,
i_BGMONITORENB=0b1,
i_BGPDB=0b1,
i_BGRCALOVRD=0b11111,
i_RCALENB=0b1,
i_QPLLRSVD1=0b0,
i_QPLLRSVD2=0b11111,
# Dynamic Reconfiguration Ports
i_DRPADDR=self.daddr.storage,
i_DRPCLK=ClockSignal("sys"),
i_DRPEN=(self.dread.re | self.din_stb.re),
i_DRPWE=self.din_stb.re,
i_DRPDI=self.din.storage,
o_DRPDO=self.dout.status,
o_DRPRDY=dready,
)
]
self.sync += [
If(dready,
self.dready.w.eq(1),
),
If(self.dready.re,
self.dready.w.eq(0),
),
]
class RX_Resetter(Module):
def __init__(self, reset_period=10_000_000):
self.rx_ready = Signal()
self.rx_reset = Signal()
# # #
# periodically reset rx until rx is connected and receiving valid data
# as after connecting RXP/RXN, the whole RX need to be reset
reset_counter = Signal(reset=reset_period-1, max=reset_period)
self.sync += [
self.rx_reset.eq(0),
If(~self.rx_ready,
If(reset_counter == 0,
reset_counter.eq(reset_counter.reset),
self.rx_reset.eq(1),
).Else(
reset_counter.eq(reset_counter - 1),
)
)
]
# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped
# compared to the usual 8b10b binary representation.
class Comma_Aligner(Module):
def __init__(self, comma, reset_period=10_000_000):
self.data = Signal(20)
self.comma_aligned = Signal()
self.comma_realigned = Signal()
self.comma_det = Signal()
self.aligner_en = Signal()
self.ready_sys = Signal()
# # #
# Data and comma checker
# From UG476 (v1.12.1) p.228
# The built-in RXBYTEISALIGNED can be falsely asserted at linerate higher than 5Gbps
# The validity of data and comma needed to be checked externally
comma_n = ~comma & 0b1111111111
comma_seen = Signal()
error_seen = Signal()
one_counts = Signal(max=11)
# From CXP-001-2021 section 9.2.5.1
# For high speed connection an IDLE word shall be transmitted at least once every 100 words
counter_period = 200
counter = Signal(reset=counter_period-1, max=counter_period)
check_reset = Signal()
check = Signal()
self.sync.cxp_gtx_rx += [
If(check_reset,
counter.eq(counter.reset),
check.eq(0),
).Elif(counter == 0,
check.eq(1),
).Else(
counter.eq(counter - 1),
),
If(check_reset,
comma_seen.eq(0),
).Elif((self.data[:10] == comma) | (self.data[:10] == comma_n),
comma_seen.eq(1)
),
one_counts.eq(reduce(add, [self.data[i] for i in range(10)])),
If(check_reset,
error_seen.eq(0),
).Elif((one_counts != 4) & (one_counts != 5) & (one_counts != 6),
error_seen.eq(1),
),
]
self.submodules.rxfsm = rxfsm = ClockDomainsRenamer("cxp_gtx_rx")(FSM(reset_state="WAIT_COMMA"))
rxfsm.act("WAIT_COMMA",
If(self.comma_det,
NextState("ALIGNING"),
)
)
rxfsm.act("ALIGNING",
If(self.comma_aligned & (~self.comma_realigned),
NextState("WAIT_ALIGNED_DATA"),
).Else(
self.aligner_en.eq(1),
)
)
# wait for the aligned data to arrive at the FPGA RX interface
# as there is a delay before the data is avaiable after RXBYTEISALIGNED is asserted
self.submodules.timer = timer = ClockDomainsRenamer("cxp_gtx_rx")(WaitTimer(10_000))
rxfsm.act("WAIT_ALIGNED_DATA",
timer.wait.eq(1),
If(timer.done,
check_reset.eq(1),
NextState("CHECKING"),
)
)
rxfsm.act("CHECKING",
If(check,
check_reset.eq(1),
If(comma_seen & (~error_seen),
NextState("READY"),
).Else(
NextState("WAIT_COMMA")
)
)
)
ready = Signal()
self.specials += MultiReg(ready, self.ready_sys)
rxfsm.act("READY",
ready.eq(1),
If(check,
check_reset.eq(1),
If(~(comma_seen & (~error_seen)),
NextState("WAIT_COMMA"),
)
)
)
class GTX(Module):
"""
A linerate reconfigurable 40bit width GTX with QPLL
Designed for 12.5, 10, 6.25, 5, 3.125, 2.5, 1.25Gpbs
To change the linerate:
1) Change the QPLL VCO frequency
- 12.5, 6.25, 3.125Gbps: VCO @ 12.5GHz,
- 10, 6.25, 5, 2.5, 1.25Gbps: VCO @ 10GHz
2) Update the xXOUT_DIV and TXUSRCLK frequency if using tx
3) Reset the entire rx and tx
"""
def __init__(self, qpll, pads, sys_clk_freq, tx_mode="single", rx_mode="single"):
assert tx_mode in ["single", "master", "slave", None]
assert rx_mode in ["single", "master", "slave", None]
self.tx_restart = Signal()
self.rx_manual_restart = Signal()
self.loopback_mode = Signal(3)
self.txenable = Signal()
self.rx_ready = Signal()
# Dynamic Reconfiguration Ports
self.daddr = Signal(9)
self.dclk = Signal()
self.den = Signal()
self.dwen = Signal()
self.din = Signal(16)
self.dout = Signal(16)
self.dready = Signal()
# transceiver direct clock outputs
# useful to specify clock constraints in a way palatable to Vivado
self.txoutclk = Signal()
self.rxoutclk = Signal()
# # #
txdata = Signal(40)
rxdata = Signal(40)
if tx_mode:
self.submodules.tx_init = tx_init = GTXInit(sys_clk_freq, False, mode=tx_mode)
self.comb += tx_init.cplllock.eq(qpll.lock),
self.submodules.encoder = ClockDomainsRenamer("cxp_gtx_tx")(Encoder(4, True))
self.comb += txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1], self.encoder.output[2], self.encoder.output[3])),
if rx_mode:
self.submodules.rx_init = rx_init = GTXInit(sys_clk_freq, True, mode=rx_mode)
self.comb += rx_init.cplllock.eq(qpll.lock)
self.submodules.decoders = [ClockDomainsRenamer("cxp_gtx_rx")(
(Decoder(True))) for _ in range(4)]
self.comb += [
self.decoders[0].input.eq(rxdata[:10]),
self.decoders[1].input.eq(rxdata[10:20]),
self.decoders[2].input.eq(rxdata[20:30]),
self.decoders[3].input.eq(rxdata[30:]),
]
comma_aligned = Signal()
comma_realigned = Signal()
comma_det = Signal()
comma_aligner_en = Signal()
# Note: the following parameters were set after consulting AR45360
self.specials += \
Instance("GTXE2_CHANNEL",
# PMA Attributes
p_PMA_RSV=0x001E7080,
p_PMA_RSV2=0x2050, # PMA_RSV2[5] = 0: Eye scan feature disabled
p_PMA_RSV3=0,
p_PMA_RSV4=1, # PMA_RSV[4],RX_CM_TRIM[2:0] = 0b1010: Common mode 800mV
p_RX_BIAS_CFG=0b000000000100,
p_RX_OS_CFG=0b0000010000000,
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,
i_CPLLPD=1,
# QPLL
i_QPLLCLK=qpll.clk,
i_QPLLREFCLK=qpll.refclk,
p_RXOUT_DIV=qpll.Xxout_div,
p_TXOUT_DIV=qpll.Xxout_div,
i_RXSYSCLKSEL=0b11, # use QPLL & QPLL's REFCLK
i_TXSYSCLKSEL=0b11, # use QPLL & CPLL's 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_TXPHDLYRESET=0,
i_TXDLYBYPASS=0,
i_TXPHALIGNEN=1 if tx_mode in ["master", "slave"] else 0,
i_GTTXRESET=tx_init.gtXxreset if tx_mode else 0,
o_TXRESETDONE=tx_init.Xxresetdone if tx_mode else 0,
i_TXDLYSRESET=tx_init.Xxdlysreset if tx_mode else 0,
o_TXDLYSRESETDONE=tx_init.Xxdlysresetdone if tx_mode else 0,
i_TXPHINIT=tx_init.txphinit if tx_mode in ["master", "slave"] else 0,
o_TXPHINITDONE=tx_init.txphinitdone if tx_mode in ["master", "slave"] else Signal(),
i_TXPHALIGN=tx_init.Xxphalign if tx_mode in ["master", "slave"] else 0,
i_TXDLYEN=tx_init.Xxdlyen if tx_mode in ["master", "slave"] else 0,
o_TXPHALIGNDONE=tx_init.Xxphaligndone if tx_mode else 0,
i_TXUSERRDY=tx_init.Xxuserrdy if tx_mode else 0,
p_TXPMARESET_TIME=1,
p_TXPCSRESET_TIME=1,
i_TXINHIBIT=~self.txenable,
# TX data
p_TX_DATA_WIDTH=40,
p_TX_INT_DATAWIDTH=1, # 1 if a line rate is greater than 6.6 Gbps
i_TXCHARDISPMODE=Cat(txdata[9], txdata[19], txdata[29], txdata[39]),
i_TXCHARDISPVAL=Cat(txdata[8], txdata[18], txdata[28], txdata[38]),
i_TXDATA=Cat(txdata[:8], txdata[10:18], txdata[20:28], txdata[30:38]),
i_TXUSRCLK=ClockSignal("cxp_gtx_tx") if tx_mode else 0,
i_TXUSRCLK2=ClockSignal("cxp_gtx_tx") if tx_mode else 0,
# TX electrical
i_TXBUFDIFFCTRL=0b100,
i_TXDIFFCTRL=0b1000,
# RX Startup/Reset
i_RXPHDLYRESET=0,
i_RXDLYBYPASS=0,
i_RXPHALIGNEN=1 if rx_mode in ["master", "slave"] else 0,
i_GTRXRESET=rx_init.gtXxreset if rx_mode else 0,
o_RXRESETDONE=rx_init.Xxresetdone if rx_mode else 0,
i_RXDLYSRESET=rx_init.Xxdlysreset if rx_mode else 0,
o_RXDLYSRESETDONE=rx_init.Xxdlysresetdone if rx_mode else 0,
i_RXPHALIGN=rx_init.Xxphalign if rx_mode in ["master", "slave"] else 0,
i_RXDLYEN=rx_init.Xxdlyen if rx_mode in ["master", "slave"] else 0,
o_RXPHALIGNDONE=rx_init.Xxphaligndone if rx_mode else 0,
i_RXUSERRDY=rx_init.Xxuserrdy if rx_mode else 0,
p_RXPMARESET_TIME=1,
p_RXPCSRESET_TIME=1,
# RX AFE
p_RX_DFE_XYD_CFG=0,
p_RX_CM_SEL=0b11, # RX_CM_SEL = 0b11: Common mode is programmable
p_RX_CM_TRIM=0b010, # PMA_RSV[4],RX_CM_TRIM[2:0] = 0b1010: Common mode 800mV
i_RXDFEXYDEN=1,
i_RXDFEXYDHOLD=0,
i_RXDFEXYDOVRDEN=0,
i_RXLPMEN=1, # RXLPMEN = 1: LPM mode is enable for non scramble 8b10b data
p_RXLPM_HF_CFG=0b00000011110000,
p_RXLPM_LF_CFG=0b00000011110000,
p_RX_DFE_GAIN_CFG=0x0207EA,
p_RX_DFE_VP_CFG=0b00011111100000011,
p_RX_DFE_UT_CFG=0b10001000000000000,
p_RX_DFE_KL_CFG=0b0000011111110,
p_RX_DFE_KL_CFG2=0x3788140A,
p_RX_DFE_H2_CFG=0b000110000000,
p_RX_DFE_H3_CFG=0b000110000000,
p_RX_DFE_H4_CFG=0b00011100000,
p_RX_DFE_H5_CFG=0b00011100000,
p_RX_DFE_LPM_CFG=0x0904, # RX_DFE_LPM_CFG = 0x0904: linerate <= 6.6Gb/s
# = 0x0104: linerate > 6.6Gb/s
# RX clock
i_RXDDIEN=1,
# i_RXSYSCLKSEL=0b00,
i_RXOUTCLKSEL=0b010,
o_RXOUTCLK=self.rxoutclk,
i_RXUSRCLK=ClockSignal("cxp_gtx_rx") if rx_mode else 0,
i_RXUSRCLK2=ClockSignal("cxp_gtx_rx") if rx_mode else 0,
# 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=40,
p_RX_INT_DATAWIDTH=1, # 1 if a line rate is greater than 6.6 Gbps
o_RXDISPERR=Cat(rxdata[9], rxdata[19], rxdata[29], rxdata[39]),
o_RXCHARISK=Cat(rxdata[8], rxdata[18], rxdata[28], rxdata[38]),
o_RXDATA=Cat(rxdata[:8], rxdata[10:18], rxdata[20:28], rxdata[30:38]),
# RX Byte and Word Alignment Attributes
p_ALIGN_COMMA_DOUBLE="FALSE",
p_ALIGN_COMMA_ENABLE=0b1111111111,
p_ALIGN_COMMA_WORD=4, # align comma to rxdata[:10] only
p_ALIGN_MCOMMA_DET="TRUE",
p_ALIGN_MCOMMA_VALUE=0b1010000011,
p_ALIGN_PCOMMA_DET="TRUE",
p_ALIGN_PCOMMA_VALUE=0b0101111100,
p_SHOW_REALIGN_COMMA="FALSE",
p_RXSLIDE_AUTO_WAIT=7,
p_RXSLIDE_MODE="OFF",
p_RX_SIG_VALID_DLY=10,
i_RXPCOMMAALIGNEN=comma_aligner_en,
i_RXMCOMMAALIGNEN=comma_aligner_en,
i_RXCOMMADETEN=1,
i_RXSLIDE=0,
o_RXBYTEISALIGNED=comma_aligned,
o_RXBYTEREALIGN=comma_realigned,
o_RXCOMMADET=comma_det,
# RX 8B/10B Decoder Attributes
p_RX_DISPERR_SEQ_MATCH="FALSE",
p_DEC_MCOMMA_DETECT="TRUE",
p_DEC_PCOMMA_DETECT="TRUE",
p_DEC_VALID_COMMA_ONLY="FALSE",
# RX Buffer Attributes
p_RXBUF_ADDR_MODE="FAST",
p_RXBUF_EIDLE_HI_CNT=0b1000,
p_RXBUF_EIDLE_LO_CNT=0b0000,
p_RXBUF_EN="FALSE",
p_RX_BUFFER_CFG=0b000000,
p_RXBUF_RESET_ON_CB_CHANGE="TRUE",
p_RXBUF_RESET_ON_COMMAALIGN="FALSE",
p_RXBUF_RESET_ON_EIDLE="FALSE", # RXBUF_RESET_ON_EIDLE = FALSE: OOB is disabled
p_RXBUF_RESET_ON_RATE_CHANGE="TRUE",
p_RXBUFRESET_TIME=0b00001,
p_RXBUF_THRESH_OVFLW=61,
p_RXBUF_THRESH_OVRD="FALSE",
p_RXBUF_THRESH_UNDFLW=4,
p_RXDLY_CFG=0x001F,
p_RXDLY_LCFG=0x030,
p_RXDLY_TAP_CFG=0x0000,
p_RXPH_CFG=0xC00002,
p_RXPHDLY_CFG=0x084020,
p_RXPH_MONITOR_SEL=0b00000,
p_RX_XCLK_SEL="RXUSR",
p_RX_DDI_SEL=0b000000,
p_RX_DEFER_RESET_BUF_EN="TRUE",
# CDR Attributes
p_RXCDR_CFG=0x03_0000_23FF_1008_0020, # LPM @ 0.5G-1.5625G , 8B/10B encoded data, CDR setting < +/- 200ppm
# (See UG476 (v1.12.1), p.206)
p_RXCDR_FR_RESET_ON_EIDLE=0b0,
p_RXCDR_HOLD_DURING_EIDLE=0b0,
p_RXCDR_PH_RESET_ON_EIDLE=0b0,
p_RXCDR_LOCK_CFG=0b010101,
# Pads
i_GTXRXP=pads.rxp,
i_GTXRXN=pads.rxn,
o_GTXTXP=pads.txp,
o_GTXTXN=pads.txn,
# Dynamic Reconfiguration Ports
p_IS_DRPCLK_INVERTED=0b0,
i_DRPADDR=self.daddr,
i_DRPCLK=self.dclk,
i_DRPEN=self.den,
i_DRPWE=self.dwen,
i_DRPDI=self.din,
o_DRPDO=self.dout,
o_DRPRDY=self.dready,
# Nearend Loopback
i_LOOPBACK = self.loopback_mode,
p_TX_LOOPBACK_DRIVE_HIZ = "FALSE",
p_RXPRBS_ERR_LOOPBACK = 0b0,
# Other parameters
p_PCS_RSVD_ATTR=(
(tx_mode != "single") << 1 | # PCS_RSVD_ATTR[1] = 0: TX Single Lane Auto Mode
# = 1: TX Manual Mode
(rx_mode != "single") << 2 | # [2] = 0: RX Single Lane Auto Mode
# = 1: RX Manual Mode
0 << 8 # [8] = 0: OOB is disabled
),
i_RXELECIDLEMODE=0b11, # RXELECIDLEMODE = 0b11: OOB is disabled
p_RX_DFE_LPM_HOLD_DURING_EIDLE=0b0,
p_ES_EYE_SCAN_EN="TRUE", # Must be TRUE for GTX
)
# TX clocking
# When bypassing the TX buffer and changing frequency of VCO of QPLL/CPLL,
# TXUSRCLK rate will always be the refclk rate.
# To match the required TXUSRCLK rate = linerate/datewidth (UG476 (v1.12.1) Equation 3-1), a DRP PLL is used.
# Slave TX will use cxp_gtx_tx from master
if tx_mode == "single" or tx_mode == "master":
self.clock_domains.cd_cxp_gtx_tx = ClockDomain()
txpll_fb_clk = Signal()
txoutclk_buf = Signal()
txpll_clkout = Signal()
self.txpll_reset = Signal()
self.pll_daddr = Signal(7)
self.pll_dclk = Signal()
self.pll_den = Signal()
self.pll_din = Signal(16)
self.pll_dwen = Signal()
self.txpll_locked = Signal()
self.pll_dout = Signal(16)
self.pll_dready = Signal()
pll_fbout_mult = 8
txusr_pll_div = pll_fbout_mult*sys_clk_freq/qpll.tx_usrclk_freq
self.specials += [
Instance("PLLE2_ADV",
p_BANDWIDTH="HIGH",
o_LOCKED=self.txpll_locked,
i_RST=self.txpll_reset,
p_CLKIN1_PERIOD=1e9/sys_clk_freq, # ns
i_CLKIN1=txoutclk_buf,
# VCO @ 1.25GHz
p_CLKFBOUT_MULT=pll_fbout_mult, p_DIVCLK_DIVIDE=1,
i_CLKFBIN=txpll_fb_clk, o_CLKFBOUT=txpll_fb_clk,
# frequency = linerate/40
p_CLKOUT0_DIVIDE=txusr_pll_div, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=txpll_clkout,
# Dynamic Reconfiguration Ports
i_DADDR = self.pll_daddr,
i_DCLK = self.pll_dclk,
i_DEN = self.pll_den,
i_DI = self.pll_din,
i_DWE = self.pll_dwen,
o_DO = self.pll_dout,
o_DRDY = self.pll_dready,
),
Instance("BUFG", i_I=self.txoutclk, o_O=txoutclk_buf),
Instance("BUFG", i_I=txpll_clkout, o_O=self.cd_cxp_gtx_tx.clk),
AsyncResetSynchronizer(self.cd_cxp_gtx_tx, ~self.txpll_locked & ~tx_init.done)
]
self.comb += tx_init.restart.eq(self.tx_restart)
# RX clocking
# When frequency of VCO of QPLL/CPLL is changed, RXUSRCLK will match the required frequency
# RXUSRCLK rate = linerate/datewidth (UG476 (v1.12.1) Equation 4-2). And PLL is not needed.
# Slave RX will use cxp_gtx_rx from master
if rx_mode == "single" or rx_mode == "master":
self.clock_domains.cd_cxp_gtx_rx = ClockDomain()
self.specials += [
Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_cxp_gtx_rx.clk),
AsyncResetSynchronizer(self.cd_cxp_gtx_rx, ~rx_init.done)
]
if rx_mode:
self.comb += rx_init.restart.eq(self.rx_manual_restart)
self.submodules.comma_aligner = comma_aligner = Comma_Aligner(0b0101111100)
self.comb += [
comma_aligner.data.eq(rxdata),
comma_aligner.comma_aligned.eq(comma_aligned),
comma_aligner.comma_realigned.eq(comma_realigned),
comma_aligner.comma_det.eq(comma_det),
comma_aligner_en.eq(comma_aligner.aligner_en),
self.rx_ready.eq(comma_aligner.ready_sys),
]

View File

@ -0,0 +1,808 @@
from migen import *
from migen.genlib.coding import PriorityEncoder
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine
from cxp_pipeline import *
# from src.gateware.cxp_pipeline import * # for sim only
from types import SimpleNamespace
from math import lcm
from operator import or_, add
pixel_width = 16
pixel4x_layout = [
("data", pixel_width*4),
("valid", 4),
]
def switch_endianness(s):
assert len(s) % 8 == 0
char = [s[i*8:(i+1)*8] for i in range(len(s)//8)]
return Cat(char[::-1])
class End_Of_Line_Inserter(Module):
"""
Insert eop to indicate end of line
And drop the K codes and Duplicate char
"""
def __init__(self):
self.l_size = Signal(3*char_width)
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint([("data", word_width)]) # pixel data don't need k code
# # #
# TODO: there maybe a reset bug where cxp_gtx_rx is not reset but frame size is changed
# cnt will be reset to last l_size instead of the new l_size resulting in wrong eop tag
# NOTE: because the self.sink.stb is only active after new_frame, the cnt is changed after the new_frame is high
# Also, after transmitting the last word, cnt = 1, so cnt will update to the correct self.l_size regardless
cnt = Signal.like(self.l_size, reset=1)
self.sync += [
If((~self.source.stb | self.source.ack),
self.sink.connect(self.source, omit={"ack", "eop", "k", "dchar", "dchar_k"}),
If(self.sink.stb,
If(cnt == 1,
cnt.eq(self.l_size)
).Else(
cnt.eq(cnt - 1),
)
),
),
]
self.comb += [
self.sink.ack.eq(~self.source.stb | self.source.ack),
# repurpose eop as end of line
self.source.eop.eq(cnt == 1),
]
class Stream_Arbiter(Module):
def __init__(self, n_channels):
assert n_channels > 1 # don't need a arbiter if there is only one channel
self.active_channels = Signal(n_channels)
self.sinks = [stream.Endpoint(word_layout_dchar) for _ in range(n_channels)]
self.source = stream.Endpoint(word_layout_dchar)
# # #
self.submodules.fsm = fsm = FSM(reset_state="0")
# Section 9.5.5 (CXP-001-2021)
# When Multiple connections are active, stream packets are transmitted in
# ascending order of Connection ID
# Support ch0->1->2->4 topology only
for n, sink in enumerate(self.sinks):
if n < n_channels - 1:
fsm.act(str(n),
sink.connect(self.source),
If(sink.stb & sink.eop & self.source.ack,
If(self.active_channels[n+1],
NextState(str(n+1)),
). Else(
NextState(str(0)),
),
)
)
else:
fsm.act(str(n),
sink.connect(self.source),
If(sink.stb & sink.eop & self.source.ack,
NextState(str(0))
),
)
@ResetInserter()
@CEInserter()
class CXPCRC32(Module):
# Section 9.2.2.2 (CXP-001-2021)
width = 32
polynom = 0x04C11DB7
seed = 2**width - 1
check = 0x00000000
def __init__(self, data_width):
self.data = Signal(data_width)
self.value = Signal(self.width)
self.error = Signal()
# # #
self.submodules.engine = LiteEthMACCRCEngine(
data_width, self.width, self.polynom
)
reg = Signal(self.width, reset=self.seed)
self.sync += reg.eq(self.engine.next)
self.comb += [
self.engine.data.eq(self.data),
self.engine.last.eq(reg),
self.value.eq(reg[::-1]),
self.error.eq(reg != self.check),
]
class CXPCRC32_Checker(Module):
"""Verify crc in stream data packet"""
def __init__(self):
# TODO: handle the error into a counter
self.error = Signal()
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
# # #
self.submodules.crc = crc = CXPCRC32(word_width)
self.comb += crc.data.eq(self.sink.data),
self.submodules.fsm = fsm = FSM(reset_state="INIT")
fsm.act("INIT",
crc.reset.eq(1),
NextState("CHECKING"),
)
fsm.act("RESET",
crc.reset.eq(1),
self.error.eq(crc.error),
NextState("CHECKING"),
)
fsm.act("CHECKING",
If(self.sink.stb & self.sink.eop,
# discard the crc
self.sink.ack.eq(1),
NextState("RESET"),
).Else(
self.sink.connect(self.source),
),
crc.ce.eq(self.sink.stb),
)
class Stream_Broadcaster(Module):
def __init__(self, n_buffer=1, default_id=0):
assert n_buffer > 0
self.routing_ids = [Signal(char_width) for _ in range(1, n_buffer)]
self.sources = [stream.Endpoint(word_layout_dchar) for _ in range(n_buffer)]
self.sink = stream.Endpoint(word_layout_dchar)
# # #
routing_ids_r = [Signal(char_width) for _ in range(1, n_buffer)]
for i, id in enumerate(self.routing_ids):
self.sync += routing_ids_r[i].eq(id)
stream_id = Signal(char_width)
pak_tag = Signal(char_width)
stream_pak_size = Signal(char_width * 2)
self.submodules.fsm = fsm = FSM(reset_state="WAIT_HEADER")
fsm.act(
"WAIT_HEADER",
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(stream_id, self.sink.dchar),
NextState("GET_PAK_TAG"),
),
)
fsm.act(
"GET_PAK_TAG",
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(pak_tag, self.sink.dchar),
NextState("GET_PAK_SIZE_0"),
),
)
fsm.act(
"GET_PAK_SIZE_0",
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(stream_pak_size[8:], self.sink.dchar),
NextState("GET_PAK_SIZE_1"),
),
)
fsm.act(
"GET_PAK_SIZE_1",
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(stream_pak_size[:8], self.sink.dchar),
NextState("STORE_BUFFER"),
),
)
# routing decoder
sel = Signal(n_buffer)
no_match = Signal()
self.comb += sel[0].eq(stream_id == default_id)
for i, id in enumerate(routing_ids_r):
self.comb += sel[i+1].eq(stream_id == id)
# DEBUG: disrecard the stream id = 0 rule
# self.comb += source_sel[0].eq(self.stream_id == self.routing_table[0])
# ensure the lower source has priority when two or more bits of sel are high
self.submodules.coder = coder = PriorityEncoder(n_buffer)
sel_r = Signal.like(coder.o)
self.sync += [
coder.i.eq(sel),
sel_r.eq(coder.o),
no_match.eq(coder.n),
]
routing = dict((i, self.sink.connect(s))for i, s in enumerate(self.sources))
routing["default"] = self.sink.ack.eq(1) # discard if invalid
fsm.act(
"STORE_BUFFER",
If(no_match,
self.sink.ack.eq(1),
).Else(
Case(sel_r, routing),
),
# assume downstream is not blocked
If(self.sink.stb,
NextValue(stream_pak_size, stream_pak_size - 1),
If(stream_pak_size == 0,
NextValue(stream_id, stream_id.reset),
NextValue(pak_tag, pak_tag.reset),
NextValue(stream_pak_size, stream_pak_size.reset),
NextState("WAIT_HEADER"),
)
),
)
class Frame_Header_Decoder(Module):
def __init__(self):
self.decode_err = Signal()
self.new_frame = Signal()
self.new_line = Signal()
# Table 47 (CXP-001-2021)
n_metadata_chars = 23
img_header_layout = [
("stream_id", char_width),
("source_tag", 2*char_width),
("x_size", 3*char_width),
("x_offset", 3*char_width),
("y_size", 3*char_width),
("y_offset", 3*char_width),
("l_size", 3*char_width), # number of data words per image line
("pixel_format", 2*char_width),
("tap_geo", 2*char_width),
("flag", char_width),
]
assert layout_len(img_header_layout) == n_metadata_chars*char_width
# # #
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
self.sink.ack.eq(1),
If((self.sink.stb & (self.sink.dchar == KCode["stream_marker"]) & (self.sink.dchar_k == 1)),
NextState("DECODE"),
)
)
fsm.act("COPY",
# until for new line or new frame
If((self.sink.stb & (self.sink.dchar == KCode["stream_marker"]) & (self.sink.dchar_k == 1)),
self.sink.ack.eq(1),
NextState("DECODE"),
).Else(
self.sink.connect(self.source),
)
)
type = {
"new_frame": 0x01,
"line_break": 0x02,
}
cnt = Signal(max=n_metadata_chars)
fsm.act("DECODE",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(self.sink.dchar, {
type["new_frame"]: [
self.new_frame.eq(1),
NextValue(cnt, cnt.reset),
NextState("GET_FRAME_DATA"),
],
type["line_break"]: [
self.new_line.eq(1),
NextState("COPY"),
],
"default": [
self.decode_err.eq(1),
# discard all data until valid frame header
NextState("IDLE"),
],
}),
)
)
packet_buffer = Signal(layout_len(img_header_layout))
case = dict(
(i, NextValue(packet_buffer[8*i:8*(i+1)], self.sink.dchar))
for i in range(n_metadata_chars)
)
fsm.act("GET_FRAME_DATA",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(cnt, case),
If(cnt == n_metadata_chars - 1,
NextState("COPY"),
NextValue(cnt, cnt.reset),
).Else(
NextValue(cnt, cnt + 1),
),
),
)
# dissect packet
self.metadata = SimpleNamespace()
idx = 0
for name, size in img_header_layout:
# CXP use MSB even when sending duplicate chars
setattr(self.metadata, name, switch_endianness(packet_buffer[idx:idx+size]))
idx += size
class Pixel_Gearbox(Module):
"""Convert 32 bits word into 4x pixel"""
def __init__(self, size):
assert size <= pixel_width
assert size in [8, 10, 12, 14, 16]
self.x_size = Signal(3*char_width)
sink_dw, source_dw = word_width, size*4
self.sink = stream.Endpoint([("data", sink_dw)])
self.source = stream.Endpoint(pixel4x_layout)
# # #
ring_buf_size = lcm(sink_dw, source_dw)
# ensure the shift register is at least twice the size of sink/source dw
if (ring_buf_size//sink_dw) < 2:
ring_buf_size = ring_buf_size * 2
if (ring_buf_size//source_dw) < 2:
ring_buf_size = ring_buf_size * 2
# Control interface
reset_reg = Signal()
we = Signal()
re = Signal()
level = Signal(max=ring_buf_size)
w_cnt = Signal(max=ring_buf_size//sink_dw)
r_cnt = Signal(max=ring_buf_size//source_dw)
self.sync += [
If(reset_reg,
level.eq(level.reset),
).Else(
If(we & ~re, level.eq(level + sink_dw)),
If(~we & re, level.eq(level - source_dw)),
If(we & re, level.eq(level + sink_dw - source_dw)),
),
If(reset_reg,
w_cnt.eq(w_cnt.reset),
r_cnt.eq(r_cnt.reset),
).Else(
If(we,
If(w_cnt == ((ring_buf_size//sink_dw) - 1),
w_cnt.eq(w_cnt.reset),
).Else(
w_cnt.eq(w_cnt + 1),
)
),
If(re,
If(r_cnt == ((ring_buf_size//source_dw) - 1),
r_cnt.eq(r_cnt.reset),
).Else(
r_cnt.eq(r_cnt + 1),
)
),
)
]
extra_eol_handling = size in [10, 12, 14]
if extra_eol_handling:
# the source need to be stb twice
# (one for level >= source_dw and the other for the remaining pixels)
# when last word of each line packet satisfied the following condition:
#
# if there exist an integers j such that
# sink_dw * i > size * j > source_dw * k
# where i,k are postive integers and source_dw * k - sink_dw * (i-1) > 0
#
stb_aligned = Signal()
match size:
case 10:
# For example size == 10
# 32 * 2 > 10 * (5) > 40 * 1
# 32 * 2 > 10 * (6) > 40 * 1
# 32 * 3 > 10 * (9) > 40 * 2
# ...
#
# the packing pattern for size == 10 repeat every 16 pixels
# the remaining special case can be taken care off using modulo operation
stb_cases = {
5: stb_aligned.eq(1),
6: stb_aligned.eq(1),
9: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:4], stb_cases) # mod 16
case 12:
stb_cases = {
5: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:3], stb_cases) # mod 8
case 14:
stb_cases = {
9: stb_aligned.eq(1),
13: stb_aligned.eq(1),
}
self.sync += Case(self.x_size[:4], stb_cases) # mod 16
self.submodules.fsm = fsm = FSM(reset_state="SHIFTING")
fsm.act(
"SHIFTING",
self.sink.ack.eq(1),
self.source.stb.eq(level >= source_dw),
we.eq(self.sink.stb),
re.eq((self.source.stb & self.source.ack)),
If(self.sink.stb & self.sink.eop,
(If(stb_aligned,
NextState("MOVE_ALIGNED_PIX"),
).Else(
NextState("MOVE_REMAINING_PIX"),
) if extra_eol_handling else
NextState("MOVE_REMAINING_PIX"),
)
),
)
if extra_eol_handling:
fsm.act(
"MOVE_ALIGNED_PIX",
self.source.stb.eq(1),
re.eq((self.source.stb & self.source.ack)),
NextState("MOVE_REMAINING_PIX"),
)
stb_remaining_pix = Signal()
fsm.act(
"MOVE_REMAINING_PIX",
reset_reg.eq(1),
self.source.stb.eq(1),
stb_remaining_pix.eq(1),
NextState("SHIFTING"),
)
# Data path
ring_buf = Signal(ring_buf_size, reset_less=True)
sink_cases = {}
for i in range(ring_buf_size//sink_dw):
sink_cases[i] = [
ring_buf[sink_dw*i:sink_dw*(i+1)].eq(self.sink.data),
]
self.sync += If(self.sink.stb, Case(w_cnt, sink_cases))
source_cases = {}
for i in range(ring_buf_size//source_dw):
source_cases[i] = []
for j in range(4):
source_cases[i].append(
self.source.data[pixel_width * j : pixel_width * (j + 1)].eq(
ring_buf[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))]
)
)
# calcule which last pixels are valid
valid = Signal(4)
bit_cases = {
0: valid.eq(0b1111),
1: valid.eq(0b0001),
2: valid.eq(0b0011),
3: valid.eq(0b0111),
}
self.sync += Case(self.x_size[:2], bit_cases)
self.comb += [
Case(r_cnt, source_cases),
If(stb_remaining_pix,
self.source.valid.eq(valid),
self.source.eop.eq(1),
).Else(
self.source.valid.eq(0b1111),
),
]
class Pixel_Coordinate_Tracker(Module):
"""
Track pixel coordinates
Assume
- camera is in area scan mode
- 1X-1Y Tap geometry
"""
def __init__(self, res_width):
# largest x/y pixel size supported by frame header are 24 bits
assert res_width <= 3*char_width
# line scaning frame will have y_size = 0 and won't trigger the end of frame bit
self.y_size = Signal(3*char_width)
self.sink = stream.Endpoint(pixel4x_layout)
# # #
# NOTE: no need for last_x/last_y csr which is use to indicate how big is the frame
# layout = Record([
# ("x", res_width),
# ("y", res_width),
# ("d", pixel_width),
# ("stb", 1),
# ("eof", 1), # end of frame
# ])
# self.pixel4x = [layout for _ in range(4)]
# DEBUG: for sim only, to show all record in sim
self.pixel4x = []
for _ in range(4):
self.pixel4x.append(Record([
("x", res_width),
("y", res_width),
("gray", pixel_width),
("stb", 1),
("eof", 1), # end of frame
]))
x_4x = [Signal(len(self.pixel4x[0].x), reset=i) for i in range(4)]
y_r = Signal(len(self.pixel4x[0].y))
y_max = Signal.like(self.y_size)
self.sync += [
self.sink.ack.eq(1),
y_max.eq(self.y_size - 1),
]
for i, (x_r, pix) in enumerate(zip(x_4x, self.pixel4x)):
self.sync += [
pix.stb.eq(0),
pix.eof.eq(0),
If(self.sink.stb,
If(self.sink.eop,
# new line
x_r.eq(x_r.reset),
If(y_r == y_max,
pix.eof.eq(1),
y_r.eq(y_r.reset),
).Else(
y_r.eq(y_r + 1),
)
).Else(
x_r.eq(x_r + 4),
),
pix.stb.eq(self.sink.valid[i]),
pix.x.eq(x_r),
pix.y.eq(y_r),
pix.gray.eq(self.sink.data[pixel_width*i:pixel_width*(i+1)]),
)
]
class ROI(Module):
"""
ROI Engine. For each frame, accumulates pixels values within a
rectangular region of interest, and reports the total.
"""
def __init__(self, pixel_4x, count_width):
assert len(pixel_4x) == 4
self.cfg = Record([
("x0", len(pixel_4x[0].x)),
("y0", len(pixel_4x[0].y)),
("x1", len(pixel_4x[0].x)),
("y1", len(pixel_4x[0].y)),
])
self.out = Record([
("update", 1),
# registered output - can be used as CDC input
("count", count_width),
])
# # #
# TODO: remove the self. from self.roi_4x
self.roi_4x = []
for _ in range(4):
self.roi_4x.append(Record([
("x_good", 1),
("y_good", 1),
("gray", len(pixel_4x[0].gray)),
("stb", 1),
("count", count_width),
]))
for pix, roi in zip(pixel_4x, self.roi_4x):
self.sync += [
# TODO: replace the comparision with preprocess equal
# e.g. pix.x == self.cfg.x0 - i
# stage 1 - generate "good" (in-ROI) signals
If(pix.x <= self.cfg.x0,
roi.x_good.eq(1)
),
# NOTE: this gate doens't work as 4 pixes are coming in
If(pix.x >= self.cfg.x1,
roi.x_good.eq(0)
),
# This is fine because 4x pixel are on the same line
If(pix.y == self.cfg.y0,
roi.y_good.eq(1)
),
If(pix.y == self.cfg.y1,
roi.y_good.eq(0)
),
If(pix.eof,
roi.x_good.eq(0),
roi.y_good.eq(0)
),
roi.gray.eq(pix.gray),
roi.stb.eq(pix.stb),
# stage 2 - accumulate
If((roi.stb & roi.x_good & roi.y_good),
roi.count.eq(roi.count + roi.gray)
)
]
eof = Signal()
eof_buf = Signal()
count_buf = [Signal(count_width), Signal(count_width)]
# stage 3 - update
self.sync += [
eof.eq(reduce(or_, [pix.eof for pix in pixel_4x])),
eof_buf.eq(eof),
count_buf[0].eq(self.roi_4x[0].count + self.roi_4x[1].count),
count_buf[1].eq(self.roi_4x[2].count + self.roi_4x[3].count),
self.out.update.eq(0),
If(eof_buf,
[roi.count.eq(0) for roi in self.roi_4x],
self.out.update.eq(1),
self.out.count.eq(reduce(add, count_buf))
),
]
class Pixel_Parser(Module):
"""
Convert the raw frame data into pixel data
Currently only support:
- Pixel format: mono8, mono10, mono12, mono14, mono16
- Tap geometry: 1X-1Y
- Scaning mode: area scanning
"""
def __init__(self, res_width):
self.l_size = Signal(3*char_width)
self.x_size = Signal(3*char_width)
self.y_size = Signal(3*char_width)
self.pixel_format = Signal(2*char_width)
# # #
#
# 32+8(dchar) 32 pixel 4x
# ----/----> end of line ---/---> Pixel Gearboxes ----/----> Pixel Coordinate ------> pixel 4x
# inserter Tracker w/ coord
#
self.submodules.eol_inserter = eol_inserter = End_Of_Line_Inserter()
self.sync += eol_inserter.l_size.eq(self.l_size)
self.sink = eol_inserter.sink
gearboxes = {}
for s in [8, 10, 12, 14, 16]:
gearbox = Pixel_Gearbox(s)
gearboxes["mono"+str(s)] = gearbox
self.submodules += gearbox
self.sync += gearbox.x_size.eq(self.x_size),
# From Table 34 (CXP-001-2021)
pix_fmt = {
"mono8": 0x0101,
"mono10": 0x0102,
"mono12": 0x0103,
"mono14": 0x0104,
"mono16": 0x0105,
}
self.submodules.tracker = tracker = Pixel_Coordinate_Tracker(res_width)
self.sync += tracker.y_size.eq(self.y_size)
self.pixel4x = tracker.pixel4x
mux_cases = {
"default": [
# discard unknown pixel format
eol_inserter.source.ack.eq(1),
],
}
for fmt in pix_fmt:
mux_cases[pix_fmt[fmt]] = [
eol_inserter.source.connect(gearboxes[fmt].sink),
gearboxes[fmt].source.connect(tracker.sink),
]
self.comb += Case(self.pixel_format, mux_cases)
class Pixel_Pipeline(Module):
def __init__(self, res_width, count_width, packet_size):
# NOTE: csr need to stay outside since this module need to be cdr in the CXP_FRAME_Pipeline module
# NOTE: TapGeo other than 1X-1Y are not supported
# TODO: match pixel and see whether the it matches the supported ones (via csr perhaps?)
# 32+8(dchar)
# ----/----> crc checker ------> frame header ------> Pixel Parser ------> pixel 4x
# decoder w/ coord
# DEBUG: adding fifo doesn't help
self.submodules.buffer = buffer = stream.SyncFIFO(word_layout_dchar, 32, True)
# self.submodules.buffer = buffer = Buffer(word_layout_dchar) # to improve timing from broadcaster
self.submodules.crc_checker = crc_checker = CXPCRC32_Checker()
self.submodules.header_decoder = header_decoder = Frame_Header_Decoder()
self.submodules.parser = parser = Pixel_Parser(res_width)
self.submodules.roi = ROI(parser.pixel4x, count_width)
self.comb += [
parser.l_size.eq(header_decoder.metadata.l_size),
parser.x_size.eq(header_decoder.metadata.x_size),
parser.y_size.eq(header_decoder.metadata.y_size),
parser.pixel_format.eq(header_decoder.metadata.pixel_format),
]
self.pipeline = [buffer, crc_checker, header_decoder, parser]
for s, d in zip(self.pipeline, self.pipeline[1:]):
self.comb += s.source.connect(d.sink)
self.sink = self.pipeline[0].sink
# DEBUG
# self.pix = self.pipeline[-1].pix
# self.source = self.pipeline[-1].source
# self.comb += self.source.ack.eq(1) # simulated a proper consumer, idk why but without this it will destory timing

View File

@ -0,0 +1,633 @@
from migen import *
from migen.genlib.cdc import MultiReg
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from functools import reduce
from itertools import combinations
from operator import or_, and_
char_width = 8
char_layout = [("data", char_width), ("k", char_width//8)]
word_width = 32
word_layout = [("data", word_width), ("k", word_width//8)]
word_layout_dchar = [
("data", word_width),
("k", word_width//8),
("dchar", char_width),
("dchar_k", char_width//8),
]
buffer_count = 4
buffer_depth = 512
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 Packet_Wrapper(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout)
# # #
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
self.sink.ack.eq(1),
If(self.sink.stb,
self.sink.ack.eq(0),
NextState("INSERT_HEADER"),
)
)
fsm.act("INSERT_HEADER",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(KCode["pak_start"], 4)),
self.source.k.eq(Replicate(1, 4)),
If(self.source.ack, NextState("COPY")),
)
fsm.act("COPY",
self.sink.connect(self.source),
self.source.eop.eq(0),
If(self.sink.stb & self.sink.eop & self.source.ack,
NextState("INSERT_FOOTER"),
),
)
fsm.act("INSERT_FOOTER",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(KCode["pak_end"], 4)),
self.source.k.eq(Replicate(1, 4)),
self.source.eop.eq(1),
If(self.source.ack, NextState("IDLE")),
)
class Trigger_Inserter(Module):
def __init__(self):
self.stb = Signal()
self.delay = Signal(char_width)
self.linktrig_mode = Signal()
# # #
self.sink = stream.Endpoint(char_layout)
self.source = stream.Endpoint(char_layout)
# Table 15 & 16 (CXP-001-2021)
# Send [K28.2, K28.4, K28.4] or [K28.4, K28.2, K28.2] and 3x delay as trigger packet
trig_packet = [Signal(char_width), Signal(char_width), Signal(char_width), self.delay, self.delay, self.delay]
trig_packet_k = [1, 1, 1, 0, 0, 0]
self.comb += [
If(self.linktrig_mode,
trig_packet[0].eq(KCode["trig_indic_28_4"]),
trig_packet[1].eq(KCode["trig_indic_28_2"]),
trig_packet[2].eq(KCode["trig_indic_28_2"]),
).Else(
trig_packet[0].eq(KCode["trig_indic_28_2"]),
trig_packet[1].eq(KCode["trig_indic_28_4"]),
trig_packet[2].eq(KCode["trig_indic_28_4"]),
),
]
self.submodules.fsm = fsm = FSM(reset_state="COPY")
cnt = Signal(max=6)
fsm.act("COPY",
NextValue(cnt, cnt.reset),
self.sink.connect(self.source),
If(self.stb, NextState("WRITE_TRIG"))
)
fsm.act("WRITE_TRIG",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Array(trig_packet)[cnt]),
self.source.k.eq(Array(trig_packet_k)[cnt]),
If(self.source.ack,
If(cnt == 5,
NextState("COPY"),
).Else(
NextValue(cnt, cnt + 1),
)
)
)
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)
# Section 9.2.5.1 (CXP-001-2021)
# IDLE should be transmitter every 10000 words
cnt = Signal(max=10000, reset=9999)
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 Trigger_ACK_Inserter(Module):
def __init__(self):
self.stb = Signal()
# # #
# Section 9.3.2 (CXP-001-2021)
# Send 4x K28.6 and 4x 0x01 as trigger packet ack
self.submodules.fsm = fsm = FSM(reset_state="COPY")
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout)
fsm.act("COPY",
self.sink.connect(self.source),
If(self.stb, NextState("WRITE_ACK0"))
)
fsm.act("WRITE_ACK0",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(KCode["io_ack"], 4)),
self.source.k.eq(Replicate(1, 4)),
If(self.source.ack, NextState("WRITE_ACK1")),
)
fsm.act("WRITE_ACK1",
self.sink.ack.eq(0),
self.source.stb.eq(1),
self.source.data.eq(Replicate(C(0x01, char_width), 4)),
self.source.k.eq(Replicate(0, 4)),
If(self.source.ack, NextState("COPY")),
)
@FullMemoryWE()
class Control_Packet_Writer(Module):
def __init__(self):
self.word_len = Signal(log2_int(buffer_depth))
self.stb = Signal()
self.stb_testseq = Signal()
self.busy = Signal()
# # #
self.specials.mem = mem = Memory(word_width, buffer_depth)
self.specials.mem_port = mem_port = mem.get_port()
self.source = stream.Endpoint(word_layout)
# increment addr in the same cycle the moment addr_inc is high
# as memory takes one cycle to shift to the correct addr
addr_next = Signal(log2_int(buffer_depth))
addr = Signal.like(addr_next)
addr_rst = Signal()
addr_inc = Signal()
self.sync += addr.eq(addr_next),
self.comb += [
addr_next.eq(addr),
If(addr_rst,
addr_next.eq(addr_next.reset),
).Elif(addr_inc,
addr_next.eq(addr + 1),
),
mem_port.adr.eq(addr_next),
]
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
self.comb += self.busy.eq(~fsm.ongoing("IDLE"))
cnt = Signal(max=0xFFF)
fsm.act("IDLE",
addr_rst.eq(1),
If(self.stb, NextState("TRANSMIT")),
If(self.stb_testseq,
NextValue(cnt, cnt.reset),
NextState("WRITE_TEST_PACKET_TYPE"),
)
)
fsm.act("TRANSMIT",
self.source.stb.eq(1),
self.source.data.eq(mem_port.dat_r),
If(self.source.ack,
addr_inc.eq(1),
),
If(addr_next == self.word_len,
self.source.eop.eq(1),
NextState("IDLE")
)
)
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"))
)
fsm.act("WRITE_TEST_COUNTER",
self.source.stb.eq(1),
self.source.data[:8].eq(cnt[:8]),
self.source.data[8:16].eq(cnt[:8]+1),
self.source.data[16:24].eq(cnt[:8]+2),
self.source.data[24:].eq(cnt[:8]+3),
self.source.k.eq(Cat(0, 0, 0, 0)),
If(self.source.ack,
If(cnt == 0xFFC,
self.source.eop.eq(1),
NextState("IDLE")
).Else(
NextValue(cnt, cnt + 4),
)
)
)
class RX_Debug_Buffer(Module,AutoCSR):
def __init__(self, layout, size):
self.submodules.buf_out = buf_out = stream.SyncFIFO(layout, size, True)
self.sink = buf_out.sink
self.inc = CSR()
self.dout_valid = CSRStatus()
self.dout_pak = CSRStatus(word_width)
self.kout_pak = CSRStatus(word_width//8)
self.crc_error = CSRStatus()
self.eop = CSRStatus()
self.sync += [
buf_out.source.ack.eq(self.inc.re),
self.dout_valid.status.eq(buf_out.source.stb),
# output
self.eop.status.eq(buf_out.source.eop),
self.dout_pak.status.eq(buf_out.source.data),
self.kout_pak.status.eq(buf_out.source.k),
# self.crc_error.status.eq(buf_out.source.error)
]
class Duplicated_Char_Decoder(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout_dchar)
# # #
# For duplicated characters, an error correction method (e.g. majority voting) is required to meet the CXP spec:
# RX decoder should immune to single bit errors when handling duplicated characters - Section 9.2.2.1 (CXP-001-2021)
#
#
# 32
# +---> buffer -----/-----+
# 32 | | 32+8(dchar)
# sink ---/---+ ---> source -----/-----> downstream
# | 8(dchar) | decoders
# +---> majority -----/-----+
# voting
#
#
# Due to the tight setup/hold time requiremnt for 12.5Gbps CXP, the voting logic cannot be implemented as combinational logic
# Hence, a pipeline approach is needed to avoid any s/h violation, where the majority voting result are pre-calculate and injected into the bus immediate after the PHY.
# And any downstream modules can access the voting result anytime
# cycle 1 - buffer data & calculate intermediate result
buffer = stream.Endpoint(word_layout)
self.sync += [
If((~buffer.stb | buffer.ack),
self.sink.connect(buffer, omit={"ack"}),
)
]
self.comb += self.sink.ack.eq(~buffer.stb | buffer.ack)
# calculate ABC, ABD, ACD, BCD
char = [[self.sink.data[i*8:(i+1)*8], self.sink.k[i]] for i in range(4)]
voters = [Record([("data", 8), ("k", 1)]) for _ in range(4)]
for i, comb in enumerate(combinations(char, 3)):
self.sync += [
If((~buffer.stb | buffer.ack),
voters[i].data.eq(reduce(and_, [code[0] for code in comb])),
voters[i].k.eq(reduce(and_, [code[1] for code in comb])),
)
]
# cycle 2 - inject the voting result
self.sync += [
If((~self.source.stb | self.source.ack),
buffer.connect(self.source, omit={"ack", "dchar", "dchar_k"}),
self.source.dchar.eq(Replicate(reduce(or_, [v.data for v in voters]), 4)),
self.source.dchar_k.eq(Replicate(reduce(or_, [v.k for v in voters]), 4)),
)
]
self.comb += buffer.ack.eq(~self.source.stb | self.source.ack)
@FullMemoryWE()
class Control_Packet_Reader(Module):
def __init__(self):
self.packet_type = Signal(8)
self.decode_err = Signal()
self.buffer_err = Signal()
self.test_err_cnt = Signal(16)
self.test_pak_cnt = Signal(16)
self.test_cnt_reset = Signal()
# # #
type = {
"data_stream": 0x01,
"control_ack_no_tag": 0x03,
"test_packet": 0x04,
"control_ack_with_tag": 0x06,
"event": 0x07,
"heartbeat": 0x09,
}
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
self.sink.ack.eq(1),
If((self.sink.stb & (self.sink.dchar == KCode["pak_start"]) & (self.sink.dchar_k == 1)),
NextState("DECODE"),
)
)
cnt = Signal(max=0x100)
addr_nbits = log2_int(buffer_depth)
addr = Signal(addr_nbits)
test_pak = Signal()
fsm.act("DECODE",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(self.sink.dchar, {
type["data_stream"]: NextState("STREAMING"),
type["test_packet"]: [
test_pak.eq(1),
NextValue(cnt, cnt.reset),
NextState("VERIFY_TEST_PATTERN"),
],
type["control_ack_no_tag"]:[
NextValue(self.packet_type, self.sink.dchar),
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
type["control_ack_with_tag"]:[
NextValue(self.packet_type, self.sink.dchar),
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
type["event"]: [
NextValue(self.packet_type, self.sink.dchar),
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
type["heartbeat"] : [
# TODO: handle heartbeat
NextState("IDLE"),
],
"default": [
self.decode_err.eq(1),
# wait till next valid packet
NextState("IDLE"),
],
}),
)
)
# For stream data packet
fsm.act("STREAMING",
self.sink.connect(self.source),
# assume downstream is not blocked
If((self.sink.stb & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE")
)
)
# Section 9.9.1 (CXP-001-2021)
# the received test data packet (0x00, 0x01 ... 0xFF)
# need to be compared against the local test sequence generator
# TODO: improve this to avoid tight setup/hold time
test_err = Signal()
fsm.act("VERIFY_TEST_PATTERN",
self.sink.ack.eq(1),
If(self.sink.stb,
If(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE"),
).Else(
If(self.sink.data[:8] != cnt,
test_err.eq(1),
).Elif(self.sink.data[8:16] != cnt + 1,
test_err.eq(1),
).Elif(self.sink.data[16:24] != cnt + 2,
test_err.eq(1),
).Elif(self.sink.data[24:] != cnt + 3,
test_err.eq(1),
),
If(cnt == 0xFC,
NextValue(cnt, cnt.reset),
).Else(
NextValue(cnt, cnt + 4)
)
)
)
)
self.sync += [
If(self.test_cnt_reset,
self.test_err_cnt.eq(self.test_err_cnt.reset),
).Elif(test_err,
self.test_err_cnt.eq(self.test_err_cnt + 1),
),
If(self.test_cnt_reset,
self.test_pak_cnt.eq(self.test_pak_cnt.reset),
).Elif(test_pak,
self.test_pak_cnt.eq(self.test_pak_cnt + 1),
)
]
# A circular buffer for firmware to read packet from
self.specials.mem = mem = Memory(word_width, buffer_count*buffer_depth)
self.specials.mem_port = mem_port = mem.get_port(write_capable=True)
# buffered mem_port to improve timing
buf_mem_we = Signal.like(mem_port.we)
buf_mem_dat_w = Signal.like(mem_port.dat_w)
buf_mem_adr = Signal.like(mem_port.adr)
self.sync += [
mem_port.we.eq(buf_mem_we),
mem_port.dat_w.eq(buf_mem_dat_w),
mem_port.adr.eq(buf_mem_adr)
]
write_ptr = Signal(log2_int(buffer_count))
self.write_ptr_sys = Signal.like(write_ptr)
self.specials += MultiReg(write_ptr, self.write_ptr_sys),
self.comb += [
buf_mem_adr[:addr_nbits].eq(addr),
buf_mem_adr[addr_nbits:].eq(write_ptr),
]
# For control ack, event packet
fsm.act("LOAD_BUFFER",
buf_mem_we.eq(0),
self.sink.ack.eq(1),
If(self.sink.stb,
If(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("MOVE_BUFFER_PTR"),
).Else(
buf_mem_we.eq(1),
buf_mem_dat_w.eq(self.sink.data),
NextValue(addr, addr + 1),
If(addr == buffer_depth - 1,
# discard the packet
self.buffer_err.eq(1),
NextState("IDLE"),
)
)
)
)
self.read_ptr_rx = Signal.like(write_ptr)
fsm.act("MOVE_BUFFER_PTR",
self.sink.ack.eq(0),
If(write_ptr + 1 == self.read_ptr_rx,
# if next one hasn't been read, overwrite the current buffer when new packet comes in
self.buffer_err.eq(1),
).Else(
NextValue(write_ptr, write_ptr + 1),
),
NextState("IDLE"),
)
class Trigger_Ack_Checker(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
self.ack = Signal()
# # #
self.submodules.fsm = fsm = FSM(reset_state="COPY")
fsm.act("COPY",
If((self.sink.stb & (self.sink.dchar == KCode["io_ack"]) & (self.sink.dchar_k == 1)),
# discard K28,6
self.sink.ack.eq(1),
NextState("CHECK_ACK")
).Else(
self.sink.connect(self.source),
)
)
fsm.act("CHECK_ACK",
If(self.sink.stb,
NextState("COPY"),
# discard the word after K28,6
self.sink.ack.eq(1),
If((self.sink.dchar == 0x01) & (self.sink.dchar_k == 0),
self.ack.eq(1),
)
)
)
class Buffer(Module):
def __init__(self, layout):
self.sink = stream.Endpoint(layout)
self.source = stream.Endpoint(layout)
# # #
self.sync += [
If((~self.source.stb | self.source.ack),
self.sink.connect(self.source, omit={"ack"}),
),
]
self.comb += [
self.sink.ack.eq(~self.source.stb | self.source.ack),
]
class DChar_Dropper(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout)
# # #
self.sync += [
If((~self.source.stb | self.source.ack),
self.sink.connect(self.source, omit={"ack", "dchar", "dchar_k", "error"}),
),
]
self.comb += [
self.sink.ack.eq(~self.source.stb | self.source.ack),
]
class EOP_Marker(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
# # #
self.sync += [
If((~self.source.stb | self.source.ack),
If(~((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
self.sink.connect(self.source, omit={"ack", "eop"}),
).Else(
self.source.stb.eq(0),
)
),
]
self.comb += [
self.sink.ack.eq(~self.source.stb | self.source.ack),
self.source.eop.eq(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1))),
]

12
src/gateware/cxp_rtio.py Normal file
View File

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

142
src/gateware/cxp_upconn.py Normal file
View File

@ -0,0 +1,142 @@
from math import ceil
from migen import *
from misoc.cores.code_8b10b import SingleEncoder
from misoc.interconnect import stream
from misoc.interconnect.csr import *
from cxp_pipeline import char_layout
@ResetInserter()
class ClockGen(Module):
def __init__(self, sys_clk_freq):
self.clk = Signal()
self.clk_10x = Signal() # 20.83MHz 48ns or 41.66MHz 24ns
self.freq2x_enable = Signal()
# # #
period = 1e9/sys_clk_freq
max_count = ceil(48/period)
counter = Signal(max=max_count, reset=max_count-1)
clk_div = Signal(max=10, reset=9)
self.sync += [
self.clk.eq(0),
self.clk_10x.eq(0),
If(counter == 0,
self.clk_10x.eq(1),
If(self.freq2x_enable,
counter.eq(int(max_count/2)-1),
).Else(
counter.eq(counter.reset),
),
).Else(
counter.eq(counter-1),
),
If(counter == 0,
If(clk_div == 0,
self.clk.eq(1),
clk_div.eq(clk_div.reset),
).Else(
clk_div.eq(clk_div-1),
)
)
]
@ResetInserter()
@CEInserter()
class SERDES_10bits(Module):
def __init__(self, pad):
self.oe = Signal()
self.d = Signal(10)
# # #
tx_bitcount = Signal(max=10)
tx_reg = Signal(10)
self.sync += [
If(self.oe,
# send LSB first
pad.eq(tx_reg[0]),
tx_reg.eq(Cat(tx_reg[1:], 0)),
tx_bitcount.eq(tx_bitcount + 1),
If(tx_bitcount == 9,
tx_bitcount.eq(0),
tx_reg.eq(self.d),
),
).Else(
pad.eq(0),
tx_bitcount.eq(0),
)
]
class Transmitter(Module, AutoCSR):
def __init__(self, pad, sys_clk_freq):
self.bitrate2x_enable = Signal()
self.clk_reset = Signal()
self.enable = Signal()
# # #
self.sink = stream.Endpoint(char_layout)
self.submodules.cg = cg = ClockGen(sys_clk_freq)
self.submodules.encoder = encoder = SingleEncoder(True)
oe = Signal()
self.sync += [
If(self.enable,
self.sink.ack.eq(0),
If(cg.clk,
oe.eq(1),
encoder.disp_in.eq(encoder.disp_out),
self.sink.ack.eq(1),
encoder.d.eq(self.sink.data),
encoder.k.eq(self.sink.k),
)
).Else(
# discard packets until tx is enabled
self.sink.ack.eq(1),
oe.eq(0),
)
]
self.submodules.serdes = serdes = SERDES_10bits(pad)
self.comb += [
cg.reset.eq(self.clk_reset),
cg.freq2x_enable.eq(self.bitrate2x_enable),
serdes.reset.eq(self.clk_reset),
serdes.ce.eq(cg.clk_10x),
serdes.d.eq(encoder.output),
serdes.oe.eq(oe),
]
class CXP_TXPHYs(Module, AutoCSR):
def __init__(self, pads, sys_clk_freq):
self.clk_reset = CSR()
self.bitrate2x_enable = CSRStorage()
self.enable = CSRStorage()
# # #
self.phys = []
for i, pad in enumerate(pads):
tx = Transmitter(pad, sys_clk_freq)
self.phys.append(tx)
setattr(self.submodules, "tx"+str(i), tx)
self.sync += [
tx.clk_reset.eq(self.clk_reset.re),
tx.bitrate2x_enable.eq(self.bitrate2x_enable.storage),
tx.enable.eq(self.enable.storage),
]

307
src/gateware/ebaz4205.py Normal file
View File

@ -0,0 +1,307 @@
#!/usr/bin/env python
import argparse
import analyzer
import dma
from artiq.gateware import rtio
from artiq.gateware.rtio.phy import spi2, ttl_simple
from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
from config import write_csr_file, write_mem_file, write_rustc_cfg_file
from migen import *
from migen.build.generic_platform import IOStandard, Misc, Pins, Subsignal
from migen.build.platforms import ebaz4205
from migen_axi.integration.soc_core import SoCCore
from misoc.interconnect.csr import *
_ps = [
(
"ps",
0,
Subsignal("clk", Pins("E7"), IOStandard("LVCMOS33"), Misc("SLEW=FAST")),
Subsignal("por_b", Pins("C7"), IOStandard("LVCMOS33"), Misc("SLEW=FAST")),
Subsignal("srst_b", Pins("B10"), IOStandard("LVCMOS18"), Misc("SLEW=FAST")),
)
]
_ddr = [
(
"ddr",
0,
Subsignal(
"a",
Pins("N2 K2 M3 K3 M4 L1 L4 K4 K1 J4 F5 G4 E4 D4 F4"),
IOStandard("SSTL15"),
),
Subsignal("ba", Pins("L5 R4 J5"), IOStandard("SSTL15")),
Subsignal("cas_n", Pins("P5"), IOStandard("SSTL15")),
Subsignal("cke", Pins("N3"), IOStandard("SSTL15")),
Subsignal("cs_n", Pins("N1"), IOStandard("SSTL15")),
Subsignal("ck_n", Pins("M2"), IOStandard("DIFF_SSTL15"), Misc("SLEW=FAST")),
Subsignal("ck_p", Pins("L2"), IOStandard("DIFF_SSTL15"), Misc("SLEW=FAST")),
# Pins "T1 Y1" not connected
Subsignal("dm", Pins("A1 F1"), IOStandard("SSTL15_T_DCI"), Misc("SLEW=FAST")),
Subsignal(
"dq",
Pins("C3 B3 A2 A4 D3 D1 C1 E1 E2 E3 G3 H3 J3 H2 H1 J1"),
# Pins "P1 P3 R3 R1 T4 U4 U2 U3 V1 Y3 W1 Y4 Y2 W3 V2 V3" not connected
IOStandard("SSTL15_T_DCI"),
Misc("SLEW=FAST"),
),
Subsignal(
"dqs_n",
Pins("B2 F2"), # Pins "T2 W4" not connected
IOStandard("DIFF_SSTL15_T_DCI"),
Misc("SLEW=FAST"),
),
Subsignal(
"dqs_p",
Pins("C2 G2"), # Pins "R2 W5" not connected
IOStandard("DIFF_SSTL15_T_DCI"),
Misc("SLEW=FAST"),
),
Subsignal("vrn", Pins("G5"), IOStandard("SSTL15_T_DCI"), Misc("SLEW=FAST")),
Subsignal("vrp", Pins("H5"), IOStandard("SSTL15_T_DCI"), Misc("SLEW=FAST")),
Subsignal("drst_n", Pins("B4"), IOStandard("SSTL15"), Misc("SLEW=FAST")),
Subsignal("odt", Pins("N5"), IOStandard("SSTL15")),
Subsignal("ras_n", Pins("P4"), IOStandard("SSTL15")),
Subsignal("we_n", Pins("M5"), IOStandard("SSTL15")),
)
]
# Connector J3
_i2c = [
(
"i2c",
0,
Subsignal("scl", Pins("U12"), IOStandard("LVCMOS33")),
Subsignal("sda", Pins("V13"), IOStandard("LVCMOS33")),
)
]
_spi = [
(
"spi",
0,
Subsignal("clk", Pins("V20")),
Subsignal("mosi", Pins("U20")),
Subsignal("cs_n", Pins("P19")),
IOStandard("LVCMOS33"),
)
]
# Connector DATA1
def _create_ttl():
_ttl = []
for idx, elem in enumerate([x for x in range(5, 21) if x not in (10, 12)]):
_ttl.append(
("ttl", idx, Pins("DATA1:DATA1-{}".format(elem)), IOStandard("LVCMOS33")),
)
return _ttl
class EBAZ4205(SoCCore):
def __init__(self, rtio_clk=125e6, acpki=False):
self.acpki = acpki
platform = ebaz4205.Platform()
platform.toolchain.bitstream_commands.extend(
[
"set_property BITSTREAM.GENERAL.COMPRESS True [current_design]",
]
)
platform.add_extension(_ps)
platform.add_extension(_ddr)
platform.add_extension(_i2c)
platform.add_extension(_spi)
platform.add_extension(_create_ttl())
gmii = platform.request("gmii")
platform.add_period_constraint(gmii.rx_clk, 10)
platform.add_period_constraint(gmii.tx_clk, 10)
platform.add_platform_command(
"set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets gmii_tx_clk_IBUF]"
)
ident = self.__class__.__name__
if self.acpki:
ident = "acpki_" + ident
SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident)
fix_serdes_timing_path(platform)
self.config["RTIO_FREQUENCY"] = str(rtio_clk / 1e6)
platform.add_period_constraint(self.ps7.cd_sys.clk, 10)
self.comb += [
self.ps7.enet0.enet.gmii.tx_clk.eq(gmii.tx_clk),
self.ps7.enet0.enet.gmii.rx_clk.eq(gmii.rx_clk),
]
self.clock_domains.cd_eth_rx = ClockDomain(reset_less=False)
self.clock_domains.cd_eth_tx = ClockDomain(reset_less=False)
self.comb += [
ClockSignal("eth_rx").eq(gmii.rx_clk),
ClockSignal("eth_tx").eq(gmii.tx_clk),
]
self.sync.eth_tx += [
gmii.txd.eq(self.ps7.enet0.enet.gmii.txd),
gmii.tx_en.eq(self.ps7.enet0.enet.gmii.tx_en),
]
self.sync.eth_rx += [
self.ps7.enet0.enet.gmii.rxd.eq(gmii.rxd),
self.ps7.enet0.enet.gmii.rx_dv.eq(gmii.rx_dv),
]
# MDIO
mdio = platform.request("mdio")
self.comb += mdio.mdc.eq(self.ps7.enet0.enet.mdio.mdc)
self.specials += Instance(
"IOBUF",
i_I=self.ps7.enet0.enet.mdio.o,
io_IO=mdio.mdio,
o_O=self.ps7.enet0.enet.mdio.i,
i_T=~self.ps7.enet0.enet.mdio.t_n,
)
# I2C
i2c = self.platform.request("i2c")
self.specials += [
# SCL
Instance(
"IOBUF",
i_I=self.ps7.i2c0.scl.o,
io_IO=i2c.scl,
o_O=self.ps7.i2c0.scl.i,
i_T=~self.ps7.i2c0.scl.t_n,
),
# SDA
Instance(
"IOBUF",
i_I=self.ps7.i2c0.sda.o,
io_IO=i2c.sda,
o_O=self.ps7.i2c0.sda.i,
i_T=~self.ps7.i2c0.sda.t_n,
),
]
self.rtio_channels = []
for i in (0, 1):
print("USER LED at RTIO channel 0x{:06x}".format(len(self.rtio_channels)))
user_led = self.platform.request("user_led", i)
phy = ttl_simple.Output(user_led)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
for i in range(14):
print("TTL at RTIO channel 0x{:06x}".format(len(self.rtio_channels)))
ttl = self.platform.request("ttl", i)
phy = ttl_simple.InOut(ttl)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
print("SPI at RTIO channel 0x{:06x}".format(len(self.rtio_channels)))
spi_phy = spi2.SPIMaster(platform.request("spi"))
self.submodules += spi_phy
self.rtio_channels.append(rtio.Channel.from_phy(spi_phy, ififo_depth=4))
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
self.submodules.rtio_core = rtio.Core(self.rtio_tsc, self.rtio_channels)
self.csr_devices.append("rtio_core")
if self.acpki:
import acpki
self.config["KI_IMPL"] = "acp"
self.submodules.rtio = acpki.KernelInitiator(
self.rtio_tsc,
bus=self.ps7.s_axi_acp,
user=self.ps7.s_axi_acp_user,
evento=self.ps7.event.o,
)
self.csr_devices.append("rtio")
else:
self.config["KI_IMPL"] = "csr"
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc, now64=True)
self.csr_devices.append("rtio")
self.submodules.rtio_dma = dma.DMA(self.ps7.s_axi_hp0)
self.csr_devices.append("rtio_dma")
self.submodules.cri_con = rtio.CRIInterconnectShared(
[self.rtio.cri, self.rtio_dma.cri],
[self.rtio_core.cri],
enable_routing=True,
)
self.csr_devices.append("cri_con")
self.submodules.rtio_moninj = rtio.MonInj(self.rtio_channels)
self.csr_devices.append("rtio_moninj")
self.submodules.rtio_analyzer = analyzer.Analyzer(
self.rtio_tsc, self.rtio_core.cri, self.ps7.s_axi_hp1
)
self.csr_devices.append("rtio_analyzer")
class BASE(EBAZ4205):
def __init__(self, rtio_clk, acpki):
EBAZ4205.__init__(self, rtio_clk, acpki)
VARIANTS = {cls.__name__.lower(): cls for cls in [BASE]}
def main():
parser = argparse.ArgumentParser(
description="ARTIQ port to the EBAZ4205 control card of Ebit E9+ BTC miner"
)
parser.add_argument(
"-r", default=None, help="build Rust interface into the specified file"
)
parser.add_argument(
"-m", default=None, help="build Rust memory interface into the specified file"
)
parser.add_argument(
"-c",
default=None,
help="build Rust compiler configuration into the specified file",
)
parser.add_argument(
"-g", default=None, help="build gateware into the specified directory"
)
parser.add_argument("--rtio-clk", default=125e6, help="RTIO Clock Frequency (Hz)")
parser.add_argument(
"-V",
"--variant",
default="base",
help="variant: " "[acpki_]base" "(default: %(default)s)",
)
args = parser.parse_args()
rtio_clk = int(args.rtio_clk)
variant = args.variant.lower()
acpki = variant.startswith("acpki_")
if acpki:
variant = variant[6:]
try:
cls = VARIANTS[variant]
except KeyError:
raise SystemExit("Invalid variant (-V/--variant)")
soc = cls(rtio_clk=rtio_clk, acpki=acpki)
soc.finalize()
if args.r is not None:
write_csr_file(soc, args.r)
if args.m is not None:
write_mem_file(soc, args.m)
if args.c is not None:
write_rustc_cfg_file(soc, args.c)
if args.g is not None:
soc.build(build_dir=args.g)
if __name__ == "__main__":
main()

View File

@ -560,7 +560,10 @@ class GenericSatellite(SoCCore):
self.submodules.local_io = SyncRTIO(
self.rtio_tsc, self.rtio_channels, lane_count=description["sed_lanes"]
)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors)
self.comb += [
self.drtiosat.async_errors.eq(self.local_io.async_errors),
self.local_io.sed_spread_enable.eq(self.drtiosat.sed_spread_enable.storage)
]
self.submodules.cri_con = rtio.CRIInterconnectShared(
[self.drtiosat.cri, self.rtio_dma.cri, self.rtio.cri],

View File

@ -25,6 +25,8 @@ import analyzer
import acpki
import drtio_aux_controller
import zynq_clocking
import cxp_4r_fmc
import cxp
from config import write_csr_file, write_mem_file, write_rustc_cfg_file
class SMAClkinForward(Module):
@ -138,7 +140,7 @@ class ZC706(SoCCore):
platform.add_extension(si5324_fmc33)
self.comb += platform.request("si5324_33").rst_n.eq(1)
cdr_clk = Signal()
self.cdr_clk = Signal()
cdr_clk_buf = Signal()
si5324_out = platform.request("si5324_clkout")
platform.add_period_constraint(si5324_out.p, 8.0)
@ -146,11 +148,11 @@ class ZC706(SoCCore):
Instance("IBUFDS_GTE2",
i_CEB=0,
i_I=si5324_out.p, i_IB=si5324_out.n,
o_O=cdr_clk,
o_O=self.cdr_clk,
p_CLKCM_CFG="TRUE",
p_CLKRCV_TRST="TRUE",
p_CLKSWING_CFG=3),
Instance("BUFG", i_I=cdr_clk, o_O=cdr_clk_buf)
Instance("BUFG", i_I=self.cdr_clk, o_O=cdr_clk_buf)
]
self.config["HAS_SI5324"] = None
self.config["SI5324_AS_SYNTHESIZER"] = None
@ -487,6 +489,10 @@ class _SatelliteBase(SoCCore):
self.csr_devices.append("rtio_dma")
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels)
self.comb += [
self.drtiosat.async_errors.eq(self.local_io.async_errors),
self.local_io.sed_spread_enable.eq(self.drtiosat.sed_spread_enable.storage)
]
self.submodules.cri_con = rtio.CRIInterconnectShared(
[self.drtiosat.cri, self.rtio_dma.cri, self.rtio.cri],
[self.local_io.cri] + self.drtio_cri,
@ -648,6 +654,119 @@ class _NIST_QC2_RTIO:
self.add_rtio(rtio_channels)
class CXP_FMC():
"""
CoaXpress FMC with 4 CXP channel and 1 SMA trigger
"""
def __init__(self):
platform = self.platform
platform.add_extension(cxp_4r_fmc.fmc_adapter_io)
platform.add_extension(leds_fmc33)
debug_sma = [
("user_sma_clock_33", 0,
Subsignal("p_tx", Pins("AD18"), IOStandard("LVCMOS33")),
Subsignal("n_rx", Pins("AD19"), IOStandard("LVCMOS33")),
),
]
pmod1_33 = [
("pmod1_33", 0, Pins("AJ21"), IOStandard("LVCMOS33")),
("pmod1_33", 1, Pins("AK21"), IOStandard("LVCMOS33")),
("pmod1_33", 2, Pins("AB21"), IOStandard("LVCMOS33")),
("pmod1_33", 3, Pins("AB16"), IOStandard("LVCMOS33")),
("pmod1_33", 4, Pins("Y20"), IOStandard("LVCMOS33")),
("pmod1_33", 5, Pins("AA20"), IOStandard("LVCMOS33")),
("pmod1_33", 6, Pins("AC18"), IOStandard("LVCMOS33")),
("pmod1_33", 7, Pins("AC19"), IOStandard("LVCMOS33")),
]
platform.add_extension(debug_sma)
platform.add_extension(pmod1_33)
debug_sma_pad = platform.request("user_sma_clock_33")
pmod_pads = [platform.request("pmod1_33", i) for i in range(8)]
clk_freq = 125e6
links = 4
master_ch = 0
cxp_downconn_pads = [platform.request("CXP_HS", i) for i in range(links)]
cxp_upconn_pads = [platform.request("CXP_LS", i) for i in range(links)]
self.submodules.cxp_phys = cxp_phys = cxp.CXP_PHYS(
refclk=self.cdr_clk,
upconn_pads=cxp_upconn_pads,
downconn_pads=cxp_downconn_pads,
sys_clk_freq=clk_freq,
master=master_ch,
)
self.csr_devices.append("cxp_phys")
rtio_channels = []
cxp_csr_group = []
cxp_mem_group = []
cxp_core_pipelines = []
for i, phy in enumerate(cxp_phys.phys):
cxp_name = "cxp" + str(i)
core = cxp.CXP_Core(phy)
setattr(self.submodules, cxp_name, core)
self.csr_devices.append(cxp_name)
cxp_csr_group.append(cxp_name)
cxp_core_pipelines.append(core)
# Add memory group
mem_name = "cxp_" + str(i) + "_mem"
cxp_mem_group.append(mem_name)
mem_size = core.get_mem_size()
# upper half is tx while lower half is rx
memory_address = self.axi2csr.register_port(core.get_tx_port(), mem_size)
self.axi2csr.register_port(core.get_rx_port(), mem_size)
self.add_memory_region(mem_name, self.mem_map["csr"] + memory_address, mem_size * 2)
self.add_memory_group("cxp_mem", cxp_mem_group)
self.add_csr_group("cxp", cxp_csr_group)
self.submodules.cxp_frame_pipeline = cxp_frame_pipeline = cxp.CXP_Frame_Pipeline(cxp_core_pipelines, pmod_pads, master=master_ch)
self.csr_devices.append("cxp_frame_pipeline")
print("CoaXPress at RTIO channel 0x{:06x}".format(len(rtio_channels)))
# rtio_channels.append(rtio.Channel.from_phy(cxp_frame_pipeline ))
rtio_channels += [
rtio.Channel(cxp_frame_pipeline.trigger),
rtio.Channel(cxp_frame_pipeline.config),
rtio.Channel(cxp_frame_pipeline.gate_data),
]
# max freq of cxp_gtx_rx = linerate/internal_datawidth = 12.5Gbps/40 = 312.5MHz
# zc706 use speed grade 2 which only support up to 10.3125Gbps (4ns)
# pushing to 12.5Gbps (3.2ns) will result in Pulse width violation but setup/hold times are met
rx = cxp_phys.phys[0].rx
platform.add_period_constraint(rx.gtx.cd_cxp_gtx_rx.clk, 3.2)
# constraint the CLK path
platform.add_false_path_constraints(self.sys_crg.cd_sys.clk, rx.gtx.cd_cxp_gtx_rx.clk)
# FIXME remove this placeholder RTIO channel
# There are too few RTIO channels and cannot be compiled (adr width issue of the lane distributor)
# see https://github.com/m-labs/artiq/pull/2158 for similar issue
print("USER LED at RTIO channel 0x{:06x}".format(len(rtio_channels)))
phy = ttl_simple.Output(self.platform.request("user_led_33", 0))
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy))
self.config["HAS_RTIO_LOG"] = None
rtio_channels.append(rtio.LogChannel())
self.config["RTIO_LOG_CHANNEL"] = len(rtio_channels)
self.add_rtio(rtio_channels)
class NIST_CLOCK(ZC706, _NIST_CLOCK_RTIO):
def __init__(self, acpki, drtio100mhz):
ZC706.__init__(self, acpki)
@ -680,8 +799,13 @@ class NIST_QC2_Satellite(_SatelliteBase, _NIST_QC2_RTIO):
_SatelliteBase.__init__(self, acpki, drtio100mhz)
_NIST_QC2_RTIO.__init__(self)
class CXP_Demo(ZC706, CXP_FMC):
def __init__(self, acpki, drtio100mhz):
ZC706.__init__(self, acpki)
CXP_FMC.__init__(self)
VARIANTS = {cls.__name__.lower(): cls for cls in [NIST_CLOCK, NIST_CLOCK_Master, NIST_CLOCK_Satellite,
NIST_QC2, NIST_QC2_Master, NIST_QC2_Satellite]}
NIST_QC2, NIST_QC2_Master, NIST_QC2_Satellite, CXP_Demo]}
def main():
parser = argparse.ArgumentParser(

View File

@ -10,6 +10,7 @@ name = "libboard_artiq"
[features]
target_zc706 = ["libboard_zynq/target_zc706", "libconfig/target_zc706"]
target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libconfig/target_kasli_soc"]
target_ebaz4205 = ["libboard_zynq/target_ebaz4205", "libconfig/target_ebaz4205"]
calibrate_wrpll_skew = []
[build-dependencies]
@ -23,6 +24,7 @@ core_io = { version = "0.1", features = ["collections"] }
embedded-hal = "0.2"
nb = "1.0"
void = { version = "1", default-features = false }
byteorder = { version = "1.3", default-features = false }
io = { path = "../libio", features = ["byteorder"] }
libboard_zynq = { path = "@@ZYNQ_RS@@/libboard_zynq" }

View File

@ -0,0 +1,396 @@
use core::{fmt, result::Result};
use embedded_hal::blocking::delay::DelayMs;
use libboard_zynq::timer::GlobalTimer;
use log::{info, warn};
use crate::{cxp_ctrl::{read_u32, read_u64, reset_tag, send_test_packet, write_bytes_no_ack, write_u32, write_u64},
cxp_phys::{self, CXP_CHANNELS, CXP_SPEED},
cxp_proto::Error as ProtoError,
pl::csr::CXP};
// Bootstrap registers address
const STANDARD: u32 = 0x0000;
const REVISION: u32 = 0x0004;
const CONNECTION_RESET: u32 = 0x4000;
const DEVICE_CONNECTION_ID: u32 = 0x4004;
const MASTER_HOST_CONNECTION_ID: u32 = 0x4008;
const CONTROL_PACKET_SIZE_MAX: u32 = 0x400C;
const STREAM_PACKET_SIZE_MAX: u32 = 0x4010;
const CONNECTION_CFG: u32 = 0x4014;
const CONNECTION_CFG_DEFAULT: u32 = 0x4018;
const TESTMODE: u32 = 0x401C;
const TEST_ERROR_COUNT_SELECTOR: u32 = 0x4020;
const TEST_ERROR_COUNT: u32 = 0x4024;
const TEST_PACKET_COUNT_TX: u32 = 0x4028;
const TEST_PACKET_COUNT_RX: u32 = 0x4030;
const VERSION_SUPPORTED: u32 = 0x4044;
const VERSION_USED: u32 = 0x4048;
// Setup const
// TEST: result
// Currently, multilane/channel is not working properly in gateware due to buffer overflow issue
// only single lane @ 0x800 max pak size or dual lane 256 max pak size is working
// tho the small lane size only masked the issue as it just create more overhead for the camera
// 0x800/16 OK for fifo size 256 or 32
// 0x800/8 NG for fifo size 256 + pixel fifo 32
// TODO: upgrate it to 2 KiB (0x800) after implementing the correct stream merger in gateware
// const MAX_STREAM_PAK_SIZE: u32 = 0x800; // 2 KiB
const MAX_STREAM_PAK_SIZE: u32 = 0x800 / 16;
const TX_TEST_CNT: u8 = 10;
pub const MASTER_CHANNEL: u8 = 0;
const HOST_CONNECTION_ID: u32 = 0xC001C0DE; // TODO: rename this
#[derive(Debug)]
pub enum Error {
CameraNotDetected,
ConnectionLost,
UnstableRX,
UnstableTX,
UnsupportedSpeed(u32),
UnsupportedTopology,
UnsupportedVersion,
Protocol(ProtoError),
}
impl From<ProtoError> for Error {
fn from(value: ProtoError) -> Error {
Error::Protocol(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::CameraNotDetected => write!(f, "CameraNotDetected"),
&Error::ConnectionLost => write!(f, "ConnectionLost Some active channels cannot be detected"),
&Error::UnstableRX => write!(f, "UnstableRX RX connection test failed"),
&Error::UnstableTX => write!(f, "UnstableTX TX connection test failed"),
&Error::UnsupportedSpeed(linerate_code) => write!(
f,
"UnsupportedSpeed {:#X} linerate code is not supported",
linerate_code
),
&Error::UnsupportedTopology => write!(
f,
"UnsupportedTopology CH#1 should be the master channel while CH#2-4 should be connected to extension \
#1-3 respectively"
),
&Error::UnsupportedVersion => write!(
f,
"UnsupportedVersion Cannot find a compatible protocol version between the grabber & camera"
),
&Error::Protocol(ref err) => write!(f, "ProtocolError {}", err),
}
}
}
pub fn setup() -> Result<bool, Error> {
// assume timer was initialized successfully
let mut timer = unsafe { GlobalTimer::get() };
camera_setup(&mut timer)
}
fn scan_active_channels() -> u8 {
let mut active_channels: u8 = 0;
for ch in 0..CXP_CHANNELS {
if unsafe { (CXP[ch as usize].rx_ready_read)() } == 1 {
// info!("ch#{} is up <---------------------------------", ch);
active_channels += 1;
}
}
active_channels
}
fn discover_camera(timer: &mut GlobalTimer) -> Result<(), Error> {
// Section 7.6 (CXP-001-2021)
// 1.25Gbps (CXP_1) and 3.125Gbps (CXP_3) are the discovery rate
// both linerate need to be checked as camera only support ONE of discovery rates
for speed in [CXP_SPEED::CXP_1, CXP_SPEED::CXP_3].iter() {
cxp_phys::change_linerate(*speed);
// Section 12.1.2 (CXP-001-2021)
// send ConnectionReset on all channels -> wait 200ms -> scan for active channels
for ch in 0..CXP_CHANNELS {
write_bytes_no_ack(ch, CONNECTION_RESET, &1_u32.to_be_bytes(), false)?;
}
timer.delay_ms(200);
if scan_active_channels() > 0 {
return Ok(());
}
}
Err(Error::CameraNotDetected)
}
fn check_master_channel() -> Result<(), Error> {
if read_u32(MASTER_CHANNEL, DEVICE_CONNECTION_ID, false)? == 0 {
Ok(())
} else {
warn!(
"Channel #{} is not connected to master channel of the camera",
MASTER_CHANNEL
);
Err(Error::UnsupportedTopology)
}
}
fn check_connection_topology(active_channels: u8) -> Result<(), Error> {
// only the simple topology MASTER:ch0, extension:ch1,2,3 is supported right now
for ch in 0..active_channels {
if read_u32(ch, DEVICE_CONNECTION_ID, false)? != ch as u32 {
warn!("Channel #{} is not connected to the right port of the camera", ch);
return Err(Error::UnsupportedTopology);
};
match ch {
0 => info!("CHANNEL #{} is active - master connection", ch),
_ => info!("CHANNEL #{} is active - extension connection", ch),
}
}
Ok(())
}
fn negotiate_active_channels(timer: &mut GlobalTimer) -> Result<u8, Error> {
let mut available_chs = read_u32(MASTER_CHANNEL, CONNECTION_CFG_DEFAULT, false)? >> 16;
available_chs = available_chs.min(CXP_CHANNELS as u32);
// activate channels on camera but preserve the discovery linerate
let current_cfg = read_u32(MASTER_CHANNEL, CONNECTION_CFG, false)?;
write_u32(
MASTER_CHANNEL,
CONNECTION_CFG,
current_cfg & 0xFFFF | available_chs << 16,
false,
)?;
timer.delay_ms(200);
let active_channels = scan_active_channels();
check_connection_topology(active_channels)?;
if available_chs > active_channels as u32 {
info!(
"Only detected {} channel(s), disabling excess channels on camera",
active_channels
);
write_u32(
MASTER_CHANNEL,
CONNECTION_CFG,
current_cfg & 0xFFFF | ((active_channels as u32) << 16),
false,
)?;
timer.delay_ms(200);
// check no active channels are down after the cfg change
if active_channels != scan_active_channels() {
return Err(Error::ConnectionLost);
}
}
Ok(active_channels)
}
fn set_host_connection_id() -> Result<(), Error> {
// TODO: not mandatory??
write_u32(MASTER_CHANNEL, MASTER_HOST_CONNECTION_ID, HOST_CONNECTION_ID, false)?;
let reg = read_u32(MASTER_CHANNEL, MASTER_HOST_CONNECTION_ID, false)?;
info!("host connection id set as = {}", reg);
Ok(())
}
fn negotiate_cxp_version() -> Result<bool, Error> {
let rev = read_u32(MASTER_CHANNEL, REVISION, false)?;
let mut major_rev: u32 = rev >> 16;
let mut minor_rev: u32 = rev & 0xFF;
info!("Camera's CXP revision is {}.{}", major_rev, minor_rev);
// Section 12.1.4 (CXP-001-2021)
// For CXP 2.0 and onward, Host need to check the VersionSupported register to determine
// the highest common version that supported by both device & host
if major_rev >= 2 {
let reg = read_u32(MASTER_CHANNEL, VERSION_SUPPORTED, false)?;
if ((reg >> 3) & 1) == 1 {
major_rev = 2;
minor_rev = 1;
} else if ((reg >> 2) & 1) == 1 {
major_rev = 2;
minor_rev = 0;
} else {
return Err(Error::UnsupportedVersion);
}
write_u32(MASTER_CHANNEL, VERSION_USED, major_rev << 16 | minor_rev, false)?;
}
info!(
"Camera supports CXP {}.{}, using CXP {}.{} protcol now",
major_rev, minor_rev, major_rev, minor_rev
);
Ok(major_rev >= 2)
}
fn negotiate_pak_max_size(with_tag: bool) -> Result<(), Error> {
let reg = read_u32(MASTER_CHANNEL, CONTROL_PACKET_SIZE_MAX, with_tag)?;
info!("Max CTRL PAK size = {:#010X}", reg);
let reg = read_u32(MASTER_CHANNEL, STREAM_PACKET_SIZE_MAX, with_tag)?;
info!("Max STREAM PAK size = {:#010X}", reg);
write_u32(MASTER_CHANNEL, STREAM_PACKET_SIZE_MAX, MAX_STREAM_PAK_SIZE, with_tag)?;
let reg = read_u32(MASTER_CHANNEL, STREAM_PACKET_SIZE_MAX, with_tag)?;
info!("Max STREAM PAK size = {:#010X}", reg);
Ok(())
}
fn check_magic_word() -> Result<(), Error> {
let reg = read_u32(MASTER_CHANNEL, STANDARD, true)?;
info!("CoaXPress magic WORD = {:#010X} !!!", reg);
Ok(())
}
fn decode_cxp_speed(linerate_code: u32) -> Option<CXP_SPEED> {
match linerate_code {
0x28 => Some(CXP_SPEED::CXP_1),
0x30 => Some(CXP_SPEED::CXP_2),
0x38 => Some(CXP_SPEED::CXP_3),
0x40 => Some(CXP_SPEED::CXP_5),
0x48 => Some(CXP_SPEED::CXP_6),
0x50 => Some(CXP_SPEED::CXP_10),
0x58 => Some(CXP_SPEED::CXP_12),
_ => None,
}
}
fn set_operation_linerate(active_channels: u8, with_tag: bool, timer: &mut GlobalTimer) -> Result<(), Error> {
let current_cfg = read_u32(MASTER_CHANNEL, CONNECTION_CFG, with_tag)?;
info!("Current connection cfg = {:#010X}", current_cfg);
let recommended_linerate_code = read_u32(MASTER_CHANNEL, CONNECTION_CFG_DEFAULT, with_tag)? & 0xFFFF;
info!("recommended_linerate_code = {:#010X}", recommended_linerate_code);
if let Some(speed) = decode_cxp_speed(recommended_linerate_code) {
// preserve the number of active channels
write_u32(
MASTER_CHANNEL,
CONNECTION_CFG,
current_cfg & 0xFFFF0000 | recommended_linerate_code,
with_tag,
)?;
cxp_phys::change_linerate(speed);
timer.delay_ms(200);
// check no active channels are down after linerate change
if scan_active_channels() == active_channels {
Ok(())
} else {
Err(Error::ConnectionLost)
}
} else {
Err(Error::UnsupportedSpeed(recommended_linerate_code))
}
}
fn test_counter_reset(channel: u8, with_tag: bool) -> Result<(), Error> {
unsafe { (CXP[channel as usize].rx_test_counts_reset_write)(1) };
write_u32(MASTER_CHANNEL, TEST_ERROR_COUNT_SELECTOR, channel as u32, with_tag)?;
write_u32(MASTER_CHANNEL, TEST_ERROR_COUNT, 0, with_tag)?;
write_u64(MASTER_CHANNEL, TEST_PACKET_COUNT_TX, 0, with_tag)?;
write_u64(MASTER_CHANNEL, TEST_PACKET_COUNT_RX, 0, with_tag)?;
Ok(())
}
fn verify_test_result(channel: u8, with_tag: bool) -> Result<(), Error> {
write_u32(MASTER_CHANNEL, TEST_ERROR_COUNT_SELECTOR, channel as u32, with_tag)?;
// Section 9.9.3 (CXP-001-2021)
// verify grabber -> camera connection test result
if read_u64(MASTER_CHANNEL, TEST_PACKET_COUNT_RX, with_tag)? != TX_TEST_CNT as u64 {
return Err(Error::UnstableTX);
};
if read_u32(MASTER_CHANNEL, TEST_ERROR_COUNT, with_tag)? > 0 {
return Err(Error::UnstableTX);
};
// Section 9.9.4 (CXP-001-2021)
// verify camera -> grabber connection test result
let camera_test_pak_cnt = read_u64(MASTER_CHANNEL, TEST_PACKET_COUNT_TX, true)?;
unsafe {
if (CXP[channel as usize].rx_test_packet_counter_read)() != camera_test_pak_cnt as u16 {
info!(
"CHANNEL #{} test packet cnt = {}",
channel,
(CXP[channel as usize].rx_test_packet_counter_read)()
);
return Err(Error::UnstableRX);
};
if (CXP[channel as usize].rx_test_error_counter_read)() > 0 {
info!(
"CHANNEL #{} test packet error cnt = {}",
channel,
(CXP[channel as usize].rx_test_error_counter_read)()
);
return Err(Error::UnstableRX);
};
};
info!("CHANNEL #{} pass testing", channel);
Ok(())
}
fn test_channels_stability(active_channels: u8, with_tag: bool, timer: &mut GlobalTimer) -> Result<(), Error> {
// let active_channels = active_channels as usize;
for ch in 0..active_channels {
test_counter_reset(ch, with_tag)?;
}
// grabber -> camera connection test
for ch in 0..active_channels {
for _ in 0..TX_TEST_CNT {
send_test_packet(ch)?;
// sending the whole test sequence @ 20.833Mbps will take a minimum of 1.972ms
// and leave some room to send IDLE word
timer.delay_ms(2);
}
}
// camera -> grabber connection test
// enabling the TESTMODE on master channel will send test packets on all channels
// and ctrl packet write overhead is used as a delay
write_u32(MASTER_CHANNEL, TESTMODE, 1, with_tag)?;
write_u32(MASTER_CHANNEL, TESTMODE, 0, with_tag)?;
for ch in 0..active_channels {
verify_test_result(ch, with_tag)?;
}
Ok(())
}
fn camera_setup(timer: &mut GlobalTimer) -> Result<bool, Error> {
reset_tag();
discover_camera(timer)?;
check_master_channel()?;
let active_channels = negotiate_active_channels(timer)?;
set_host_connection_id()?;
let cxp_v2_or_greater = negotiate_cxp_version()?;
negotiate_pak_max_size(cxp_v2_or_greater)?;
set_operation_linerate(active_channels, cxp_v2_or_greater, timer)?;
// DEBUG: print
check_magic_word()?;
test_channels_stability(active_channels, cxp_v2_or_greater, timer)?;
// unsafe{
// // don't set two identical routingid, the router cannot handle it
// // it can handle it after adding the priority decoder
// cxp_frame_pipeline::buffer_1_routingid_write(0x00);
// }
Ok(cxp_v2_or_greater)
}

View File

@ -0,0 +1,261 @@
use core::slice;
use byteorder::{ByteOrder, NetworkEndian};
use io::Cursor;
use libboard_zynq::{println, time::Milliseconds, timer::GlobalTimer};
use crate::{cxp_proto::{print_packet, Error, RXPacket, TXPacket, DATA_MAXSIZE},
mem::mem::CXP_MEM,
pl::csr::CXP};
const BUF_LEN: usize = 0x800;
const TRANSMISSION_TIMEOUT: u64 = 200;
fn packet_pending(channel: u8) -> bool {
unsafe { (CXP[channel as usize].rx_pending_packet_read)() == 1 }
}
fn receive(channel: u8) -> Result<Option<RXPacket>, Error> {
if packet_pending(channel) {
let channel = channel as usize;
unsafe {
let read_buffer_ptr = (CXP[channel].rx_read_ptr_read)() as usize;
println!("buffer ptr = {}", read_buffer_ptr);
let ptr = (CXP_MEM[channel].base + CXP_MEM[channel].size / 2 + read_buffer_ptr * BUF_LEN) as *mut u32;
let mut reader = Cursor::new(slice::from_raw_parts_mut(ptr as *mut u8, BUF_LEN));
let packet_type = (CXP[channel].rx_packet_type_read)();
println!("packet_type = {}", packet_type);
// DEBUG:
// println!("RX MEM before reading");
// print_packet(&reader.get_ref()[0..40]);
let packet = RXPacket::read_from(&mut reader, packet_type);
println!("{:X?}", packet);
(CXP[channel].rx_pending_packet_write)(1);
Ok(Some(packet?))
}
} else {
Ok(None)
}
}
fn receive_timeout(channel: u8, timeout_ms: u64) -> Result<RXPacket, Error> {
// assume timer was initialized successfully
let timer = unsafe { GlobalTimer::get() };
let limit = timer.get_time() + Milliseconds(timeout_ms);
while timer.get_time() < limit {
match receive(channel)? {
None => (),
Some(packet) => return Ok(packet),
}
}
Err(Error::TimedOut)
}
fn send_data_packet(channel: u8, packet: &TXPacket) -> Result<(), Error> {
// assume tx is enabled
let channel = channel as usize;
unsafe {
while (CXP[channel].tx_writer_busy_read)() == 1 {}
let ptr = CXP_MEM[channel].base as *mut u32;
let mut writer = Cursor::new(slice::from_raw_parts_mut(ptr as *mut u8, BUF_LEN));
packet.write_to(&mut writer)?;
// DEBUG:
// println!("TX MEM after writing");
// print_packet(&writer.get_ref()[0..40]);
(CXP[channel].tx_writer_word_len_write)(writer.position() as u16 / 4);
(CXP[channel].tx_writer_stb_write)(1);
}
Ok(())
}
pub fn send_test_packet(channel: u8) -> Result<(), Error> {
// assume tx is enabled
let channel = channel as usize;
unsafe {
while (CXP[channel].tx_writer_busy_read)() == 1 {}
(CXP[channel].tx_writer_stb_testseq_write)(1);
}
Ok(())
}
//
//
// DEBUG:
//
//
pub fn rx_debug_mem_print(channel: u8) {
unsafe {
let ptr = CXP_MEM[channel as usize].base as *mut u32;
let arr = slice::from_raw_parts_mut(ptr as *mut u8, BUF_LEN * 4);
print_packet(arr);
}
}
pub fn print_decode_error(channel: u8) {
unsafe {
println!(
"CH#{} Decode error = {}",
channel,
(CXP[channel as usize].rx_reader_decode_err_read)()
);
}
}
//
//
// CTRL packet
//
//
// Section 9.6.1.2 (CXP-001-2021)
// CTRL packet need to be tagged for CXP 2.0 or greater
static mut TAG: u8 = 0;
pub fn reset_tag() {
unsafe { TAG = 0 }
}
fn increment_tag() {
unsafe { TAG = TAG.wrapping_add(1) };
}
fn check_tag(tag: Option<u8>) -> Result<(), Error> {
unsafe {
if tag.is_some() && tag != Some(TAG) {
Err(Error::TagMismatch)
} else {
Ok(())
}
}
}
fn check_length(length: u32) -> Result<(), Error> {
if length > DATA_MAXSIZE as u32 || length == 0 {
Err(Error::LengthOutOfRange)
} else {
Ok(())
}
}
fn process_ack_packet(channel: u8, timeout: u64) -> Result<(), Error> {
match receive_timeout(channel, timeout) {
Ok(RXPacket::CtrlAck { tag }) => {
check_tag(tag)?;
Ok(())
}
Ok(RXPacket::CtrlDelay { tag, time }) => {
check_tag(tag)?;
// info!("delaying by {} ms ....", time);
process_ack_packet(channel, time as u64)
}
Ok(_) => Err(Error::UnexpectedReply),
Err(e) => Err(e),
}
}
fn process_reply_packet(channel: u8, timeout: u64, expected_length: u32) -> Result<[u8; DATA_MAXSIZE], Error> {
match receive_timeout(channel, timeout) {
Ok(RXPacket::CtrlReply { tag, length, data }) => {
check_tag(tag)?;
if length != expected_length {
return Err(Error::UnexpectedReply);
};
Ok(data)
}
Ok(RXPacket::CtrlDelay { tag, time }) => {
check_tag(tag)?;
// info!("delaying by {} ms ....", time);
process_reply_packet(channel, time as u64, expected_length)
}
Ok(_) => Err(Error::UnexpectedReply),
Err(e) => Err(e),
}
}
pub fn write_bytes_no_ack(channel: u8, addr: u32, val: &[u8], with_tag: bool) -> Result<(), Error> {
let length = val.len() as u32;
check_length(length)?;
let mut data: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
data[..length as usize].clone_from_slice(val);
let tag: Option<u8> = if with_tag { Some(unsafe { TAG }) } else { None };
send_data_packet(
channel,
&TXPacket::CtrlWrite {
tag,
addr,
length,
data,
},
)
}
pub fn write_bytes(channel: u8, addr: u32, val: &[u8], with_tag: bool) -> Result<(), Error> {
write_bytes_no_ack(channel, addr, val, with_tag)?;
process_ack_packet(channel, TRANSMISSION_TIMEOUT)?;
// DEBUG
print_decode_error(channel);
if with_tag {
increment_tag();
};
Ok(())
}
pub fn write_u32(channel: u8, addr: u32, val: u32, with_tag: bool) -> Result<(), Error> {
write_bytes(channel, addr, &val.to_be_bytes(), with_tag)
}
pub fn write_u64(channel: u8, addr: u32, val: u64, with_tag: bool) -> Result<(), Error> {
write_bytes(channel, addr, &val.to_be_bytes(), with_tag)
}
fn read(channel: u8, addr: u32, length: u32, with_tag: bool) -> Result<(), Error> {
check_length(length)?;
let tag: Option<u8> = if with_tag { Some(unsafe { TAG }) } else { None };
send_data_packet(channel, &TXPacket::CtrlRead { tag, addr, length })
}
pub fn read_bytes(channel: u8, addr: u32, bytes: &mut [u8], with_tag: bool) -> Result<(), Error> {
let length = bytes.len() as u32;
read(channel, addr, length, with_tag)?;
let data = process_reply_packet(channel, TRANSMISSION_TIMEOUT, length)?;
bytes.clone_from_slice(&data[..length as usize]);
// DEBUG
print_decode_error(channel);
if with_tag {
increment_tag();
};
Ok(())
}
pub fn read_u32(channel: u8, addr: u32, with_tag: bool) -> Result<u32, Error> {
let mut bytes: [u8; 4] = [0; 4];
read_bytes(channel, addr, &mut bytes, with_tag)?;
let val = NetworkEndian::read_u32(&bytes);
Ok(val)
}
pub fn read_u64(channel: u8, addr: u32, with_tag: bool) -> Result<u64, Error> {
let mut bytes: [u8; 8] = [0; 8];
read_bytes(channel, addr, &mut bytes, with_tag)?;
let val = NetworkEndian::read_u64(&bytes);
Ok(val)
}

View File

@ -0,0 +1,89 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::{println, timer::GlobalTimer};
use log::info;
use crate::{cxp_phys, pl::csr::CXP};
pub fn loopback_testing(channel: usize, timer: &mut GlobalTimer, speed: cxp_phys::CXP_SPEED) {
println!("==============================================================================");
cxp_phys::change_linerate(speed);
unsafe {
info!("waiting for tx&rx setup...");
timer.delay_us(50_000);
// info!(
// "tx_phaligndone = {} | rx_phaligndone = {}",
// (CXP[channel].rx_txinit_phaligndone_read)(),
// (CXP[channel].rx_rxinit_phaligndone_read)(),
// );
// enable txdata tranmission thought MGTXTXP, required by PMA loopback
// (CXP[channel].rx_txenable_write)(1);
info!("waiting for rx to align...");
while (CXP[channel].rx_ready_read)() != 1 {}
info!("rx ready!");
// cxp_proto::rx_send_test_packet(channel);
// FIXME: why test + trig ack doesn't work well for rx??
// cxp_proto::rx_debug_send_trig_ack(channel);
// const DATA_MAXSIZE: usize = 253;
// let data_size = 4; // no. of bytes
// let data: u32 = 0xDADA as u32;
// let mut data_slice: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
// data_slice[..4].clone_from_slice(&data.to_be_bytes());
// cxp_proto::rx_debug_send(
// channel,
// &cxp_proto::UpConnPacket::Event {
// conn_id: 0x1234_5678_u32,
// packet_tag: 0x69_u8,
// length: data_size + 3,
// event_size: data_size,
// namespace: 0x02_u8,
// event_id: 0x00_6969u16,
// timestamp: 0x1234_5678u64,
// data: data_slice,
// },
// )
// .expect("loopback gtx tx error");
// timer.delay_us(1000); // wait packet has arrive at RX async fifo
// if (CXP[channel].rx_trigger_ack_read)() == 1 {
// (CXP[channel].rx_trigger_ack_write)(1);
// info!("trig ack and cleared");
// }
// if (CXP[channel].rx_bootstrap_decoder_err_read)() == 1 {
// info!("!!!!!!!DECODER ERROR!!!!!!! and cleared");
// (CXP[channel].rx_bootstrap_decoder_err_write)(1);
// }
// info!("packet type = {:#06X}", (CXP[channel].rx_packet_type_read)());
// // cxp_proto::receive(channel as u8).expect("loopback gtx rx error");
// // cxp_proto::rx_debug_mem_print(channel);
// // DEBUG: print loopback packets
// const LEN: usize = 20;
// let mut pak_arr: [u32; LEN] = [0; LEN];
// let mut k_arr: [u8; LEN] = [0; LEN];
// let mut i: usize = 0;
// while (CXP[channel].rx_debug_out_dout_valid_read)() == 1 {
// pak_arr[i] = (CXP[channel].rx_debug_out_dout_pak_read)();
// k_arr[i] = (CXP[channel].rx_debug_out_kout_pak_read)();
// // println!("received {:#04X}", pak_arr[i]);
// (CXP[channel].rx_debug_out_inc_write)(1);
// i += 1;
// if i == LEN {
// break;
// }
// }
// info!("rx ready = {}", (CXP[channel].rx_rx_ready_read)());
// // cxp_proto::print_packetu32(&pak_arr, &k_arr);
}
}

View File

@ -0,0 +1,215 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::timer::GlobalTimer;
use log::info;
use crate::pl::{csr, csr::CXP};
pub const CXP_CHANNELS: u8 = csr::CXP_LEN as u8;
#[derive(Clone, Copy, Debug)]
#[allow(non_camel_case_types)]
pub enum CXP_SPEED {
CXP_1,
CXP_2,
CXP_3,
CXP_5,
CXP_6,
CXP_10,
CXP_12,
}
pub fn setup(timer: &mut GlobalTimer) {
rx::setup(timer);
tx::setup();
change_linerate(CXP_SPEED::CXP_1);
}
pub fn change_linerate(speed: CXP_SPEED) {
info!("Changing all channels datarate to {:?}", speed);
rx::change_linerate(speed);
tx::change_linerate(speed);
}
mod tx {
use super::*;
pub fn setup() {
unsafe {
csr::cxp_phys::tx_enable_write(1);
}
}
pub fn change_linerate(speed: CXP_SPEED) {
unsafe {
match speed {
CXP_SPEED::CXP_1 | CXP_SPEED::CXP_2 | CXP_SPEED::CXP_3 | CXP_SPEED::CXP_5 | CXP_SPEED::CXP_6 => {
csr::cxp_phys::tx_bitrate2x_enable_write(0);
}
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => {
csr::cxp_phys::tx_bitrate2x_enable_write(1);
}
};
csr::cxp_phys::tx_clk_reset_write(1);
}
}
}
mod rx {
use super::*;
pub fn setup(timer: &mut GlobalTimer) {
unsafe {
csr::cxp_phys::rx_qpll_reset_write(1);
info!("waiting for QPLL/CPLL to lock...");
while csr::cxp_phys::rx_qpll_locked_read() != 1 {}
info!("QPLL locked");
csr::cxp_phys::rx_gtx_start_init_write(1);
// DEBUG: printout
info!("waiting for rx setup...");
timer.delay_us(50_000);
for ch in 0..CXP_CHANNELS {
info!("rx_phaligndone = {}", (CXP[ch as usize].rx_rxinit_phaligndone_read)());
}
}
}
pub fn change_linerate(speed: CXP_SPEED) {
change_qpll_fb_divider(speed);
change_gtx_divider(speed);
change_cdr_cfg(speed);
unsafe {
csr::cxp_phys::rx_qpll_reset_write(1);
info!("waiting for QPLL/CPLL to lock...");
while csr::cxp_phys::rx_qpll_locked_read() != 1 {}
info!("QPLL locked");
csr::cxp_phys::rx_gtx_restart_write(1);
}
}
fn change_qpll_fb_divider(speed: CXP_SPEED) {
let qpll_div_reg = match speed {
CXP_SPEED::CXP_1 | CXP_SPEED::CXP_2 | CXP_SPEED::CXP_5 | CXP_SPEED::CXP_10 => 0x0120, // FB_Divider = 80, QPLL VCO @ 10GHz
CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 | CXP_SPEED::CXP_12 => 0x0170, // FB_Divider = 100, QPLL VCO @ 12.5GHz
};
// DEBUG:
// println!("QPLL DRP:");
// println!("0x36 = {:#06x}", qpll_read(0x36));
qpll_write(0x36, qpll_div_reg);
// println!("0x36 = {:#06x}", qpll_read(0x36));
}
fn change_gtx_divider(speed: CXP_SPEED) {
let div_reg = match speed {
CXP_SPEED::CXP_1 => 0x33, // RXOUT_DIV = 8
CXP_SPEED::CXP_2 | CXP_SPEED::CXP_3 => 0x22, // RXOUT_DIV = 4
CXP_SPEED::CXP_5 | CXP_SPEED::CXP_6 => 0x11, // RXOUT_DIV = 2
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => 0x00, // RXOUT_DIV = 1
};
// DEBUG:
// println!("RX GTX DRP:");
for ch in 0..CXP_CHANNELS {
// println!("channel {}, 0x88 = {:#06x}", channel, gtx_read(channel, 0x88));
gtx_write(ch, 0x88, div_reg);
// println!("channel {}, 0x88 = {:#06x}", channel, gtx_read(channel, 0x88));
}
}
fn change_cdr_cfg(speed: CXP_SPEED) {
struct CdrConfig {
pub cfg_reg0: u16, // addr = 0xA8
pub cfg_reg1: u16, // addr = 0xA9
pub cfg_reg2: u16, // addr = 0xAA
pub cfg_reg3: u16, // addr = 0xAB
pub cfg_reg4: u16, // addr = 0xAC
}
let cdr_cfg = match speed {
// when RXOUT_DIV = 8
CXP_SPEED::CXP_1 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1008,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV = 4
CXP_SPEED::CXP_2 | CXP_SPEED::CXP_5 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1010,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 2
CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1020,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 1
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1040,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x000B,
},
};
for channel in 0..CXP_CHANNELS {
gtx_write(channel, 0x0A8, cdr_cfg.cfg_reg0);
gtx_write(channel, 0x0A9, cdr_cfg.cfg_reg1);
gtx_write(channel, 0x0AA, cdr_cfg.cfg_reg2);
gtx_write(channel, 0x0AB, cdr_cfg.cfg_reg3);
gtx_write(channel, 0x0AC, cdr_cfg.cfg_reg4);
}
}
#[allow(dead_code)]
fn gtx_read(channel: u8, address: u16) -> u16 {
let channel = channel as usize;
unsafe {
(CXP[channel].rx_gtx_daddr_write)(address);
(CXP[channel].rx_gtx_dread_write)(1);
while (CXP[channel].rx_gtx_dready_read)() != 1 {}
(CXP[channel].rx_gtx_dout_read)()
}
}
fn gtx_write(channel: u8, address: u16, value: u16) {
let channel = channel as usize;
unsafe {
(CXP[channel].rx_gtx_daddr_write)(address);
(CXP[channel].rx_gtx_din_write)(value);
(CXP[channel].rx_gtx_din_stb_write)(1);
while (CXP[channel].rx_gtx_dready_read)() != 1 {}
}
}
#[allow(dead_code)]
fn qpll_read(address: u8) -> u16 {
unsafe {
csr::cxp_phys::rx_qpll_daddr_write(address);
csr::cxp_phys::rx_qpll_dread_write(1);
while csr::cxp_phys::rx_qpll_dready_read() != 1 {}
csr::cxp_phys::rx_qpll_dout_read()
}
}
fn qpll_write(address: u8, value: u16) {
unsafe {
csr::cxp_phys::rx_qpll_daddr_write(address);
csr::cxp_phys::rx_qpll_din_write(value);
csr::cxp_phys::rx_qpll_din_stb_write(1);
while csr::cxp_phys::rx_qpll_dready_read() != 1 {}
}
}
}

View File

@ -0,0 +1,423 @@
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};
use core_io::{Error as IoError, Read, Write};
use crc::crc32::checksum_ieee;
use io::Cursor;
use libboard_zynq::println;
const EV_MAXSIZE: usize = 253;
const CTRL_PACKET_MAXSIZE: usize = 128; // for compatibility with version1.x compliant Devices - Section 12.1.6 (CXP-001-2021)
pub const DATA_MAXSIZE: usize =
CTRL_PACKET_MAXSIZE - /*packet start KCodes, data packet types, CMD, Tag, Addr, CRC, packet end KCode*/4*7;
#[derive(Debug)]
pub enum Error {
CorruptedPacket,
CtrlAckError(u8),
Io(IoError),
LengthOutOfRange,
TagMismatch,
TimedOut,
UnexpectedReply,
UnknownPacket(u8),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::CorruptedPacket => write!(f, "CorruptedPacket Received packet fail CRC test"),
&Error::CtrlAckError(ref ack_code) => match ack_code {
0x40 => write!(f, "CtrlAckError Invalid Address"),
0x41 => write!(f, "CtrlAckError Invalid data for the address"),
0x42 => write!(f, "CtrlAckError Invalid operation code"),
0x43 => write!(f, "CtrlAckError Write attempted to a read-only address"),
0x44 => write!(f, "CtrlAckError Read attempted from a write-only address"),
0x45 => write!(f, "CtrlAckError Size field too large, exceed packet size limit"),
0x46 => write!(f, "CtrlAckError Message size is inconsistent with size field"),
0x47 => write!(f, "CtrlAckError Malformed packet"),
0x80 => write!(f, "CtrlAckError Failed CRC test in last received command"),
_ => write!(f, "CtrlAckError Unknown ack code {:#X}", ack_code),
},
&Error::Io(ref err) => write!(f, "IoError {:?}", err),
&Error::LengthOutOfRange => write!(f, "LengthOutOfRange Message Length is too long"),
&Error::TagMismatch => write!(f, "TagMismatch Received tag is different from the transmitted tag"),
&Error::TimedOut => write!(f, "MessageTimedOut"),
&Error::UnexpectedReply => write!(f, "UnexpectedReply"),
&Error::UnknownPacket(packet_type) => write!(f, "UnknownPacket with type id {:#X} ", packet_type),
}
}
}
impl From<IoError> for Error {
fn from(value: IoError) -> Error {
Error::Io(value)
}
}
fn get_cxp_crc(bytes: &[u8]) -> u32 {
// Section 9.2.2.2 (CXP-001-2021)
// Only Control packet need CRC32 appended in the end of the packet
// CoaXpress use the polynomial of IEEE-802.3 (Ethernet) CRC but the checksum calculation is different
(!checksum_ieee(bytes)).swap_bytes()
}
trait CxpRead {
fn read_u8(&mut self) -> Result<u8, Error>;
fn read_u16(&mut self) -> Result<u16, Error>;
fn read_u32(&mut self) -> Result<u32, Error>;
fn read_u64(&mut self) -> Result<u64, Error>;
fn read_exact_4x(&mut self, buf: &mut [u8]) -> Result<(), Error>;
fn read_4x_u8(&mut self) -> Result<u8, Error>;
fn read_4x_u16(&mut self) -> Result<u16, Error>;
fn read_4x_u32(&mut self) -> Result<u32, Error>;
}
impl<Cursor: Read> CxpRead for Cursor {
fn read_u8(&mut self) -> Result<u8, Error> {
let mut bytes = [0; 1];
self.read_exact(&mut bytes)?;
Ok(bytes[0])
}
fn read_u16(&mut self) -> Result<u16, Error> {
let mut bytes = [0; 2];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u16(&bytes))
}
fn read_u32(&mut self) -> Result<u32, Error> {
let mut bytes = [0; 4];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u32(&bytes))
}
fn read_u64(&mut self) -> Result<u64, Error> {
let mut bytes = [0; 8];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u64(&bytes))
}
fn read_exact_4x(&mut self, buf: &mut [u8]) -> Result<(), Error> {
for byte in buf {
// Section 9.2.2.1 (CXP-001-2021)
// decoder should immune to single bit errors when handling 4x duplicated characters
let a = self.read_u8()?;
let b = self.read_u8()?;
let c = self.read_u8()?;
let d = self.read_u8()?;
// vote and return majority
*byte = a & b & c | a & b & d | a & c & d | b & c & d;
}
Ok(())
}
fn read_4x_u8(&mut self) -> Result<u8, Error> {
let mut bytes = [0; 1];
self.read_exact_4x(&mut bytes)?;
Ok(bytes[0])
}
fn read_4x_u16(&mut self) -> Result<u16, Error> {
let mut bytes = [0; 2];
self.read_exact_4x(&mut bytes)?;
Ok(NetworkEndian::read_u16(&bytes))
}
fn read_4x_u32(&mut self) -> Result<u32, Error> {
let mut bytes = [0; 4];
self.read_exact_4x(&mut bytes)?;
Ok(NetworkEndian::read_u32(&bytes))
}
}
#[derive(Debug)]
pub enum NameSpace {
GenICam,
DeviceSpecific,
}
#[derive(Debug)]
pub enum RXPacket {
CtrlReply {
tag: Option<u8>,
length: u32,
data: [u8; DATA_MAXSIZE],
},
CtrlDelay {
tag: Option<u8>,
time: u32,
},
CtrlAck {
tag: Option<u8>,
},
Event {
conn_id: u32,
packet_tag: u8,
length: u16,
ev_size: u16,
namespace: NameSpace,
event_id: u16,
timestamp: u64,
ev: [u8; EV_MAXSIZE],
},
}
impl RXPacket {
pub fn read_from(reader: &mut Cursor<&mut [u8]>, packet_type: u8) -> Result<Self, Error> {
match packet_type {
0x03 => RXPacket::get_ctrl_packet(reader, false),
0x06 => RXPacket::get_ctrl_packet(reader, true),
0x07 => RXPacket::get_event_packet(reader),
_ => Err(Error::UnknownPacket(packet_type)),
}
}
fn get_ctrl_packet(reader: &mut Cursor<&mut [u8]>, with_tag: bool) -> Result<Self, Error> {
let mut tag: Option<u8> = None;
if with_tag {
tag = Some(reader.read_4x_u8()?);
}
let ackcode = reader.read_4x_u8()?;
match ackcode {
0x00 | 0x04 => {
let length = reader.read_u32()?;
let mut data: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
reader.read(&mut data[0..length as usize])?;
let checksum = get_cxp_crc(&reader.get_ref()[0..reader.position()]);
if reader.read_u32()? != checksum {
return Err(Error::CorruptedPacket);
}
if ackcode == 0x00 {
return Ok(RXPacket::CtrlReply { tag, length, data });
} else {
return Ok(RXPacket::CtrlDelay {
tag,
time: NetworkEndian::read_u32(&data[..4]),
});
}
}
0x01 => return Ok(RXPacket::CtrlAck { tag }),
_ => return Err(Error::CtrlAckError(ackcode)),
}
}
fn get_event_packet(reader: &mut Cursor<&mut [u8]>) -> Result<Self, Error> {
let conn_id = reader.read_4x_u32()?;
let packet_tag = reader.read_4x_u8()?;
let length = reader.read_4x_u16()?;
let ev_size = reader.read_u16()?;
if ev_size + 3 != length {
println!("length mismatch");
return Err(Error::CorruptedPacket);
}
let mut bytes = [0; 2];
reader.read_exact(&mut bytes)?;
let namespace_bits = (bytes[0] & 0xC0) >> 6;
let namespace = match namespace_bits {
0 => NameSpace::GenICam,
2 => NameSpace::DeviceSpecific,
_ => {
println!("namespace = {} error", namespace_bits);
return Err(Error::CorruptedPacket);
}
};
let event_id = (bytes[0] & 0xF) as u16 | (bytes[1] as u16);
let timestamp = reader.read_u64()?;
let mut ev: [u8; EV_MAXSIZE] = [0; EV_MAXSIZE];
reader.read(&mut ev[0..ev_size as usize])?;
let checksum = get_cxp_crc(&reader.get_ref()[0..reader.position()]);
if reader.read_u32()? != checksum {
println!("crc error");
return Err(Error::CorruptedPacket);
}
Ok(RXPacket::Event {
conn_id,
packet_tag,
length,
ev_size,
namespace,
event_id,
timestamp,
ev,
})
}
}
trait CxpWrite {
fn write_all_4x(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_4x_u8(&mut self, value: u8) -> Result<(), Error>;
fn write_4x_u16(&mut self, value: u16) -> Result<(), Error>;
fn write_4x_u32(&mut self, value: u32) -> Result<(), Error>;
fn write_u32(&mut self, value: u32) -> Result<(), Error>;
}
impl<Cursor: Write> CxpWrite for Cursor {
fn write_all_4x(&mut self, buf: &[u8]) -> Result<(), Error> {
for byte in buf {
self.write_all(&[*byte; 4])?;
}
Ok(())
}
fn write_4x_u8(&mut self, value: u8) -> Result<(), Error> {
self.write_all_4x(&[value])
}
fn write_4x_u16(&mut self, value: u16) -> Result<(), Error> {
let mut bytes = [0; 2];
NetworkEndian::write_u16(&mut bytes, value);
self.write_all_4x(&bytes)
}
fn write_4x_u32(&mut self, value: u32) -> Result<(), Error> {
let mut bytes = [0; 4];
NetworkEndian::write_u32(&mut bytes, value);
self.write_all_4x(&bytes)
}
fn write_u32(&mut self, value: u32) -> Result<(), Error> {
let mut bytes = [0; 4];
NetworkEndian::write_u32(&mut bytes, value);
self.write_all(&bytes)?;
Ok(())
}
}
#[derive(Debug)]
pub enum TXPacket {
CtrlRead {
tag: Option<u8>,
addr: u32,
length: u32,
},
CtrlWrite {
tag: Option<u8>,
addr: u32,
length: u32,
data: [u8; DATA_MAXSIZE],
},
EventAck {
packet_tag: u8,
},
}
impl TXPacket {
pub fn write_to(&self, writer: &mut Cursor<&mut [u8]>) -> Result<(), Error> {
match *self {
TXPacket::CtrlRead { tag, addr, length } => {
match tag {
Some(t) => {
writer.write_4x_u8(0x05)?;
writer.write_4x_u8(t)?;
}
None => {
writer.write_4x_u8(0x02)?;
}
}
let mut bytes = [0; 3];
NetworkEndian::write_u24(&mut bytes, length);
writer.write_all(&[0x00, bytes[0], bytes[1], bytes[2]])?;
writer.write_u32(addr)?;
// Section 9.6.2 (CXP-001-2021)
// only bytes after the first 4 are used in calculating the checksum
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
TXPacket::CtrlWrite {
tag,
addr,
length,
data,
} => {
match tag {
Some(t) => {
writer.write_4x_u8(0x05)?;
writer.write_4x_u8(t)?;
}
None => {
writer.write_4x_u8(0x02)?;
}
}
let mut bytes = [0; 3];
NetworkEndian::write_u24(&mut bytes, length);
writer.write_all(&[0x01, bytes[0], bytes[1], bytes[2]])?;
writer.write_u32(addr)?;
writer.write_all(&data[0..length as usize])?;
// Section 9.6.2 (CXP-001-2021)
// only bytes after the first 4 are used in calculating the checksum
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
TXPacket::EventAck { packet_tag } => {
writer.write_4x_u8(0x08)?;
writer.write_4x_u8(packet_tag)?;
}
}
Ok(())
}
}
// DEBUG: use only
//
//
//
pub fn print_packet(pak: &[u8]) {
println!("pak = [");
for i in 0..(pak.len() / 4) {
println!(
"{:#03} {:#04X} {:#04X} {:#04X} {:#04X},",
i + 1,
pak[i * 4],
pak[i * 4 + 1],
pak[i * 4 + 2],
pak[i * 4 + 3]
)
}
println!("]");
println!("============================================");
}
pub fn print_packetu32(pak: &[u32], k: &[u8]) {
println!("pak = [");
for i in 0..(pak.len()) {
let data: [u8; 4] = pak[i].to_le_bytes();
println!(
"{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b},",
i + 1,
data[0],
data[1],
data[2],
data[3],
k[i],
)
}
println!("]");
println!("============================================");
}

View File

@ -0,0 +1,91 @@
use core::slice;
use embedded_hal::blocking::delay::DelayUs;
use io::Cursor;
use libboard_zynq::{println, timer::GlobalTimer};
use crate::{cxp_proto,
mem::mem::CXP_MEM,
pl::csr::{self, CXP}};
const BUF_LEN: usize = 0x800;
pub fn tx_test(channel: u8, timer: &mut GlobalTimer) {
const LEN: usize = 4 * 100;
let mut pak_arr: [u8; LEN] = [0; LEN];
let channel = channel as usize;
unsafe {
// (CXP[channel].tx_trig_delay_write)(0x86);
// (CXP[channel].tx_linktrigger_write)(0x00);
// DEBUG: prepare the packet before tx enable to avoid overhead
preload_tx_packet(
channel as u8,
&cxp_proto::TXPacket::CtrlRead {
tag: None,
addr: 0,
length: 4,
},
);
csr::cxp_phys::tx_enable_write(1);
timer.delay_us(1);
// DEBUG: send ctrl packet or test
(CXP[channel].tx_writer_stb_write)(1);
(CXP[channel].tx_writer_stb_testseq_write)(1);
// DEBUG: Trigger packet (NOTE: disconnected in Gateware)
// (CXP[channel].upconn_trig_stb_write)(1); // send trig
// DEBUG: Trigger ACK packet
// (CXP[channel].upconn_ack_write)(1);
timer.delay_us(2000);
csr::cxp_phys::tx_enable_write(0);
// Collect data
let mut i: usize = 0;
// match channel {
// 0 => {
// while csr::cxp_phys::tx_tx0_debug_buf_dout_valid_read() == 1 {
// pak_arr[i] = csr::cxp_phys::tx_tx0_debug_buf_dout_pak_read();
// csr::cxp_phys::tx_tx0_debug_buf_inc_write(1);
// i += 1;
// if i == LEN {
// break;
// }
// }
// }
// 1 => {
// while csr::cxp_phys::tx_tx1_debug_buf_dout_valid_read() == 1 {
// pak_arr[i] = csr::cxp_phys::tx_tx1_debug_buf_dout_pak_read();
// csr::cxp_phys::tx_tx1_debug_buf_inc_write(1);
// i += 1;
// if i == LEN {
// break;
// }
// }
// }
// _ => {}
// }
cxp_proto::print_packet(&pak_arr);
}
}
fn preload_tx_packet(channel: u8, packet: &cxp_proto::TXPacket) {
let channel = channel as usize;
unsafe {
while (CXP[channel].tx_writer_busy_read)() == 1 {}
let ptr = CXP_MEM[channel].base as *mut u32;
let mut writer = Cursor::new(slice::from_raw_parts_mut(ptr as *mut u8, BUF_LEN));
packet.write_to(&mut writer).expect("unstable to write to tx mem");
// DEBUG:
println!("TX MEM after writing");
cxp_proto::print_packet(&writer.get_ref()[0..40]);
(CXP[channel].tx_writer_word_len_write)(writer.position() as u16 / 4);
}
}

View File

@ -185,6 +185,24 @@ unsafe fn align_comma(timer: &mut GlobalTimer) {
}
}
pub unsafe fn align_wordslip(timer: &mut GlobalTimer, trx_no: u8) -> bool {
pl::csr::eem_transceiver::transceiver_sel_write(trx_no);
for slip in 0..=1 {
pl::csr::eem_transceiver::wordslip_write(slip as u8);
timer.delay_us(1);
pl::csr::eem_transceiver::comma_align_reset_write(1);
timer.delay_us(100);
if pl::csr::eem_transceiver::comma_read() == 1 {
debug!("comma alignment completed with {} wordslip", slip);
return true;
}
}
false
}
pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
for trx_no in 0..pl::csr::CONFIG_EEM_DRTIO_COUNT {
unsafe {
@ -222,7 +240,6 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
unsafe {
align_comma(timer);
pl::csr::eem_transceiver::rx_ready_write(1);
}
}
}

View File

@ -255,6 +255,7 @@ pub enum Packet {
destination: u8,
id: u32,
run: bool,
timestamp: u64,
},
SubkernelLoadRunReply {
destination: u8,
@ -267,12 +268,14 @@ pub enum Packet {
exception_src: u8,
},
SubkernelExceptionRequest {
source: u8,
destination: u8,
},
SubkernelException {
destination: u8,
last: bool,
length: u16,
data: [u8; SAT_PAYLOAD_MAX_SIZE],
data: [u8; MASTER_PAYLOAD_MAX_SIZE],
},
SubkernelMessage {
source: u8,
@ -285,6 +288,77 @@ pub enum Packet {
SubkernelMessageAck {
destination: u8,
},
CoreMgmtGetLogRequest {
destination: u8,
clear: bool,
},
CoreMgmtClearLogRequest {
destination: u8,
},
CoreMgmtSetLogLevelRequest {
destination: u8,
log_level: u8,
},
CoreMgmtSetUartLogLevelRequest {
destination: u8,
log_level: u8,
},
CoreMgmtConfigReadRequest {
destination: u8,
length: u16,
key: [u8; MASTER_PAYLOAD_MAX_SIZE],
},
CoreMgmtConfigReadContinue {
destination: u8,
},
CoreMgmtConfigWriteRequest {
destination: u8,
last: bool,
length: u16,
data: [u8; MASTER_PAYLOAD_MAX_SIZE],
},
CoreMgmtConfigRemoveRequest {
destination: u8,
length: u16,
key: [u8; MASTER_PAYLOAD_MAX_SIZE],
},
CoreMgmtConfigEraseRequest {
destination: u8,
},
CoreMgmtRebootRequest {
destination: u8,
},
CoreMgmtAllocatorDebugRequest {
destination: u8,
},
CoreMgmtFlashRequest {
destination: u8,
payload_length: u32,
},
CoreMgmtFlashAddDataRequest {
destination: u8,
last: bool,
length: u16,
data: [u8; MASTER_PAYLOAD_MAX_SIZE],
},
CoreMgmtDropLinkAck {
destination: u8,
},
CoreMgmtDropLink,
CoreMgmtGetLogReply {
last: bool,
length: u16,
data: [u8; SAT_PAYLOAD_MAX_SIZE],
},
CoreMgmtConfigReadReply {
last: bool,
length: u16,
value: [u8; SAT_PAYLOAD_MAX_SIZE],
},
CoreMgmtReply {
succeeded: bool,
},
}
impl Packet {
@ -512,6 +586,7 @@ impl Packet {
destination: reader.read_u8()?,
id: reader.read_u32()?,
run: reader.read_bool()?,
timestamp: reader.read_u64()?,
},
0xc5 => Packet::SubkernelLoadRunReply {
destination: reader.read_u8()?,
@ -524,14 +599,17 @@ impl Packet {
exception_src: reader.read_u8()?,
},
0xc9 => Packet::SubkernelExceptionRequest {
source: reader.read_u8()?,
destination: reader.read_u8()?,
},
0xca => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::SubkernelException {
destination: destination,
last: last,
length: length,
data: data,
@ -558,6 +636,115 @@ impl Packet {
destination: reader.read_u8()?,
},
0xd0 => Packet::CoreMgmtGetLogRequest {
destination: reader.read_u8()?,
clear: reader.read_bool()?,
},
0xd1 => Packet::CoreMgmtClearLogRequest {
destination: reader.read_u8()?,
},
0xd2 => Packet::CoreMgmtSetLogLevelRequest {
destination: reader.read_u8()?,
log_level: reader.read_u8()?,
},
0xd3 => Packet::CoreMgmtSetUartLogLevelRequest {
destination: reader.read_u8()?,
log_level: reader.read_u8()?,
},
0xd4 => {
let destination = reader.read_u8()?;
let length = reader.read_u16()?;
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut key[0..length as usize])?;
Packet::CoreMgmtConfigReadRequest {
destination: destination,
length: length,
key: key,
}
}
0xd5 => Packet::CoreMgmtConfigReadContinue {
destination: reader.read_u8()?,
},
0xd6 => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtConfigWriteRequest {
destination: destination,
last: last,
length: length,
data: data,
}
}
0xd7 => {
let destination = reader.read_u8()?;
let length = reader.read_u16()?;
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut key[0..length as usize])?;
Packet::CoreMgmtConfigRemoveRequest {
destination: destination,
length: length,
key: key,
}
}
0xd8 => Packet::CoreMgmtConfigEraseRequest {
destination: reader.read_u8()?,
},
0xd9 => Packet::CoreMgmtRebootRequest {
destination: reader.read_u8()?,
},
0xda => Packet::CoreMgmtAllocatorDebugRequest {
destination: reader.read_u8()?,
},
0xdb => Packet::CoreMgmtFlashRequest {
destination: reader.read_u8()?,
payload_length: reader.read_u32()?,
},
0xdc => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtFlashAddDataRequest {
destination: destination,
last: last,
length: length,
data: data,
}
}
0xdd => Packet::CoreMgmtDropLinkAck {
destination: reader.read_u8()?,
},
0xde => Packet::CoreMgmtDropLink,
0xdf => {
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtGetLogReply {
last: last,
length: length,
data: data,
}
}
0xe0 => {
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut value: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut value[0..length as usize])?;
Packet::CoreMgmtConfigReadReply {
last: last,
length: length,
value: value,
}
}
0xe1 => Packet::CoreMgmtReply {
succeeded: reader.read_bool()?,
},
ty => return Err(Error::UnknownPacket(ty)),
})
}
@ -872,12 +1059,14 @@ impl Packet {
destination,
id,
run,
timestamp,
} => {
writer.write_u8(0xc4)?;
writer.write_u8(source)?;
writer.write_u8(destination)?;
writer.write_u32(id)?;
writer.write_bool(run)?;
writer.write_u64(timestamp)?;
}
Packet::SubkernelLoadRunReply { destination, succeeded } => {
writer.write_u8(0xc5)?;
@ -896,12 +1085,19 @@ impl Packet {
writer.write_bool(with_exception)?;
writer.write_u8(exception_src)?;
}
Packet::SubkernelExceptionRequest { destination } => {
Packet::SubkernelExceptionRequest { source, destination } => {
writer.write_u8(0xc9)?;
writer.write_u8(source)?;
writer.write_u8(destination)?;
}
Packet::SubkernelException { last, length, data } => {
Packet::SubkernelException {
destination,
last,
length,
data,
} => {
writer.write_u8(0xca)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
@ -926,6 +1122,115 @@ impl Packet {
writer.write_u8(0xcc)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtGetLogRequest { destination, clear } => {
writer.write_u8(0xd0)?;
writer.write_u8(destination)?;
writer.write_bool(clear)?;
}
Packet::CoreMgmtClearLogRequest { destination } => {
writer.write_u8(0xd1)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtSetLogLevelRequest { destination, log_level } => {
writer.write_u8(0xd2)?;
writer.write_u8(destination)?;
writer.write_u8(log_level)?;
}
Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level } => {
writer.write_u8(0xd3)?;
writer.write_u8(destination)?;
writer.write_u8(log_level)?;
}
Packet::CoreMgmtConfigReadRequest {
destination,
length,
key,
} => {
writer.write_u8(0xd4)?;
writer.write_u8(destination)?;
writer.write_u16(length)?;
writer.write_all(&key[0..length as usize])?;
}
Packet::CoreMgmtConfigReadContinue { destination } => {
writer.write_u8(0xd5)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtConfigWriteRequest {
destination,
last,
length,
data,
} => {
writer.write_u8(0xd6)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
}
Packet::CoreMgmtConfigRemoveRequest {
destination,
length,
key,
} => {
writer.write_u8(0xd7)?;
writer.write_u8(destination)?;
writer.write_u16(length)?;
writer.write_all(&key[0..length as usize])?;
}
Packet::CoreMgmtConfigEraseRequest { destination } => {
writer.write_u8(0xd8)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtRebootRequest { destination } => {
writer.write_u8(0xd9)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtAllocatorDebugRequest { destination } => {
writer.write_u8(0xda)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtFlashRequest {
destination,
payload_length,
} => {
writer.write_u8(0xdb)?;
writer.write_u8(destination)?;
writer.write_u32(payload_length)?;
}
Packet::CoreMgmtFlashAddDataRequest {
destination,
last,
length,
data,
} => {
writer.write_u8(0xdc)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[..length as usize])?;
}
Packet::CoreMgmtDropLinkAck { destination } => {
writer.write_u8(0xdd)?;
writer.write_u8(destination)?;
}
Packet::CoreMgmtDropLink => writer.write_u8(0xde)?,
Packet::CoreMgmtGetLogReply { last, length, data } => {
writer.write_u8(0xdf)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
}
Packet::CoreMgmtConfigReadReply { last, length, value } => {
writer.write_u8(0xe0)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&value[0..length as usize])?;
}
Packet::CoreMgmtReply { succeeded } => {
writer.write_u8(0xe1)?;
writer.write_bool(succeeded)?;
}
}
Ok(())
}
@ -943,6 +1248,8 @@ impl Packet {
Packet::SubkernelLoadRunReply { destination, .. } => Some(*destination),
Packet::SubkernelMessage { destination, .. } => Some(*destination),
Packet::SubkernelMessageAck { destination } => Some(*destination),
Packet::SubkernelExceptionRequest { destination, .. } => Some(*destination),
Packet::SubkernelException { destination, .. } => Some(*destination),
Packet::DmaPlaybackStatus { destination, .. } => Some(*destination),
Packet::SubkernelFinished { destination, .. } => Some(*destination),
_ => None,
@ -960,7 +1267,9 @@ impl Packet {
| Packet::SubkernelLoadRunReply { .. }
| Packet::SubkernelMessageAck { .. }
| Packet::DmaPlaybackStatus { .. }
| Packet::SubkernelFinished { .. } => false,
| Packet::SubkernelFinished { .. }
| Packet::CoreMgmtDropLinkAck { .. }
| Packet::InjectionRequest { .. } => false,
_ => true,
}
}

View File

@ -3,6 +3,7 @@
#![feature(naked_functions)]
#![feature(asm)]
extern crate byteorder;
extern crate core_io;
extern crate crc;
extern crate embedded_hal;
@ -25,7 +26,7 @@ pub mod fiq;
#[cfg(feature = "target_kasli_soc")]
pub mod io_expander;
pub mod logger;
#[cfg(has_drtio)]
#[cfg(any(has_drtio, has_cxp_phys))]
#[rustfmt::skip]
#[path = "../../../build/mem.rs"]
pub mod mem;
@ -42,6 +43,19 @@ pub mod si5324;
pub mod si549;
use core::{cmp, str};
#[cfg(has_cxp_phys)]
pub mod cxp_downconn;
#[cfg(has_cxp_phys)]
pub mod cxp_upconn;
pub mod cxp_proto;
pub mod cxp_phys;
pub mod cxp;
pub mod cxp_ctrl;
pub fn identifier_read(buf: &mut [u8]) -> &str {
unsafe {
pl::csr::identifier::address_write(0);

View File

@ -85,10 +85,7 @@ unsafe fn get_ttype_entry(
encoding | DW_EH_PE_pcrel,
ttype_base,
)
.map(|v| match v {
ttype_base => None,
ttype_entry => Some(ttype_entry as *const u8),
})
.map(|v| (v != ttype_base).then(|| v as *const u8))
}
pub unsafe fn find_eh_action(

View File

@ -32,3 +32,9 @@ unwind = { path = "../libunwind" }
libc = { path = "../libc" }
io = { path = "../libio" }
libboard_artiq = { path = "../libboard_artiq" }
[dependencies.nalgebra]
git = "https://git.m-labs.hk/M-Labs/nalgebra.git"
rev = "dd00f9b"
default-features = false
features = ["libm", "alloc"]

262
src/libksupport/src/cxp.rs Normal file
View File

@ -0,0 +1,262 @@
use byteorder::{ByteOrder, NetworkEndian};
use cslice::CMutSlice;
use libboard_artiq::{cxp::{setup, MASTER_CHANNEL},
cxp_ctrl,
cxp_proto::DATA_MAXSIZE};
// for downloading the XML files
// TODO: change this to read bytes and accept TBytearray
pub extern "C" fn cxp_read_words(addr: i32, val: &mut CMutSlice<i32>, with_tag: bool) {
let mut bytes: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
cxp_ctrl::read_bytes(MASTER_CHANNEL, addr as u32, &mut bytes[..val.len() * 4], with_tag)
.unwrap_or_else(|e| panic!("CXP readu64 failure: {}", e));
for i in 0..val.len() {
val.as_mut_slice()[i] = NetworkEndian::read_u32(&bytes[i * 4..(i + 1) * 4]) as i32;
}
}
pub extern "C" fn cxp_readu32(addr: i32, with_tag: bool) -> i32 {
// TODO: use artiq_raise like i2c?
cxp_ctrl::read_u32(MASTER_CHANNEL, addr as u32, with_tag).unwrap_or_else(|e| panic!("CXP readu64 failure: {}", e))
as i32
}
pub extern "C" fn cxp_readu64(addr: i32, with_tag: bool) -> i64 {
// TODO: use artiq_raise like i2c?
cxp_ctrl::read_u64(MASTER_CHANNEL, addr as u32, with_tag).unwrap_or_else(|e| panic!("CXP read u64 failure: {}", e))
as i64
}
pub extern "C" fn cxp_writeu32(addr: i32, val: i32, with_tag: bool) {
// TODO: use artiq_raise like i2c?
cxp_ctrl::write_u32(MASTER_CHANNEL, addr as u32, val as u32, with_tag)
.unwrap_or_else(|e| panic!("CXP write u32 failure: {}", e));
}
pub extern "C" fn cxp_writeu64(addr: i32, val: i64, with_tag: bool) {
// TODO: use artiq_raise like i2c?
cxp_ctrl::write_u64(MASTER_CHANNEL, addr as u32, val as u64, with_tag)
.unwrap_or_else(|e| panic!("CXP write u64 failure: {}", e));
}
pub extern "C" fn cxp_setup() -> bool {
setup().unwrap()
}
// DEBUG: ONLY
pub extern "C" fn cxp_debug_frame_print() {
use libboard_zynq::println;
use crate::pl::csr::CXP;
const LEN: usize = 512;
let mut eop: [u8; LEN] = [0; LEN];
let mut pak_arr: [u32; LEN] = [0; LEN];
let mut k_arr: [u8; LEN] = [0; LEN];
let mut err: [u8; LEN] = [0; LEN];
let mut i: usize = 0;
unsafe {
use crate::pl::csr::cxp_frame_pipeline;
println!(
"arbiter active ch = {:#06b} | CRC error count = {} ",
cxp_frame_pipeline::arbiter_active_ch_read(),
cxp_frame_pipeline::crc_error_cnt_read(),
);
println!(
"roi counter = {} | roi update = {}",
cxp_frame_pipeline::roi_counter_read(),
cxp_frame_pipeline::roi_update_read()
);
if cxp_frame_pipeline::roi_update_read() == 1 {
println!("roi update clear");
cxp_frame_pipeline::roi_update_write(1);
};
println!("pixel4x[0] y = {}", cxp_frame_pipeline::pix_y_read(),);
println!(
"header decode l_size = {} | x_size = {} | y_size = {} | new lines count = {}",
cxp_frame_pipeline::header_l_size_read(),
cxp_frame_pipeline::header_x_size_read(),
cxp_frame_pipeline::header_y_size_read(),
cxp_frame_pipeline::header_new_line_read(),
);
// while cxp_frame_pipeline::debug_out_dout_valid_read() == 1 {
// pak_arr[i] = cxp_frame_pipeline::debug_out_dout_pak_read();
// k_arr[i] = cxp_frame_pipeline::debug_out_kout_pak_read();
// eop[i] = cxp_frame_pipeline::debug_out_eop_read();
// err[i] = cxp_frame_pipeline::debug_out_crc_error_read();
// // println!("received {:#04X}", pak_arr[i]);
// cxp_frame_pipeline::debug_out_inc_write(1);
// i += 1;
// if i == LEN {
// break;
// }
// }
// let channel: usize = 0;
// while (CXP[channel].rx_debug_out_dout_valid_read)() == 1 {
// pak_arr[i] = (CXP[channel].rx_debug_out_dout_pak_read)();
// k_arr[i] = (CXP[channel].rx_debug_out_kout_pak_read)();
// // println!("received {:#04X}", pak_arr[i]);
// (CXP[channel].rx_debug_out_inc_write)(1);
// i += 1;
// if i == LEN {
// break;
// }
// }
// println!(
// "Decode error = {}",
// (CXP[MASTER_CHANNEL as usize].rx_reader_decode_err_read)()
// );
if (CXP[MASTER_CHANNEL as usize].rx_trigger_ack_read)() == 1 {
println!("Trigger ack recv and clear");
(CXP[MASTER_CHANNEL as usize].rx_trigger_ack_write)(1);
} else {
println!("NO Trigger ack");
}
}
cxp_ctrl::print_decode_error(MASTER_CHANNEL);
if i > 0 {
print_packetu32(&pak_arr, &k_arr, &err, &eop, i as usize);
} else {
println!("No frame data received");
}
}
pub fn print_packetu32(pak: &[u32], k: &[u8], err: &[u8], eop: &[u8], len: usize) {
use libboard_zynq::{print, println};
println!("pak = [");
let mut i_stream_id: usize = len + 1;
let mut i_pak_tag: usize = len + 1;
let mut i_size_0: usize = len + 1;
let mut i_size_1: usize = len + 1;
for i in 0..len {
let data: [u8; 4] = pak[i].to_be_bytes();
// if eop[i] == 1 {
// if err[i] == 1 {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | eop crc error",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | eop",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// }
// i_stream_id = i + 1;
// i_pak_tag = i + 2;
// i_size_0 = i + 3;
// i_size_1 = i + 4
// } else if i == i_stream_id {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | stream id",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else if i == i_pak_tag {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | packet tag",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else if i == i_size_0 {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | DsizeP[15:8]",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else if i == i_size_1 {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | DsizeP[7:0]",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else {
// if err[i] == 1 {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | crc error",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} |",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// }
// if err[i] == 1 {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} | crc error",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// } else {
// println!(
// "{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} |",
// i + 1,
// data[0],
// data[1],
// data[2],
// data[3],
// k[i],
// );
// };
print!(
"{:#03} {:#04X} {:#04X} {:#04X} {:#04X} | K {:04b} |",
i + 1,
data[0],
data[1],
data[2],
data[3],
k[i],
);
if err[i] == 1 {
print!(" crc error |")
};
if eop[i] == 1 {
print!(" eop |")
};
println!("")
}
println!("]");
println!("============================================");
}

View File

@ -14,8 +14,10 @@
use core::mem;
use cslice::CSlice;
use core_io::Error as ReadError;
use cslice::{AsCSlice, CSlice};
use dwarf::eh::{self, EHAction, EHContext};
use io::{Cursor, ProtoRead};
use libc::{c_int, c_void, uintptr_t};
use log::{error, trace};
use unwind as uw;
@ -220,8 +222,6 @@ pub unsafe fn artiq_personality(
}
pub unsafe extern "C" fn raise(exception: *const Exception) -> ! {
use cslice::AsCSlice;
let count = EXCEPTION_BUFFER.exception_count;
let stack = &mut EXCEPTION_BUFFER.exception_stack;
let diff = exception as isize - EXCEPTION_BUFFER.exceptions.as_ptr() as isize;
@ -295,6 +295,60 @@ pub unsafe extern "C" fn raise(exception: *const Exception) -> ! {
unreachable!();
}
fn read_exception_string<'a>(reader: &mut Cursor<&[u8]>) -> Result<CSlice<'a, u8>, ReadError> {
let len = reader.read_u32()? as usize;
if len == usize::MAX {
let data = reader.read_u32()?;
Ok(unsafe { CSlice::new(data as *const u8, len) })
} else {
let pos = reader.position();
let slice = unsafe {
let ptr = reader.get_ref().as_ptr().offset(pos as isize);
CSlice::new(ptr, len)
};
reader.set_position(pos + len);
Ok(slice)
}
}
fn read_exception(raw_exception: &[u8]) -> Result<Exception, ReadError> {
let mut reader = Cursor::new(raw_exception);
let mut byte = reader.read_u8()?;
// to sync
while byte != 0x5a {
byte = reader.read_u8()?;
}
// skip sync bytes, 0x09 indicates exception
while byte != 0x09 {
byte = reader.read_u8()?;
}
let _len = reader.read_u32()?;
// ignore the remaining exceptions, stack traces etc. - unwinding from another device would be unwise anyway
Ok(Exception {
id: reader.read_u32()?,
message: read_exception_string(&mut reader)?,
param: [
reader.read_u64()? as i64,
reader.read_u64()? as i64,
reader.read_u64()? as i64,
],
file: read_exception_string(&mut reader)?,
line: reader.read_u32()?,
column: reader.read_u32()?,
function: read_exception_string(&mut reader)?,
})
}
pub fn raise_raw(raw_exception: &[u8]) -> ! {
use crate::artiq_raise;
if let Ok(exception) = read_exception(raw_exception) {
unsafe { raise(&exception) };
} else {
artiq_raise!("SubkernelError", "Error passing exception");
}
}
pub unsafe extern "C" fn resume() -> ! {
trace!("resume");
assert!(EXCEPTION_BUFFER.exception_count != 0);
@ -421,20 +475,30 @@ extern "C" fn stop_fn(
}
}
// Must be kept in sync with preallocate_runtime_exception_names() in artiq/language/embedding_map.py
static EXCEPTION_ID_LOOKUP: [(&str, u32); 12] = [
("RuntimeError", 0),
("RTIOUnderflow", 1),
("RTIOOverflow", 2),
("RTIODestinationUnreachable", 3),
("DMAError", 4),
("I2CError", 5),
("CacheError", 6),
("SPIError", 7),
("ZeroDivisionError", 8),
("IndexError", 9),
("UnwrapNoneError", 10),
("SubkernelError", 11),
// Must be kept in sync with preallocate_runtime_exception_names() in `artiq.compiler.embedding`
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
("RTIOUnderflow", 0),
("RTIOOverflow", 1),
("RTIODestinationUnreachable", 2),
("DMAError", 3),
("I2CError", 4),
("CacheError", 5),
("SPIError", 6),
("SubkernelError", 7),
("AssertionError", 8),
("AttributeError", 9),
("IndexError", 10),
("IOError", 11),
("KeyError", 12),
("NotImplementedError", 13),
("OverflowError", 14),
("RuntimeError", 15),
("TimeoutError", 16),
("TypeError", 17),
("ValueError", 18),
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
];
pub fn get_exception_id(name: &str) -> u32 {
@ -469,3 +533,29 @@ macro_rules! artiq_raise {
}};
($name:expr, $message:expr) => {{ artiq_raise!($name, $message, 0, 0, 0) }};
}
/// Takes as input exception id from host
/// Generates a new exception with:
/// * `id` set to `exn_id`
/// * `message` set to corresponding exception name from `EXCEPTION_ID_LOOKUP`
///
/// The message is matched on host to ensure correct exception is being referred
/// This test checks the synchronization of exception ids for runtime errors
#[no_mangle]
pub extern "C" fn test_exception_id_sync(exn_id: u32) {
let message = EXCEPTION_ID_LOOKUP
.iter()
.find_map(|&(name, id)| if id == exn_id { Some(name) } else { None })
.unwrap_or("unallocated internal exception id");
let exn = Exception {
id: exn_id,
file: file!().as_c_slice(),
line: 0,
column: 0,
function: "test_exception_id_sync".as_c_slice(),
message: message.as_c_slice(),
param: [0, 0, 0],
};
unsafe { raise(&exn) };
}

View File

@ -9,8 +9,10 @@ use log::{info, warn};
use super::subkernel;
use super::{cache,
core1::rtio_get_destination_status,
dma,
dma, linalg,
rpc::{rpc_recv, rpc_send, rpc_send_async}};
#[cfg(has_cxp_phys)]
use crate::cxp;
use crate::{eh_artiq, i2c, rtio};
extern "C" {
@ -126,6 +128,22 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
#[cfg(has_drtio)]
api!(subkernel_await_message = subkernel::await_message),
// CoaXPress
#[cfg(has_cxp_phys)]
api!(cxp_read_words = cxp::cxp_read_words),
#[cfg(has_cxp_phys)]
api!(cxp_readu32 = cxp::cxp_readu32),
#[cfg(has_cxp_phys)]
api!(cxp_readu64 = cxp::cxp_readu64),
#[cfg(has_cxp_phys)]
api!(cxp_writeu32 = cxp::cxp_writeu32),
#[cfg(has_cxp_phys)]
api!(cxp_writeu64 = cxp::cxp_writeu64),
#[cfg(has_cxp_phys)]
api!(cxp_setup = cxp::cxp_setup),
#[cfg(has_cxp_phys)]
api!(cxp_debug_frame_print = cxp::cxp_debug_frame_print),
// Double-precision floating-point arithmetic helper functions
// RTABI chapter 4.1.2, Table 2
api!(__aeabi_dadd),
@ -303,6 +321,7 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
api_libm_f64f64f64!(nextafter),
api_libm_f64f64f64!(pow),
api_libm_f64f64!(round),
api_libm_f64f64!(rint),
api_libm_f64f64!(sin),
api_libm_f64f64!(sinh),
api_libm_f64f64!(sqrt),
@ -318,6 +337,26 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
}
api!(yn = yn)
},
// linalg
api!(np_linalg_cholesky = linalg::np_linalg_cholesky),
api!(np_linalg_qr = linalg::np_linalg_qr),
api!(np_linalg_svd = linalg::np_linalg_svd),
api!(np_linalg_inv = linalg::np_linalg_inv),
api!(np_linalg_pinv = linalg::np_linalg_pinv),
api!(np_linalg_matrix_power = linalg::np_linalg_matrix_power),
api!(np_linalg_det = linalg::np_linalg_det),
api!(sp_linalg_lu = linalg::sp_linalg_lu),
api!(sp_linalg_schur = linalg::sp_linalg_schur),
api!(sp_linalg_hessenberg = linalg::sp_linalg_hessenberg),
/*
* syscall for unit tests
* Used in `artiq.tests.coredevice.test_exceptions.ExceptionTest.test_raise_exceptions_kernel`
* This syscall checks that the exception IDs used in the Python `EmbeddingMap` (in `artiq.language.embedding`)
* match the `EXCEPTION_ID_LOOKUP` defined in the firmware (`libksupport::src::eh_artiq`)
*/
api!(test_exception_id_sync = eh_artiq::test_exception_id_sync)
];
api.iter()
.find(|&&(exported, _)| exported.as_bytes() == required)

View File

@ -0,0 +1,440 @@
// Uses `nalgebra` crate to invoke `np_linalg` and `sp_linalg` functions
// When converting between `nalgebra::Matrix` and `NDArray` following considerations are necessary
//
// * Both `nalgebra::Matrix` and `NDArray` require their content to be stored in row-major order
// * `NDArray` data pointer can be directly read and converted to `nalgebra::Matrix` (row and column number must be known)
// * `nalgebra::Matrix::as_slice` returns the content of matrix in column-major order and initial data needs to be transposed before storing it in `NDArray` data pointer
use alloc::vec::Vec;
use core::slice;
use nalgebra::DMatrix;
use crate::artiq_raise;
pub struct InputMatrix {
pub ndims: usize,
pub dims: *const usize,
pub data: *mut f64,
}
impl InputMatrix {
fn get_dims(&mut self) -> Vec<usize> {
let dims = unsafe { slice::from_raw_parts(self.dims, self.ndims) };
dims.to_vec()
}
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_cholesky(mat1: *mut InputMatrix, out: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out = out.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
if dim1[0] != dim1[1] {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
let outdim = out.get_dims();
let out_slice = slice::from_raw_parts_mut(out.data, outdim[0] * outdim[1]);
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let matrix1 = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let result = matrix1.cholesky();
match result {
Some(res) => {
out_slice.copy_from_slice(res.unpack().transpose().as_slice());
}
None => {
artiq_raise!("LinAlgError", "Matrix is not positive definite");
}
};
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_qr(mat1: *mut InputMatrix, out_q: *mut InputMatrix, out_r: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out_q = out_q.as_mut().unwrap();
let out_r = out_r.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let outq_dim = (*out_q).get_dims();
let outr_dim = (*out_r).get_dims();
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let out_q_slice = slice::from_raw_parts_mut(out_q.data, outq_dim[0] * outq_dim[1]);
let out_r_slice = slice::from_raw_parts_mut(out_r.data, outr_dim[0] * outr_dim[1]);
// Refer to https://github.com/dimforge/nalgebra/issues/735
let matrix1 = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let res = matrix1.qr();
let (q, r) = res.unpack();
// Uses different algo need to match numpy
out_q_slice.copy_from_slice(q.transpose().as_slice());
out_r_slice.copy_from_slice(r.transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_svd(
mat1: *mut InputMatrix,
outu: *mut InputMatrix,
outs: *mut InputMatrix,
outvh: *mut InputMatrix,
) {
let mat1 = mat1.as_mut().unwrap();
let outu = outu.as_mut().unwrap();
let outs = outs.as_mut().unwrap();
let outvh = outvh.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let outu_dim = (*outu).get_dims();
let outs_dim = (*outs).get_dims();
let outvh_dim = (*outvh).get_dims();
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let out_u_slice = slice::from_raw_parts_mut(outu.data, outu_dim[0] * outu_dim[1]);
let out_s_slice = slice::from_raw_parts_mut(outs.data, outs_dim[0]);
let out_vh_slice = slice::from_raw_parts_mut(outvh.data, outvh_dim[0] * outvh_dim[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let result = matrix.svd(true, true);
out_u_slice.copy_from_slice(result.u.unwrap().transpose().as_slice());
out_s_slice.copy_from_slice(result.singular_values.as_slice());
out_vh_slice.copy_from_slice(result.v_t.unwrap().transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_inv(mat1: *mut InputMatrix, out: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out = out.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
if dim1[0] != dim1[1] {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
let outdim = out.get_dims();
let out_slice = slice::from_raw_parts_mut(out.data, outdim[0] * outdim[1]);
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
if !matrix.is_invertible() {
artiq_raise!("LinAlgError", "no inverse for Singular Matrix");
}
let inv = matrix.try_inverse().unwrap();
out_slice.copy_from_slice(inv.transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_pinv(mat1: *mut InputMatrix, out: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out = out.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let outdim = out.get_dims();
let out_slice = slice::from_raw_parts_mut(out.data, outdim[0] * outdim[1]);
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let svd = matrix.svd(true, true);
let inv = svd.pseudo_inverse(1e-15);
match inv {
Ok(m) => {
out_slice.copy_from_slice(m.transpose().as_slice());
}
Err(_) => {
artiq_raise!("LinAlgError", "SVD computation does not converge");
}
}
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_matrix_power(mat1: *mut InputMatrix, mat2: *mut InputMatrix, out: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let mat2 = mat2.as_mut().unwrap();
let out = out.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let power = slice::from_raw_parts_mut(mat2.data, 1);
let power = power[0];
let outdim = out.get_dims();
let out_slice = slice::from_raw_parts_mut(out.data, outdim[0] * outdim[1]);
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let mut abs_power = power;
if abs_power < 0.0 {
abs_power = abs_power * -1.0;
}
let matrix1 = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
if !matrix1.is_square() {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
let mut result = matrix1.pow(abs_power as u32);
if power < 0.0 {
if !matrix1.is_invertible() {
artiq_raise!("LinAlgError", "no inverse for Singular Matrix");
}
result = result.try_inverse().unwrap();
}
out_slice.copy_from_slice(result.transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn np_linalg_det(mat1: *mut InputMatrix, out: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out = out.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let out_slice = slice::from_raw_parts_mut(out.data, 1);
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
if !matrix.is_square() {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
out_slice[0] = matrix.determinant();
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn sp_linalg_lu(mat1: *mut InputMatrix, out_l: *mut InputMatrix, out_u: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out_l = out_l.as_mut().unwrap();
let out_u = out_u.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
let outl_dim = (*out_l).get_dims();
let outu_dim = (*out_u).get_dims();
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let out_l_slice = slice::from_raw_parts_mut(out_l.data, outl_dim[0] * outl_dim[1]);
let out_u_slice = slice::from_raw_parts_mut(out_u.data, outu_dim[0] * outu_dim[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let (_, l, u) = matrix.lu().unpack();
out_l_slice.copy_from_slice(l.transpose().as_slice());
out_u_slice.copy_from_slice(u.transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn sp_linalg_schur(mat1: *mut InputMatrix, out_t: *mut InputMatrix, out_z: *mut InputMatrix) {
let mat1 = mat1.as_mut().unwrap();
let out_t = out_t.as_mut().unwrap();
let out_z = out_z.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
if dim1[0] != dim1[1] {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
let out_t_dim = (*out_t).get_dims();
let out_z_dim = (*out_z).get_dims();
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let out_t_slice = slice::from_raw_parts_mut(out_t.data, out_t_dim[0] * out_t_dim[1]);
let out_z_slice = slice::from_raw_parts_mut(out_z.data, out_z_dim[0] * out_z_dim[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let (z, t) = matrix.schur().unpack();
out_t_slice.copy_from_slice(t.transpose().as_slice());
out_z_slice.copy_from_slice(z.transpose().as_slice());
}
/// # Safety
///
/// `mat1` should point to a valid 2DArray of `f64` floats in row-major order
#[no_mangle]
pub unsafe extern "C" fn sp_linalg_hessenberg(
mat1: *mut InputMatrix,
out_h: *mut InputMatrix,
out_q: *mut InputMatrix,
) {
let mat1 = mat1.as_mut().unwrap();
let out_h = out_h.as_mut().unwrap();
let out_q = out_q.as_mut().unwrap();
if mat1.ndims != 2 {
artiq_raise!(
"ValueError",
"expected 2D Vector Input, but received {1}D input)",
0,
mat1.ndims as i64,
0
);
}
let dim1 = (*mat1).get_dims();
if dim1[0] != dim1[1] {
artiq_raise!(
"ValueError",
"last 2 dimensions of the array must be square: {1} != {2}",
0,
dim1[0] as i64,
dim1[1] as i64
);
}
let out_h_dim = (*out_h).get_dims();
let out_q_dim = (*out_q).get_dims();
let data_slice1 = slice::from_raw_parts_mut(mat1.data, dim1[0] * dim1[1]);
let out_h_slice = slice::from_raw_parts_mut(out_h.data, out_h_dim[0] * out_h_dim[1]);
let out_q_slice = slice::from_raw_parts_mut(out_q.data, out_q_dim[0] * out_q_dim[1]);
let matrix = DMatrix::from_row_slice(dim1[0], dim1[1], data_slice1);
let (q, h) = matrix.hessenberg().unpack();
out_h_slice.copy_from_slice(h.transpose().as_slice());
out_q_slice.copy_from_slice(q.transpose().as_slice());
}

View File

@ -13,6 +13,7 @@ mod dma;
mod rpc;
pub use dma::DmaRecorder;
mod cache;
mod linalg;
#[cfg(has_drtio)]
mod subkernel;
@ -23,6 +24,7 @@ pub enum SubkernelStatus {
Timeout,
IncorrectState,
CommLost,
Exception(Vec<u8>),
OtherError,
}
@ -79,6 +81,7 @@ pub enum Message {
id: u32,
destination: u8,
run: bool,
timestamp: u64,
},
#[cfg(has_drtio)]
SubkernelLoadRunReply {
@ -90,9 +93,7 @@ pub enum Message {
timeout: i64,
},
#[cfg(has_drtio)]
SubkernelAwaitFinishReply {
status: SubkernelStatus,
},
SubkernelAwaitFinishReply,
#[cfg(has_drtio)]
SubkernelMsgSend {
id: u32,
@ -109,9 +110,10 @@ pub enum Message {
},
#[cfg(has_drtio)]
SubkernelMsgRecvReply {
status: SubkernelStatus,
count: u8,
},
#[cfg(has_drtio)]
SubkernelError(SubkernelStatus),
}
static CHANNEL_0TO1: Mutex<Option<sync_channel::Sender<'static, Message>>> = Mutex::new(None);

View File

@ -3,7 +3,7 @@ use alloc::vec::Vec;
use cslice::CSlice;
use super::{Message, SubkernelStatus, KERNEL_CHANNEL_0TO1, KERNEL_CHANNEL_1TO0};
use crate::{artiq_raise, rpc::send_args};
use crate::{artiq_raise, eh_artiq, rpc::send_args, rtio::now_mu};
pub extern "C" fn load_run(id: u32, destination: u8, run: bool) {
unsafe {
@ -14,6 +14,7 @@ pub extern "C" fn load_run(id: u32, destination: u8, run: bool) {
id: id,
destination: destination,
run: run,
timestamp: now_mu() as u64,
});
}
match unsafe { KERNEL_CHANNEL_0TO1.as_mut().unwrap() }.recv() {
@ -36,21 +37,18 @@ pub extern "C" fn await_finish(id: u32, timeout: i64) {
});
}
match unsafe { KERNEL_CHANNEL_0TO1.as_mut().unwrap() }.recv() {
Message::SubkernelAwaitFinishReply {
status: SubkernelStatus::NoError,
} => (),
Message::SubkernelAwaitFinishReply {
status: SubkernelStatus::IncorrectState,
} => artiq_raise!("SubkernelError", "Subkernel not running"),
Message::SubkernelAwaitFinishReply {
status: SubkernelStatus::Timeout,
} => artiq_raise!("SubkernelError", "Subkernel timed out"),
Message::SubkernelAwaitFinishReply {
status: SubkernelStatus::CommLost,
} => artiq_raise!("SubkernelError", "Lost communication with satellite"),
Message::SubkernelAwaitFinishReply {
status: SubkernelStatus::OtherError,
} => artiq_raise!("SubkernelError", "An error occurred during subkernel operation"),
Message::SubkernelAwaitFinishReply => (),
Message::SubkernelError(SubkernelStatus::IncorrectState) => {
artiq_raise!("SubkernelError", "Subkernel not running")
}
Message::SubkernelError(SubkernelStatus::Timeout) => artiq_raise!("SubkernelError", "Subkernel timed out"),
Message::SubkernelError(SubkernelStatus::CommLost) => {
artiq_raise!("SubkernelError", "Lost communication with satellite")
}
Message::SubkernelError(SubkernelStatus::OtherError) => {
artiq_raise!("SubkernelError", "An error occurred during subkernel operation")
}
Message::SubkernelError(SubkernelStatus::Exception(raw_exception)) => eh_artiq::raise_raw(&raw_exception),
_ => panic!("expected SubkernelAwaitFinishReply after SubkernelAwaitFinishRequest"),
}
}
@ -92,30 +90,22 @@ pub extern "C" fn await_message(id: i32, timeout: i64, tags: &CSlice<u8>, min: u
});
}
match unsafe { KERNEL_CHANNEL_0TO1.as_mut().unwrap() }.recv() {
Message::SubkernelMsgRecvReply {
status: SubkernelStatus::NoError,
count,
} => {
Message::SubkernelMsgRecvReply { count } => {
if min > count || count > max {
artiq_raise!("SubkernelError", "Received more or less arguments than required")
}
}
Message::SubkernelMsgRecvReply {
status: SubkernelStatus::IncorrectState,
..
} => artiq_raise!("SubkernelError", "Subkernel not running"),
Message::SubkernelMsgRecvReply {
status: SubkernelStatus::Timeout,
..
} => artiq_raise!("SubkernelError", "Subkernel timed out"),
Message::SubkernelMsgRecvReply {
status: SubkernelStatus::CommLost,
..
} => artiq_raise!("SubkernelError", "Lost communication with satellite"),
Message::SubkernelMsgRecvReply {
status: SubkernelStatus::OtherError,
..
} => artiq_raise!("SubkernelError", "An error occurred during subkernel operation"),
Message::SubkernelError(SubkernelStatus::IncorrectState) => {
artiq_raise!("SubkernelError", "Subkernel not running")
}
Message::SubkernelError(SubkernelStatus::Timeout) => artiq_raise!("SubkernelError", "Subkernel timed out"),
Message::SubkernelError(SubkernelStatus::CommLost) => {
artiq_raise!("SubkernelError", "Lost communication with satellite")
}
Message::SubkernelError(SubkernelStatus::OtherError) => {
artiq_raise!("SubkernelError", "An error occurred during subkernel operation")
}
Message::SubkernelError(SubkernelStatus::Exception(raw_exception)) => eh_artiq::raise_raw(&raw_exception),
_ => panic!("expected SubkernelMsgRecvReply after SubkernelMsgRecvRequest"),
}
// RpcRecvRequest should be called after this to receive message data

View File

@ -35,6 +35,8 @@ pub mod rtio;
#[path = "../../../build/pl.rs"]
pub mod pl;
#[cfg(has_cxp_phys)]
pub mod cxp;
#[derive(Debug, Clone)]
pub struct RPCException {

View File

@ -23,6 +23,7 @@ mod llvm_libunwind {
cfg.flag("--target=armv7-none-eabihf");
cfg.flag("-O2");
cfg.flag("-flto");
cfg.flag("-Wno-everything");
cfg.flag("-std=c99");
cfg.flag("-fstrict-aliasing");

View File

@ -8,6 +8,7 @@ edition = "2018"
[features]
target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"]
target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"]
target_ebaz4205 = ["libboard_zynq/target_ebaz4205", "libsupport_zynq/target_ebaz4205", "libconfig/target_ebaz4205", "libboard_artiq/target_ebaz4205"]
default = ["target_zc706"]
[build-dependencies]
@ -20,6 +21,7 @@ cslice = "0.3"
log = "0.4"
embedded-hal = "0.2"
core_io = { version = "0.1", features = ["collections"] }
crc = { version = "1.7", default-features = false }
byteorder = { version = "1.3", default-features = false }
void = { version = "1", default-features = false }
futures = { version = "0.3", default-features = false, features = ["async-await"] }

View File

@ -405,8 +405,9 @@ async fn handle_run_kernel(
id,
destination: _,
run,
timestamp,
} => {
let succeeded = match subkernel::load(aux_mutex, routing_table, timer, id, run).await {
let succeeded = match subkernel::load(aux_mutex, routing_table, timer, id, run, timestamp).await {
Ok(()) => true,
Err(e) => {
error!("Error loading subkernel: {:?}", e);
@ -422,33 +423,23 @@ async fn handle_run_kernel(
#[cfg(has_drtio)]
kernel::Message::SubkernelAwaitFinishRequest { id, timeout } => {
let res = subkernel::await_finish(aux_mutex, routing_table, timer, id, timeout).await;
let status = match res {
Ok(ref res) => {
let response = match res {
Ok(res) => {
if res.status == subkernel::FinishStatus::CommLost {
kernel::SubkernelStatus::CommLost
} else if let Some(exception) = &res.exception {
error!("Exception in subkernel");
match stream {
None => (),
Some(stream) => {
write_chunk(stream, exception).await?;
}
}
// will not be called after exception is served
kernel::SubkernelStatus::OtherError
kernel::Message::SubkernelError(kernel::SubkernelStatus::CommLost)
} else if let Some(exception) = res.exception {
kernel::Message::SubkernelError(kernel::SubkernelStatus::Exception(exception))
} else {
kernel::SubkernelStatus::NoError
kernel::Message::SubkernelAwaitFinishReply
}
}
Err(SubkernelError::Timeout) => kernel::SubkernelStatus::Timeout,
Err(SubkernelError::IncorrectState) => kernel::SubkernelStatus::IncorrectState,
Err(_) => kernel::SubkernelStatus::OtherError,
Err(SubkernelError::Timeout) => kernel::Message::SubkernelError(kernel::SubkernelStatus::Timeout),
Err(SubkernelError::IncorrectState) => {
kernel::Message::SubkernelError(kernel::SubkernelStatus::IncorrectState)
}
Err(_) => kernel::Message::SubkernelError(kernel::SubkernelStatus::OtherError),
};
control
.borrow_mut()
.tx
.async_send(kernel::Message::SubkernelAwaitFinishReply { status: status })
.await;
control.borrow_mut().tx.async_send(response).await;
}
#[cfg(has_drtio)]
kernel::Message::SubkernelMsgSend { id, destination, data } => {
@ -469,35 +460,23 @@ async fn handle_run_kernel(
#[cfg(has_drtio)]
kernel::Message::SubkernelMsgRecvRequest { id, timeout, tags } => {
let message_received = subkernel::message_await(id as u32, timeout, timer).await;
let (status, count) = match message_received {
Ok(ref message) => (kernel::SubkernelStatus::NoError, message.count),
Err(SubkernelError::Timeout) => (kernel::SubkernelStatus::Timeout, 0),
Err(SubkernelError::IncorrectState) => (kernel::SubkernelStatus::IncorrectState, 0),
Err(SubkernelError::CommLost) => (kernel::SubkernelStatus::CommLost, 0),
let response = match message_received {
Ok(ref message) => kernel::Message::SubkernelMsgRecvReply { count: message.count },
Err(SubkernelError::Timeout) => kernel::Message::SubkernelError(kernel::SubkernelStatus::Timeout),
Err(SubkernelError::IncorrectState) => {
kernel::Message::SubkernelError(kernel::SubkernelStatus::IncorrectState)
}
Err(SubkernelError::CommLost) => kernel::Message::SubkernelError(kernel::SubkernelStatus::CommLost),
Err(SubkernelError::SubkernelException) => {
error!("Exception in subkernel");
// just retrieve the exception
let status = subkernel::await_finish(aux_mutex, routing_table, timer, id as u32, timeout)
.await
.unwrap();
match stream {
None => (),
Some(stream) => {
write_chunk(stream, &status.exception.unwrap()).await?;
}
}
(kernel::SubkernelStatus::OtherError, 0)
kernel::Message::SubkernelError(kernel::SubkernelStatus::Exception(status.exception.unwrap()))
}
Err(_) => (kernel::SubkernelStatus::OtherError, 0),
Err(_) => kernel::Message::SubkernelError(kernel::SubkernelStatus::OtherError),
};
control
.borrow_mut()
.tx
.async_send(kernel::Message::SubkernelMsgRecvReply {
status: status,
count: count,
})
.await;
control.borrow_mut().tx.async_send(response).await;
if let Ok(message) = message_received {
// receive code almost identical to RPC recv, except we are not reading from a stream
let mut reader = Cursor::new(message.data);
@ -529,7 +508,7 @@ async fn handle_run_kernel(
.async_send(kernel::Message::RpcRecvReply(Ok(0)))
.await;
i += 1;
if i < count {
if i < message.count {
current_tags = remaining_tags;
} else {
break;
@ -771,14 +750,13 @@ pub fn main(timer: GlobalTimer, cfg: Config) {
#[cfg(has_drtio_routing)]
drtio_routing::interconnect_disable_all();
rtio_mgt::startup(&aux_mutex, &drtio_routing_table, &up_destinations, timer);
rtio_mgt::startup(&aux_mutex, &drtio_routing_table, &up_destinations, &cfg, timer);
ksupport::setup_device_map(&cfg);
analyzer::start(&aux_mutex, &drtio_routing_table, &up_destinations, timer);
moninj::start(timer, &aux_mutex, &drtio_routing_table);
let control: Rc<RefCell<kernel::Control>> = Rc::new(RefCell::new(kernel::Control::start()));
let idle_kernel = Rc::new(cfg.read("idle_kernel").ok());
if let Ok(buffer) = cfg.read("startup_kernel") {
info!("Loading startup kernel...");
let routing_table = drtio_routing_table.borrow();
@ -805,13 +783,34 @@ pub fn main(timer: GlobalTimer, cfg: Config) {
}
}
mgmt::start(cfg);
let cfg = Rc::new(cfg);
let restart_idle = Rc::new(Semaphore::new(1, 1));
mgmt::start(
cfg.clone(),
restart_idle.clone(),
Some(mgmt::DrtioContext(
aux_mutex.clone(),
drtio_routing_table.clone(),
timer,
)),
);
task::spawn(async move {
let connection = Rc::new(Semaphore::new(1, 1));
let terminate = Rc::new(Semaphore::new(0, 1));
let can_restart_idle = Rc::new(Semaphore::new(1, 1));
let restart_idle = restart_idle.clone();
loop {
let mut stream = TcpStream::accept(1381, 0x10_000, 0x10_000).await.unwrap();
let control = control.clone();
let mut maybe_stream = select_biased! {
s = (async {
TcpStream::accept(1381, 0x10_000, 0x10_000).await.unwrap()
}).fuse() => Some(s),
_ = (async {
restart_idle.async_wait().await;
can_restart_idle.async_wait().await;
}).fuse() => None
};
if connection.try_wait().is_none() {
// there is an existing connection
@ -819,47 +818,58 @@ pub fn main(timer: GlobalTimer, cfg: Config) {
connection.async_wait().await;
}
let maybe_idle_kernel = cfg.read("idle_kernel").ok();
if maybe_idle_kernel.is_none() && maybe_stream.is_none() {
control.borrow_mut().restart(); // terminate idle kernel if running
}
let control = control.clone();
let idle_kernel = idle_kernel.clone();
let connection = connection.clone();
let terminate = terminate.clone();
let can_restart_idle = can_restart_idle.clone();
let up_destinations = up_destinations.clone();
let aux_mutex = aux_mutex.clone();
let routing_table = drtio_routing_table.clone();
// we make sure the value of terminate is 0 before we start
let _ = terminate.try_wait();
let _ = can_restart_idle.try_wait();
task::spawn(async move {
let routing_table = routing_table.borrow();
select_biased! {
_ = (async {
let _ = handle_connection(&mut stream, control.clone(), &up_destinations, &aux_mutex, &routing_table, timer)
.await
.map_err(|e| warn!("connection terminated: {}", e));
if let Some(buffer) = &*idle_kernel {
info!("Loading idle kernel");
let res = handle_flash_kernel(&buffer, &control, &up_destinations, &aux_mutex, &routing_table, timer)
.await;
match res {
#[cfg(has_drtio)]
Err(Error::DestinationDown) => {
let mut countdown = timer.countdown();
delay(&mut countdown, Milliseconds(500)).await;
if let Some(stream) = &mut maybe_stream {
let _ = handle_connection(stream, control.clone(), &up_destinations, &aux_mutex, &routing_table, timer)
.await
.map_err(|e| warn!("connection terminated: {}", e));
}
can_restart_idle.signal();
match maybe_idle_kernel {
Some(buffer) => {
loop {
info!("loading idle kernel");
match handle_flash_kernel(&buffer, &control, &up_destinations, &aux_mutex, &routing_table, timer).await {
Ok(_) => {
info!("running idle kernel");
match handle_run_kernel(None, &control, &up_destinations, &aux_mutex, &routing_table, timer).await {
Ok(_) => info!("idle kernel finished"),
Err(_) => warn!("idle kernel running error")
}
},
Err(_) => warn!("idle kernel loading error")
}
}
Err(_) => warn!("error loading idle kernel"),
_ => (),
}
info!("Running idle kernel");
let _ = handle_run_kernel(None, &control, &up_destinations, &aux_mutex, &routing_table, timer)
.await.map_err(|_| warn!("error running idle kernel"));
info!("Idle kernel terminated");
},
None => info!("no idle kernel found")
}
}).fuse() => (),
_ = terminate.async_wait().fuse() => ()
}
connection.signal();
let _ = stream.flush().await;
let _ = stream.abort().await;
if let Some(stream) = maybe_stream {
let _ = stream.flush().await;
let _ = stream.abort().await;
}
});
}
});
@ -908,7 +918,8 @@ pub fn soft_panic_main(timer: GlobalTimer, cfg: Config) -> ! {
Sockets::init(32);
mgmt::start(cfg);
let dummy = Rc::new(Semaphore::new(0, 1));
mgmt::start(Rc::new(cfg), dummy, None);
// getting eth settings disables the LED as it resets GPIO
// need to re-enable it here

View File

@ -13,6 +13,8 @@ use core::cell::RefCell;
use ksupport;
use libasync::task;
#[cfg(has_cxp_phys)]
use libboard_artiq::cxp_phys;
#[cfg(has_drtio_eem)]
use libboard_artiq::drtio_eem;
#[cfg(feature = "target_kasli_soc")]
@ -150,5 +152,8 @@ pub fn main_core0() {
task::spawn(ksupport::report_async_rtio_errors());
#[cfg(has_cxp_phys)]
cxp_phys::setup(&mut timer);
comms::main(timer, cfg);
}

File diff suppressed because it is too large Load Diff

View File

@ -102,7 +102,7 @@ mod remote_moninj {
overrd: i8,
value: i8,
) {
let _lock = aux_mutex.lock();
let _lock = aux_mutex.async_lock().await;
drtioaux_async::send(
linkno,
&drtioaux_async::Packet::InjectionRequest {

View File

@ -1,6 +1,8 @@
#[cfg(not(feature = "target_ebaz4205"))]
use embedded_hal::blocking::delay::DelayMs;
#[cfg(has_si5324)]
use ksupport::i2c;
#[cfg(not(feature = "target_ebaz4205"))]
use libboard_artiq::pl;
#[cfg(has_si5324)]
use libboard_artiq::si5324;
@ -11,6 +13,8 @@ use libboard_zynq::i2c::I2c;
use libboard_zynq::timer::GlobalTimer;
use libconfig::Config;
use log::{info, warn};
#[cfg(feature = "target_ebaz4205")]
use {libboard_zynq::slcr, libregister::RegisterRW};
#[derive(Debug, PartialEq, Copy, Clone)]
#[allow(non_camel_case_types)]
@ -69,7 +73,7 @@ fn get_rtio_clock_cfg(cfg: &Config) -> RtioClock {
res
}
#[cfg(not(has_drtio))]
#[cfg(not(any(has_drtio, feature = "target_ebaz4205")))]
fn init_rtio(timer: &mut GlobalTimer) {
info!("Switching SYS clocks...");
unsafe {
@ -406,6 +410,38 @@ fn get_si549_setting(clk: RtioClock) -> si549::FrequencySetting {
}
}
#[cfg(feature = "target_ebaz4205")]
fn set_fclk0_freq(clk: RtioClock, cfg: &Config) {
let io_pll_freq: u32 = 1_000_000_000; // Hardcoded in zynq-rs
let mut target_freq = 0;
let mut divisor0 = 1u8;
match clk {
RtioClock::Int_100 => {
target_freq = 100_000_000;
divisor0 = 10;
}
RtioClock::Int_125 => {
target_freq = 125_000_000;
divisor0 = 8;
}
_ => {
warn!("Unsupported RTIO Clock: '{:?}'", clk);
return;
}
}
slcr::RegisterBlock::unlocked(|slcr| {
slcr.fpga0_clk_ctrl.modify(|_, w| w.divisor0(divisor0));
});
info!(
"Set FCLK0 to {:.2} MHz (target: {} MHz).",
io_pll_freq as f64 / divisor0 as f64,
target_freq / 1_000_000
);
}
pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
let clk = get_rtio_clock_cfg(cfg);
#[cfg(has_si5324)]
@ -429,9 +465,19 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
#[cfg(has_drtio)]
init_drtio(timer);
#[cfg(not(has_drtio))]
#[cfg(not(any(has_drtio, feature = "target_ebaz4205")))]
init_rtio(timer);
#[cfg(feature = "target_ebaz4205")]
{
match clk {
RtioClock::Int_100 | RtioClock::Int_125 => {
set_fclk0_freq(clk, cfg);
}
_ => {} // Not set for external clocks
}
}
#[cfg(all(has_si549, has_wrpll))]
{
// SYS CLK switch will reset CSRs that are used by WRPLL

View File

@ -3,7 +3,9 @@ use core::cell::RefCell;
use libboard_artiq::{drtio_routing, drtio_routing::RoutingTable, pl::csr};
use libboard_zynq::timer::GlobalTimer;
use libconfig::Config;
use libcortex_a9::mutex::Mutex;
use log::{info, warn};
#[cfg(has_drtio)]
pub mod drtio {
@ -11,9 +13,13 @@ pub mod drtio {
use core::fmt;
use embedded_hal::blocking::delay::DelayMs;
#[cfg(has_drtio_eem)]
use embedded_hal::blocking::delay::DelayUs;
use ksupport::{resolve_channel_name, ASYNC_ERROR_BUSY, ASYNC_ERROR_COLLISION, ASYNC_ERROR_SEQUENCE_ERROR,
SEEN_ASYNC_ERRORS};
use libasync::{delay, task};
#[cfg(has_drtio_eem)]
use libboard_artiq::drtio_eem;
use libboard_artiq::{drtioaux::Error as DrtioError,
drtioaux_async,
drtioaux_async::Packet,
@ -24,6 +30,10 @@ pub mod drtio {
use super::*;
use crate::{analyzer::remote_analyzer::RemoteBuffer, rtio_dma::remote_dma, subkernel};
#[cfg(has_drtio_eem)]
const DRTIO_EEM_LINKNOS: core::ops::Range<usize> =
(csr::DRTIO.len() - csr::CONFIG_EEM_DRTIO_COUNT as usize)..csr::DRTIO.len();
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Error {
Timeout,
@ -74,8 +84,18 @@ pub mod drtio {
});
}
async fn link_rx_up(linkno: u8) -> bool {
async fn link_rx_up(linkno: u8, _timer: &mut GlobalTimer) -> bool {
let linkno = linkno as usize;
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&linkno) {
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start;
unsafe {
csr::eem_transceiver::transceiver_sel_write(eem_trx_no as u8);
csr::eem_transceiver::comma_align_reset_write(1);
}
_timer.delay_us(100);
return unsafe { csr::eem_transceiver::comma_read() == 1 };
}
unsafe { (csr::DRTIO[linkno].rx_up_read)() == 1 }
}
@ -127,6 +147,8 @@ pub mod drtio {
| Packet::SubkernelLoadRunReply { destination, .. }
| Packet::SubkernelMessage { destination, .. }
| Packet::SubkernelMessageAck { destination, .. }
| Packet::SubkernelException { destination, .. }
| Packet::SubkernelExceptionRequest { destination, .. }
| Packet::DmaPlaybackStatus { destination, .. }
| Packet::SubkernelFinished { destination, .. } => {
if destination == 0 {
@ -148,8 +170,8 @@ pub mod drtio {
}
}
async fn recv_aux_timeout(linkno: u8, timeout: u64, timer: GlobalTimer) -> Result<Packet, Error> {
if !link_rx_up(linkno).await {
async fn recv_aux_timeout(linkno: u8, timeout: u64, mut timer: GlobalTimer) -> Result<Packet, Error> {
if !link_rx_up(linkno, &mut timer).await {
return Err(Error::LinkDown);
}
match drtioaux_async::recv_timeout(linkno, Some(timeout), timer).await {
@ -164,9 +186,9 @@ pub mod drtio {
linkno: u8,
routing_table: &RoutingTable,
request: &Packet,
timer: GlobalTimer,
mut timer: GlobalTimer,
) -> Result<Packet, Error> {
if !link_rx_up(linkno).await {
if !link_rx_up(linkno, &mut timer).await {
return Err(Error::LinkDown);
}
let _lock = aux_mutex.async_lock().await;
@ -181,10 +203,7 @@ pub mod drtio {
async fn drain_buffer(linkno: u8, draining_time: Milliseconds, timer: GlobalTimer) {
let max_time = timer.get_time() + draining_time;
loop {
if timer.get_time() > max_time {
return;
}
while timer.get_time() < max_time {
let _ = drtioaux_async::recv(linkno).await;
}
}
@ -193,11 +212,11 @@ pub mod drtio {
aux_mutex: &Rc<Mutex<bool>>,
linkno: u8,
routing_table: &RoutingTable,
timer: GlobalTimer,
mut timer: GlobalTimer,
) -> u32 {
let mut count = 0;
loop {
if !link_rx_up(linkno).await {
if !link_rx_up(linkno, &mut timer).await {
return 0;
}
count += 1;
@ -461,7 +480,7 @@ pub mod drtio {
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &RoutingTable,
up_destinations: &Rc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
timer: GlobalTimer,
mut timer: GlobalTimer,
) {
let mut up_links = [false; csr::DRTIO.len()];
loop {
@ -469,16 +488,35 @@ pub mod drtio {
let linkno = linkno as u8;
if up_links[linkno as usize] {
/* link was previously up */
if link_rx_up(linkno).await {
if link_rx_up(linkno, &mut timer).await {
process_unsolicited_aux(aux_mutex, linkno, routing_table).await;
process_local_errors(linkno).await;
} else {
info!("[LINK#{}] link is down", linkno);
up_links[linkno as usize] = false;
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
unsafe {
csr::eem_transceiver::rx_ready_write(0);
}
while !matches!(drtioaux_async::recv(linkno).await, Ok(None)) {}
}
}
} else {
/* link was previously down */
if link_rx_up(linkno).await {
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start as u8;
if !unsafe { drtio_eem::align_wordslip(&mut timer, eem_trx_no) } {
continue;
}
unsafe {
csr::eem_transceiver::rx_ready_write(1);
}
}
if link_rx_up(linkno, &mut timer).await {
info!("[LINK#{}] link RX became up, pinging", linkno);
let ping_count = ping_remote(aux_mutex, linkno, routing_table, timer).await;
if ping_count > 0 {
@ -522,7 +560,7 @@ pub mod drtio {
for linkno in 0..csr::DRTIO.len() {
let linkno = linkno as u8;
if task::block_on(link_rx_up(linkno)) {
if task::block_on(link_rx_up(linkno, &mut timer)) {
let reply = task::block_on(aux_transact(
&aux_mutex,
linkno,
@ -539,7 +577,7 @@ pub mod drtio {
}
}
async fn partition_data<PacketF, HandlerF>(
pub async fn partition_data<PacketF, HandlerF>(
linkno: u8,
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &RoutingTable,
@ -791,6 +829,7 @@ pub mod drtio {
id: u32,
destination: u8,
run: bool,
timestamp: u64,
) -> Result<(), Error> {
let linkno = routing_table.0[destination as usize][0] - 1;
let reply = aux_transact(
@ -802,6 +841,7 @@ pub mod drtio {
source: 0,
destination: destination,
run: run,
timestamp,
},
timer,
)
@ -833,13 +873,19 @@ pub mod drtio {
linkno,
routing_table,
&Packet::SubkernelExceptionRequest {
source: 0,
destination: destination,
},
timer,
)
.await?;
match reply {
Packet::SubkernelException { last, length, data } => {
Packet::SubkernelException {
destination: 0,
last,
length,
data,
} => {
remote_data.extend(&data[0..length as usize]);
if last {
return Ok(remote_data);
@ -898,12 +944,36 @@ pub mod drtio {
pub fn reset(_aux_mutex: Rc<Mutex<bool>>, _routing_table: &RoutingTable, mut _timer: GlobalTimer) {}
}
fn toggle_sed_spread(val: u8) {
unsafe {
csr::rtio_core::sed_spread_enable_write(val);
}
}
fn setup_sed_spread(cfg: &Config) {
if let Ok(spread_enable) = cfg.read_str("sed_spread_enable") {
match spread_enable.as_ref() {
"1" => toggle_sed_spread(1),
"0" => toggle_sed_spread(0),
_ => {
warn!("sed_spread_enable value not supported (only 1, 0 allowed), disabling by default");
toggle_sed_spread(0)
}
};
} else {
info!("SED spreading disabled by default");
toggle_sed_spread(0);
}
}
pub fn startup(
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &Rc<RefCell<RoutingTable>>,
up_destinations: &Rc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
cfg: &Config,
timer: GlobalTimer,
) {
setup_sed_spread(cfg);
drtio::startup(aux_mutex, routing_table, up_destinations, timer);
unsafe {
csr::rtio_core::reset_phy_write(1);

View File

@ -100,12 +100,22 @@ pub async fn load(
timer: GlobalTimer,
id: u32,
run: bool,
timestamp: u64,
) -> Result<(), Error> {
if let Some(subkernel) = SUBKERNELS.async_lock().await.get_mut(&id) {
if subkernel.state != SubkernelState::Uploaded {
return Err(Error::IncorrectState);
}
drtio::subkernel_load(aux_mutex, routing_table, timer, id, subkernel.destination, run).await?;
drtio::subkernel_load(
aux_mutex,
routing_table,
timer,
id,
subkernel.destination,
run,
timestamp,
)
.await?;
if run {
subkernel.state = SubkernelState::Running;
}

View File

@ -15,7 +15,9 @@ build_zynq = { path = "../libbuild_zynq" }
[dependencies]
log = { version = "0.4", default-features = false }
byteorder = { version = "1.3", default-features = false }
core_io = { version = "0.1", features = ["collections"] }
crc = { version = "1.7", default-features = false }
cslice = "0.3"
embedded-hal = "0.2"

File diff suppressed because it is too large Load Diff

149
src/satman/src/mgmt.rs Normal file
View File

@ -0,0 +1,149 @@
use alloc::vec::Vec;
use byteorder::{ByteOrder, NativeEndian};
use crc::crc32;
use io::{ProtoRead, ProtoWrite};
use libboard_artiq::{drtioaux_proto::SAT_PAYLOAD_MAX_SIZE,
logger::{BufferLogger, LogBufferRef}};
use libconfig::Config;
use log::{debug, error, info, warn, LevelFilter};
use crate::routing::{SliceMeta, Sliceable};
type Result<T> = core::result::Result<T, ()>;
pub fn byte_to_level_filter(level_byte: u8) -> Result<LevelFilter> {
Ok(match level_byte {
0 => LevelFilter::Off,
1 => LevelFilter::Error,
2 => LevelFilter::Warn,
3 => LevelFilter::Info,
4 => LevelFilter::Debug,
5 => LevelFilter::Trace,
lv => {
error!("unknown log level: {}", lv);
return Err(());
}
})
}
fn get_logger_buffer() -> LogBufferRef<'static> {
let logger = unsafe { BufferLogger::get_logger().as_mut().unwrap() };
loop {
if let Some(buffer_ref) = logger.buffer() {
return buffer_ref;
}
}
}
pub fn clear_log() {
let mut buffer = get_logger_buffer();
buffer.clear();
}
pub struct Manager<'a> {
cfg: &'a mut Config,
last_log: Sliceable,
config_payload: Vec<u8>,
last_value: Sliceable,
image_payload: Vec<u8>,
}
impl<'a> Manager<'_> {
pub fn new(cfg: &mut Config) -> Manager {
Manager {
cfg: cfg,
last_log: Sliceable::new(0, Vec::new()),
config_payload: Vec::new(),
last_value: Sliceable::new(0, Vec::new()),
image_payload: Vec::new(),
}
}
pub fn log_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE], consume: bool) -> SliceMeta {
// Populate buffer if depleted
if self.last_log.at_end() {
let mut buffer = get_logger_buffer();
self.last_log.extend(buffer.extract().as_bytes());
if consume {
buffer.clear();
}
}
self.last_log.get_slice_satellite(data_slice)
}
pub fn fetch_config_value(&mut self, key: &str) -> Result<()> {
self.cfg
.read(&key)
.map(|value| {
debug!("got value");
self.last_value = Sliceable::new(0, value)
})
.map_err(|_| warn!("read error: no such key"))
}
pub fn get_config_value_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta {
self.last_value.get_slice_satellite(data_slice)
}
pub fn add_config_data(&mut self, data: &[u8], data_len: usize) {
self.config_payload.write_all(&data[..data_len]).unwrap();
}
pub fn clear_config_data(&mut self) {
self.config_payload.clear();
}
pub fn write_config(&mut self) -> Result<()> {
let mut payload = &self.config_payload[..];
let key = payload.read_string().map_err(|_err| error!("error on reading key"))?;
debug!("write key: {}", key);
let value = payload.read_bytes().unwrap();
self.cfg
.write(&key, value)
.map(|()| debug!("write success"))
.map_err(|err| error!("failed to write: {:?}", err))
}
pub fn remove_config(&mut self, key: &str) -> Result<()> {
debug!("erase key: {}", key);
self.cfg
.remove(&key)
.map(|()| debug!("erase success"))
.map_err(|err| warn!("failed to erase: {:?}", err))
}
pub fn allocate_image_buffer(&mut self, image_size: usize) {
self.image_payload = Vec::with_capacity(image_size);
}
pub fn add_image_data(&mut self, data: &[u8], data_len: usize) {
self.image_payload.extend(&data[..data_len]);
}
pub fn write_image(&self) {
let mut image = self.image_payload.clone();
let image_ref = &image[..];
let bin_len = image.len() - 4;
let (image_ref, expected_crc) = {
let (image_ref, crc_slice) = image_ref.split_at(bin_len);
(image_ref, NativeEndian::read_u32(crc_slice))
};
let actual_crc = crc32::checksum_ieee(image_ref);
if actual_crc == expected_crc {
info!("CRC passed. Writing boot image to SD card...");
image.truncate(bin_len);
self.cfg.write("boot", image).expect("failed to write boot image");
} else {
panic!(
"CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})",
actual_crc, expected_crc
);
}
}
}

View File

@ -87,6 +87,10 @@ impl Repeater {
if rep_link_rx_up(self.repno) {
if let Ok(Some(drtioaux::Packet::EchoReply)) = drtioaux::recv(self.auxno) {
info!("[REP#{}] remote replied after {} packets", self.repno, ping_count);
let max_time = timer.get_time() + Milliseconds(200);
while timer.get_time() < max_time {
let _ = drtioaux::recv(self.auxno);
}
self.state = RepeaterState::Up;
if let Err(e) = self.sync_tsc(timer) {
error!("[REP#{}] failed to sync TSC ({:?})", self.repno, e);
@ -204,10 +208,37 @@ impl Repeater {
}
}
pub fn aux_forward(&self, request: &drtioaux::Packet, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> {
pub fn aux_forward(
&self,
request: &drtioaux::Packet,
router: &mut Router,
routing_table: &drtio_routing::RoutingTable,
rank: u8,
self_destination: u8,
timer: &mut GlobalTimer,
) -> Result<(), drtioaux::Error> {
self.aux_send(request)?;
let reply = self.recv_aux_timeout(200, timer)?;
drtioaux::send(0, &reply).unwrap();
loop {
let reply = self.recv_aux_timeout(200, timer)?;
match reply {
// async/locally requested packets to be consumed or routed
// these may come while a packet would be forwarded
drtioaux::Packet::DmaPlaybackStatus { .. }
| drtioaux::Packet::SubkernelFinished { .. }
| drtioaux::Packet::SubkernelMessage { .. }
| drtioaux::Packet::SubkernelMessageAck { .. }
| drtioaux::Packet::SubkernelLoadRunReply { .. }
| drtioaux::Packet::SubkernelException { .. }
| drtioaux::Packet::DmaAddTraceReply { .. }
| drtioaux::Packet::DmaPlaybackReply { .. } => {
router.route(reply, routing_table, rank, self_destination);
}
_ => {
drtioaux::send(0, &reply).unwrap();
break;
}
}
}
Ok(())
}

View File

@ -57,8 +57,8 @@ impl Sliceable {
self.data.extend(data);
}
get_slice_fn!(get_slice_sat, SAT_PAYLOAD_MAX_SIZE);
get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE);
get_slice_fn!(get_slice_satellite, SAT_PAYLOAD_MAX_SIZE);
}
// Packets from downstream (further satellites) are received and routed appropriately.

View File

@ -8,10 +8,10 @@ use core_io::{Error as IoError, Write};
use cslice::AsCSlice;
use dma::{Error as DmaError, Manager as DmaManager};
use io::{Cursor, ProtoWrite};
use ksupport::{eh_artiq, kernel, rpc};
use ksupport::{eh_artiq, kernel, rpc, rtio};
use libboard_artiq::{drtio_routing::RoutingTable,
drtioaux,
drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE},
drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE},
pl::csr};
use libboard_zynq::{time::Milliseconds, timer::GlobalTimer};
use libcortex_a9::sync_channel::Receiver;
@ -47,6 +47,9 @@ enum KernelState {
DmaAwait {
max_time: Milliseconds,
},
SubkernelRetrievingException {
destination: u8,
},
}
#[derive(Debug)]
@ -123,10 +126,11 @@ struct MessageManager {
struct Session {
id: u32,
kernel_state: KernelState,
last_exception: Option<Sliceable>,
last_exception: Option<Sliceable>, // exceptions raised locally
external_exception: Option<Vec<u8>>, // exceptions from sub-subkernels
messages: MessageManager,
source: u8, // which destination requested running the kernel
subkernels_finished: Vec<u32>,
subkernels_finished: Vec<(u32, Option<u8>)>,
}
impl Session {
@ -135,6 +139,7 @@ impl Session {
id: id,
kernel_state: KernelState::Absent,
last_exception: None,
external_exception: None,
messages: MessageManager::new(),
source: 0,
subkernels_finished: Vec::new(),
@ -344,7 +349,7 @@ impl<'a> Manager<'_> {
}
}
pub fn run(&mut self, source: u8, id: u32) -> Result<(), Error> {
pub fn run(&mut self, source: u8, id: u32, timestamp: u64) -> Result<(), Error> {
if self.session.kernel_state != KernelState::Loaded || self.session.id != id {
self.load(id)?;
}
@ -354,6 +359,7 @@ impl<'a> Manager<'_> {
csr::cri_con::selected_write(2);
}
rtio::at_mu(timestamp as i64);
self.control.tx.send(kernel::Message::StartRequest);
Ok(())
}
@ -410,9 +416,9 @@ impl<'a> Manager<'_> {
}
}
pub fn exception_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta {
pub fn exception_get_slice(&mut self, data_slice: &mut [u8; MASTER_PAYLOAD_MAX_SIZE]) -> SliceMeta {
match self.session.last_exception.as_mut() {
Some(exception) => exception.get_slice_sat(data_slice),
Some(exception) => exception.get_slice_master(data_slice),
None => SliceMeta {
destination: 0,
len: 0,
@ -540,7 +546,7 @@ impl<'a> Manager<'_> {
return;
}
match self.process_external_messages(timer) {
match self.process_external_messages(router, routing_table, rank, destination, timer) {
Ok(()) => (),
Err(Error::AwaitingMessage) => return, // kernel still waiting, do not process kernel messages
Err(Error::KernelException(exception)) => {
@ -596,6 +602,41 @@ impl<'a> Manager<'_> {
}
}
fn check_finished_kernels(
&mut self,
id: u32,
router: &mut Router,
routing_table: &RoutingTable,
rank: u8,
self_destination: u8,
) {
for (i, (status, exception_source)) in self.session.subkernels_finished.iter().enumerate() {
if *status == id {
if exception_source.is_none() {
self.control.tx.send(kernel::Message::SubkernelAwaitFinishReply);
self.session.kernel_state = KernelState::Running;
self.session.subkernels_finished.swap_remove(i);
} else {
let destination = exception_source.unwrap();
self.session.external_exception = Some(Vec::new());
self.session.kernel_state = KernelState::SubkernelRetrievingException {
destination: destination,
};
router.route(
drtioaux::Packet::SubkernelExceptionRequest {
source: self_destination,
destination: destination,
},
&routing_table,
rank,
self_destination,
);
}
break;
}
}
}
pub fn subkernel_load_run_reply(&mut self, succeeded: bool) {
if self.session.kernel_state == KernelState::SubkernelAwaitLoad {
self.control
@ -608,16 +649,46 @@ impl<'a> Manager<'_> {
}
pub fn remote_subkernel_finished(&mut self, id: u32, with_exception: bool, exception_source: u8) {
if with_exception {
self.kernel_stop();
self.last_finished = Some(SubkernelFinished {
source: self.session.source,
id: self.session.id,
with_exception: true,
exception_source: exception_source,
})
let exception_src = if with_exception { Some(exception_source) } else { None };
self.session.subkernels_finished.push((id, exception_src));
}
pub fn received_exception(
&mut self,
exception_data: &[u8],
last: bool,
router: &mut Router,
routing_table: &RoutingTable,
rank: u8,
self_destination: u8,
) {
if let KernelState::SubkernelRetrievingException { destination } = self.session.kernel_state {
self.session
.external_exception
.as_mut()
.unwrap()
.extend_from_slice(exception_data);
if last {
self.control
.tx
.send(kernel::Message::SubkernelError(kernel::SubkernelStatus::Exception(
self.session.external_exception.take().unwrap(),
)));
self.session.kernel_state = KernelState::Running;
} else {
/* fetch another slice */
router.route(
drtioaux::Packet::SubkernelExceptionRequest {
source: self_destination,
destination: destination,
},
routing_table,
rank,
self_destination,
);
}
} else {
self.session.subkernels_finished.push(id);
warn!("Received unsolicited exception data");
}
}
@ -742,6 +813,7 @@ impl<'a> Manager<'_> {
id,
destination: sk_destination,
run,
timestamp,
} => {
self.session.kernel_state = KernelState::SubkernelAwaitLoad;
router.route(
@ -750,6 +822,7 @@ impl<'a> Manager<'_> {
destination: sk_destination,
id: id,
run: run,
timestamp,
},
routing_table,
rank,
@ -780,28 +853,35 @@ impl<'a> Manager<'_> {
Ok(false)
}
fn process_external_messages(&mut self, timer: &GlobalTimer) -> Result<(), Error> {
fn process_external_messages(
&mut self,
router: &mut Router,
routing_table: &RoutingTable,
rank: u8,
self_destination: u8,
timer: &GlobalTimer,
) -> Result<(), Error> {
match &self.session.kernel_state {
KernelState::MsgAwait { max_time, id, tags } => {
if let Some(max_time) = *max_time {
if timer.get_time() > max_time {
self.control.tx.send(kernel::Message::SubkernelMsgRecvReply {
status: kernel::SubkernelStatus::Timeout,
count: 0,
});
self.control
.tx
.send(kernel::Message::SubkernelError(kernel::SubkernelStatus::Timeout));
self.session.kernel_state = KernelState::Running;
return Ok(());
}
}
if let Some(message) = self.session.messages.get_incoming(*id) {
self.control.tx.send(kernel::Message::SubkernelMsgRecvReply {
status: kernel::SubkernelStatus::NoError,
count: message.count,
});
self.control
.tx
.send(kernel::Message::SubkernelMsgRecvReply { count: message.count });
let tags = tags.clone();
self.session.kernel_state = KernelState::Running;
self.pass_message_to_kernel(&message, tags, timer)
} else {
let id = *id;
self.check_finished_kernels(id, router, routing_table, rank, self_destination);
Err(Error::AwaitingMessage)
}
}
@ -817,27 +897,18 @@ impl<'a> Manager<'_> {
KernelState::SubkernelAwaitFinish { max_time, id } => {
if let Some(max_time) = *max_time {
if timer.get_time() > max_time {
self.control.tx.send(kernel::Message::SubkernelAwaitFinishReply {
status: kernel::SubkernelStatus::Timeout,
});
self.control
.tx
.send(kernel::Message::SubkernelError(kernel::SubkernelStatus::Timeout));
self.session.kernel_state = KernelState::Running;
return Ok(());
}
}
let mut i = 0;
for status in &self.session.subkernels_finished {
if *status == *id {
self.control.tx.send(kernel::Message::SubkernelAwaitFinishReply {
status: kernel::SubkernelStatus::NoError,
});
self.session.kernel_state = KernelState::Running;
self.session.subkernels_finished.swap_remove(i);
break;
}
i += 1;
}
let id = *id;
self.check_finished_kernels(id, router, routing_table, rank, self_destination);
Ok(())
}
KernelState::SubkernelRetrievingException { .. } => Err(Error::AwaitingMessage),
KernelState::DmaAwait { max_time } | KernelState::DmaPendingAwait { max_time, .. } => {
if timer.get_time() > *max_time {
self.control.tx.send(kernel::Message::DmaAwaitRemoteReply {