Diving into the nitty-gritty: implementing a trivial IPv4 switching program
This tutorial implements a basic IPv4 switching program on the X-Switch ISA (XISA). The Parser identifies the Ethernet and IPv4 headers, applies port-based VRF through the built-in Ingress Port Mapping table, and preloads the destination IP address. The Match-Action Processor (MAP) then performs a longest-prefix-match lookup on the VRF and destination IP and forwards the packet to the resulting egress queue — the whole program in about 50 lines of code.
We're glad to see that our network enthusiasts are back for more. Remember our last deep dive into the nuts and bolts of a network cross-connect? Well, buckle up, because we're back for another peek under the hood! This time, we're tackling something a little more involved, yet super insightful: a basic IPv4 switching program built using the X-Switch Instruction Set Architecture (XISA). This latest example will set you straight regarding what makes network traffic zip along from point A to B.
First, let's define the functionality: our trivial IPv4 switching program will parse the packets, arriving at an ingress port, and continue to forward IPv4 packets to a specified egress port based on the result of the Longest Prefix Match (LPM) lookup of the packet's Destination IP address (DIP) field. For a more realistic LPM lookup, we'll add port-based Virtual Routing and Forwarding (VRF) functionality, meaning that each port can be assigned to a specific routing domain. This program will drop all non-IPv4 packets.
To make things even simpler, we're leaving out several steps typically associated with IPv4 processing, which the current program will not be performing:
New in this example, this program will introduce X-Switch packet header parsing concepts, as well as how to perform lookups in explicitly defined tables.
In order to perform an IPv4-based lookup, the program must first parse the incoming packet, that is, identify the specific headers that the packet contains; this is performed by the X-Switch Parser. In addition to identifying the specific headers, the Parser can also preload the values from the selected header fields into the registers that it shares with the X-Switch Match-Action Pipeline (MAP), so that they can be immediately available for further processing in the MAP.[1]
For example, the Parser program identifies the IPv4 header in the incoming packet and loads the IPv4 Destination Address field into a designated register that is accessible by the Match-Action Pipeline (MAP) program. This preloaded info (i.e. destination IP address in MAP register) is then readily available for the MAP program to use as a key when performing a lookup in a table, such as the IPv4 LPM Forwarding table.
The program also utilizes the built-in Ingress Port Mapping table to implement port-based VRF. The value provided by that table is used as a VRF ID and passed along with the IPv4 destination address to the IPV4 LPM Forwarding table.
The value provided as the result of the LPM Forwarding table lookup is the desired egress queue ID; since, as we learnt in our previous Simple Cross-Connect example, in order to send a packet to a selected egress port, it must be sent to one of the egress queues associated with that egress port. Overall, the algorithm can be graphically represented as shown in Figure 1.

Figure 1. Overall processing diagram
We'll continue to explore the microcode (uCode) in detail in the following sections. Also, see our recently opened XISA for further Instruction details.
XISA processes packet switching first via the Parser, and then via the MAP.
Packet processing in the Parser has several goals.
The parsed header information is passed from the Parser to the MAP using the three registers specifically reserved for this purpose.
MAP register R11, is the HDR_PRESENT register and it contains individual Header Present bits, which the Parser sets as it identifies the corresponding headers inside the packet. This allows the data plane program to work with up to 128 different headers.

Figure 2. MAP register R11: HDR.PRESENT layout
In addition, MAP registers, R12: HDR.OFFSET0 and R13: HDR.OFFSET1, contain 16 slots each to store the individual header offsets, which are expressed as the number of bytes starting from the beginning of the packet. Each offset slot (entry) occupies one byte, meaning that the headers can be located at offsets 0–255 bytes from the beginning of the packet. Using two registers allows the parser to record up to 32 individual header offsets.

