Compare commits

..

25 Commits

Author SHA1 Message Date
morgan 8822fcbc1d 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
2024-11-13 16:16:49 +08:00
morgan ca95feaabd cxp coredevice driver: init 2024-11-13 16:16:44 +08:00
morgan 4a1d41832b api: add cxp api support for CTRL packet 2024-11-13 16:16:44 +08:00
morgan 7fb4b4088e 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
2024-11-13 16:16:44 +08:00
morgan 67a7d39b54 cxp downconn firmware: packet testing 2024-11-13 16:16:44 +08:00
morgan 2991950757 cxp upconn firmware: packet testing 2024-11-13 16:16:44 +08:00
morgan 3a61bc9ce5 cxp protocol: init
testing: add packet printing helper function
testing: add rx loopback
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
2024-11-13 16:16:44 +08:00
morgan fbd244cbe5 Cargo: add byteorder 2024-11-13 16:16:44 +08:00
morgan 5ba1da6e40 main: add testing 2024-11-13 16:16:44 +08:00
morgan 19beb8488e 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
2024-11-13 16:16:44 +08:00
morgan 577fa03b78 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
2024-11-13 16:16:44 +08:00
morgan 1a3ac9763d 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
2024-11-13 16:16:44 +08:00
morgan ba1e1f9951 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
2024-11-13 16:16:44 +08:00
morgan dd8f4a172b 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
2024-11-13 16:16:19 +08:00
morgan 08ae1e92c5 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
2024-11-13 16:16:19 +08:00
morgan 3a5d508674 fmc: add cxp_4r_fmc adepter io 2024-11-13 16:16:19 +08:00
morgan 9f306063a2 temp flake & local_run mod
flake: download llvm11 binary instead of compiling
local_run: preset cxp zc706 dev board ip addr
flake: add pillow for sim
2024-11-13 16:16:19 +08:00
morgan bfa16e0560 flake: add CXP_DEMO variant build options 2024-11-13 16:16:08 +08:00
morgan f4362c1e54 flake: update dependencies 2024-11-13 16:16:07 +08:00
Sebastien Bourdeauducq a3ae5ddd00 flake: update dependencies 2024-11-13 16:16:06 +08:00
abdul124 cb1558ec4e firmware: add unit tests for exception sync 2024-11-13 16:16:05 +08:00
abdul124 d514171d6f firmware: sync exception names and ids 2024-11-13 16:16:05 +08:00
abdul124 c3ae34123c fix nalgebra url 2024-11-13 16:16:04 +08:00
abdul124 9f5e15e6d2 kernel/linalg: remove redundant unsafe blocks 2024-11-13 16:16:03 +08:00
Sebastien Bourdeauducq fbb636460b cargo fmt 2024-11-13 16:16:03 +08:00
50 changed files with 4674 additions and 2433 deletions

View File

@ -4,102 +4,60 @@ ARTIQ on Zynq
How to use
----------
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.
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.
Configuration
-------------
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.
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.
The following configuration keys are available among others:
The following configuration keys are available:
- ``mac``: Ethernet MAC address.
- ``ip``: IPv4 address.
- ``ip6``: IPv6 address.
- ``idle_kernel``: idle kernel in ELF format (as produced by ``artiq_compile``).
- ``startup_kernel``: startup kernel in ELF format (as produced by ``artiq_compile``).
- ``startup``: 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.
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.
Configurations can be read/written/removed via ``artiq_coremgmt``. Config erase is
not implemented as it seems not very useful.
Development instructions
------------------------
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``).
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``).
**Pure build with Nix:**
Pure build with Nix and execution on a remote JTAG server:
```shell
nix build .#zc706-nist_clock-jtag # or zc706-nist_qc2-jtag or zc706-nist_clock-sd or etc
nix build .#zc706-nist_clock-jtag # or zc706-nist_qc2-jtag or zc706-nist_clock_satellite-jtag etc.
./remote_run.sh
```
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. :
Impure incremental build and execution on a remote JTAG server:
```shell
nix develop
cd src
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
gateware/zc706.py -g ../build/gateware -V <variant> # build gateware
make GWARGS="-V <variant>" <runtime/satman> # build firmware
cd ..
./remote_run.sh -i
```
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 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.
- If the board is connected to the local machine, use the ``local_run.sh`` script.
License
-------

58
cxp_kernel.py Normal file
View File

