What is the role of #include <tna.p4>

Hi,
I am a beginner of P4 language and I am confused of the pipeline process.
It seems that the whole P4 program is executed following the pipeline function defined at the end and there are many control functions in it. I find that we can define these control functions arbitrarily. However, I also find that many P4 program #include <v1model.p4> or #include <tna.p4> at the beginning. I read some relative documents and they say these two header files defines the whole pipeline like “parser – ingress pipeline – egress pipeline – deparser”.
So, I am confused about the role of #include <tna.p4> or other header files, do they define the following workflow? since we can arbitrarily define the control function.

Best wishes!

And I have another question. Since we can define the pipeline, what are the differences between v1model and tna? Does the program have to exactly follow the pre-defined v1model or tna workflow?

It is true that architectures like v1model and TNA have an ingress control and an egress control that you are allowed to fill in with whatever P4 code you want, and then your code will run to process packets at those times (if it compiles successfully, and “fits” into the constraints of the target device, e.g. on total table sizes fitting in the existing memory).

But what are “those times”? In a physical Ethernet switch, there is typically Ethernet MAC hardware that is NOT P4-programmable that first interprets the physical signals received, converts them to a sequence of bits, determines where the beginning and end of the Ethernet frame are, check the CRC, etc. None of that is typically P4-programmable at all, but it is there in the device.

Then ingress parser and ingress code run, as defined by the architecture. Does your P4 ingress code have only access to the packet contents, and nothing else? If the switch has 10 physical ports on it, is your P4 code allowed to know which port the packet arrived on, or not? That is defined by the P4 archticture, and if your P4 code is allowed to know the input port (it is in both v1model and TNA architectures), you can find out what the name of that field is, how many bits in size it is, etc.

Is your P4 ingress code allowed to know a time that the packet arrived? Again, this is defined in the P4 architecture, including what the units of this time value are in for that device, and how many bits wide it is, and thus how frequently it “wraps around” from max value back to 0.

What if you want to drop the packet during ingress processing, and never do egress processing? How can you write P4 code that makes that happen? Again, the P4 architecture defines whether you can do that, or not, and if you can, how you must write code that causes it to happen.

The mechanisms are slightly different between v1model and TNA architectures for all of the things I’ve mentioned. Why? For various detailed implementation reasons of the authors of those architectures.

There is a PNA (Portable NIC architecture) spec in the process of being defined now, and it is quite different from both v1model and TNA in that there is no egress control. Why not? Because NICs are typically designed in hardware quite differently than switch ASICs, is the short answer.

Hi
Thanks a lot for your reply! My understanding is that the architecture of the P4 code defines the switch logic to process incoming packets. But are v1model and tna two architectures? And the following P4 code must following the pre-defined architecture?
Or v1model and tna only defines some control function block, and our P4 code just combine these funtion block and fill them up to satisfy our requirement?
I got the idea that it is our concrete P4 code to define the action like just dropping the packets in the ingress and donot go into egress. But I am confused about the role of the tna.p4 or v1model.p4 here. Since in c++ or other language, there are only functions and we combine them to finish our goal. Architecture like “parser – ingress pipeline – egress pipeline – deparser” does not exist in these language.

Best wishes!

If you are familiar with C and an include file like stdlib.h, then there is the definition of the C language, e.g. if statements, loop statements, how to declare functions, etc.

Then there is the contents of an include file like stdlib.h, which defines the types of arguments, the number and order of arguments, and the return type of functions like strtol, srandom, random_r, and many others, as well as struct types and probably also some #define symbols.

Imagine you wanted to write a C program on two different operating systems, and they had different functions defined in stdlib.h, and/or they had some function names in common with each other, but sometimes the functions with the same name had different numbers of parameters, or different type for the first parameter, or different return types.

For most interesting C programs you might want to write, it is very likely that you could write a C program for operating system A with its version of stdlib.h, and a variation of that C program for operating system B with its version of stdlib.h, and both of those C programs would behave the same way. But everywhere that you needed different functions in the two versions of stdlib.h in your C program, you would have to change at least one and perhaps more lines of code between those 2 C programs. Those two C programs would be different in their source code from each other, but could both compute the same results from the same input.

