├── .github └── workflows │ └── modelsim-test.yml ├── LICENSE ├── README.md ├── docs ├── fifos.png ├── fifos.wavedrom ├── ft2232h_de10lite.png ├── ft245_async.png ├── ft245_sync_ft2xx.png ├── ft245_sync_ft60x.png ├── system.excalidraw └── system.png ├── examples └── ft2232h_de10lite │ ├── .gitignore │ ├── README.md │ ├── hw │ ├── .gitignore │ ├── async245.svh │ ├── clean.sh │ ├── create_proj.sh │ ├── proto245.tcl │ ├── sync245.svh │ ├── top.sdc │ └── top.sv │ ├── test_ftd2xx.py │ ├── test_ftdi1.py │ ├── test_pylibftdi.py │ └── test_pyusb.py ├── src ├── dpram.sv ├── fifo_async.sv ├── fifo_sync.sv ├── proto245a.sv └── proto245s.sv └── tests ├── .gitignore ├── README.md ├── common ├── fifo_if.sv ├── ft245_async_if.sv ├── ft245_sync_if.sv └── utils.svh ├── conftest.py ├── sim.py ├── tb_245async ├── tb.sv ├── test_rx.svh └── test_tx.svh ├── tb_245sync ├── tb.sv ├── test_rx_flow_control.svh ├── test_rx_simple.svh ├── test_rx_thresholds.svh ├── test_tx_flow_control.svh ├── test_tx_simple.svh └── test_tx_thresholds.svh ├── test_245async.py └── test_245sync.py /.github/workflows/modelsim-test.yml: -------------------------------------------------------------------------------- 1 | name: pytest-modelsim 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-20.04 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.x 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install pytest pytest-xdist 20 | sudo dpkg --add-architecture i386 21 | sudo apt update 22 | sudo apt install -y libc6:i386 libxtst6:i386 libncurses5:i386 libxft2:i386 libstdc++6:i386 libc6-dev-i386 lib32z1 libqt5xml5 liblzma-dev 23 | wget https://download.altera.com/akdlm/software/acdsinst/20.1std/711/ib_installers/ModelSimSetup-20.1.0.711-linux.run 24 | chmod +x ModelSimSetup-20.1.0.711-linux.run 25 | ./ModelSimSetup-20.1.0.711-linux.run --mode unattended --accept_eula 1 --installdir $HOME/ModelSim-20.1.0 --unattendedmodeui none 26 | echo "$HOME/ModelSim-20.1.0/modelsim_ase/bin" >> $GITHUB_PATH 27 | - name: Test code 28 | working-directory: ./tests 29 | run: | 30 | pytest -n auto -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 esynr3z 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PyTest Modelsim Status](https://github.com/esynr3z/proto245/workflows/pytest-modelsim/badge.svg) 2 | 3 | # Synchronous/Asynchronous FT245 FIFO protocol cores 4 | 5 | ![system-arch](docs/system.png) 6 | 7 | IP-cores written in SystemVerilog for communicating with numerous FTDI chips using FT245 FIFO protocol. 8 | 9 | The protocol exists in the two versions: synchronous and asynchronous, and there are two cores ```proto245s``` and ```proto245a``` to use with the appropriate version. 10 | Table with supported FTDI devices (based on the protocol specifications) below. 11 | 12 | | FTDI Chip | USB Speed | Asynchronous FIFO | Synchronous FIFO | 13 | | :------------------------------------------------------------------------------------------------------ | :--------- | :---------------- | :--------------- | 14 | | [FT245R](https://ftdichip.com/products/ft245rq/) | FullSpeed | ↔️ 1MB/s | ❌ | 15 | | [FT240X](https://ftdichip.com/products/ft240xs/) | FullSpeed | ↔️ 1MB/s | ❌ | 16 | | [FT2232D](https://ftdichip.com/products/ft2232d/) | FullSpeed | ↔️ 1MB/s | ❌ | 17 | | [FT232H](https://ftdichip.com/products/ft232hq/) | HighSpeed | ↔️ 8MB/s | ↔️ 40MB/s | 18 | | [FT2232H](https://ftdichip.com/products/ft2232hq/) | HighSpeed | ↔️ 8MB/s | ↔️ 40MB/s | 19 | | [FT232HP](https://ftdichip.com/products/ft232hp/)/[FT233HP](https://ftdichip.com/products/ft233hp/) | HighSpeed | ↔️ 8MB/s | ↔️ 40MB/s | 20 | | [FT2232HP](https://ftdichip.com/products/ft2232hp/)/[FT2233HP](https://ftdichip.com/products/ft2233hp/) | HighSpeed | ↔️ 8MB/s | ↔️ 40MB/s | 21 | | [FT600Q](https://ftdichip.com/products/ft600q-b/) | SuperSpeed | ❌ | ↔️ 200MB/s | 22 | | [FT601Q](https://ftdichip.com/products/ft601q-b/) | SuperSpeed | ❌ | ↔️ 400MB/s | 23 | | [FT602Q](https://ftdichip.com/products/ft602q-b/) | SuperSpeed | ❌ | ↔️ 400MB/s | 24 | 25 | ## proto245a core 26 | 27 | Implements asynchronous FT245 protocol from the one side and provides RX & TX FIFO interfaces from the other side. 28 | 29 | Features: 30 | 31 | - configurable RX & TX FIFO size 32 | - configurable ```RD#``` & ```WR#``` assertion time to adapt to any clock frequency 33 | - flexible clock domains - FSM and FIFOs might share the same clock or use separate clocks 34 | 35 | Notes: 36 | 37 | - Send Immediate / Wake Up signal (SIWU) tied to inactive state 38 | 39 | ### Core hierarchy 40 | 41 | Single clock domain: 42 | 43 | ``` 44 | proto245a (proto245a.sv) 45 | ├── rxfifo (fifo_sync.sv) 46 | │ └── dpram (dpram.sv) 47 | └── txfifo (fifo_sync.sv) 48 | └── dpram (dpram.sv) 49 | ``` 50 | 51 | Multiple clock domains: 52 | ``` 53 | proto245a (proto245a.sv) 54 | ├── rxfifo (fifo_async.sv) 55 | │ └── dpram (dpram.sv) 56 | └── txfifo (fifo_async.sv) 57 | └── dpram (dpram.sv) 58 | ``` 59 | 60 | ### FTDI side access waveforms 61 | 62 | From FT2232H [datasheet](http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf). 63 | 64 | ![ft245_async](docs/ft245_async.png) 65 | 66 | ### FIFOs side access 67 | 68 | ![fifos](docs/fifos.png) 69 | 70 | ## proto245s core 71 | 72 | Implements synchronous FT245 protocol from the one side and provides RX & TX FIFO interfaces from the other side. 73 | 74 | Features: 75 | 76 | - configurable data size (e.g. 8 bits needed for HS devices, 16/32 bits for SS devices) 77 | - configurable RX & TX FIFO size 78 | - configurable RX & TX burst size - maximum number of words per one read/write burst (optional) 79 | - configurable RX & TX FIFO thresholds - transaction will start only if FIFO is filled below/above the threshold 80 | - configurable pause between transactions 81 | - flexible clock domains - FSM and FIFOs might share the same clock or use separate clocks 82 | 83 | Notes: 84 | 85 | - FT2xx chips: Send Immediate / Wake Up signal (SIWU) tied to inactive state 86 | - FT60x chips: Byte Enable signals (BE) are not supported at the moment, so, all transactions have to be word aligned 87 | 88 | ### Core hierarchy 89 | 90 | Single clock domain: 91 | 92 | ``` 93 | proto245s (proto245s.sv) 94 | ├── rxfifo (fifo_sync.sv) 95 | │ └── dpram (dpram.sv) 96 | ├── txfifo (fifo_sync.sv) 97 | │ └── dpram (dpram.sv) 98 | └── txovrbuf (fifo_sync.sv) 99 | └── dpram (dpram.sv) 100 | ``` 101 | 102 | Multiple clock domains: 103 | ``` 104 | proto245s (proto245s.sv) 105 | ├── rxfifo (fifo_async.sv) 106 | │ └── dpram (dpram.sv) 107 | ├── txfifo (fifo_async.sv) 108 | │ └── dpram (dpram.sv) 109 | └── txovrbuf (fifo_sync.sv) 110 | └── dpram (dpram.sv) 111 | ``` 112 | 113 | ### FTDI side access waveforms 114 | 115 | From FT2232H [datasheet](http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf). 116 | 117 | ![ft245_sync_ft2xx](docs/ft245_sync_ft2xx.png) 118 | 119 | From FT600Q/FT601Q [datasheet](https://ftdichip.com/wp-content/uploads/2020/07/DS_FT600Q-FT601Q-IC-Datasheet.pdf). 120 | 121 | ![ft245_sync_ft60x](docs/ft245_sync_ft60x.png) 122 | 123 | ### FIFOs side access 124 | 125 | The same as for ```proto245a``` core. 126 | 127 | ## Examples 128 | 129 | | Project | FPGA/Board | FTDI chip | Software | 130 | | :-------------------------------------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | 131 | | [ft2232h_de10lite](examples/ft2232h_de10lite) | [DE10-Lite](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&No=1021) | [FT2232H](https://ftdichip.com/products/ft2232hq/) | Python ([ftd2xx](https://pypi.org/project/ftd2xx/), [pylibftdi](https://github.com/codedstructure/pylibftdi), [pyusb](https://github.com/pyusb/pyusb), ftdi1) | 132 | 133 | For more details please follow README files inside ```examples/$PROJNAME$``` directories. 134 | 135 | ## Simulation and testing 136 | 137 | Environment is built around Python [pytest](https://docs.pytest.org/) framework - it offers some nice and easy to use tools for test execution control and parametrization out of the box. 138 | 139 | Current workflow is based on the [pyhdlsim](https://github.com/esynr3z/pyhdlsim) example - ```sim.py``` is a wrapper around HDL simulators and ```test_*.py``` files contain tests. 140 | 141 | Tested on: 142 | 143 | * Windows 10, Python 3.8, Modelsim 10.6d 144 | * Ubuntu 20.04, Python 3.8, Modelsim 2020.02 145 | 146 | For more details please follow testing [README](tests/README.md). 147 | -------------------------------------------------------------------------------- /docs/fifos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/fifos.png -------------------------------------------------------------------------------- /docs/fifos.wavedrom: -------------------------------------------------------------------------------- 1 | {"signal": [ 2 | ['RX FIFO read', 3 | {"name": "rxfifo_clk", "wave": "p|pppppppppppppppp"}, 4 | {"name": "rxfifo_rst", "wave": "0|................"}, 5 | {"name": "rxfifo_rd", "wave": "0|.1....0.|.1...0."}, 6 | {"name": "rxfifo_data", "wave": "x|..44444x|..44x..", "data": ["D0", "D1", "D2", "D3", "D4", "D5", "D6"]}, 7 | {"name": "rxfifo_valid", "wave": "0|..1....0|..1.0.."}, 8 | {"name": "rxfifo_load", "wave": "5|5..55555|5..55..", "data": ["0", "5", "4", "3", "2", "1", "0", "2", "1", "0"]}, 9 | {"name": "rxfifo_empty", "wave": "0|.......1|0...1.."}, 10 | ], 11 | {}, 12 | ['TX FIFO write', 13 | {"name": "txfifo_clk", "wave": "p|pppppppppppppppp"}, 14 | {"name": "txfifo_rst", "wave": "0|................"}, 15 | {"name": "txfifo_wr", "wave": "0|.1....0.|.1..0.."}, 16 | {"name": "txfifo_data", "wave": "x|.44444x.|.444x..", "data": ["D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"]}, 17 | {"name": "txfifo_load", "wave": "5|..55555.|..555..", "data": ["0", "1", "2", "3", "4", "5", "6", "7", "8"]}, 18 | {"name": "txfifo_full", "wave": "0|........|....1.."}, 19 | ], 20 | ]} -------------------------------------------------------------------------------- /docs/ft2232h_de10lite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/ft2232h_de10lite.png -------------------------------------------------------------------------------- /docs/ft245_async.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/ft245_async.png -------------------------------------------------------------------------------- /docs/ft245_sync_ft2xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/ft245_sync_ft2xx.png -------------------------------------------------------------------------------- /docs/ft245_sync_ft60x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/ft245_sync_ft60x.png -------------------------------------------------------------------------------- /docs/system.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 528, 9 | "versionNonce": 185953926, 10 | "isDeleted": false, 11 | "id": "C2JwIdhyeYD3_Y8KtEtNG", 12 | "fillStyle": "cross-hatch", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 2, 16 | "opacity": 50, 17 | "angle": 0, 18 | "x": 749.3890245488974, 19 | "y": 163.1690944557472, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#ced4da", 22 | "width": 1053.5999145507812, 23 | "height": 905.0667521158853, 24 | "seed": 1895555546, 25 | "groupIds": [], 26 | "strokeSharpness": "sharp", 27 | "boundElementIds": [] 28 | }, 29 | { 30 | "type": "rectangle", 31 | "version": 824, 32 | "versionNonce": 1492354374, 33 | "isDeleted": false, 34 | "id": "eDtMDhZa73ihbnH7tvxrO", 35 | "fillStyle": "cross-hatch", 36 | "strokeWidth": 1, 37 | "strokeStyle": "solid", 38 | "roughness": 2, 39 | "opacity": 100, 40 | "angle": 0, 41 | "x": 113.94425596231622, 42 | "y": 161.01009071941644, 43 | "strokeColor": "#000000", 44 | "backgroundColor": "#fa5252", 45 | "width": 352.7999267578125, 46 | "height": 905.0667521158853, 47 | "seed": 183171974, 48 | "groupIds": [], 49 | "strokeSharpness": "sharp", 50 | "boundElementIds": [ 51 | "TOO-xDTa-XwgWWd5tjvEs", 52 | "gP2xgaTLVYXty168llEyj", 53 | "FnPze7wfT9RiazG_eW9o0", 54 | "jnGQ_mi-WZlJhzp0d1-zM", 55 | "wvfOQvIfweZdtTOb_LGDK", 56 | "SsAotnp5A5Qt78tZjmwzO", 57 | "Zk6JiCp0bXz_v2kQA1BPx", 58 | "VCcIGab_Pe9udIxhhd3I9", 59 | "vv2V5cysiamFxyLGDfEyx" 60 | ] 61 | }, 62 | { 63 | "type": "rectangle", 64 | "version": 627, 65 | "versionNonce": 1420930502, 66 | "isDeleted": false, 67 | "id": "QW_2AUQKoO6h82rd7AsVm", 68 | "fillStyle": "hachure", 69 | "strokeWidth": 1, 70 | "strokeStyle": "solid", 71 | "roughness": 2, 72 | "opacity": 100, 73 | "angle": 0, 74 | "x": 831.6746865187633, 75 | "y": 313.22636287092575, 76 | "strokeColor": "#000000", 77 | "backgroundColor": "transparent", 78 | "width": 665.5999959309898, 79 | "height": 709.8667246500651, 80 | "seed": 818939142, 81 | "groupIds": [], 82 | "strokeSharpness": "sharp", 83 | "boundElementIds": [ 84 | "jnGQ_mi-WZlJhzp0d1-zM", 85 | "wvfOQvIfweZdtTOb_LGDK", 86 | "SsAotnp5A5Qt78tZjmwzO", 87 | "Zk6JiCp0bXz_v2kQA1BPx", 88 | "gP2xgaTLVYXty168llEyj", 89 | "FnPze7wfT9RiazG_eW9o0", 90 | "VCcIGab_Pe9udIxhhd3I9", 91 | "vv2V5cysiamFxyLGDfEyx" 92 | ] 93 | }, 94 | { 95 | "type": "text", 96 | "version": 167, 97 | "versionNonce": 1256028570, 98 | "isDeleted": false, 99 | "id": "Pgx-mDCqWrygJsgbJrP5l", 100 | "fillStyle": "solid", 101 | "strokeWidth": 1, 102 | "strokeStyle": "solid", 103 | "roughness": 0, 104 | "opacity": 100, 105 | "angle": 0, 106 | "x": 857.0602572265386, 107 | "y": 318.8215660889318, 108 | "strokeColor": "#000000", 109 | "backgroundColor": "#ffffff", 110 | "width": 482, 111 | "height": 46, 112 | "seed": 244108230, 113 | "groupIds": [], 114 | "strokeSharpness": "sharp", 115 | "boundElementIds": null, 116 | "fontSize": 36, 117 | "fontFamily": 1, 118 | "text": "proto245s/proto245a core", 119 | "baseline": 32, 120 | "textAlign": "center", 121 | "verticalAlign": "top" 122 | }, 123 | { 124 | "type": "text", 125 | "version": 170, 126 | "versionNonce": 1996752134, 127 | "isDeleted": false, 128 | "id": "SFsdUedc5y8X3RRu7OR2p", 129 | "fillStyle": "solid", 130 | "strokeWidth": 1, 131 | "strokeStyle": "solid", 132 | "roughness": 0, 133 | "opacity": 100, 134 | "angle": 0, 135 | "x": 287.6269991698981, 136 | "y": 65.85489382737568, 137 | "strokeColor": "#000000", 138 | "backgroundColor": "#ffffff", 139 | "width": 74, 140 | "height": 46, 141 | "seed": 409377094, 142 | "groupIds": [], 143 | "strokeSharpness": "sharp", 144 | "boundElementIds": [], 145 | "fontSize": 36, 146 | "fontFamily": 1, 147 | "text": "USB", 148 | "baseline": 32, 149 | "textAlign": "center", 150 | "verticalAlign": "top" 151 | }, 152 | { 153 | "type": "rectangle", 154 | "version": 982, 155 | "versionNonce": 1861170778, 156 | "isDeleted": false, 157 | "id": "zStYYw9UvMUXifFw58G1D", 158 | "fillStyle": "hachure", 159 | "strokeWidth": 1, 160 | "strokeStyle": "solid", 161 | "roughness": 1, 162 | "opacity": 100, 163 | "angle": 0, 164 | "x": 846.5314747907131, 165 | "y": 383.18339121448935, 166 | "strokeColor": "#000000", 167 | "backgroundColor": "#fa5252", 168 | "width": 224.00004069010433, 169 | "height": 616.419006347656, 170 | "seed": 2128839130, 171 | "groupIds": [ 172 | "qy-vZtgXiGxRU4ttygluD" 173 | ], 174 | "strokeSharpness": "sharp", 175 | "boundElementIds": [ 176 | "O2Tw6hZKMoBQI6O9ngXIf", 177 | "_sI6tb937H83snHAE_HTj" 178 | ] 179 | }, 180 | { 181 | "type": "text", 182 | "version": 398, 183 | "versionNonce": 596210758, 184 | "isDeleted": false, 185 | "id": "xf4ZEsuAv-wlQGmFQeWdQ", 186 | "fillStyle": "solid", 187 | "strokeWidth": 1, 188 | "strokeStyle": "solid", 189 | "roughness": 0, 190 | "opacity": 100, 191 | "angle": 0, 192 | "x": 881.2933301432055, 193 | "y": 638.7550377686188, 194 | "strokeColor": "#000000", 195 | "backgroundColor": "#fa5252", 196 | "width": 147, 197 | "height": 91, 198 | "seed": 1060406682, 199 | "groupIds": [ 200 | "qy-vZtgXiGxRU4ttygluD" 201 | ], 202 | "strokeSharpness": "sharp", 203 | "boundElementIds": [], 204 | "fontSize": 36, 205 | "fontFamily": 1, 206 | "text": "Protocol\nFSM", 207 | "baseline": 78, 208 | "textAlign": "center", 209 | "verticalAlign": "top" 210 | }, 211 | { 212 | "type": "rectangle", 213 | "version": 1045, 214 | "versionNonce": 1261132570, 215 | "isDeleted": false, 216 | "id": "UzQrxe0SRWejyuMCK7lUM", 217 | "fillStyle": "hachure", 218 | "strokeWidth": 1, 219 | "strokeStyle": "solid", 220 | "roughness": 1, 221 | "opacity": 100, 222 | "angle": 0, 223 | "x": 1151.2342551963932, 224 | "y": 463.2894446576722, 225 | "strokeColor": "#000000", 226 | "backgroundColor": "#fa5252", 227 | "width": 142.00001525878903, 228 | "height": 76.00007820129399, 229 | "seed": 341331142, 230 | "groupIds": [ 231 | "L7CPiv-eEu84Z3UyYzecS" 232 | ], 233 | "strokeSharpness": "sharp", 234 | "boundElementIds": [ 235 | "O2Tw6hZKMoBQI6O9ngXIf" 236 | ] 237 | }, 238 | { 239 | "type": "rectangle", 240 | "version": 926, 241 | "versionNonce": 1411316614, 242 | "isDeleted": false, 243 | "id": "u8o7Da1oLQBPKUH3Qh82c", 244 | "fillStyle": "hachure", 245 | "strokeWidth": 1, 246 | "strokeStyle": "solid", 247 | "roughness": 1, 248 | "opacity": 100, 249 | "angle": 0, 250 | "x": 1294.7557684541014, 251 | "y": 462.66451236854846, 252 | "strokeColor": "#000000", 253 | "backgroundColor": "#12b886", 254 | "width": 160.50003814697263, 255 | "height": 77.00006294250493, 256 | "seed": 1437303450, 257 | "groupIds": [ 258 | "L7CPiv-eEu84Z3UyYzecS" 259 | ], 260 | "strokeSharpness": "sharp", 261 | "boundElementIds": [ 262 | "22nmIkn8bXnVTj9dggVnK" 263 | ] 264 | }, 265 | { 266 | "type": "text", 267 | "version": 497, 268 | "versionNonce": 1040508890, 269 | "isDeleted": false, 270 | "id": "D7jYqp4Tu23dGDskyKFSg", 271 | "fillStyle": "solid", 272 | "strokeWidth": 1, 273 | "strokeStyle": "solid", 274 | "roughness": 0, 275 | "opacity": 100, 276 | "angle": 0, 277 | "x": 1232.3936007323978, 278 | "y": 476.9882658163077, 279 | "strokeColor": "#000000", 280 | "backgroundColor": "#ced4da", 281 | "width": 151, 282 | "height": 46, 283 | "seed": 2130099462, 284 | "groupIds": [ 285 | "L7CPiv-eEu84Z3UyYzecS" 286 | ], 287 | "strokeSharpness": "sharp", 288 | "boundElementIds": [], 289 | "fontSize": 36, 290 | "fontFamily": 1, 291 | "text": "RX FIFO", 292 | "baseline": 32, 293 | "textAlign": "center", 294 | "verticalAlign": "top" 295 | }, 296 | { 297 | "type": "rectangle", 298 | "version": 971, 299 | "versionNonce": 1057069766, 300 | "isDeleted": false, 301 | "id": "QmEOMWq6QFm9JnUE8iivm", 302 | "fillStyle": "hachure", 303 | "strokeWidth": 1, 304 | "strokeStyle": "solid", 305 | "roughness": 1, 306 | "opacity": 100, 307 | "angle": 0, 308 | "x": 1151.1091331260807, 309 | "y": 771.4144637311589, 310 | "strokeColor": "#000000", 311 | "backgroundColor": "#fa5252", 312 | "width": 143.00003814697263, 313 | "height": 72.55267605028662, 314 | "seed": 1865049242, 315 | "groupIds": [ 316 | "l9hwDzBmIkZSjxmgbr3Dc" 317 | ], 318 | "strokeSharpness": "sharp", 319 | "boundElementIds": [ 320 | "_sI6tb937H83snHAE_HTj" 321 | ] 322 | }, 323 | { 324 | "type": "rectangle", 325 | "version": 860, 326 | "versionNonce": 114216346, 327 | "isDeleted": false, 328 | "id": "uJHU04kCtCv9t86XsZDd8", 329 | "fillStyle": "hachure", 330 | "strokeWidth": 1, 331 | "strokeStyle": "solid", 332 | "roughness": 1, 333 | "opacity": 100, 334 | "angle": 0, 335 | "x": 1294.755562460449, 336 | "y": 771.797706974058, 337 | "strokeColor": "#000000", 338 | "backgroundColor": "#fab005", 339 | "width": 157.99999999999991, 340 | "height": 73.50012588500978, 341 | "seed": 601429274, 342 | "groupIds": [ 343 | "l9hwDzBmIkZSjxmgbr3Dc" 344 | ], 345 | "strokeSharpness": "sharp", 346 | "boundElementIds": [ 347 | "_sI6tb937H83snHAE_HTj", 348 | "SnQ3Zze2AhJo3pTwHOTVK" 349 | ] 350 | }, 351 | { 352 | "type": "text", 353 | "version": 397, 354 | "versionNonce": 1255035398, 355 | "isDeleted": false, 356 | "id": "utS6_8aakMsHgCXv12YyR", 357 | "fillStyle": "solid", 358 | "strokeWidth": 1, 359 | "strokeStyle": "solid", 360 | "roughness": 0, 361 | "opacity": 100, 362 | "angle": 0, 363 | "x": 1232.2935335937261, 364 | "y": 786.3548607666659, 365 | "strokeColor": "#000000", 366 | "backgroundColor": "#ffffff", 367 | "width": 155, 368 | "height": 46, 369 | "seed": 1817741702, 370 | "groupIds": [ 371 | "l9hwDzBmIkZSjxmgbr3Dc" 372 | ], 373 | "strokeSharpness": "sharp", 374 | "boundElementIds": [], 375 | "fontSize": 36, 376 | "fontFamily": 1, 377 | "text": "TX FIFO", 378 | "baseline": 32, 379 | "textAlign": "center", 380 | "verticalAlign": "top" 381 | }, 382 | { 383 | "type": "arrow", 384 | "version": 1270, 385 | "versionNonce": 1262908186, 386 | "isDeleted": false, 387 | "id": "jnGQ_mi-WZlJhzp0d1-zM", 388 | "fillStyle": "hachure", 389 | "strokeWidth": 2, 390 | "strokeStyle": "solid", 391 | "roughness": 1, 392 | "opacity": 100, 393 | "angle": 3.141592653589793, 394 | "x": 807.9404665663146, 395 | "y": 775.0040780421399, 396 | "strokeColor": "#000000", 397 | "backgroundColor": "#fa5252", 398 | "width": 331.8297392135281, 399 | "height": 0.07980988986003013, 400 | "seed": 1370828762, 401 | "groupIds": [ 402 | "n81ngRDIl_88W9JmYOFEP" 403 | ], 404 | "strokeSharpness": "round", 405 | "boundElementIds": [], 406 | "startBinding": { 407 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 408 | "focus": 0.35949636161387244, 409 | "gap": 11.062950166982773 410 | }, 411 | "endBinding": { 412 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 413 | "focus": -0.30071709826209386, 414 | "gap": 23.73421995244871 415 | }, 416 | "lastCommittedPoint": null, 417 | "startArrowhead": null, 418 | "endArrowhead": "arrow", 419 | "points": [ 420 | [ 421 | 0, 422 | 0 423 | ], 424 | [ 425 | -331.8297392135281, 426 | 0.07980988986003013 427 | ] 428 | ] 429 | }, 430 | { 431 | "type": "text", 432 | "version": 856, 433 | "versionNonce": 42082630, 434 | "isDeleted": false, 435 | "id": "31eCOXeSlHKk1Fufgn5uz", 436 | "fillStyle": "solid", 437 | "strokeWidth": 2, 438 | "strokeStyle": "solid", 439 | "roughness": 0, 440 | "opacity": 100, 441 | "angle": 0, 442 | "x": 582.4638904359917, 443 | "y": 780.4328118346389, 444 | "strokeColor": "#000000", 445 | "backgroundColor": "#ffffff", 446 | "width": 103, 447 | "height": 46, 448 | "seed": 861524678, 449 | "groupIds": [ 450 | "n81ngRDIl_88W9JmYOFEP" 451 | ], 452 | "strokeSharpness": "sharp", 453 | "boundElementIds": [], 454 | "fontSize": 36, 455 | "fontFamily": 1, 456 | "text": "TXE#", 457 | "baseline": 32, 458 | "textAlign": "center", 459 | "verticalAlign": "top" 460 | }, 461 | { 462 | "type": "arrow", 463 | "version": 1375, 464 | "versionNonce": 989659098, 465 | "isDeleted": false, 466 | "id": "wvfOQvIfweZdtTOb_LGDK", 467 | "fillStyle": "hachure", 468 | "strokeWidth": 2, 469 | "strokeStyle": "solid", 470 | "roughness": 1, 471 | "opacity": 100, 472 | "angle": 3.141592653589793, 473 | "x": 809.7921337820563, 474 | "y": 701.2647294896716, 475 | "strokeColor": "#000000", 476 | "backgroundColor": "#fa5252", 477 | "width": 332.3388273248528, 478 | "height": 0.04434017077471708, 479 | "seed": 100445254, 480 | "groupIds": [ 481 | "lQC3ihSS08kIOVcvIj7ix" 482 | ], 483 | "strokeSharpness": "round", 484 | "boundElementIds": [], 485 | "startBinding": { 486 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 487 | "focus": 0.19693812865021576, 488 | "gap": 12.91461738272426 489 | }, 490 | "endBinding": { 491 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 492 | "focus": -0.09345840190519486, 493 | "gap": 21.88255273670711 494 | }, 495 | "lastCommittedPoint": null, 496 | "startArrowhead": null, 497 | "endArrowhead": "arrow", 498 | "points": [ 499 | [ 500 | 0, 501 | 0 502 | ], 503 | [ 504 | -332.3388273248528, 505 | 0.04434017077471708 506 | ] 507 | ] 508 | }, 509 | { 510 | "type": "text", 511 | "version": 928, 512 | "versionNonce": 158992518, 513 | "isDeleted": false, 514 | "id": "JCjmb3s32Guizmt-4iCu0", 515 | "fillStyle": "solid", 516 | "strokeWidth": 2, 517 | "strokeStyle": "solid", 518 | "roughness": 0, 519 | "opacity": 100, 520 | "angle": 0, 521 | "x": 587.8064695404086, 522 | "y": 706.7104288804504, 523 | "strokeColor": "#000000", 524 | "backgroundColor": "#ffffff", 525 | "width": 95, 526 | "height": 46, 527 | "seed": 1722262298, 528 | "groupIds": [ 529 | "lQC3ihSS08kIOVcvIj7ix" 530 | ], 531 | "strokeSharpness": "sharp", 532 | "boundElementIds": [], 533 | "fontSize": 36, 534 | "fontFamily": 1, 535 | "text": "RXF#", 536 | "baseline": 32, 537 | "textAlign": "center", 538 | "verticalAlign": "top" 539 | }, 540 | { 541 | "type": "arrow", 542 | "version": 1321, 543 | "versionNonce": 902034586, 544 | "isDeleted": false, 545 | "id": "SsAotnp5A5Qt78tZjmwzO", 546 | "fillStyle": "hachure", 547 | "strokeWidth": 2, 548 | "strokeStyle": "solid", 549 | "roughness": 1, 550 | "opacity": 100, 551 | "angle": 0, 552 | "x": 813.6937197785662, 553 | "y": 588.4280006839497, 554 | "strokeColor": "#000000", 555 | "backgroundColor": "#fa5252", 556 | "width": 330.13333367920313, 557 | "height": 0.826426221031852, 558 | "seed": 1638808730, 559 | "groupIds": [ 560 | "8yIHcL9-qVWIwmUk1kAlq" 561 | ], 562 | "strokeSharpness": "round", 563 | "boundElementIds": [], 564 | "startBinding": { 565 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 566 | "focus": 0.22463857437834717, 567 | "gap": 17.980966740197005 568 | }, 569 | "endBinding": { 570 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 571 | "focus": -0.05255336813451538, 572 | "gap": 16.816203379234366 573 | }, 574 | "lastCommittedPoint": null, 575 | "startArrowhead": null, 576 | "endArrowhead": "arrow", 577 | "points": [ 578 | [ 579 | 0, 580 | 0 581 | ], 582 | [ 583 | -330.13333367920313, 584 | 0.826426221031852 585 | ] 586 | ] 587 | }, 588 | { 589 | "type": "text", 590 | "version": 842, 591 | "versionNonce": 259196870, 592 | "isDeleted": false, 593 | "id": "l3St-H95jGGVbi5Wcri_a", 594 | "fillStyle": "solid", 595 | "strokeWidth": 2, 596 | "strokeStyle": "solid", 597 | "roughness": 0, 598 | "opacity": 100, 599 | "angle": 0, 600 | "x": 601.4135491825683, 601 | "y": 594.7460777713543, 602 | "strokeColor": "#000000", 603 | "backgroundColor": "#ffffff", 604 | "width": 80, 605 | "height": 46, 606 | "seed": 81064454, 607 | "groupIds": [ 608 | "8yIHcL9-qVWIwmUk1kAlq" 609 | ], 610 | "strokeSharpness": "sharp", 611 | "boundElementIds": [], 612 | "fontSize": 36, 613 | "fontFamily": 1, 614 | "text": "WR#", 615 | "baseline": 32, 616 | "textAlign": "center", 617 | "verticalAlign": "top" 618 | }, 619 | { 620 | "type": "arrow", 621 | "version": 1429, 622 | "versionNonce": 1296978266, 623 | "isDeleted": false, 624 | "id": "Zk6JiCp0bXz_v2kQA1BPx", 625 | "fillStyle": "hachure", 626 | "strokeWidth": 2, 627 | "strokeStyle": "solid", 628 | "roughness": 1, 629 | "opacity": 100, 630 | "angle": 0, 631 | "x": 809.0865791012504, 632 | "y": 514.6423114662464, 633 | "strokeColor": "#000000", 634 | "backgroundColor": "#fa5252", 635 | "width": 330.13333367920313, 636 | "height": 0.7905728403122794, 637 | "seed": 900592026, 638 | "groupIds": [ 639 | "bVz0yeaiFNBlhTjXMD9mx" 640 | ], 641 | "strokeSharpness": "round", 642 | "boundElementIds": [], 643 | "startBinding": { 644 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 645 | "focus": 0.43252460891272143, 646 | "gap": 22.588107417512788 647 | }, 648 | "endBinding": { 649 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 650 | "focus": -0.21560363828355233, 651 | "gap": 12.209062701918583 652 | }, 653 | "lastCommittedPoint": null, 654 | "startArrowhead": null, 655 | "endArrowhead": "arrow", 656 | "points": [ 657 | [ 658 | 0, 659 | 0 660 | ], 661 | [ 662 | -330.13333367920313, 663 | 0.7905728403122794 664 | ] 665 | ] 666 | }, 667 | { 668 | "type": "text", 669 | "version": 950, 670 | "versionNonce": 1149902598, 671 | "isDeleted": false, 672 | "id": "ftnhffHuLzoQ3OFhfLv--", 673 | "fillStyle": "solid", 674 | "strokeWidth": 2, 675 | "strokeStyle": "solid", 676 | "roughness": 0, 677 | "opacity": 100, 678 | "angle": 0, 679 | "x": 596.3064085052525, 680 | "y": 520.960388553651, 681 | "strokeColor": "#000000", 682 | "backgroundColor": "#ffffff", 683 | "width": 81, 684 | "height": 46, 685 | "seed": 966200582, 686 | "groupIds": [ 687 | "bVz0yeaiFNBlhTjXMD9mx" 688 | ], 689 | "strokeSharpness": "sharp", 690 | "boundElementIds": [], 691 | "fontSize": 36, 692 | "fontFamily": 1, 693 | "text": "RD#", 694 | "baseline": 32, 695 | "textAlign": "center", 696 | "verticalAlign": "top" 697 | }, 698 | { 699 | "type": "arrow", 700 | "version": 1511, 701 | "versionNonce": 375296410, 702 | "isDeleted": false, 703 | "id": "gP2xgaTLVYXty168llEyj", 704 | "fillStyle": "hachure", 705 | "strokeWidth": 2, 706 | "strokeStyle": "dashed", 707 | "roughness": 1, 708 | "opacity": 100, 709 | "angle": 3.141592653589793, 710 | "x": 801.1532042749659, 711 | "y": 940.1689380001285, 712 | "strokeColor": "#000000", 713 | "backgroundColor": "#fa5252", 714 | "width": 306.40682336356576, 715 | "height": 0.3676845674326614, 716 | "seed": 858896774, 717 | "groupIds": [ 718 | "arcdJQNq8QL3lkhjEKzz4" 719 | ], 720 | "strokeSharpness": "round", 721 | "boundElementIds": [], 722 | "startBinding": { 723 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 724 | "focus": 0.7203069199833835, 725 | "gap": 28.275728565738746 726 | }, 727 | "endBinding": { 728 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 729 | "focus": -0.7645003053584241, 730 | "gap": 30.52148224379698 731 | }, 732 | "lastCommittedPoint": null, 733 | "startArrowhead": null, 734 | "endArrowhead": "arrow", 735 | "points": [ 736 | [ 737 | 0, 738 | 0 739 | ], 740 | [ 741 | -306.40682336356576, 742 | 0.3676845674326614 743 | ] 744 | ] 745 | }, 746 | { 747 | "type": "text", 748 | "version": 967, 749 | "versionNonce": 1295797830, 750 | "isDeleted": false, 751 | "id": "vTu7wpbJo7ESQxL-5P3kk", 752 | "fillStyle": "solid", 753 | "strokeWidth": 2, 754 | "strokeStyle": "dashed", 755 | "roughness": 0, 756 | "opacity": 100, 757 | "angle": 0, 758 | "x": 595.0995033045015, 759 | "y": 947.0397602481855, 760 | "strokeColor": "#000000", 761 | "backgroundColor": "#ffffff", 762 | "width": 67, 763 | "height": 46, 764 | "seed": 486869466, 765 | "groupIds": [ 766 | "arcdJQNq8QL3lkhjEKzz4" 767 | ], 768 | "strokeSharpness": "sharp", 769 | "boundElementIds": [], 770 | "fontSize": 36, 771 | "fontFamily": 1, 772 | "text": "CLK", 773 | "baseline": 32, 774 | "textAlign": "center", 775 | "verticalAlign": "top" 776 | }, 777 | { 778 | "type": "arrow", 779 | "version": 1586, 780 | "versionNonce": 1519210074, 781 | "isDeleted": false, 782 | "id": "FnPze7wfT9RiazG_eW9o0", 783 | "fillStyle": "hachure", 784 | "strokeWidth": 2, 785 | "strokeStyle": "dashed", 786 | "roughness": 1, 787 | "opacity": 100, 788 | "angle": 0, 789 | "x": 802.865100338552, 790 | "y": 870.3917536306747, 791 | "strokeColor": "#000000", 792 | "backgroundColor": "#fa5252", 793 | "width": 306.1332929890988, 794 | "height": 0.17765619531348875, 795 | "seed": 1762433222, 796 | "groupIds": [ 797 | "BNAM6fhdFaC3dUbgfflhY" 798 | ], 799 | "strokeSharpness": "round", 800 | "boundElementIds": [], 801 | "startBinding": { 802 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 803 | "focus": -0.5704487687729365, 804 | "gap": 28.809586180211227 805 | }, 806 | "endBinding": { 807 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 808 | "focus": 0.5681073961723323, 809 | "gap": 29.9876246293245 810 | }, 811 | "lastCommittedPoint": null, 812 | "startArrowhead": null, 813 | "endArrowhead": "arrow", 814 | "points": [ 815 | [ 816 | 0, 817 | 0 818 | ], 819 | [ 820 | -306.1332929890988, 821 | 0.17765619531348875 822 | ] 823 | ] 824 | }, 825 | { 826 | "type": "text", 827 | "version": 1036, 828 | "versionNonce": 912692614, 829 | "isDeleted": false, 830 | "id": "k5SK91kO9beH2uvxW64_K", 831 | "fillStyle": "solid", 832 | "strokeWidth": 2, 833 | "strokeStyle": "dashed", 834 | "roughness": 0, 835 | "opacity": 100, 836 | "angle": 0, 837 | "x": 589.9420900383128, 838 | "y": 876.9491018181163, 839 | "strokeColor": "#000000", 840 | "backgroundColor": "#ffffff", 841 | "width": 79, 842 | "height": 46, 843 | "seed": 1849343642, 844 | "groupIds": [ 845 | "BNAM6fhdFaC3dUbgfflhY" 846 | ], 847 | "strokeSharpness": "sharp", 848 | "boundElementIds": [], 849 | "fontSize": 36, 850 | "fontFamily": 1, 851 | "text": "OE#", 852 | "baseline": 32, 853 | "textAlign": "center", 854 | "verticalAlign": "top" 855 | }, 856 | { 857 | "type": "arrow", 858 | "version": 1509, 859 | "versionNonce": 1693390362, 860 | "isDeleted": false, 861 | "id": "VCcIGab_Pe9udIxhhd3I9", 862 | "fillStyle": "hachure", 863 | "strokeWidth": 2, 864 | "strokeStyle": "solid", 865 | "roughness": 1, 866 | "opacity": 100, 867 | "angle": 0, 868 | "x": 814.3173649322405, 869 | "y": 421.13407733739894, 870 | "strokeColor": "#000000", 871 | "backgroundColor": "#fa5252", 872 | "width": 330.13333367920325, 873 | "height": 0.7355115986962346, 874 | "seed": 47749574, 875 | "groupIds": [ 876 | "Zw54skganrFNt2ZxMfwus", 877 | "bV611B7hhLstI-2_bwufZ" 878 | ], 879 | "strokeSharpness": "round", 880 | "boundElementIds": [], 881 | "startBinding": { 882 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 883 | "focus": 0.6959775385452326, 884 | "gap": 17.357321586522858 885 | }, 886 | "endBinding": { 887 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 888 | "focus": -0.4222364284291233, 889 | "gap": 17.439848532908513 890 | }, 891 | "lastCommittedPoint": null, 892 | "startArrowhead": null, 893 | "endArrowhead": "arrow", 894 | "points": [ 895 | [ 896 | 0, 897 | 0 898 | ], 899 | [ 900 | -330.13333367920325, 901 | 0.7355115986962346 902 | ] 903 | ] 904 | }, 905 | { 906 | "type": "text", 907 | "version": 1033, 908 | "versionNonce": 1766192326, 909 | "isDeleted": false, 910 | "id": "MvQ4OEOqeW3pb6Sx4KUWe", 911 | "fillStyle": "solid", 912 | "strokeWidth": 2, 913 | "strokeStyle": "solid", 914 | "roughness": 0, 915 | "opacity": 100, 916 | "angle": 0, 917 | "x": 590.0371943362427, 918 | "y": 427.45215442480355, 919 | "strokeColor": "#000000", 920 | "backgroundColor": "#ffffff", 921 | "width": 104, 922 | "height": 46, 923 | "seed": 669732250, 924 | "groupIds": [ 925 | "Zw54skganrFNt2ZxMfwus", 926 | "bV611B7hhLstI-2_bwufZ" 927 | ], 928 | "strokeSharpness": "sharp", 929 | "boundElementIds": [], 930 | "fontSize": 36, 931 | "fontFamily": 1, 932 | "text": "DATA", 933 | "baseline": 32, 934 | "textAlign": "center", 935 | "verticalAlign": "top" 936 | }, 937 | { 938 | "type": "line", 939 | "version": 109, 940 | "versionNonce": 768942746, 941 | "isDeleted": false, 942 | "id": "L76ciKGZzGzXi4jONRWx_", 943 | "fillStyle": "solid", 944 | "strokeWidth": 2, 945 | "strokeStyle": "solid", 946 | "roughness": 1, 947 | "opacity": 100, 948 | "angle": 0, 949 | "x": 656.4375474981164, 950 | "y": 405.27112511551917, 951 | "strokeColor": "#000000", 952 | "backgroundColor": "#ced4da", 953 | "width": 14.15386493389417, 954 | "height": 28.923081618088986, 955 | "seed": 431764570, 956 | "groupIds": [ 957 | "bV611B7hhLstI-2_bwufZ" 958 | ], 959 | "strokeSharpness": "round", 960 | "boundElementIds": null, 961 | "startBinding": null, 962 | "endBinding": null, 963 | "lastCommittedPoint": null, 964 | "startArrowhead": null, 965 | "endArrowhead": null, 966 | "points": [ 967 | [ 968 | 0, 969 | 0 970 | ], 971 | [ 972 | -14.15386493389417, 973 | 28.923081618088986 974 | ] 975 | ] 976 | }, 977 | { 978 | "type": "line", 979 | "version": 114, 980 | "versionNonce": 527920134, 981 | "isDeleted": false, 982 | "id": "ImXBkSpUNGfdQ6u2QOJR-", 983 | "fillStyle": "solid", 984 | "strokeWidth": 2, 985 | "strokeStyle": "solid", 986 | "roughness": 1, 987 | "opacity": 100, 988 | "angle": 0, 989 | "x": 813.9759996465539, 990 | "y": 422.5018896512764, 991 | "strokeColor": "#000000", 992 | "backgroundColor": "#ced4da", 993 | "width": 27.800258044781685, 994 | "height": 12.823359787177537, 995 | "seed": 809246150, 996 | "groupIds": [ 997 | "bV611B7hhLstI-2_bwufZ" 998 | ], 999 | "strokeSharpness": "round", 1000 | "boundElementIds": null, 1001 | "startBinding": null, 1002 | "endBinding": null, 1003 | "lastCommittedPoint": null, 1004 | "startArrowhead": null, 1005 | "endArrowhead": null, 1006 | "points": [ 1007 | [ 1008 | 0, 1009 | 0 1010 | ], 1011 | [ 1012 | -27.800258044781685, 1013 | -12.823359787177537 1014 | ] 1015 | ] 1016 | }, 1017 | { 1018 | "type": "line", 1019 | "version": 105, 1020 | "versionNonce": 1191821146, 1021 | "isDeleted": false, 1022 | "id": "nTgM2MlDoROvNYm8ZKO4k", 1023 | "fillStyle": "solid", 1024 | "strokeWidth": 2, 1025 | "strokeStyle": "solid", 1026 | "roughness": 1, 1027 | "opacity": 100, 1028 | "angle": 0, 1029 | "x": 784.6424528023718, 1030 | "y": 434.3758178417575, 1031 | "strokeColor": "#000000", 1032 | "backgroundColor": "#ced4da", 1033 | "width": 28.10279639346095, 1034 | "height": 12.489303415841732, 1035 | "seed": 504034822, 1036 | "groupIds": [ 1037 | "bV611B7hhLstI-2_bwufZ" 1038 | ], 1039 | "strokeSharpness": "round", 1040 | "boundElementIds": null, 1041 | "startBinding": null, 1042 | "endBinding": null, 1043 | "lastCommittedPoint": null, 1044 | "startArrowhead": null, 1045 | "endArrowhead": null, 1046 | "points": [ 1047 | [ 1048 | 0, 1049 | 0 1050 | ], 1051 | [ 1052 | 28.10279639346095, 1053 | -12.489303415841732 1054 | ] 1055 | ] 1056 | }, 1057 | { 1058 | "type": "text", 1059 | "version": 217, 1060 | "versionNonce": 1459092294, 1061 | "isDeleted": false, 1062 | "id": "27hJ-VJn7sTNRmiQwSP2d", 1063 | "fillStyle": "solid", 1064 | "strokeWidth": 1, 1065 | "strokeStyle": "solid", 1066 | "roughness": 0, 1067 | "opacity": 100, 1068 | "angle": 0, 1069 | "x": 795.7289476669596, 1070 | "y": 188.77677366212407, 1071 | "strokeColor": "#000000", 1072 | "backgroundColor": "#ffffff", 1073 | "width": 171.22604834515118, 1074 | "height": 81.19998168945312, 1075 | "seed": 664390554, 1076 | "groupIds": [], 1077 | "strokeSharpness": "sharp", 1078 | "boundElementIds": [], 1079 | "fontSize": 63.547811756963334, 1080 | "fontFamily": 1, 1081 | "text": "FPGA", 1082 | "baseline": 57.199981689453125, 1083 | "textAlign": "center", 1084 | "verticalAlign": "top" 1085 | }, 1086 | { 1087 | "type": "arrow", 1088 | "version": 1093, 1089 | "versionNonce": 822920218, 1090 | "isDeleted": false, 1091 | "id": "O2Tw6hZKMoBQI6O9ngXIf", 1092 | "fillStyle": "hachure", 1093 | "strokeWidth": 1, 1094 | "strokeStyle": "solid", 1095 | "roughness": 1, 1096 | "opacity": 100, 1097 | "angle": 3.141592653589793, 1098 | "x": 1149.024706361118, 1099 | "y": 500.33416830900086, 1100 | "strokeColor": "#000000", 1101 | "backgroundColor": "#fa5252", 1102 | "width": 76.70260137269156, 1103 | "height": 0.12967685208059265, 1104 | "seed": 1731109830, 1105 | "groupIds": [], 1106 | "strokeSharpness": "round", 1107 | "boundElementIds": [], 1108 | "startBinding": { 1109 | "elementId": "zStYYw9UvMUXifFw58G1D", 1110 | "focus": -0.6237619527900352, 1111 | "gap": 2.5522172383271595 1112 | }, 1113 | "endBinding": { 1114 | "elementId": "UzQrxe0SRWejyuMCK7lUM", 1115 | "focus": 0.07108824891302296, 1116 | "gap": 1.4479211045570537 1117 | }, 1118 | "lastCommittedPoint": null, 1119 | "startArrowhead": null, 1120 | "endArrowhead": "arrow", 1121 | "points": [ 1122 | [ 1123 | 0, 1124 | 0 1125 | ], 1126 | [ 1127 | -76.70260137269156, 1128 | 0.12967685208059265 1129 | ] 1130 | ] 1131 | }, 1132 | { 1133 | "type": "arrow", 1134 | "version": 1173, 1135 | "versionNonce": 1176806022, 1136 | "isDeleted": false, 1137 | "id": "_sI6tb937H83snHAE_HTj", 1138 | "fillStyle": "hachure", 1139 | "strokeWidth": 1, 1140 | "strokeStyle": "solid", 1141 | "roughness": 1, 1142 | "opacity": 100, 1143 | "angle": 0, 1144 | "x": 1148.4654629757263, 1145 | "y": 808.9000417272772, 1146 | "strokeColor": "#000000", 1147 | "backgroundColor": "#fa5252", 1148 | "width": 76.85387429756042, 1149 | "height": 0.648043618056704, 1150 | "seed": 135915226, 1151 | "groupIds": [], 1152 | "strokeSharpness": "round", 1153 | "boundElementIds": [], 1154 | "startBinding": { 1155 | "elementId": "QmEOMWq6QFm9JnUE8iivm", 1156 | "gap": 2.64367015035436, 1157 | "focus": -0.015836557712439154 1158 | }, 1159 | "endBinding": { 1160 | "elementId": "zStYYw9UvMUXifFw58G1D", 1161 | "gap": 1.0800731973484907, 1162 | "focus": 0.38527315318545946 1163 | }, 1164 | "lastCommittedPoint": null, 1165 | "startArrowhead": null, 1166 | "endArrowhead": "arrow", 1167 | "points": [ 1168 | [ 1169 | 0, 1170 | 0 1171 | ], 1172 | [ 1173 | -76.85387429756042, 1174 | 0.648043618056704 1175 | ] 1176 | ] 1177 | }, 1178 | { 1179 | "type": "arrow", 1180 | "version": 1123, 1181 | "versionNonce": 366031066, 1182 | "isDeleted": false, 1183 | "id": "22nmIkn8bXnVTj9dggVnK", 1184 | "fillStyle": "hachure", 1185 | "strokeWidth": 1, 1186 | "strokeStyle": "solid", 1187 | "roughness": 1, 1188 | "opacity": 100, 1189 | "angle": 3.141592653589793, 1190 | "x": 1590.412615463578, 1191 | "y": 498.56689034655557, 1192 | "strokeColor": "#000000", 1193 | "backgroundColor": "#fa5252", 1194 | "width": 132.32826969537246, 1195 | "height": 1.6205539191914557e-14, 1196 | "seed": 1291879706, 1197 | "groupIds": [ 1198 | "jJIZbsyr5N975WT05rs7w" 1199 | ], 1200 | "strokeSharpness": "round", 1201 | "boundElementIds": [], 1202 | "startBinding": { 1203 | "elementId": "u8o7Da1oLQBPKUH3Qh82c", 1204 | "focus": -0.08437391353103262, 1205 | "gap": 2.459716328048671 1206 | }, 1207 | "endBinding": { 1208 | "elementId": "LCnY-i8GZo5L0edBec2QR", 1209 | "focus": -0.07909222436758276, 1210 | "gap": 16.519028157056482 1211 | }, 1212 | "lastCommittedPoint": null, 1213 | "startArrowhead": null, 1214 | "endArrowhead": "arrow", 1215 | "points": [ 1216 | [ 1217 | 0, 1218 | 0 1219 | ], 1220 | [ 1221 | -132.32826969537246, 1222 | -1.6205539191914557e-14 1223 | ] 1224 | ] 1225 | }, 1226 | { 1227 | "type": "line", 1228 | "version": 156, 1229 | "versionNonce": 1337614790, 1230 | "isDeleted": false, 1231 | "id": "NWBMQZH0CtEhC2Dr5x0jB", 1232 | "fillStyle": "hachure", 1233 | "strokeWidth": 1, 1234 | "strokeStyle": "solid", 1235 | "roughness": 1, 1236 | "opacity": 100, 1237 | "angle": 0, 1238 | "x": 1458.7109211031036, 1239 | "y": 498.1768006193188, 1240 | "strokeColor": "#000000", 1241 | "backgroundColor": "transparent", 1242 | "width": 25.500005086262945, 1243 | "height": 13.166669209798215, 1244 | "seed": 803709338, 1245 | "groupIds": [ 1246 | "jJIZbsyr5N975WT05rs7w" 1247 | ], 1248 | "strokeSharpness": "round", 1249 | "boundElementIds": null, 1250 | "startBinding": null, 1251 | "endBinding": null, 1252 | "lastCommittedPoint": null, 1253 | "startArrowhead": null, 1254 | "endArrowhead": null, 1255 | "points": [ 1256 | [ 1257 | 0, 1258 | 0 1259 | ], 1260 | [ 1261 | 25.500005086262945, 1262 | -13.166669209798215 1263 | ] 1264 | ] 1265 | }, 1266 | { 1267 | "type": "line", 1268 | "version": 186, 1269 | "versionNonce": 1760167322, 1270 | "isDeleted": false, 1271 | "id": "HwgNuEzLK1R_LlZ5ahKWQ", 1272 | "fillStyle": "hachure", 1273 | "strokeWidth": 1, 1274 | "strokeStyle": "solid", 1275 | "roughness": 1, 1276 | "opacity": 100, 1277 | "angle": 0.7853981633974474, 1278 | "x": 1459.1275877697701, 1279 | "y": 509.8851364957835, 1280 | "strokeColor": "#000000", 1281 | "backgroundColor": "transparent", 1282 | "width": 25.500005086262945, 1283 | "height": 13.166669209798215, 1284 | "seed": 1110689734, 1285 | "groupIds": [ 1286 | "jJIZbsyr5N975WT05rs7w" 1287 | ], 1288 | "strokeSharpness": "round", 1289 | "boundElementIds": [], 1290 | "startBinding": null, 1291 | "endBinding": null, 1292 | "lastCommittedPoint": null, 1293 | "startArrowhead": null, 1294 | "endArrowhead": null, 1295 | "points": [ 1296 | [ 1297 | 0, 1298 | 0 1299 | ], 1300 | [ 1301 | 25.500005086262945, 1302 | -13.166669209798215 1303 | ] 1304 | ] 1305 | }, 1306 | { 1307 | "type": "arrow", 1308 | "version": 1205, 1309 | "versionNonce": 1993750790, 1310 | "isDeleted": false, 1311 | "id": "SnQ3Zze2AhJo3pTwHOTVK", 1312 | "fillStyle": "hachure", 1313 | "strokeWidth": 1, 1314 | "strokeStyle": "solid", 1315 | "roughness": 1, 1316 | "opacity": 100, 1317 | "angle": 3.141592653589793, 1318 | "x": 1591.244611232976, 1319 | "y": 809.392317214932, 1320 | "strokeColor": "#000000", 1321 | "backgroundColor": "#fa5252", 1322 | "width": 133.86448401483085, 1323 | "height": 0.12998501700951692, 1324 | "seed": 382193286, 1325 | "groupIds": [ 1326 | "mpYfuNZd0rszHErBxFfmu" 1327 | ], 1328 | "strokeSharpness": "round", 1329 | "boundElementIds": [], 1330 | "startBinding": { 1331 | "elementId": "uJHU04kCtCv9t86XsZDd8", 1332 | "focus": 0.021694430992592755, 1333 | "gap": 6.16077907715453 1334 | }, 1335 | "endBinding": { 1336 | "elementId": "9AKf4BAgBdNHIM2fl2KBn", 1337 | "focus": -0.03357530196931693, 1338 | "gap": 16.318270583731874 1339 | }, 1340 | "lastCommittedPoint": null, 1341 | "startArrowhead": null, 1342 | "endArrowhead": "arrow", 1343 | "points": [ 1344 | [ 1345 | 0, 1346 | 0 1347 | ], 1348 | [ 1349 | -133.86448401483085, 1350 | 0.12998501700951692 1351 | ] 1352 | ] 1353 | }, 1354 | { 1355 | "type": "line", 1356 | "version": 234, 1357 | "versionNonce": 864698970, 1358 | "isDeleted": false, 1359 | "id": "vblhvQH6dyMonaxM2ROuK", 1360 | "fillStyle": "hachure", 1361 | "strokeWidth": 1, 1362 | "strokeStyle": "solid", 1363 | "roughness": 1, 1364 | "opacity": 100, 1365 | "angle": 0, 1366 | "x": 1458.0067025530432, 1367 | "y": 809.1840828713232, 1368 | "strokeColor": "#000000", 1369 | "backgroundColor": "transparent", 1370 | "width": 25.500005086262945, 1371 | "height": 13.166669209798215, 1372 | "seed": 1967572186, 1373 | "groupIds": [ 1374 | "mpYfuNZd0rszHErBxFfmu" 1375 | ], 1376 | "strokeSharpness": "round", 1377 | "boundElementIds": [], 1378 | "startBinding": null, 1379 | "endBinding": null, 1380 | "lastCommittedPoint": null, 1381 | "startArrowhead": null, 1382 | "endArrowhead": null, 1383 | "points": [ 1384 | [ 1385 | 0, 1386 | 0 1387 | ], 1388 | [ 1389 | 25.500005086262945, 1390 | -13.166669209798215 1391 | ] 1392 | ] 1393 | }, 1394 | { 1395 | "type": "line", 1396 | "version": 264, 1397 | "versionNonce": 1391798342, 1398 | "isDeleted": false, 1399 | "id": "6pQoisncSgza6OIjK2umA", 1400 | "fillStyle": "hachure", 1401 | "strokeWidth": 1, 1402 | "strokeStyle": "solid", 1403 | "roughness": 1, 1404 | "opacity": 100, 1405 | "angle": 0.7853981633974474, 1406 | "x": 1458.4233692197097, 1407 | "y": 820.8924187477879, 1408 | "strokeColor": "#000000", 1409 | "backgroundColor": "transparent", 1410 | "width": 25.500005086262945, 1411 | "height": 13.166669209798215, 1412 | "seed": 2065173958, 1413 | "groupIds": [ 1414 | "mpYfuNZd0rszHErBxFfmu" 1415 | ], 1416 | "strokeSharpness": "round", 1417 | "boundElementIds": [], 1418 | "startBinding": null, 1419 | "endBinding": null, 1420 | "lastCommittedPoint": null, 1421 | "startArrowhead": null, 1422 | "endArrowhead": null, 1423 | "points": [ 1424 | [ 1425 | 0, 1426 | 0 1427 | ], 1428 | [ 1429 | 25.500005086262945, 1430 | -13.166669209798215 1431 | ] 1432 | ] 1433 | }, 1434 | { 1435 | "type": "text", 1436 | "version": 432, 1437 | "versionNonce": 1375980422, 1438 | "isDeleted": false, 1439 | "id": "xKysxks687R1Q-1J3bDfF", 1440 | "fillStyle": "solid", 1441 | "strokeWidth": 1, 1442 | "strokeStyle": "solid", 1443 | "roughness": 0, 1444 | "opacity": 100, 1445 | "angle": 0, 1446 | "x": 192.47755064005048, 1447 | "y": 585.610051046565, 1448 | "strokeColor": "#000000", 1449 | "backgroundColor": "#ffffff", 1450 | "width": 172, 1451 | "height": 160, 1452 | "seed": 1156798214, 1453 | "groupIds": [], 1454 | "strokeSharpness": "sharp", 1455 | "boundElementIds": [], 1456 | "fontSize": 63.547811756963334, 1457 | "fontFamily": 1, 1458 | "text": "FTDI\nchip", 1459 | "baseline": 136, 1460 | "textAlign": "center", 1461 | "verticalAlign": "top" 1462 | }, 1463 | { 1464 | "type": "rectangle", 1465 | "version": 1116, 1466 | "versionNonce": 982971526, 1467 | "isDeleted": false, 1468 | "id": "AB-sot-aDZvsKSOgzVbVR", 1469 | "fillStyle": "cross-hatch", 1470 | "strokeWidth": 1, 1471 | "strokeStyle": "solid", 1472 | "roughness": 2, 1473 | "opacity": 100, 1474 | "angle": 0, 1475 | "x": 99.07183130682654, 1476 | "y": -173.61697080656367, 1477 | "strokeColor": "#000000", 1478 | "backgroundColor": "#868e96", 1479 | "width": 826.8000183105468, 1480 | "height": 198.06676355997692, 1481 | "seed": 1629684250, 1482 | "groupIds": [], 1483 | "strokeSharpness": "sharp", 1484 | "boundElementIds": [ 1485 | "TOO-xDTa-XwgWWd5tjvEs" 1486 | ] 1487 | }, 1488 | { 1489 | "type": "arrow", 1490 | "version": 1178, 1491 | "versionNonce": 721572378, 1492 | "isDeleted": false, 1493 | "id": "TOO-xDTa-XwgWWd5tjvEs", 1494 | "fillStyle": "cross-hatch", 1495 | "strokeWidth": 2, 1496 | "strokeStyle": "solid", 1497 | "roughness": 1, 1498 | "opacity": 100, 1499 | "angle": 0, 1500 | "x": 278.6655104002121, 1501 | "y": 35.58274754282673, 1502 | "strokeColor": "#000000", 1503 | "backgroundColor": "transparent", 1504 | "width": 1.4290677614536662, 1505 | "height": 119.05156787205757, 1506 | "seed": 1309305050, 1507 | "groupIds": [ 1508 | "MnNvtiNAU_AzA1t-iaKcE" 1509 | ], 1510 | "strokeSharpness": "round", 1511 | "boundElementIds": [], 1512 | "startBinding": { 1513 | "elementId": "AB-sot-aDZvsKSOgzVbVR", 1514 | "focus": 0.5607578593690444, 1515 | "gap": 11.13295478941346 1516 | }, 1517 | "endBinding": { 1518 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 1519 | "focus": -0.10238247321832461, 1520 | "gap": 6.375775304532112 1521 | }, 1522 | "lastCommittedPoint": null, 1523 | "startArrowhead": null, 1524 | "endArrowhead": "arrow", 1525 | "points": [ 1526 | [ 1527 | 0, 1528 | 0 1529 | ], 1530 | [ 1531 | -1.4290677614536662, 1532 | 119.05156787205757 1533 | ] 1534 | ] 1535 | }, 1536 | { 1537 | "type": "line", 1538 | "version": 366, 1539 | "versionNonce": 830649030, 1540 | "isDeleted": false, 1541 | "id": "rsFthVPLF4C804tA021pP", 1542 | "fillStyle": "cross-hatch", 1543 | "strokeWidth": 2, 1544 | "strokeStyle": "solid", 1545 | "roughness": 1, 1546 | "opacity": 100, 1547 | "angle": 0, 1548 | "x": 279.3383812850757, 1549 | "y": 32.111794982995015, 1550 | "strokeColor": "#000000", 1551 | "backgroundColor": "transparent", 1552 | "width": 11.789454409950622, 1553 | "height": 24.842095626027913, 1554 | "seed": 893140378, 1555 | "groupIds": [ 1556 | "MnNvtiNAU_AzA1t-iaKcE" 1557 | ], 1558 | "strokeSharpness": "round", 1559 | "boundElementIds": null, 1560 | "startBinding": null, 1561 | "endBinding": null, 1562 | "lastCommittedPoint": null, 1563 | "startArrowhead": null, 1564 | "endArrowhead": null, 1565 | "points": [ 1566 | [ 1567 | 0, 1568 | 0 1569 | ], 1570 | [ 1571 | -11.789454409950622, 1572 | 24.842095626027913 1573 | ] 1574 | ] 1575 | }, 1576 | { 1577 | "type": "line", 1578 | "version": 375, 1579 | "versionNonce": 1679378906, 1580 | "isDeleted": false, 1581 | "id": "UbBjjddBlZF6CirD0fFMB", 1582 | "fillStyle": "cross-hatch", 1583 | "strokeWidth": 2, 1584 | "strokeStyle": "solid", 1585 | "roughness": 1, 1586 | "opacity": 100, 1587 | "angle": 5.497787143782138, 1588 | "x": 290.98140090236143, 1589 | "y": 34.32598784263586, 1590 | "strokeColor": "#000000", 1591 | "backgroundColor": "transparent", 1592 | "width": 11.789454409950622, 1593 | "height": 24.842095626027913, 1594 | "seed": 1108594438, 1595 | "groupIds": [ 1596 | "MnNvtiNAU_AzA1t-iaKcE" 1597 | ], 1598 | "strokeSharpness": "round", 1599 | "boundElementIds": [], 1600 | "startBinding": null, 1601 | "endBinding": null, 1602 | "lastCommittedPoint": null, 1603 | "startArrowhead": null, 1604 | "endArrowhead": null, 1605 | "points": [ 1606 | [ 1607 | 0, 1608 | 0 1609 | ], 1610 | [ 1611 | -11.789454409950622, 1612 | 24.842095626027913 1613 | ] 1614 | ] 1615 | }, 1616 | { 1617 | "type": "text", 1618 | "version": 455, 1619 | "versionNonce": 1414014682, 1620 | "isDeleted": false, 1621 | "id": "VbfkBQsHocf-kLmM3lohB", 1622 | "fillStyle": "solid", 1623 | "strokeWidth": 1, 1624 | "strokeStyle": "solid", 1625 | "roughness": 0, 1626 | "opacity": 100, 1627 | "angle": 0, 1628 | "x": 145.1430456815699, 1629 | "y": -156.9402723059136, 1630 | "strokeColor": "#000000", 1631 | "backgroundColor": "#ffffff", 1632 | "width": 734, 1633 | "height": 160, 1634 | "seed": 1951335066, 1635 | "groupIds": [], 1636 | "strokeSharpness": "sharp", 1637 | "boundElementIds": [ 1638 | "TOO-xDTa-XwgWWd5tjvEs" 1639 | ], 1640 | "fontSize": 63.547811756963334, 1641 | "fontFamily": 1, 1642 | "text": "USB Host\nWindows / Linux / OS X", 1643 | "baseline": 136, 1644 | "textAlign": "center", 1645 | "verticalAlign": "top" 1646 | }, 1647 | { 1648 | "type": "text", 1649 | "version": 124, 1650 | "versionNonce": 1446069594, 1651 | "isDeleted": false, 1652 | "id": "LCnY-i8GZo5L0edBec2QR", 1653 | "fillStyle": "hachure", 1654 | "strokeWidth": 2, 1655 | "strokeStyle": "solid", 1656 | "roughness": 1, 1657 | "opacity": 100, 1658 | "angle": 0, 1659 | "x": 1606.5628207815516, 1660 | "y": 448.8174493047784, 1661 | "strokeColor": "#000000", 1662 | "backgroundColor": "#fab005", 1663 | "width": 166, 1664 | "height": 91, 1665 | "seed": 366190554, 1666 | "groupIds": [], 1667 | "strokeSharpness": "sharp", 1668 | "boundElementIds": [ 1669 | "22nmIkn8bXnVTj9dggVnK" 1670 | ], 1671 | "fontSize": 36, 1672 | "fontFamily": 1, 1673 | "text": "raw FIFO\ninterface", 1674 | "baseline": 78, 1675 | "textAlign": "center", 1676 | "verticalAlign": "top" 1677 | }, 1678 | { 1679 | "type": "text", 1680 | "version": 167, 1681 | "versionNonce": 1416877382, 1682 | "isDeleted": false, 1683 | "id": "9AKf4BAgBdNHIM2fl2KBn", 1684 | "fillStyle": "hachure", 1685 | "strokeWidth": 2, 1686 | "strokeStyle": "solid", 1687 | "roughness": 1, 1688 | "opacity": 100, 1689 | "angle": 0, 1690 | "x": 1607.5628818167079, 1691 | "y": 762.3173653814385, 1692 | "strokeColor": "#000000", 1693 | "backgroundColor": "#fab005", 1694 | "width": 166, 1695 | "height": 91, 1696 | "seed": 1929647450, 1697 | "groupIds": [], 1698 | "strokeSharpness": "sharp", 1699 | "boundElementIds": [ 1700 | "22nmIkn8bXnVTj9dggVnK", 1701 | "SnQ3Zze2AhJo3pTwHOTVK" 1702 | ], 1703 | "fontSize": 36, 1704 | "fontFamily": 1, 1705 | "text": "raw FIFO\ninterface", 1706 | "baseline": 78, 1707 | "textAlign": "center", 1708 | "verticalAlign": "top" 1709 | }, 1710 | { 1711 | "type": "rectangle", 1712 | "version": 1255, 1713 | "versionNonce": 1237077382, 1714 | "isDeleted": false, 1715 | "id": "HtMyf02Qz5hp_Bfk-nDLw", 1716 | "fillStyle": "hachure", 1717 | "strokeWidth": 1, 1718 | "strokeStyle": "solid", 1719 | "roughness": 1, 1720 | "opacity": 100, 1721 | "angle": 0, 1722 | "x": 1119.6491791395406, 1723 | "y": -163.68267107011252, 1724 | "strokeColor": "#000000", 1725 | "backgroundColor": "#fa5252", 1726 | "width": 75.33334859212252, 1727 | "height": 76.00007820129399, 1728 | "seed": 1547303366, 1729 | "groupIds": [], 1730 | "strokeSharpness": "sharp", 1731 | "boundElementIds": [ 1732 | "O2Tw6hZKMoBQI6O9ngXIf" 1733 | ] 1734 | }, 1735 | { 1736 | "type": "text", 1737 | "version": 707, 1738 | "versionNonce": 2031990234, 1739 | "isDeleted": false, 1740 | "id": "aXnudsjyE8Vin_TAwKw0e", 1741 | "fillStyle": "solid", 1742 | "strokeWidth": 1, 1743 | "strokeStyle": "solid", 1744 | "roughness": 0, 1745 | "opacity": 100, 1746 | "angle": 0, 1747 | "x": 1218.0863160166912, 1748 | "y": -154.4283536956567, 1749 | "strokeColor": "#000000", 1750 | "backgroundColor": "#ced4da", 1751 | "width": 328, 1752 | "height": 46, 1753 | "seed": 2141376774, 1754 | "groupIds": [], 1755 | "strokeSharpness": "sharp", 1756 | "boundElementIds": [], 1757 | "fontSize": 36, 1758 | "fontFamily": 1, 1759 | "text": "FTDI clock domain", 1760 | "baseline": 32, 1761 | "textAlign": "center", 1762 | "verticalAlign": "top" 1763 | }, 1764 | { 1765 | "type": "rectangle", 1766 | "version": 1286, 1767 | "versionNonce": 2032446662, 1768 | "isDeleted": false, 1769 | "id": "2ipKx_E-vOLgC84N9Ak3G", 1770 | "fillStyle": "hachure", 1771 | "strokeWidth": 1, 1772 | "strokeStyle": "solid", 1773 | "roughness": 1, 1774 | "opacity": 100, 1775 | "angle": 0, 1776 | "x": 1124.6043102767237, 1777 | "y": -75.08537350761961, 1778 | "strokeColor": "#000000", 1779 | "backgroundColor": "#12b886", 1780 | "width": 75.33334859212252, 1781 | "height": 76.00007820129399, 1782 | "seed": 2033417542, 1783 | "groupIds": [], 1784 | "strokeSharpness": "sharp", 1785 | "boundElementIds": [ 1786 | "O2Tw6hZKMoBQI6O9ngXIf" 1787 | ] 1788 | }, 1789 | { 1790 | "type": "rectangle", 1791 | "version": 1322, 1792 | "versionNonce": 442362522, 1793 | "isDeleted": false, 1794 | "id": "1DEe3GktE5imDp2SdR27p", 1795 | "fillStyle": "hachure", 1796 | "strokeWidth": 1, 1797 | "strokeStyle": "solid", 1798 | "roughness": 1, 1799 | "opacity": 100, 1800 | "angle": 0, 1801 | "x": 1123.2709633800218, 1802 | "y": 18.692431396894307, 1803 | "strokeColor": "#000000", 1804 | "backgroundColor": "#fab005", 1805 | "width": 75.33334859212252, 1806 | "height": 76.00007820129399, 1807 | "seed": 250425670, 1808 | "groupIds": [], 1809 | "strokeSharpness": "sharp", 1810 | "boundElementIds": [ 1811 | "O2Tw6hZKMoBQI6O9ngXIf" 1812 | ] 1813 | }, 1814 | { 1815 | "type": "text", 1816 | "version": 790, 1817 | "versionNonce": 851387398, 1818 | "isDeleted": false, 1819 | "id": "lgRi0J7Bu2VnBtIETvc3b", 1820 | "fillStyle": "solid", 1821 | "strokeWidth": 1, 1822 | "strokeStyle": "solid", 1823 | "roughness": 0, 1824 | "opacity": 100, 1825 | "angle": 0, 1826 | "x": 1212.9376987112398, 1827 | "y": -59.14089165794803, 1828 | "strokeColor": "#000000", 1829 | "backgroundColor": "#ced4da", 1830 | "width": 458, 1831 | "height": 46, 1832 | "seed": 2005484186, 1833 | "groupIds": [], 1834 | "strokeSharpness": "sharp", 1835 | "boundElementIds": [], 1836 | "fontSize": 36, 1837 | "fontFamily": 1, 1838 | "text": "RX clock domain (optional)", 1839 | "baseline": 32, 1840 | "textAlign": "center", 1841 | "verticalAlign": "top" 1842 | }, 1843 | { 1844 | "type": "text", 1845 | "version": 826, 1846 | "versionNonce": 926517082, 1847 | "isDeleted": false, 1848 | "id": "av3BFf4-zDcsNdPsx9DKo", 1849 | "fillStyle": "solid", 1850 | "strokeWidth": 1, 1851 | "strokeStyle": "solid", 1852 | "roughness": 0, 1853 | "opacity": 100, 1854 | "angle": 0, 1855 | "x": 1212.9377190562923, 1856 | "y": 30.192477279226523, 1857 | "strokeColor": "#000000", 1858 | "backgroundColor": "#ced4da", 1859 | "width": 462, 1860 | "height": 46, 1861 | "seed": 2085676698, 1862 | "groupIds": [], 1863 | "strokeSharpness": "sharp", 1864 | "boundElementIds": [], 1865 | "fontSize": 36, 1866 | "fontFamily": 1, 1867 | "text": "TX clock domain (optional)", 1868 | "baseline": 32, 1869 | "textAlign": "center", 1870 | "verticalAlign": "top" 1871 | }, 1872 | { 1873 | "type": "text", 1874 | "version": 1198, 1875 | "versionNonce": 666814554, 1876 | "isDeleted": false, 1877 | "id": "eeWeBkKka1r_xU4U19kLL", 1878 | "fillStyle": "solid", 1879 | "strokeWidth": 2, 1880 | "strokeStyle": "dashed", 1881 | "roughness": 0, 1882 | "opacity": 100, 1883 | "angle": 0, 1884 | "x": 758.6424440748095, 1885 | "y": 1092.1204121600715, 1886 | "strokeColor": "#000000", 1887 | "backgroundColor": "#ffffff", 1888 | "width": 518, 1889 | "height": 46, 1890 | "seed": 1909332058, 1891 | "groupIds": [ 1892 | "v6brJKfibFtWId9Ct0nkM" 1893 | ], 1894 | "strokeSharpness": "sharp", 1895 | "boundElementIds": [ 1896 | "O54BnhhbjyxtBeCxBls8t" 1897 | ], 1898 | "fontSize": 36, 1899 | "fontFamily": 1, 1900 | "text": "Only for synchronous protocol", 1901 | "baseline": 32, 1902 | "textAlign": "center", 1903 | "verticalAlign": "top" 1904 | }, 1905 | { 1906 | "type": "arrow", 1907 | "version": 225, 1908 | "versionNonce": 1320419910, 1909 | "isDeleted": false, 1910 | "id": "O54BnhhbjyxtBeCxBls8t", 1911 | "fillStyle": "hachure", 1912 | "strokeWidth": 2, 1913 | "strokeStyle": "dashed", 1914 | "roughness": 1, 1915 | "opacity": 100, 1916 | "angle": 0, 1917 | "x": 744.2202571173442, 1918 | "y": 1122.4760368888037, 1919 | "strokeColor": "#000000", 1920 | "backgroundColor": "#868e96", 1921 | "width": 147.999979654948, 1922 | "height": 133.33333333333348, 1923 | "seed": 1114010374, 1924 | "groupIds": [], 1925 | "strokeSharpness": "round", 1926 | "boundElementIds": null, 1927 | "startBinding": { 1928 | "elementId": "eeWeBkKka1r_xU4U19kLL", 1929 | "focus": -0.750038791844975, 1930 | "gap": 14.422186957465385 1931 | }, 1932 | "endBinding": null, 1933 | "lastCommittedPoint": null, 1934 | "startArrowhead": null, 1935 | "endArrowhead": "arrow", 1936 | "points": [ 1937 | [ 1938 | 0, 1939 | 0 1940 | ], 1941 | [ 1942 | -106.66666666666674, 1943 | -13.333333333333485 1944 | ], 1945 | [ 1946 | -147.999979654948, 1947 | -133.33333333333348 1948 | ] 1949 | ] 1950 | }, 1951 | { 1952 | "type": "arrow", 1953 | "version": 1686, 1954 | "versionNonce": 2107196122, 1955 | "isDeleted": false, 1956 | "id": "vv2V5cysiamFxyLGDfEyx", 1957 | "fillStyle": "hachure", 1958 | "strokeWidth": 2, 1959 | "strokeStyle": "dashed", 1960 | "roughness": 1, 1961 | "opacity": 100, 1962 | "angle": 0, 1963 | "x": 813.7174368847732, 1964 | "y": 335.8913446778608, 1965 | "strokeColor": "#000000", 1966 | "backgroundColor": "#fa5252", 1967 | "width": 330.13333367920313, 1968 | "height": 0.6974843443244367, 1969 | "seed": 1259321242, 1970 | "groupIds": [ 1971 | "bcdqf2-Scq8O6BXgWwb_F", 1972 | "yBg32GEk8SIjRLfxe05Re" 1973 | ], 1974 | "strokeSharpness": "round", 1975 | "boundElementIds": [], 1976 | "startBinding": { 1977 | "elementId": "QW_2AUQKoO6h82rd7AsVm", 1978 | "focus": 0.9361429941145419, 1979 | "gap": 17.957249633990045 1980 | }, 1981 | "endBinding": { 1982 | "elementId": "eDtMDhZa73ihbnH7tvxrO", 1983 | "focus": -0.610604264194288, 1984 | "gap": 16.839920485441326 1985 | }, 1986 | "lastCommittedPoint": null, 1987 | "startArrowhead": null, 1988 | "endArrowhead": "arrow", 1989 | "points": [ 1990 | [ 1991 | 0, 1992 | 0 1993 | ], 1994 | [ 1995 | -330.13333367920313, 1996 | 0.6974843443244367 1997 | ] 1998 | ] 1999 | }, 2000 | { 2001 | "type": "text", 2002 | "version": 1147, 2003 | "versionNonce": 382192006, 2004 | "isDeleted": false, 2005 | "id": "UEz44lzEHkVfCNcH3rb6p", 2006 | "fillStyle": "solid", 2007 | "strokeWidth": 2, 2008 | "strokeStyle": "dashed", 2009 | "roughness": 0, 2010 | "opacity": 100, 2011 | "angle": 0, 2012 | "x": 619.8658725945791, 2013 | "y": 349.06652538552197, 2014 | "strokeColor": "#000000", 2015 | "backgroundColor": "#ffffff", 2016 | "width": 50, 2017 | "height": 46, 2018 | "seed": 2023532294, 2019 | "groupIds": [ 2020 | "bcdqf2-Scq8O6BXgWwb_F", 2021 | "yBg32GEk8SIjRLfxe05Re" 2022 | ], 2023 | "strokeSharpness": "sharp", 2024 | "boundElementIds": [], 2025 | "fontSize": 36, 2026 | "fontFamily": 1, 2027 | "text": "BE", 2028 | "baseline": 32, 2029 | "textAlign": "center", 2030 | "verticalAlign": "top" 2031 | }, 2032 | { 2033 | "type": "line", 2034 | "version": 150, 2035 | "versionNonce": 1905785306, 2036 | "isDeleted": false, 2037 | "id": "7KvDDagODIFDIR0Y9YSvX", 2038 | "fillStyle": "solid", 2039 | "strokeWidth": 2, 2040 | "strokeStyle": "dashed", 2041 | "roughness": 1, 2042 | "opacity": 100, 2043 | "angle": 0, 2044 | "x": 655.8376194506492, 2045 | "y": 320.0283924559811, 2046 | "strokeColor": "#000000", 2047 | "backgroundColor": "#ced4da", 2048 | "width": 14.15386493389417, 2049 | "height": 28.923081618088986, 2050 | "seed": 1781118042, 2051 | "groupIds": [ 2052 | "yBg32GEk8SIjRLfxe05Re" 2053 | ], 2054 | "strokeSharpness": "round", 2055 | "boundElementIds": [], 2056 | "startBinding": null, 2057 | "endBinding": null, 2058 | "lastCommittedPoint": null, 2059 | "startArrowhead": null, 2060 | "endArrowhead": null, 2061 | "points": [ 2062 | [ 2063 | 0, 2064 | 0 2065 | ], 2066 | [ 2067 | -14.15386493389417, 2068 | 28.923081618088986 2069 | ] 2070 | ] 2071 | }, 2072 | { 2073 | "type": "line", 2074 | "version": 155, 2075 | "versionNonce": 257992902, 2076 | "isDeleted": false, 2077 | "id": "8_3Y5FTgyBVN0xXMsC5CM", 2078 | "fillStyle": "solid", 2079 | "strokeWidth": 2, 2080 | "strokeStyle": "dashed", 2081 | "roughness": 1, 2082 | "opacity": 100, 2083 | "angle": 0, 2084 | "x": 813.3760715990867, 2085 | "y": 337.25915699173817, 2086 | "strokeColor": "#000000", 2087 | "backgroundColor": "#ced4da", 2088 | "width": 27.800258044781685, 2089 | "height": 12.823359787177537, 2090 | "seed": 1841157702, 2091 | "groupIds": [ 2092 | "yBg32GEk8SIjRLfxe05Re" 2093 | ], 2094 | "strokeSharpness": "round", 2095 | "boundElementIds": [], 2096 | "startBinding": null, 2097 | "endBinding": null, 2098 | "lastCommittedPoint": null, 2099 | "startArrowhead": null, 2100 | "endArrowhead": null, 2101 | "points": [ 2102 | [ 2103 | 0, 2104 | 0 2105 | ], 2106 | [ 2107 | -27.800258044781685, 2108 | -12.823359787177537 2109 | ] 2110 | ] 2111 | }, 2112 | { 2113 | "type": "line", 2114 | "version": 146, 2115 | "versionNonce": 317697690, 2116 | "isDeleted": false, 2117 | "id": "7MdBhlaUJvBPxbOrv40E9", 2118 | "fillStyle": "solid", 2119 | "strokeWidth": 2, 2120 | "strokeStyle": "dashed", 2121 | "roughness": 1, 2122 | "opacity": 100, 2123 | "angle": 0, 2124 | "x": 784.0425247549048, 2125 | "y": 349.1330851822193, 2126 | "strokeColor": "#000000", 2127 | "backgroundColor": "#ced4da", 2128 | "width": 28.10279639346095, 2129 | "height": 12.489303415841732, 2130 | "seed": 912660762, 2131 | "groupIds": [ 2132 | "yBg32GEk8SIjRLfxe05Re" 2133 | ], 2134 | "strokeSharpness": "round", 2135 | "boundElementIds": [], 2136 | "startBinding": null, 2137 | "endBinding": null, 2138 | "lastCommittedPoint": null, 2139 | "startArrowhead": null, 2140 | "endArrowhead": null, 2141 | "points": [ 2142 | [ 2143 | 0, 2144 | 0 2145 | ], 2146 | [ 2147 | 28.10279639346095, 2148 | -12.489303415841732 2149 | ] 2150 | ] 2151 | }, 2152 | { 2153 | "type": "text", 2154 | "version": 1202, 2155 | "versionNonce": 404463622, 2156 | "isDeleted": false, 2157 | "id": "0JO1Onkd7pprkJD6aZsAT", 2158 | "fillStyle": "solid", 2159 | "strokeWidth": 2, 2160 | "strokeStyle": "dashed", 2161 | "roughness": 0, 2162 | "opacity": 100, 2163 | "angle": 0, 2164 | "x": 704.4761106794721, 2165 | "y": 94.97602380984199, 2166 | "strokeColor": "#000000", 2167 | "backgroundColor": "#ffffff", 2168 | "width": 375, 2169 | "height": 46, 2170 | "seed": 1038970694, 2171 | "groupIds": [ 2172 | "_JPHG2dlDsF7JtotZS-68" 2173 | ], 2174 | "strokeSharpness": "sharp", 2175 | "boundElementIds": [], 2176 | "fontSize": 36, 2177 | "fontFamily": 1, 2178 | "text": "Only for FT60x chips", 2179 | "baseline": 32, 2180 | "textAlign": "center", 2181 | "verticalAlign": "top" 2182 | }, 2183 | { 2184 | "type": "arrow", 2185 | "version": 87, 2186 | "versionNonce": 1089653594, 2187 | "isDeleted": false, 2188 | "id": "wSuuYM5zAQRyhfyqdvhYq", 2189 | "fillStyle": "hachure", 2190 | "strokeWidth": 2, 2191 | "strokeStyle": "dashed", 2192 | "roughness": 1, 2193 | "opacity": 100, 2194 | "angle": 0, 2195 | "x": 686.3332360979987, 2196 | "y": 119.19031824343551, 2197 | "strokeColor": "#000000", 2198 | "backgroundColor": "#868e96", 2199 | "width": 86.85703822544633, 2200 | "height": 187.42856706891735, 2201 | "seed": 1558372038, 2202 | "groupIds": [], 2203 | "strokeSharpness": "round", 2204 | "boundElementIds": null, 2205 | "startBinding": null, 2206 | "endBinding": null, 2207 | "lastCommittedPoint": null, 2208 | "startArrowhead": null, 2209 | "endArrowhead": "arrow", 2210 | "points": [ 2211 | [ 2212 | 0, 2213 | 0 2214 | ], 2215 | [ 2216 | -60.5712890625, 2217 | 45.71428571428578 2218 | ], 2219 | [ 2220 | -86.85703822544633, 2221 | 187.42856706891735 2222 | ] 2223 | ] 2224 | } 2225 | ], 2226 | "appState": { 2227 | "gridSize": null, 2228 | "viewBackgroundColor": "#ffffff" 2229 | } 2230 | } -------------------------------------------------------------------------------- /docs/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/proto245/0285265ad71151c08a293d301682832caaa703af/docs/system.png -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/README.md: -------------------------------------------------------------------------------- 1 | # FT2232H + DE10-Lite example 2 | 3 | ![ft2232h_de10lite](../../docs/ft2232h_de10lite.png) 4 | 5 | ## Hardware 6 | 7 | ### Compilation 8 | 9 | Quartus 19.1.0 Lite is used. 10 | 11 | Simply run ```./create_proj.sh``` to create the Quartus project from the tcl file. 12 | 13 | ### Syncronous vs Asynchronous FT245 14 | 15 | By default, synchronous mode is enabled. To switch to asynchronous just comment the define ```FIFO245_SYNC``` 16 | in ```top.sv``` file. 17 | 18 | ``` 19 | //------------------------------------------------------------------------------ 20 | // FT245 protocol master 21 | //------------------------------------------------------------------------------ 22 | //`define FIFO245_SYNC 23 | 24 | `ifdef FIFO245_SYNC 25 | `include "sync245.svh" 26 | `else 27 | `include "async245.svh" 28 | `endif 29 | ``` 30 | 31 | ### Connections 32 | 33 | All the connections are below. 34 | 35 | | FT Pin | FT Function | GPIO J1 Header | RTL Top | 36 | | :----- | :---------- | :------------- | :--------- | 37 | | ADBUS0 | D0 | gpio[8] | ft_data[0] | 38 | | ADBUS1 | D1 | gpio[10] | ft_data[1] | 39 | | ADBUS2 | D2 | gpio[12] | ft_data[2] | 40 | | ADBUS3 | D3 | gpio[14] | ft_data[3] | 41 | | ADBUS4 | D4 | gpio[16] | ft_data[4] | 42 | | ADBUS5 | D5 | gpio[18] | ft_data[5] | 43 | | ADBUS6 | D6 | gpio[20] | ft_data[6] | 44 | | ADBUS7 | D7 | gpio[22] | ft_data[7] | 45 | | ACBUS0 | RXF# | gpio[24] | ft_rxfn | 46 | | ACBUS1 | TXE# | gpio[26] | ft_txen | 47 | | ACBUS2 | RD# | gpio[28] | ft_rdn | 48 | | ACBUS3 | WR# | gpio[30] | ft_wrn | 49 | | ACBUS4 | SIWU | gpio[32] | ft_siwu | 50 | | ACBUS5 | CLKOUT | gpio[2] | ft_clk | 51 | | ACBUS6 | OE# | gpio[34] | ft_oen | 52 | 53 | ## Software 54 | 55 | System: 56 | 57 | * Ubuntu 20.04 58 | * Python 3.7 59 | 60 | All tests evaluate read and write throughtput. 61 | 62 | ### test_ftd2xx.py 63 | 64 | Requirements: 65 | 66 | * [D2xx Driver](https://ftdichip.com/drivers/d2xx-drivers/) 67 | * [ftd2xx](https://github.com/snmishra/ftd2xx) 68 | 69 | Results (synchronous 245): 70 | 71 | ``` 72 | $ ./test_ftd2xx.py 73 | Read 100.00 MiB (104857600 bytes) from FPGA in 10.846953 seconds (9.22 MiB/s) 74 | Verify data: ok 75 | Wrote 100.00 MiB (104857600 bytes) to FPGA in 2.632750 seconds (37.98 MiB/s) 76 | Verify data: ok 77 | ``` 78 | 79 | *Note* - known [issue](https://github.com/0x6a77/JD2XX/issues/9) with sync fifo speed under linux. 80 | 81 | ### test_pylibftdi.py 82 | 83 | Requirements: 84 | 85 | * [pylibftdi](https://github.com/codedstructure/pylibftdi) 86 | * [libftdi](https://www.intra2net.com/en/developer/libftdi/index.php) 87 | 88 | Results (synchronous 245): 89 | 90 | ``` 91 | $ ./test_pylibftdi.py 92 | Read 100.00 MiB (104857600 bytes) from FPGA in 2.705440 seconds (36.96 MiB/s) 93 | Verify data: ok 94 | Wrote 100.00 MiB (104857600 bytes) to FPGA in 2.717165 seconds (36.80 MiB/s) 95 | Verify data: ok 96 | ``` 97 | 98 | ### test_ftdi1.py 99 | 100 | Requirements: 101 | 102 | * [pylibftdi](https://github.com/codedstructure/pylibftdi) 103 | * ftdi1 - SWIG wrapper (check ```python``` folder in ```libftdi``` sources root) 104 | 105 | Results (synchronous 245): 106 | 107 | ``` 108 | $ ./test_ftdi1.py 109 | Read 100.00 MiB (104857600 bytes) from FPGA in 2.422061 seconds (41.29 MiB/s) 110 | Verify data: ok 111 | Wrote 100.00 MiB (104857600 bytes) to FPGA in 2.342588 seconds (42.69 MiB/s) 112 | Verify data: ok 113 | ``` 114 | 115 | ### test_pyusb.py 116 | 117 | Requirements: 118 | 119 | * [pyusb](https://github.com/pyusb/pyusb) 120 | 121 | Results (synchronous 245): 122 | 123 | ``` 124 | $ ./test_ftdi1.py 125 | Read 100.00 MiB (104857600 bytes) from FPGA in 2.422061 seconds (41.29 MiB/s) 126 | Verify data: ok 127 | Wrote 100.00 MiB (104857600 bytes) to FPGA in 2.342588 seconds (42.69 MiB/s) 128 | Verify data: ok 129 | ``` 130 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !proto245.tcl 4 | !README.md 5 | !*.sv 6 | !*.svh 7 | !*.sdc 8 | !*.sh 9 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/async245.svh: -------------------------------------------------------------------------------- 1 | localparam TX_FIFO_SIZE = 4096; 2 | localparam RX_FIFO_SIZE = 4096; 3 | localparam SINGLE_CLK_DOMAIN = 1; 4 | localparam READ_TICKS = 2; 5 | localparam WRITE_TICKS = 2; 6 | localparam TX_FIFO_LOAD_W = $clog2(TX_FIFO_SIZE) + 1; 7 | localparam RX_FIFO_LOAD_W = $clog2(RX_FIFO_SIZE) + 1; 8 | 9 | logic [DATA_W-1:0] ft_din, ft_dout; 10 | 11 | logic rxfifo_rd; 12 | logic [DATA_W-1:0] rxfifo_data; 13 | logic rxfifo_valid; 14 | logic [RX_FIFO_LOAD_W-1:0] rxfifo_load; 15 | logic rxfifo_empty; 16 | logic [DATA_W-1:0] txfifo_data; 17 | logic txfifo_wr; 18 | logic [TX_FIFO_LOAD_W-1:0] txfifo_load; 19 | logic txfifo_full; 20 | 21 | proto245a #( 22 | .DATA_W (DATA_W), 23 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 24 | .RX_FIFO_SIZE (RX_FIFO_SIZE), 25 | .SINGLE_CLK_DOMAIN (SINGLE_CLK_DOMAIN), 26 | .READ_TICKS (READ_TICKS), 27 | .WRITE_TICKS (WRITE_TICKS) 28 | ) proto245 ( 29 | // FT interface - should be routed directly to IO 30 | .ft_rst (sys_rst), 31 | .ft_clk (sys_clk), 32 | .ft_rxfn (ft_rxfn), 33 | .ft_txen (ft_txen), 34 | .ft_din (ft_din), 35 | .ft_dout (ft_dout), 36 | .ft_rdn (ft_rdn), 37 | .ft_wrn (ft_wrn), 38 | .ft_siwu (ft_siwu), 39 | // RX FIFO (Host -> FTDI chip -> FPGA -> FIFO) 40 | .rxfifo_clk (sys_clk), 41 | .rxfifo_rst (sys_rst), 42 | .rxfifo_rd (rxfifo_rd), 43 | .rxfifo_data (rxfifo_data), 44 | .rxfifo_valid (rxfifo_valid), 45 | .rxfifo_load (rxfifo_load), 46 | .rxfifo_empty (rxfifo_empty), 47 | // TX FIFO (FIFO -> FPGA -> FTDI chip -> Host) 48 | .txfifo_clk (sys_clk), 49 | .txfifo_rst (sys_rst), 50 | .txfifo_data (txfifo_data), 51 | .txfifo_wr (txfifo_wr), 52 | .txfifo_load (txfifo_load), 53 | .txfifo_full (txfifo_full) 54 | ); 55 | 56 | assign ft_oen = 1'b1; 57 | assign ft_data = ft_rdn ? ft_dout : 'z; 58 | assign ft_din = ft_data; 59 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf db incremental_db output_files *.q* *.stp -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/create_proj.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | quartus_sh -t proto245.tcl -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/proto245.tcl: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 Intel Corporation. All rights reserved. 2 | # Your use of Intel Corporation's design tools, logic functions 3 | # and other software and tools, and any partner logic 4 | # functions, and any output files from any of the foregoing 5 | # (including device programming or simulation files), and any 6 | # associated documentation or information are expressly subject 7 | # to the terms and conditions of the Intel Program License 8 | # Subscription Agreement, the Intel Quartus Prime License Agreement, 9 | # the Intel FPGA IP License Agreement, or other applicable license 10 | # agreement, including, without limitation, that your use is for 11 | # the sole purpose of programming logic devices manufactured by 12 | # Intel and sold by Intel or its authorized distributors. Please 13 | # refer to the applicable agreement for further details, at 14 | # https://fpgasoftware.intel.com/eula. 15 | 16 | # Quartus Prime: Generate Tcl File for Project 17 | # File: proto245.tcl 18 | # Generated on: Thu Jun 17 22:50:06 2021 19 | 20 | # Load Quartus Prime Tcl Project package 21 | package require ::quartus::project 22 | 23 | set need_to_close_project 0 24 | set make_assignments 1 25 | 26 | # Check that the right project is open 27 | if {[is_project_open]} { 28 | if {[string compare $quartus(project) "proto245"]} { 29 | puts "Project proto245 is not open" 30 | set make_assignments 0 31 | } 32 | } else { 33 | # Only open if not already open 34 | if {[project_exists proto245]} { 35 | project_open -revision top proto245 36 | } else { 37 | project_new -revision top proto245 38 | } 39 | set need_to_close_project 1 40 | } 41 | 42 | # Make assignments 43 | if {$make_assignments} { 44 | set_global_assignment -name FAMILY "MAX 10 FPGA" 45 | set_global_assignment -name DEVICE 10M50DAF484C7G 46 | set_global_assignment -name ORIGINAL_QUARTUS_VERSION 19.1.0 47 | set_global_assignment -name PROJECT_CREATION_TIME_DATE "20:50:27 июня 05, 2021" 48 | set_global_assignment -name LAST_QUARTUS_VERSION "19.1.0 Lite Edition" 49 | set_global_assignment -name DEVICE_FILTER_PACKAGE FBGA 50 | set_global_assignment -name DEVICE_FILTER_PIN_COUNT 484 51 | set_global_assignment -name DEVICE_FILTER_SPEED_GRADE 6 52 | set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files 53 | set_global_assignment -name MIN_CORE_JUNCTION_TEMP 0 54 | set_global_assignment -name MAX_CORE_JUNCTION_TEMP 85 55 | set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR 256 56 | set_global_assignment -name EDA_DESIGN_ENTRY_SYNTHESIS_TOOL "Precision Synthesis" 57 | set_global_assignment -name EDA_LMF_FILE mentor.lmf -section_id eda_design_synthesis 58 | set_global_assignment -name EDA_INPUT_DATA_FORMAT VQM -section_id eda_design_synthesis 59 | set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_timing 60 | set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_symbol 61 | set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_signal_integrity 62 | set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_boundary_scan 63 | set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top 64 | set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top 65 | set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top 66 | set_global_assignment -name SYSTEMVERILOG_FILE ../../../src/proto245s.sv 67 | set_global_assignment -name SYSTEMVERILOG_FILE ../../../src/proto245a.sv 68 | set_global_assignment -name SYSTEMVERILOG_FILE ../../../src/fifo_sync.sv 69 | set_global_assignment -name SYSTEMVERILOG_FILE ../../../src/fifo_async.sv 70 | set_global_assignment -name SYSTEMVERILOG_FILE ../../../src/dpram.sv 71 | set_global_assignment -name SYSTEMVERILOG_FILE top.sv 72 | set_global_assignment -name SDC_FILE top.sdc 73 | set_global_assignment -name POWER_PRESET_COOLING_SOLUTION "23 MM HEAT SINK WITH 200 LFPM AIRFLOW" 74 | set_global_assignment -name POWER_BOARD_THERMAL_MODEL "NONE (CONSERVATIVE)" 75 | set_global_assignment -name VERILOG_INPUT_VERSION SYSTEMVERILOG_2005 76 | set_global_assignment -name VERILOG_SHOW_LMF_MAPPING_MESSAGES OFF 77 | set_global_assignment -name OPTIMIZATION_MODE BALANCED 78 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to max10_clk1_50 79 | set_location_assignment PIN_P11 -to max10_clk1_50 80 | set_instance_assignment -name IO_STANDARD "3.3 V SCHMITT TRIGGER" -to key[0] 81 | set_instance_assignment -name IO_STANDARD "3.3 V SCHMITT TRIGGER" -to key[1] 82 | set_location_assignment PIN_B8 -to key[0] 83 | set_location_assignment PIN_A7 -to key[1] 84 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[0] 85 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[1] 86 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[2] 87 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[3] 88 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[4] 89 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[5] 90 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[6] 91 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[7] 92 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[8] 93 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[9] 94 | set_location_assignment PIN_C10 -to sw[0] 95 | set_location_assignment PIN_C11 -to sw[1] 96 | set_location_assignment PIN_D12 -to sw[2] 97 | set_location_assignment PIN_C12 -to sw[3] 98 | set_location_assignment PIN_A12 -to sw[4] 99 | set_location_assignment PIN_B12 -to sw[5] 100 | set_location_assignment PIN_A13 -to sw[6] 101 | set_location_assignment PIN_A14 -to sw[7] 102 | set_location_assignment PIN_B14 -to sw[8] 103 | set_location_assignment PIN_F15 -to sw[9] 104 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_clk 105 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[0] 106 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[1] 107 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[2] 108 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[3] 109 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[4] 110 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[5] 111 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[6] 112 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_data[7] 113 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_rxfn 114 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_txen 115 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_rdn 116 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_wrn 117 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_siwu 118 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ft_oen 119 | set_location_assignment PIN_V9 -to ft_clk 120 | set_location_assignment PIN_W6 -to ft_data[0] 121 | set_location_assignment PIN_W5 -to ft_data[1] 122 | set_location_assignment PIN_AA14 -to ft_data[2] 123 | set_location_assignment PIN_W12 -to ft_data[3] 124 | set_location_assignment PIN_AB12 -to ft_data[4] 125 | set_location_assignment PIN_AB11 -to ft_data[5] 126 | set_location_assignment PIN_AB10 -to ft_data[6] 127 | set_location_assignment PIN_AA9 -to ft_data[7] 128 | set_location_assignment PIN_AA8 -to ft_rxfn 129 | set_location_assignment PIN_AA7 -to ft_txen 130 | set_location_assignment PIN_AA6 -to ft_rdn 131 | set_location_assignment PIN_AA5 -to ft_wrn 132 | set_location_assignment PIN_AB3 -to ft_siwu 133 | set_location_assignment PIN_AB2 -to ft_oen 134 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[0] 135 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[1] 136 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[2] 137 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[3] 138 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[4] 139 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[5] 140 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[6] 141 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[7] 142 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[8] 143 | set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to ledr[9] 144 | set_location_assignment PIN_A8 -to ledr[0] 145 | set_location_assignment PIN_A9 -to ledr[1] 146 | set_location_assignment PIN_A10 -to ledr[2] 147 | set_location_assignment PIN_B10 -to ledr[3] 148 | set_location_assignment PIN_D13 -to ledr[4] 149 | set_location_assignment PIN_C13 -to ledr[5] 150 | set_location_assignment PIN_E14 -to ledr[6] 151 | set_location_assignment PIN_D14 -to ledr[7] 152 | set_location_assignment PIN_A11 -to ledr[8] 153 | set_location_assignment PIN_B11 -to ledr[9] 154 | set_instance_assignment -name FAST_OUTPUT_REGISTER ON -to ft_rdn 155 | set_instance_assignment -name FAST_INPUT_REGISTER ON -to ft_rxfn 156 | set_instance_assignment -name FAST_INPUT_REGISTER ON -to ft_txen 157 | set_instance_assignment -name FAST_OUTPUT_REGISTER ON -to ft_wrn 158 | set_instance_assignment -name FAST_OUTPUT_REGISTER ON -to ft_data 159 | set_instance_assignment -name FAST_INPUT_REGISTER ON -to ft_data 160 | set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top 161 | 162 | # Commit assignments 163 | export_assignments 164 | 165 | # Close project 166 | if {$need_to_close_project} { 167 | project_close 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/sync245.svh: -------------------------------------------------------------------------------- 1 | localparam TX_FIFO_SIZE = 4096; 2 | localparam TX_START_THRESHOLD = 1024; 3 | localparam TX_BURST_SIZE = 0; 4 | localparam TX_BACKOFF_TIMEOUT = 64; 5 | localparam RX_FIFO_SIZE = 4096; 6 | localparam RX_START_THRESHOLD = 3072; 7 | localparam RX_BURST_SIZE = 0; 8 | localparam SINGLE_CLK_DOMAIN = 0; 9 | localparam TX_FIFO_LOAD_W = $clog2(TX_FIFO_SIZE) + 1; 10 | localparam RX_FIFO_LOAD_W = $clog2(RX_FIFO_SIZE) + 1; 11 | 12 | logic [DATA_W-1:0] ft_din, ft_dout; 13 | 14 | logic rxfifo_rd; 15 | logic [DATA_W-1:0] rxfifo_data; 16 | logic rxfifo_valid; 17 | logic [RX_FIFO_LOAD_W-1:0] rxfifo_load; 18 | logic rxfifo_empty; 19 | logic [DATA_W-1:0] txfifo_data; 20 | logic txfifo_wr; 21 | logic [TX_FIFO_LOAD_W-1:0] txfifo_load; 22 | logic txfifo_full; 23 | 24 | proto245s #( 25 | .DATA_W (DATA_W), 26 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 27 | .TX_START_THRESHOLD (TX_START_THRESHOLD), 28 | .TX_BURST_SIZE (TX_BURST_SIZE), 29 | .TX_BACKOFF_TIMEOUT (TX_BACKOFF_TIMEOUT), 30 | .RX_FIFO_SIZE (RX_FIFO_SIZE), 31 | .RX_START_THRESHOLD (RX_START_THRESHOLD), 32 | .RX_BURST_SIZE (RX_BURST_SIZE), 33 | .SINGLE_CLK_DOMAIN (SINGLE_CLK_DOMAIN) 34 | ) proto245 ( 35 | // FT interface - should be routed directly to IO 36 | .ft_rst (ft_rst), 37 | .ft_clk (ft_clk), 38 | .ft_rxfn (ft_rxfn), 39 | .ft_txen (ft_txen), 40 | .ft_din (ft_din), 41 | .ft_dout (ft_dout), 42 | .ft_bein (0), 43 | .ft_beout (), 44 | .ft_rdn (ft_rdn), 45 | .ft_wrn (ft_wrn), 46 | .ft_oen (ft_oen), 47 | .ft_siwu (ft_siwu), 48 | // RX FIFO (Host -> FTDI chip -> FPGA -> FIFO) 49 | .rxfifo_clk (sys_clk), 50 | .rxfifo_rst (sys_rst), 51 | .rxfifo_rd (rxfifo_rd), 52 | .rxfifo_data (rxfifo_data), 53 | .rxfifo_valid (rxfifo_valid), 54 | .rxfifo_load (rxfifo_load), 55 | .rxfifo_empty (rxfifo_empty), 56 | // TX FIFO (FIFO -> FPGA -> FTDI chip -> Host) 57 | .txfifo_clk (sys_clk), 58 | .txfifo_rst (sys_rst), 59 | .txfifo_data (txfifo_data), 60 | .txfifo_wr (txfifo_wr), 61 | .txfifo_load (txfifo_load), 62 | .txfifo_full (txfifo_full) 63 | ); 64 | 65 | assign ft_data = ft_oen ? ft_dout : 'z; 66 | assign ft_din = ft_data; 67 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/top.sdc: -------------------------------------------------------------------------------- 1 | set_time_format -unit ns -decimal_places 3 2 | 3 | #************************************************************** 4 | # Create Clock 5 | #************************************************************** 6 | create_clock -period "50.0 MHz" [get_ports {max10_clk1_50}] 7 | create_clock -period "60.0 MHz" [get_ports {ft_clk}] 8 | 9 | #************************************************************** 10 | # Create Generated Clock 11 | #************************************************************** 12 | derive_pll_clocks 13 | 14 | #************************************************************** 15 | # Set Clock Latency 16 | #************************************************************** 17 | 18 | #************************************************************** 19 | # Set Clock Uncertainty 20 | #************************************************************** 21 | derive_clock_uncertainty 22 | 23 | #************************************************************** 24 | # Set Input Delay 25 | #************************************************************** 26 | set_input_delay -clock [get_clocks {ft_clk}] 8.333 [get_ports {ft_data[*] ft_rxfn ft_txen}] 27 | 28 | #************************************************************** 29 | # Set Output Delay 30 | #************************************************************** 31 | set_output_delay -clock [get_clocks {ft_clk}] -min 8.333 [get_ports {ft_data[*] ft_oen ft_wrn ft_rdn ft_siwu}] 32 | set_output_delay -clock [get_clocks {ft_clk}] -max 8.333 [get_ports {ft_data[*] ft_oen ft_wrn ft_rdn ft_siwu}] 33 | 34 | #************************************************************** 35 | # Set Clock Groups 36 | #************************************************************** 37 | set_clock_groups -asynchronous -group [get_clocks {ft_clk}] -group [get_clocks {max10_clk1_50}] 38 | 39 | #************************************************************** 40 | # Set False Path 41 | #************************************************************** 42 | set_false_path -from [get_clocks {max10_clk1_50}] -to [get_ports {ledr[*]}] 43 | set_false_path -from [get_clocks {ft_clk}] -to [get_ports {ledr[*]}] 44 | 45 | #************************************************************** 46 | # Set Multicycle Path 47 | #************************************************************** 48 | 49 | #************************************************************** 50 | # Set Maximum Delay 51 | #************************************************************** 52 | 53 | #************************************************************** 54 | # Set Minimum Delay 55 | #************************************************************** 56 | 57 | #************************************************************** 58 | # Set Input Transition 59 | #************************************************************** 60 | 61 | #************************************************************** 62 | # Set Load 63 | #************************************************************** 64 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/hw/top.sv: -------------------------------------------------------------------------------- 1 | module top #( 2 | parameter DATA_W = 8 3 | )( 4 | // dev board 5 | input max10_clk1_50, 6 | input [1:0] key, 7 | output [9:0] ledr, 8 | input [9:0] sw, 9 | // ft board 10 | output ft_oen, 11 | input ft_clk, 12 | output ft_siwu, 13 | output ft_wrn, 14 | output ft_rdn, 15 | input ft_txen, 16 | input ft_rxfn, 17 | inout [DATA_W-1:0] ft_data 18 | ); 19 | 20 | //------------------------------------------------------------------------------ 21 | // Clocks and resets 22 | //------------------------------------------------------------------------------ 23 | logic sys_clk; 24 | assign sys_clk = max10_clk1_50; 25 | 26 | // System synchronous active high reset 27 | logic [5:0] sys_reset_cnt = '0; 28 | logic sys_rst = 1'b1; 29 | always_ff @(posedge sys_clk) begin 30 | if (sys_reset_cnt < '1) begin 31 | sys_rst <= 1'b1; 32 | sys_reset_cnt <= sys_reset_cnt + 1'b1; 33 | end else begin 34 | sys_rst <= 1'b0; 35 | end 36 | end 37 | 38 | // FT domain synchronous active high reset 39 | logic [5:0] ft_reset_cnt = '0; 40 | logic ft_rst = 1'b1; 41 | always_ff @(posedge ft_clk) begin 42 | if (ft_reset_cnt < '1) begin 43 | ft_rst <= 1'b1; 44 | ft_reset_cnt <= ft_reset_cnt + 1'b1; 45 | end else begin 46 | ft_rst <= 1'b0; 47 | end 48 | end 49 | 50 | //------------------------------------------------------------------------------ 51 | // FT245 protocol master 52 | //------------------------------------------------------------------------------ 53 | `define FIFO245_SYNC 54 | 55 | `ifdef FIFO245_SYNC 56 | `include "sync245.svh" 57 | `else 58 | `include "async245.svh" 59 | `endif 60 | 61 | //------------------------------------------------------------------------------ 62 | // Test logic 63 | //------------------------------------------------------------------------------ 64 | enum logic [3:0] { 65 | CMD_WAIT_S, 66 | CMD_READ_S, 67 | CMD_PARSE_S, 68 | TX_TEST_S, 69 | RX_TEST_S 70 | } fsm_state, fsm_next; 71 | 72 | logic [63:0] cmd_shifter, cmd_shifter_next; 73 | logic [7:0] cmd_prefix; 74 | logic [7:0] cmd_suffix; 75 | logic [31:0] cmd_data; 76 | logic [15:0] cmd_code; 77 | logic rxfifo_rd_next; 78 | logic [DATA_W-1:0] txfifo_data_next; 79 | logic txfifo_wr_next; 80 | logic led0_drv, led0_drv_next; 81 | logic [31:0] word_cnt, word_cnt_next; 82 | logic [DATA_W-1:0] golden_data, golden_data_next; 83 | logic dbg_led, dbg_led_next; 84 | 85 | assign {cmd_prefix, cmd_code, cmd_data, cmd_suffix} = cmd_shifter; 86 | 87 | always_comb begin 88 | fsm_next = fsm_state; 89 | cmd_shifter_next = cmd_shifter; 90 | rxfifo_rd_next = rxfifo_rd; 91 | txfifo_data_next = txfifo_data; 92 | txfifo_wr_next = txfifo_wr; 93 | led0_drv_next = led0_drv; 94 | word_cnt_next = word_cnt; 95 | golden_data_next = golden_data; 96 | dbg_led_next = dbg_led; 97 | 98 | case (fsm_state) 99 | CMD_WAIT_S: begin 100 | txfifo_wr_next = 1'b0; 101 | rxfifo_rd_next = 1'b0; 102 | if (!rxfifo_empty) begin 103 | rxfifo_rd_next = 1'b1; 104 | fsm_next = CMD_READ_S; 105 | end 106 | end 107 | 108 | CMD_READ_S: begin 109 | rxfifo_rd_next = 1'b0; 110 | if (rxfifo_valid) begin 111 | cmd_shifter_next = {rxfifo_data, cmd_shifter[63:DATA_W]}; 112 | fsm_next = CMD_PARSE_S; 113 | end 114 | end 115 | 116 | CMD_PARSE_S: begin 117 | if ((cmd_prefix == 8'hAA) && (cmd_suffix == 8'h55)) begin 118 | case (cmd_code) 119 | 16'hbeef: begin 120 | cmd_shifter_next = '0; 121 | txfifo_wr_next = 1'b1; 122 | txfifo_data_next = '0; 123 | word_cnt_next = cmd_data; 124 | fsm_next = TX_TEST_S; 125 | end 126 | 16'hcafe: begin 127 | cmd_shifter_next = '0; 128 | word_cnt_next = cmd_data; 129 | golden_data_next = '0; 130 | txfifo_data_next = 8'h42; 131 | fsm_next = RX_TEST_S; 132 | end 133 | 16'h1ed0: begin 134 | cmd_shifter_next = '0; 135 | led0_drv_next = cmd_data[0]; 136 | fsm_next = CMD_WAIT_S; 137 | end 138 | default: begin 139 | //do nothing 140 | end 141 | endcase 142 | end else begin 143 | fsm_next = CMD_WAIT_S; 144 | end 145 | end 146 | 147 | TX_TEST_S: begin 148 | if (word_cnt == 0) begin 149 | txfifo_wr_next = 1'b0; 150 | fsm_next = CMD_WAIT_S; 151 | end else if (!txfifo_full) begin 152 | word_cnt_next = word_cnt - 1'b1; 153 | txfifo_data_next = txfifo_data + 1'b1; 154 | end 155 | end 156 | 157 | RX_TEST_S: begin 158 | rxfifo_rd_next = !rxfifo_empty; 159 | if (rxfifo_valid) begin 160 | if (word_cnt == 0) begin 161 | rxfifo_rd_next = 1'b0; 162 | txfifo_wr_next = 1'b1; 163 | fsm_next = CMD_WAIT_S; 164 | end else begin 165 | word_cnt_next = word_cnt - 1'b1; 166 | end 167 | txfifo_data_next = (rxfifo_data != golden_data) ? 8'hee : txfifo_data; 168 | golden_data_next = golden_data + 1'b1; 169 | end 170 | end 171 | 172 | default: begin 173 | //do nothing 174 | end 175 | endcase 176 | end 177 | 178 | always_ff @(posedge sys_clk) begin 179 | if (sys_rst) begin 180 | fsm_state <= CMD_WAIT_S; 181 | cmd_shifter <= '0; 182 | rxfifo_rd <= 1'b0; 183 | txfifo_data <= '0; 184 | txfifo_wr <= 1'b0; 185 | led0_drv <= 1'b0; 186 | word_cnt <= '0; 187 | golden_data <= '0; 188 | dbg_led <= 1'b0; 189 | end else begin 190 | fsm_state <= fsm_next; 191 | cmd_shifter <= cmd_shifter_next; 192 | rxfifo_rd <= rxfifo_rd_next; 193 | txfifo_data <= txfifo_data_next; 194 | txfifo_wr <= txfifo_wr_next; 195 | led0_drv <= led0_drv_next; 196 | word_cnt <= word_cnt_next; 197 | golden_data <= golden_data_next; 198 | dbg_led <= dbg_led_next; 199 | end 200 | end 201 | 202 | `ifdef FIFO245_SYNC 203 | assign ledr[7] = '0; 204 | `else 205 | assign ledr[7] = '1; 206 | `endif 207 | assign ledr[6] = ~ft_wrn; 208 | assign ledr[5] = ~ft_rdn; 209 | assign ledr[4] = ~ft_txen; 210 | assign ledr[3] = ~ft_rxfn; 211 | assign ledr[2] = rxfifo_rd; 212 | assign ledr[1] = txfifo_wr; 213 | assign ledr[0] = led0_drv; 214 | 215 | //------------------------------------------------------------------------------ 216 | // Heartbeats 217 | //------------------------------------------------------------------------------ 218 | localparam HEARTBEAT_CNT_W = 25; 219 | 220 | // System clock domain 221 | logic [HEARTBEAT_CNT_W-1:0] sys_heartbeat_cnt; 222 | always_ff @(posedge sys_clk) begin 223 | if (sys_rst) 224 | sys_heartbeat_cnt <= '0; 225 | else 226 | sys_heartbeat_cnt <= sys_heartbeat_cnt + 1'b1; 227 | end 228 | assign ledr[9] = sys_heartbeat_cnt[HEARTBEAT_CNT_W-1]; 229 | 230 | // FT clock domain 231 | logic [HEARTBEAT_CNT_W-1:0] ft_heartbeat_cnt; 232 | always_ff @(posedge ft_clk) begin 233 | if (ft_rst) 234 | ft_heartbeat_cnt <= '0; 235 | else 236 | ft_heartbeat_cnt <= ft_heartbeat_cnt + 1'b1; 237 | end 238 | assign ledr[8] = ft_heartbeat_cnt[HEARTBEAT_CNT_W-1]; 239 | 240 | endmodule 241 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/test_ftd2xx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import ftd2xx as ft 4 | from time import time, sleep 5 | 6 | KiB = 1024 7 | MiB = KiB * 1024 8 | 9 | 10 | class FPGA: 11 | def __init__(self, ftdi_serial, fifo245_mode): 12 | self.ftdi_serial = ftdi_serial 13 | self.fifo245_mode = fifo245_mode 14 | 15 | def __enter__(self): 16 | try: 17 | ftdev_id = ft.listDevices().index(self.ftdi_serial) 18 | except ValueError: 19 | raise Exception("No board found!") 20 | self.ftdev = ft.open(ftdev_id) 21 | self.ftdev.resetDevice() 22 | # AN130 for more details about commands below 23 | self.ftdev.setBitMode(0xff, 0x40 if self.fifo245_mode == 'sync' else 0x00) 24 | self.ftdev.setTimeouts(10, 10) # in ms 25 | self.ftdev.setUSBParameters(64 * KiB, 64 * KiB) # set rx, tx buffer size in bytes 26 | self.ftdev.setFlowControl(ft.defines.FLOW_RTS_CTS, 0, 0) 27 | return self 28 | 29 | def __exit__(self, exc_type, exc_val, exc_tb): 30 | self.ftdev.close() 31 | 32 | def __cmd(self, code, data): 33 | return ((0xAA << 56) | (code << 40) | (data << 8) | 0x55).to_bytes(8, 'little') 34 | 35 | def test_led(self): 36 | self.ftdev.write(self.__cmd(0x1ED0, 1)) 37 | sleep(2) 38 | self.ftdev.write(self.__cmd(0x1ED0, 0)) 39 | sleep(2) 40 | 41 | def test_read(self, total_bytes=1 * MiB): 42 | # Prepare data 43 | golden_data = [i % 256 for i in range(total_bytes)] 44 | 45 | # Start read test 46 | self.ftdev.write(self.__cmd(0xBEEF, total_bytes - 1)) 47 | 48 | # Receive data 49 | chunks = [] 50 | start_time = time() 51 | while total_bytes > 0: 52 | chunk = self.ftdev.read(1 * MiB) 53 | if not chunk: 54 | break 55 | chunks.append(chunk) 56 | total_bytes -= len(chunk) 57 | exec_time = time() - start_time 58 | 59 | # Print statistics 60 | data = [b for chunk in chunks for b in chunk] # flatten all chunks 61 | data_len = len(data) 62 | data_len_mb = data_len / MiB 63 | print("Read %.02f MiB (%d bytes) from FPGA in %f seconds (%.02f MiB/s)" % 64 | (data_len_mb, data_len, exec_time, data_len_mb / exec_time)) 65 | 66 | # Verify data 67 | print("Verify data: %s" % ('ok' if golden_data == data else 'error')) 68 | 69 | def test_write(self, total_bytes=1 * MiB): 70 | # Prepare data 71 | data = bytes(bytearray([i % 256 for i in range(total_bytes)])) 72 | 73 | # Start write test 74 | self.ftdev.write(self.__cmd(0xCAFE, total_bytes - 1)) 75 | 76 | # Transmit data 77 | offset = 0 78 | data_len = total_bytes 79 | result = 0 80 | start_time = time() 81 | while data_len > 0: 82 | chunk_len = 1 * MiB if data_len > 1 * MiB else data_len 83 | chunk_len = self.ftdev.write(data[offset:offset + chunk_len]) 84 | data_len -= chunk_len 85 | offset += chunk_len 86 | result = self.ftdev.read(1) 87 | exec_time = time() - start_time 88 | 89 | # Print statistics 90 | data_len_mb = total_bytes / MiB 91 | print("Wrote %.02f MiB (%d bytes) to FPGA in %f seconds (%.02f MiB/s)" % 92 | (data_len_mb, total_bytes, exec_time, data_len_mb / exec_time)) 93 | 94 | # Verify data 95 | result = 0 if not result else result[0] 96 | print("Verify data: %s" % ('ok' if result == 0x42 else 'error')) 97 | 98 | 99 | if __name__ == "__main__": 100 | with FPGA(ftdi_serial=b'FT3C8Z0AA', fifo245_mode='sync') as de10lite: 101 | de10lite.test_led() 102 | de10lite.test_read(100 * MiB) 103 | de10lite.test_write(100 * MiB) 104 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/test_ftdi1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import ftdi1 as ft 4 | from time import time, sleep 5 | 6 | KiB = 1024 7 | MiB = KiB * 1024 8 | 9 | 10 | class FPGA: 11 | def __init__(self, serial, sync=True, vid=0x0403, pid=0x6010): 12 | self._vid = vid 13 | self._pid = pid 14 | self._serial = serial 15 | self._sync = sync 16 | 17 | def _err_wrap(self, ret): 18 | if ret < 0: # prints last error message 19 | raise Exception("%s (%d)" % (ft.get_error_string(self._ctx), ret)) 20 | else: 21 | return ret 22 | 23 | def __enter__(self): 24 | self._ctx = ft.new() 25 | self._err_wrap(ft.init(self._ctx)) 26 | self._err_wrap(ft.usb_open_desc(self._ctx, self._vid, self._pid, None, self._serial)) 27 | self._err_wrap(ft.set_bitmode(self._ctx, 0xff, ft.BITMODE_SYNCFF if self._sync else ft.BITMODE_RESET)) 28 | self._err_wrap(ft.read_data_set_chunksize(self._ctx, 16 * KiB)) 29 | self._err_wrap(ft.write_data_set_chunksize(self._ctx, 16 * KiB)) 30 | return self 31 | 32 | def __exit__(self, type, value, traceback): 33 | self._err_wrap(ft.usb_close(self._ctx)) 34 | ft.deinit(self._ctx) 35 | 36 | def write(self, data): 37 | bytes_wrote = self._err_wrap(ft.write_data(self._ctx, data)) 38 | return bytes_wrote 39 | 40 | def read(self, n): 41 | bytes_read, data = ft.read_data(self._ctx, n) 42 | self._err_wrap(bytes_read) 43 | return (bytes_read, data) 44 | 45 | def __cmd(self, code, data): 46 | return ((0xAA << 56) | (code << 40) | (data << 8) | 0x55).to_bytes(8, 'little') 47 | 48 | def test_led(self): 49 | self.write(self.__cmd(0x1ED0, 1)) 50 | sleep(2) 51 | self.write(self.__cmd(0x1ED0, 0)) 52 | sleep(2) 53 | 54 | def test_read(self, total_bytes=1 * MiB): 55 | # Prepare data 56 | golden_data = [i % 256 for i in range(total_bytes)] 57 | 58 | # Start read test 59 | self._err_wrap(ft.tcioflush(self._ctx)) 60 | self.write(self.__cmd(0xBEEF, total_bytes - 1)) 61 | 62 | # Receive data 63 | chunks = [] 64 | start_time = time() 65 | while total_bytes > 0: 66 | chunk_len, chunk = self.read(16 * KiB if total_bytes > 16 * KiB else total_bytes) 67 | if chunk_len == 0: 68 | break 69 | else: 70 | chunks.append(chunk[:chunk_len]) 71 | total_bytes -= chunk_len 72 | exec_time = time() - start_time 73 | 74 | # Print statistics 75 | data = [b for chunk in chunks for b in chunk] # flatten all chunks 76 | # print(data) 77 | data_len = len(data) 78 | data_len_mb = data_len / MiB 79 | print("Read %.02f MiB (%d bytes) from FPGA in %f seconds (%.02f MiB/s)" % 80 | (data_len_mb, data_len, exec_time, data_len_mb / exec_time)) 81 | 82 | # Verify data 83 | print("Verify data: %s" % ('ok' if golden_data == data else 'error')) 84 | 85 | def test_write(self, total_bytes=1 * MiB): 86 | # Prepare data 87 | data = bytes(bytearray([i % 256 for i in range(total_bytes)])) 88 | 89 | # Start write test 90 | self._err_wrap(ft.tcioflush(self._ctx)) 91 | self.write(self.__cmd(0xCAFE, total_bytes - 1)) 92 | 93 | # Transmit data 94 | result = 0 95 | start_time = time() 96 | self.write(data) 97 | while not result: 98 | result_len, result = self.read(1) 99 | if result_len == 0: 100 | result = 0 101 | exec_time = time() - start_time 102 | 103 | # Print statistics 104 | data_len_mb = total_bytes / MiB 105 | print("Wrote %.02f MiB (%d bytes) to FPGA in %f seconds (%.02f MiB/s)" % 106 | (data_len_mb, total_bytes, exec_time, data_len_mb / exec_time)) 107 | 108 | # Verify data 109 | result = 0 if not result else result[0] 110 | print("Verify data: %s" % ('ok' if result == 0x42 else 'error')) 111 | 112 | 113 | if __name__ == '__main__': 114 | with FPGA('FT3C8Z0A') as de10lite: 115 | de10lite.test_led() 116 | de10lite.test_read(100 * MiB) 117 | de10lite.test_write(100 * MiB) 118 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/test_pylibftdi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pylibftdi import Driver, Device 4 | from time import time, sleep 5 | 6 | KiB = 1024 7 | MiB = KiB * 1024 8 | 9 | 10 | class FPGA(Device): 11 | def __init__(self, ftdi_serial, fifo245_mode): 12 | super().__init__(device_id=ftdi_serial, mode='b', 13 | lazy_open=True, interface_select=1) 14 | self.fifo245_mode = fifo245_mode 15 | 16 | def __enter__(self): 17 | super().open() 18 | self.ftdi_fn.ftdi_set_bitmode(0, 0x40 if self.fifo245_mode == 'sync' else 0x00) 19 | self.flush() 20 | return self 21 | 22 | def __exit__(self, exc_type, exc_val, exc_tb): 23 | super().close() 24 | 25 | def __cmd(self, code, data): 26 | return ((0xAA << 56) | (code << 40) | (data << 8) | 0x55).to_bytes(8, 'little') 27 | 28 | def test_led(self): 29 | self.write(self.__cmd(0x1ED0, 1)) 30 | sleep(2) 31 | self.write(self.__cmd(0x1ED0, 0)) 32 | sleep(2) 33 | 34 | def test_read(self, total_bytes=1 * MiB): 35 | # Prepare data 36 | golden_data = [i % 256 for i in range(total_bytes)] 37 | 38 | # Start read test 39 | self.write(self.__cmd(0xBEEF, total_bytes - 1)) 40 | 41 | # Receive data 42 | self.flush() 43 | start_time = time() 44 | data = self.read(total_bytes) 45 | exec_time = time() - start_time 46 | 47 | # Print statistics 48 | data = [b for b in data] 49 | data_len = len(data) 50 | data_len_mb = data_len / MiB 51 | print("Read %.02f MiB (%d bytes) from FPGA in %f seconds (%.02f MiB/s)" % 52 | (data_len_mb, data_len, exec_time, data_len_mb / exec_time)) 53 | 54 | # Verify data 55 | print("Verify data: %s" % ('ok' if golden_data == data else 'error')) 56 | 57 | def test_write(self, total_bytes=1 * MiB): 58 | # Prepare data 59 | data = bytes(bytearray([i % 256 for i in range(total_bytes)])) 60 | 61 | # Start write test 62 | self.write(self.__cmd(0xCAFE, total_bytes - 1)) 63 | 64 | # Transmit data 65 | self.flush() 66 | result = 0 67 | start_time = time() 68 | self.write(data) 69 | while not result: 70 | result = self.read(1) 71 | exec_time = time() - start_time 72 | 73 | # Print statistics 74 | data_len_mb = total_bytes / MiB 75 | print("Wrote %.02f MiB (%d bytes) to FPGA in %f seconds (%.02f MiB/s)" % 76 | (data_len_mb, total_bytes, exec_time, data_len_mb / exec_time)) 77 | 78 | # Verify data 79 | result = 0 if not result else result[0] 80 | print("Verify data: %s" % ('ok' if result == 0x42 else 'error')) 81 | 82 | 83 | if __name__ == "__main__": 84 | with FPGA(ftdi_serial='FT3C8Z0A', fifo245_mode='sync') as de10lite: 85 | de10lite.test_led() 86 | de10lite.test_read(100 * MiB) 87 | de10lite.test_write(100 * MiB) 88 | -------------------------------------------------------------------------------- /examples/ft2232h_de10lite/test_pyusb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import usb.core 4 | import usb.util 5 | from time import time, sleep 6 | 7 | KiB = 1024 8 | MiB = KiB * 1024 9 | 10 | 11 | class FPGA: 12 | def __init__(self, serial, sync=True, vid=0x0403, pid=0x6010): 13 | self._vid = vid 14 | self._pid = pid 15 | self._serial = serial 16 | self._sync = sync 17 | 18 | def __enter__(self): 19 | dev = usb.core.find(idVendor=self._vid, idProduct=self._pid) 20 | if dev is None or dev.serial_number != self._serial: 21 | raise Exception("Device was not found!") 22 | self._ft = dev 23 | if self._ft.is_kernel_driver_active(0): 24 | self._ft.detach_kernel_driver(0) 25 | usb.util.claim_interface(self._ft, 0) 26 | self._ft.ctrl_transfer(bmRequestType=0x40, bRequest=11, wValue=0x000140ff if self._sync else 0x000000ff) 27 | return self 28 | 29 | def __exit__(self, type, value, traceback): 30 | usb.util.release_interface(self._ft, 0) 31 | 32 | def write(self, data): 33 | self._ft.write(0x2, data) # OUT EP 34 | 35 | def read(self, n): 36 | return self._ft.read(0x81, n, 100) # IN EP 37 | 38 | def __cmd(self, code, data): 39 | return ((0xAA << 56) | (code << 40) | (data << 8) | 0x55).to_bytes(8, 'little') 40 | 41 | def test_led(self): 42 | self.write(self.__cmd(0x1ED0, 1)) 43 | sleep(2) 44 | self.write(self.__cmd(0x1ED0, 0)) 45 | sleep(2) 46 | 47 | def test_read(self, total_bytes=1 * MiB): 48 | # Prepare data 49 | golden_data = [i % 256 for i in range(total_bytes)] 50 | 51 | # Start read test 52 | self.write(self.__cmd(0xBEEF, total_bytes - 1)) 53 | 54 | # Receive data 55 | chunks = [] 56 | start_time = time() 57 | while total_bytes > 0: 58 | chunk = self.read(256 * KiB) 59 | chunk_len = len(chunk) 60 | if chunk_len == 0: 61 | break 62 | elif chunk_len > 2: # skip if read modem status bytes only 63 | chunks.append(chunk) 64 | modem_bytes = ((chunk_len // 512) + (1 if chunk_len % 512 else 0)) * 2 65 | total_bytes -= (chunk_len - modem_bytes) 66 | exec_time = time() - start_time 67 | 68 | # Print statistics 69 | data = [b for chunk in chunks for b in chunk] # flatten all chunks 70 | # strips the two modem status bytes transfered during every read 71 | data = [b for i, b in enumerate(data) if i % 512 not in [0, 1]] 72 | data_len = len(data) 73 | data_len_mb = data_len / MiB 74 | print("Read %.02f MiB (%d bytes) from FPGA in %f seconds (%.02f MiB/s)" % 75 | (data_len_mb, data_len, exec_time, data_len_mb / exec_time)) 76 | 77 | # Verify data 78 | print("Verify data: %s" % ('ok' if golden_data == data else 'error')) 79 | 80 | def test_write(self, total_bytes=1 * MiB): 81 | # Prepare data 82 | data = bytes(bytearray([i % 256 for i in range(total_bytes)])) 83 | 84 | # Start write test 85 | self.write(self.__cmd(0xCAFE, total_bytes - 1)) 86 | 87 | # Transmit data 88 | result = 0 89 | start_time = time() 90 | self.write(data) 91 | while not result: 92 | data = self.read(3) 93 | if len(data) > 2: 94 | result = data[2] 95 | exec_time = time() - start_time 96 | 97 | # Print statistics 98 | data_len_mb = total_bytes / MiB 99 | print("Wrote %.02f MiB (%d bytes) to FPGA in %f seconds (%.02f MiB/s)" % 100 | (data_len_mb, total_bytes, exec_time, data_len_mb / exec_time)) 101 | 102 | # Verify data 103 | print("Verify data: %s" % ('ok' if result == 0x42 else 'error')) 104 | 105 | 106 | if __name__ == '__main__': 107 | with FPGA('FT3C8Z0A') as de10lite: 108 | de10lite.test_led() 109 | de10lite.test_read(100 * MiB) 110 | de10lite.test_write(10 * MiB) 111 | -------------------------------------------------------------------------------- /src/dpram.sv: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Dual port RAM primitive 3 | //------------------------------------------------------------------- 4 | module dpram #( 5 | parameter ADDR_W = 10, // Memory depth 6 | parameter DATA_W = 8, // Data width 7 | parameter INIT_FILE = "" // Path to initial file 8 | )( 9 | // Write interface 10 | input logic wclk, // Write clock 11 | input logic [DATA_W-1:0] wdata, // Write data 12 | input logic [ADDR_W-1:0] waddr, // Write address 13 | input logic wr, // Write operation enable 14 | // Read interface 15 | input logic rclk, // Read clock 16 | output logic [DATA_W-1:0] rdata, // Read data 17 | input logic [ADDR_W-1:0] raddr, // Read address 18 | input logic rd // Read operation enable 19 | ); 20 | 21 | // Memory array 22 | logic [DATA_W-1:0] mem [2**ADDR_W-1:0]; 23 | 24 | // Init memory 25 | initial begin 26 | if (INIT_FILE) 27 | $readmemh(INIT_FILE, mem); 28 | end 29 | 30 | // Write port 31 | always_ff @(posedge wclk) begin 32 | if (wr) 33 | mem[waddr] <= wdata; 34 | end 35 | 36 | // Read port 37 | always_ff @(posedge rclk) begin 38 | if (rd) 39 | rdata <= mem[raddr]; 40 | end 41 | 42 | endmodule 43 | -------------------------------------------------------------------------------- /src/fifo_async.sv: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Two clock (asynchronous) FIFO 3 | //------------------------------------------------------------------------------ 4 | module fifo_async #( 5 | parameter ADDR_W = 10, 6 | parameter DATA_W = 8, 7 | parameter WORDS_TOTAL = 2 ** ADDR_W, // might be less if needed 8 | // Derived parameters 9 | parameter PTR_W = ADDR_W + 1 10 | )( 11 | // write side 12 | input logic wclk, 13 | input logic wrst, 14 | output logic [ADDR_W:0] wload, 15 | input logic [DATA_W-1:0] wdata, 16 | input logic wen, 17 | output logic wfull, 18 | // read sidemode 19 | input logic rclk, 20 | input logic rrst, 21 | output logic [ADDR_W:0] rload, 22 | output logic [DATA_W-1:0] rdata, 23 | input logic ren, 24 | output logic rvalid, 25 | output logic rempty 26 | ); 27 | 28 | //------------------------------------------------------------------------------ 29 | // Functions 30 | //------------------------------------------------------------------------------ 31 | integer i; 32 | function [PTR_W-1:0] gray2bin (input [PTR_W-1:0] gray); 33 | for (i=0; i> i); 35 | end 36 | endfunction 37 | 38 | function [PTR_W-1:0] bin2gray (input [PTR_W-1:0] bin); 39 | bin2gray = (bin >> 1) ^ bin; 40 | endfunction 41 | 42 | function [PTR_W-1:0] load (input [PTR_W-1:0] wptr, input [PTR_W-1:0] rptr); 43 | load = (rptr <= wptr)? (wptr - rptr) : wptr + (PTR_W'(2 * WORDS_TOTAL) - rptr); 44 | endfunction 45 | 46 | //------------------------------------------------------------------------------ 47 | // Variables 48 | //------------------------------------------------------------------------------ 49 | logic wr; 50 | logic [PTR_W-1:0] wptr; 51 | logic [PTR_W-1:0] wptr_next; 52 | logic [PTR_W-1:0] wptr_sync0; 53 | logic [PTR_W-1:0] wptr_sync1; 54 | logic [PTR_W-1:0] wptr_sync1_bin; 55 | logic [PTR_W-1:0] waddr; 56 | 57 | logic rd; 58 | logic [PTR_W-1:0] rptr; 59 | logic [PTR_W-1:0] rptr_next; 60 | logic [PTR_W-1:0] rptr_sync0; 61 | logic [PTR_W-1:0] rptr_sync1; 62 | logic [PTR_W-1:0] rptr_sync1_bin; 63 | logic [PTR_W-1:0] raddr; 64 | 65 | logic [DATA_W-1:0] mem [2 ** ADDR_W]; 66 | 67 | //------------------------------------------------------------------------------ 68 | // Write side 69 | //------------------------------------------------------------------------------ 70 | assign wr = wen & ~wfull; 71 | assign wptr_next = wr ? gray2bin(wptr) + 1'b1 : gray2bin(wptr); 72 | 73 | // write pointer is stored in the gray code 74 | always_ff @(posedge wclk) begin 75 | if (wrst) 76 | wptr <= '0; 77 | else 78 | wptr <= bin2gray(wptr_next); 79 | end 80 | assign waddr = gray2bin(wptr); 81 | 82 | // read pointer is also in the gray code - so it can be syncronized easily 83 | always_ff @(posedge wclk) begin 84 | if (wrst) begin 85 | rptr_sync0 <= '0; 86 | rptr_sync1 <= '0; 87 | end else begin 88 | rptr_sync0 <= rptr; 89 | rptr_sync1 <= rptr_sync0; 90 | end 91 | end 92 | 93 | // do load logic in a binary form 94 | assign rptr_sync1_bin = gray2bin(rptr_sync1); 95 | always_ff @(posedge wclk) begin 96 | if (wrst) 97 | wload <= '0; 98 | else 99 | wload <= load(wptr_next, rptr_sync1_bin); 100 | end 101 | assign wfull = (wload == WORDS_TOTAL); 102 | 103 | //------------------------------------------------------------------------------ 104 | // Read side 105 | //------------------------------------------------------------------------------ 106 | assign rd = ren & ~rempty; 107 | assign rptr_next = rd ? gray2bin(rptr) + 1'b1 : gray2bin(rptr); 108 | 109 | // read pointer is stored in the gray code 110 | always_ff @(posedge rclk) begin 111 | if (rrst) 112 | rptr <= 0; 113 | else 114 | rptr <= bin2gray(rptr_next); 115 | end 116 | assign raddr = gray2bin(rptr); 117 | 118 | // write pointer is also in the gray code - so it can be syncronized easily 119 | always_ff @(posedge rclk) begin 120 | if (rrst) begin 121 | wptr_sync0 <= '0; 122 | wptr_sync1 <= '0; 123 | end else begin 124 | wptr_sync0 <= wptr; 125 | wptr_sync1 <= wptr_sync0; 126 | end 127 | end 128 | 129 | always_ff @(posedge rclk) begin 130 | if (rrst) 131 | rvalid <= '0; 132 | else 133 | rvalid <= rd; 134 | end 135 | 136 | // do load logic in a binary form 137 | assign wptr_sync1_bin = gray2bin(wptr_sync1); 138 | always_ff @(posedge rclk) begin 139 | if (rrst) 140 | rload <= '0; 141 | else 142 | rload <= load(wptr_sync1_bin, rptr_next); 143 | end 144 | assign rempty = (rload == '0); 145 | 146 | //------------------------------------------------------------------------------ 147 | // RAM module 148 | //------------------------------------------------------------------------------ 149 | dpram #( 150 | .ADDR_W (ADDR_W), 151 | .DATA_W (DATA_W), 152 | .INIT_FILE ("") 153 | ) dpram ( 154 | // Write interface 155 | .wclk (wclk), 156 | .wdata (wdata), 157 | .waddr (waddr[ADDR_W-1:0]), 158 | .wr (wr), 159 | // Read interface 160 | .rclk (rclk), 161 | .rdata (rdata), 162 | .raddr (raddr[ADDR_W-1:0]), 163 | .rd (rd) 164 | ); 165 | 166 | endmodule 167 | -------------------------------------------------------------------------------- /src/fifo_sync.sv: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // One clock FIFO 3 | //------------------------------------------------------------------------------ 4 | module fifo_sync #( 5 | parameter ADDR_W = 10, 6 | parameter DATA_W = 8, 7 | parameter WORDS_TOTAL = 2 ** ADDR_W // might be less if needed 8 | )( 9 | input logic clk, 10 | input logic rst, 11 | output logic [ADDR_W:0] load, 12 | input logic [DATA_W-1:0] wdata, 13 | input logic wen, 14 | output logic full, 15 | output logic [DATA_W-1:0] rdata, 16 | input logic ren, 17 | output logic rvalid, 18 | output logic empty 19 | ); 20 | 21 | //------------------------------------------------------------------------------ 22 | // Variables 23 | //------------------------------------------------------------------------------ 24 | logic wr; 25 | logic [ADDR_W-1:0] waddr; 26 | 27 | logic rd; 28 | logic [ADDR_W-1:0] raddr; 29 | 30 | logic [DATA_W-1:0] mem [2 ** ADDR_W]; 31 | 32 | //------------------------------------------------------------------------------ 33 | // FIFO load counter 34 | //------------------------------------------------------------------------------ 35 | assign full = (load == WORDS_TOTAL); 36 | assign empty = (load == 0); 37 | 38 | assign wr = wen & ~full; 39 | assign rd = ren & ~empty; 40 | 41 | always_ff @(posedge clk) begin 42 | if (rst) 43 | load <= 0; 44 | else if (wr && !rd) 45 | load <= load + 1'b1; 46 | else if (rd && !wr) 47 | load <= load - 1'b1; 48 | end 49 | 50 | //------------------------------------------------------------------------------ 51 | // Write side 52 | //------------------------------------------------------------------------------ 53 | always @(posedge clk) begin 54 | if (rst) 55 | waddr <= 0; 56 | else if (wr) 57 | waddr <= waddr + 1'b1; 58 | end 59 | 60 | //------------------------------------------------------------------------------ 61 | // Read side 62 | //------------------------------------------------------------------------------ 63 | always_ff @(posedge clk) begin 64 | if (rst) 65 | raddr <= 0; 66 | else if (rd) 67 | raddr <= raddr + 1'b1; 68 | end 69 | 70 | always_ff @(posedge clk) begin 71 | if (rst) 72 | rvalid <= 0; 73 | else 74 | rvalid <= rd; 75 | end 76 | 77 | //------------------------------------------------------------------------------ 78 | // RAM module 79 | //------------------------------------------------------------------------------ 80 | dpram #( 81 | .ADDR_W (ADDR_W), 82 | .DATA_W (DATA_W), 83 | .INIT_FILE ("") 84 | ) dpram ( 85 | // Write interface 86 | .wclk (clk), 87 | .wdata (wdata), 88 | .waddr (waddr), 89 | .wr (wr), 90 | // Read interface 91 | .rclk (clk), 92 | .rdata (rdata), 93 | .raddr (raddr), 94 | .rd (rd) 95 | ); 96 | 97 | endmodule 98 | -------------------------------------------------------------------------------- /src/proto245a.sv: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // FT245-style asynchronous FIFO protocol master. 3 | // 4 | // This protocol is supported on the FTDI USB FullSpeed and HighSpeed devices: 5 | // - FT245R 6 | // - FT245BL 7 | // - FT240X 8 | // - FT232H 9 | // - FT2232D 10 | // - FT2232H 11 | // - FT232HP/FT233HP 12 | // - FT2232HP/FT2233HP 13 | // 14 | // Note: 15 | // Send immediate / wake up signal (SIWU) tied to inactive state. 16 | //------------------------------------------------------------------------------ 17 | module proto245a #( 18 | parameter DATA_W = 8, // FT chip data bus width 19 | parameter TX_FIFO_SIZE = 4096, // TXFIFO size in data words 20 | parameter RX_FIFO_SIZE = 4096, // RXFIFO size in data words 21 | parameter SINGLE_CLK_DOMAIN = 0, // If FT clock and FIFO clocks are from the same clock domain 22 | parameter READ_TICKS = 4, // Active RD# time (ft_clk based) 23 | parameter WRITE_TICKS = 4, // Active WR# time (ft_clk based) 24 | // Derived parameters 25 | parameter TX_FIFO_LOAD_W = $clog2(TX_FIFO_SIZE) + 1, 26 | parameter RX_FIFO_LOAD_W = $clog2(RX_FIFO_SIZE) + 1 27 | )( 28 | // FT clock and reset 29 | input logic ft_rst, // Active high synchronous reset (ft_clk domain) 30 | input logic ft_clk, // FT clock 31 | // FT interface - should be routed directly to IO 32 | input logic ft_rxfn, // FT RXF# signal 33 | input logic ft_txen, // FT TXE# signal 34 | input logic [DATA_W-1:0] ft_din, // FT DATA tri-state IOs: input 35 | output logic [DATA_W-1:0] ft_dout, // FT DATA tri-state IOs: output 36 | output logic ft_rdn, // FT RD# signal 37 | output logic ft_wrn, // FT WR# signal 38 | output logic ft_siwu, // FT SIWU signal 39 | // RX FIFO (Host -> FTDI chip -> FPGA -> FIFO) 40 | input logic rxfifo_clk, // RX FIFO clock 41 | input logic rxfifo_rst, // RX FIFO active high synchronous reset 42 | input logic rxfifo_rd, // RX FIFO read enable 43 | output logic [DATA_W-1:0] rxfifo_data, // RX FIFO read data 44 | output logic rxfifo_valid, // RX FIFO read data is valid 45 | output logic [RX_FIFO_LOAD_W-1:0] rxfifo_load, // RX FIFO load counter 46 | output logic rxfifo_empty, // RX FIFO is empty 47 | // TX FIFO (FIFO -> FPGA -> FTDI chip -> Host) 48 | input logic txfifo_clk, // TX FIFO clock 49 | input logic txfifo_rst, // TX FIFO active high synchronous reset 50 | input logic [DATA_W-1:0] txfifo_data, // TX FIFO write data 51 | input logic txfifo_wr, // TX FIFO read enable 52 | output logic [TX_FIFO_LOAD_W-1:0] txfifo_load, // TX FIFO load counter 53 | output logic txfifo_full // TX FIFO is full 54 | ); 55 | 56 | localparam TX_FIFO_ADDR_W = $clog2(TX_FIFO_SIZE); 57 | localparam RX_FIFO_ADDR_W = $clog2(RX_FIFO_SIZE); 58 | 59 | //------------------------------------------------------------------- 60 | // From FT chip 61 | //------------------------------------------------------------------- 62 | logic [DATA_W-1:0] din; 63 | logic rxfn_ff0; 64 | logic rxfn_ff1; 65 | logic txen_ff0; 66 | logic txen_ff1; 67 | logic din_valid, din_valid_next; 68 | logic ft_not_empty, ft_not_full; 69 | logic ft_empty, ft_full; 70 | 71 | always_ff @(posedge ft_clk) begin 72 | if (ft_rst) begin 73 | din <= 0; 74 | rxfn_ff0 <= 1'b1; 75 | rxfn_ff1 <= 1'b1; 76 | txen_ff0 <= 1'b0; 77 | txen_ff1 <= 1'b0; 78 | end else begin 79 | din <= ft_din; 80 | rxfn_ff0 <= ft_rxfn; 81 | rxfn_ff1 <= rxfn_ff0; 82 | txen_ff0 <= ft_txen; 83 | txen_ff1 <= txen_ff0; 84 | end 85 | end 86 | 87 | assign ft_not_empty = ~rxfn_ff1; 88 | assign ft_empty = rxfn_ff1; 89 | assign ft_not_full = ~txen_ff1; 90 | assign ft_full = txen_ff1; 91 | 92 | //------------------------------------------------------------------- 93 | // RX FIFO 94 | //------------------------------------------------------------------- 95 | logic [RX_FIFO_LOAD_W-1:0] rxfifo_wload; 96 | logic rxfifo_full; 97 | logic [DATA_W-1:0] rxfifo_wdata; 98 | logic rxfifo_wen, rxfifo_wen_next; 99 | 100 | assign rxfifo_wdata = din; 101 | 102 | generate if (SINGLE_CLK_DOMAIN) begin: rxfifo_sync_genblk 103 | fifo_sync #( 104 | .ADDR_W (RX_FIFO_ADDR_W), 105 | .DATA_W (DATA_W) 106 | ) rxfifo ( 107 | .clk (ft_clk), 108 | .rst (ft_rst), 109 | .load (rxfifo_wload), 110 | .wdata (rxfifo_wdata), 111 | .wen (rxfifo_wen), 112 | .full (rxfifo_full), 113 | .rdata (rxfifo_data), 114 | .ren (rxfifo_rd), 115 | .rvalid (rxfifo_valid), 116 | .empty (rxfifo_empty) 117 | ); 118 | assign rxfifo_load = rxfifo_wload; 119 | end else begin: rxfifo_async_genblk 120 | fifo_async #( 121 | .ADDR_W (RX_FIFO_ADDR_W), 122 | .DATA_W (DATA_W) 123 | ) rxfifo ( 124 | // write side - from FT chip 125 | .wclk (ft_clk), 126 | .wrst (ft_rst), 127 | .wload (rxfifo_wload), 128 | .wdata (rxfifo_wdata), 129 | .wen (rxfifo_wen), 130 | .wfull (rxfifo_full), 131 | // read side - to FPGA system 132 | .rclk (rxfifo_clk), 133 | .rrst (rxfifo_rst), 134 | .rload (rxfifo_load), 135 | .rdata (rxfifo_data), 136 | .ren (rxfifo_rd), 137 | .rvalid (rxfifo_valid), 138 | .rempty (rxfifo_empty) 139 | ); 140 | end endgenerate 141 | 142 | //------------------------------------------------------------------- 143 | // TX FIFO 144 | //------------------------------------------------------------------- 145 | logic [DATA_W-1:0] txfifo_rdata; 146 | logic txfifo_rvalid; 147 | logic [TX_FIFO_LOAD_W-1:0] txfifo_rload; 148 | logic txfifo_empty; 149 | logic txfifo_ren, txfifo_ren_next; 150 | 151 | generate if (SINGLE_CLK_DOMAIN) begin: txfifo_sync_genblk 152 | fifo_sync #( 153 | .ADDR_W (TX_FIFO_ADDR_W), 154 | .DATA_W (DATA_W) 155 | ) txfifo ( 156 | .clk (ft_clk), 157 | .rst (ft_rst), 158 | .load (txfifo_rload), 159 | .wdata (txfifo_data), 160 | .wen (txfifo_wr), 161 | .full (txfifo_full), 162 | .rdata (txfifo_rdata), 163 | .ren (txfifo_ren), 164 | .rvalid (txfifo_rvalid), 165 | .empty (txfifo_empty) 166 | ); 167 | assign txfifo_load = txfifo_rload; 168 | end else begin: txfifo_async_genblk 169 | fifo_async #( 170 | .ADDR_W (TX_FIFO_ADDR_W), 171 | .DATA_W (DATA_W) 172 | ) txfifo ( 173 | // write side - from system 174 | .wclk (txfifo_clk), 175 | .wrst (txfifo_rst), 176 | .wload (txfifo_load), 177 | .wdata (txfifo_data), 178 | .wen (txfifo_wr), 179 | .wfull (txfifo_full), 180 | // read side - to FT chip 181 | .rclk (ft_clk), 182 | .rrst (ft_rst), 183 | .rload (txfifo_rload), 184 | .rdata (txfifo_rdata), 185 | .ren (txfifo_ren), 186 | .rvalid (txfifo_rvalid), 187 | .rempty (txfifo_empty) 188 | ); 189 | end endgenerate 190 | 191 | //------------------------------------------------------------------- 192 | // Protocol FSM 193 | //------------------------------------------------------------------- 194 | localparam RD_CNT_W = $clog2(READ_TICKS); 195 | localparam RD_CNT_MAX = RD_CNT_W'(READ_TICKS - 1); 196 | localparam WR_CNT_W = $clog2(WRITE_TICKS); 197 | localparam WR_CNT_MAX = WR_CNT_W'(WRITE_TICKS - 1); 198 | 199 | enum logic [2:0] { 200 | IDLE_S, 201 | START_TX_S, 202 | TX_S, 203 | END_TX_S, 204 | RX_S, 205 | END_RX_S 206 | } fsm_state, fsm_next; 207 | 208 | logic [DATA_W-1:0] dout; 209 | logic rdn; 210 | logic wrn; 211 | logic [DATA_W-1:0] dout_next; 212 | logic rdn_next; 213 | logic wrn_next; 214 | 215 | logic [RD_CNT_W-1:0] rd_cnt, rd_cnt_next; 216 | logic [WR_CNT_W-1:0] wr_cnt, wr_cnt_next; 217 | 218 | always_comb begin 219 | fsm_next = fsm_state; 220 | dout_next = dout; 221 | rdn_next = rdn; 222 | wrn_next = wrn; 223 | rxfifo_wen_next = 1'b0; 224 | txfifo_ren_next = 1'b0; 225 | rd_cnt_next = rd_cnt; 226 | wr_cnt_next = wr_cnt; 227 | 228 | case (fsm_state) 229 | IDLE_S: begin 230 | if (ft_not_empty && !rxfifo_full) begin 231 | // go receive, if FT chip has some data and our receive fifo is not full 232 | rdn_next = 1'b0; 233 | fsm_next = RX_S; 234 | end else if (ft_not_full && !txfifo_empty) begin 235 | // go transmit, if FT chip has empty space and our tranmsmit fifo is not empty 236 | fsm_next = START_TX_S; 237 | txfifo_ren_next = 1'b1; 238 | end 239 | end 240 | 241 | RX_S: begin 242 | if (rd_cnt == '0) begin 243 | rd_cnt_next = RD_CNT_MAX; 244 | rxfifo_wen_next = 1'b1; 245 | rdn_next = 1'b1; 246 | fsm_next = END_RX_S; 247 | end else begin 248 | rd_cnt_next = rd_cnt - 1'b1; 249 | end 250 | end 251 | 252 | END_RX_S: begin 253 | if (ft_empty) begin 254 | fsm_next = IDLE_S; 255 | end 256 | end 257 | 258 | START_TX_S : begin 259 | if (txfifo_rvalid) begin 260 | dout_next = txfifo_rdata; 261 | fsm_next = TX_S; 262 | end 263 | end 264 | 265 | TX_S : begin 266 | wrn_next = 1'b0; 267 | if (wr_cnt == '0) begin 268 | wr_cnt_next = WR_CNT_MAX; 269 | fsm_next = END_TX_S; 270 | end else begin 271 | wr_cnt_next = wr_cnt - 1'b1; 272 | end 273 | end 274 | 275 | END_TX_S: begin 276 | wrn_next = 1'b1; 277 | if (ft_full) begin 278 | fsm_next = IDLE_S; 279 | end 280 | end 281 | 282 | default: begin 283 | //do nothing 284 | end 285 | endcase 286 | end 287 | 288 | always_ff @(posedge ft_clk) begin 289 | if (ft_rst) begin 290 | fsm_state <= IDLE_S; 291 | rdn <= 1'b1; 292 | wrn <= 1'b1; 293 | dout <= '0; 294 | rxfifo_wen <= 1'b0; 295 | txfifo_ren <= 1'b0; 296 | rd_cnt <= RD_CNT_MAX; 297 | wr_cnt <= WR_CNT_MAX; 298 | end else begin 299 | fsm_state <= fsm_next; 300 | rdn <= rdn_next; 301 | wrn <= wrn_next; 302 | dout <= dout_next; 303 | rxfifo_wen <= rxfifo_wen_next; 304 | txfifo_ren <= txfifo_ren_next; 305 | rd_cnt <= rd_cnt_next; 306 | wr_cnt <= wr_cnt_next; 307 | end 308 | end 309 | 310 | //------------------------------------------------------------------- 311 | // To FT chip 312 | //------------------------------------------------------------------- 313 | assign ft_rdn = rdn; 314 | assign ft_wrn = wrn; 315 | assign ft_dout = dout; 316 | assign ft_siwu = 1'b1; 317 | 318 | endmodule -------------------------------------------------------------------------------- /src/proto245s.sv: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // FT245-style synchronous FIFO protocol master. 3 | // 4 | // This protocol is supported on the FTDI USB HighSpeed and SuperSpeed devices: 5 | // - FT232H 6 | // - FT2232H 7 | // - FT232HP/FT233HP 8 | // - FT2232HP/FT2233HP 9 | // - FT600Q/FT601Q 10 | // - FT602Q 11 | // 12 | // Note for the FT2xx chips: 13 | // Send immediate / wake up signal (SIWU) tied to inactive state. 14 | // 15 | // Note for the FT60x chips: 16 | // Byte enable signals (BE) are not supported at the moment. 17 | // So, all transactions have to be word aligned. 18 | //------------------------------------------------------------------------------ 19 | module proto245s #( 20 | parameter DATA_W = 8, // FT chip data bus width 21 | parameter TX_FIFO_SIZE = 4096, // TXFIFO size in data words 22 | parameter TX_START_THRESHOLD = 1024, // TXFIFO is ready to trasmit data to the chip if TXFIFO is filled >= threshold 23 | parameter TX_BURST_SIZE = 0, // Maximum number of words inside write (send) burst; use 0 to disable this feature and enable unlimited bursts 24 | parameter TX_BACKOFF_TIMEOUT = 64, // Number of ticks after last TXFIFO write when tx transaction will be forced to start 25 | parameter RX_FIFO_SIZE = 4096, // RXFIFO size in data words 26 | parameter RX_START_THRESHOLD = 3072, // RXFIFO is ready to receive data from the chip if RXFIFO FIFO is filled <= threshold 27 | parameter RX_BURST_SIZE = 0, // Maximum number of words inside read (receive) burst; use 0 to disable this feature and enable unlimited bursts 28 | parameter SINGLE_CLK_DOMAIN = 0, // If FT clock and FIFO clocks are from the same clock domain 29 | parameter TURNAROUND_TICKS = 4, // Number of ticks (pause) after every burst 30 | // Derived parameters 31 | parameter BE_W = DATA_W / 8 + ((DATA_W % 8 != 0) ? 1 : 0), 32 | parameter TX_FIFO_LOAD_W = $clog2(TX_FIFO_SIZE) + 1, 33 | parameter RX_FIFO_LOAD_W = $clog2(RX_FIFO_SIZE) + 1 34 | )( 35 | // FT interface - should be routed directly to IO 36 | input logic ft_rst, // Active high synchronous reset (ft_clk domain) 37 | input logic ft_clk, // FT CLOCKOUT signal 38 | input logic ft_rxfn, // FT RXF# signal 39 | input logic ft_txen, // FT TXE# signal 40 | input logic [DATA_W-1:0] ft_din, // FT DATA tri-state IOs: input 41 | output logic [DATA_W-1:0] ft_dout, // FT DATA tri-state IOs: output 42 | input logic [BE_W-1:0] ft_bein, // FT BE tri-state IOs: input 43 | output logic [BE_W-1:0] ft_beout, // FT BE tri-state IOs: output 44 | output logic ft_rdn, // FT RD# signal 45 | output logic ft_wrn, // FT WR# signal 46 | output logic ft_oen, // FT OE# signal 47 | output logic ft_siwu, // FT SIWU signal 48 | // RX FIFO (Host -> FTDI chip -> FPGA -> FIFO) 49 | input logic rxfifo_clk, // RX FIFO clock 50 | input logic rxfifo_rst, // RX FIFO active high synchronous reset 51 | input logic rxfifo_rd, // RX FIFO read enable 52 | output logic [DATA_W-1:0] rxfifo_data, // RX FIFO read data 53 | output logic rxfifo_valid, // RX FIFO read data is valid 54 | output logic [RX_FIFO_LOAD_W-1:0] rxfifo_load, // RX FIFO load counter 55 | output logic rxfifo_empty, // RX FIFO is empty 56 | // TX FIFO (FIFO -> FPGA -> FTDI chip -> Host) 57 | input logic txfifo_clk, // RX FIFO clock 58 | input logic txfifo_rst, // RX FIFO active high synchronous reset 59 | input logic [DATA_W-1:0] txfifo_data, // TXFIFO write data 60 | input logic txfifo_wr, // TXFIFO read enable 61 | output logic [TX_FIFO_LOAD_W-1:0] txfifo_load, // TXFIFO load counter 62 | output logic txfifo_full // TXFIFO is full 63 | ); 64 | 65 | localparam TX_FIFO_ADDR_W = $clog2(TX_FIFO_SIZE); 66 | localparam RX_FIFO_ADDR_W = $clog2(RX_FIFO_SIZE); 67 | 68 | //------------------------------------------------------------------- 69 | // From FT chip 70 | //------------------------------------------------------------------- 71 | logic [DATA_W-1:0] din; 72 | logic rxfn; 73 | logic txen; 74 | logic din_valid, din_valid_next; 75 | logic ft_not_empty, ft_not_full; 76 | logic ft_empty, ft_full; 77 | 78 | always_ff @(posedge ft_clk) begin 79 | if (ft_rst) begin 80 | din <= 0; 81 | rxfn <= 1'b1; 82 | txen <= 1'b0; 83 | end else begin 84 | din <= ft_din; 85 | rxfn <= ft_rxfn; 86 | txen <= ft_txen; 87 | end 88 | end 89 | 90 | assign ft_not_empty = ~rxfn; 91 | assign ft_empty = rxfn; 92 | assign ft_not_full = ~txen; 93 | assign ft_full = txen; 94 | 95 | //------------------------------------------------------------------- 96 | // RX FIFO 97 | //------------------------------------------------------------------- 98 | logic [RX_FIFO_LOAD_W-1:0] rxfifo_wload; 99 | logic rxfifo_ready; 100 | logic rxfifo_full; 101 | logic [DATA_W-1:0] rxfifo_wdata; 102 | logic rxfifo_wvalid; 103 | 104 | always_ff @(posedge ft_clk) begin 105 | if (ft_rst) begin 106 | rxfifo_wdata <= 0; 107 | rxfifo_wvalid <= 1'b0; 108 | end else begin 109 | rxfifo_wdata <= din; 110 | rxfifo_wvalid <= din_valid & ft_not_empty; 111 | end 112 | end 113 | 114 | generate if (SINGLE_CLK_DOMAIN) begin: rxfifo_sync_genblk 115 | fifo_sync #( 116 | .ADDR_W (RX_FIFO_ADDR_W), 117 | .DATA_W (DATA_W) 118 | ) rxfifo ( 119 | .clk (ft_clk), 120 | .rst (ft_rst), 121 | .load (rxfifo_wload), 122 | .wdata (rxfifo_wdata), 123 | .wen (rxfifo_wvalid), 124 | .full (rxfifo_full), 125 | .rdata (rxfifo_data), 126 | .ren (rxfifo_rd), 127 | .rvalid (rxfifo_valid), 128 | .empty (rxfifo_empty) 129 | ); 130 | assign rxfifo_load = rxfifo_wload; 131 | end else begin: rxfifo_async_genblk 132 | fifo_async #( 133 | .ADDR_W (RX_FIFO_ADDR_W), 134 | .DATA_W (DATA_W) 135 | ) rxfifo ( 136 | // write side - from FT chip 137 | .wclk (ft_clk), 138 | .wrst (ft_rst), 139 | .wload (rxfifo_wload), 140 | .wdata (rxfifo_wdata), 141 | .wen (rxfifo_wvalid), 142 | .wfull (rxfifo_full), 143 | // read side - to FPGA system 144 | .rclk (rxfifo_clk), 145 | .rrst (rxfifo_rst), 146 | .rload (rxfifo_load), 147 | .rdata (rxfifo_data), 148 | .ren (rxfifo_rd), 149 | .rvalid (rxfifo_valid), 150 | .rempty (rxfifo_empty) 151 | ); 152 | end endgenerate 153 | assign rxfifo_ready = (rxfifo_wload <= RX_START_THRESHOLD); 154 | 155 | // When rxfifo becomes full, we need to save last data words already pushed out 156 | // from the FT to prevent data loss. 157 | // Maximum rxfifo overflow is 4 words in the current configuration. 158 | // In general, it is equal to the number of ticks between rxfifo_full assertion and ft_rdn deassertion + 1 tick. 159 | // So we have to slightly move full threshold to earn some space for the overflow handling. 160 | localparam RX_OVERFLOW_MAX = 4; 161 | logic rxfifo_almost_full; 162 | assign rxfifo_almost_full = (rxfifo_wload >= (RX_FIFO_SIZE - RX_OVERFLOW_MAX)); 163 | 164 | //------------------------------------------------------------------- 165 | // RX burst counter 166 | //------------------------------------------------------------------- 167 | logic rx_burst_end; 168 | generate if (RX_BURST_SIZE != 0) begin: rx_burst_genblk 169 | logic [$clog2(RX_BURST_SIZE)-1:0] rx_burst_cnt; 170 | always_ff @(posedge ft_clk) begin 171 | if (ft_rst) begin 172 | rx_burst_cnt <= 0; 173 | end else if (rxfifo_wvalid) begin 174 | rx_burst_cnt <= rx_burst_cnt + 1'b1; 175 | end else begin 176 | rx_burst_cnt <= 0; 177 | end 178 | end 179 | assign rx_burst_end = (rx_burst_cnt == (RX_BURST_SIZE - RX_OVERFLOW_MAX)); 180 | end else begin 181 | assign rx_burst_end = 1'b0; 182 | end endgenerate 183 | 184 | //------------------------------------------------------------------- 185 | // TX FIFO 186 | //------------------------------------------------------------------- 187 | logic [DATA_W-1:0] txfifo_rdata, txfifo_rdata_prev; 188 | logic txfifo_rvalid; 189 | logic [TX_FIFO_LOAD_W-1:0] txfifo_rload; 190 | logic txfifo_ready; 191 | logic txfifo_empty; 192 | logic txfifo_ren, txfifo_ren_next; 193 | 194 | logic txovrbuf_wr; 195 | 196 | always_ff @(posedge ft_clk) begin 197 | if (ft_rst) begin 198 | txfifo_rdata_prev <= '0; 199 | end else begin 200 | txfifo_rdata_prev <= txfifo_rdata; 201 | end 202 | end 203 | 204 | generate if (SINGLE_CLK_DOMAIN) begin: txfifo_sync_genblk 205 | fifo_sync #( 206 | .ADDR_W (TX_FIFO_ADDR_W), 207 | .DATA_W (DATA_W) 208 | ) txfifo ( 209 | .clk (ft_clk), 210 | .rst (ft_rst), 211 | .load (txfifo_rload), 212 | .wdata (txfifo_data), 213 | .wen (txfifo_wr), 214 | .full (txfifo_full), 215 | .rdata (txfifo_rdata), 216 | .ren (txfifo_ren), 217 | .rvalid (txfifo_rvalid), 218 | .empty (txfifo_empty) 219 | ); 220 | assign txfifo_load = txfifo_rload; 221 | end else begin: txfifo_async_genblk 222 | fifo_async #( 223 | .ADDR_W (TX_FIFO_ADDR_W), 224 | .DATA_W (DATA_W) 225 | ) txfifo ( 226 | // write side - from system 227 | .wclk (txfifo_clk), 228 | .wrst (txfifo_rst), 229 | .wload (txfifo_load), 230 | .wdata (txfifo_data), 231 | .wen (txfifo_wr), 232 | .wfull (txfifo_full), 233 | // read side - to FT chip 234 | .rclk (ft_clk), 235 | .rrst (ft_rst), 236 | .rload (txfifo_rload), 237 | .rdata (txfifo_rdata), 238 | .ren (txfifo_ren), 239 | .rvalid (txfifo_rvalid), 240 | .rempty (txfifo_empty) 241 | ); 242 | end endgenerate 243 | 244 | // we need a timeout to prevent blocking small portion of data inside FIFO, 245 | // so after timeout expires - all data will be transmited 246 | logic [$clog2(TX_BACKOFF_TIMEOUT):0] backoff_timeout_cnt; 247 | 248 | // timeout counter resets every time tx fifo being written 249 | always_ff @(posedge ft_clk) begin 250 | if (ft_rst) 251 | backoff_timeout_cnt <= 0; 252 | else if (txfifo_wr || txovrbuf_wr) 253 | backoff_timeout_cnt <= 0; 254 | else if (backoff_timeout_cnt < TX_BACKOFF_TIMEOUT) 255 | backoff_timeout_cnt <= backoff_timeout_cnt + 1'b1; 256 | end 257 | 258 | assign txfifo_ready = (txfifo_rload >= TX_START_THRESHOLD) || 259 | ((backoff_timeout_cnt == TX_BACKOFF_TIMEOUT) && !txfifo_empty); 260 | 261 | //------------------------------------------------------------------- 262 | // TX overrun buffer 263 | //------------------------------------------------------------------- 264 | // maximum txfifo overrun is 4 words in the current configuration; 265 | // in general, it is equal to the number of ticks between ft_txen assertion and txfifo_ren deassertion plus 1 266 | localparam TX_OVERRUN_MAX = 4; 267 | 268 | localparam TXOVRBUF_ADDR_W = $clog2(TX_OVERRUN_MAX); 269 | localparam TXOVRBUF_LOAD_W = TXOVRBUF_ADDR_W + 1; 270 | 271 | logic [DATA_W-1:0] txovrbuf_rdata; 272 | logic [DATA_W-1:0] txovrbuf_wdata; 273 | logic txovrbuf_wr0, txovrbuf_wr1; 274 | logic [TXOVRBUF_LOAD_W-1:0] txovrbuf_load; 275 | logic txovrbuf_rvalid; 276 | logic txovrbuf_empty; 277 | logic txovrbuf_ren, txovrbuf_ren_next; 278 | logic txovrbuf_ready; 279 | 280 | always_ff @(posedge ft_clk) begin 281 | if (ft_rst) begin 282 | txovrbuf_wr0 <= 1'b0; 283 | txovrbuf_wr1 <= 1'b0; 284 | txovrbuf_wdata <= '0; 285 | end else begin 286 | txovrbuf_wr0 <= txfifo_rvalid; 287 | txovrbuf_wr1 <= txovrbuf_wr0; 288 | txovrbuf_wdata <= txfifo_rdata_prev; 289 | end 290 | end 291 | assign txovrbuf_wr = txovrbuf_wr0 | txovrbuf_wr1; 292 | 293 | // tx overrun buffer - when FT chip becomes full, 294 | // we need to save last data already pushed out from our txfifo to prevent data loss 295 | fifo_sync #( 296 | .ADDR_W (TXOVRBUF_ADDR_W), 297 | .DATA_W (DATA_W), 298 | .WORDS_TOTAL (TX_OVERRUN_MAX) 299 | ) txovrbuf ( 300 | .clk (ft_clk), 301 | .rst (ft_rst), 302 | .load (txovrbuf_load), 303 | .wdata (txovrbuf_wdata), 304 | .wen (txovrbuf_wr & ft_full), 305 | .full (), 306 | .rdata (txovrbuf_rdata), 307 | .ren (txovrbuf_ren), 308 | .rvalid (txovrbuf_rvalid), 309 | .empty (txovrbuf_empty) 310 | ); 311 | 312 | assign txovrbuf_ready = (backoff_timeout_cnt == TX_BACKOFF_TIMEOUT) && !txovrbuf_empty; 313 | 314 | //------------------------------------------------------------------- 315 | // TX burst counter 316 | //------------------------------------------------------------------- 317 | logic tx_burst_end; 318 | generate if (TX_BURST_SIZE != 0) begin: tx_burst_genblk 319 | logic [$clog2(TX_BURST_SIZE)-1:0] tx_burst_cnt; 320 | always_ff @(posedge ft_clk) begin 321 | if (ft_rst) begin 322 | tx_burst_cnt <= 0; 323 | end else if (txovrbuf_ren || txfifo_ren) begin 324 | tx_burst_cnt <= tx_burst_cnt + 1'b1; 325 | end else begin 326 | tx_burst_cnt <= 0; 327 | end 328 | end 329 | assign tx_burst_end = (tx_burst_cnt == (TX_BURST_SIZE - 1)); 330 | end else begin 331 | assign tx_burst_end = 1'b0; 332 | end endgenerate 333 | 334 | //------------------------------------------------------------------- 335 | // Protocol FSM 336 | //------------------------------------------------------------------- 337 | localparam TA_CNT_W = $clog2(TURNAROUND_TICKS); 338 | localparam TA_CNT_MAX = TA_CNT_W'(TURNAROUND_TICKS - 1); 339 | 340 | enum logic [2:0] { 341 | IDLE_S, 342 | TX_S, 343 | TX_OVERRUN_S, 344 | TURNAROUND_S, 345 | RX_S, 346 | RX_OVERFLOW0_S, 347 | RX_OVERFLOW1_S 348 | } fsm_state, fsm_next; 349 | 350 | logic [DATA_W-1:0] dout, dout_next; 351 | logic rdn, rdn_next; 352 | logic wrn, wrn_next; 353 | logic oen, oen_next; 354 | 355 | logic [TA_CNT_W-1:0] ta_cnt, ta_cnt_next; 356 | 357 | always_comb begin 358 | fsm_next = fsm_state; 359 | dout_next = txovrbuf_rvalid ? txovrbuf_rdata : txfifo_rvalid ? txfifo_rdata : '0; 360 | rdn_next = rdn; 361 | wrn_next = ~(txfifo_rvalid | txovrbuf_rvalid); 362 | oen_next = oen; 363 | din_valid_next = din_valid; 364 | ta_cnt_next = ta_cnt; 365 | txfifo_ren_next = txfifo_ren; 366 | txovrbuf_ren_next = txovrbuf_ren; 367 | 368 | case (fsm_state) 369 | IDLE_S: begin 370 | if (ft_not_empty && rxfifo_ready) begin 371 | // go receive, if FT chip has some data and our receive fifo is empty enough 372 | oen_next = 1'b0; 373 | fsm_next = RX_S; 374 | end else if (ft_not_full && (txfifo_ready || txovrbuf_ready)) begin 375 | // go transmit, if FT chip has empty space and our tranmsmit fifo is full enough, 376 | // but if we have data words left from the previous burst we need to transfer them first 377 | fsm_next = !txovrbuf_empty ? TX_OVERRUN_S : TX_S; 378 | txovrbuf_ren_next = !txovrbuf_empty; 379 | txfifo_ren_next = txovrbuf_empty; 380 | end 381 | end 382 | 383 | RX_S: begin 384 | din_valid_next = ft_not_empty & ~rdn; 385 | rdn_next = 1'b0; 386 | if (ft_empty) begin 387 | fsm_next = TURNAROUND_S; 388 | din_valid_next = 1'b0; 389 | rdn_next = 1'b1; 390 | oen_next = 1'b1; 391 | end else if(rxfifo_almost_full || rx_burst_end) begin 392 | // If rxfifo becomes full while FT buffer is still not empty, 393 | // we will lost some data words already pushed out from FT due to control signal latency. 394 | // That's why there is a special handler that solves this problem. 395 | fsm_next = RX_OVERFLOW0_S; 396 | rdn_next = 1'b1; 397 | end 398 | end 399 | 400 | RX_OVERFLOW0_S: begin 401 | oen_next = 1'b1; 402 | if (ft_empty) begin 403 | din_valid_next = 1'b0; 404 | fsm_next = TURNAROUND_S; 405 | end else begin 406 | din_valid_next = 1'b1; 407 | fsm_next = RX_OVERFLOW1_S; 408 | end 409 | end 410 | 411 | RX_OVERFLOW1_S: begin 412 | din_valid_next = 1'b0; 413 | fsm_next = TURNAROUND_S; 414 | end 415 | 416 | TURNAROUND_S: begin 417 | if (ta_cnt == '0) begin 418 | ta_cnt_next = TA_CNT_MAX; 419 | fsm_next = IDLE_S; 420 | end else begin 421 | ta_cnt_next = ta_cnt - 1'b1; 422 | end 423 | end 424 | 425 | TX_OVERRUN_S: begin 426 | txfifo_ren_next = (txovrbuf_load <= 1) && txfifo_ready; 427 | if (txovrbuf_empty) begin 428 | txovrbuf_ren_next = 1'b0; 429 | fsm_next = txfifo_ready ? TX_S : IDLE_S; 430 | end 431 | end 432 | 433 | TX_S : begin 434 | if (!ft_not_full || txfifo_empty || tx_burst_end) begin 435 | txfifo_ren_next = 1'b0; 436 | fsm_next = TURNAROUND_S; 437 | end 438 | end 439 | 440 | default: begin 441 | //do nothing 442 | end 443 | endcase 444 | end 445 | 446 | always_ff @(posedge ft_clk) begin 447 | if (ft_rst) begin 448 | fsm_state <= IDLE_S; 449 | oen <= 1'b1; 450 | rdn <= 1'b1; 451 | wrn <= 1'b1; 452 | dout <= '0; 453 | din_valid <= 1'b0; 454 | ta_cnt <= TA_CNT_MAX; 455 | txfifo_ren <= 1'b0; 456 | txovrbuf_ren <= 1'b0; 457 | end else begin 458 | fsm_state <= fsm_next; 459 | oen <= oen_next; 460 | rdn <= rdn_next; 461 | wrn <= wrn_next; 462 | dout <= dout_next; 463 | din_valid <= din_valid_next; 464 | ta_cnt <= ta_cnt_next; 465 | txfifo_ren <= txfifo_ren_next; 466 | txovrbuf_ren <= txovrbuf_ren_next; 467 | end 468 | end 469 | 470 | //------------------------------------------------------------------- 471 | // To FT chip 472 | //------------------------------------------------------------------- 473 | assign ft_rdn = rdn; 474 | assign ft_oen = oen; 475 | assign ft_wrn = wrn; 476 | assign ft_dout = dout; 477 | assign ft_beout = '1; 478 | assign ft_siwu = 1'b1; 479 | 480 | endmodule -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pytest_cache 3 | work -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Test environment 2 | 3 | Environment is built around Python [pytest](https://docs.pytest.org/) framework - it offers some nice and easy to use tools for test execution control and parametrization out of the box. 4 | 5 | Current workflow is based on the [pyhdlsim](https://github.com/esynr3z/pyhdlsim) example - ```sim.py``` is a wrapper around HDL simulators and ```test_*.py``` files contain tests. 6 | 7 | Tested on: 8 | 9 | * Windows 10, Python 3.8, Modelsim 10.6d 10 | * Ubuntu 20.04, Python 3.8, Modelsim 2020.02 11 | 12 | ## Requirements 13 | 14 | Several Python modules are required: 15 | 16 | ```bash 17 | python3 -m pip install pytest pytest-xdist 18 | ``` 19 | 20 | ## Frequently used commands 21 | 22 | All the commands are invoked from the ```tests``` directory. 23 | 24 | List all tests: 25 | 26 | ```bash 27 | pytest --collect-only -q 28 | ``` 29 | 30 | Run all tests on all available cores in parallel: 31 | 32 | ```bash 33 | pytest -v -n auto 34 | ``` 35 | 36 | Run only tests that have ```SINGLE``` and ```60e6``` substrings in their name: 37 | 38 | ```bash 39 | pytest -v -n auto -k "SINGLE and 60e6" 40 | ``` 41 | 42 | Run "default" test with no parametrization to debug the testbench inside the simulator GUI: 43 | 44 | ```bash 45 | pytest -v test_245sync.py::test_debug --gui 46 | ``` 47 | 48 | Run specific test using it's full name: 49 | 50 | ```bash 51 | pytest -v test_245sync.py::test[SINGLE_CLK_DOMAIN-DATA_W=32-FIFO_CLK_FREQ=48e6-FT_CLK_FREQ=100e6-TESTCASE=test_read_corners] 52 | ``` 53 | 54 | Run specific test inside the simulator GUI (helpful for failed tests debugging): 55 | 56 | ```bash 57 | pytest -v --gui test_245sync.py::test[SINGLE_CLK_DOMAIN-DATA_W=32-FIFO_CLK_FREQ=48e6-FT_CLK_FREQ=100e6-TESTCASE=test_read_corners] 58 | ``` 59 | 60 | Run tests in the specified simulator (also compatible with variants above): 61 | 62 | ```bash 63 | pytest -v -n auto --sim vivado 64 | ``` 65 | -------------------------------------------------------------------------------- /tests/common/fifo_if.sv: -------------------------------------------------------------------------------- 1 | interface fifo_if #( 2 | parameter DATA_W = 32, 3 | parameter TX_FIFO_SIZE = 4096, 4 | parameter RX_FIFO_SIZE = 4096 5 | )( 6 | input bit clk 7 | ); 8 | 9 | localparam TX_FIFO_LOAD_W = $clog2(TX_FIFO_SIZE) + 1; 10 | localparam RX_FIFO_LOAD_W = $clog2(RX_FIFO_SIZE) + 1; 11 | 12 | logic rxfifo_rd = 1'b0; 13 | logic [DATA_W-1:0] rxfifo_data; 14 | logic rxfifo_valid; 15 | logic rxfifo_empty; 16 | logic [RX_FIFO_LOAD_W-1:0] rxfifo_load; 17 | logic [DATA_W-1:0] txfifo_data = '0; 18 | logic txfifo_wr = 1'b0; 19 | logic txfifo_full; 20 | logic [TX_FIFO_LOAD_W-1:0] txfifo_load; 21 | 22 | modport dut ( 23 | input rxfifo_rd, 24 | output rxfifo_data, rxfifo_valid, rxfifo_empty, rxfifo_load, 25 | input txfifo_data, txfifo_wr, 26 | output txfifo_full, txfifo_load 27 | ); 28 | 29 | clocking drv @(posedge clk); 30 | input rxfifo_data, rxfifo_valid, rxfifo_empty, rxfifo_load; 31 | output rxfifo_rd; 32 | input txfifo_full, txfifo_load; 33 | output txfifo_data, txfifo_wr; 34 | endclocking 35 | 36 | typedef logic [DATA_W-1:0] data_t; 37 | 38 | task automatic send(ref data_t data []); 39 | foreach (data[i]) begin 40 | @(drv); 41 | if (drv.txfifo_full) begin 42 | wait(!drv.txfifo_full); 43 | @(drv); 44 | end 45 | drv.txfifo_wr <= 1'b1; 46 | drv.txfifo_data <= data[i]; 47 | end 48 | @(drv); 49 | drv.txfifo_wr <= 1'b0; 50 | drv.txfifo_data <= 0; 51 | endtask 52 | 53 | task automatic recv(int n, ref data_t data []); 54 | data = new[n]; 55 | fork 56 | begin : rd_drv 57 | wait(!drv.rxfifo_empty); 58 | @(drv); 59 | drv.rxfifo_rd <= 1'b1; 60 | foreach (data[i]) begin 61 | @(drv); 62 | if (drv.rxfifo_empty) begin 63 | wait(!drv.rxfifo_empty); 64 | end 65 | end 66 | drv.rxfifo_rd <= 1'b0; 67 | end 68 | foreach (data[i]) begin : data_drv 69 | @(drv); 70 | if (!drv.rxfifo_valid) begin 71 | wait(drv.rxfifo_valid); 72 | end 73 | data[i] = drv.rxfifo_data; 74 | end 75 | join 76 | @(drv); 77 | endtask 78 | 79 | endinterface 80 | -------------------------------------------------------------------------------- /tests/common/ft245_async_if.sv: -------------------------------------------------------------------------------- 1 | interface ft245_async_if #( 2 | parameter DATA_W = 8 3 | )( 4 | input bit clk 5 | ); 6 | 7 | logic rxfn; 8 | logic txen; 9 | logic [DATA_W-1:0] din; 10 | logic [DATA_W-1:0] dout; 11 | logic rdn; 12 | logic wrn; 13 | 14 | modport dut ( 15 | input din, rxfn, txen, 16 | output dout, rdn, wrn 17 | ); 18 | 19 | typedef logic [DATA_W-1:0] data_t; 20 | 21 | data_t txbuf [$]; 22 | data_t rxbuf [$]; 23 | localparam DEFAULT_TXBUF_LIMIT = 0; 24 | int txbuf_limit = DEFAULT_TXBUF_LIMIT; 25 | 26 | task automatic send(ref data_t data []); // when host send data, it go to the receive buffer 27 | foreach (data[i]) 28 | rxbuf.push_front(data[i]); 29 | wait(rxbuf.size() == 0); 30 | repeat(2) @(posedge clk); 31 | endtask 32 | 33 | task automatic recv(int n, ref data_t data []); // when host want to receive data, it reads transmit buffer 34 | if (txbuf.size() < n) begin 35 | txbuf_limit = n; 36 | wait(txbuf.size() == n); 37 | repeat(4) @(posedge clk); 38 | end 39 | data = new[n]; 40 | foreach (data[i]) 41 | data[i] = txbuf.pop_back(); 42 | txbuf_limit = DEFAULT_TXBUF_LIMIT; 43 | endtask 44 | 45 | task serve; 46 | fork 47 | serve_read; 48 | serve_write; 49 | join 50 | endtask 51 | 52 | task serve_read; 53 | rxfn = 1'b1; 54 | din = '0; 55 | forever begin: data_drv 56 | wait(rxbuf.size() != 0); 57 | do begin 58 | #49ns rxfn = 1'b0; 59 | @(negedge rdn); 60 | #14ns din = rxbuf.pop_back(); 61 | @(posedge rdn); 62 | #14ns rxfn = 1'b1; 63 | end while (rxbuf.size() != 0); 64 | end 65 | endtask 66 | 67 | task serve_write; 68 | txen = 1'b0; 69 | forever begin: data_drv 70 | wait(txbuf.size() < txbuf_limit); 71 | #49ns txen = 1'b0; 72 | @(negedge wrn); 73 | txbuf.push_front(dout); 74 | #14ns txen = 1'b1; 75 | end 76 | endtask 77 | 78 | endinterface -------------------------------------------------------------------------------- /tests/common/ft245_sync_if.sv: -------------------------------------------------------------------------------- 1 | interface ft245_sync_if #( 2 | parameter DATA_W = 32 3 | )( 4 | input bit clk 5 | ); 6 | 7 | logic rxfn; 8 | logic txen; 9 | logic [DATA_W-1:0] din; 10 | logic [DATA_W-1:0] dout; 11 | logic rdn; 12 | logic wrn; 13 | logic oen; 14 | 15 | modport dut ( 16 | input din, rxfn, txen, 17 | output dout, rdn, wrn, oen 18 | ); 19 | 20 | clocking drv @(posedge clk); 21 | default input #4ns output #4ns; 22 | input dout, rdn, wrn, oen; 23 | output din, rxfn, txen; 24 | endclocking 25 | 26 | typedef logic [DATA_W-1:0] data_t; 27 | 28 | data_t txbuf [$]; 29 | data_t rxbuf [$]; 30 | localparam DEFAULT_TXBUF_LIMIT = 0; 31 | int txbuf_limit = DEFAULT_TXBUF_LIMIT; 32 | 33 | task automatic send(ref data_t data []); // when host send data, it go to the receive buffer 34 | foreach (data[i]) 35 | rxbuf.push_front(data[i]); 36 | wait(rxbuf.size() == 0); 37 | repeat(2) @(drv); 38 | endtask 39 | 40 | task automatic recv(int n, ref data_t data []); // when host want to receive data, it reads transmit buffer 41 | if (txbuf.size() < n) begin 42 | txbuf_limit = n; 43 | wait(txbuf.size() == n); 44 | repeat(4) @(drv); 45 | end 46 | data = new[n]; 47 | foreach (data[i]) 48 | data[i] = txbuf.pop_back(); 49 | txbuf_limit = DEFAULT_TXBUF_LIMIT; 50 | endtask 51 | 52 | task serve; 53 | fork 54 | serve_read; 55 | serve_write; 56 | join 57 | endtask 58 | 59 | task serve_read; 60 | drv.rxfn <= 1'b1; 61 | drv.din <= '0; 62 | forever begin: data_drv 63 | @(drv); 64 | if (rxbuf.size() != 0) begin 65 | drv.rxfn <= 1'b0; 66 | do begin 67 | wait(drv.oen); 68 | wait(!drv.oen); 69 | din <= rxbuf.pop_back(); 70 | wait(!drv.rdn); 71 | while(!drv.rdn) begin 72 | if (rxbuf.size() == 0) break; 73 | drv.din <= rxbuf.pop_back(); 74 | @(drv); 75 | end 76 | end while (rxbuf.size() != 0); 77 | drv.rxfn <= 1'b1; 78 | wait(drv.oen); 79 | end 80 | end 81 | endtask 82 | 83 | task serve_write; 84 | forever begin: data_drv 85 | @(drv); 86 | if (!drv.wrn && !drv.txen && (txbuf.size() < txbuf_limit)) begin 87 | txbuf.push_front(drv.dout); 88 | end 89 | drv.txen <= txbuf_limit ? (txbuf.size() >= txbuf_limit) : 1'b1; 90 | end 91 | endtask 92 | 93 | endinterface -------------------------------------------------------------------------------- /tests/common/utils.svh: -------------------------------------------------------------------------------- 1 | function automatic void new_randomized(int n, ref data_t data []); 2 | data = new[n]; 3 | foreach (data[i]) 4 | data[i] = $random(); 5 | endfunction 6 | 7 | function automatic void push_to_queue(ref data_t queue [$], ref data_t data []); 8 | foreach (data[i]) 9 | queue.push_front(data[i]); 10 | endfunction 11 | 12 | function automatic int compare_queues(ref data_t expected [$], ref data_t actual [$]); 13 | int err; 14 | if (expected.size() != actual.size()) begin 15 | $error("Length of the expected data is %0d, but length of actual data is %0d!", 16 | expected.size(), actual.size()); 17 | err += 1; 18 | end else begin 19 | for (int i=0; i 225 | 226 | 227 | 228 | 229 | """ 230 | with path_join(self.cwd, 'work.wcfg').open(mode='w', encoding="utf-8") as f: 231 | f.write(work_wcfg) 232 | sim_args = "%s.%s" % (self.worklib, self.top) 233 | if not self.gui: 234 | sim_args += ' --R' 235 | else: 236 | sim_args += ' --gui --t reinvoke.tcl --view work.wcfg' 237 | self._exec('xsim', sim_args) 238 | -------------------------------------------------------------------------------- /tests/tb_245async/tb.sv: -------------------------------------------------------------------------------- 1 | module tb; 2 | 3 | // To control from launch scripts 4 | `ifndef DATA_W `define DATA_W 8 `endif 5 | `ifndef TX_FIFO_SIZE `define TX_FIFO_SIZE 32 `endif 6 | `ifndef RX_FIFO_SIZE `define RX_FIFO_SIZE 32 `endif 7 | `ifndef READ_TICKS `define READ_TICKS 4 `endif 8 | `ifndef WRITE_TICKS `define WRITE_TICKS 4 `endif 9 | 10 | `ifndef FT_CLK_FREQ `define FT_CLK_FREQ 100e6 `endif 11 | `ifndef FIFO_CLK_FREQ `define FIFO_CLK_FREQ 50e6 `endif 12 | 13 | //`define SINGLE_CLK_DOMAIN 14 | 15 | //------------------------------------------------------------------- 16 | // Testbench parameters 17 | //------------------------------------------------------------------- 18 | localparam DATA_W = `DATA_W; 19 | localparam TX_FIFO_SIZE = `TX_FIFO_SIZE; 20 | localparam RX_FIFO_SIZE = `RX_FIFO_SIZE; 21 | localparam READ_TICKS = `READ_TICKS; 22 | localparam WRITE_TICKS = `WRITE_TICKS; 23 | `ifdef SINGLE_CLK_DOMAIN 24 | localparam SINGLE_CLK_DOMAIN = 1; 25 | `else 26 | localparam SINGLE_CLK_DOMAIN = 0; 27 | `endif 28 | 29 | localparam FT_CLK_FREQ = `FT_CLK_FREQ; 30 | localparam FIFO_CLK_FREQ = `FIFO_CLK_FREQ; 31 | 32 | typedef logic [DATA_W-1:0] data_t; 33 | 34 | //------------------------------------------------------------------- 35 | // Clock and reset generation 36 | //------------------------------------------------------------------- 37 | bit ft_clk; 38 | initial forever #(1ns * (0.5 / FT_CLK_FREQ) / 1e-9) ft_clk = ~ft_clk; 39 | 40 | bit ft_rst = 1; 41 | initial begin 42 | repeat(3) @(negedge ft_clk); 43 | ft_rst = 0; 44 | end 45 | 46 | bit fifo_clk; 47 | bit fifo_rst; 48 | 49 | `ifdef SINGLE_CLK_DOMAIN 50 | assign fifo_clk = ft_clk; 51 | assign fifo_rst = ft_rst; 52 | `else 53 | initial forever #(1ns * (0.5 / FIFO_CLK_FREQ) / 1e-9) fifo_clk = ~fifo_clk; 54 | 55 | initial begin 56 | fifo_rst = 1; 57 | repeat(3) @(negedge fifo_clk); 58 | fifo_rst = 0; 59 | end 60 | `endif 61 | 62 | //------------------------------------------------------------------- 63 | // DUT environment 64 | //------------------------------------------------------------------- 65 | ft245_async_if #(.DATA_W (DATA_W)) ft245_if (.clk (ft_clk)); 66 | initial @(negedge ft_rst) ft245_if.serve(); 67 | 68 | fifo_if #( 69 | .DATA_W (DATA_W), 70 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 71 | .RX_FIFO_SIZE (RX_FIFO_SIZE) 72 | ) fifo_if (.clk (fifo_clk)); 73 | 74 | //------------------------------------------------------------------- 75 | // DUT 76 | //------------------------------------------------------------------- 77 | proto245a #( 78 | .DATA_W (DATA_W), 79 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 80 | .RX_FIFO_SIZE (RX_FIFO_SIZE), 81 | .SINGLE_CLK_DOMAIN (SINGLE_CLK_DOMAIN), 82 | .READ_TICKS (READ_TICKS), 83 | .WRITE_TICKS (WRITE_TICKS) 84 | ) dut ( 85 | // FT interface 86 | .ft_rst (ft_rst), 87 | .ft_clk (ft_clk), 88 | .ft_rxfn (ft245_if.rxfn), 89 | .ft_txen (ft245_if.txen), 90 | .ft_din (ft245_if.din), 91 | .ft_dout (ft245_if.dout), 92 | .ft_rdn (ft245_if.rdn), 93 | .ft_wrn (ft245_if.wrn), 94 | .ft_siwu (), 95 | // FIFO interface 96 | .rxfifo_clk (fifo_clk), 97 | .rxfifo_rst (fifo_rst), 98 | .rxfifo_rd (fifo_if.rxfifo_rd), 99 | .rxfifo_data (fifo_if.rxfifo_data), 100 | .rxfifo_valid (fifo_if.rxfifo_valid), 101 | .rxfifo_load (fifo_if.rxfifo_load), 102 | .rxfifo_empty (fifo_if.rxfifo_empty), 103 | .txfifo_clk (fifo_clk), 104 | .txfifo_rst (fifo_rst), 105 | .txfifo_data (fifo_if.txfifo_data), 106 | .txfifo_wr (fifo_if.txfifo_wr), 107 | .txfifo_load (fifo_if.txfifo_load), 108 | .txfifo_full (fifo_if.txfifo_full) 109 | ); 110 | 111 | //------------------------------------------------------------------- 112 | // Utilites 113 | //------------------------------------------------------------------- 114 | `include "utils.svh" 115 | 116 | `define START_TEST $display("--- Start %m ---") 117 | `define END_TEST $display("--- End %m ---") 118 | 119 | //------------------------------------------------------------------- 120 | // Tests 121 | //------------------------------------------------------------------- 122 | `include "test_rx.svh" 123 | `include "test_tx.svh" 124 | 125 | `ifndef TESTCASE `define TESTCASE test_tx `endif 126 | 127 | initial begin : main 128 | int test_err; 129 | wait(!ft_rst && !fifo_rst); 130 | #1us; 131 | `TESTCASE(test_err); 132 | #1us; 133 | if (test_err) 134 | $error("!@# TEST FAILED - %0d ERRORS #@!", test_err); 135 | else 136 | $display("!@# TEST PASSED #@!"); 137 | $finish(); 138 | end 139 | 140 | initial begin : watchdog 141 | #1ms; 142 | $error("!@# TEST FAILED - TIMEOUT #@!"); 143 | $finish(); 144 | end 145 | 146 | endmodule 147 | -------------------------------------------------------------------------------- /tests/tb_245async/test_rx.svh: -------------------------------------------------------------------------------- 1 | task test_rx(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | 7 | `START_TEST; 8 | fork 9 | begin 10 | new_randomized(8, ft245_data); 11 | push_to_queue(expected_data, ft245_data); 12 | ft245_if.send(ft245_data); 13 | #1us; 14 | new_randomized(4, ft245_data); 15 | push_to_queue(expected_data, ft245_data); 16 | ft245_if.send(ft245_data); 17 | end 18 | begin 19 | fifo_if.recv(4, fifo_data); 20 | push_to_queue(actual_data, fifo_data); 21 | fifo_if.recv(8, fifo_data); 22 | push_to_queue(actual_data, fifo_data); 23 | end 24 | join 25 | err += compare_queues(expected_data, actual_data); 26 | `END_TEST; 27 | endtask -------------------------------------------------------------------------------- /tests/tb_245async/test_tx.svh: -------------------------------------------------------------------------------- 1 | task test_tx(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | 7 | `START_TEST; 8 | new_randomized(6, fifo_data); 9 | push_to_queue(expected_data, fifo_data); 10 | fork 11 | fifo_if.send(fifo_data); 12 | ft245_if.recv(6, ft245_data); 13 | join 14 | push_to_queue(actual_data, ft245_data); 15 | err += compare_queues(expected_data, actual_data); 16 | expected_data.delete(); 17 | actual_data.delete(); 18 | 19 | new_randomized(24, fifo_data); 20 | push_to_queue(expected_data, fifo_data); 21 | fork 22 | fifo_if.send(fifo_data); 23 | begin 24 | ft245_if.recv(16, ft245_data); 25 | push_to_queue(actual_data, ft245_data); 26 | #1us; 27 | ft245_if.recv(8, ft245_data); 28 | push_to_queue(actual_data, ft245_data); 29 | end 30 | join 31 | err += compare_queues(expected_data, actual_data); 32 | `END_TEST; 33 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/tb.sv: -------------------------------------------------------------------------------- 1 | module tb; 2 | 3 | // To control from launch scripts 4 | `ifndef DATA_W `define DATA_W 8 `endif 5 | `ifndef TX_FIFO_SIZE `define TX_FIFO_SIZE 32 `endif 6 | `ifndef TX_START_THRESHOLD `define TX_START_THRESHOLD 16 `endif 7 | `ifndef TX_BURST_SIZE `define TX_BURST_SIZE 0 `endif 8 | `ifndef TX_BACKOFF_TIMEOUT `define TX_BACKOFF_TIMEOUT 64 `endif 9 | `ifndef RX_FIFO_SIZE `define RX_FIFO_SIZE 32 `endif 10 | `ifndef RX_START_THRESHOLD `define RX_START_THRESHOLD 16 `endif 11 | `ifndef RX_BURST_SIZE `define RX_BURST_SIZE 0 `endif 12 | 13 | `ifndef FT_CLK_FREQ `define FT_CLK_FREQ 60e6 `endif 14 | `ifndef FIFO_CLK_FREQ `define FIFO_CLK_FREQ 48e6 `endif 15 | 16 | //`define SINGLE_CLK_DOMAIN 17 | 18 | //------------------------------------------------------------------- 19 | // Testbench parameters 20 | //------------------------------------------------------------------- 21 | localparam DATA_W = `DATA_W; 22 | localparam TX_FIFO_SIZE = `TX_FIFO_SIZE; 23 | localparam TX_START_THRESHOLD = `TX_START_THRESHOLD; 24 | localparam TX_BURST_SIZE = `TX_BURST_SIZE; 25 | localparam TX_BACKOFF_TIMEOUT = `TX_BACKOFF_TIMEOUT; 26 | localparam RX_FIFO_SIZE = `RX_FIFO_SIZE; 27 | localparam RX_START_THRESHOLD = `RX_START_THRESHOLD; 28 | localparam RX_BURST_SIZE = `RX_BURST_SIZE; 29 | `ifdef SINGLE_CLK_DOMAIN 30 | localparam SINGLE_CLK_DOMAIN = 1; 31 | `else 32 | localparam SINGLE_CLK_DOMAIN = 0; 33 | `endif 34 | 35 | localparam FT_CLK_FREQ = `FT_CLK_FREQ; 36 | localparam FIFO_CLK_FREQ = `FIFO_CLK_FREQ; 37 | 38 | typedef logic [DATA_W-1:0] data_t; 39 | 40 | //------------------------------------------------------------------- 41 | // Clock and reset generation 42 | //------------------------------------------------------------------- 43 | bit ft_clk; 44 | initial forever #(1ns * (0.5 / FT_CLK_FREQ) / 1e-9) ft_clk = ~ft_clk; 45 | 46 | bit ft_rst = 1; 47 | initial begin 48 | repeat(3) @(negedge ft_clk); 49 | ft_rst = 0; 50 | end 51 | 52 | bit fifo_clk; 53 | bit fifo_rst; 54 | 55 | `ifdef SINGLE_CLK_DOMAIN 56 | assign fifo_clk = ft_clk; 57 | assign fifo_rst = ft_rst; 58 | `else 59 | initial forever #(1ns * (0.5 / FIFO_CLK_FREQ) / 1e-9) fifo_clk = ~fifo_clk; 60 | 61 | initial begin 62 | fifo_rst = 1; 63 | repeat(3) @(negedge fifo_clk); 64 | fifo_rst = 0; 65 | end 66 | `endif 67 | 68 | //------------------------------------------------------------------- 69 | // DUT environment 70 | //------------------------------------------------------------------- 71 | ft245_sync_if #(.DATA_W (DATA_W)) ft245_if (.clk (ft_clk)); 72 | initial @(negedge ft_rst) ft245_if.serve(); 73 | 74 | fifo_if #( 75 | .DATA_W (DATA_W), 76 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 77 | .RX_FIFO_SIZE (RX_FIFO_SIZE) 78 | ) fifo_if (.clk (fifo_clk)); 79 | 80 | //------------------------------------------------------------------- 81 | // DUT 82 | //------------------------------------------------------------------- 83 | proto245s #( 84 | .DATA_W (DATA_W), 85 | .TX_FIFO_SIZE (TX_FIFO_SIZE), 86 | .TX_START_THRESHOLD (TX_START_THRESHOLD), 87 | .TX_BURST_SIZE (TX_BURST_SIZE), 88 | .TX_BACKOFF_TIMEOUT (TX_BACKOFF_TIMEOUT), 89 | .RX_FIFO_SIZE (RX_FIFO_SIZE), 90 | .RX_START_THRESHOLD (RX_START_THRESHOLD), 91 | .RX_BURST_SIZE (RX_BURST_SIZE), 92 | .SINGLE_CLK_DOMAIN (SINGLE_CLK_DOMAIN) 93 | ) dut ( 94 | // FT interface 95 | .ft_rst (ft_rst), 96 | .ft_clk (ft_clk), 97 | .ft_rxfn (ft245_if.rxfn), 98 | .ft_txen (ft245_if.txen), 99 | .ft_din (ft245_if.din), 100 | .ft_dout (ft245_if.dout), 101 | .ft_bein ('1), 102 | .ft_beout (), 103 | .ft_rdn (ft245_if.rdn), 104 | .ft_wrn (ft245_if.wrn), 105 | .ft_oen (ft245_if.oen), 106 | .ft_siwu (), 107 | // FIFO interface 108 | .rxfifo_clk (fifo_clk), 109 | .rxfifo_rst (fifo_rst), 110 | .rxfifo_rd (fifo_if.rxfifo_rd), 111 | .rxfifo_data (fifo_if.rxfifo_data), 112 | .rxfifo_valid (fifo_if.rxfifo_valid), 113 | .rxfifo_load (fifo_if.rxfifo_load), 114 | .rxfifo_empty (fifo_if.rxfifo_empty), 115 | .txfifo_clk (fifo_clk), 116 | .txfifo_rst (fifo_rst), 117 | .txfifo_data (fifo_if.txfifo_data), 118 | .txfifo_wr (fifo_if.txfifo_wr), 119 | .txfifo_load (fifo_if.txfifo_load), 120 | .txfifo_full (fifo_if.txfifo_full) 121 | ); 122 | 123 | //------------------------------------------------------------------- 124 | // Utilites 125 | //------------------------------------------------------------------- 126 | `include "utils.svh" 127 | 128 | `define START_TEST $display("--- Start %m ---") 129 | `define END_TEST $display("--- End %m ---") 130 | 131 | //------------------------------------------------------------------- 132 | // Tests 133 | //------------------------------------------------------------------- 134 | `include "test_rx_simple.svh" 135 | `include "test_rx_flow_control.svh" 136 | `include "test_rx_thresholds.svh" 137 | `include "test_tx_simple.svh" 138 | `include "test_tx_flow_control.svh" 139 | `include "test_tx_thresholds.svh" 140 | 141 | `ifndef TESTCASE `define TESTCASE test_rx_flow_control `endif 142 | 143 | initial begin : main 144 | int test_err; 145 | wait(!ft_rst && !fifo_rst); 146 | #1us; 147 | `TESTCASE(test_err); 148 | #1us; 149 | if (test_err) 150 | $error("!@# TEST FAILED - %0d ERRORS #@!", test_err); 151 | else 152 | $display("!@# TEST PASSED #@!"); 153 | $finish(); 154 | end 155 | 156 | initial begin : watchdog 157 | #1ms; 158 | $error("!@# TEST FAILED - TIMEOUT #@!"); 159 | $finish(); 160 | end 161 | 162 | endmodule 163 | -------------------------------------------------------------------------------- /tests/tb_245sync/test_rx_flow_control.svh: -------------------------------------------------------------------------------- 1 | task test_rx_flow_control(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | int words; 7 | int offset; 8 | 9 | `START_TEST; 10 | // do series of send bursts from FT with payload -7..+7 around RX FIFO size 11 | // to check how FT buffer empty event (rxf deassertion) and RX FIFO full event work 12 | words = RX_FIFO_SIZE - 7; 13 | repeat(15) begin 14 | $display("%0t: Send %0d words", $time, words); 15 | fork 16 | begin 17 | new_randomized(words, ft245_data); 18 | push_to_queue(expected_data, ft245_data); 19 | ft245_if.send(ft245_data); 20 | end 21 | begin 22 | @(posedge ft245_if.rdn) 23 | fifo_if.recv(words, fifo_data); 24 | push_to_queue(actual_data, fifo_data); 25 | end 26 | join 27 | err += compare_queues(expected_data, actual_data); 28 | expected_data.delete(); 29 | actual_data.delete(); 30 | words += 1; 31 | end 32 | `END_TEST; 33 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/test_rx_simple.svh: -------------------------------------------------------------------------------- 1 | task test_rx_simple(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | 7 | `START_TEST; 8 | // simple several reads 9 | fork 10 | begin 11 | new_randomized(8, ft245_data); 12 | push_to_queue(expected_data, ft245_data); 13 | ft245_if.send(ft245_data); 14 | new_randomized(4, ft245_data); 15 | push_to_queue(expected_data, ft245_data); 16 | ft245_if.send(ft245_data); 17 | end 18 | begin 19 | fifo_if.recv(4, fifo_data); 20 | push_to_queue(actual_data, fifo_data); 21 | fifo_if.recv(8, fifo_data); 22 | push_to_queue(actual_data, fifo_data); 23 | end 24 | join 25 | err += compare_queues(expected_data, actual_data); 26 | `END_TEST; 27 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/test_rx_thresholds.svh: -------------------------------------------------------------------------------- 1 | task test_rx_thresholds(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | int words; 7 | int start_err; 8 | int stop_err; 9 | 10 | `START_TEST; 11 | words = RX_START_THRESHOLD + RX_START_THRESHOLD / 2; 12 | fork 13 | begin // send data 14 | new_randomized(words, ft245_data); 15 | push_to_queue(expected_data, ft245_data); 16 | ft245_if.send(ft245_data); 17 | end 18 | begin 19 | // check that reading stops when "BURST_SIZE" words were received 20 | @(posedge ft245_if.rdn); 21 | repeat(10) @(posedge ft245_if.clk); 22 | if (fifo_if.rxfifo_load != RX_BURST_SIZE) begin 23 | stop_err = 1; 24 | $error("RX_BURST_SIZE error!"); 25 | end else $display("RX_BURST_SIZE ok!"); 26 | // check that receiveng starts only if load counter <= threshold 27 | fifo_if.recv(RX_BURST_SIZE - RX_START_THRESHOLD, fifo_data); 28 | push_to_queue(actual_data, fifo_data); 29 | wait(!ft245_if.oen); 30 | if (fifo_if.rxfifo_load > RX_START_THRESHOLD) begin 31 | start_err = 1; 32 | $error("RX_START_THRESHOLD error!"); 33 | end else $display("RX_START_THRESHOLD ok!"); 34 | fifo_if.recv(words - (RX_BURST_SIZE - RX_START_THRESHOLD), fifo_data); 35 | push_to_queue(actual_data, fifo_data); 36 | end 37 | join 38 | err += start_err; 39 | err += stop_err; 40 | err += compare_queues(expected_data, actual_data); 41 | `END_TEST; 42 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/test_tx_flow_control.svh: -------------------------------------------------------------------------------- 1 | task test_tx_flow_control(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | int words; 7 | int words1, words2; 8 | int offset; 9 | 10 | `START_TEST; 11 | // do series of write bursts to FIFO to check how FT buffer overflow is handled (txe deassertion) 12 | words1 = 16; 13 | offset = 0; 14 | repeat(5) begin 15 | $display("%0t: Send %0d words, receive %0d+%0d words", $time, words1, words1-offset, offset); 16 | fork 17 | begin 18 | new_randomized(words1, fifo_data); 19 | push_to_queue(expected_data, fifo_data); 20 | fifo_if.send(fifo_data); 21 | end 22 | begin 23 | ft245_if.recv(words1 - offset, ft245_data); 24 | push_to_queue(actual_data, ft245_data); 25 | if (offset) begin 26 | ft245_if.recv(offset, ft245_data); 27 | push_to_queue(actual_data, ft245_data); 28 | end 29 | end 30 | join 31 | err += compare_queues(expected_data, actual_data); 32 | expected_data.delete(); 33 | actual_data.delete(); 34 | offset += 1; 35 | end 36 | 37 | words1 = 16; 38 | words2 = 8; 39 | offset = 1; 40 | repeat(3) begin 41 | $display("%0t: Send %0d+%0d words, receive %0d+%0d words", $time, 42 | words1, words2, words1-offset, words2+offset); 43 | fork 44 | begin 45 | new_randomized(words1, fifo_data); 46 | push_to_queue(expected_data, fifo_data); 47 | fifo_if.send(fifo_data); 48 | @(negedge ft245_if.txen); 49 | new_randomized(words2, fifo_data); 50 | push_to_queue(expected_data, fifo_data); 51 | fifo_if.send(fifo_data); 52 | end 53 | begin 54 | ft245_if.recv(words1-offset, ft245_data); 55 | push_to_queue(actual_data, ft245_data); 56 | ft245_if.recv(words2+offset, ft245_data); 57 | push_to_queue(actual_data, ft245_data); 58 | end 59 | join 60 | err += compare_queues(expected_data, actual_data); 61 | expected_data.delete(); 62 | actual_data.delete(); 63 | offset += 1; 64 | end 65 | `END_TEST; 66 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/test_tx_simple.svh: -------------------------------------------------------------------------------- 1 | task test_tx_simple(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | 7 | `START_TEST; 8 | // simple several writes to FT chip 9 | new_randomized(6, fifo_data); 10 | push_to_queue(expected_data, fifo_data); 11 | fork 12 | fifo_if.send(fifo_data); 13 | ft245_if.recv(6, ft245_data); 14 | join 15 | push_to_queue(actual_data, ft245_data); 16 | err += compare_queues(expected_data, actual_data); 17 | expected_data.delete(); 18 | actual_data.delete(); 19 | 20 | new_randomized(24, fifo_data); 21 | push_to_queue(expected_data, fifo_data); 22 | fork 23 | fifo_if.send(fifo_data); 24 | ft245_if.recv(24, ft245_data); 25 | join 26 | push_to_queue(actual_data, ft245_data); 27 | err += compare_queues(expected_data, actual_data); 28 | `END_TEST; 29 | endtask -------------------------------------------------------------------------------- /tests/tb_245sync/test_tx_thresholds.svh: -------------------------------------------------------------------------------- 1 | task test_tx_thresholds(output int err); 2 | data_t expected_data [$]; 3 | data_t actual_data [$]; 4 | data_t ft245_data []; 5 | data_t fifo_data []; 6 | int words; 7 | int start_err; 8 | int stop_err; 9 | 10 | `START_TEST; 11 | words = TX_START_THRESHOLD + TX_START_THRESHOLD / 2; 12 | fork 13 | begin // write to fifo 14 | new_randomized(words, fifo_data); 15 | push_to_queue(expected_data, fifo_data); 16 | fifo_if.send(fifo_data); 17 | end 18 | begin 19 | ft245_if.recv(words, ft245_data); 20 | push_to_queue(actual_data, ft245_data); 21 | end 22 | begin 23 | // check that sending starts only if load counter >= threshold 24 | @(negedge ft245_if.wrn); 25 | if (fifo_if.txfifo_load < TX_START_THRESHOLD) begin 26 | start_err = 1; 27 | $error("TX_START_THRESHOLD error!"); 28 | end else $display("TX_START_THRESHOLD ok!"); 29 | // check that sending stops only when "BURST_SIZE" words were transmitted 30 | @(posedge ft245_if.wrn); 31 | repeat(5) @(posedge ft245_if.clk); 32 | if (fifo_if.txfifo_load != (words - TX_BURST_SIZE)) begin 33 | stop_err = 1; 34 | $error("TX_BURST_SIZE error!"); 35 | end else $display("TX_BURST_SIZE ok!"); 36 | end 37 | join 38 | err += start_err; 39 | err += stop_err; 40 | err += compare_queues(expected_data, actual_data); 41 | `END_TEST; 42 | endtask -------------------------------------------------------------------------------- /tests/test_245async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for proto245a""" 5 | 6 | import pytest 7 | from sim import Simulator, path, get_test_names 8 | 9 | 10 | def run_sim(cwd, defines, simtool, gui): 11 | sim = Simulator(name=simtool, gui=gui, cwd=cwd) 12 | tb_dir = path("tb_245async") 13 | tb_common_dir = path("common") 14 | rtl_dir = path("../src") 15 | sim.incdirs += [tb_dir, tb_common_dir, rtl_dir, cwd] 16 | sim.sources += tb_common_dir.glob('*.sv') 17 | sim.sources += tb_dir.glob('*.sv') 18 | sim.sources += rtl_dir.glob('*.sv') 19 | sim.defines += defines 20 | sim.top = "tb" 21 | sim.setup() 22 | sim.run() 23 | return sim.is_passed 24 | 25 | 26 | @pytest.fixture 27 | def simtool(pytestconfig): 28 | return pytestconfig.getoption("sim") 29 | 30 | 31 | @pytest.fixture 32 | def gui(pytestconfig): 33 | return pytestconfig.getoption("gui") 34 | 35 | 36 | @pytest.mark.parametrize('testcase', ["TESTCASE=test_rx", "TESTCASE=test_tx"]) 37 | @pytest.mark.parametrize('clock_domains', ["MULTIPLE_CLK_DOMAINS", "SINGLE_CLK_DOMAIN"]) 38 | def test(tmp_path, testcase, clock_domains, simtool, gui): 39 | defines = [testcase, clock_domains] 40 | res = run_sim(tmp_path, defines, simtool, gui) 41 | if not gui: 42 | assert res 43 | 44 | 45 | def test_debug(tmp_path, simtool, gui): 46 | if not gui: 47 | pytest.skip("Run this test separately and add --gui key to debug the testbench in a simulator") 48 | run_sim(tmp_path, [], simtool, gui) 49 | -------------------------------------------------------------------------------- /tests/test_245sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for proto245s""" 5 | 6 | import pytest 7 | from sim import Simulator, path, get_test_names 8 | 9 | 10 | def run_sim(cwd, defines, simtool, gui): 11 | sim = Simulator(name=simtool, gui=gui, cwd=cwd) 12 | tb_dir = path("tb_245sync") 13 | tb_common_dir = path("common") 14 | rtl_dir = path("../src") 15 | sim.incdirs += [tb_dir, tb_common_dir, rtl_dir, cwd] 16 | sim.sources += tb_dir.glob('*.sv') 17 | sim.sources += tb_common_dir.glob('*.sv') 18 | sim.sources += rtl_dir.glob('*.sv') 19 | sim.defines += defines 20 | sim.top = "tb" 21 | sim.setup() 22 | sim.run() 23 | return sim.is_passed 24 | 25 | 26 | @pytest.fixture 27 | def simtool(pytestconfig): 28 | return pytestconfig.getoption("sim") 29 | 30 | 31 | @pytest.fixture 32 | def gui(pytestconfig): 33 | return pytestconfig.getoption("gui") 34 | 35 | 36 | def ignore_some_combinations(testcase, clock_domains, ft_clock, fifo_clock, data_width): 37 | """Special function to ignore some fixtures combinations""" 38 | if (('SINGLE' in clock_domains) and ('48' not in fifo_clock)): 39 | # no need to use multiple fifo clock values in the single domain mode 40 | return True 41 | 42 | 43 | @pytest.mark.uncollect_if(func=ignore_some_combinations) 44 | @pytest.mark.parametrize('testcase', ["TESTCASE=test_rx_simple", "TESTCASE=test_rx_flow_control", 45 | "TESTCASE=test_rx_thresholds", "TESTCASE=test_tx_simple", 46 | "TESTCASE=test_tx_flow_control", "TESTCASE=test_tx_thresholds"]) 47 | @pytest.mark.parametrize('ft_clock', ["FT_CLK_FREQ=60e6", "FT_CLK_FREQ=66e6", "FT_CLK_FREQ=100e6"]) 48 | @pytest.mark.parametrize('fifo_clock', ["FIFO_CLK_FREQ=48e6", "FIFO_CLK_FREQ=72e6", 49 | "FIFO_CLK_FREQ=96e6", "FIFO_CLK_FREQ=120e6"]) 50 | @pytest.mark.parametrize('data_width', ["DATA_W=8", "DATA_W=16", "DATA_W=32"]) 51 | @pytest.mark.parametrize('clock_domains', ["MULTIPLE_CLK_DOMAINS", "SINGLE_CLK_DOMAIN"]) 52 | def test(tmp_path, testcase, clock_domains, ft_clock, fifo_clock, data_width, simtool, gui): 53 | defines = [testcase, clock_domains, ft_clock, fifo_clock, data_width] 54 | if "thresholds" in testcase: 55 | defines += ["TX_FIFO_SIZE=64", "TX_START_THRESHOLD=20", "TX_BURST_SIZE=16", 56 | "RX_FIFO_SIZE=64", "RX_START_THRESHOLD=16", "RX_BURST_SIZE=20"] 57 | res = run_sim(tmp_path if not gui else "work", defines, simtool, gui) 58 | if not gui: 59 | assert res 60 | 61 | 62 | def test_debug(simtool, gui): 63 | if not gui: 64 | pytest.skip("Run this test separately and add --gui key to debug the testbench in a simulator") 65 | run_sim("work", [], simtool, gui) 66 | --------------------------------------------------------------------------------