Networks
A network instantiates other entities (tasks or sub-networks) and wires their ports together. A network has no behaviour of its own. It can declare ports, properties, and instances; everything else is structural.
package com.btccorp;
import com.neosyn.sha.SHA256;
import org.crypto.Padding;
network TopBitcoin {
in u32 msg;
out u256 hash;
pad = new Padding();
inst1 = new SHA256();
inst2 = new SHA256();
pad.reads(msg);
inst1.reads(pad.padded_msg);
inst2.reads(inst1.hashed);
inst2.writes(hash);
}The example instantiates Padding once, SHA256 twice, and
wires them in a linear chain.
Note -
com.neosyn.sha.SHA256andorg.crypto.Paddingare placeholder import paths used to keep the snippet self-contained. They are not part of the standard library. Pick your own package names; see Project layout for the conventions.
A larger real-world network, the Intel-8088 reference CPU, has
two top-level instances communicating through push and stream
ports:
The diagram shows the same shape every C⏚ network has: parameterised instances, ports between them, external ports exposed to the parent network.
Instantiation
Every instance is a fresh, parameter-fixed copy of its entity. Two instances of the same entity share nothing.
inst = new Entity();
ram = new SinglePortRAM({size: 8, width: 12});The second form passes a properties object. Constants declared on the entity can be overridden here; clocks and resets can be associated. See Properties for the keys.
Connecting ports
Inside a network, every port is either readable or writable:
| Direction | Readable inside the network | Writable inside the network |
|---|---|---|
in on this network | Yes | - |
out on this network | - | Yes |
in on an instance | - | Yes |
out on an instance | Yes | - |
A readable port may have many readers (fan-out) or zero. A writable port must have exactly one writer.
.reads(...) and .writes(...)
Use these two methods to wire instances together.
instance.reads(...) connects readable ports into the instance.
instance.writes(...) connects the instance's outputs into
writable ports.
subrot = new SubRot();
subrot.reads(loadKey.toRotSub);
subrot.writes(loadKey.fromRotSub);
loadKey = new LoadKey();
loadKey.reads(key); // network's input port
this.writes(loadKey.w_addr, loadKey.w_data); // network's output portsthis refers to the enclosing network.
.reads(...) consumes unconnected ports positionally
reads matches its arguments to the instance's unconnected input
ports in declaration order. After a port is connected, it leaves
the pool. The order of reads and writes calls is therefore
significant.
If you wire instances in the wrong order, the compiler may pair arguments with the wrong ports without warning. Either keep the calls in declaration order, or prefer single-call wiring when the port list is short and fixed.
Beware: split .reads(...) calls
Consider a Store task that declares two input ports:
task Store {
in u8 addr;
in u8 data;
// …
}Inside a network that exposes net_addr and net_data as inputs,
this wiring is correct:
store = new Store();
store.reads(net_addr); // pool = [addr, data]; pairs net_addr → addr
store.reads(net_data); // pool = [data]; pairs net_data → dataSwap the two calls and the wiring silently inverts. The pool still
starts as [addr, data], so the first call's argument lands on
addr:
store.reads(net_data); // pool = [addr, data]; pairs net_data → addr (wrong!)
store.reads(net_addr); // pool = [data]; pairs net_addr → data (wrong!)The compiler does not check argument names against port names. Pass
all arguments in declaration order in a single call
(store.reads(net_addr, net_data)) or keep separate calls in port
order.
Connected ports must match width
A connection wires two ports of the same width. The compiler reports an error on a mismatch - it does not silently zero-extend or truncate to make the widths fit.
producer = new Producer(); // out push u8 o (8-bit)
consumer = new Consumer(); // in push u4 i (4-bit)
consumer.reads(producer.o); // error: width mismatch, 8-bit -> 4-bitThis matters most with parameterized ports: when the same task is specialized to different widths, wire each producer to a consumer of the matching width. If you need to change a width on purpose, resize explicitly inside a task rather than relying on the connection.
Inner tasks
An inner task is an anonymous task declared and instantiated in one go inside a network. It exists only there. It has access to its own ports, to the network's ports, and to the ports of other instances in the same network.
network N {
in u8 addr;
out u12 data;
ctrl = new task {
void loop() {
ram.address.write(addr.read() << 1);
idle(1);
data.write(ram.q.read());
}
};
ram = new SinglePortRAM({ size: 8, width: 12 });
}The semicolon after the closing brace of the inner task is required: it terminates the instantiation statement, not the task body.
In a network that mixes inner tasks and explicit reads /
writes calls, the compiler resolves inner-task port references
first.
When to choose a network over a task
| Use a network when… | Use a task when… |
|---|---|
| You're composing sub-entities. | You're describing behaviour. |
| You need hierarchy or reusable sub-modules. | The work fits in one setup / loop. |
| Multiple clocks need to be associated explicitly. | A single clock domain is enough. |
Networks contain no loop. Behaviour lives inside tasks.
Next: Properties for clock/reset/test configuration, or Standard library for the built-in FIFO and memory entities.