Add end-to-end EMBS runbook (Vivado -> HLS -> Vitis -> board)
This commit is contained in:
553
Practicals/RUNBOOK.md
Normal file
553
Practicals/RUNBOOK.md
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
# EMBS runbook - Vivado -> Vitis HLS -> Vitis -> board
|
||||||
|
|
||||||
|
Personal quick-reference for the assessment. Compiled from the EMBS docs at
|
||||||
|
<https://iangray001.github.io/embs/>. Running example: the **Game of Life**
|
||||||
|
(Practical 3b), because that is the one you already have working.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Read this first: three corrections to the step order
|
||||||
|
|
||||||
|
You listed eleven steps. Eight are right. Two are in the wrong place, and one
|
||||||
|
was ambiguous. Everything below follows the corrected order.
|
||||||
|
|
||||||
|
**1. "Application which uses the HLS component" cannot come before "wiring the
|
||||||
|
HLS IP into the block diagram."** The driver (`xtoplevel.h`), the base addresses
|
||||||
|
(`xparameters.h`) and the `XToplevel_*` functions are all *generated from the
|
||||||
|
exported XSA*. Until the IP is in the block design, a bitstream is built and the
|
||||||
|
hardware is re-exported, those files do not exist. Writing the app first means
|
||||||
|
writing against nothing.
|
||||||
|
|
||||||
|
**2. "Simulating this" is not a separate step, and the docs only cover one kind.**
|
||||||
|
See the note below.
|
||||||
|
|
||||||
|
**3. You build the bitstream twice, and they are different things.** The first
|
||||||
|
(step 2) validates the base system and is what creates the platform. The second
|
||||||
|
(step 7) is the real one, containing your IP. Expect to throw the first away.
|
||||||
|
|
||||||
|
### The corrected order
|
||||||
|
|
||||||
|
| # | Step | Where |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Create the block design | P1a |
|
||||||
|
| 2 | Build the **base** bitstream | P1a |
|
||||||
|
| 3 | Export hardware, create the **Platform**, first app | P1a |
|
||||||
|
| 4 | Write the **HLS component** | P3a |
|
||||||
|
| 5 | Write the **testbench**, run **C Simulation** | P3a |
|
||||||
|
| 6 | **Wire the IP into the block design** | P4 |
|
||||||
|
| 7 | Build the bitstream **again**, re-export the XSA | P4 |
|
||||||
|
| 8 | **Re-read the XSA**, then write the app that drives the IP | P4 |
|
||||||
|
| 9 | **Program the board** and run | P1a / P4 |
|
||||||
|
| 10 | **Test it out** | P4 / Software API |
|
||||||
|
|
||||||
|
### About "simulating this"
|
||||||
|
|
||||||
|
Three different things get called simulation, and the docs only document the
|
||||||
|
first:
|
||||||
|
|
||||||
|
- **HLS C Simulation** - runs your testbench as ordinary C++ in software. This
|
||||||
|
is step 5, and it is the only simulation the practicals use. It checks logic,
|
||||||
|
**not** speed or hardware size.
|
||||||
|
- **C/RTL co-simulation** - re-runs the testbench against the *generated RTL*.
|
||||||
|
More thorough, much slower. Not covered anywhere in the EMBS docs.
|
||||||
|
- **Vivado behavioural simulation** - simulates the whole block design. Not
|
||||||
|
covered anywhere in the EMBS docs.
|
||||||
|
|
||||||
|
So: do step 5 properly, and treat the FPGA as the only other test rig you have.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 0 - Once per session
|
||||||
|
|
||||||
|
On the CS servers (or your own machine) Vivado and HLS need the licence server
|
||||||
|
and the tools on the PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'export LM_LICENSE_FILE=2100@cslm0.its.york.ac.uk' >> ~/.bashrc
|
||||||
|
echo 'export PATH=/opt/york/cs/net/xilinx_vivado-2025.1-x86_64-1/2025.1/Vivado/bin:$PATH' >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Reopen the terminal. Check `vivado -version` says **2025.1**, not 2020.x.
|
||||||
|
|
||||||
|
Verify the licence is visible: **Help -> Manage License -> Manage License Search
|
||||||
|
Paths** should show `LM_LICENSE_FILE = 2100@cslm0.its.york.ac.uk`.
|
||||||
|
|
||||||
|
The three tools, in one line each:
|
||||||
|
|
||||||
|
- **Vivado** - builds the hardware design (the bitfile) and programs the FPGA.
|
||||||
|
- **Vitis** - builds software for the ARM cores, and loads both.
|
||||||
|
- **Vitis HLS** - turns C/C++ into a custom IP core you can drop into Vivado.
|
||||||
|
|
||||||
|
> **Never put spaces** in a Vivado project path, a Vitis workspace, or a project
|
||||||
|
> name. It genuinely breaks the tools later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1 - Base hardware and platform (Practical 1a)
|
||||||
|
|
||||||
|
### 1.1 New project
|
||||||
|
|
||||||
|
1. Vivado -> **Create Project**.
|
||||||
|
2. Name and location (**no spaces**).
|
||||||
|
3. **RTL Project** + "Do not specify sources at this time".
|
||||||
|
4. Next past Constraints.
|
||||||
|
5. Default Part page -> **Boards** -> Vendor `digilentinc.com`, Name
|
||||||
|
`Zybo Z7-10`. First time only: **Refresh** to pull the board catalogue, then
|
||||||
|
click the download arrow in the Status column to install the board files.
|
||||||
|
6. Select **Zybo Z7-10** -> Next -> Finish.
|
||||||
|
|
||||||
|
### 1.2 Block design
|
||||||
|
|
||||||
|
1. Flow Navigator -> **Create Block Design** (default name `design_1` is fine).
|
||||||
|
2. **Add IP** -> `ZYNQ7 Processing System`.
|
||||||
|
3. Switch the centre panel to **Board** -> double-click **4 LEDs** -> OK.
|
||||||
|
Then **4 Buttons** -> OK. (This drops in an `axi_gpio` core.)
|
||||||
|
4. Green banner -> **Run Connection Automation** -> OK.
|
||||||
|
This adds a *Processor System Reset*, an *AXI Interconnect*, and wires the PS
|
||||||
|
master `M_AXI_GP0` through to the GPIO slave `S_AXI`.
|
||||||
|
5. Green banner -> **Run Block Automation** -> OK. This attaches DDR and the
|
||||||
|
fixed I/O, and sets clock speeds.
|
||||||
|
6. Toolbar -> **Regenerate Layout** to tidy the diagram.
|
||||||
|
|
||||||
|
### 1.3 Wrapper and bitstream
|
||||||
|
|
||||||
|
1. **Sources** panel -> right-click `design_1.bd` -> **Create HDL Wrapper** ->
|
||||||
|
"Let Vivado manage wrapper and auto-update" -> OK.
|
||||||
|
2. A critical warning about `PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_*` is **expected and
|
||||||
|
ignorable**. Any *other* critical warning is not.
|
||||||
|
3. Flow Navigator -> **Generate Bitstream** -> Yes to run synthesis and
|
||||||
|
implementation. Minutes for this design; much longer later.
|
||||||
|
4. Progress: **Design Runs** tab at the bottom.
|
||||||
|
|
||||||
|
Worth a look once, for the assessment: **Open Implemented Design** -> Device view,
|
||||||
|
**Report Utilization**, **Open Synthesized Design** -> **Schematic**.
|
||||||
|
|
||||||
|
### 1.4 Program the FPGA (base design)
|
||||||
|
|
||||||
|
1. Flow Navigator -> **Open Hardware Manager** -> **Open Target** -> **Open New
|
||||||
|
Target**.
|
||||||
|
2. Right-click `xc7z010` -> **Program Device**.
|
||||||
|
3. Bitstream should already be `design_1_wrapper.bit` -> **Program**.
|
||||||
|
|
||||||
|
The green DONE LED lights, **and nothing else happens** - the ARM cores have no
|
||||||
|
software yet.
|
||||||
|
|
||||||
|
### 1.5 Platform, then first application
|
||||||
|
|
||||||
|
1. Vivado -> **File -> Export -> Export Hardware** -> Next -> **Include
|
||||||
|
bitstream** -> Next -> Finish. This produces `design_1_wrapper.xsa`.
|
||||||
|
2. **Tools -> Launch Vitis IDE** -> OK.
|
||||||
|
3. Vitis -> **File -> Set Workspace** (e.g. `~/vitis_workspace`, no spaces).
|
||||||
|
4. **File -> New Component -> Platform**. Name it e.g. `basicIO_platform`.
|
||||||
|
5. Browse to the exported **`design_1_wrapper.xsa`**.
|
||||||
|
6. Defaults are correct: `standalone` and `ps7_cortexa9_0` -> Next -> Finish.
|
||||||
|
7. **View -> Examples -> Embedded Software Examples -> Hello World -> Create
|
||||||
|
Application Component From Template**. Pick your platform, then the
|
||||||
|
`standalone_ps7_cortexa9_0` domain.
|
||||||
|
|
||||||
|
Vitis project types, because they get confused:
|
||||||
|
|
||||||
|
- **Platform** - the exported hardware plus drivers. Comes from Vivado.
|
||||||
|
- **System** - generated; drivers/frameworks your apps sit on ("the OS").
|
||||||
|
- **Application** - your code.
|
||||||
|
|
||||||
|
**You must make a new System whenever the hardware interface changes.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2 - The HLS component and its testbench (Practical 3a/3b)
|
||||||
|
|
||||||
|
### 2.1 New HLS component
|
||||||
|
|
||||||
|
1. Vitis -> **File -> New Component -> HLS**.
|
||||||
|
2. Name it descriptively (not `hls_component`).
|
||||||
|
3. Configuration File -> Next. Add Source Files -> Next.
|
||||||
|
4. **Part = `xc7z010clg400-1`**. Next -> Next -> Finish.
|
||||||
|
5. Add your sources (`gameoflife.cpp`, `gameoflife.h`).
|
||||||
|
6. **Settings -> hls_config.cfg** -> under *C Synthesis sources* set
|
||||||
|
**top = `game_of_life_step`**.
|
||||||
|
|
||||||
|
Your existing `hls_config.cfg` already has the right shape:
|
||||||
|
|
||||||
|
```
|
||||||
|
part=xc7z010clg400-1
|
||||||
|
[hls]
|
||||||
|
flow_target=vivado
|
||||||
|
package.output.format=ip_catalog
|
||||||
|
package.output.syn=false
|
||||||
|
tb.file=testbench.cpp
|
||||||
|
syn.file=gameoflife.cpp
|
||||||
|
syn.file=gameoflife.h
|
||||||
|
syn.top=game_of_life_step
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 The interface template
|
||||||
|
|
||||||
|
Two interfaces, and they do different jobs:
|
||||||
|
|
||||||
|
- **AXI Slave (`s_axilite`)** - the ARM uses it to start/stop the core and to
|
||||||
|
pass a few scalars.
|
||||||
|
- **AXI Master (`m_axi`)** - the core uses it to read/write **main memory**
|
||||||
|
directly. Only needed if you are moving a lot of data.
|
||||||
|
|
||||||
|
The Game of Life packs its whole 10x10 grid into 4 x 32-bit words, so it needs
|
||||||
|
**only the slave interface**:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma HLS INTERFACE s_axilite port = in_words bundle = AXILiteS
|
||||||
|
#pragma HLS INTERFACE s_axilite port = out_words bundle = AXILiteS
|
||||||
|
#pragma HLS INTERFACE s_axilite port = return bundle = AXILiteS
|
||||||
|
```
|
||||||
|
|
||||||
|
If you *do* use an AXI Master, the template is:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#pragma HLS INTERFACE m_axi port=ram offset=slave bundle=MAXI
|
||||||
|
#pragma HLS INTERFACE s_axilite port=ram bundle=AXILiteS
|
||||||
|
#pragma HLS INTERFACE s_axilite port=return bundle=AXILiteS register
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Trap.** The knowledge base uses `register` on the slave ports; Practical 3a's
|
||||||
|
> template omits it. The docs are explicit that the `port=return` pragma is what
|
||||||
|
> bundles **start / stop / done / reset** into a control register: *"If you do not
|
||||||
|
> include this pragma, then HLS will generate simple wires for these signals
|
||||||
|
> instead and your IP core will not be controllable from the ARM cores directly."*
|
||||||
|
> Include it, or you get an IP you cannot start.
|
||||||
|
|
||||||
|
> **Trap.** For an `m_axi` port the datatype must be **32-bit** (`uint32`), because
|
||||||
|
> RAM is 32 bits wide. Returning `uint100 toplevel(uint100 *ram)` makes HLS
|
||||||
|
> generate 100 wires that you cannot wire up in Vivado.
|
||||||
|
|
||||||
|
### 2.3 Testbench and C Simulation
|
||||||
|
|
||||||
|
1. Right-click **Test Bench** -> **New Test Bench File** -> `testbench.cpp`.
|
||||||
|
2. **Settings -> hls_config.cfg -> Testbench Sources -> Add File** and add it.
|
||||||
|
(Creating the file is *not* enough - this is the step people miss.)
|
||||||
|
3. Flow panel -> **C Simulation -> Run**.
|
||||||
|
|
||||||
|
Good testbench habits, from the docs: return **0 on success, 1 on failure**,
|
||||||
|
check against pre-calculated values, and for a design like the Game of Life
|
||||||
|
**run several generations in sequence** so you know state carries over correctly.
|
||||||
|
Edge behaviour (wrap-around or not) is your choice - but decide, and test it.
|
||||||
|
|
||||||
|
### 2.4 Synthesis and tuning
|
||||||
|
|
||||||
|
Flow panel -> **C Synthesis -> Run**. Then read the report:
|
||||||
|
|
||||||
|
- **Timing Estimate** - at 100 MHz the target period is 10 ns. The 3a design
|
||||||
|
estimates ~7.3 ns, so it meets timing.
|
||||||
|
- **Performance and Resource Estimates** - overall latency and interval, then
|
||||||
|
per-loop. This is why loops are **labelled** in EMBS code.
|
||||||
|
- **Utilisation** - LUTs, FFs, DSPs, BRAMs. Click the % toggle to see it as a
|
||||||
|
fraction of the device. The Z-7010 has **60 BRAMs**, 36 kbit each.
|
||||||
|
|
||||||
|
The 3a worked example is the clearest demonstration of the trade-off:
|
||||||
|
|
||||||
|
| Change | Latency | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Baseline (with `PIPELINE off` deliberately) | 910 | |
|
||||||
|
| `PIPELINE` on both loops | 521 | Interval 1; trip counts 100 and 99 |
|
||||||
|
| `UNROLL` both loops | ~420 | ~7300 LUTs - **3x the size**; HLS used 4 BRAMs instead of 1 |
|
||||||
|
| + `INLINE` on both functions | 366 | Saved hardware by scheduling across functions |
|
||||||
|
| + `ARRAY_PARTITION type=complete` on the array, `UNROLL` the read loop | **115** | BRAMs down, LUTs even slightly down |
|
||||||
|
|
||||||
|
Inspect with **C Synthesis -> Reports -> Schedule Viewer** and the **Analysis**
|
||||||
|
perspective. Columns are clock states; things in the same column happen in
|
||||||
|
parallel. A successfully unrolled design looks *vertical*; a stuck one looks
|
||||||
|
*horizontal*.
|
||||||
|
|
||||||
|
**BRAMs only have two access ports**, so an array in BRAM caps your parallelism
|
||||||
|
at 2. `ARRAY_PARTITION` with `type=complete` moves it into registers. For a
|
||||||
|
multidimensional array, set **`dim=0`** so all dimensions are partitioned.
|
||||||
|
|
||||||
|
Game of Life targets (P3b): **latency under 300 cycles**; ~200-250 is reachable;
|
||||||
|
the author has one at **43 cycles**.
|
||||||
|
|
||||||
|
Useful directives, from the knowledge base: `PIPELINE`, `UNROLL`, `INLINE`,
|
||||||
|
`ARRAY_PARTITION`, `ARRAY_MAP`, `ALLOCATION`, `LATENCY`, `LOOP_FLATTEN`,
|
||||||
|
`LOOP_TRIPCOUNT`, `DATAFLOW`, `RESOURCE`, `BIND_STORAGE`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3 - Put the IP into the hardware (Practical 4)
|
||||||
|
|
||||||
|
### 3.1 Export the IP
|
||||||
|
|
||||||
|
In Vitis HLS -> Flow Navigator -> **Package**:
|
||||||
|
|
||||||
|
- **output.file** = something sensible (e.g. `practical4`)
|
||||||
|
- **output.format** = `ip_catalog`
|
||||||
|
- **Run**
|
||||||
|
|
||||||
|
### 3.2 Add it to Vivado
|
||||||
|
|
||||||
|
1. Vivado -> open your Block Design.
|
||||||
|
2. **Window -> IP Catalog**.
|
||||||
|
3. Flow Navigator -> **Settings** -> **IP** -> **Repository** -> **+**.
|
||||||
|
4. Select your **HLS project directory** -> Select. Vivado scans and reports an
|
||||||
|
IP was added -> OK -> OK.
|
||||||
|
5. In the Block Design -> **Add IP** -> your core is listed under the Display
|
||||||
|
Name you set (or "Toplevel" if you forgot).
|
||||||
|
|
||||||
|
### 3.3 Wire it up
|
||||||
|
|
||||||
|
**Only if your IP has an `m_axi` port** (it needs main memory):
|
||||||
|
|
||||||
|
1. Double-click the **Zynq** block -> **PS-PL Configuration** -> expand
|
||||||
|
**HP Slave AXI Interface** -> tick **S AXI HP0** -> OK. A new port appears.
|
||||||
|
2. **Run Connection Automation** -> tick **S_AXI_HP0** -> it offers to connect it
|
||||||
|
to your IP's `m_axi`.
|
||||||
|
|
||||||
|
**Always:**
|
||||||
|
|
||||||
|
3. In the same Connection Automation dialog, tick **`s_axi_AXILiteS`** -> it
|
||||||
|
offers to connect to **`M_AXI_GP0`** on the processing system -> OK.
|
||||||
|
|
||||||
|
Result: your slave port goes through `processing_system7_0_axi_periph` to the PS,
|
||||||
|
and (if you have one) your master port goes through `axi_mem_intercon` into the
|
||||||
|
Zynq.
|
||||||
|
|
||||||
|
> The Game of Life as written has **no `m_axi` port**, so it needs **no S AXI
|
||||||
|
> HP0** - just the GP0 path. Don't tick HP0 unless you actually have a master
|
||||||
|
> interface.
|
||||||
|
|
||||||
|
**Tracing rule to remember:** the Zynq's `M_AXI` must be traceable to every
|
||||||
|
`S_AXI` in your design, and any IP's `M_AXI` must be traceable to `S_AXI_HP0` on
|
||||||
|
the Zynq. Vivado may use an **AXI SmartConnect** instead of an AXI Interconnect -
|
||||||
|
that is fine.
|
||||||
|
|
||||||
|
If the AXI structure comes out wrong: delete all the AXI Interconnect blocks and
|
||||||
|
re-run Connection Automation.
|
||||||
|
|
||||||
|
### 3.4 Rebuild and re-export
|
||||||
|
|
||||||
|
1. Save the block design.
|
||||||
|
2. **Generate Bitstream**.
|
||||||
|
3. **File -> Export -> Export Hardware** -> **Include bitstream** ->
|
||||||
|
**overwrite** the existing XSA.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4 - Application, programming, testing
|
||||||
|
|
||||||
|
### 4.1 Re-read the XSA, then write the app
|
||||||
|
|
||||||
|
**Order matters here - this is the step that was out of place in your list.**
|
||||||
|
|
||||||
|
1. In Vitis, open your **platform**'s `vitis-comp.json` ->
|
||||||
|
**Switch / re-read XSA** -> point at the new XSA.
|
||||||
|
2. Under **drivers**, your IP now appears (e.g. `toplevel`).
|
||||||
|
3. **File -> New Component -> Application**, on the `design_1_wrapper`
|
||||||
|
platform, **letting it create a new System**. Required whenever the hardware
|
||||||
|
interface changes.
|
||||||
|
|
||||||
|
### 4.2 Driving the IP from the ARM
|
||||||
|
|
||||||
|
The core principle, from the Software API page: **you never "pass data" to HLS.**
|
||||||
|
The data is in shared main memory; the ARM just tells the core where to look.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "xil_printf.h"
|
||||||
|
#include "xparameters.h"
|
||||||
|
#include "xtoplevel.h"
|
||||||
|
#include "xil_cache.h"
|
||||||
|
|
||||||
|
u32 shared[1000];
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
XToplevel hls;
|
||||||
|
Xil_DCacheDisable();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) shared[i] = i;
|
||||||
|
shared[0] = 8000;
|
||||||
|
|
||||||
|
XToplevel_Initialize(&hls, XPAR_TOPLEVEL_0_BASEADDR);
|
||||||
|
XToplevel_Set_ram(&hls, (u32) shared); // where the core should look
|
||||||
|
XToplevel_Start(&hls);
|
||||||
|
while (!XToplevel_IsDone(&hls));
|
||||||
|
|
||||||
|
printf("arg2 = %lu\narg3 = %lu\n",
|
||||||
|
XToplevel_Get_arg2(&hls), XToplevel_Get_arg3(&hls));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always set the RAM offset.** `XToplevel_Set_ram` is what makes `ram[0]` inside
|
||||||
|
HLS mean "start of my shared array" rather than "address 0 of DDR".
|
||||||
|
|
||||||
|
Getter/setter names change with direction. If the core only *reads* an argument
|
||||||
|
you get `XToplevel_Set_arg1` / `XToplevel_Get_arg1`; if it reads **and writes**
|
||||||
|
you get `XToplevel_Set_arg1_V_i` / `XToplevel_Get_arg1_V_o`. Check the generated
|
||||||
|
`xtoplevel.h` rather than guessing.
|
||||||
|
|
||||||
|
### 4.3 Caches - the silent wrong-answer bug
|
||||||
|
|
||||||
|
The ARM's caches are invisible to your IP, and HLS's writes do not invalidate
|
||||||
|
them. You must handle it explicitly:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <xil_cache.h>
|
||||||
|
|
||||||
|
// after writing shared data, before starting the core
|
||||||
|
Xil_DCacheFlush();
|
||||||
|
// or: Xil_DCacheFlushRange((INTPTR) shared, sizeof(shared));
|
||||||
|
|
||||||
|
XToplevel_Start(&hls);
|
||||||
|
while (!XToplevel_IsDone(&hls));
|
||||||
|
|
||||||
|
// before reading results back
|
||||||
|
Xil_DCacheInvalidate();
|
||||||
|
// or: Xil_DCacheInvalidateRange((INTPTR) shared, sizeof(shared));
|
||||||
|
```
|
||||||
|
|
||||||
|
`Xil_DCacheDisable()` also works and is simpler - just slower.
|
||||||
|
|
||||||
|
### 4.4 Fix the bitstream path before you iterate
|
||||||
|
|
||||||
|
Do this **once**, before you start changing HLS code. Vitis does not reliably
|
||||||
|
refresh its own copy of the bitfile, so it can silently program **old hardware**.
|
||||||
|
|
||||||
|
1. Application -> **Settings -> launch.json**.
|
||||||
|
2. **Bitstream File** is probably
|
||||||
|
`${workspaceFolder}/<project>/_ide/bitstream/design_1_wrapper.bit`.
|
||||||
|
3. Change it to Vivado's real output:
|
||||||
|
`<vivado project>/<project>.runs/impl_1/design_1_wrapper.bit`.
|
||||||
|
|
||||||
|
### 4.5 Program and run
|
||||||
|
|
||||||
|
From Vitis, hitting **Run** does both halves: programs the **PL** with the
|
||||||
|
bitstream, and loads the **ARM executable** into DDR. That is why "upload to
|
||||||
|
hardware" is really two uploads.
|
||||||
|
|
||||||
|
### 4.6 Test it out
|
||||||
|
|
||||||
|
- **Serial output** on a lab PC: `screen /dev/ttyUSB1 115200` (try ttyUSB0/2 if
|
||||||
|
that fails). Exit with **Ctrl-A**, release, then **k**, then **y**.
|
||||||
|
Full settings: 115200 8N1, no flow control.
|
||||||
|
- **Do not use the SDK/Vitis built-in console** - it is line-buffered and gets
|
||||||
|
slow with lots of data.
|
||||||
|
- For **single keypresses** use `XUartPs_IsReceiveData` /
|
||||||
|
`XUartPs_RecvByte` / `XUartPs_SendByte` from `xuartps_hw.h`, because `scanf`
|
||||||
|
blocks until a newline.
|
||||||
|
- **Timing** measurements: `XTime_GetTime`, with `COUNTS_PER_SECOND`, from
|
||||||
|
`xiltimer.h`. Put a `usleep(1000)` first or the timer reads 0.
|
||||||
|
- **Debug your own IP**: add a `version` slave argument set to a constant (e.g.
|
||||||
|
42). If `XToplevel_Get_version()` returns 42, the slave interface and the whole
|
||||||
|
build/export chain are good. Then add a `mode` argument and switch between
|
||||||
|
normal operation and small AXI read/write test cases - that isolates a broken
|
||||||
|
master interface from a broken algorithm.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The loop: when you change your HLS
|
||||||
|
|
||||||
|
This is the sequence you will actually live in. The docs list it in two places
|
||||||
|
(Practical 4 and the knowledge base) and it is the same list:
|
||||||
|
|
||||||
|
1. Re-run **synthesis** in HLS.
|
||||||
|
2. **Re-export** the IP core (Package -> Run).
|
||||||
|
3. In Vivado, it should say **"IP Catalog is out-of-date"**. If not: **IP Status**
|
||||||
|
-> **Re-Run Report**, then **Refresh IP Catalog**.
|
||||||
|
4. In the **IP Status** panel, select your IP -> **Upgrade Selected**.
|
||||||
|
5. **Generate Output Products** -> **Generate**.
|
||||||
|
6. **Generate Bitstream**.
|
||||||
|
7. **Export Hardware** (include bitstream).
|
||||||
|
8. In Vitis, platform -> **Settings -> vitis-comp.json -> Switch / re-read XSA**.
|
||||||
|
9. Reprogram and run.
|
||||||
|
10. If you changed the **interface**, regenerate the System and move the
|
||||||
|
application into it.
|
||||||
|
|
||||||
|
> Step 8 is not optional even with the launch.json fix. Re-reading the XSA is what
|
||||||
|
> updates the hardware description, the **drivers** and **xparameters.h**. The
|
||||||
|
> launch.json change only fixes *which bitfile gets programmed*.
|
||||||
|
|
||||||
|
**Symptom of getting this wrong:** software hangs talking to your core, or the
|
||||||
|
core returns stale/nonsense results, straight after a change that "should" have
|
||||||
|
worked. Compare modification times:
|
||||||
|
|
||||||
|
```
|
||||||
|
<workspace>/<project>/_ide/bitstream/design_1_wrapper.bit <- Vitis' copy
|
||||||
|
<vivado project>/<project>.runs/impl_1/design_1_wrapper.bit <- what Vivado built
|
||||||
|
```
|
||||||
|
|
||||||
|
If the first is older, Vitis is running old hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick reference
|
||||||
|
|
||||||
|
| I want to... | Where |
|
||||||
|
| --- | --- |
|
||||||
|
| Set the HLS top function | HLS: Settings -> `hls_config.cfg` -> C Synthesis sources -> top |
|
||||||
|
| Register a testbench | HLS: Settings -> `hls_config.cfg` -> Testbench Sources -> Add File |
|
||||||
|
| Run the testbench | HLS: Flow -> C Simulation -> Run |
|
||||||
|
| See latency / II | HLS: C Synthesis -> Reports -> Synthesis |
|
||||||
|
| See what runs in parallel | HLS: C Synthesis -> Reports -> Schedule Viewer, and the Analysis perspective |
|
||||||
|
| Add a directive | HLS: View -> HLS Directives (or the two-star button) |
|
||||||
|
| Package the IP | HLS: Flow Navigator -> Package -> `ip_catalog` -> Run |
|
||||||
|
| Register the IP repo | Vivado: Settings -> IP -> Repository -> + |
|
||||||
|
| Enable DDR access | Vivado: Zynq block -> PS-PL Configuration -> HP Slave AXI -> S AXI HP0 |
|
||||||
|
| Auto-wire AXI | Vivado: green banner -> Run Connection Automation |
|
||||||
|
| Export hardware | Vivado: File -> Export -> Export Hardware -> Include bitstream |
|
||||||
|
| Re-read hardware in Vitis | Platform -> Settings -> `vitis-comp.json` -> Switch / re-read XSA |
|
||||||
|
| Fix which bitfile is programmed | Application -> Settings -> `launch.json` -> Bitstream File |
|
||||||
|
| Import a driver example | Platform -> `vitis-comp.json` -> ... -> Board Support Package -> drivers -> Import Examples |
|
||||||
|
| Measure code time | `XTime_GetTime` from `xiltimer.h` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Traps that cost hours
|
||||||
|
|
||||||
|
1. **The `port=return` pragma with `register`.** Without it your IP has no
|
||||||
|
control register and cannot be started from the ARM.
|
||||||
|
2. **Forgetting to add the testbench to `hls_config.cfg`.** Creating the file is
|
||||||
|
not enough.
|
||||||
|
3. **Forgetting `XToplevel_Set_ram`.** Your core writes over arbitrary memory.
|
||||||
|
4. **Caches.** Flush before the core starts, invalidate before reading results.
|
||||||
|
5. **Not re-reading the XSA.** Stale drivers and stale `xparameters.h`.
|
||||||
|
6. **Vitis programming an old bitfile.** Fix the launch.json path once.
|
||||||
|
7. **Spaces in paths or project names.**
|
||||||
|
8. **A `uint32`-vs-wide datatype on an `m_axi` port.** 100 wires you cannot route.
|
||||||
|
9. **Running out of disk.** Check `quota -s`; aim for ~500 MB free. `~/.cache` is
|
||||||
|
the usual culprit. If the tools misbehave after filling the disk, delete
|
||||||
|
`.Xilinx` and `.Xil` from your home directory.
|
||||||
|
10. **Not making a new System after changing the interface.** The application
|
||||||
|
will not see the new driver.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VLAB notes (you are off campus)
|
||||||
|
|
||||||
|
Everything above works unchanged, with three differences:
|
||||||
|
|
||||||
|
- **Program the FPGA** via **Remote Server** rather than a local target:
|
||||||
|
Hostname `localhost`, Port `12345` (whatever you passed to `vlab.py -l`).
|
||||||
|
In Vivado: Open Hardware Manager -> Open Target -> Open New Target -> Remote
|
||||||
|
Server. In Vitis: Settings -> `launch.json` -> Target Connection -> new.
|
||||||
|
- **Serial** - you do not need `screen /dev/ttyUSB*`. The VLAB client *is* the
|
||||||
|
serial terminal.
|
||||||
|
- **No buttons, no LEDs, no live HDMI.** You cannot observe anything except over
|
||||||
|
the UART. Design for that: this is exactly why the Game of Life testbench and
|
||||||
|
the `version`/`mode` debug arguments matter so much more on the VLAB.
|
||||||
|
|
||||||
|
Remember `vlab.py` must run **on the machine running the Xilinx tools**, and on
|
||||||
|
a shared server you need your own port (`-l 20041`) because 12345 collides.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where each part came from
|
||||||
|
|
||||||
|
| Topic | Page |
|
||||||
|
| --- | --- |
|
||||||
|
| Project, block design, bitstream, platform, first app | `docs/practicals/practical-1a` |
|
||||||
|
| GPIO, UART calculator, extending the hardware | `docs/practicals/practical-1b` |
|
||||||
|
| HLS project, testbench, synthesis, directives | `docs/practicals/practical-3a` |
|
||||||
|
| Game of Life spec and targets | `docs/practicals/practical-3b` |
|
||||||
|
| Exporting IP, wiring AXI, iteration loop, Collatz | `docs/practicals/practical-4` |
|
||||||
|
| Sharing memory, caches, memcpy, timers, UART, Ethernet | `docs/resourcestips/software-api` |
|
||||||
|
| Terms, directives, AXI interfaces, loop optimisations | `docs/resourcestips/hls-knowledge-base` |
|
||||||
|
| Debug interfaces and debug modes for your own IP | `docs/resourcestips/debugging` |
|
||||||
|
| Serial terminal settings | `docs/resourcestips/uart` |
|
||||||
|
| Disk quota and cleaning projects | `docs/resourcestips/disk-usage` |
|
||||||
|
| VLAB remote target and serial | `docs/setup/vlab`, `docs/setup/vlab-quickstart` |
|
||||||
Reference in New Issue
Block a user