Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
- `Distinct<L>` (`rules.h`): no layer twice in one composition, checked on exact types at instantiation (packs, aliases, equal types spelled
differently, types from other headers). Named compositions (anything with `::Types`) are spliced recursively; closed operands are also
compared by `is_base_of`. Covered in `tests/compile_tests.cpp`. Used by the `.RnD/openDerivation` translator in every composed struct.
- Example `examples/static_net`: static networks (a typelist of parts wired by index, id or query, no runtime data), tinyML on an 8-bit AVR as the running example, with checks, timing
- Example `examples/static_net`: static networks (a typelist of parts wired by index, id or query; the values a net reads are the slots of a state, `hapi/slots.h`, and a register is a layer of it), tinyML on an 8-bit AVR as the running example, with checks, timing
(simavr, confirmed on a real chip), and a comparison against a table loop and against emlearn. Uses `Expand` and `Drop<n>`; GCC/Clang only, not built by CI.

## 0.7.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ confirmed by eye.

[`examples/static_net`](examples/static_net) is a different kind of composition: **static networks**,
dry, typed, zero-runtime descriptions of dataflow nets (a typelist of parts, wired by index, id or query;
no runtime data but the input view), with tinyML as the running example: a 4-input classifier in 44 B and
the values a net reads are the slots of a state, `hapi/slots.h`, and a register is a layer of it), with tinyML as the running example: a 4-input classifier in 44 B and
25 cycles on an 8-bit ATmega328p. It is measured against a table loop and against emlearn on the same
folds, in one build setup; the cycle counts come from simavr and were confirmed on a real Arduino Nano
(100 numbers, no difference), and the realization of a cell (unrolled or as a table and a loop) is a
Expand Down
28 changes: 16 additions & 12 deletions examples/static_net/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

Static networks: dry, typed descriptions of dataflow nets.

