Add local HLS dev setup: clangd, pragma highlighting + linting, local C-sim
This commit is contained in:
178
Practicals/hlsdev/README.md
Normal file
178
Practicals/hlsdev/README.md
Normal 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.
|
||||
Reference in New Issue
Block a user