An open research prototype

P4, down to its semantic core.

A small, architecture-free intermediate representation for P4. Author in Python. Explore what your packets actually do.

Executable semantics. Independent implementations.

01 / THE BIG PICTURE
Pythontyped eDSL
P4 sourceIL bridge
SHARED REPRESENTATIONp4blo IR

Headers · parsers · actions · tables

Python interpreter
Lean semantics
Compare packets + persistent state
One core. Two independent ways to execute it.
Architecture-free by designLean defines the meaningProtobuf carries the syntaxApache 2.0

A smaller place to reason

Keep the meaning.
Separate the machinery.

P4’s essential operations live in the core. Architectures and extern services connect through explicit contracts.

01 / AUTHOR

Make the program explicit.

Build headers, parsers, tables and actions in Python. Inspect the same serialized IR, with widths and names resolved.

Meet the authoring API
02 / EXECUTE

Follow every packet.

Run the core under ordinary switch and filter adapters. Compare outputs and persistent extern state across independent interpreters.

Read the semantics
03 / CHECK

Make claims inspectable.

Connect core IR proofs with independent examples, P4 oracles and deliberate fault campaigns. Follow each claim to its evidence.

Explore the evidence

A complete Python eDSL program

See it in action.

A VLAN access gateway. Admit a packet by policy, remove its tag and remember the decision.

IN / PORT 1
EthernetVLAN 42Payload
POLICY

VLAN 42 · destination …:02

OUT / PORT 2
EthernetPayload

Scroll through the code. Follow the explanation, or choose a step.

vlan_gateway.pyPYTHON eDSL
"""A single-tag VLAN access gateway, built with the typed Python eDSL."""

from __future__ import annotations

from p4blo import edsl as p4
from p4blo.arch import assemble
from p4blo.arch import wire as arch_wire
from p4blo.arch.externs.declarations import Counter
from p4blo.arch.v0 import assembly_pb2 as apb


class Ethernet(p4.Header):
    dst: p4.bit48
    src: p4.bit48
    ether_type: p4.bit16


class Vlan(p4.Header):
    pcp: p4.bit3
    dei: p4.bit1
    vid: p4.bit12
    ether_type: p4.bit16


class Headers(p4.Struct):
    ethernet: Ethernet
    vlan: Vlan


class Metadata(p4.Struct):
    ingress_port: p4.bit9
    egress_spec: p4.bit9


class Parse(p4.Parser[Headers, Metadata]):
    @p4.state(start=True)
    def start(self) -> p4.Transition:
        self.extract(self.hdr.ethernet)
        return self.select(
            self.hdr.ethernet.ether_type,
            {0x8100: self.tagged},
            default=self.accept,
        )

    @p4.state
    def tagged(self) -> p4.Transition:
        self.extract(self.hdr.vlan)
        return self.accept


admissions = Counter("admissions", size=512)


class Gateway(p4.Control[Headers, Metadata]):
    @p4.action
    def deny(self) -> None:
        self.assign(self.meta.egress_spec, 511)

    @p4.action
    def deliver(self, port: p4.bit9) -> None:
        self.assign(self.meta.egress_spec, port)
        self.assign(self.hdr.ethernet.ether_type, self.hdr.vlan.ether_type)
        self.set_invalid(self.hdr.vlan)
        admissions.count(port.cast(p4.bit32))

    access = p4.Table(
        keys=(
            p4.exact(Metadata.ingress_port),
            p4.exact(Headers.vlan.vid),
            p4.exact(Headers.ethernet.dst),
        ),
        actions=[deliver, deny],
        default=deny(),
        size=1024,
    )

    def apply(self) -> None:
        self.assign(self.meta.egress_spec, 511)
        vlan = self.hdr.vlan
        with self.if_(
            vlan.is_valid()
            & (vlan.vid > 0)
            & (vlan.vid < 4095)
            & (vlan.ether_type != 0x8100)
            & (vlan.ether_type != 0x88A8)
        ):
            self.apply_table(self.access)


class Emit(p4.Deparser[Headers]):
    def apply(self) -> None:
        self.emit(self.hdr.ethernet)
        self.emit(self.hdr.vlan)


def build() -> apb.BlockAssembly:
    return assemble(
        p4.BlockLibrary(Parse, Gateway, Emit, externs=[admissions]),
        name="vlan_gateway",
        headers=Headers,
        metadata=Metadata,
        exports={"parser": Parse, "ingress": Gateway, "deparser": Emit},
    )


if __name__ == "__main__":
    print(arch_wire.dump_text(build()), end="")

From the runnable demo

Three packets.
One persistent counter.

One installed rule admits VLAN 42. VLAN 43 misses. The next admitted packet sees the previous count.

Run it and inspect the tests

$ nix develop -c uv run python -m tests.programs.corpus.vlan_gateway.demo

  1. VLAN 42port 2 · tag removedcount 1
  2. VLAN 43drop · policy misscount 1
  3. VLAN 42port 2 · tag removedcount 2

Admitted packets: 38 → 34 bytes. Payload preserved.

A single-tag gateway with a trusted host policy, not a complete VLAN switch. The walkthrough presents tested source; it doesn’t execute Python in your browser. Read its exact boundary ↗

Evidence you can inspect

Trust starts with
knowing what was checked.

The core IR combines tested conformance with selected Lean proofs. Each kind of evidence has a stated boundary.

Read the release evidence

Start with a packet

Make the semantics concrete.

Build and run Python examples against the core IR.
The quickstart introduces authoring and independent execution checks.

Open the quickstart