Router¶
This example models a packet router with two input ports and four output ports. Incoming packets are forwarded to an output based on the low-order bits of their address field. When both inputs have packets ready in the same cycle, the router uses round-robin arbitration to decide which to serve first.
What this example demonstrates:
- Multi-port modules using C++ pointer arrays for indexed port access
- Address-based routing with a compile-time mask
address & (N-1) - Round-robin arbitration across input ports
- Per-output buffering via net capacity
- Two-phase routing: arbitrate in phase 0, forward in phase 1
Token format¶
All ports carry 8-byte tokens encoding two 32-bit integers: an address and a data payload.
The destination output port is selected by the low-order bits of the address:
Structure¶
Two Sources inject packets cycling through all four addresses. The Router forwards each packet to the appropriate output net, which acts as the per-output buffer. Four Sinks drain and log received packets.
flowchart LR
src0["Source 0"]
src1["Source 1"]
rtr["Router\n(2-in, 4-out)"]
snk0["Sink 0"]
snk1["Sink 1"]
snk2["Sink 2"]
snk3["Sink 3"]
src0 -->|"i0 cap=2"| rtr
src1 -->|"i1 cap=2"| rtr
rtr -->|"o0 cap=4"| snk0
rtr -->|"o1 cap=4"| snk1
rtr -->|"o2 cap=4"| snk2
rtr -->|"o3 cap=4"| snk3
The input nets (i0, i1) have a small capacity of 2, so back-pressure reaches the Sources quickly when the router stalls. The output nets (o0-o3) have capacity 4, acting as per-output buffers.
Router module¶
The Router declares its ports individually and creates C++ pointer arrays in decl and init to allow indexed access inside code blocks. This is the standard pattern for multi-port modules in Sitar.
The routing loop operates in two phases each cycle:
- Phase 0 — arbitrate: Scan inports starting at the round-robin pointer
rr. The first inport with a token wins. Unpack the address, compute the destination, and advancerr. - Phase 1 — forward: Push the packet to the destination outport. If the output buffer is full, retry each phase until it drains.
One packet per cycle
The router handles at most one packet per cycle. When neither inport has a token, the router stalls at the phase 0 wait until one arrives.
Source¶
Each Source generates 12 packets (3 per output port), cycling through addresses 0, 1, 2, 3. The two Sources start at different address offsets to create interleaving traffic.
Sink¶
Each Sink drains its output net every phase 0 and logs the address and data of each received packet.
Expected output (excerpt)¶
(0,1) TOP.src0 : src[0] addr=0 data=0
(0,1) TOP.src0 : src[0] addr=1 data=1
(0,1) TOP.src1 : src[1] addr=1 data=0
(0,1) TOP.src1 : src[1] addr=2 data=1
(1,0) TOP.router : in[0] -> out[0] addr=0 data=0
(1,1) TOP.src0 : src[0] addr=2 data=2
(2,0) TOP.router : in[1] -> out[1] addr=1 data=0
(2,0) TOP.snk0 : snk[0] addr=0 data=0
...
Simulation stopped at time (19,1)
Because the router handles at most one packet per cycle, the two arbitration events (in[0] -> out[0] and in[1] -> out[1]) land on consecutive cycles, (1,0) and (2,0), not the same cycle. The round-robin arbiter alternates between in[0] and in[1] whenever both have packets waiting, distributing load evenly across the two sources.
Each Source stops the whole simulation as soon as it has sent its 12th packet (NUM_PKTS=12). Since stop simulation halts every module at the end of that phase, packets still in flight inside the router and the output nets are never drained or logged by the Sinks. With the default instantiation, the run stops at (19,1) with final per-sink totals of snk0=5, snk1=5, snk2=4, snk3=4 (18 of the 24 packets sent), rather than the 6-per-sink total that full draining would produce.