Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
07c810a
feat: add Nucleo-L476RG as experimental RTE bring-up platform
hibyemy Jul 29, 2026
17c93c7
Add HostSim base image and merge upstream NodeGUI runtime visualizer.
hibyemy Jul 29, 2026
d6602cc
Ignore HostSim build artifacts and trace output.
hibyemy Jul 29, 2026
958c0c1
Add HostSim live telemetry, SPWM demo, and NodeGUI scope plotting.
hibyemy Jul 29, 2026
8b3677a
Default PWM scope off unless scenario opts in.
hibyemy Jul 29, 2026
cb7bcb3
Fix SVPWM resolution, WSL permissions, and NodeGUI multi-threading de…
hibyemy Jul 30, 2026
3ae32de
NodeGUI: plant backend + SPICE render controls, larger plot buffers
hibyemy Jul 31, 2026
95d68cc
WIP: ngspice free-running co-sim + live console + two-view scenarios
hibyemy Jul 31, 2026
da5957a
live_check: add realtime-target and CLI pass-through args
hibyemy Aug 1, 2026
a4ea349
SVPWM demo: fix launchers for native Windows emitter, update graph no…
hibyemy Aug 1, 2026
207b985
SPICE render: record burst to CSV with progress, add static plot wind…
hibyemy Aug 1, 2026
ab64ca5
RTECodeEmitter: static-link MinGW runtime, fix copy_file overwrite on…
hibyemy Aug 1, 2026
13e8777
cleanup(HostSim,NodeGUI): cross-platform demo scripts and docs
datacrystals Aug 2, 2026
c6d2ddc
fix(HostSim): don't kill NodeGUI from run_spwm_live.sh
datacrystals Aug 2, 2026
3bdafb0
fix(HostSim,NodeGUI): run loaded graph in simulator and add FOC runti…
datacrystals Aug 2, 2026
3eb0835
fix(NodeGUI,NodeAPI): fix Windows build CMake duplicate target and un…
hibyemy Aug 5, 2026
8ce0e35
fix(scripts): prepend Qt and MinGW bin paths to PATH in launchers to …
hibyemy Aug 5, 2026
a73ca1c
fix(audit): remove hardcoded SPWM fallback, add --templatesDir to lau…
hibyemy Aug 5, 2026
466f8a2
fix(templates,gui): add zero-guards for division operations in templa…
hibyemy Aug 5, 2026
77d5b97
fix(emitter): improve const-correctness and silence unused variable w…
hibyemy Aug 5, 2026
2329a47
feat(fpga): implement Q15 fixed-point hardware FOC acceleration engin…
hibyemy Aug 5, 2026
198ee65
feat(templates): add Control.Ladrc node and port descriptions to Nucl…
hibyemy Aug 7, 2026
cf6b999
docs: add agent-log cross-LLM journal and hybrid Nucleo+FPGA pivot ro…
hibyemy Aug 7, 2026
d1dddfc
fix(cmake): drop leftover conflict-marker tail in root CMakeLists
hibyemy Aug 7, 2026
f1c1f2e
docs: correct PR #3 status in hybrid roadmap
hibyemy Aug 7, 2026
beb2ac8
fix(protocol): harden host transports and client
hibyemy Aug 7, 2026
4e34621
fix(nodeapi,logger): graph API and loader hardening
hibyemy Aug 7, 2026
faf6e7d
fix(codegen,emitter,builder): filesystem and parameter safety
hibyemy Aug 7, 2026
8f1fde7
feat(gui): selectable firmware target for Generate/Build
hibyemy Aug 7, 2026
5765f50
fix(tools,launchers): script correctness and portability
hibyemy Aug 7, 2026
4b99aed
fix(hostsim): correct emitter flag and multi-config exe lookup
hibyemy Aug 7, 2026
adfd411
docs: FPGA FOC fundamentals guide + agent-log entries
hibyemy Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
build/
build_linux/
out/
.tools/
.vs/
*.user
__pycache__/
*.pyc
*.csv
.mxproject
.vscode/
2 changes: 1 addition & 1 deletion Assets/NodeTemplates/Control.Pi/inline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ float raw_output = Kp * error + Ki * Integral;
* the six-step boundary (2*Vdc/3) so the SVPWM stage can use the full
* hexagon when commanded. Linear SVPWM limit is Vdc/sqrt(3). */
const float vdc = platform_get_dc_link_voltage();
const float dynamic_max = vdc * 2.0f / 3.0f;
const float dynamic_max = (vdc > 0.0f) ? (vdc * 2.0f / 3.0f) : OutputMax;
const float max_limit = (dynamic_max < OutputMax) ? dynamic_max : OutputMax;
const float min_limit = (-dynamic_max > OutputMin) ? -dynamic_max : OutputMin;

