# Local HLS development (`hlsdev`) Vitis is slow over X11, so this folder lets you **edit, check and test** the practical code on your Mac, and only go to the server when you want a real synthesis run. It gives you four things: | | What | Why | | --- | --- | --- | | 1 | **clangd diagnostics** for the C++ itself | Real errors and warnings as you type, with no Xilinx install | | 2 | **`#pragma HLS` highlighting** | Stock syntax files treat pragmas as anonymous preprocessor text | | 3 | **`#pragma HLS` linting** | Compilers silently ignore misspelled pragmas - this catches them | | 4 | **A local C-simulation** (`./sim.sh`) | Runs the real testbench in about a second instead of minutes | --- ## The one thing worth understanding first `#pragma HLS ...` is **not C++**. clang and gcc have never heard of it and just ignore it: ```c #pragma HLS PIPELINE II=2 #pragma HLS TOTALLY_MADE_UP nonsense=1 // compiles identically fine ``` That has two consequences, and they are the reason this folder exists: * **Good news:** you get correct C++ diagnostics with any normal compiler. The pragmas never produce false errors. * **Bad news:** nothing but the HLS compiler can tell a valid pragma from a typo, and even Vitis HLS is often happy to ignore one silently. So a mistyped `PIPLINE` can quietly cost you the optimisation you asked for. Hence items 1-2 are for editing comfort, and item 3 is the one that actually catches bugs. --- ## Setup ### 1. Point Neovim at the highlighting and linter (one line) Add this to your `init.lua`: ```lua vim.opt.runtimepath:append(vim.fn.expand('~/Projects/EMBS/Practicals/hlsdev/nvim')) vim.opt.runtimepath:append(vim.fn.expand('~/Projects/EMBS/Practicals/hlsdev/nvim/after')) ``` Both lines are needed: the first picks up `plugin/`, the second picks up `after/syntax/`. Nothing else to install - no plugin manager involved. ### 2. Get the real HLS headers (optional but recommended) ```bash cd ~/Projects/EMBS/Practicals/hlsdev ./fetch-headers.sh --dry-run # see what is there and how big it is ./fetch-headers.sh # copy them (asks for your gateway password) ``` This copies just the header files (a few MB) from `/opt/york/cs/net/xilinx_vivado-2025.1-x86_64-1/2025.1/Vitis` - not the 60 GB toolchain. `clangd` and `./sim.sh` then use the genuine `ap_int.h`. If you skip this, everything still works using the stand-in header in `include-stub/`. That stub is **not** the real header - it only implements the handful of `ap_uint` operations the practicals use, and `./sim.sh` prints a warning when it falls back to it. --- ## Daily use ```bash cd ~/Projects/EMBS/Practicals/hlsdev ./sim.sh # compile + run the C simulation locally (fast) ./lint.sh # check every #pragma HLS line ./sim.sh practical-4 # any other practical folder ./lint.sh ../practical-3b/game_of_life_practical3b/gameoflife.cpp # one file ``` In Neovim: * Diagnostics from the pragma linter appear alongside clangd's, automatically on open and on save. * `:HlsPragmaLint` re-runs it on the current buffer. --- ## What the linter catches ``` $ ./lint.sh gameoflife.cpp:2:9: error: unknown HLS pragma 'PIPLINE' - did you mean 'PIPELINE'? gameoflife.cpp:3:9: error: UNROLL: factor must be an integer, got 'two' gameoflife.cpp:4:9: warning: ARRAY_PARTITION: variable='missing_array' is not declared anywhere in this file gameoflife.cpp:5:9: error: ARRAY_PARTITION: type='complet' is invalid (expected one of: block, complete, cyclic) gameoflife.cpp:6:9: error: INTERFACE: 's_axillite' is not a valid mode (expected one of: ... s_axilite ...) gameoflife.cpp:8:9: error: unknown HLS pragma 'TOTALLY_MADE_UP' gameoflife.cpp:9:9: error: LOOP_TRIPCOUNT: missing required option 'min=' ``` It checks pragma names, option names, option values, required options, and whether the `variable=` / `port=` you named actually exists in the file - with "did you mean" suggestions throughout. --- ## How clangd is wired up Vitis writes a `compile_commands.json` next to the sources, but its paths point at `/home/userfs/b/...` on the server and its flags (`-fhls`, `-hls-syn-headers-dir=`, `--gcc-toolchain=`) are Xilinx-only, so clangd cannot use it. Rather than overwrite it (Vitis regenerates and hard-codes paths), the file `game_of_life_practical3b/.clangd` redirects clangd to a local flag set: ```yaml CompileFlags: CompilationDatabase: ../../hlsdev/clangd ``` and `hlsdev/clangd/compile_flags.txt` holds the actual flags: ``` -xc++ -std=c++14 -Wno-unknown-pragmas -I../include -I../include-stub ``` Relative paths are resolved from the `compile_flags.txt` directory, so this stays portable - no absolute paths are committed. --- ## Files | File | Purpose | | --- | --- | | `sim.sh` | Compile + run the C simulation locally | | `lint.sh` | Run the pragma linter over the practicals | | `hls_pragma_lint.py` | The linter itself (also usable in CI) | | `fetch-headers.sh` | Copy the real HLS headers down from csteach0 | | `clangd/compile_flags.txt` | Flags clangd uses for these files | | `include-stub/ap_int.h` | Editor-only stand-in for the real `ap_int.h` | | `include/` | Where `fetch-headers.sh` puts the real headers (gitignored) | | `nvim/after/syntax/cpp.vim` | `#pragma HLS` highlighting | | `nvim/plugin/hls_pragma_lint.lua` | Neovim diagnostics integration | --- ## Two caveats **Treesitter will override the highlighting if the `cpp` parser is installed.** Your config runs `vim.treesitter.start()` on every filetype, and Neovim has no `cpp` parser installed, so it currently falls back to Vim syntax (where our rules live). If you ever run `:TSInstall cpp`, the pragma highlighting will stop working. Fix it by not starting treesitter for cpp, e.g.: ```lua vim.api.nvim_create_autocmd('FileType', { callback = function() if vim.bo.filetype == 'cpp' then return end -- keep Vim syntax for cpp pcall(vim.treesitter.start) end, }) ``` **The local simulation is not synthesis.** It checks your logic against the testbench, which is the bulk of the work - but it does not tell you about timing, `II`, resource usage or whether the pragmas did what you wanted. For that you still need a real HLS run on the server. `./lint.sh` covers the pragma typos that the compiler would otherwise swallow.