Figure 3. MAP registers R12/13: HDR.OFFSET0/1 layout
Therefore, firstly, we need to enumerate the headers that the program will process along with the layers that they will appear in.
Let's use Table 1 to assign Header ID and Layer ID numbers to the headers in our program. You can use numbers other than 0 and 1, if so desired.
Table 1: Enumerating IPv4 packet headers
Table 2 offers a more complicated case that further illustrates the difference between the Header ID and the Layer ID. Usually, mutually exclusive headers tend to have the same Layer ID, which is why the number of supported Layers (Header Offset IDs) is smaller than the number of Header IDs.
Table 2: A typical header enumeration table
Our next planning step is to decide which header fields to preload into the MAP registers and how to allocate them.
Given that we only need to use the Destination IP Address field from the IPv4 header for the lookup, this is the only field that needs to be preloaded. We can use any spare register for that, such as R2. The 32-bit destination IPv4 address field can be placed anywhere within the R2 128-bit register. We'll go ahead and place it in the least significant bits (Word R2.3), as shown in Figure 4.

Figure 4. MAP register R2 layout: contains destination IP address
Now, let's plan what needs to be done for each of the headers that the Parser is going to identify. The header fields are named in Table 3. Additionally, their offsets (from the start of the header) and their widths (both in bit units) are provided in parentheses.

Table 3. Parser header processing
Once the preliminary work has been done, writing the parser code will be very easy.
As always, we start with the initial Jump table that automatically directs the packets to the entry points that correspond to different packet paths. For our example, we're only interested in the ingress packet path that corresponds to regular packets arriving on Ethernet ports. Any specialized packet will simply be dropped as defined in Figure 8.
Figure 5. Parser code. Initial state Jump table
{ special_entry_points 0: ingress: entry_point_ethernet 1: reparse: entry_point_reparse 2: trap: entry_point_trap 3: host_pss: entry_point_host_pss 4: loopback: entry_point_loopback 5: low_wm: entry_point_low_wm 8: hi_wm: entry_point_hi_wm}Next, let's write the code for the initial state and process the Ethernet header according to the details specified in Table 3.
Figure 6. Parser code. Ethernet header parsing (lines 12–19 in the source numbering used by the breakdown below)
entry_point_ethernet: EXTNXTP R0, 96, 16 { entry_point_ethernet 0: 0x000800: entry_point_ipv4 } STHC 14, 0, 0, 1 HALTLet's continue by writing the code for the state that processes IPv4 headers. The details of this processing have already been planned and described in Table 3.
Figure 7. Parser code. IPv4 header parsing
entry_point_ipv4: EXTMAP MAPR2, 0, 128, 32 STHC.H 20, 1, 1, 0Finally, we'll add trivial code to handle all special packet paths.
Figure 8. Entry points for the special packet paths
entry_point_trap:entry_point_host_pss:entry_point_loopback:entry_point_reparse:entry_point_low_wm:entry_point_hi_wm: HALTDROPAnd that's it! The Parser coding is done; even with the long explanations, the code for each state is short and easy to read.
Now, let's look at the MAP microcode needed to implement the algorithm, graphically depicted in Figure 1.
The goal of this code is to prepare the information required by the SENDOUT instruction needed for sending out the packet. This information needs to be provided in two MAP Registers. One of them, contains the basic SENDOUT parameters, such as the ID of the Egress Queue where the packet needs to be set and the FrameDelta, i.e. the number of bytes added or deleted from the packet (see Figure 9). The other MAP register contains advanced packet editing information and should be zeroed out for the purposes of this example.