Expand Down
2 changes: 0 additions & 2 deletions Assets/NodeTemplates/Sensors.CanRx/inline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@ if (dlc > 0) {
Dlc = static_cast<float>(dlc);
D0 = buf[0]; D1 = buf[1]; D2 = buf[2]; D3 = buf[3];
D4 = buf[4]; D5 = buf[5]; D6 = buf[6]; D7 = buf[7];
} else {
Dlc = 0.0f;
}
2 changes: 1 addition & 1 deletion Assets/NodeTemplates/Sensors.DcLinkVoltage/inline.cpp
Original file line number Diff line number Diff line change
@@ -1 +1 @@
V_Dc = rte::Volts(platform_get_dc_link_voltage());
V_Dc = platform_get_dc_link_voltage();
2 changes: 1 addition & 1 deletion Assets/NodeTemplates/Sensors.DcLinkVoltage/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"description": "Measured DC-link voltage in volts.",
"direction": "output",
"type": {
"quantity": "voltage",
"quantity": "dimensionless",
"frame": "scalar",
"dtype": "f32"
}
Expand Down
5 changes: 5 additions & 0 deletions Assets/NodeTemplates/Transforms.Derivative/inline.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* Filtered derivative: Out ~= d(In)/dt, low-passed at Fc [Hz] to keep
* sampling noise out of the estimate (Fc <= 0 disables filtering).
* Prev/OutState are persistent state (leave at graph defaults). */
if (Dt <= 0.000001f) {
Out = OutState;
return;
}

