Architectures¶
4ward supports three P4 architectures: v1model, PSA, and PNA. Each defines a fixed pipeline of stages, a set of externs, and metadata structures. You can also fork or extend these — see Architecture Modifications.
v1model¶
The BMv2 simple_switch architecture, originally designed to support P4_14 programs compiled to P4_16. Defined in v1model.p4; see the simple_switch documentation for the canonical behavioral spec.
The pipeline has six stages in fixed order:
Ports are 9-bit by default (bit<9>). The drop port is 511 (2^9 - 1) and
the CPU port is 510 (2^9 - 2, auto-enabled when @controller_header is
present). Both can be overridden via --drop-port and --cpu-port flags.
Key metadata (standard_metadata_t)¶
| Field | Type | Description |
|---|---|---|
ingress_port |
bit<9> |
Input port (read-only) |
egress_spec |
bit<9> |
Desired output port (set by ingress) |
egress_port |
bit<9> |
Actual output port (read-only in egress) |
instance_type |
bit<32> |
0 = normal, 1 = ingress clone, 2 = egress clone |
mcast_grp |
bit<16> |
Multicast group ID |
packet_length |
bit<32> |
Packet byte count |
Externs¶
Forwarding and drops:
mark_to_drop(standard_metadata)drops the packet.
Cloning, resubmit, recirculate:
clone(CloneType, session_id)creates an I2E or E2E clone.clone_preserving_field_list(CloneType, session_id, field_list_id)clones and preserves metadata fields annotated with the matching@field_listID. Replaces the deprecatedclone3.resubmit_preserving_field_list(field_list_id)re-injects the packet into ingress. Replaces the deprecatedresubmit(data).recirculate_preserving_field_list(field_list_id)feeds the deparsed packet back to the parser. Replaces the deprecatedrecirculate(data).
Avoid the deprecated clone3, resubmit, and recirculate
These still compile but do not reliably preserve metadata with modern
p4c — field lists may be
silently emitted as empty,
and the underlying semantics
violate P4_16 call conventions.
Always use the _preserving_field_list variants. See the
v1model special ops guide
for a thorough writeup.
Multiple calls use last-writer-wins semantics. All of these produce trace tree forks.
Checksums:
verify_checksum(condition, data, checksum, algo)validates a checksum.update_checksum(condition, data, checksum, algo)computes a checksum.- The
_with_payloadvariants include the packet payload.
Hashing:
hash(out result, algo, base, data, max)computesresult = base + hash(data) mod max.
Logging:
log_msg(msg)/log_msg(msg, data)prints debug output, with{}as placeholders.
Stateful:
register<T>.read(out result, index)/.write(index, value)provides generic array storage.counter.count(index)/direct_counter.count()increments a counter (fire-and-forget).meter.execute_meter(index, out color)always returns GREEN — there are no real packet rates in the simulator.
PSA¶
The Portable Switch Architecture is a more modern design with a cleaner separation between ingress and egress. Defined in psa.p4; see the PSA specification for the canonical behavioral spec.
It uses a two-pipeline design with separate ingress and egress:
Unlike v1model, egress re-parses the deparsed packet, so header and metadata state does not persist across the ingress→egress boundary.
Ports are 32-bit by default (bit<32>). The drop port is 2^32 - 1 and the
CPU port is 2^32 - 2 (when @controller_header is present).
Key metadata¶
The ingress output metadata (psa_ingress_output_metadata_t) controls the
packet's fate after ingress:
| Field | Type | Default | Description |
|---|---|---|---|
drop |
bool |
true | Must clear via send_to_port() or multicast() to forward |
egress_port |
bit<32> |
- | Output port |
multicast_group |
bit<16> |
- | Multicast group ID |
clone |
bool |
false | I2E clone requested |
clone_session_id |
bit<16> |
- | Clone session |
resubmit |
bool |
false | Resubmit requested |
Note that ingress drops by default — you must explicitly call
send_to_port() to forward a packet. This is the opposite of v1model, where
packets forward by default.
Externs¶
Forwarding:
send_to_port(ostd, port)clears the drop flag and sets the egress port.multicast(ostd, group)clears the drop flag and sets the multicast group.ingress_drop(ostd)/egress_drop(ostd)sets the drop flag.
Stateful:
Register<T>.read(index) → T/.write(index, value)returns the value directly (unlike v1model'soutparameter).Counter.count()/DirectCounter.count()increments a counter (fire-and-forget).Meter.execute(index) → coloralways returns GREEN.Random.read() → Treturns a random value in the constructor-specified range.
Hashing:
Hash.get_hash(data) → hashis the 1-arg form.Hash.get_hash(base, data, max) → (base + hash) mod maxis the 3-arg form.
Supported algorithms: IDENTITY, CRC16, CRC32, ONES_COMPLEMENT16.
Checksum:
InternetChecksumsupportsclear(),add(data),subtract(data),get(),get_state(), andset_state(value).
Other:
Digest.pack(data)queues a message to the control plane (no-op in the simulator).
PNA¶
The Portable NIC Architecture is designed for smart NICs with a single-pipeline structure. Defined in pna.p4.
It uses a single-pipeline design with no separate egress:
Unlike PSA, forwarding uses free functions (send_to_port(),
drop_packet()) with last-writer-wins semantics instead of output metadata
fields. Like PSA, it drops by default — you must call send_to_port() to
forward a packet.
Ports are 32-bit (bit<32>) and direction-aware: each packet has a
PNA_Direction_t (NET_TO_HOST or HOST_TO_NET).
Key metadata¶
The main input metadata (pna_main_input_metadata_t):
| Field | Type | Description |
|---|---|---|
direction |
PNA_Direction_t |
NET_TO_HOST or HOST_TO_NET |
input_port |
bit<32> |
Input port |
parser_error |
error |
Parser error (set after parsing) |
pass |
bit<3> |
Recirculation pass number (0–7) |
loopedback |
bool |
True if recirculated |
timestamp |
bit<64> |
Packet arrival timestamp |
Externs¶
Forwarding (last-writer-wins):
send_to_port(port)forwards to a port.drop_packet()drops the packet (main control only per spec).recirculate()loops the deparsed bytes back to the parser.mirror_packet(slot_id, session_id)mirrors the deparsed bytes to a clone session.
Direction:
SelectByDirection(direction, n2h_value, h2n_value)selects a value based on the packet's direction.
Add-on-miss (data-plane table insertion):
add_entry(action_name, action_params, expire_time_profile_id) → boolinserts a table entry on a miss.
Stateful: Same as PSA — Register, Counter, Meter (always GREEN),
Hash, InternetChecksum, Digest (no-op), Random.
Limitations¶
These limitations apply to all architectures:
- Meters always return GREEN because the simulator has no real packet rates.
- Counters are fire-and-forget and can't be read from P4 logic (only via P4Runtime Read from the control plane).
- Digest is a no-op because there's no control-plane receiver in the simulator.
See LIMITATIONS.md for the full list.