@ -0,0 +1,58 @@
"""
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
from artiq.experiment import *
class CoaXPress:
def __init__(self, channel, core_device="core"):
# __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 = channel
# the first 8 bits is reserved for the rtlink.OInterface.addr not for channel no.
self.target_o = channel << 8
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def trigger(self, linktrig, trigdelay):
rtio_output(self.target_o, linktrig | trigdelay << 1)
@syscall(flags={"nounwind", "nowrite"})
def cxp_readu32(channel: TInt32, addr: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def cxp_writeu32(channel: TInt32, addr: TInt32, val: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
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)
@kernel
def run(self):
self.core.reset()
# cxp_readu32(0, 3)
# cxp_writeu32(0, 0, 0xABCD)
self.cxp.trigger(1, 10)

View File

@ -1,78 +0,0 @@
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"},
},
)

View File

@ -11,11 +11,11 @@
"src-pythonparser": "src-pythonparser"
},
"locked": {
"lastModified": 1732066716,
"narHash": "sha256-krjvt9+RccnAxSEZcFhRpjA2S3CoqE4MSa1JUg421b4=",
"lastModified": 1725373154,
"narHash": "sha256-fq9EW9fDWrV0v1vNj7ZqDNpNYx8+OxoFdPwpvkPf67g=",
"ref": "refs/heads/master",
"rev": "270a417a28b516d36983779a1adb6d33a3c55a4a",
"revCount": 9102,
"rev": "0c1ffa9f4f6a3e7864459923ec4b9cc45f16327a",
"revCount": 9005,
"type": "git",
"url": "https://github.com/m-labs/artiq.git"
},
@ -68,13 +68,45 @@
"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"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1731319897,
"narHash": "sha256-PbABj4tnbWFMfBp6OcUK5iGy1QY+/Z96ZcLpooIbuEI=",
"lastModified": 1724224976,
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dc460ec76cbff0e66e269457d7b728432263166c",
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
"type": "github"
},
"original": {
@ -84,9 +116,27 @@
"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"
}
},
"root": {
"inputs": {
"artiq": "artiq",
"mozilla-overlay": "mozilla-overlay",
"nixpkgs_old": "nixpkgs_old",
"zynq-rs": "zynq-rs"
}
},
@ -112,28 +162,6 @@
"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": [
@ -142,11 +170,11 @@
]
},
"locked": {
"lastModified": 1728371104,
"narHash": "sha256-PPnAyDedUQ7Og/Cby9x5OT9wMkNGTP8GS53V6N/dk4w=",
"lastModified": 1717637367,
"narHash": "sha256-4mSm9wl5EMgzzrW6w86IDUevkEOT99FESHGcxcyQbD0=",
"owner": "m-labs",
"repo": "sipyco",
"rev": "094a6cd63ffa980ef63698920170e50dc9ba77fd",
"rev": "02b96ec2473a3c3d3c980899de2564ddce949dab",
"type": "github"
},
"original": {
@ -158,11 +186,11 @@
"src-migen": {
"flake": false,
"locked": {
"lastModified": 1727677091,
"narHash": "sha256-Zg3SQnTwMM/VkOGKogbPyuCC2NhLy8HB2SPEUWWNgCU=",
"lastModified": 1724304798,
"narHash": "sha256-tQ02N0eXY5W/Z7CrOy3Cu4WjDZDQWb8hYlzsFzr3Mus=",
"owner": "m-labs",
"repo": "migen",
"rev": "c19ae9f8ae162ffe2d310a92bfce53ac2a821bc8",
"rev": "832a7240ba32af9cbd4fdd519ddcb4f912534726",
"type": "github"
},
"original": {
@ -174,11 +202,11 @@
"src-misoc": {
"flake": false,
"locked": {
"lastModified": 1729234629,
"narHash": "sha256-TLsTCXV5AC2xh+bS7EhBVBKqdqIU3eKrnlWcFF9LtAM=",
"lastModified": 1715647536,
"narHash": "sha256-q+USDcaKHABwW56Jzq8u94iGPWlyLXMyVt0j/Gyg+IE=",
"ref": "refs/heads/master",
"rev": "6085a312bca26adeca6584e37d08c8ba2e1d6e38",
"revCount": 2460,
"rev": "fea9de558c730bc394a5936094ae95bb9d6fa726",
"revCount": 2455,
"submodules": true,
"type": "git",
"url": "https://github.com/m-labs/misoc.git"
@ -222,18 +250,18 @@
},
"zynq-rs": {
"inputs": {
"mozilla-overlay": "mozilla-overlay_2",
"nixpkgs": [
"artiq",
"nixpkgs"
],
"rust-overlay": "rust-overlay_2"
]
},
"locked": {
"lastModified": 1731749494,
"narHash": "sha256-WGigAhvVCGN5YZ1dHPyvoqAh47W1Gtph036O1aKFlLE=",
"lastModified": 1720537402,
"narHash": "sha256-ybvaQ48SVBqYVqgYmGUdefGZkni7PJ90qYQPHnFOwDs=",
"ref": "refs/heads/master",
"rev": "12975de2e110d7948bf47b768559f727d0abc8fc",
"revCount": 655,
"rev": "b2b3e5c933cbc4b7cb14adde480d7561a3ae71ee",
"revCount": 648,
"type": "git",
"url": "https://git.m-labs.hk/m-labs/zynq-rs"
},

View File

@ -2,15 +2,20 @@
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.zynq-rs.url = git+https://git.m-labs.hk/m-labs/zynq-rs;
inputs.zynq-rs.inputs.nixpkgs.follows = "artiq/nixpkgs";
outputs = { self, zynq-rs, artiq }:
inputs.nixpkgs_old.url = "github:nixos/nixpkgs?ref=nixos-23.11";
outputs = { self, mozilla-overlay, zynq-rs, artiq, nixpkgs_old }:
let
pkgs = import artiq.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import zynq-rs.inputs.rust-overlay) ]; };
pkgs_old = import nixpkgs_old { system = "x86_64-linux";};
pkgs = import artiq.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-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;
@ -74,7 +79,7 @@
propagatedBuildInputs = with pkgs.python3Packages; [ setuptools click numpy toolz jinja2 ramda artiqpkgs.migen artiqpkgs.misoc ];
checkInputs = with pkgs.python3Packages; [ pytestCheckHook pytest-timeout ];
checkInputs = with pkgs.python3Packages; [ pytest-runner pytestCheckHook pytest-timeout ];
# migen/misoc version checks are broken with pyproject for some reason
postPatch = ''
@ -341,6 +346,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,8 +369,7 @@
(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 = "ebaz4205"; variant = "base"; });
(board-package-set { target = "kasli_soc"; variant = "satellite"; json = ./kasli-soc-satellite.json; });
hydraJobs = packages.x86_64-linux // { inherit zc706-hitl-tests; inherit gateware-sim; inherit fmt-check; };
@ -380,11 +385,10 @@
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

131
sim_frame.py Normal file
View File

@ -0,0 +1,131 @@
from migen import *
from misoc.interconnect import stream
from sim_pipeline import *
from sim_generator import CXPCRC32Inserter
from sim_frame_gen import get_frame_packet
from src.gateware.cxp_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()
self.submodules.stream_pipe = stream_pipe = Stream_Pipeline()
pipeline = [buffer, crc_inserter, dchar_decoder, 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 sim_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

120
sim_generator.py Normal file
View File

@ -0,0 +1,120 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from sim_pipeline import CXPCRC32
from src.gateware.cxp_pipeline import *
class CXPCRC32Inserter(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout)
self.source = stream.Endpoint(word_layout)
# # #
self.submodules.crc = crc = CXPCRC32(word_dw)
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
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),
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

406
sim_pipeline.py Normal file
View File

@ -0,0 +1,406 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect import stream
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine
from src.gateware.cxp_pipeline import *
from types import SimpleNamespace
class EOP_Marker(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
# # #
last_stb = Signal()
self.sync += [
If((~self.source.stb | self.source.ack),
self.source.stb.eq(self.sink.stb),
self.source.payload.eq(self.sink.payload),
),
last_stb.eq(self.sink.stb),
]
self.comb += [
self.sink.ack.eq(~self.source.stb | self.source.ack),
self.source.eop.eq(~self.sink.stb & last_stb),
]
class Streams_Crossbar(Module):
def __init__(self, downconn_sources, stream_sinks):
n_downconn = len(downconn_sources)
self.active_conn= C(n_downconn)
# TODO: change self.active_conns to signal and link it to rx_ready of GTX lanes
# # #
self.submodules.mux = mux = stream.Multiplexer(word_layout_dchar, n_downconn)
for i, downconn in enumerate(downconn_sources):
self.comb += downconn.source.connect(getattr(mux, "sink"+str(i)))
self.submodules.fsm = fsm = FSM(reset_state="WAIT_HEADER")
self.stream_id = Signal(char_width)
case = dict((i, mux.source.connect(b.sink)) for i, b in enumerate(stream_sinks))
fsm.act(
"WAIT_HEADER",
NextValue(self.stream_id, mux.source.dchar),
If(mux.source.stb,
NextState("COPY"),
),
)
fsm.act(
"COPY",
Case(self.stream_id, case),
If(mux.source.eop,
NextState("SWITCH_CONN"),
),
)
# Section 9.5.5 (CXP-001-2021)
# When Multiple connections are active, stream packets are transmitted in
# ascending order of Connection ID. And one connection shall be transmitting data at a time.
read_mask = Signal(max=n_downconn)
self.comb += mux.sel.eq(read_mask)
fsm.act(
"SWITCH_CONN",
# assuming downconn_sources have ascending Connection ID
If(read_mask == self.active_conn - 1,
NextValue(read_mask, read_mask.reset),
).Else(
NextValue(read_mask, read_mask + 1),
),
NextState("WAIT_HEADER"),
)
def reverse_bytes(s):
assert len(s) % 8 == 0
char = [s[i*8:(i+1)*8] for i in range(len(s)//8)]
return Cat(char[::-1])
@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 += [
# the CRC Engine use Big Endian, need to reverse the bytes
self.engine.data.eq(reverse_bytes(self.data)),
self.engine.last.eq(reg),
self.value.eq(reverse_bytes(reg[::-1])),
self.error.eq(reg != self.check),
]
# For verifying crc in stream data packet
class Double_Stream_Buffer(Module):
def __init__(self, size):
self.sink = stream.Endpoint(word_layout_dchar)
self.submodules.crc = crc = CXPCRC32(word_dw)
self.comb += crc.data.eq(self.sink.data)
self.submodules.fsm = fsm = FSM(reset_state="RESET")
write_mask = Signal()
self.submodules.line_buffer0 = line_buffer0 = ResetInserter()(stream.SyncFIFO(word_layout_dchar, 2**bits_for(size//word_dw)))
self.submodules.line_buffer1 = line_buffer1 = ResetInserter()(stream.SyncFIFO(word_layout_dchar, 2**bits_for(size//word_dw)))
fsm.act("RESET",
Case(write_mask,
{
0: line_buffer0.reset.eq(1),
1: line_buffer1.reset.eq(1),
}
),
crc.reset.eq(1),
NextState("CHECKING"),
)
fsm.act("CHECKING",
self.sink.ack.eq(1),
If(self.sink.stb,
crc.ce.eq(1),
If(self.sink.eop,
# discard the crc at the end
NextState("SWITCH_BUFFER")
).Else(
If(write_mask == 0,
self.sink.connect(line_buffer0.sink),
).Else(
self.sink.connect(line_buffer1.sink),
),
)
)
)
# only valid data will be passed to downstream
fsm.act("SWITCH_BUFFER",
If(~crc.error,
NextValue(write_mask, ~write_mask),
),
NextState("RESET"),
)
self.submodules.mux = mux = stream.Multiplexer(word_layout_dchar, 2)
self.comb += [
line_buffer0.source.connect(mux.sink0),
line_buffer1.source.connect(mux.sink1),
mux.sel.eq(~write_mask),
]
self.source = mux.source
# DEBUG:
self.write = Signal()
self.error = Signal()
self.comb += [
self.write.eq(write_mask),
self.error.eq(crc.error),
]
# # to add eop in the same cycle
# # the tricks relies on the fact source lags sink one cycle
# # but fsm .connect by default use combinational logic which the same cycle rising/falling edge check immpossible
# fsm.act("CHECKING",
# NextValue(self.source.payload.raw_bits(), 0),
# NextValue(self.source.stb, 0),
# If(self.sink.stb,
# crc.ce.eq(1),
# If(self.sink.eop,
# NextState("RESET")
# ).Else(
# NextValue(self.source.payload.raw_bits(), self.sink.payload.raw_bits()),
# NextValue(self.source.stb, 1),
# )
# )
# )
# last_eop = Signal()
# self.comb += self.source.eop.eq(~last_eop & self.sink.eop)
class Stream_Parser(Module):
def __init__(self):
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
# # #
self.stream_id = Signal(char_width)
self.pak_tag = Signal(char_width)
self.stream_pak_size = Signal(char_width * 2)
self.submodules.fsm = fsm = FSM(reset_state="WAIT_HEADER")
fsm.act(
"WAIT_HEADER",
NextValue(self.stream_id, self.stream_id.reset),
NextValue(self.pak_tag, self.pak_tag.reset),
NextValue(self.stream_pak_size, self.stream_pak_size.reset),
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(self.stream_id, self.sink.dchar),
NextState("GET_PAK_TAG"),
),
)
fsm.act(
"GET_PAK_TAG",
self.sink.ack.eq(1),
If(
self.sink.stb,
NextValue(self.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(self.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(self.stream_pak_size[:8], self.sink.dchar),
NextState("STORE_BUFFER"),
),
)
fsm.act(
"STORE_BUFFER",
self.sink.connect(self.source),
# both serve the same function but using the pak size I can remove eop injecter and save 1 cycle
If(self.sink.stb,
NextValue(self.stream_pak_size, self.stream_pak_size - 1),
If(self.stream_pak_size == 1,
NextState("WAIT_HEADER"),
)
),
)
class Frame_Extractor(Module):
def __init__(self, pixel_format="mono16"):
assert pixel_format in ["mono16"]
pixel_format = {
"mono16": C(0x0105, 2*char_width)
}
self.format_error = Signal()
self.decode_err = Signal()
self.new_frame = Signal()
self.new_line = Signal()
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
# # #
# TODO: decode Image header, line break
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
# DEBUG: remove this
self.fsm_state = Signal()
self.comb += self.fsm_state.eq(fsm.ongoing("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
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, reverse_bytes(packet_buffer[idx:idx+size]))
idx += size
class Pixel_Decoder(Module):
def __init__(self, pixel_format="mono16"):
assert pixel_format == "mono16"
self.sink = stream.Endpoint(word_layout_dchar)
self.source = stream.Endpoint(word_layout_dchar)
# # #
# TODO: support mono16 for now?
class Stream_Pipeline(Module):
# optimal stream packet size is 2 kBtyes - Section 9.5.2 (CXP-001-2021)
def __init__(self, size=16000):
self.submodules.double_buffer = double_buffer = Double_Stream_Buffer(size)
self.submodules.parser = parser = Stream_Parser()
self.submodules.frame_extractor = frame_extractor = Frame_Extractor()
pipeline = [double_buffer, parser, frame_extractor]
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 purposes
self.sync += self.source.ack.eq(1)

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 = RX_Bootstrap()
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")

View File

@ -1,32 +0,0 @@
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

View File

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

View File

@ -1,32 +0,0 @@
# 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]

2
src/Cargo.lock generated
View File

@ -559,9 +559,7 @@ name = "satman"
version = "0.0.0"
dependencies = [
"build_zynq",
"byteorder",
"core_io",
"crc",
"cslice",
"embedded-hal",
"io",

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

@ -0,0 +1,381 @@
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_DownConn_PHYS
from cxp_upconn import CXP_UpConn_PHYS
from cxp_pipeline import *
class CXP_PHYS(Module, AutoCSR):
def __init__(self, refclk, upconn_pads, downconn_pads, sys_clk_freq, debug_sma, pmod_pads):
assert len(upconn_pads) == len(downconn_pads)
self.submodules.upconn = CXP_UpConn_PHYS(upconn_pads, sys_clk_freq, debug_sma, pmod_pads)
self.submodules.downconn = CXP_DownConn_PHYS(refclk, downconn_pads, sys_clk_freq, debug_sma, pmod_pads)
@FullMemoryWE()
class CXP_Interface(Module, AutoCSR):
def __init__(self, upconn_phy, downconn_phy, debug_sma, pmod_pads):
self.submodules.upconn = UpConn_Interface(upconn_phy, debug_sma, pmod_pads)
self.submodules.downconn = DownConn_Interface(downconn_phy, debug_sma, pmod_pads)
def get_tx_port(self):
return self.upconn.bootstrap.mem.get_port(write_capable=True)
def get_tx_mem_size(self):
# FIXME: if tx mem size is 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
return self.upconn.bootstrap.mem.depth*self.upconn.bootstrap.mem.width // 8
# return self.downconn.bootstrap.mem.depth*self.downconn.bootstrap.mem.width // 8
def get_rx_port(self):
return self.downconn.bootstrap.mem.get_port(write_capable=False)
def get_rx_mem_size(self):
return self.downconn.bootstrap.mem.depth*self.downconn.bootstrap.mem.width // 8
def get_loopback_tx_port(self):
return self.downconn.bootstrap_loopback.mem.get_port(write_capable=True)
def get_loopback_tx_mem_size(self):
return self.downconn.bootstrap_loopback.mem.depth*self.downconn.bootstrap_loopback.mem.width // 8
class CXP_Master(CXP_Interface):
def __init__(self, upconn_phy, downconn_phy, debug_sma, pmod_pads):
CXP_Interface.__init__(self, upconn_phy, downconn_phy, debug_sma, pmod_pads)
nbit_trigdelay = 8
nbit_linktrig = 1
self.rtlink = rtlink.Interface(
rtlink.OInterface(nbit_trigdelay + nbit_linktrig),
rtlink.IInterface(word_dw, timestamped=False)
)
self.sync.rio += [
If(self.rtlink.o.stb,
self.upconn.trig.delay.eq(self.rtlink.o.data[nbit_linktrig:]),
self.upconn.trig.linktrig_mode.eq(self.rtlink.o.data[:nbit_linktrig]),
),
self.upconn.trig.stb.eq(self.rtlink.o.stb),
]
# DEBUG: out
self.specials += Instance("OBUF", i_I=self.rtlink.o.stb, o_O=debug_sma.p_tx),
# self.specials += Instance("OBUF", i_I=self.rtlink.o.stb, o_O=debug_sma.n_rx),
class CXP_Extension(CXP_Interface):
def __init__(self, upconn_phy, downconn_phy, debug_sma, pmod_pads):
CXP_Interface.__init__(self, upconn_phy, downconn_phy, debug_sma, pmod_pads)
class DownConn_Interface(Module, AutoCSR):
def __init__(self, phy, debug_sma, pmod_pads):
self.rx_start_init = CSRStorage()
self.rx_restart = CSR()
self.rx_ready = CSRStatus()
# # #
gtx = phy.gtx
# GTX Control
self.sync += [
gtx.rx_restart.eq(self.rx_restart.re),
gtx.rx_init.clk_path_ready.eq(self.rx_start_init.storage),
self.rx_ready.status.eq(gtx.rx_ready),
]
# DEBUG: tx control
self.tx_start_init = CSRStorage()
self.tx_restart = CSR()
self.txenable = CSRStorage()
self.sync += [
gtx.txenable.eq(self.txenable.storage),
gtx.tx_restart.eq(self.tx_restart.re),
gtx.tx_init.clk_path_ready.eq(self.tx_start_init.storage),
]
# DEBUG: loopback control
self.loopback_mode = CSRStorage(3)
self.comb += gtx.loopback_mode.eq(self.loopback_mode.storage)
# DEBUG: init status
self.txinit_phaligndone = CSRStatus()
self.rxinit_phaligndone = CSRStatus()
self.comb += [
self.txinit_phaligndone.status.eq(gtx.tx_init.Xxphaligndone),
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),
),
]
# DEBUG: txusrclk PLL DRP
self.txpll_reset = CSRStorage()
self.pll_daddr = CSRStorage(7)
self.pll_dclk = CSRStorage()
self.pll_den = CSRStorage()
self.pll_din = CSRStorage(16)
self.pll_dwen = CSRStorage()
self.txpll_locked = CSRStatus()
self.pll_dout = CSRStatus(16)
self.pll_dready = CSRStatus()
self.comb += [
gtx.txpll_reset.eq(self.txpll_reset.storage),
gtx.pll_daddr.eq(self.pll_daddr.storage),
gtx.pll_dclk.eq(self.pll_dclk.storage),
gtx.pll_den.eq(self.pll_den.storage),
gtx.pll_din.eq(self.pll_din.storage),
gtx.pll_dwen.eq(self.pll_dwen.storage),
self.txinit_phaligndone.status.eq(gtx.tx_init.Xxphaligndone),
self.rxinit_phaligndone.status.eq(gtx.rx_init.Xxphaligndone),
self.txpll_locked.status.eq(gtx.txpll_locked),
self.pll_dout.status.eq(gtx.pll_dout),
self.pll_dready.status.eq(gtx.pll_dready),
]
# DEBUG: Transmission Pipeline
#
# rtio pak ----+
# from gw | 32 32
# mux---/---> packet -----> trigger ack ---/---> PHY
# | wrapper inserter
# data/test ----+
# pak from fw
#
# DEBUG: TX pipeline
self.submodules.bootstrap_loopback = bootstrap_loopback = TX_Bootstrap()
self.submodules.pak_wrp = pak_wrp = Packet_Wrapper()
self.submodules.trig_ack = trig_ack = Trigger_ACK_Inserter()
self.ack = CSR()
self.sync += trig_ack.stb.eq(self.ack.re),
tx_pipeline = [bootstrap_loopback, pak_wrp, trig_ack, phy]
for s, d in zip(tx_pipeline, tx_pipeline[1:]):
self.comb += s.source.connect(d.sink)
# Receiver Pipeline WIP
#
# 32 32+8(dchar)
# PHY ---/---> dchar -----/-----> trigger ack ------> packet ------> CDC FIFO ------> debug buffer
# decoder checker decoder
#
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.bootstrap = bootstrap = cdr(RX_Bootstrap())
self.bootstrap_decoder_err = CSR()
self.bootstrap_test_err = CSR()
self.boostrap_buffer_err = CSR()
decode_err_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
test_err_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
buffer_err_ps = PulseSynchronizer("cxp_gtx_rx", "sys")
self.submodules += decode_err_ps, test_err_ps, buffer_err_ps
self.sync.cxp_gtx_rx += [
decode_err_ps.i.eq(bootstrap.decode_err),
test_err_ps.i.eq(bootstrap.test_err),
buffer_err_ps.i.eq(bootstrap.buffer_err),
]
self.sync += [
If(decode_err_ps.o,
self.bootstrap_decoder_err.w.eq(1),
).Elif(self.bootstrap_decoder_err.re,
self.bootstrap_decoder_err.w.eq(0),
),
If(test_err_ps.o,
self.bootstrap_test_err.w.eq(1),
).Elif(self.bootstrap_test_err.re,
self.bootstrap_test_err.w.eq(0),
),
If(buffer_err_ps.o,
self.boostrap_buffer_err.w.eq(1),
).Elif(self.bootstrap_test_err.re,
self.boostrap_buffer_err.w.eq(0),
),
]
# Cicular buffer interface
self.packet_type = CSRStatus(8)
self.pending_packet = CSR()
self.read_ptr = CSRStatus(log2_int(buffer_count))
self.specials += [
MultiReg(bootstrap.packet_type, self.packet_type.status),
MultiReg(self.read_ptr.status, bootstrap.read_ptr_rx, odomain="cxp_gtx_rx"),
]
self.sync += [
self.pending_packet.w.eq(self.read_ptr.status != bootstrap.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),
)
]
cdc_fifo = stream.AsyncFIFO(word_layout_dchar, 512)
self.submodules += ClockDomainsRenamer({"write": "cxp_gtx_rx", "read": "sys"})(cdc_fifo)
self.submodules.debug_out = debug_out = RX_Debug_Buffer()
rx_pipeline = [phy, dchar_decoder, trig_ack_checker, bootstrap, cdc_fifo, debug_out]
for s, d in zip(rx_pipeline, rx_pipeline[1:]):
self.comb += s.source.connect(d.sink)
# DEBUG: CSR
self.trigger_ack = CSR()
self.sync += [
self.trig_clr.eq(self.trigger_ack.re),
self.trigger_ack.w.eq(self.trig_ack),
]
pak_start = Signal()
self.sync += [
pak_start.eq(bootstrap.sink.data == 0xFBFBFBFB),
]
self.specials += [
# Instance("OBUF", i_I=phy.gtx.cd_cxp_gtx_rx.clk, o_O=debug_sma.p_tx),
# Instance("OBUF", i_I=, o_O=debug_sma.p_rx),
# # pmod 0-7 pin
# Instance("OBUF", i_I=bootstrap.test_err, o_O=pmod_pads[0]),
# Instance("OBUF", i_I=pak_start, o_O=pmod_pads[1]),
# Instance("OBUF", i_I=fifo_in.source.ack, o_O=pmod_pads[2]),
# Instance("OBUF", i_I=gtx.comma_checker.aligner_en, o_O=pmod_pads[3]),
# Instance("OBUF", i_I=gtx.comma_checker.check_reset, o_O=pmod_pads[4]),
# Instance("OBUF", i_I=gtx.comma_checker.has_comma, o_O=pmod_pads[5]),
# Instance("OBUF", i_I=gtx.comma_checker.has_error, o_O=pmod_pads[6]),
# Instance("OBUF", i_I=gtx.comma_checker.ready_sys, o_O=pmod_pads[7]),
]
class UpConn_Interface(Module, AutoCSR):
def __init__(self, phy, debug_sma, pmod_pads):
# 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 = TX_Trigger()
# # DEBUG: INPUT
self.trig_stb = CSR()
self.trig_delay = CSRStorage(8)
self.linktrigger = CSRStorage()
# self.sync += [
# trig.stb.eq(self.trig_stb.re),
# trig.delay.eq(self.trig_delay.storage),
# trig.linktrig_mode.eq(self.linktrigger.storage),
# ]
# 1: IO acknowledgment for trigger packet
self.submodules.trig_ack = trig_ack = Trigger_ACK_Inserter()
# DEBUG: INPUT
self.ack = CSR()
self.sync += trig_ack.stb.eq(self.ack.re),
# 2: All other packets (data & test packet)
# Control is not timing dependent, all the data packets are handled in firmware
self.submodules.bootstrap = bootstrap = TX_Bootstrap()
self.submodules.pak_wrp = pak_wrp = Packet_Wrapper()
self.submodules.idle = idle = Idle_Word_Inserter()
# Section 9.2.5.1 (CXP-001-2021)
# IDLE should be transmitter every 10000 words
cnt = Signal(max=10000)
self.sync += [
idle.stb.eq(0),
If((~idle.sink.stb) | (cnt == 9999),
idle.stb.eq(1),
cnt.eq(cnt.reset),
).Else(
cnt.eq(cnt + 1),
),
]
self.submodules.converter = converter = stream.StrideConverter(word_layout, char_layout)
tx_pipeline = [bootstrap, 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)

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 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,742 @@
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_DownConn_PHYS(Module, AutoCSR):
def __init__(self, refclk, pads, sys_clk_freq, debug_sma, pmod_pads):
self.qpll_reset = CSR()
self.qpll_locked = CSRStatus()
self.rx_phys = []
# # #
# For speed higher than 6.6Gbps, QPLL need to be used instead of CPLL
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),
]
for i, pad in enumerate(pads):
rx = Receiver(qpll, pad, sys_clk_freq, "single", "single", debug_sma, pmod_pads)
self.rx_phys.append(rx)
setattr(self.submodules, "rx"+str(i), rx)
# TODO: add extension gtx connections
# TODO: add connection interface
# TODO: Connect slave cxp_gtx_rx clock tgt
# checkout channel interfaces & drtio_gtx
# GTPTXPhaseAlignement for inspiration
# Connect slave i's `cxp_gtx_rx` clock to `cxp_gtx_rxi` clock
for rx in self.rx_phys:
name = "cd_cxp_gtx_rx" + str(i)
setattr(self.clock_domains, name, ClockDomain(name=name))
self.comb += [
getattr(self, name).clk.eq(rx.gtx.cd_cxp_gtx_rx.clk),
getattr(self, name).rst.eq(rx.gtx.cd_cxp_gtx_rx.rst)
]
class Receiver(Module):
def __init__(self, qpll, pad, sys_clk_freq, tx_mode, rx_mode, debug_sma, pmod_pads):
self.submodules.gtx = gtx = GTX(qpll, pad, sys_clk_freq, tx_mode="single", rx_mode="single")
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 & self.source.ack & ~((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))),
)
]
# DEBUG: tx fifos for loopback
# fw -> -> cdc fifo -> buffered fifo -> gtx tx
cdc_fifo = stream.AsyncFIFO(word_layout, 512)
self.submodules += ClockDomainsRenamer({"write": "sys", "read": "cxp_gtx_tx"})(cdc_fifo)
self.sink = cdc_fifo.sink
# fix timing violation
cdr = ClockDomainsRenamer("cxp_gtx_tx")
self.submodules.buf = tx_fifo = cdr(stream.SyncFIFO(word_layout, 2, buffered=True))
self.comb += [
cdc_fifo.source.connect(tx_fifo.sink),
]
idle_period = 50 # express in word
word_count = Signal(max=idle_period)
# JANK: fix the every 98th word got eaten
# cnt 97 98 99 0
# out fifo[97] IDLE IDLE fifo[99]
# ack 1 0 0 1
self.sync.cxp_gtx_tx += [
tx_fifo.source.ack.eq(0),
If(word_count == idle_period-1,
word_count.eq(word_count.reset),
).Else(
If(tx_fifo.source.stb,
If(word_count != idle_period-2, tx_fifo.source.ack.eq(1)),
word_count.eq(word_count + 1),
)
)
]
# NOTE: prevent the first word send twice due to stream stb delay
self.comb += [
If((tx_fifo.source.stb & tx_fifo.source.ack & (word_count != idle_period-1)),
gtx.encoder.d[0].eq(tx_fifo.source.data[:8]),
gtx.encoder.d[1].eq(tx_fifo.source.data[8:16]),
gtx.encoder.d[2].eq(tx_fifo.source.data[16:24]),
gtx.encoder.d[3].eq(tx_fifo.source.data[24:]),
gtx.encoder.k[0].eq(tx_fifo.source.k[0]),
gtx.encoder.k[1].eq(tx_fifo.source.k[1]),
gtx.encoder.k[2].eq(tx_fifo.source.k[2]),
gtx.encoder.k[3].eq(tx_fifo.source.k[3]),
).Else(
# NOTE: IDLE WORD
gtx.encoder.d[0].eq(0xBC),
gtx.encoder.k[0].eq(1),
gtx.encoder.d[1].eq(0x3C),
gtx.encoder.k[1].eq(1),
gtx.encoder.d[2].eq(0x3C),
gtx.encoder.k[2].eq(1),
gtx.encoder.d[3].eq(0xB5),
gtx.encoder.k[3].eq(0),
)
]
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
# DEBUG: txuserclk
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
# DEBUG: remove after use
self.has_comma = Signal()
self.has_error = Signal()
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),
),
# DEBUG:
self.has_comma.eq(0),
If((self.data[:10] == comma) | (self.data[:10] == comma_n),
self.has_comma.eq(1),
),
self.has_error.eq(0),
If((one_counts != 4) & (one_counts != 5) & (one_counts != 6),
self.has_error.eq(1),
),
]
# DEBUG: expose signal
self.check_reset = Signal()
self.comb +=[
self.check_reset.eq(check_reset),
]
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):
# Settings:
# * GTX reference clock @ 125MHz
# * GTX data width = 20
# * GTX PLL frequency @ 3.125GHz
# * GTX line rate (TX & RX) @ 3.125Gb/s
# * GTX TX/RX USRCLK @ PLL/datawidth = 156MHz
def __init__(self, qpll, pads, sys_clk_freq, tx_mode="single", rx_mode="single"):
assert tx_mode in ["single", "master", "slave"]
assert rx_mode in ["single", "master", "slave"]
# linerate = USRCLK * datawidth
pll_fbout_mult = 8
txusr_pll_div = pll_fbout_mult*sys_clk_freq/qpll.tx_usrclk_freq
self.tx_restart = Signal()
self.rx_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()
self.submodules.encoder = ClockDomainsRenamer("cxp_gtx_tx")(Encoder(4, True))
self.submodules.decoders = [ClockDomainsRenamer("cxp_gtx_rx")(
(Decoder(True))) for _ in range(4)]
# transceiver direct clock outputs
# useful to specify clock constraints in a way palatable to Vivado
self.txoutclk = Signal()
self.rxoutclk = Signal()
# # #
# TX generates cxp_tx clock, init must be in system domain
# FIXME: 500e6 is used to fix Xx reset by holding gtxXxreset for a couple cycle more
self.submodules.tx_init = tx_init = GTXInit(500e6, False, mode=tx_mode)
self.submodules.rx_init = rx_init = GTXInit(sys_clk_freq, True, mode=rx_mode)
# RX receives restart commands from txusrclk domain
# self.submodules.rx_init = rx_init = ClockDomainsRenamer("cxp_gtx_tx")(GTXInit(500e6, True, mode=rx_mode))
self.comb += [
tx_init.cplllock.eq(qpll.lock),
rx_init.cplllock.eq(qpll.lock)
]
txdata = Signal(40)
rxdata = Signal(40)
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 != "single" else 0,
i_GTTXRESET=tx_init.gtXxreset,
o_TXRESETDONE=tx_init.Xxresetdone,
i_TXDLYSRESET=tx_init.Xxdlysreset,
o_TXDLYSRESETDONE=tx_init.Xxdlysresetdone,
i_TXPHINIT=tx_init.txphinit if tx_mode != "single" else 0,
o_TXPHINITDONE=tx_init.txphinitdone if tx_mode != "single" else Signal(),
i_TXPHALIGN=tx_init.Xxphalign if tx_mode != "single" else 0,
i_TXDLYEN=tx_init.Xxdlyen if tx_mode != "single" else 0,
o_TXPHALIGNDONE=tx_init.Xxphaligndone,
i_TXUSERRDY=tx_init.Xxuserrdy,
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"),
i_TXUSRCLK2=ClockSignal("cxp_gtx_tx"),
# TX electrical
i_TXBUFDIFFCTRL=0b100,
i_TXDIFFCTRL=0b1000,
# RX Startup/Reset
i_RXPHDLYRESET=0,
i_RXDLYBYPASS=0,
i_RXPHALIGNEN=1 if rx_mode != "single" else 0,
i_GTRXRESET=rx_init.gtXxreset,
o_RXRESETDONE=rx_init.Xxresetdone,
i_RXDLYSRESET=rx_init.Xxdlysreset,
o_RXDLYSRESETDONE=rx_init.Xxdlysresetdone,
i_RXPHALIGN=rx_init.Xxphalign if rx_mode != "single" else 0,
i_RXDLYEN=rx_init.Xxdlyen if rx_mode != "single" else 0,
o_RXPHALIGNDONE=rx_init.Xxphaligndone,
i_RXUSERRDY=rx_init.Xxuserrdy,
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"),
i_RXUSRCLK2=ClockSignal("cxp_gtx_rx"),
# 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,
# ! loopback for debugging
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
# A PLL is used to generate the correct frequency for TXUSRCLK (UG476 Equation 3-1)
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()
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
# the CDR matches the required frequency for RXUSRCLK, no need for PLL
# Slave Rx will use cxp_gtx_rx instead
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)
]
self.submodules.rx_resetter = rx_resetter = RX_Resetter()
self.comb += [
rx_resetter.rx_ready.eq(self.rx_ready),
rx_init.restart.eq(self.rx_restart | rx_resetter.rx_reset),
]
else:
self.comb += rx_init.restart.eq(self.rx_restart),
# 8b10b Encoder/Decoder
self.comb += [
txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1], self.encoder.output[2], self.encoder.output[3])),
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:]),
]
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,526 @@
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_dw = 32
word_layout = [("data", word_dw), ("k", word_dw//8)]
word_layout_dchar = [
("data", word_dw),
("k", word_dw//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 TX_Trigger(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):
self.stb = Signal()
# # #
# 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="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_IDLE"))
)
fsm.act("WRITE_IDLE",
self.sink.ack.eq(0),
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)),
If(self.source.ack, NextState("COPY")),
)
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 TX_Bootstrap(Module, AutoCSR):
def __init__(self):
self.tx_word_len = CSRStorage(log2_int(buffer_depth))
self.tx = CSR()
self.tx_testseq = CSR()
self.tx_busy = CSRStatus()
# # #
self.specials.mem = mem = Memory(word_dw, 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.source.data.eq(mem_port.dat_r)
]
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
self.sync += self.tx_busy.status.eq(~fsm.ongoing("IDLE"))
cnt = Signal(max=0xFFF)
fsm.act("IDLE",
addr_rst.eq(1),
If(self.tx.re, NextState("TRANSMIT")),
If(self.tx_testseq.re,
NextValue(cnt, cnt.reset),
NextState("WRITE_TEST_PACKET_TYPE"),
)
)
fsm.act("TRANSMIT",
self.source.stb.eq(1),
If(self.source.ack,
addr_inc.eq(1),
),
If(addr_next == self.tx_word_len.storage,
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.eq(Cat(cnt[:8], cnt[:8]+1, cnt[:8]+2, cnt[:8]+3)),
self.source.k.eq(Cat(0, 0, 0, 0)),
If(self.source.ack,
If(cnt == 0xFFF-3,
self.source.eop.eq(1),
NextState("IDLE")
).Else(
NextValue(cnt, cnt + 4),
)
)
)
class RX_Debug_Buffer(Module,AutoCSR):
def __init__(self):
self.submodules.buf_out = buf_out = stream.SyncFIFO(word_layout_dchar, 128)
self.sink = buf_out.sink
self.inc = CSR()
self.dout_pak = CSRStatus(word_dw)
self.kout_pak = CSRStatus(word_dw//8)
self.dout_valid = CSRStatus()
self.sync += [
# output
buf_out.source.ack.eq(self.inc.re),
self.dout_pak.status.eq(buf_out.source.data),
self.kout_pak.status.eq(buf_out.source.k),
self.dout_valid.status.eq(buf_out.source.stb),
]
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 without implementing the voting logic inside the decoder
# cycle 1 - buffer data & calculate intermediate result
buffer = stream.Endpoint(word_layout)
self.sync += [
If((~buffer.stb | buffer.ack),
buffer.stb.eq(self.sink.stb),
buffer.payload.eq(self.sink.payload),
)
]
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),
self.source.stb.eq(buffer.stb),
self.source.data.eq(buffer.data),
self.source.k.eq(buffer.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 RX_Bootstrap(Module):
def __init__(self):
self.packet_type = Signal(8)
self.decode_err = Signal()
self.test_err = Signal()
self.buffer_err = Signal()
# # #
# TODO: heartbeat
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)
fsm.act("DECODE",
self.sink.ack.eq(1),
If(self.sink.stb,
NextValue(self.packet_type, self.sink.dchar),
Case(self.sink.dchar, {
type["data_stream"]: NextState("STREAMING"),
type["test_packet"]: [
NextValue(cnt, cnt.reset),
NextState("VERIFY_TEST_PATTERN"),
],
type["control_ack_no_tag"]:[
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
type["control_ack_with_tag"]:[
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
type["event"]: [
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
],
"default": [
self.decode_err.eq(1),
# wait till next valid packet
NextState("IDLE"),
],
}),
)
)
# For stream data packet
fsm.act("STREAMING",
If((self.sink.stb & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
# discard K29,7
self.sink.ack.eq(1),
NextState("IDLE")
).Else(
self.sink.connect(self.source),
)
)
# 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
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 != Cat(cnt, cnt+1, cnt+2, cnt+3))),
self.test_err.eq(1),
),
If(cnt == 0xFC,
NextValue(cnt, cnt.reset),
).Else(
NextValue(cnt, cnt + 4)
)
)
)
)
# A circular buffer for firmware to read packet from
self.specials.mem = mem = Memory(word_dw, buffer_count*buffer_depth)
self.specials.mem_port = mem_port = mem.get_port(write_capable=True)
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 += [
mem_port.adr[:addr_nbits].eq(addr),
mem_port.adr[addr_nbits:].eq(write_ptr),
]
# For control ack, event packet
fsm.act("LOAD_BUFFER",
mem_port.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(
mem_port.we.eq(1),
mem_port.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, AutoCSR):
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),
)
)
)

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

@ -0,0 +1,224 @@
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 UpConn_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)
# # #
o = Signal()
tx_bitcount = Signal(max=10)
tx_reg = Signal(10)
# DEBUG:
self.o = Signal()
self.comb += self.o.eq(o)
self.specials += Instance("OBUF", i_I=o, o_O=pad),
self.sync += [
If(self.oe,
# send LSB first
o.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(
o.eq(0),
tx_bitcount.eq(0),
)
]
class Debug_buffer(Module,AutoCSR):
def __init__(self, layout):
self.sink_stb = Signal()
self.sink_ack = Signal()
self.sink_data = Signal(8)
self.sink_k = Signal()
# # #
self.submodules.buf_out = buf_out = stream.SyncFIFO(layout, 512)
self.sync += [
buf_out.sink.stb.eq(self.sink_stb),
self.sink_ack.eq(buf_out.sink.ack),
buf_out.sink.data.eq(self.sink_data),
buf_out.sink.k.eq(self.sink_k),
]
self.inc = CSR()
self.dout_pak = CSRStatus(8)
self.kout_pak = CSRStatus()
self.dout_valid = CSRStatus()
self.sync += [
# output
buf_out.source.ack.eq(self.inc.re),
self.dout_pak.status.eq(buf_out.source.data),
self.kout_pak.status.eq(buf_out.source.k),
self.dout_valid.status.eq(buf_out.source.stb),
]
class Transmitter(Module, AutoCSR):
def __init__(self, pad, sys_clk_freq, debug_sma, pmod_pads):
self.bitrate2x_enable = Signal()
self.clk_reset = Signal()
self.tx_enable = Signal()
# # #
self.sink = stream.Endpoint(char_layout)
self.submodules.cg = cg = UpConn_ClockGen(sys_clk_freq)
self.submodules.encoder = encoder = SingleEncoder(True)
self.submodules.debug_buf = debug_buf = Debug_buffer(char_layout)
oe = Signal()
self.sync += [
If(self.tx_enable,
self.sink.ack.eq(0),
# DEBUG:
debug_buf.sink_stb.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),
# DEBUG:
If(debug_buf.sink_ack,
debug_buf.sink_stb.eq(1),
debug_buf.sink_data.eq(self.sink.data),
debug_buf.sink_k.eq(self.sink.k),
)
)
).Else(
# DEBUG:
debug_buf.sink_stb.eq(0),
# no backpressure
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),
]
# DEBUG: remove pads
self.specials += [
# # debug sma
# Instance("OBUF", i_I=serdes.o, o_O=debug_sma.p_tx),
Instance("OBUF", i_I=serdes.o, o_O=debug_sma.n_rx),
# # pmod 0-7 pin
# Instance("OBUF", i_I=serdes.o, o_O=pmod_pads[0]),
# Instance("OBUF", i_I=cg.clk_10x, o_O=pmod_pads[1]),
# Instance("OBUF", i_I=~tx_fifos.pe.n, o_O=pmod_pads[2]),
# Instance("OBUF", i_I=prioity_0, o_O=pmod_pads[3]),
# Instance("OBUF", i_I=word_bound, o_O=pmod_pads[4]),
# Instance("OBUF", i_I=debug_buf.buf_out.sink.stb, o_O=pmod_pads[4]),
# Instance("OBUF", i_I=debug_buf.buf_out.sink.ack, o_O=pmod_pads[5]),
# Instance("OBUF", i_I=debug_buf.buf_out.source.stb, o_O=pmod_pads[6]),
# Instance("OBUF", i_I=debug_buf.buf_out.source.ack, o_O=pmod_pads[7]),
# Instance("OBUF", i_I=scheduler.idling, o_O=pmod_pads[5]),
# # Instance("OBUF", i_I=tx_fifos.source_ack[0], o_O=pmod[6]),
# # Instance("OBUF", i_I=tx_fifos.source_ack[2], o_O=pmod[6]),
# # Instance("OBUF", i_I=tx_fifos.source_ack[1], o_O=pmod[7]),
# Instance("OBUF", i_I=p0, o_O=pmod_pads[6]),
# Instance("OBUF", i_I=p3, o_O=pmod_pads[7]),
]
class CXP_UpConn_PHYS(Module, AutoCSR):
def __init__(self, pads, sys_clk_freq, debug_sma, pmod_pads):
self.clk_reset = CSR()
self.bitrate2x_enable = CSRStorage()
self.tx_enable = CSRStorage()
# # #
self.tx_phys = []
for i, pad in enumerate(pads):
tx = Transmitter(pad, sys_clk_freq, debug_sma, pmod_pads)
self.tx_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.tx_enable.eq(self.tx_enable.storage),
]

View File

@ -1,307 +0,0 @@
#!/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

@ -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
@ -652,6 +654,129 @@ 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 = 1
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,
debug_sma=debug_sma_pad,
pmod_pads = pmod_pads
)
self.csr_devices.append("cxp_phys")
rtio_channels = []
cxp_csr_group = []
cxp_tx_mem_group = []
cxp_rx_mem_group = []
cxp_loopback_mem_group = []
for i, (tx, rx) in enumerate(zip(cxp_phys.upconn.tx_phys, cxp_phys.downconn.rx_phys)):
cxp_name = "cxp" + str(i)
cdr = ClockDomainsRenamer({"cxp_gtx_rx": "cxp_gtx_rx" + str(i)})
if i == 0:
cxp_interface = cdr(cxp.CXP_Master(tx, rx, debug_sma_pad, pmod_pads))
# Add rtlink for Master Connection only
print("CoaXPress at RTIO channel 0x{:06x}".format(len(rtio_channels)))
rtio_channels.append(rtio.Channel.from_phy(cxp_interface))
else:
cxp_interface = cdr(cxp.CXP_Extension(tx, rx, debug_sma_pad, pmod_pads))
setattr(self.submodules, cxp_name, cxp_interface)
self.csr_devices.append(cxp_name)
cxp_csr_group.append(cxp_name)
# Add memory group
rx_mem_name = "cxp_rx" + str(i) + "_mem"
rx_mem_size = cxp_interface.get_rx_mem_size()
cxp_rx_mem_group.append(rx_mem_name)
memory_address = self.axi2csr.register_port(cxp_interface.get_rx_port(), rx_mem_size)
self.add_memory_region(rx_mem_name, self.mem_map["csr"] + memory_address, rx_mem_size)
tx_mem_name = "cxp_tx" + str(i) + "_mem"
tx_mem_size = cxp_interface.get_tx_mem_size()
cxp_tx_mem_group.append(tx_mem_name)
memory_address = self.axi2csr.register_port(cxp_interface.get_tx_port(), tx_mem_size)
self.add_memory_region(tx_mem_name, self.mem_map["csr"] + memory_address, tx_mem_size)
# DEBUG loopback tx memory
loopback_mem_name = "cxp_loopback_tx" + str(i) + "_mem"
loopback_mem_size = cxp_interface.get_loopback_tx_mem_size()
cxp_loopback_mem_group.append(loopback_mem_name)
memory_address = self.axi2csr.register_port(cxp_interface.get_loopback_tx_port(), loopback_mem_size)
self.add_memory_region(loopback_mem_name, self.mem_map["csr"] + memory_address, loopback_mem_size)
self.add_memory_group("cxp_tx_mem", cxp_tx_mem_group)
self.add_memory_group("cxp_rx_mem", cxp_rx_mem_group)
self.add_memory_group("cxp_loopback_mem", cxp_loopback_mem_group)
self.add_csr_group("cxp", cxp_csr_group)
# 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
for rx in cxp_phys.downconn.rx_phys :
platform.add_period_constraint(rx.gtx.cd_cxp_gtx_tx.clk, 3.2)
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_tx.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)
@ -684,8 +809,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,7 +10,6 @@ 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]
@ -24,6 +23,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,94 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::{println, timer::GlobalTimer};
use log::info;
use crate::{cxp_phys, cxp_proto, 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].downconn_txinit_phaligndone_read)(),
(CXP[channel].downconn_rxinit_phaligndone_read)(),
);
// enable txdata tranmission thought MGTXTXP, required by PMA loopback
(CXP[channel].downconn_txenable_write)(1);
info!("waiting for rx to align...");
while (CXP[channel].downconn_rx_ready_read)() != 1 {}
info!("rx ready!");
cxp_proto::downconn_send_test_packet(channel);
// FIXME: why test + trig ack doesn't work well for rx??
cxp_proto::downconn_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::downconn_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].downconn_trigger_ack_read)() == 1 {
(CXP[channel].downconn_trigger_ack_write)(1);
info!("trig ack and cleared");
}
if (CXP[channel].downconn_bootstrap_decoder_err_read)() == 1 {
info!("!!!!!!!DECODER ERROR!!!!!!! and cleared");
(CXP[channel].downconn_bootstrap_decoder_err_write)(1);
}
if (CXP[channel].downconn_bootstrap_test_err_read)() == 1 {
info!("!!!!!!!TEST ERROR!!!!!!! and cleared");
(CXP[channel].downconn_bootstrap_test_err_write)(1);
}
info!("packet type = {:#06X}", (CXP[channel].downconn_packet_type_read)());
cxp_proto::receive(channel).expect("loopback gtx rx error");
// cxp_proto::downconn_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].downconn_debug_out_dout_valid_read)() == 1 {
pak_arr[i] = (CXP[channel].downconn_debug_out_dout_pak_read)();
k_arr[i] = (CXP[channel].downconn_debug_out_kout_pak_read)();
// println!("received {:#04X}", pak_arr[i]);
(CXP[channel].downconn_debug_out_inc_write)(1);
i += 1;
if i == LEN {
break;
}
}
info!("rx ready = {}", (CXP[channel].downconn_rx_ready_read)());
// cxp_proto::print_packetu32(&pak_arr, &k_arr);
}
}

View File

@ -0,0 +1,491 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::{println, timer::GlobalTimer};
use log::info;
use crate::pl::{csr, csr::CXP};
const CHANNEL_LEN: usize = csr::CXP_LEN;
#[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) {
down_conn::setup(timer);
up_conn::setup();
change_linerate(CXP_SPEED::CXP_1);
}
pub fn change_linerate(speed: CXP_SPEED) {
info!("Changing all channels datarate to {:?}", speed);
down_conn::change_linerate(speed);
up_conn::change_linerate(speed);
}
mod up_conn {
use super::*;
pub fn setup() {
unsafe {
csr::cxp_phys::upconn_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::upconn_bitrate2x_enable_write(0);
}
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => {
csr::cxp_phys::upconn_bitrate2x_enable_write(1);
}
};
csr::cxp_phys::upconn_clk_reset_write(1);
}
}
}
mod down_conn {
use super::*;
pub fn setup(timer: &mut GlobalTimer) {
unsafe {
info!("turning on pmc loopback mode...");
for channel in 0..CHANNEL_LEN {
(CXP[channel].downconn_loopback_mode_write)(0b010); // Near-End PMA Loopback
}
// QPLL setup
csr::cxp_phys::downconn_qpll_reset_write(1);
info!("waiting for QPLL/CPLL to lock...");
while csr::cxp_phys::downconn_qpll_locked_read() != 1 {}
info!("QPLL locked");
for channel in 0..CHANNEL_LEN {
// tx/rx setup
(CXP[channel].downconn_tx_start_init_write)(1);
(CXP[channel].downconn_rx_start_init_write)(1);
}
// DEBUG: printout
info!("waiting for tx & rx setup...");
timer.delay_us(50_000);
for channel in 0..CHANNEL_LEN {
info!(
"tx_phaligndone = {} | rx_phaligndone = {}",
(CXP[channel].downconn_txinit_phaligndone_read)(),
(CXP[channel].downconn_rxinit_phaligndone_read)(),
);
}
}
}
pub fn change_linerate(speed: CXP_SPEED) {
// DEBUG: DRP pll for TXUSRCLK = freq(linerate)/20
let settings = txusrclk::get_txusrclk_config(speed);
txusrclk::setup(settings);
change_qpll_fb_divider(speed);
change_gtx_divider(speed);
change_cdr_cfg(speed);
unsafe {
csr::cxp_phys::downconn_qpll_reset_write(1);
info!("waiting for QPLL/CPLL to lock...");
while csr::cxp_phys::downconn_qpll_locked_read() != 1 {}
info!("QPLL locked");
for channel in 0..CHANNEL_LEN {
(CXP[channel].downconn_tx_restart_write)(1);
(CXP[channel].downconn_rx_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
CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 | CXP_SPEED::CXP_12 => 0x0170, // FB_Divider = 100
};
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
};
for channel in 0..CHANNEL_LEN {
println!("channel {}, 0x88 = {:#06x}", channel, gtx_read(channel, 0x88));
gtx_write(channel, 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..CHANNEL_LEN {
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: usize, address: u16) -> u16 {
unsafe {
(CXP[channel].downconn_gtx_daddr_write)(address);
(CXP[channel].downconn_gtx_dread_write)(1);
while (CXP[channel].downconn_gtx_dready_read)() != 1 {}
(CXP[channel].downconn_gtx_dout_read)()
}
}
fn gtx_write(channel: usize, address: u16, value: u16) {
unsafe {
(CXP[channel].downconn_gtx_daddr_write)(address);
(CXP[channel].downconn_gtx_din_write)(value);
(CXP[channel].downconn_gtx_din_stb_write)(1);
while (CXP[channel].downconn_gtx_dready_read)() != 1 {}
}
}
#[allow(dead_code)]
fn qpll_read(address: u8) -> u16 {
unsafe {
csr::cxp_phys::downconn_qpll_daddr_write(address);
csr::cxp_phys::downconn_qpll_dread_write(1);
while csr::cxp_phys::downconn_qpll_dready_read() != 1 {}
csr::cxp_phys::downconn_qpll_dout_read()
}
}
fn qpll_write(address: u8, value: u16) {
unsafe {
csr::cxp_phys::downconn_qpll_daddr_write(address);
csr::cxp_phys::downconn_qpll_din_write(value);
csr::cxp_phys::downconn_qpll_din_stb_write(1);
while csr::cxp_phys::downconn_qpll_dready_read() != 1 {}
}
}
// DEBUG: remove this
pub mod txusrclk {
use super::*;
#[derive(Copy, Clone)]
pub struct PLLSetting {
pub clkout0_reg1: u16, //0x08
pub clkout0_reg2: u16, //0x09
pub clkfbout_reg1: u16, //0x14
pub clkfbout_reg2: u16, //0x15
pub div_reg: u16, //0x16
pub lock_reg1: u16, //0x18
pub lock_reg2: u16, //0x19
pub lock_reg3: u16, //0x1A
pub power_reg: u16, //0x28
pub filt_reg1: u16, //0x4E
pub filt_reg2: u16, //0x4F
}
fn one_clock_cycle(channel: usize) {
unsafe {
(CXP[channel].downconn_pll_dclk_write)(1);
(CXP[channel].downconn_pll_dclk_write)(0);
}
}
fn set_addr(channel: usize, address: u8) {
unsafe {
(CXP[channel].downconn_pll_daddr_write)(address);
}
}
fn set_data(channel: usize, value: u16) {
unsafe {
(CXP[channel].downconn_pll_din_write)(value);
}
}
fn set_enable(channel: usize, en: bool) {
unsafe {
let val = if en { 1 } else { 0 };
(CXP[channel].downconn_pll_den_write)(val);
}
}
fn set_write_enable(channel: usize, en: bool) {
unsafe {
let val = if en { 1 } else { 0 };
(CXP[channel].downconn_pll_dwen_write)(val);
}
}
fn get_data(channel: usize) -> u16 {
unsafe { (CXP[channel].downconn_pll_dout_read)() }
}
fn drp_ready(channel: usize) -> bool {
unsafe { (CXP[channel].downconn_pll_dready_read)() == 1 }
}
#[allow(dead_code)]
fn read(channel: usize, address: u8) -> u16 {
set_addr(channel, address);
set_enable(channel, true);
// Set DADDR on the mmcm and assert DEN for one clock cycle
one_clock_cycle(channel);
set_enable(channel, false);
while !drp_ready(channel) {
// keep the clock signal until data is ready
one_clock_cycle(channel);
}
get_data(channel)
}
fn write(channel: usize, address: u8, value: u16) {
set_addr(channel, address);
set_data(channel, value);
set_write_enable(channel, true);
set_enable(channel, true);
// Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle
one_clock_cycle(channel);
set_write_enable(channel, false);
set_enable(channel, false);
while !drp_ready(channel) {
// keep the clock signal until write is finished
one_clock_cycle(channel);
}
}
fn reset(channel: usize, rst: bool) {
unsafe {
let val = if rst { 1 } else { 0 };
(CXP[channel].downconn_txpll_reset_write)(val)
}
}
pub fn setup(settings: PLLSetting) {
for channel in 0..CHANNEL_LEN {
if false {
info!("0x08 = {:#06x}", read(channel, 0x08));
info!("0x09 = {:#06x}", read(channel, 0x09));
info!("0x14 = {:#06x}", read(channel, 0x14));
info!("0x15 = {:#06x}", read(channel, 0x15));
info!("0x16 = {:#06x}", read(channel, 0x16));
info!("0x18 = {:#06x}", read(channel, 0x18));
info!("0x19 = {:#06x}", read(channel, 0x19));
info!("0x1A = {:#06x}", read(channel, 0x1A));
info!("0x28 = {:#06x}", read(channel, 0x28));
info!("0x4E = {:#06x}", read(channel, 0x4E));
info!("0x4F = {:#06x}", read(channel, 0x4F));
} else {
// Based on "DRP State Machine" from XAPP888
// hold reset HIGH during pll config
reset(channel, true);
write(channel, 0x08, settings.clkout0_reg1);
write(channel, 0x09, settings.clkout0_reg2);
write(channel, 0x14, settings.clkfbout_reg1);
write(channel, 0x15, settings.clkfbout_reg2);
write(channel, 0x16, settings.div_reg);
write(channel, 0x18, settings.lock_reg1);
write(channel, 0x19, settings.lock_reg2);
write(channel, 0x1A, settings.lock_reg3);
write(channel, 0x28, settings.power_reg);
write(channel, 0x4E, settings.filt_reg1);
write(channel, 0x4F, settings.filt_reg2);
reset(channel, false);
info!("waiting for PLL of txusrclk to lock...");
while unsafe { (CXP[channel].downconn_txpll_locked_read)() == 0 } {}
info!("txusrclk locked :D");
}
}
}
pub fn get_txusrclk_config(speed: CXP_SPEED) -> PLLSetting {
match speed {
CXP_SPEED::CXP_1 => {
// CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 32
// TXUSRCLK=62.5MHz
PLLSetting {
clkout0_reg1: 0x1410, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1104, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x5801, //0x19
lock_reg3: 0xdbe9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9808, //0x4E
filt_reg2: 0x9100, //0x4F
}
}
CXP_SPEED::CXP_2 => {
// CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 16
// TXUSRCLK=62.5MHz
PLLSetting {
clkout0_reg1: 0x1208, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1104, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x5801, //0x19
lock_reg3: 0xdbe9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9808, //0x4E
filt_reg2: 0x9100, //0x4F
}
}
CXP_SPEED::CXP_3 => {
// CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 16
// TXUSRCLK=78.125MHz
PLLSetting {
clkout0_reg1: 0x1208, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1145, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x7001, //0x19
lock_reg3: 0xf3e9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9908, //0x4E
filt_reg2: 0x1900, //0x4F
}
}
CXP_SPEED::CXP_5 => {
// CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8
// TXUSRCLK=125MHz
PLLSetting {
clkout0_reg1: 0x1104, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1104, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x5801, //0x19
lock_reg3: 0xdbe9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9808, //0x4E
filt_reg2: 0x9100, //0x4F
}
}
CXP_SPEED::CXP_6 => {
// CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8
// TXUSRCLK=156.25MHz
PLLSetting {
clkout0_reg1: 0x1104, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1145, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x7001, //0x19
lock_reg3: 0xf3e9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9908, //0x4E
filt_reg2: 0x1900, //0x4F
}
}
CXP_SPEED::CXP_10 => {
// CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4
// TXUSRCLK=250MHz
PLLSetting {
clkout0_reg1: 0x1082, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1104, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x5801, //0x19
lock_reg3: 0xdbe9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9808, //0x4E
filt_reg2: 0x9100, //0x4F
}
}
CXP_SPEED::CXP_12 => {
// CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4
// TXUSRCLK=312.5MHz
PLLSetting {
clkout0_reg1: 0x1082, //0x08
clkout0_reg2: 0x0000, //0x09
clkfbout_reg1: 0x1145, //0x14
clkfbout_reg2: 0x0000, //0x15
div_reg: 0x1041, //0x16
lock_reg1: 0x03e8, //0x18
lock_reg2: 0x7001, //0x19
lock_reg3: 0xf3e9, //0x1A
power_reg: 0x0000, //0x28
filt_reg1: 0x9908, //0x4E
filt_reg2: 0x1900, //0x4F
}
}
}
}
}
}

View File

@ -0,0 +1,608 @@
use core::slice;
use byteorder::{ByteOrder, NetworkEndian};
use core_io::{Error as IoError, Read, Write};
use crc::crc32::checksum_ieee;
use io::Cursor;
use libboard_zynq::println;
use crate::{mem::mem::{CXP_LOOPBACK_MEM, CXP_RX_MEM, CXP_TX_MEM},
pl::{csr, csr::CXP}};
const BUF_LEN: usize = 0x800;
const DATA_MAXSIZE: usize = 48;
const EV_MAXSIZE: usize = 253;
#[derive(Debug)]
pub enum Error {
BufferError,
CorruptedPacket,
CtrlAckError(u8),
LinkDown,
UnknownPacket(u8),
}
impl From<IoError> for Error {
fn from(_: IoError) -> Error {
Error::BufferError
}
}
// 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
fn get_cxp_crc(bytes: &[u8]) -> u32 {
(!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>;
fn read_4x_u64(&mut self) -> Result<u64, 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))
}
fn read_4x_u64(&mut self) -> Result<u64, Error> {
let mut bytes = [0; 6];
self.read_exact_4x(&mut bytes)?;
Ok(NetworkEndian::read_u64(&bytes))
}
}
#[derive(Debug)]
pub enum NameSpace {
GenICam,
DeviceSpecific,
}
#[derive(Debug)]
pub enum DownConnPacket {
CtrlReply {
tag: Option<u8>,
length: u32,
data: [u8; DATA_MAXSIZE],
},
CtrlDelay {
tag: Option<u8>,
length: u32,
time: [u8; DATA_MAXSIZE],
},
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 DownConnPacket {
pub fn read_from(reader: &mut Cursor<&mut [u8]>, packet_type: u8) -> Result<Self, Error> {
match packet_type {
0x03 => DownConnPacket::get_ctrl_packet(reader, false),
0x06 => DownConnPacket::get_ctrl_packet(reader, true),
0x07 => DownConnPacket::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(DownConnPacket::CtrlReply { tag, length, data });
} else {
return Ok(DownConnPacket::CtrlDelay {
tag,
length,
time: data,
});
}
}
0x01 => return Ok(DownConnPacket::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(DownConnPacket::Event {
conn_id,
packet_tag,
length,
ev_size,
namespace,
event_id,
timestamp,
ev,
})
}
}
pub fn receive(channel: usize) -> Result<Option<DownConnPacket>, Error> {
unsafe {
if (CXP[channel].downconn_pending_packet_read)() == 1 {
let read_buffer_ptr = (CXP[channel].downconn_read_ptr_read)() as usize;
println!("buffer ptr = {}", read_buffer_ptr);
let ptr = (CXP_RX_MEM[channel].base + 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].downconn_packet_type_read)();
let packet = DownConnPacket::read_from(&mut reader, packet_type);
println!("{:X?}", packet);
(CXP[channel].downconn_pending_packet_write)(1);
Ok(Some(packet?))
} else {
Ok(None)
}
}
}
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_4x_u64(&mut self, value: u64) -> 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_4x_u64(&mut self, value: u64) -> Result<(), Error> {
let mut bytes = [0; 6];
NetworkEndian::write_u64(&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 UpConnPacket {
CtrlRead {
tag: Option<u8>,
addr: u32,
length: u8,
},
CtrlWrite {
tag: Option<u8>,
addr: u32,
length: u8,
data: [u8; DATA_MAXSIZE],
}, // max register size is 8 bytes
EventAck {
packet_tag: u8,
},
TestPacket,
// DEBUG: Loopback message
CtrlAckLoopback {
ackcode: u8,
length: u8,
data: [u8; DATA_MAXSIZE],
},
Event {
conn_id: u32,
packet_tag: u8,
length: u16,
event_size: u16,
namespace: u8,
event_id: u16,
timestamp: u64,
data: [u8; 253],
},
}
impl UpConnPacket {
pub fn write_to(&self, writer: &mut Cursor<&mut [u8]>) -> Result<(), Error> {
match *self {
UpConnPacket::CtrlRead { tag, addr, length } => {
match tag {
Some(t) => {
writer.write_4x_u8(0x05)?;
writer.write_4x_u8(t)?;
}
None => {
writer.write_4x_u8(0x02)?;
}
}
writer.write_all(&[0x00, 0x00, 0x00, length])?;
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)?;
}
UpConnPacket::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)?;
}
}
writer.write_all(&[0x01, 0x00, 0x00, length])?;
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)?;
}
UpConnPacket::EventAck { packet_tag } => {
writer.write_4x_u8(0x08)?;
writer.write_4x_u8(packet_tag)?;
}
// DEBUG: Loopback message
UpConnPacket::CtrlAckLoopback { ackcode, length, data } => {
writer.write_4x_u8(0x03)?;
writer.write_4x_u8(ackcode)?;
if ackcode == 0x00 || ackcode == 0x04 {
writer.write_all(&[0x00, 0x00, 0x00, length])?;
writer.write_all(&data[0..length as usize])?;
}
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
UpConnPacket::Event {
conn_id,
packet_tag,
length,
event_size,
namespace,
event_id,
timestamp,
data,
} => {
// event packet header
writer.write_4x_u8(0x07)?;
writer.write_4x_u32(conn_id)?;
writer.write_4x_u8(packet_tag)?;
writer.write_4x_u16(length)?;
// event message
let ev_size = event_size.to_be_bytes();
let p2: u8 = ((namespace & 0b11) << 6) | ((event_id & 0xF00) >> 8) as u8;
let p3: u8 = (event_id & 0xFF) as u8;
writer.write_all(&[ev_size[0], ev_size[1], p2, p3])?;
writer.write_all(&timestamp.to_be_bytes())?;
writer.write_all(&data[0..event_size as usize])?;
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
_ => {}
}
Ok(())
}
}
pub fn send(channel: usize, packet: &UpConnPacket) -> Result<(), Error> {
if unsafe { csr::cxp_phys::upconn_tx_enable_read() } == 0 {
Err(Error::LinkDown)?
}
match *packet {
UpConnPacket::TestPacket => send_test_packet(channel),
_ => send_data_packet(channel, packet),
}
}
fn send_data_packet(channel: usize, packet: &UpConnPacket) -> Result<(), Error> {
unsafe {
while (CXP[channel].upconn_bootstrap_tx_busy_read)() == 1 {}
let ptr = CXP_TX_MEM[0].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)?;
println!("TX MEM after writing");
print_packet(&writer.get_ref()[0..40]);
(CXP[channel].upconn_bootstrap_tx_word_len_write)(writer.position() as u16 / 4);
(CXP[channel].upconn_bootstrap_tx_write)(1);
}
Ok(())
}
fn send_test_packet(channel: usize) -> Result<(), Error> {
unsafe {
while (CXP[channel].upconn_bootstrap_tx_busy_read)() == 1 {}
(CXP[channel].upconn_bootstrap_tx_testseq_write)(1);
}
Ok(())
}
pub fn write_u32(channel: u8, addr: u32, val: u32) -> Result<(), Error> {
// TODO: add tags after connection & verify it's CXPv2
let mut data: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
NetworkEndian::write_u32(&mut data[..4], val);
send(
channel as usize,
&UpConnPacket::CtrlWrite {
tag: None,
addr,
length: 4,
data,
},
)?;
Ok(())
}
pub fn read_u32(channel: u8, addr: u32) -> Result<(), Error> {
// TODO: add tags after connection & verify it's CXPv2
send(
channel as usize,
&UpConnPacket::CtrlRead {
tag: None,
addr,
length: 4,
},
)?;
Ok(())
}
// pub fn write_u64(channel: usize, addr: u32, data: u64) -> Result<(), Error> {
// let mut data_slice: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
// data_slice[..8].clone_from_slice(&data.to_be_bytes());
// send(
// channel,
// &UpConnPacket::CtrlWrite {
// tag: None,
// addr,
// length: 8,
// data: data_slice,
// },
// )?;
// 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!("============================================");
}
pub fn downconn_debug_send(channel: usize, packet: &UpConnPacket) -> Result<(), Error> {
unsafe {
while (CXP[channel].downconn_bootstrap_loopback_tx_busy_read)() == 1 {}
let ptr = CXP_LOOPBACK_MEM[0].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)?;
(CXP[channel].downconn_bootstrap_loopback_tx_word_len_write)(writer.position() as u16 / 4);
(CXP[channel].downconn_bootstrap_loopback_tx_write)(1);
}
Ok(())
}
pub fn downconn_debug_mem_print(channel: usize) {
unsafe {
let ptr = CXP_RX_MEM[channel].base as *mut u32;
let arr = slice::from_raw_parts_mut(ptr as *mut u8, BUF_LEN * 4);
print_packet(arr);
}
}
pub fn downconn_debug_send_trig_ack(channel: usize) {
unsafe {
(CXP[channel].downconn_ack_write)(1);
}
}
pub fn downconn_send_test_packet(channel: usize) {
unsafe {
while (CXP[channel].downconn_bootstrap_loopback_tx_busy_read)() == 1 {}
(CXP[channel].downconn_bootstrap_loopback_tx_testseq_write)(1);
}
}

View File

@ -0,0 +1,42 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::timer::GlobalTimer;
pub use crate::cxp_proto;
use crate::pl::{csr, csr::CXP};
pub fn tx_test(channel: usize, timer: &mut GlobalTimer) {
const LEN: usize = 4 * 30;
let mut pak_arr: [u8; LEN] = [0; LEN];
unsafe {
// cxp_proto::read_u32(channel, 0x00).expect("Cannot Write CoaXpress Register");
// cxp_proto::write_u64(channel, 0x00, 0x01);
// cxp_proto::send(channel, &cxp_proto::Packet::EventAck { packet_tag: 0x04 }).expect("Cannot send CoaXpress packet");
// cxp_proto::send(channel, &cxp_proto::Packet::TestPacket).expect("Cannot send CoaXpress packet");
timer.delay_us(2); // send one word
// DEBUG: Trigger packet
(CXP[channel].upconn_trig_delay_write)(0x86);
(CXP[channel].upconn_linktrigger_write)(0x00);
(CXP[channel].upconn_trig_stb_write)(1); // send trig
// DEBUG: Trigger ACK packet
// CXP[channel].upconn_ack_write(1);
timer.delay_us(20);
csr::cxp_phys::upconn_tx_enable_write(0);
// Collect data
let mut i: usize = 0;
while csr::cxp_phys::upconn_tx0_debug_buf_dout_valid_read() == 1 {
pak_arr[i] = csr::cxp_phys::upconn_tx0_debug_buf_dout_pak_read();
csr::cxp_phys::upconn_tx0_debug_buf_inc_write(1);
i += 1;
if i == LEN {
break;
}
}
cxp_proto::print_packet(&pak_arr);
}
}

View File

@ -185,24 +185,6 @@ 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 {
@ -240,6 +222,7 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
unsafe {
align_comma(timer);
pl::csr::eem_transceiver::rx_ready_write(1);
}
}
}

View File

@ -255,7 +255,6 @@ pub enum Packet {
destination: u8,
id: u32,
run: bool,
timestamp: u64,
},
SubkernelLoadRunReply {
destination: u8,
@ -288,77 +287,6 @@ 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 {
@ -586,7 +514,6 @@ 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()?,
@ -636,115 +563,6 @@ 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)),
})
}
@ -1059,14 +877,12 @@ 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)?;
@ -1122,115 +938,6 @@ 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(())
}
@ -1267,8 +974,7 @@ impl Packet {
| Packet::SubkernelLoadRunReply { .. }
| Packet::SubkernelMessageAck { .. }
| Packet::DmaPlaybackStatus { .. }
| Packet::SubkernelFinished { .. }
| Packet::CoreMgmtDropLinkAck { .. } => false,
| Packet::SubkernelFinished { .. } => 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,15 @@ 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 fn identifier_read(buf: &mut [u8]) -> &str {
unsafe {
pl::csr::identifier::address_write(0);

View File

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

View File

@ -0,0 +1,11 @@
use libboard_artiq::cxp_proto;
pub extern "C" fn cxp_readu32(channel: i32, addr: i32) {
// TODO: use artiq_raise like i2c?
cxp_proto::read_u32(channel as u8, addr as u32).expect("CXP transmission failed");
}
pub extern "C" fn cxp_writeu32(channel: i32, addr: i32, val: i32) {
// TODO: use artiq_raise like i2c?
cxp_proto::write_u32(channel as u8, addr as u32, val as u32).expect("CXP transmission failed");
}

View File

@ -476,7 +476,7 @@ extern "C" fn stop_fn(
}
// Must be kept in sync with preallocate_runtime_exception_names() in `artiq.compiler.embedding`
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
static EXCEPTION_ID_LOOKUP: [(&str, u32); 20] = [
("RTIOUnderflow", 0),
("RTIOOverflow", 1),
("RTIODestinationUnreachable", 2),
@ -497,8 +497,6 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
("TypeError", 17),
("ValueError", 18),
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
];
pub fn get_exception_id(name: &str) -> u32 {

View File

@ -11,6 +11,8 @@ use super::{cache,
core1::rtio_get_destination_status,
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,12 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
#[cfg(has_drtio)]
api!(subkernel_await_message = subkernel::await_message),
// CoaXPress
#[cfg(has_cxp_phys)]
api!(cxp_readu32 = cxp::cxp_readu32),
#[cfg(has_cxp_phys)]
api!(cxp_writeu32 = cxp::cxp_writeu32),
// Double-precision floating-point arithmetic helper functions
// RTABI chapter 4.1.2, Table 2
api!(__aeabi_dadd),

View File

@ -81,7 +81,6 @@ pub enum Message {
id: u32,
destination: u8,
run: bool,
timestamp: u64,
},
#[cfg(has_drtio)]
SubkernelLoadRunReply {

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, eh_artiq, rpc::send_args, rtio::now_mu};
use crate::{artiq_raise, eh_artiq, rpc::send_args};
pub extern "C" fn load_run(id: u32, destination: u8, run: bool) {
unsafe {
@ -14,7 +14,6 @@ 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() {

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

@ -8,7 +8,6 @@ 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]
@ -21,7 +20,6 @@ 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,9 +405,8 @@ async fn handle_run_kernel(
id,
destination: _,
run,
timestamp,
} => {
let succeeded = match subkernel::load(aux_mutex, routing_table, timer, id, run, timestamp).await {
let succeeded = match subkernel::load(aux_mutex, routing_table, timer, id, run).await {
Ok(()) => true,
Err(e) => {
error!("Error loading subkernel: {:?}", e);
@ -697,6 +696,27 @@ async fn handle_connection(
}
}
async fn load_and_run_idle_kernel(
buffer: &Vec<u8>,
control: &Rc<RefCell<kernel::Control>>,
up_destinations: &Rc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &drtio_routing::RoutingTable,
timer: GlobalTimer,
) {
info!("Loading idle kernel");
let res = handle_flash_kernel(buffer, control, up_destinations, aux_mutex, routing_table, timer).await;
match res {
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");
}
pub fn main(timer: GlobalTimer, cfg: Config) {
let net_addresses = net_settings::get_addresses(&cfg);
info!("network addresses: {}", net_addresses);
@ -757,6 +777,7 @@ pub fn main(timer: GlobalTimer, cfg: Config) {
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();
@ -783,34 +804,35 @@ pub fn main(timer: GlobalTimer, cfg: Config) {
}
}
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,
)),
);
mgmt::start(cfg);
task::spawn(async move {
let connection = Rc::new(Semaphore::new(1, 1));
let connection = Rc::new(Semaphore::new(0, 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 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
};
let idle_kernel = idle_kernel.clone();
let connection = connection.clone();
let terminate = terminate.clone();
let up_destinations = up_destinations.clone();
let aux_mutex = aux_mutex.clone();
let routing_table = drtio_routing_table.clone();
task::spawn(async move {
let routing_table = routing_table.borrow();
select_biased! {
_ = (async {
if let Some(buffer) = &*idle_kernel {
load_and_run_idle_kernel(&buffer, &control, &up_destinations, &aux_mutex, &routing_table, timer).await;
}
}).fuse() => (),
_ = terminate.async_wait().fuse() => ()
}
connection.signal();
});
}
loop {
let mut stream = TcpStream::accept(1381, 0x10_000, 0x10_000).await.unwrap();
if connection.try_wait().is_none() {
// there is an existing connection
@ -818,58 +840,32 @@ 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 {
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")
}
}
},
None => info!("no idle kernel found")
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 {
load_and_run_idle_kernel(&buffer, &control, &up_destinations, &aux_mutex, &routing_table, timer).await;
}
}).fuse() => (),
_ = terminate.async_wait().fuse() => ()
}
connection.signal();
if let Some(stream) = maybe_stream {
let _ = stream.flush().await;
let _ = stream.abort().await;
}
let _ = stream.flush().await;
let _ = stream.abort().await;
});
}
});
@ -918,8 +914,7 @@ pub fn soft_panic_main(timer: GlobalTimer, cfg: Config) -> ! {
Sockets::init(32);
let dummy = Rc::new(Semaphore::new(0, 1));
mgmt::start(Rc::new(cfg), dummy, None);
mgmt::start(cfg);
// getting eth settings disables the LED as it resets GPIO
// need to re-enable it here

View File

@ -17,7 +17,7 @@ use libasync::task;
use libboard_artiq::drtio_eem;
#[cfg(feature = "target_kasli_soc")]
use libboard_artiq::io_expander;
use libboard_artiq::{identifier_read, logger, pl};
use libboard_artiq::{cxp_downconn, cxp_phys, cxp_proto, cxp_upconn, identifier_read, logger, pl};
use libboard_zynq::{gic, mpcore, timer::GlobalTimer};
use libconfig::Config;
use libcortex_a9::l2c::enable_l2_cache;
@ -150,5 +150,15 @@ pub fn main_core0() {
task::spawn(ksupport::report_async_rtio_errors());
cxp_phys::setup(&mut timer);
cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_1);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_2);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_3);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_5);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_6);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_10);
// cxp_downconn::loopback_testing(0, &mut timer, cxp_phys::CXP_SPEED::CXP_12);
// cxp_upconn::tx_test(0, &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.async_lock().await;
let _lock = aux_mutex.lock();
drtioaux_async::send(
linkno,
&drtioaux_async::Packet::InjectionRequest {

View File

@ -1,8 +1,6 @@
#[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;
@ -13,8 +11,6 @@ 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)]
@ -73,7 +69,7 @@ fn get_rtio_clock_cfg(cfg: &Config) -> RtioClock {
res
}
#[cfg(not(any(has_drtio, feature = "target_ebaz4205")))]
#[cfg(not(has_drtio))]
fn init_rtio(timer: &mut GlobalTimer) {
info!("Switching SYS clocks...");
unsafe {
@ -410,38 +406,6 @@ 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)]
@ -465,19 +429,9 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
#[cfg(has_drtio)]
init_drtio(timer);
#[cfg(not(any(has_drtio, feature = "target_ebaz4205")))]
#[cfg(not(has_drtio))]
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

@ -13,13 +13,9 @@ 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,
@ -30,10 +26,6 @@ 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,
@ -84,18 +76,8 @@ pub mod drtio {
});
}
async fn link_rx_up(linkno: u8, _timer: &mut GlobalTimer) -> bool {
async fn link_rx_up(linkno: u8) -> 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 }
}
@ -170,8 +152,8 @@ pub mod drtio {
}
}
async fn recv_aux_timeout(linkno: u8, timeout: u64, mut timer: GlobalTimer) -> Result<Packet, Error> {
if !link_rx_up(linkno, &mut timer).await {
async fn recv_aux_timeout(linkno: u8, timeout: u64, timer: GlobalTimer) -> Result<Packet, Error> {
if !link_rx_up(linkno).await {
return Err(Error::LinkDown);
}
match drtioaux_async::recv_timeout(linkno, Some(timeout), timer).await {
@ -186,9 +168,9 @@ pub mod drtio {
linkno: u8,
routing_table: &RoutingTable,
request: &Packet,
mut timer: GlobalTimer,
timer: GlobalTimer,
) -> Result<Packet, Error> {
if !link_rx_up(linkno, &mut timer).await {
if !link_rx_up(linkno).await {
return Err(Error::LinkDown);
}
let _lock = aux_mutex.async_lock().await;
@ -212,11 +194,11 @@ pub mod drtio {
aux_mutex: &Rc<Mutex<bool>>,
linkno: u8,
routing_table: &RoutingTable,
mut timer: GlobalTimer,
timer: GlobalTimer,
) -> u32 {
let mut count = 0;
loop {
if !link_rx_up(linkno, &mut timer).await {
if !link_rx_up(linkno).await {
return 0;
}
count += 1;
@ -480,7 +462,7 @@ pub mod drtio {
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &RoutingTable,
up_destinations: &Rc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
mut timer: GlobalTimer,
timer: GlobalTimer,
) {
let mut up_links = [false; csr::DRTIO.len()];
loop {
@ -488,35 +470,16 @@ pub mod drtio {
let linkno = linkno as u8;
if up_links[linkno as usize] {
/* link was previously up */
if link_rx_up(linkno, &mut timer).await {
if link_rx_up(linkno).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 */
#[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 {
if link_rx_up(linkno).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 {
@ -560,7 +523,7 @@ pub mod drtio {
for linkno in 0..csr::DRTIO.len() {
let linkno = linkno as u8;
if task::block_on(link_rx_up(linkno, &mut timer)) {
if task::block_on(link_rx_up(linkno)) {
let reply = task::block_on(aux_transact(
&aux_mutex,
linkno,
@ -577,7 +540,7 @@ pub mod drtio {
}
}
pub async fn partition_data<PacketF, HandlerF>(
async fn partition_data<PacketF, HandlerF>(
linkno: u8,
aux_mutex: &Rc<Mutex<bool>>,
routing_table: &RoutingTable,
@ -829,7 +792,6 @@ 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(
@ -841,7 +803,6 @@ pub mod drtio {
source: 0,
destination: destination,
run: run,
timestamp,
},
timer,
)

View File

@ -100,22 +100,12 @@ 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,
timestamp,
)
.await?;
drtio::subkernel_load(aux_mutex, routing_table, timer, id, subkernel.destination, run).await?;
if run {
subkernel.state = SubkernelState::Running;
}

View File

@ -15,9 +15,7 @@ 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"

View File

@ -4,9 +4,7 @@
#[macro_use]
extern crate log;
extern crate byteorder;
extern crate core_io;
extern crate crc;
extern crate cslice;
extern crate embedded_hal;
@ -40,18 +38,16 @@ use libboard_artiq::{drtio_routing, drtioaux,
pl::csr};
#[cfg(feature = "target_kasli_soc")]
use libboard_zynq::error_led::ErrorLED;
use libboard_zynq::{i2c::I2c, print, println, slcr, time::Milliseconds, timer::GlobalTimer};
use libboard_zynq::{i2c::I2c, print, println, time::Milliseconds, timer::GlobalTimer};
use libconfig::Config;
use libcortex_a9::{l2c::enable_l2_cache, regs::MPIDR};
use libregister::RegisterR;
use libsupport_zynq::{exception_vectors, ram};
use mgmt::Manager as CoreManager;
use routing::Router;
use subkernel::Manager as KernelManager;
mod analyzer;
mod dma;
mod mgmt;
mod repeater;
mod routing;
mod subkernel;
@ -153,7 +149,6 @@ fn process_aux_packet(
dma_manager: &mut DmaManager,
analyzer: &mut Analyzer,
kernel_manager: &mut KernelManager,
core_manager: &mut CoreManager,
router: &mut Router,
) -> Result<(), drtioaux::Error> {
// In the code below, *_chan_sel_write takes an u8 if there are fewer than 256 channels,
@ -831,7 +826,6 @@ fn process_aux_packet(
destination: _destination,
id,
run,
timestamp,
} => {
forward!(
router,
@ -850,7 +844,7 @@ fn process_aux_packet(
// cannot run kernel while DDMA is running
succeeded = false;
} else {
succeeded |= kernel_manager.run(source, id, timestamp).is_ok();
succeeded |= kernel_manager.run(source, id).is_ok();
}
}
router.send(
@ -1016,335 +1010,6 @@ fn process_aux_packet(
}
Ok(())
}
drtioaux::Packet::CoreMgmtGetLogRequest {
destination: _destination,
clear,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
let mut data_slice = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = core_manager.log_get_slice(&mut data_slice, clear);
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtGetLogReply {
last: meta.status.is_last(),
length: meta.len as u16,
data: data_slice,
},
)
}
drtioaux::Packet::CoreMgmtClearLogRequest {
destination: _destination,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
mgmt::clear_log();
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
drtioaux::Packet::CoreMgmtSetLogLevelRequest {
destination: _destination,
log_level,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
info!("Changing log level to {}", level_filter);
log::set_max_level(level_filter);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
drtioaux::Packet::CoreMgmtSetUartLogLevelRequest {
destination: _destination,
log_level,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
info!("Changing UART log level to {}", level_filter);
unsafe {
logger::BufferLogger::get_logger()
.as_ref()
.unwrap()
.set_uart_log_level(level_filter);
}
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
drtioaux::Packet::CoreMgmtConfigReadRequest {
destination: _destination,
length,
key,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
let key_slice = &key[..length as usize];
if !key_slice.is_ascii() {
error!("invalid key");
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
} else {
let key = core::str::from_utf8(key_slice).unwrap();
if core_manager.fetch_config_value(key).is_ok() {
let meta = core_manager.get_config_value_slice(&mut value_slice);
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtConfigReadReply {
last: meta.status.is_last(),
length: meta.len as u16,
value: value_slice,
},
)
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
}
drtioaux::Packet::CoreMgmtConfigReadContinue {
destination: _destination,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = core_manager.get_config_value_slice(&mut value_slice);
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtConfigReadReply {
last: meta.status.is_last(),
length: meta.len as u16,
value: value_slice,
},
)
}
drtioaux::Packet::CoreMgmtConfigWriteRequest {
destination: _destination,
last,
length,
data,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
core_manager.add_config_data(&data, length as usize);
let mut succeeded = true;
if last {
succeeded = core_manager.write_config().is_ok();
core_manager.clear_config_data();
}
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
}
drtioaux::Packet::CoreMgmtConfigRemoveRequest {
destination: _destination,
length,
key,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
let key_slice = &key[..length as usize];
if !key_slice.is_ascii() {
error!("invalid key");
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
} else {
let key = core::str::from_utf8(key_slice).unwrap();
let succeeded = core_manager.remove_config(key).is_ok();
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
}
}
drtioaux::Packet::CoreMgmtConfigEraseRequest {
destination: _destination,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
error!("config erase not supported on zynq device");
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
drtioaux::Packet::CoreMgmtRebootRequest {
destination: _destination,
} => {
info!("received reboot request");
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })?;
info!("reboot imminent");
slcr::reboot();
unreachable!();
}
drtioaux::Packet::CoreMgmtAllocatorDebugRequest {
destination: _destination,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
error!("debug allocator not supported on zynq device");
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
drtioaux::Packet::CoreMgmtFlashRequest {
destination: _destination,
payload_length,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
core_manager.allocate_image_buffer(payload_length as usize);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
drtioaux::Packet::CoreMgmtFlashAddDataRequest {
destination: _destination,
last,
length,
data,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
core_manager.add_image_data(&data, length as usize);
if last {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtDropLink)
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
}
drtioaux::Packet::CoreMgmtDropLinkAck {
destination: _destination,
} => {
forward!(
router,
_routing_table,
_destination,
*rank,
*self_destination,
_repeaters,
&packet,
timer
);
unsafe {
csr::gt_drtio::txenable_write(0);
}
core_manager.write_image();
info!("reboot imminent");
slcr::reboot();
Ok(())
}
p => {
warn!("received unexpected aux packet: {:?}", p);
@ -1363,7 +1028,6 @@ fn process_aux_packets(
dma_manager: &mut DmaManager,
analyzer: &mut Analyzer,
kernel_manager: &mut KernelManager,
core_manager: &mut CoreManager,
router: &mut Router,
) {
let result = drtioaux::recv(0).and_then(|packet| {
@ -1379,7 +1043,6 @@ fn process_aux_packets(
dma_manager,
analyzer,
kernel_manager,
core_manager,
router,
)
} else {
@ -1576,7 +1239,7 @@ pub extern "C" fn main_core0() -> i32 {
#[cfg(has_si549)]
si549::helper_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize helper Si549");
let mut cfg = match Config::new() {
let cfg = match Config::new() {
Ok(cfg) => cfg,
Err(err) => {
warn!("config initialization failed: {}", err);
@ -1651,7 +1314,6 @@ pub extern "C" fn main_core0() -> i32 {
let mut dma_manager = DmaManager::new();
let mut analyzer = Analyzer::new();
let mut kernel_manager = KernelManager::new(&mut control);
let mut core_manager = CoreManager::new(&mut cfg);
drtioaux::reset(0);
drtiosat_reset(false);
@ -1669,7 +1331,6 @@ pub extern "C" fn main_core0() -> i32 {
&mut dma_manager,
&mut analyzer,
&mut kernel_manager,
&mut core_manager,
&mut router,
);
#[allow(unused_mut)]

View File

@ -1,149 +0,0 @@
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

@ -4,7 +4,7 @@ use core::cmp::min;
#[cfg(has_drtio_routing)]
use libboard_artiq::pl::csr;
use libboard_artiq::{drtio_routing, drtioaux,
drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE}};
drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}};
pub struct SliceMeta {
pub destination: u8,
@ -58,7 +58,6 @@ impl Sliceable {
}
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,7 +8,7 @@ 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, rtio};
use ksupport::{eh_artiq, kernel, rpc};
use libboard_artiq::{drtio_routing::RoutingTable,
drtioaux,
drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE},
@ -349,7 +349,7 @@ impl<'a> Manager<'_> {
}
}
pub fn run(&mut self, source: u8, id: u32, timestamp: u64) -> Result<(), Error> {
pub fn run(&mut self, source: u8, id: u32) -> Result<(), Error> {
if self.session.kernel_state != KernelState::Loaded || self.session.id != id {
self.load(id)?;
}
@ -359,7 +359,6 @@ impl<'a> Manager<'_> {
csr::cri_con::selected_write(2);
}
rtio::at_mu(timestamp as i64);
self.control.tx.send(kernel::Message::StartRequest);
Ok(())
}
@ -813,7 +812,6 @@ impl<'a> Manager<'_> {
id,
destination: sk_destination,
run,
timestamp,
} => {
self.session.kernel_state = KernelState::SubkernelAwaitLoad;
router.route(
@ -822,7 +820,6 @@ impl<'a> Manager<'_> {
destination: sk_destination,
id: id,
run: run,
timestamp,
},
routing_table,
rank,