Figure 9. Parameter register[4] layout for SENDOUT instruction
First, let's review the input the MAP will receive from the Parser.
Table 4: MAP input from Parser
The X-Switch match tables are very flexible and this is reflected in XISA. The details of the tables, such as their size, placement, etc., are abstracted from the table lookup instructions using the concept of Table IDs – an integer assigned to each table during its provisioning.
For this reason, the tables are defined separately from the assembly code, using two JSON files. The first file, xdefs-tables.json specifies the tables used by the given program at a high-level.[5]
Figure 10. IPv4_Lpm table definition
{ "table": [ { "name":"IPv4_Lpm", "id":"IPV4_LPM_TABLE_ID",⁶ "type":"LPM", "size":262144, "value_size":32, "key":"IPv4PLpmKey", "value":"IPv4LpmValue", } ]}The second file, xdefs.json specifies the layout of the table entry (both the key and the value) as well as defines the Table ID:
Figure 11. IPv4_Lpm Id, key and value definitions
{ "struct": [ { "name": "IPv4LpmKey", "description": "IPv4 LPM table key", "fields": [ { "type": "BitField", "name": "vrf", "size": 12, "value": 0 }, { "type": "IPField", "name": "prefix", "size": 32, "value": 0 } ] }, { "name": "IPv4LpmValue", "description": "IPv4 LPM Result Table Value", "fields": [ { "type": "BitField", "name": "reserved", "size": 16, "value": 0 }, { "type": "BitField", "name": "egress_qid", "size": 16, "value": 0 } ] } ], "enumerator" : [ {"name": "ipv4_lpm_table", "values": [ {"name":"id", "value": 0} ] } ]}First, let's review our algorithm:
Next, let's create a register allocation plan. Here is what we know so far:
Table 5 describes register allocations. For each register we first show where it is being written (darker hues) and then, where the value that was written (in the register) is going to be used. We use lighter hues to color all the places where the register needs to hold (carry) the previously assigned value, meaning that it cannot be used for anything else. The vertical arrows show how a value is derived from others.
Compilers often rely on similar data structures to efficiently perform register allocation. In this particular example we chose a straightforward allocation rather than the most optimized one. Zooming in, it's clear that register R1 can be reused to hold everything that was placed in R0 and register R2 can be reused to hold the value for the Advanced SENDOUT parameters that we placed in R3. We'll leave this as an exercise for the reader.

Table 5. Register allocation diagram
Let's get started writing the code:
Figure 12. MAP assembly code
ingress: BRBTSTCLR R11.3, 1, not_ipv4_packet CONCAT.CD R2.2, 0, R1.3, 0, 12 LKPLPM.LF5.R R0.3, R0.3, -1, -1, R2, R2, 0, 4 SYNC.N 32, ipv4_lpm_miss AQMEG.LF3.NOMIRR R0.0, R0.3 MOVI R0.2, 0 MOVI.CD R3.3, 0 SYNC 8 BRBTSTSET R0.0, 0, aqm_drop SENDOUT.H R0, R3, 0not_ipv4_packet:ipv4_lpm_miss:aqm_drop: DROP.H 0host_eth:host_pss:exception:loopback:parser_congestion:trap: DROP.H 0
Figure 13. Forming the lookup key for the IPv4_LPM table
All X-Switch lookup operations execute asynchronously. The device provides eight special bit flags (LF0 through LF7) that are set when an asynchronous instruction completes and thus, can be used for synchronization. The .LF5 suffix indicates that we chose bit LF5 for synchronization with this instruction. Meanwhile, program execution continues in parallel with the lookup.

Figure 14. Performing the LPM lookup: key and result (value)

Figure 15. Setting Frame Delta in register R0 (R0.2)
This example demonstrates several new features and capabilities of the XISA architecture. We learned how to code a simple parser, how the parsed headers and other data are passed to the MAP, how to plan MAP register allocation, how to create a simple LPM table and finally how to put everything together, all in about 50 lines of code. The bulk of the parsing is achieved using just six instructions. Furthermore, most of the MAP processing is done using only 10 instructions.
XISA is capable of handling much more sophisticated networking tasks. Any standard routing and/or bridging feature, and more importantly, any other unique or customer-driven feature can be implemented thanks to the X-Switch's extreme flexibility and programmability. For more information, contact us.
We, at Xsight Labs, believe this transparency will drive innovation and facilitate the development of future networking technologies.
Stay tuned for further insights into the capabilities of the X-Switch ISA and the exciting possibilities it unlocks.
Previously: the five-instruction cross-connect. Next: the validation and rewrite that a real forwarding path needs.
XISA (the X-Switch Instruction Set Architecture) is Xsight Labs' open instruction set for programming packet processing on the X-Switch family, published under the Mozilla Public License version 2. Programs run across two stages: a Programmable Parser that identifies packet headers, and a Match-Action Processor (MAP) that performs lookups, edits and forwarding.