P4C
The P4 Compiler
|
Public Types | |
using | PayloadArguments = std::vector<const IR::Constant *> |
Public Member Functions | |
FindPayloadCandidates (const PhvInfo &p) | |
void | add_option (const IR::MAU::Table *, LayoutChoices &lc) |
void | clear () |
IR::MAU::Table * | convert_to_gateway (const IR::MAU::Table *) |
Static Public Member Functions | |
static bitvec | determine_payload (const IR::MAU::Table *tbl, const TableResourceAlloc *alloc, const IR::MAU::Table::Layout *layout) |
Static Public Attributes | |
static constexpr int | GATEWAY_ROWS_FOR_ENTRIES = 4 |
void FindPayloadCandidates::add_option | ( | const IR::MAU::Table * | tbl, |
LayoutChoices & | lc ) |
Copyright (C) 2024 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
SPDX-License-Identifier: Apache-2.0 A gateway in Tofino2 and future generations can be used to implement a small match table. For some context, a gateway is a 5 entry TCAM that has been used to implement conditionals. Examine the following example:
if (condition) { x.apply(); } else { y.apply(); } z.apply();
The gateway as a logical table can set the next table based on the evaluation of the condition. The gateway rows can be thought of as:
____match____|__next_table__ condition | x miss | y
A gateway can be linked with a match table as a single logical table. The gateway will verify its condition, and if the condition is false, can override the match table. This process is known as inhibiting. When the gateway inhibits, the next table comes from the gateway. If the gateway does not inhibit, the next table comes from the match table. Let's say, for example, that table x and the condition were merged into a single logical table. The gateway rows can be thought of as:
____match____|__inhibit__|__next_table__ condition | false | N/A (comes from x's execution, in this case z) miss | true | y
Now, what actually happens on an inhibit. In match central, there are two pathways for table execution, a hit pathway and a miss pathway. When the gateway inhibits, the gateway will automatically force the logical table to enter the hit pathway (even if the underlying match_table missed). The 83 bit result bus, the input to the hit pathway, is overwritten by the gateway with a 83 bit value. This value is called the payload.
For the standard example, the gateway payload is an all 0 value. Match central is then programmed to interpret the all 0 payload as a noop. Let's extend the table
____match____|__inhibit__|__next_table__|__payload__ condition | false | N/A | N/A miss | true | y | 0x0
Now, Tofino has corner cases that require the payload to be a non-zero value. Say I have the following P4 code:
field = register_action.execute(hash) // hash based index b.apply();
In this program, if the condition is true, then an action that runs a register action addressed by hash is run. Now this action will be translated in our midend to a single table:
action compiler_generated_action() { field = register_action.execute(hash); }
table compiler_generated_table { actions = { compiler_generated_action; } default_action = compiler_generated_action; }
This is a keyless table, and thus will always miss. Thus if this table ever runs, the miss action will have to set up this register action's execution. The major problem is that any information from hash is only available on the hit pathway. Hash Distribution is only accessible on hit. This is a conflict, as a table that can only run the miss pathway must access something that is only accessible on hit.
The hardware workaround is to use a gateway. The gateway can be used to generate a "hit", which then is used to access the hardware pathway. This is basically the gateway running an action. The table in this case would be:
____match____|__inhibit__|__next_table__|__payload__ true | yes | b | 0x0
In this case, the table is executed by the gateway inhibiting. Inhibit in this case runs a table. The gateway must always match, as we unconditionally want to run the table. The all 0 payload is then intepreted by match central to execute the compiler_generated_action, rather than run a noop on the previous example.
Now let's extend the example, to further understanding:
if (condition) { field = register_action.execute(hash) // hash based index b.apply(); } else { c.apply(); }
The gateway rows become:
____match____|__inhibit__|__next_table__|__payload__ condition | yes | b | 0x3 miss | no | N/A | N/A
gateway format : { action(0) : 0..0, meter_pfe(0) : 1..1 }
Now, when the condition is true, we want the register action to run. We run by inhibiting the table. When the condition is false, we want the register action to not run. The table will be run, and then the miss pathway will set the payload. The gateway payload is not necessary for this case, as on the miss the hit pathway will not run, and the input to the table will never be all 0. However, in order to match what is done for all other gateway examples (i.e. linking a table with a gateway preserves handling the all 0 input being a noop), the payload value was added.
This feature has now been extended in Tofino2. The major difference between Tofino and Tofino2 is that in Tofino, there is only a single entry allowed in the gateway format. In Tofino2, up to 5 entries are allowed in the format. This allows for separate behavior per entry.
Say for instance, I had the following program:
if (field == 3) field1 = register_action1.execute(hash); -> action compiler_generated_act1; else if (field == 7) field2 = register_action2.execute(hash); -> action compiler_generated_act2; b.apply();
where each of the register actions correspond to different stateful instructions on the same stateful ALU. This program requires two gateways in Tofino, where it could be done in a single gateway in Tofino2. Each stateful ALU execution requires a separate meter_type. Thus if I wanted to do this in a gateway:
____match____|__inhibit__|__next_table__|__payload__ field == 3 | yes | b | payload1 field == 7 | yes | b | payload2 miss | no | N/A | N/A
gateway_format = { action(0) : 0..1, meter_pfe(0) : 2..2, meter_type(0) : 3..5, action(1) : 6..7, meter_pfe(1) : 8..8, meter_type(1) : 9..11 }
Because the action that would be run (setting field1 or field2 would be different), as well as the register action's stateful instruction differing, both of the entries require a different payload. However, on Tofino, only one entry is allowed for the gateway payload
On Tofino2, however, multiple entries are allowed in the gateway format. This is because the gateway has a separate shift per entry. The register .*_exact_shiftcount has up to 5 entries per exact match table. A gateway has 5 entries, so in Tofino2, the exact_shiftcount was also made accessible to the gateway. Thus now a gateway can support different execution per gateway match.
The purpose of this pass is to find tables that can be converted to this type of gateway and convert them if table placement wishes to do so.
IR::MAU::Table * FindPayloadCandidates::convert_to_gateway | ( | const IR::MAU::Table * | tbl | ) |
A table may originally have next table propagation information in the next table map that now has to be updated for this program.
Essentially each gateway row (in order to have an entry in to the gateway_payload map), will have a unique tag: $entry#. The miss entry will currently have a $miss entry.
If the table is a hit or miss, then all entries that run the gateway payload will tie to the hit pathway, while the entry for the miss pathway goes through $miss. With an action_chain, because the actions of each entry is known ahead of time, each gateway entry can reflect that in the code. If no control flow from this table exists, just create empty entries.
|
static |
This is to determine the value that goes onto the payload_value of a gateway. Can be used for both Tofino and Tofino2 gateways, both pre and post table placement. This is currently done in the memories allocation, as this is when we write the Memories::Use object.
Will allow gateway variables such as: