Venkat Pullela

Venkat Pullela

Network programming Language (NPL) is designed to specify packet processing pipelines that can be efficiently implemented on different architectures. Apart from basic constructs like parse, tables, re-write, NPL supports advanced constructs like multi-lookup logical tables, functions, strength etc. This is an introduction to the NPL language and a few important constructs.

Program specifies the switching pipeline control flow using all the NPL constructs. The program() provides sequential execution semantics to map the constructs on to the underlying pipeline. Target specific backend compilers may re-order and/or parallelize various blocks without changing the semantics of the program to efficiently use the underlying resources.

program l3_app() {

parse_begin (start);

port_table.lookup (0);

parse_continue(ethernet);

do_vid_assign();

if (ing_pkt.l2_grp.l2._PRESENT) {

mac_table.lookup(0);

mac_table.lookup(1);

}

if (cmd_bus.l3_enable) {

do_l3_forwarding();

do_packet_edits();

do_checksum_update();

}

}

Parse_begin() and parse_continue() allows support for multi-stage re-entrant parsing. A program() combines table lookups with functions to build an efficient switching pipeline.

Standard Data Types in NPL are bit, bit array and varbit. Bit is used to denote a single bit, and bit array is used to declare fields of fixed length. Variable length fields are declared with the maximum possible field width.

bit     cfi;          // single bit

bit[12] vid;          // 12 bit vid

varbit[64] options;    // up to 64b wide

Numbers can be specified in decimal and hex. A non-zero value indicates true and a zero indicates false. All the typical arithmetic and Boolean operators are supported to form expressions.

a = 5;

ipv4.ttl = 0xF;

ipv6.dip = 0x01234567;

if (ipv4.protocol == 0x23)

User defined Type Struct is used to define aggregate data types. It is used to aggregate fields in to a header, form a header group with headers, to define a packet consisting of header groups and a bus with fields.

struct vlan_t {      // Header definition

fields {

bit[16]  tci;

bit[16]  ethertype;

}

overlays {

pcp : tci[15:13];

dei : tci[12:12];

vid : tci[11:0];

full_tag : tci <> ethertype;

}

}

struct l2_group_t {       // Header Group

fields {

l2_t    l2;

vlant_t vlan;

}

}

struct l3_packet_t {            // Packet

fields {

l2_group_t l2_grp;

l3_group_t l3_grp;

}

}

packet l3_packet_t ingress_pkt;

bus cmd_bus_t cmd_bus;      // Global Bus

Overlays and concatenation (<> operator) helps power users in using bus and other resources efficiently.

Bus is used to connect different components in a pipeline. Explicitly supporting a logical bus construct helps pipeline designers to think in their own language, especially since NPL is a domain specific language.

Logical table allows specifying user view of the underlying physical tables. Actions are separated from logical tables. This makes them lightweight and improves the throughput. This also moves switching logic out of the table scope to global scope to help common design patterns such as concurrent features. Logical table provides a dis-aggregated view of the Match-Action tables. It is dis-aggregated in to lookup, result, processing and actions to the reduce dependencies and improve the scope for parallelization. Smaller primitives also make the programming paradigm more natural and intuitive instead of force fitting to a single universal primitive.

Table attributes like size, table type attributes may be specified so that appropriate memory like TCAM or SRAM is allocated to the table from common memory.

Table attributes like size, table type attributes may be specified so that appropriate memory like TCAM or SRAM is allocated to the table from common memory.

Logical table supports multiple lookups of the same table. Key_construct() method can be used to specify a custom key for each of the lookups. Specifying conditionals allows different handling of different lookups and results.

key_construct() {

if (_LOOKUP0==1) {

mac = ing_pkt.l2_grp.l2.da;

}

if (_LOOKUP1==1) {

mac = ing_pkt.l2_grp.l2.sa;

}

}

fields_assign() {

if (_LOOKUP0==1) { //e.g. Entry 100

obj_bus.dst = port;

obj_bus.dst_discard = d_discard;

}

if (_LOOKUP1==1) { //e.g. Entry 200

temp_bus.src_port = port;

obj_bus.src_discard = s_discard;

}

}

Fields_assign() method is used to specify how the results are processed.

Function primitive isused to specify switching logic. Packet processing involves not only data processing of the packet fields and metadata, it is also a decision making process. Function primitive is used to implement the decision trees. Functions are also used to implement logical and arithmetic operations.

function do_l3_forwarding() {

local_var.no_l3_switch = 0;

cmd_bus.l3_routable = 0;

if (cmd_bus.do_l3_lookup &

cmd_bus.my_stn_routing_enable) {

if ((ingress_pkt.ipv4.ttl == 0) &&

(obj_bus.local_address == 0)) {

local_var.no_l3_switch = 1;

}

if ((ingress_pkt.ipv4.ttl == 1) &&

(obj_bus.local_address == 0)) {

local_var.no_l3_switch = 1;

}

if (ingress_pkt.ipv4.option != 0) {

local_var.no_l3_switch = 1;

}

if (local_var.no_l3_switch == 0) {

// Output to global bus

cmd_bus.l3_routable = 1;

}

}

}

Strength construct is used in prioritization and to resolve object dependencies in the decision making process. This allows multiple tables to be looked up in parallel and resolving the result objects based on priority. Profile tables are used to changing the priority of objects at runtime.

Strength resolution allows building a decision tree in multiple steps across the pipeline by combining bus objects with table results as well as carrying the result objects over the bus.

Strength Construct Diagram

strength_resolve(

local_bus.cos,          // output field

local_bus.cos_strength, // bus strength

{ pri_tbl._LOOKUP0,     // table 1

NULL, profile_tbl.cos_strength,

pri_tbl.cos},       // table 2

{ dscp_tbl._LOOKUP0, NULL,

profile_tbl.cos_strength,

dscp_tbl.cos});

Editor constructs are used to make modifications of the packet. To make sure the modifications produce a valid packet, editor constructs allow the use of the headers from the parse tree only.

add_header(egr_pkt.group2.tunnel_l2);

delete_header(egr_pkt.group1.otag);

delete_header(egr_pkt.group1);

replace_header_field(egr_pkt.ipv4.dscp,

obj_bus.new_dscp);

Special Functions are used for optimal implementation of common pipeline primitives. The functionality of these blocks is programmable. NPL supports flexible selection of inputs and outputs to the special functions. Special functions also support usage mode to select the inputs and outputs at runtime, instead of at compile time.

Network Programming Language (NPL) provides a rich set of constructs to efficiently specify and implement efficient packet processing pipelines. NPL also defined specific constructs to help support parallelization of processing to concurrently support many features at the same time.

Go to NPLang.org for a detailed discussion and specification of NPL.