Add local HLS dev setup: clangd, pragma highlighting + linting, local C-sim

This commit is contained in:
2026-10-24 17:13:18 +01:00
parent e036c194ed
commit 04b7425598
180 changed files with 745644 additions and 0 deletions

178
Practicals/hlsdev/README.md Normal file
View File

@@ -0,0 +1,178 @@
# 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.