A **net** is a typelist of parts, wired by index, by id or by a query. It carries no data of its own; the only runtime state is the input view it is evaluated on.
A **net** is a typelist of parts, wired by index, by id or by a query. It carries no data of its own: the values it reads live in a **state** of tag-addressed slots (`hapi::Slot`, see HAPI's README), and the net is evaluated over that state.
Because the whole structure is a type, the compiler resolves it at compile time: the forms below compile to the same disassembly as the hand-written equivalents (*What the composition costs*).
The running example is a 4-input classifier on an 8-bit AVR: 44 B and 25 cycles on an ATmega328p.

Parts agree on a small **contract** (a static, pure `proc(in)`, optionally `update(in)`); they do not inherit from a framework, and HAPI's `Chain<>` / `APIOf<>` / `Expand<>` do the
Parts agree on a small **contract** (a static, pure `proc(in)`); they do not inherit from a framework, and HAPI's `Chain<>` / `APIOf<>` / `Expand<>` do the
composing. Neural-net cells are the running example, not the point: any static dataflow of pure stages fits (a filter chain, a control loop, a sensor fusion), and other contracts can sit
beside `proc(in)`. Two inference engines are provided as parts, `wave` (no multiplies: shifts, masks and adds) and `lin` (plain integer linear, multiplies by design), and they mix freely in one net.

```cpp
// models/banknote/net.h -- the trained classifier is a TYPE. Its parameters are template arguments: no table, no state, no interpreter.
// models/banknote/net.h -- the trained classifier is a TYPE. Its parameters are template arguments: no table, no interpreter.
// Its four inputs are the four fields of one slot of the state, read by name (Variance is snet::Field<BanknoteTag,&BanknoteIn::variance>).
using BanknoteNet = wave::Cell<WAVE_K,
wave::Threshold,
wave::Wave<0,WAVE_F0_N,WAVE_F0_S,WAVE_F0_P,WAVE_F0_M>, wave::Wave<1,WAVE_F1_N,WAVE_F1_S,WAVE_F1_P,WAVE_F1_M>,
wave::Wave<2,WAVE_F2_N,WAVE_F2_S,WAVE_F2_P,WAVE_F2_M>, wave::Wave<3,WAVE_F3_N,WAVE_F3_S,WAVE_F3_P,WAVE_F3_M>>;
wave::Wave<Variance,WAVE_VARIANCE_N,WAVE_VARIANCE_S,WAVE_VARIANCE_P,WAVE_VARIANCE_M>, wave::Wave<Skewness,WAVE_SKEWNESS_N,WAVE_SKEWNESS_S,WAVE_SKEWNESS_P,WAVE_SKEWNESS_M>,
wave::Wave<Curtosis,WAVE_CURTOSIS_N,WAVE_CURTOSIS_S,WAVE_CURTOSIS_P,WAVE_CURTOSIS_M>, wave::Wave<Entropy,WAVE_ENTROPY_N,WAVE_ENTROPY_S,WAVE_ENTROPY_P,WAVE_ENTROPY_M>>;

BanknoteState features = banknote({variance, skewness, curtosis, entropy}); // the state: four input bytes, the only runtime data
bool y = BanknoteNet::proc(features); // a few shifts, masks and adds; 44 B of flash, 25 cycles on an ATmega328p
```

Expand All @@ -39,25 +41,27 @@ The headers use GNU attributes (`[[gnu::always_inline]]`) and the timing harness

```cpp
// sugar.h: the net as constexpr values whose TYPES are the net (values carry no data). 274 B on an ATmega328p, identical to the same net written by hand.
constexpr auto orr = cell<-1>(sign, w<2>*a, w<2>*b); // a = x<0>, b = x<1>: input slots
constexpr auto orr = cell<-1>(sign, w<2>*a, w<2>*b); // a = x<inp::A>, b = x<inp::B>: fields of the input state (inputs.h)
constexpr auto nand = cell< 3>(sign, w<-2>*a, w<-2>*b);
constexpr auto xr = cell<-3>(sign, w<2>*ref(orr), w<2>*ref(nand)); // a cell reads another cell, in place, same pass
using Net = decltype(net(orr, nand, xr)); // sizeof(Net) == 1

// refid.h: wiring by id or by a query. Cells carry a hapi::Tag<id>; a cell reads another by id, whatever the position and the engine
// (RW<id,..> is wave::WaveOf<snet::RefId<id>,..>): here a wave cell XORs a wave cell and a lin cell
using ById = snet::Net<
wave::Cell<0, hapi::Tag<NAND>, wave::Threshold, wave::Wave<0,0,6,0,0xff>, wave::Wave<1,0,6,0,0xff>>,
lin::Cell<-1, hapi::Tag<OR>, lin::Sign, lin::In<0,2>, lin::In<1,2>>,
wave::Cell<0, hapi::Tag<NAND>, wave::Threshold, wave::Wave<inp::A,0,6,0,0xff>, wave::Wave<inp::B,0,6,0,0xff>>,
lin::Cell<-1, hapi::Tag<OR>, lin::Sign, lin::In<inp::A,2>, lin::In<inp::B,2>>,
wave::Cell<128, hapi::Tag<XOR>, wave::Threshold, RW<NAND,0,6,0,0xff>, RW<OR,0,6,0,0xff>>>;
// RefId<id> is RefQ<SameAs<Tag<id>>>: any HAPI predicate over a cell's components works, e.g. RefQ<SameAs<lin::Sign>> is "the cell that has lin::Sign"; the first match wins
```

- **A cycle is a compile error**, not a runtime surprise: `snet::Ref<j>: in-place reference must point to a lower net index; a cycle needs a register (Slot<i> + Store<i>)`.
- **A cycle is a compile error**, not a runtime surprise: `snet::Ref<j>: in-place reference must point to a lower net index; a cycle needs a register (a typed layer that evaluates the cell on prev)`.
The check is a `static_assert` in the net's own library code (`include/staticNet.h`), not something a user of the net writes or can forget: a net with a combinational cycle cannot be evaluated, `proc` does not compile (`check/cycle_reject_*.cpp` name the net and call `proc`).
A reference to a cell that is not in the net says so in words (`no cell in the net matches Q`, `no cell of that type in the net`) (`check/`: `cycle_reject_*`, `refid_missing`, `sugar_missing`).
- **The realization is a compile-time choice.** `cell<bias>(sign, terms...)` returns the unrolled `lin::Cell` (one inline multiply-add per term) or, from `SUGAR_ROLL_AT` = 6 terms, the same
terms as a table and a loop (`snet::Roll`); the result is identical, the code is not (see *Realizations* below). `SUGAR_ROLL_AT` is a **size policy**, not a fact: set it huge to never roll.
terms as a table and a loop (`snet::Roll`; it applies to terms that read elements of one array field, such as a 60-band input); the result is identical, the code is not (see *Realizations* below). `SUGAR_ROLL_AT` is a **size policy**, not a fact: set it huge to never roll.
- **A value that survives a pass is a register** (`registers.h`): a layer of the state whose step evaluates cells on the previous state and stores the result. Cells stay pure `proc(in)`; two registers that read each other
(`(a,b)' = (b, a+b)`) are right because both new values come from the previous state (`check/registers_check.cpp`; `registers_avr.cpp` is the same step as a hand-indexed array: same size).
- Cells of identical type collapse in `ref()` (identical pure cells compute the same value: deduplication, pinned by `check/sugar_twins.cpp`).

## What was measured
Expand Down Expand Up @@ -141,13 +145,13 @@ The result also depends on `SNET_INLINE` (`[[gnu::always_inline]]`): avr-gcc 7.3

| | |
|---|---|
| `include/` | the headers: `staticNet.h` (the net, `Ctx`, `Ref`, `Slot`; namespace `snet`), `waveCell.h`, `linCell.h` (the engines), `refid.h` (wiring by id / query), `sugar.h`, `roll.h` |
| `include/` | the headers: `staticNet.h` (the net, `Ctx`, `Ref`, and the sources `Field` / `Elem` that read a slot of the state; namespace `snet`), `waveCell.h`, `linCell.h` (the engines), `refid.h` (wiring by id / query), `sugar.h`, `roll.h`, `registers.h` (registers as layers of the state), `inputs.h` (stock input states) |
| `models/` | the trained cells: `banknote/` (the running example's cell and its 274 held-out rows), `sonar/` (the 60-input cells of 5 folds), `roll60/` (60-term nets for the size and speed comparisons) |
| `src/main.cpp` | the running example (host and AVR) |
| `check/` | `build.sh` runs everything: host tests (g++, clang++), programs that must not compile, AVR sizes and identical-disassembly checks, the simulated row-by-row check (`bitexact.py`) |
| `measure/` | the timing harness (Timer1, UART) and programs: `run.sh` (simavr), `roll_sweep.sh`, `silicon.py` (flash a real ATmega328p and compare with simavr), `silicon_results.md` |
| `compare_emlearn/` | the Banknote comparison: `run.sh` builds and measures every model in one setup; `results.md`; generated models, the fold data and the emlearn headers it compiles against (MIT, unchanged) |
| `train/` | how the trained parameters came about, on a PC: `bn2x.c` (masked-wave search and float perceptron per fold, UCI Banknote), `sonar_lin_train.c` + `gen_lin_sonar.py` (Sonar, quantized to int8 constants) |
| `train/` | how the trained parameters came about, on a PC: `bn2x.c` (masked-wave search and float perceptron per fold, UCI Banknote; it writes the features and columns by name), `sonar_lin_train.c` + `gen_lin_sonar.py` (Sonar, quantized to int8 constants) |

## Scope

Expand Down
6 changes: 3 additions & 3 deletions examples/static_net/check/banknote_avr_check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ int main(){
uart_init();
uint16_t a=0,c=0;
for(uint16_t r=0;r<WAVE_NVEC;r++){
wave::Features<4> f{{pgm_read_byte(&WAVE_VEC[r][0]),pgm_read_byte(&WAVE_VEC[r][1]),
pgm_read_byte(&WAVE_VEC[r][2]),pgm_read_byte(&WAVE_VEC[r][3])}};
BanknoteState f=banknote({pgm_read_byte(&WAVE_VEC[r][WAVE_COL_VARIANCE]),pgm_read_byte(&WAVE_VEC[r][WAVE_COL_SKEWNESS]),
pgm_read_byte(&WAVE_VEC[r][WAVE_COL_CURTOSIS]),pgm_read_byte(&WAVE_VEC[r][WAVE_COL_ENTROPY])});
bool o=BanknoteNet::proc(f);
a+=o==pgm_read_byte(&WAVE_VEC[r][5]); c+=o==pgm_read_byte(&WAVE_VEC[r][4]);
a+=o==pgm_read_byte(&WAVE_VEC[r][WAVE_COL_MODEL]); c+=o==pgm_read_byte(&WAVE_VEC[r][WAVE_COL_LABEL]);
}
agree_=a; correct_=c;
for(;;){
Expand Down
4 changes: 2 additions & 2 deletions examples/static_net/check/banknote_host_check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
int main(){
int agree=0,correct=0;
for(int r=0;r<WAVE_NVEC;r++){
wave::Features<4> f{{WAVE_VEC[r][0],WAVE_VEC[r][1],WAVE_VEC[r][2],WAVE_VEC[r][3]}};
BanknoteState f=banknote({WAVE_VEC[r][WAVE_COL_VARIANCE],WAVE_VEC[r][WAVE_COL_SKEWNESS],WAVE_VEC[r][WAVE_COL_CURTOSIS],WAVE_VEC[r][WAVE_COL_ENTROPY]});
bool o=BanknoteNet::proc(f);
agree+=o==WAVE_VEC[r][5]; correct+=o==WAVE_VEC[r][4];
agree+=o==WAVE_VEC[r][WAVE_COL_MODEL]; correct+=o==WAVE_VEC[r][WAVE_COL_LABEL];
}
printf("model agreement %d/%d, accuracy %d/%d\n",agree,WAVE_NVEC,correct,WAVE_NVEC);
return agree!=WAVE_NVEC;
Expand Down
15 changes: 8 additions & 7 deletions examples/static_net/check/bench.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#pragma once
#include <stdint.h>
#include "waveCell.h"
#include "inputs.h"
// the rows live in RAM/flash as plain data unless the platform says otherwise (AVR: SONAR_LIN_VEC_ATTR PROGMEM and BENCH_VEC with pgm_read_byte)
#ifndef SONAR_LIN_VEC_ATTR
#define SONAR_LIN_VEC_ATTR
Expand All @@ -36,13 +37,13 @@ void bench_irq_on();
static inline void bench_start(){ DEMCR|=(1ul<<24); DWT_CYCCNT=0; DWT_CTRL|=1u; }

// out of line, so the generated code of each cell can be read from the ELF on its own (arm-none-eabi-objdump -d)
__attribute__((noinline)) bool runNarrow(const wave::Features<60>& f){ return SonarLinFold0::proc(f); }
__attribute__((noinline)) bool runWide (const wave::Features<60>& f){ return SonarLinFold0Wide::proc(f); }
__attribute__((noinline)) bool runNarrow(const inp::Bytes60& f){ return SonarLinFold0::proc(f); }
__attribute__((noinline)) bool runWide (const inp::Bytes60& f){ return SonarLinFold0Wide::proc(f); }

static wave::Features<60> benchRow(uint16_t r){ wave::Features<60> f; for(int j=0;j<60;j++) f.v[j]=BENCH_VEC(r,j); return f; }
static inp::Bytes60 benchRow(uint16_t r){ inp::Bytes60 f; for(int j=0;j<60;j++) inp::band(f)[j]=BENCH_VEC(r,j); return f; }

enum { REPS=25 };
[[maybe_unused]] static uint32_t benchCycles(bool (*fn)(const wave::Features<60>&), const wave::Features<60>& f){
[[maybe_unused]] static uint32_t benchCycles(bool (*fn)(const inp::Bytes60&), const inp::Bytes60& f){
uint32_t best=0xFFFFFFFFu, all[REPS];
for(int i=0;i<REPS;i++){
bench_irq_off();
Expand All @@ -61,8 +62,8 @@ static void bench_correctness(){
uint16_t okN=0,agN=0,okW=0,agW=0;
char rowsN[SONAR_LIN_FOLD0_NVEC+1], rowsW[SONAR_LIN_FOLD0_NVEC+1];
for(uint16_t r=0;r<SONAR_LIN_FOLD0_NVEC;r++){
wave::Features<60> f=benchRow(r);
bool truth=BENCH_VEC(r,60), flt=BENCH_VEC(r,61);
inp::Bytes60 f=benchRow(r);
bool truth=BENCH_VEC(r,SONAR_COL_LABEL), flt=BENCH_VEC(r,SONAR_COL_FLOAT);
bool n=runNarrow(f), w=runWide(f);
okN+=(n==truth); agN+=(n==flt); okW+=(w==truth); agW+=(w==flt);
rowsN[r]=n?'1':'0'; rowsW[r]=w?'1':'0';
Expand All @@ -77,7 +78,7 @@ static void bench_correctness(){
}

static void bench_cycles(){
wave::Features<60> f=benchRow(0);
inp::Bytes60 f=benchRow(0);
bench_str("cycles narrow: "); benchCycles(runNarrow,f); bench_str("\r\n");
bench_str("cycles wide : "); benchCycles(runWide,f); bench_str("\r\n");
}
Expand Down
4 changes: 4 additions & 0 deletions examples/static_net/check/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ run sugar_twins sugar_twins.cpp "twins OK"
run sugar_roll sugar_roll.cpp "dense 20000/20000, gapped 20000/20000"
run roll_host roll_host.cpp "rolled==unrolled 10000/10000, sparse-order rolled==unrolled 10000/10000"
run roll_gaps roll_gaps.cpp "gapped rolled==unrolled 20000/20000"
echo "== registers: a value that survives a pass is a typed layer (registers.h), the cells unchanged"
run registers_check registers_check.cpp "0 of 30 passes wrong, output cell wrong 0 times"
echo "== the trained models on the host"
run banknote_host_check banknote_host_check.cpp "274/274"
run sonar_lin_check sonar_lin_check.cpp "agreement 100.00"
Expand All @@ -80,6 +82,8 @@ avr sugar_roll_avr sugar_roll_avr.cpp "322 B / 62 B"
avr sugar_roll_hand_avr sugar_roll_hand_avr.cpp "322 B / 62 B"; same "sugar picks the rolled form a person would write" sugar_roll_avr sugar_roll_hand_avr
avr sugar_roll_avr_off sugar_roll_avr.cpp "1006 B / 62 B" -DSUGAR_ROLL_AT=1000
avr sugar_unroll_hand_avr sugar_unroll_hand_avr.cpp "1006 B / 62 B"; same "SUGAR_ROLL_AT huge == the unrolled cell by hand" sugar_roll_avr_off sugar_unroll_hand_avr
avr registers_avr registers_avr.cpp "220 B / 4 B"
avr registers_avr_flat registers_avr.cpp "220 B / 4 B" -DFLAT # the hand-indexed twin that reads before it writes: same size
avr banknote_avr_check banknote_avr_check.cpp "2346 B / 26 B" # the Banknote cell on a board: it reports over UART
if have simavr && have python3; then
echo "== AVR: the same rows through the same function, simulated ATmega328p against the host, row by row"
Expand Down
3 changes: 2 additions & 1 deletion examples/static_net/check/cycle_reject_pair.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "waveCell.h"
#include "inputs.h"
using namespace wave;
using Bad=Net<Cell<0,RefWave<1,0,0,0,0xff>>, Cell<0,RefWave<0,0,0,0,0xff>>>;
int main(){Features<1> f{{0}}; return Bad::proc<1>(f);}
int main(){auto f=inp::one({0}); return Bad::proc<1>(f);}
3 changes: 2 additions & 1 deletion examples/static_net/check/cycle_reject_self.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "waveCell.h"
#include "inputs.h"
using namespace wave;
using Bad=Net<Cell<0,RefWave<0,0,0,0,0xff>>>;
int main(){Features<1> f{{0}}; return Bad::proc<0>(f);}
int main(){auto f=inp::one({0}); return Bad::proc<0>(f);}
9 changes: 5 additions & 4 deletions examples/static_net/check/mixed_avr_size.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include "waveCell.h"
#include "inputs.h"
#include "linCell.h"
using Mixed=snet::Net<
wave::Cell<0, wave::Threshold, wave::Wave<0,0,6,0,0xff>, wave::Wave<1,0,6,0,0xff>>,
lin::Cell<-1, lin::Sign, lin::In<0,2>, lin::In<1,2>>,
wave::Cell<0, wave::Threshold, wave::Wave<inp::A,0,6,0,0xff>, wave::Wave<inp::B,0,6,0,0xff>>,
lin::Cell<-1, lin::Sign, lin::In<inp::A,2>, lin::In<inp::B,2>>,
wave::Cell<128,wave::Threshold, wave::RefWave<0,0,6,0,0xff>, wave::RefWave<1,0,6,0,0xff>>,
lin::Cell<0, lin::RefIn<2,10>, lin::RefIn<0,-3>, lin::In<0,7>>
lin::Cell<0, lin::RefIn<2,10>, lin::RefIn<0,-3>, lin::In<inp::A,7>>
>;
volatile uint8_t in_[2]; volatile int16_t out_;
int main(){ for(;;){ wave::Features<2> f{{in_[0],in_[1]}}; out_=Mixed::proc<3>(f);} }
int main(){ for(;;){ auto f=inp::ab({in_[0],in_[1]}); out_=Mixed::proc<3>(f);} }
9 changes: 5 additions & 4 deletions examples/static_net/check/mixed_check.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
// two engines in one static net: wave cells and integer linear cells reference each other in place
#include "waveCell.h"
#include "inputs.h"
#include "linCell.h"
#include <cstdio>
using Mixed=snet::Net<
/*0 wave NAND */ wave::Cell<0, wave::Threshold, wave::Wave<0,0,6,0,0xff>, wave::Wave<1,0,6,0,0xff>>,
/*1 lin OR */ lin::Cell<-1, lin::Sign, lin::In<0,2>, lin::In<1,2>>,
/*0 wave NAND */ wave::Cell<0, wave::Threshold, wave::Wave<inp::A,0,6,0,0xff>, wave::Wave<inp::B,0,6,0,0xff>>,
/*1 lin OR */ lin::Cell<-1, lin::Sign, lin::In<inp::A,2>, lin::In<inp::B,2>>,
/*2 wave AND(Ref0,Ref1) = XOR */
wave::Cell<128,wave::Threshold, wave::RefWave<0,0,6,0,0xff>, wave::RefWave<1,0,6,0,0xff>>,
/*3 lin raw (int16, no readout): 10*xor - 3*nand + 7*a */
lin::Cell<0, lin::RefIn<2,10>, lin::RefIn<0,-3>, lin::In<0,7>>
lin::Cell<0, lin::RefIn<2,10>, lin::RefIn<0,-3>, lin::In<inp::A,7>>
>;
int main(){
int ok=1;
for(int a=0;a<2;a++)for(int b=0;b<2;b++){
wave::Features<2> f{{(wave::u8)a,(wave::u8)b}};
auto f=inp::ab({uint8_t(a),uint8_t(b)});
int nand=Mixed::proc<0>(f), orr=Mixed::proc<1>(f), x=Mixed::proc<2>(f), r=Mixed::proc<3>(f);
int rexp=10*(a^b)-3*!(a&&b)+7*a;
printf("a=%d b=%d nand=%d or=%d xor=%d raw=%d (exp %d)\n",a,b,nand,orr,x,r,rexp);
Expand Down
7 changes: 4 additions & 3 deletions examples/static_net/check/net_expand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
// The cells themselves stay whole: a cell is an APIOf, a leaf for queries and Filter/Map (plan D3), so what a walk
// sees inside a Net is cells, never a cell's own components. refid keeps FromTypes for exactly that reason.
#include "waveCell.h"
#include "inputs.h"
#include "linCell.h"
#include "refid.h"
using namespace hapi;

namespace {
enum : int { A=1, B=2, C=3 };
using CellA = wave::Cell<0, Tag<A>, wave::Threshold, wave::Wave<0,0,6,0,0xff>>;
using CellB = lin::Cell<-1, Tag<B>, lin::Sign, lin::In<0,2>>;
using CellC = wave::Cell<128,Tag<C>, wave::Threshold, wave::Wave<1,0,6,0,0xff>>;
using CellA = wave::Cell<0, Tag<A>, wave::Threshold, wave::Wave<inp::A,0,6,0,0xff>>;
using CellB = lin::Cell<-1, Tag<B>, lin::Sign, lin::In<inp::A,2>>;
using CellC = wave::Cell<128,Tag<C>, wave::Threshold, wave::Wave<inp::B,0,6,0,0xff>>;
using N = snet::Net<CellA,CellB,CellC>;
using N0 = snet::Net<>;

Expand Down
Loading
Loading