const float raw = (In - Prev) / Dt;
Prev = In;
if (Fc > 0.0f) {
Expand Down
7 changes: 7 additions & 0 deletions Assets/NodeTemplates/Transforms.Svpwm/inline.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/* Clamp the alpha/beta voltage vector to the six-step boundary.
* The maximum line-to-neutral voltage magnitude for linear modulation is
* Vdc / sqrt(3); overmodulation is allowed up to 2*Vdc/3. */
if (V_Dc.in(au::volts) <= 0.001f) {
Duty_A = 0.0f;
Duty_B = 0.0f;
Duty_C = 0.0f;
return;
}

const float sqrt3 = 1.7320508075688772f;
const float v_max_linear = V_Dc.in(au::volts) * 2.0f / 3.0f;
float valpha = V_Alpha.in(au::volts);
Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ add_subdirectory(Source/RTEAutomation)
add_subdirectory(Source/RTECodeEmitter)
add_subdirectory(Source/RTEFirmwareBuilder)
add_subdirectory(Source/RTECLI)
add_subdirectory(Source/NodeGUI)
option(BUILD_NODEGUI "Build the NodeGUI Qt application" ON)
if(BUILD_NODEGUI)
add_subdirectory(Source/NodeGUI)
endif()

install(TARGETS rte RTECodeEmitter RTEFirmwareBuilder
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
Expand Down
97 changes: 97 additions & 0 deletions Fpga/TangNano20k/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Tang Nano 20K — standalone FPGA PWM track

> **Toolchain:** [Icarus Verilog](http://bleyer.org/icarus/) is **required** for the RTL simulation gate (`scripts/build_sim.*` must exit 0). **Gowin EDA** is the recommended path for synthesis, place/route, and programming on real hardware.

Complete **FPGA-only** workflow for Sipeed **Tang Nano 20K** (`GW2AR-LV18QN88C8/I7` / GW2AR-18C).
Does **not** depend on Gen6FW, `Lib/`, or `Source/`. The SPI register map in [`docs/register_map.md`](docs/register_map.md) is the frozen contract for a later Nucleo `SPI2` join.

## Tree

```
Fpga/TangNano20k/
├── README.md
├── rtl/ # Verilog (PWM, SPI slave, FOC stubs, top)
├── tb/ # Icarus testbenches (must exit 0)
├── constraints/ # tangnano20k.cst
├── scripts/ # build_sim, flash notes, optional Gowin tcl
└── docs/
└── register_map.md # SPI contract
```

## What it does

| Block | Role |
|-------|------|
| `pwm_complementary` | 6-channel center-aligned complementary PWM + dead-time (TIM1 electrical intent: ~10 kHz, ~1 µs DT, 50 % park) |
| `spi_regs` | Mode-0 SPI slave + register file (duty U/V/W, freq, dead-time, enable, fault/status, magic/version) |
| `foc_*_stub` | Empty Clarke / Park / SVPWM shells — **not** in the PWM path |
| `tangnano20k_top` | Wiring for board pins |

Fixed-frequency / fixed-duty defaults come from the register file reset values; change them over SPI (or edit defaults in `spi_regs.v`).

## Simulation gate (required)

Install [Icarus Verilog](http://bleyer.org/icarus/) (or oss-cad-suite). Then:

```powershell
cd Fpga\TangNano20k
.\scripts\build_sim.ps1
```

- `tb_pwm` — duty, period, dead-time, complementary non-overlap
- `tb_spi` — simulated SPI master, full register map

Both must print `PASS` and the script must exit **0**.

Optional Verilator: compile the same RTL/TB sources with `--timing` / event-driven TB support; Icarus is the checked path.

## Gowin EDA — manual hardware test

1. Install **Gowin V1.9+** from Gowin Semiconductor.
2. **New Project** → device **GW2AR-LV18QN88C8/I7** (device version **C**).
3. Add sources:
- `rtl/deadtime_pair.v`
- `rtl/pwm_complementary.v`
- `rtl/spi_regs.v`
- `rtl/foc_clarke_stub.v`
- `rtl/foc_park_stub.v`
- `rtl/foc_svpwm_stub.v`
- `rtl/tangnano20k_top.v`
- `constraints/tangnano20k.cst`
4. Set top module `tangnano20k_top`.
5. Run **Synthesize** → **Place & Route**.
6. **Program Device** over USB-C (onboard BL616). Program **external Flash** so the bitstream survives power cycle.
7. Scope bring-up (no MCU):
- Hold KEY2 released (`rst_n` high).
- Drive SPI (Mode 0): write `CTRL=1` (`addr 0x04`) to enable PWM.
- Probe pins **25/26** (UH/UL): expect ~10 kHz, ~50 % high, ~1 µs both-low dead-time.
8. Details: [`scripts/flash_notes.md`](scripts/flash_notes.md). Optional Tcl sketch: [`scripts/gowin_project.tcl`](scripts/gowin_project.tcl).

### Pin summary

| Signal | FPGA pin | Notes |
|--------|----------|--------|
| `clk_27m` | 4 | 27 MHz OSC |
| `rst_n` | 87 | KEY2, active-low |
| `led_n` | 15 | LED0, active-low; on when PWM running |
| `pwm_uh..wl` | 25–30 | LCD FPC GPIOs (LCD unused) |
| `spi_cs_n/sclk/mosi/miso` | 71–74 | Edge connector for later Nucleo SPI2 |

## Optional open-source flow (Yosys / nextpnr / Apicula)

Not required if Gowin is used. Typical outline:

```text
yosys -p "read_verilog rtl/*.v; synth_gowin -top tangnano20k_top -json top.json"
nextpnr-himbaechel --json top.json --write pnr.json --device GW2AR-18C --vopt family=GW2A-18C --vopt cst=constraints/tangnano20k.cst
gowin_pack -d GW2AR-18C -o top.fs pnr.json
openFPGALoader -b tangnano20k top.fs
```

Tool versions and Himbaechel flags change often — treat Gowin GUI as the supported path for bring-up.

## Compatibility

- Stays under `Fpga/TangNano20k/` only.
- No dependency on MCU firmware images.
- Later join: Nucleo SPI2 master ↔ this slave using `docs/register_map.md` only.
43 changes: 43 additions & 0 deletions Fpga/TangNano20k/constraints/tangnano20k.cst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Tang Nano 20K (GW2AR-LV18QN88C8/I7) — physical constraints
// Device: GW2AR-18C | Board: Sipeed Tang Nano 20K
// PWM pins use LCD FPC GPIO when the LCD is not fitted (documented headers).
// SPI pins use the HSPI-side edge connector for Nucleo join bring-up.
// Do NOT add BANK_VCCIO attributes (Gowin CT1108).

// 27 MHz oscillator
IO_LOC "clk_27m" 4;
IO_PORT "clk_27m" IO_TYPE=LVCMOS33;

// User key KEY2 as active-low reset (pressed = 0)
IO_LOC "rst_n" 87;
IO_PORT "rst_n" IO_TYPE=LVCMOS33 PULL_MODE=UP;

// Onboard LED0 (active-low)
IO_LOC "led_n" 15;
IO_PORT "led_n" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8;

// Six complementary PWM outputs — LCD connector GPIOs (LCD unused)
// Map: UH/UL/VH/VL/WH/WL → PIN25..PIN30
IO_LOC "pwm_uh" 25;
IO_PORT "pwm_uh" IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "pwm_ul" 26;
IO_PORT "pwm_ul" IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "pwm_vh" 27;
IO_PORT "pwm_vh" IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "pwm_vl" 28;
IO_PORT "pwm_vl" IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "pwm_wh" 29;
IO_PORT "pwm_wh" IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "pwm_wl" 30;
IO_PORT "pwm_wl" IO_TYPE=LVCMOS33 DRIVE=8;

// SPI slave — edge connector (HSPI DIN bank; free when HSPI unused)
// Nucleo SPI2 master → these pins (3.3 V only)
IO_LOC "spi_cs_n" 71;
IO_PORT "spi_cs_n" IO_TYPE=LVCMOS33 PULL_MODE=UP;
IO_LOC "spi_sclk" 72;
IO_PORT "spi_sclk" IO_TYPE=LVCMOS33 PULL_MODE=DOWN;
IO_LOC "spi_mosi" 73;
IO_PORT "spi_mosi" IO_TYPE=LVCMOS33 PULL_MODE=DOWN;
IO_LOC "spi_miso" 74;
IO_PORT "spi_miso" IO_TYPE=LVCMOS33 DRIVE=8;
99 changes: 99 additions & 0 deletions Fpga/TangNano20k/docs/register_map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Tang Nano 20K PWM — SPI Register Map

**Frozen contract** for a later join with Nucleo `SPI2` (MCU master → FPGA slave).
This FPGA track does **not** depend on the MCU image; the map is the only intended software interface.

## Physical / framing

| Item | Value |
|------|--------|
| Mode | SPI Mode 0 (`CPOL=0`, `CPHA=0`) |
| Bit order | MSB first |
| Frame | 24 bits: `ADDR[7:0]` then `DATA[15:0]` |
| Chip select | Active-low `CS_N`; idle high between frames |
| Clock | Master-driven; recommend ≤ 10 MHz for bring-up |
| Voltage | 3.3 V LVCMOS |

### Address byte

| Bits | Meaning |
|------|---------|
| `[7]` | `0` = write, `1` = read |
| `[6:0]` | Register address |

### Write

1. Drive `CS_N` low.
2. Clock out `{0, addr[6:0]}`, then `data[15:8]`, then `data[7:0]`.
3. Drive `CS_N` high. Write commits on the last SCLK rising edge while `CS_N` is low (or on `CS_N` rising — slave latches on bit 23 complete).

### Read

1. Drive `CS_N` low.
2. Clock out `{1, addr[6:0]}`, then two dummy bytes.
3. On `MISO`, after the address byte, the slave returns `data[15:8]` then `data[7:0]`.
4. Drive `CS_N` high.

`MISO` is high-Z when `CS_N` is high.

## Register table

All registers are 16-bit. Unlisted addresses read as `0x0000` and ignore writes.

| Addr | Name | Access | Reset | Description |
|------|------|--------|-------|-------------|
| `0x00` | `MAGIC` | RO | `0x544E` | ASCII `"TN"` — presence check |
| `0x01` | `VERSION` | RO | `0x0001` | Register-map / RTL version |
| `0x02` | `STATUS` | RO | `0x0000` | See status bits below |
| `0x03` | `FAULT` | R / W1C | `0x0000` | Sticky faults; write-1-to-clear per bit |
| `0x04` | `CTRL` | RW | `0x0000` | Control bits |
| `0x05` | `FREQ_HZ` | RW | `10000` | Switching frequency (Hz), center-aligned |
| `0x06` | `DEADTIME_NS` | RW | `1000` | Dead-time (nanoseconds), same electrical intent as MCU TIM1 (~1 µs) |
| `0x07` | `DUTY_U` | RW | `5000` | Phase U duty, units of 0.01 % (`0`…`10000` → 0…100.00 %) |
| `0x08` | `DUTY_V` | RW | `5000` | Phase V duty, same units |
| `0x09` | `DUTY_W` | RW | `5000` | Phase W duty, same units |
| `0x0A` | `SCRATCH` | RW | `0x0000` | Loopback / connectivity test |
| `0x0B` | `CLK_HZ_LO` | RO | — | System clock Hz, low 16 bits |
| `0x0C` | `CLK_HZ_HI` | RO | — | System clock Hz, high 16 bits |

### `STATUS` bits

| Bit | Name | Meaning |
|-----|------|---------|
| 0 | `ENABLED` | `CTRL.PWM_EN` is set and outputs are armed |
| 1 | `FAULT` | Any sticky bit in `FAULT` is set |
| 2 | `RUNNING` | PWM timer is counting (enabled and no blocking fault) |
| 15:3 | — | Reserved `0` |

### `CTRL` bits

| Bit | Name | Meaning |
|-----|------|---------|
| 0 | `PWM_EN` | `1` = enable complementary PWM outputs; `0` = force all six outs low |
| 15:1 | — | Reserved; write `0` |

### `FAULT` bits (W1C)

| Bit | Name | Meaning |
|-----|------|---------|
| 0 | `BAD_FREQ` | Requested `FREQ_HZ` out of range / period overflow |
| 1 | `BAD_DUTY` | Any duty > `10000` (clamped; sticky set) |
| 2 | `BAD_DEAD` | Dead-time longer than half period (clamped; sticky set) |
| 15:3 | — | Reserved |

## Electrical defaults (MCU TIM1 intent)

| Parameter | Default | Notes |
|-----------|---------|--------|
| Topology | 3× complementary pairs (UH/UL, VH/VL, WH/WL) | Same six gate signals as TIM1 CH1/1N/2/2N/3/3N |
| Alignment | Center-aligned | Matches TIM1 `CENTERALIGNED1` |
| Frequency | 10 kHz | Matches Gen6FW open-loop / reminder guidance |
| Dead-time | 1000 ns | Matches `PWM_SetDeadTime(1000)` / CubeMX ~1 µs |
| Duty | 50 % each phase | Safe idle park |

Duty is applied to the **high-side** ideal pulse; low-side is complementary with dead-time on both rising edges (non-overlap required).

## Versioning

- Bump `VERSION` when this document or the slave address decode changes.
- Hosts must check `MAGIC == 0x544E` before trusting other registers.
78 changes: 78 additions & 0 deletions Fpga/TangNano20k/rtl/deadtime_pair.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Rising-edge delayed complementary pair (non-overlap dead-time).
`timescale 1ns / 1ps

module deadtime_pair #(
parameter integer W = 16
) (
input wire clk,
input wire rst_n,
input wire enable,
input wire [W-1:0] dead_ticks,
input wire ideal_high,
output reg out_high,
output reg out_low
);

wire ideal_low = ~ideal_high;

reg [W-1:0] dly_h, dly_l;
reg next_h, next_l;
reg [W-1:0] next_dly_h, next_dly_l;

always @(*) begin
next_h = out_high;
next_l = out_low;
next_dly_h = dly_h;
next_dly_l = dly_l;

if (!enable) begin
next_h = 1'b0;
next_l = 1'b0;
next_dly_h = {W{1'b0}};
next_dly_l = {W{1'b0}};
end else begin
// High: fall immediately, rise after dead-time countdown
if (!ideal_high) begin
next_h = 1'b0;
next_dly_h = dead_ticks;
end else if (dly_h != {W{1'b0}}) begin
next_h = 1'b0;
next_dly_h = dly_h - 1'b1;
end else begin
next_h = 1'b1;
end

// Low: fall immediately, rise after dead-time countdown
if (!ideal_low) begin
next_l = 1'b0;
next_dly_l = dead_ticks;
end else if (dly_l != {W{1'b0}}) begin
next_l = 1'b0;
next_dly_l = dly_l - 1'b1;
end else begin
next_l = 1'b1;
end

// Hard safety: never both on
if (next_h && next_l) begin
next_h = 1'b0;
next_l = 1'b0;
end
end
end

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_high <= 1'b0;
out_low <= 1'b0;
dly_h <= {W{1'b0}};
dly_l <= {W{1'b0}};
end else begin
out_high <= next_h;
out_low <= next_l;
dly_h <= next_dly_h;
dly_l <= next_dly_l;
end
end

endmodule
Loading
Loading