179 lines
6.2 KiB
Markdown
179 lines
6.2 KiB
Markdown
# 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.
|