Two different P4_16 architectures can in some cases be like that: You could write my_prog_for_tna.p4 and my_prog_for_v1model.p4 that behaved the same way, but they would not be identical to each other, because everywhere you dropped a packet, there would be at least one and perhaps several lines of source code that were different from each other. Everywhere that you wanted to instantiate a counter, or update a counter, there would be one or a few lines of code different in those two P4 programs. etc.

v1model and TNA are two similar, but different, P4_16 architectures. They both corresponding to a switch architecture that has “ingress, traffic manager, then egress” as the common packet flow, but they are not identical architectures.

Another P4_16 architecture like PNA (Portable NIC Architecture) can differ even more from v1model and TNA, in that it isn’t intended to model a switch ASIC, but a programmable NIC. It doesn’t have “ingress, traffic manager, then egress” at all.

I do not know if these examples will help at all, but the v1model and PSA architectures are two different, but similar, P4_16 architectures. PSA and TNA are not identical, but in some ways they are more similar to each other than v1model and TNA are.

Here are two P4_16 programs that do not do very much – they can parse an Ethernet header in the received packet, and send the packet out. I have tried to make their source code as similar as I can to each other, but they are far from identical. Why the differences? For several reasons, but for one, the parameters to the ingress control are different in v1model and the PSA architectures in their types. Why? Because they define different intrinsic/standard metadata that is available as input for your ingress code, and they define different intrinsic/standard metadata that you may assign values to in your ingress P4 code to control what happens next to the packet, after your ingress P4 code is finished executing (e.g. how to control whether it is dropped, or whether it is multicast-replicated, or resubmitted, or cloned, etc.)

v1model version: p4-guide/v1model_ethernet_parsing_only.p4 at master · jafingerhut/p4-guide · GitHub

PSA version: p4-guide/psa_ethernet_parsing_only.p4 at master · jafingerhut/p4-guide · GitHub

Note that someone could, if they wanted to, design a P4-programmable switch ASIC that had ingress parser, ingress control, and ingress deparser, followed by a traffic manager, but HAD NO EGRESS PROCESSING AT ALL.

I am not advocating that this is a good idea for a startup company, merely pointing out the technical possibility of doing so. I have worked on multiple switch ASICs that had a structure like this.

If you were to write a P4_16 architecture file that developers would #include its definitions when writing P4 programs for such a device, there would be no egress control. The developer would not be able to write one. It would be a compile time error if you tried.

Hi,
Thanks again for your detailed reply! I get the idea that v1model, tna or other architectures we used must follow the real switch ASIC we deploying? like when we use the tofino P4 switch, do we have to follow #include <tna.p4> or we can also write some other architectures that must similar to the tna? I have seen most P4 codes include the tna.p4 and I thought it may like the stdlib.h file that it is an accepted standard. Does the real tofino ASIC work as the pipeline defined in tna? (like we can not use the PSA in tofino ASIC because it doesn’t have the egress?)

A target device’s P4 compiler MIGHT choose to support P4 programs written using only one architecture, e.g. TNA for Tofino. That is certainly less work for the compiler writer to do.

As an aid in using P4 programs written using similar, but not identical, P4 architectures on a device, a P4 compiler developer MIGHT choose to support P4 programs written using one of several different architectures, e.g. P4 program written using the v1model architecture, but compiled to Tofino. There are several extern functions and objects and intrinsic metadata fields in the TNA architecture that expose features of Tofino that have no equivalent at all in the v1model architecture, so if one chose to write a v1model architecture P4 program and compile it to Tofino, they would be explicitly giving up the option to access those features which have no definitions in the v1model architecture. The only reason I can think of why a P4 developer might want to do this is convenience – the P4 program they wrote for the v1model architecture does what they need it to do, and they do not need to take advantage of other features that v1model does not define, and the P4 developer would prefer not to write another version of their P4 program that compiles with the TNA architecture.

A much shorter answer is that you need to include the architecture file corresponding to the target where your program will run. If you compile for Tofino you have to include tna.p4.

“If you compile for Tofino you have to include tna.p4”

Except that isn’t strictly true. A P4 compiler for a particular target may choose to implement support for multiple (similar-enough) architectures, as I wrote in one of my replies above.