diff --git a/backends/bmv2/base_test.py b/backends/bmv2/base_test.py index 2004fef574c..c37f746e03e 100644 --- a/backends/bmv2/base_test.py +++ b/backends/bmv2/base_test.py @@ -4,10 +4,9 @@ # https://github.com/p4lang/PI/blob/ec6865edc770b42f22fea15e6da17ca58a83d3a6/proto/ptf/base_test.py. from collections import Counter -from functools import wraps, partial +from functools import wraps, partialmethod import logging import re -import socket import sys import threading import time @@ -31,17 +30,6 @@ sys.path.append(str(TOOLS_PATH)) import testutils - -# See https://gist.github.com/carymrobbins/8940382 -# functools.partialmethod is introduced in Python 3.4 -class partialmethod(partial): - - def __get__(self, instance, owner): - if instance is None: - return self - return partial(self.func, instance, *(self.args or ()), **(self.keywords or {})) - - def stringify(n, length=0): """Take a non-negative integer 'n' as the first parameter, and a non-negative integer 'length' in units of _bytes_ as the second @@ -65,77 +53,6 @@ def stringify(n, length=0): s = n.to_bytes(length, byteorder='big') return s - -def ipv4_to_binary(addr): - """Take an argument 'addr' containing an IPv4 address written as a - string in dotted decimal notation, e.g. '10.1.2.3', and convert it - to a string with binary contents expected by the Python P4Runtime - client operations.""" - bytes_ = [int(b, 10) for b in addr.split('.')] - assert len(bytes_) == 4 - # Note: The bytes() call below will throw exception if any - # elements of bytes_ is outside of the range [0, 255]], so no need - # to add a separate check for that here. - return bytes(bytes_) - - -def ipv4_to_int(addr): - """Take an argument 'addr' containing an IPv4 address written as a - string in dotted decimal notation, e.g. '10.1.2.3', and convert it - to an integer.""" - bytes_ = [int(b, 10) for b in addr.split('.')] - assert len(bytes_) == 4 - # Note: The bytes() call below will throw exception if any - # elements of bytes_ is outside of the range [0, 255]], so no need - # to add a separate check for that here. - return int.from_bytes(bytes(bytes_), byteorder='big') - - -def ipv6_to_binary(addr): - """Take an argument 'addr' containing an IPv6 address written in - standard syntax, e.g. '2001:0db8::3210', and convert it to a - string with binary contents expected by the Python P4Runtime - client operations.""" - return socket.inet_pton(socket.AF_INET6, addr) - - -def ipv6_to_int(addr): - """Take an argument 'addr' containing an IPv6 address written in - standard syntax, e.g. '2001:0db8::3210', and convert it to an - integer.""" - bytes_ = socket.inet_pton(socket.AF_INET6, addr) - # Note: The bytes() call below will throw exception if any - # elements of bytes_ is outside of the range [0, 255]], so no need - # to add a separate check for that here. - return int.from_bytes(bytes_, byteorder='big') - - -def mac_to_binary(addr): - """Take an argument 'addr' containing an Ethernet MAC address written - as a string in hexadecimal notation, with each byte separated by a - colon, e.g. '00:de:ad:be:ef:ff', and convert it to a string with - binary contents expected by the Python P4Runtime client - operations.""" - bytes_ = [int(b, 16) for b in addr.split(':')] - assert len(bytes_) == 6 - # Note: The bytes() call below will throw exception if any - # elements of bytes_ is outside of the range [0, 255]], so no need - # to add a separate check for that here. - return bytes(bytes_) - - -def mac_to_int(addr): - """Take an argument 'addr' containing an Ethernet MAC address written - as a string in hexadecimal notation, with each byte separated by a - colon, e.g. '00:de:ad:be:ef:ff', and convert it to an integer.""" - bytes_ = [int(b, 16) for b in addr.split(':')] - assert len(bytes_) == 6 - # Note: The bytes() call below will throw exception if any - # elements of bytes_ is outside of the range [0, 255]], so no need - # to add a separate check for that here. - return int.from_bytes(bytes(bytes_), byteorder='big') - - # Used to indicate that the gRPC error Status object returned by the server has # an incorrect format. class P4RuntimeErrorFormatException(Exception): @@ -285,6 +202,7 @@ def setUp(self): self.dataplane = ptf.dataplane_instance self.dataplane.flush() + self.p4info_obj_map = {} self._swports = [] for _, port, _ in config["interfaces"]: self._swports.append(port) @@ -346,11 +264,10 @@ def updateConfig(self): # In order to make writing tests easier, we accept any suffix that uniquely # identifies the object among p4info objects of the same type. def import_p4info_names(self): - self.p4info_obj_map = {} suffix_count = Counter() for obj_type in [ "tables", "action_profiles", "actions", "counters", "direct_counters", - "controller_packet_metadata" + "controller_packet_metadata", "meters", "direct_meters", ]: for obj in getattr(self.p4info, obj_type): pre = obj.preamble @@ -830,7 +747,7 @@ def send_request_add_entry_to_action(self, t_name, mk, a_name, params, priority= req = p4runtime_pb2.WriteRequest() req.device_id = self.device_id self.push_update_add_entry_to_action(req, t_name, mk, a_name, params, priority) - return req, self.write_request(req, store=(mk is not None)) + return req, self.write_request(req, store=mk is not None) def check_table_name_and_key(self, table_name_and_key): assert isinstance(table_name_and_key, tuple) @@ -890,7 +807,7 @@ def table_add(self, table_name_and_key, action_name_and_params, priority=None, o if key is None: update.type = p4runtime_pb2.Update.MODIFY testutils.log.info(f"table_add: req={req}") - return req, self.write_request(req, store=(key is not None)) + return req, self.write_request(req, store=key is not None) def pre_add_mcast_group(self, mcast_grp_id, port_instance_pair_list): """When a packet is sent from ingress to the packet buffer with @@ -982,6 +899,26 @@ def counter_dump_data(self, counter_name, direct=False): counter_entries.append(entry) return counter_entries + def meter_write(self, meter_name, index, meter_config, direct): + req = self.get_new_write_request() + update = req.updates.add() + update.type = p4runtime_pb2.Update.MODIFY + entity = update.entity + if direct: + meter_write = entity.direct_meter_entry + meter_obj = self.get_obj("direct_meters", meter_name) + meter_write.table_entry.table_id = meter_obj.direct_table_id + meter_write.table_entry.match.add() + else: + meter_write = entity.meter_entry + meter_write.meter_id = self.get_meter_id(meter_name) + meter_write.index.index = index + meter_write.config.cir = meter_config.cir + meter_write.config.cburst = meter_config.cburst + meter_write.config.pir = meter_config.pir + meter_write.config.pburst = meter_config.pburst + return req, self.write_request(req) + def make_table_read_request(self, table_name): req = p4runtime_pb2.ReadRequest() req.device_id = self.device_id @@ -1040,7 +977,7 @@ def send_request_add_entry_to_member(self, t_name, mk, mbr_id): req = p4runtime_pb2.WriteRequest() req.device_id = self.device_id self.push_update_add_entry_to_member(req, t_name, mk, mbr_id) - return req, self.write_request(req, store=(mk is not None)) + return req, self.write_request(req, store=mk is not None) def push_update_add_entry_to_group(self, req, t_name, mk, grp_id): update = req.updates.add() @@ -1057,7 +994,7 @@ def send_request_add_entry_to_group(self, t_name, mk, grp_id): req = p4runtime_pb2.WriteRequest() req.device_id = self.device_id self.push_update_add_entry_to_group(req, t_name, mk, grp_id) - return req, self.write_request(req, store=(mk is not None)) + return req, self.write_request(req, store=mk is not None) # iterates over all requests in reverse order; if they are INSERT updates, # replay them as DELETE updates; this is a convenient way to clean-up a lot @@ -1096,6 +1033,7 @@ def get_new_write_request(self): # get_obj("tables", x) and get_obj_id("tables", x) for obj_type, nickname in [("tables", "table"), ("action_profiles", "ap"), ("actions", "action"), ("counters", "counter"), ("direct_counters", "direct_counter"), + ("meters", "meter"), ("direct_meters", "direct_meter"), ("controller_packet_metadata", "controller_packet_metadata")]: name = "_".join(["get", nickname]) setattr(P4RuntimeTest, name, partialmethod(P4RuntimeTest.get_obj, obj_type)) @@ -1144,7 +1082,7 @@ def update_config(config_path, p4info_path, grpc_addr, device_id): ''' channel = grpc.insecure_channel(grpc_addr) stub = p4runtime_pb2_grpc.P4RuntimeStub(channel) - testutils.log.info(f"Sending P4 config from file {config_path} with P4info {p4info_path}") + testutils.log.info("Sending P4 config from file %s with P4info %s", config_path, p4info_path) request = p4runtime_pb2.SetForwardingPipelineConfigRequest() request.device_id = device_id config = request.config @@ -1155,7 +1093,7 @@ def update_config(config_path, p4info_path, grpc_addr, device_id): request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT try: response = stub.SetForwardingPipelineConfig(request) - logging.debug(f"Response {response}") + logging.debug("Response %s", response) except Exception as e: logging.error("Error during SetForwardingPipelineConfig") logging.error(e) diff --git a/backends/p4tools/modules/testgen/lib/execution_state.cpp b/backends/p4tools/modules/testgen/lib/execution_state.cpp index 5c5c05394c2..7e18821fb8e 100644 --- a/backends/p4tools/modules/testgen/lib/execution_state.cpp +++ b/backends/p4tools/modules/testgen/lib/execution_state.cpp @@ -182,7 +182,7 @@ const std::stack> } void ExecutionState::setProperty(cstring propertyName, Continuation::PropertyValue property) { - stateProperties[propertyName] = std::move(property); + stateProperties[propertyName] = property; } bool ExecutionState::hasProperty(cstring propertyName) const { diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp index 200e31d95be..92f3a895c06 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp @@ -64,7 +64,7 @@ inja::json Metadata::getVerify(const TestSpec *testSpec) { inja::json verifyData = inja::json::object(); auto egressPacket = testSpec->getEgressPacket(); if (egressPacket.has_value()) { - const auto *const packet = egressPacket.value(); + const auto *packet = egressPacket.value(); verifyData["eg_port"] = packet->getPort(); const auto *payload = packet->getEvaluatedPayload(); const auto *payloadMask = packet->getEvaluatedPayloadMask(); @@ -92,7 +92,7 @@ statement_coverage: {{coverage}} ## endfor # The input packet in hexadecimal. -input_packet: \"{{send.pkt}}\" +input_packet: "{{send.pkt}}" # Parsed headers and their offsets. header_offsets: @@ -103,7 +103,7 @@ input_packet: \"{{send.pkt}}\" # Metadata results after this test has completed. metadata: ## for metadata_field in metadata_fields - {{metadata_field.name}}: [value: \"{{metadata_field.value}}\", offset: {{metadata_field.offset}}] + {{metadata_field.name}}: [value: "{{metadata_field.value}}", offset: {{metadata_field.offset}}] ## endfor )"""); return TEST_CASE; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp index 1fb4a3e75d7..2065b2cfd71 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp @@ -39,6 +39,28 @@ inja::json::array_t PTF::getClone(const std::map &c return cloneJson; } +inja::json::array_t getMeter(const std::map &meterValues) { + auto meterJson = inja::json::array_t(); + for (auto meterValueInfoTuple : meterValues) { + const auto *meterValue = meterValueInfoTuple.second->checkedTo(); + + const auto meterEntries = meterValue->unravelMap(); + for (const auto &meterEntry : meterEntries) { + inja::json meterInfoJson; + meterInfoJson["name"] = meterValueInfoTuple.first; + meterInfoJson["value"] = formatHexExpr(meterEntry.second.second); + meterInfoJson["index"] = formatHex(meterEntry.first, meterEntry.second.first); + if (meterValue->isDirectMeter()) { + meterInfoJson["is_direct"] = "True"; + } else { + meterInfoJson["is_direct"] = "False"; + } + meterJson.push_back(meterInfoJson); + } + } + return meterJson; +} + std::vector> PTF::getIgnoreMasks(const IR::Constant *mask) { std::vector> ignoreMasks; if (mask == nullptr) { @@ -184,11 +206,12 @@ inja::json PTF::getSend(const TestSpec *testSpec) { inja::json PTF::getVerify(const TestSpec *testSpec) { inja::json verifyData = inja::json::object(); - if (testSpec->getEgressPacket() != std::nullopt) { - const auto &packet = **testSpec->getEgressPacket(); - verifyData["eg_port"] = packet.getPort(); - const auto *payload = packet.getEvaluatedPayload(); - const auto *payloadMask = packet.getEvaluatedPayloadMask(); + auto egressPacket = testSpec->getEgressPacket(); + if (egressPacket.has_value()) { + const auto *packet = egressPacket.value(); + verifyData["eg_port"] = packet->getPort(); + const auto *payload = packet->getEvaluatedPayload(); + const auto *payloadMask = packet->getEvaluatedPayloadMask(); verifyData["ignore_masks"] = getIgnoreMasks(payloadMask); auto dataStr = formatHexExpr(payload, false, true, false); @@ -197,14 +220,12 @@ inja::json PTF::getVerify(const TestSpec *testSpec) { return verifyData; } -static std::string getPreamble() { +void PTF::emitPreamble() { static const std::string PREAMBLE( R"""(# P4Runtime PTF test for {{test_name}} # p4testgen seed: {{ default(seed, "none") }} -import logging -import sys -import os +from enum import Enum from ptf.mask import Mask @@ -214,7 +235,9 @@ from ptf import testutils as ptfutils import base_test as bt + class AbstractTest(bt.P4RuntimeTest): + EnumColor = Enum("EnumColor", ["GREEN", "YELLOW", "RED"], start=0) @bt.autocleanup def setUp(self): @@ -241,19 +264,33 @@ class AbstractTest(bt.P4RuntimeTest): bt.testutils.log.info("Verifying Packet ...") self.verifyPackets() - + def meter_write_with_predefined_config(self, meter_name, index, value, direct): + """Since we can not blast the target with packets, we have to carefully craft an artificial scenario where the meter will return the color we want. We do this by setting the meter config in such a way that the meter is forced to assign the desired color. For example, for RED to the lowest threshold values, to force a RED assignment.""" + value = self.EnumColor(value) + if value == self.EnumColor.GREEN: + meter_config = bt.p4runtime_pb2.MeterConfig( + cir=4294967295, cburst=4294967295, pir=4294967295, pburst=4294967295 + ) + elif value == self.EnumColor.YELLOW: + meter_config = bt.p4runtime_pb2.MeterConfig( + cir=1, cburst=1, pir=4294967295, pburst=4294967295 + ) + elif value == self.EnumColor.RED: + meter_config = bt.p4runtime_pb2.MeterConfig( + cir=1, cburst=1, pir=1, pburst=1 + ) + else: + raise self.failureException(f"Unsupported meter value {value}") + return self.meter_write(meter_name, index, meter_config, direct) )"""); - return PREAMBLE; -} -void PTF::emitPreamble(const std::string &preamble) { inja::json dataJson; dataJson["test_name"] = basePath.stem(); if (seed) { dataJson["seed"] = *seed; } - inja::render_to(ptfFileStream, preamble, dataJson); + inja::render_to(ptfFileStream, PREAMBLE, dataJson); ptfFileStream.flush(); } @@ -314,6 +351,10 @@ class Test{{test_id}}(AbstractTest): self.insert_pre_clone_session({{clone_info.session_id}}, [{{clone_info.clone_port}}]) ## endfor ## endif +## for meter_value in meter_values + self.meter_write_with_predefined_config("{{meter_value.name}}", {{meter_value.index}}, {{meter_value.value}}, {{meter_value.is_direct}}) +## endfor + def sendPacket(self): ## if send @@ -379,6 +420,8 @@ void PTF::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_ if (!cloneInfos.empty()) { dataJson["clone_infos"] = getClone(cloneInfos); } + auto meterValues = testSpec->getTestObjectCategory("meter_values"); + dataJson["meter_values"] = getMeter(meterValues); LOG5("PTF backend: emitting testcase:" << std::setw(4) << dataJson); @@ -392,8 +435,7 @@ void PTF::outputTest(const TestSpec *testSpec, cstring selectedBranches, size_t auto ptfFile = basePath; ptfFile.replace_extension(".py"); ptfFileStream = std::ofstream(ptfFile); - std::string preamble = getPreamble(); - emitPreamble(preamble); + emitPreamble(); preambleEmitted = true; } std::string testCase = getTestCaseTemplate(); diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h index f30a07e7ebb..61018778e99 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h @@ -46,9 +46,12 @@ class PTF : public TF { float currentCoverage) override; private: + /// @returns the static preamble string used by inja to serialize the test preamble. + static std::string getPreamble(); + /// Emits the test preamble. This is only done once for all generated tests. /// For the PTF back end this is the test setup Python script.. - void emitPreamble(const std::string &preamble); + void emitPreamble(); /// Emits a test case. /// @param testIdx specifies the test name. diff --git a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp index f813a35e91f..3003d6fcd08 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp @@ -17,6 +17,7 @@ #include "backends/p4tools/common/lib/util.h" #include "ir/declaration.h" #include "ir/indexed_vector.h" +#include "ir/ir-generated.h" #include "ir/irutils.h" #include "ir/node.h" #include "lib/cstring.h" @@ -437,24 +438,24 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio std::vector replacements; const auto *receiverPath = receiver->checkedTo(); - const auto &externInstance = state.convertPathExpr(receiverPath); + const auto &externInstance = nextState.findDecl(receiverPath); // Retrieve the register state from the object store. If it is already present, just // cast the object to the correct class and retrieve the current value according to the // index. If the register has not been added had, create a new register object. const auto *registerState = - state.getTestObject("registervalues", externInstance->toString(), false); - const Bmv2RegisterValue *registerValue = nullptr; + state.getTestObject("registervalues", externInstance->controlPlaneName(), false); + const Bmv2V1ModelRegisterValue *registerValue = nullptr; if (registerState != nullptr) { - registerValue = registerState->checkedTo(); + registerValue = registerState->checkedTo(); } else { const auto *inputValue = programInfo.createTargetUninitialized(readOutput->type, false); - registerValue = new Bmv2RegisterValue(inputValue); - nextState.addTestObject("registervalues", externInstance->toString(), + registerValue = new Bmv2V1ModelRegisterValue(inputValue); + nextState.addTestObject("registervalues", externInstance->controlPlaneName(), registerValue); } - const IR::Expression *baseExpr = registerValue->getCurrentValue(index); + const IR::Expression *baseExpr = registerValue->getValueAtIndex(index); if (readOutput->type->is()) { // We need an assignment statement (and the inefficient copy) here because we need @@ -533,9 +534,9 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio return; } } - const auto *receiverPath = receiver->checkedTo(); - const auto &externInstance = state.convertPathExpr(receiverPath); auto &nextState = state.clone(); + const auto *receiverPath = receiver->checkedTo(); + const auto &externInstance = nextState.findDecl(receiverPath); // TODO: Find a better way to model a trace of this event. std::stringstream registerStream; registerStream << "RegisterWrite: Value "; @@ -546,20 +547,21 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio // "Write" to the register by update the internal test object state. If the register // did not exist previously, update it with the value to write as initial value. - const auto *registerState = - nextState.getTestObject("registervalues", externInstance->toString(), false); - Bmv2RegisterValue *registerValue = nullptr; + const auto *registerState = nextState.getTestObject( + "registervalues", externInstance->controlPlaneName(), false); + Bmv2V1ModelRegisterValue *registerValue = nullptr; if (registerState != nullptr) { - registerValue = - new Bmv2RegisterValue(*registerState->checkedTo()); - registerValue->addRegisterCondition(Bmv2RegisterCondition{index, inputValue}); + registerValue = new Bmv2V1ModelRegisterValue( + *registerState->checkedTo()); + registerValue->writeToIndex(index, inputValue); } else { const auto &writeValue = programInfo.createTargetUninitialized(inputValue->type, false); - registerValue = new Bmv2RegisterValue(writeValue); - registerValue->addRegisterCondition(Bmv2RegisterCondition{index, inputValue}); + registerValue = new Bmv2V1ModelRegisterValue(writeValue); + registerValue->writeToIndex(index, inputValue); } - nextState.addTestObject("registervalues", externInstance->toString(), registerValue); + nextState.addTestObject("registervalues", externInstance->controlPlaneName(), + registerValue); nextState.popBody(); result->emplace_back(nextState); }}, @@ -638,7 +640,7 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio result->emplace_back(nextState); }}, /* ====================================================================================== - * meter.read + * meter.execute_meter * A meter object is created by calling its constructor. This * creates an array of meter states, with the number of meter * states specified by the size parameter. The array indices are @@ -668,19 +670,83 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio * range, the final value of result is not specified, * and should be ignored by the caller. * ====================================================================================== */ - // TODO: Read currently has no effect in the symbolic interpreter. {"meter.execute_meter", {"index", "result"}, - [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, - IR::ID & /*methodName*/, const IR::Vector * /*args*/, + [](const IR::MethodCallExpression *call, const IR::Expression *receiver, + IR::ID & /*methodName*/, const IR::Vector *args, const ExecutionState &state, SmallStepEvaluator::Result &result) { - ::warning("meter.execute_meter not fully implemented."); + auto testBackend = TestgenOptions::get().testBackend; + if (testBackend != "PTF") { + ::warning("meter.execute_meter not implemented for %1%.", testBackend); + auto &nextState = state.clone(); + nextState.popBody(); + result->emplace_back(nextState); + return; + } + // TODO: Frontload this in the expression stepper for method call expressions. + const auto *index = args->at(0)->expression; + if (!SymbolicEnv::isSymbolicValue(index)) { + // Evaluate the condition. + stepToSubexpr(index, result, state, [call](const Continuation::Parameter *v) { + auto *clonedCall = call->clone(); + auto *arguments = clonedCall->arguments->clone(); + auto *arg = arguments->at(0)->clone(); + arg->expression = v->param; + (*arguments)[0] = arg; + clonedCall->arguments = arguments; + return Continuation::Return(clonedCall); + }); + return; + } + const auto *meterResult = args->at(1)->expression; auto &nextState = state.clone(); - nextState.popBody(); - result->emplace_back(nextState); + std::vector replacements; + + const auto *receiverPath = receiver->checkedTo(); + const auto &externInstance = nextState.findDecl(receiverPath); + + // Retrieve the meter state from the object store. If it is already present, just + // cast the object to the correct class and retrieve the current value according to the + // index. If the meter has not been added had, create a new meter object. + const auto *meterState = + state.getTestObject("meter_values", externInstance->controlPlaneName(), false); + Bmv2V1ModelMeterValue *meterValue = nullptr; + const auto &inputValue = nextState.createZombieConst( + meterResult->type, "meter_value" + std::to_string(call->clone_id)); + // Make sure we do not accidentally get "3" as enum assignment... + auto *cond = new IR::Lss(inputValue, IR::getConstant(meterResult->type, 3)); + if (meterState != nullptr) { + meterValue = + new Bmv2V1ModelMeterValue(*meterState->checkedTo()); + } else { + meterValue = new Bmv2V1ModelMeterValue(inputValue, false); + } + meterValue->writeToIndex(index, inputValue); + nextState.addTestObject("meter_values", externInstance->controlPlaneName(), + meterValue); + + if (meterResult->type->is()) { + // We need an assignment statement (and the inefficient copy) here because we need + // to immediately resolve the generated mux into multiple branches. + // This is only possible because meters do not return a value. + replacements.emplace_back(new IR::AssignmentStatement(meterResult, inputValue)); + + } else { + TESTGEN_UNIMPLEMENTED("Read extern output %1% of type %2% not supported", + meterResult, meterResult->type); + } + // TODO: Find a better way to model a trace of this event. + std::stringstream meterStream; + meterStream << "MeterExecute: Index "; + index->dbprint(meterStream); + meterStream << " into field "; + meterResult->dbprint(meterStream); + nextState.add(*new TraceEvents::Generic(meterStream.str())); + nextState.replaceTopBody(&replacements); + result->emplace_back(cond, state, nextState); }}, /* ====================================================================================== - * direct_meter.count + * direct_meter.read * A direct_meter object is created by calling its constructor. * You must provide a choice of whether to meter based on the * number of packets, regardless of their size @@ -709,16 +775,67 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio * color YELLOW, and 2 for color RED (see RFC 2697 * and RFC 2698 for the meaning of these colors). * ====================================================================================== */ - // TODO: Read currently has no effect in the symbolic interpreter. {"direct_meter.read", {"result"}, - [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, - IR::ID & /*methodName*/, const IR::Vector * /*args*/, + [](const IR::MethodCallExpression *call, const IR::Expression *receiver, + IR::ID & /*methodName*/, const IR::Vector *args, const ExecutionState &state, SmallStepEvaluator::Result &result) { - ::warning("direct_meter.read not fully implemented."); + auto testBackend = TestgenOptions::get().testBackend; + if (testBackend != "PTF") { + ::warning("direct_meter.read not implemented for %1%.", testBackend); + auto &nextState = state.clone(); + nextState.popBody(); + result->emplace_back(nextState); + return; + } + + const auto *meterResult = args->at(0)->expression; auto &nextState = state.clone(); - nextState.popBody(); - result->emplace_back(nextState); + std::vector replacements; + + const auto *receiverPath = receiver->checkedTo(); + const auto &externInstance = nextState.findDecl(receiverPath); + + // Retrieve the meter state from the object store. If it is already present, just + // cast the object to the correct class and retrieve the current value according to the + // index. If the meter has not been added had, create a new meter object. + const auto *index = IR::getConstant(IR::getBitType(1), 0); + const auto *meterState = + state.getTestObject("meter_values", externInstance->controlPlaneName(), false); + Bmv2V1ModelMeterValue *meterValue = nullptr; + const auto &inputValue = nextState.createZombieConst( + meterResult->type, "meter_value" + std::to_string(call->clone_id)); + // Make sure we do not accidentally get "3" as enum assignment... + auto *cond = new IR::Lss(inputValue, IR::getConstant(meterResult->type, 3)); + if (meterState != nullptr) { + meterValue = + new Bmv2V1ModelMeterValue(*meterState->checkedTo()); + } else { + meterValue = new Bmv2V1ModelMeterValue(inputValue, true); + } + meterValue->writeToIndex(index, inputValue); + nextState.addTestObject("meter_values", externInstance->controlPlaneName(), + meterValue); + + const IR::Expression *baseExpr = meterValue->getValueAtIndex(index); + + if (meterResult->type->is()) { + // We need an assignment statement (and the inefficient copy) here because we need + // to immediately resolve the generated mux into multiple branches. + // This is only possible because meters do not return a value. + replacements.emplace_back(new IR::AssignmentStatement(meterResult, baseExpr)); + + } else { + TESTGEN_UNIMPLEMENTED("Read extern output %1% of type %2% not supported", + meterResult, meterResult->type); + } + // TODO: Find a better way to model a trace of this event. + std::stringstream meterStream; + meterStream << "DirectMeterRead into field "; + meterResult->dbprint(meterStream); + nextState.add(*new TraceEvents::Generic(meterStream.str())); + nextState.replaceTopBody(&replacements); + result->emplace_back(cond, state, nextState); }}, /* ====================================================================================== @@ -1154,7 +1271,7 @@ void BMv2_V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpressio } // This is the clone state. auto &nextState = state.clone(); - auto progInfo = getProgramInfo().checkedTo(); + const auto *progInfo = getProgramInfo().checkedTo(); // We need to reset everything to the state before the ingress call. We use a trick // by calling copyIn on the entire state again. We need a little bit of information diff --git a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h index 417efa92a7e..e99cf8a06f9 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h @@ -1,8 +1,7 @@ #ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_EXPR_STEPPER_H_ #define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_EXPR_STEPPER_H_ -#include - +#include #include #include "backends/p4tools/common/core/solver.h" @@ -14,11 +13,7 @@ #include "backends/p4tools/modules/testgen/core/small_step/expr_stepper.h" #include "backends/p4tools/modules/testgen/lib/execution_state.h" -namespace P4Tools { - -namespace P4Testgen { - -namespace Bmv2 { +namespace P4Tools::P4Testgen::Bmv2 { class BMv2_V1ModelExprStepper : public ExprStepper { protected: @@ -46,10 +41,6 @@ class BMv2_V1ModelExprStepper : public ExprStepper { bool preorder(const IR::P4Table * /*table*/) override; }; -} // namespace Bmv2 - -} // namespace P4Testgen - -} // namespace P4Tools +} // namespace P4Tools::P4Testgen::Bmv2 #endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_EXPR_STEPPER_H_ */ diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_direct_meter_1.p4 b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_direct_meter_1.p4 new file mode 100644 index 00000000000..8ff79ca9189 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_direct_meter_1.p4 @@ -0,0 +1,82 @@ +#include +#include + +header ethernet_t { + bit<48> dst_addr; + bit<48> src_addr; + bit<16> eth_type; +} + +header H { + bit<8> color_status; + bit<32> idx; +} + +struct headers { + ethernet_t eth_hdr; + H h; +} + +enum bit<2> MeterColor { + GREEN = 0, + YELLOW = 1, + RED = 2 +} + +struct Meta {} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + transition parse_hdrs; + } + state parse_hdrs { + pkt.extract(hdr.eth_hdr); + pkt.extract(hdr.h); + transition accept; + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + direct_meter(MeterType.bytes) table_attached_meter; + MeterColor color = (MeterColor) 0; + + action meter_assign() { + table_attached_meter.read(color); + } + table meter_table { + actions = { + meter_assign; + NoAction; + } + key = { + h.eth_hdr.dst_addr: exact; + } + size = 16384; + default_action = NoAction(); + meters = table_attached_meter; + } + + apply { + meter_table.apply(); + if (color == MeterColor.RED) { + h.h.color_status = 2; + } else if (color == MeterColor.YELLOW) { + h.h.color_status = 1; + } else { + h.h.color_status = 0; + } + } +} + +control vrfy(inout headers h, inout Meta m) { apply {} } + +control update(inout headers h, inout Meta m) { apply {} } + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { apply {} } + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h); + } +} +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_1.p4 b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_1.p4 new file mode 100644 index 00000000000..2502ab9c9a7 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_1.p4 @@ -0,0 +1,65 @@ +#include +#include + +header ethernet_t { + bit<48> dst_addr; + bit<48> src_addr; + bit<16> eth_type; +} + +header H { + bit<8> color_status; + bit<32> idx; +} + +struct headers { + ethernet_t eth_hdr; + H h; +} + +enum bit<2> MeterColor { + GREEN = 0, + YELLOW = 1, + RED = 2 +} + +struct Meta {} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + transition parse_hdrs; + } + state parse_hdrs { + pkt.extract(hdr.eth_hdr); + pkt.extract(hdr.h); + transition accept; + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + meter(1024, MeterType.bytes) simple_meter; + apply { + MeterColor color; + simple_meter.execute_meter(h.h.idx, color); + if (color == MeterColor.RED) { + h.h.color_status = 2; + } else if (color == MeterColor.YELLOW) { + h.h.color_status = 1; + } else { + h.h.color_status = 0; + } + } +} + +control vrfy(inout headers h, inout Meta m) { apply {} } + +control update(inout headers h, inout Meta m) { apply {} } + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { apply {} } + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h); + } +} +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_2.p4 b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_2.p4 new file mode 100644 index 00000000000..8394decbd65 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_meter_2.p4 @@ -0,0 +1,74 @@ +#include +#include + +header ethernet_t { + bit<48> dst_addr; + bit<48> src_addr; + bit<16> eth_type; +} + +header H { + bit<8> color_status; + bit<8> color_status_2; + bit<32> idx; +} + +struct headers { + ethernet_t eth_hdr; + H h; +} + +enum bit<2> MeterColor { + GREEN = 0, + YELLOW = 1, + RED = 2 +} + +struct Meta {} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + transition parse_hdrs; + } + state parse_hdrs { + pkt.extract(hdr.eth_hdr); + pkt.extract(hdr.h); + transition accept; + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + meter(1024, MeterType.bytes) simple_meter; + apply { + MeterColor color; + simple_meter.execute_meter(0, color); + if (color == MeterColor.RED) { + h.h.color_status = 2; + } else if (color == MeterColor.YELLOW) { + h.h.color_status = 1; + } else { + h.h.color_status = 0; + } + simple_meter.execute_meter(1, color); + if (color == MeterColor.RED) { + h.h.color_status_2 = 2; + } else if (color == MeterColor.YELLOW) { + h.h.color_status_2 = 1; + } else { + h.h.color_status_2 = 0; + } + } +} + +control vrfy(inout headers h, inout Meta m) { apply {} } + +control update(inout headers h, inout Meta m) { apply {} } + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { apply {} } + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h); + } +} +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp index f01d2f84543..19787cbe04c 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp @@ -155,6 +155,14 @@ const TestSpec *Bmv2TestBackend::createTestSpec(const ExecutionState *executionS testSpec->addTestObject("clone_infos", sessionId, evaluatedInfo); } + const auto meterInfos = executionState->getTestObjectCategory("meter_values"); + for (const auto &testObject : meterInfos) { + const auto meterName = testObject.first; + const auto *meterInfo = testObject.second->checkedTo(); + const auto *evaluateMeterValue = meterInfo->evaluate(*completedModel); + testSpec->addTestObject("meter_values", meterName, evaluateMeterValue); + } + return testSpec; } diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp index 68e22465b21..911869c85c6 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp @@ -8,73 +8,123 @@ namespace P4Tools::P4Testgen::Bmv2 { /* ========================================================================================= - * Bmv2Register + * IndexExpression * ========================================================================================= */ -Bmv2RegisterValue::Bmv2RegisterValue(const IR::Expression *initialValue) - : initialValue(initialValue) {} +IndexExpression::IndexExpression(const IR::Expression *index, const IR::Expression *value) + : index(index), value(value) {} -void Bmv2RegisterValue::addRegisterCondition(Bmv2RegisterCondition cond) { - registerConditions.push_back(cond); +const IR::Constant *IndexExpression::getEvaluatedValue() const { + const auto *constant = value->to(); + BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", + getObjectName()); + return constant; } -const IR::Expression *Bmv2RegisterValue::getInitialValue() const { return initialValue; } +const IR::Constant *IndexExpression::getEvaluatedIndex() const { + const auto *constant = index->to(); + BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", + getObjectName()); + return constant; +} + +const IR::Expression *IndexExpression::getIndex() const { return index; } + +const IR::Expression *IndexExpression::getValue() const { return value; } + +cstring IndexExpression::getObjectName() const { return "IndexExpression"; } + +const IndexExpression *IndexExpression::evaluate(const Model &model) const { + const auto *evaluatedIndex = model.evaluate(index); + const auto *evaluatedValue = model.evaluate(value); + return new IndexExpression(evaluatedIndex, evaluatedValue); +} + +std::map> IndexMap::unravelMap() const { + std::map> valueMap; + for (auto it = indexConditions.rbegin(); it != indexConditions.rend(); ++it) { + const auto *storedIndex = it->getEvaluatedIndex(); + const auto *storedVal = it->getEvaluatedValue(); + // Important, if the element already exists in the map, ignore it. + // That index has been overwritten. + valueMap.insert({storedIndex->value, {storedIndex->type->width_bits(), storedVal}}); + } + return valueMap; +} + +/* ========================================================================================= + * Bmv2Register + * ========================================================================================= */ + +IndexMap::IndexMap(const IR::Expression *initialValue) : initialValue(initialValue) {} + +void IndexMap::writeToIndex(const IR::Expression *index, const IR::Expression *value) { + indexConditions.emplace_back(index, value); +} -cstring Bmv2RegisterValue::getObjectName() const { return "Bmv2RegisterValue"; } +const IR::Expression *IndexMap::getInitialValue() const { return initialValue; } -const IR::Expression *Bmv2RegisterValue::getCurrentValue(const IR::Expression *index) const { +const IR::Expression *IndexMap::getValueAtIndex(const IR::Expression *index) const { const IR::Expression *baseExpr = initialValue; - for (const auto &bmv2registerValue : registerConditions) { - const auto *storedIndex = bmv2registerValue.index; - const auto *storedVal = bmv2registerValue.value; + for (const auto &indexMap : indexConditions) { + const auto *storedIndex = indexMap.getIndex(); + const auto *storedVal = indexMap.getValue(); baseExpr = new IR::Mux(baseExpr->type, new IR::Equ(storedIndex, index), storedVal, baseExpr); } return baseExpr; } -const IR::Constant *Bmv2RegisterValue::getEvaluatedValue() const { +const IR::Constant *IndexMap::getEvaluatedInitialValue() const { const auto *constant = initialValue->to(); BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", getObjectName()); return constant; } -const Bmv2RegisterValue *Bmv2RegisterValue::evaluate(const Model &model) const { - const auto *evaluatedValue = model.evaluate(initialValue); - auto *evaluatedRegisterValue = new Bmv2RegisterValue(evaluatedValue); +/* ========================================================================================= + * Bmv2V1ModelMeterValue + * ========================================================================================= */ + +Bmv2V1ModelRegisterValue::Bmv2V1ModelRegisterValue(const IR::Expression *initialValue) + : IndexMap(initialValue) {} + +cstring Bmv2V1ModelRegisterValue::getObjectName() const { return "Bmv2V1ModelRegisterValue"; } + +const Bmv2V1ModelRegisterValue *Bmv2V1ModelRegisterValue::evaluate(const Model &model) const { + const auto *evaluatedValue = model.evaluate(getInitialValue()); + auto *evaluatedRegisterValue = new Bmv2V1ModelRegisterValue(evaluatedValue); const std::vector evaluatedConditions; - for (const auto &cond : registerConditions) { - evaluatedRegisterValue->addRegisterCondition(*cond.evaluate(model)); + for (const auto &cond : indexConditions) { + const auto *evaluatedCond = cond.evaluate(model); + evaluatedRegisterValue->writeToIndex(evaluatedCond->getEvaluatedIndex(), + evaluatedCond->getEvaluatedValue()); } return evaluatedRegisterValue; } -Bmv2RegisterCondition::Bmv2RegisterCondition(const IR::Expression *index, - const IR::Expression *value) - : index(index), value(value) {} +/* ========================================================================================= + * Bmv2V1ModelMeterValue + * ========================================================================================= */ -const IR::Constant *Bmv2RegisterCondition::getEvaluatedValue() const { - const auto *constant = value->to(); - BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", - getObjectName()); - return constant; -} +Bmv2V1ModelMeterValue::Bmv2V1ModelMeterValue(const IR::Expression *initialValue, bool isDirect) + : IndexMap(initialValue), isDirect(isDirect) {} -const IR::Constant *Bmv2RegisterCondition::getEvaluatedIndex() const { - const auto *constant = index->to(); - BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", - getObjectName()); - return constant; -} +cstring Bmv2V1ModelMeterValue::getObjectName() const { return "Bmv2V1ModelMeterValue"; } -const Bmv2RegisterCondition *Bmv2RegisterCondition::evaluate(const Model &model) const { - const auto *evaluatedIndex = model.evaluate(index); - const auto *evaluatedValue = model.evaluate(value); - return new Bmv2RegisterCondition(evaluatedIndex, evaluatedValue); +const Bmv2V1ModelMeterValue *Bmv2V1ModelMeterValue::evaluate(const Model &model) const { + const auto *evaluatedValue = model.evaluate(getInitialValue()); + auto *evaluatedMeterValue = new Bmv2V1ModelMeterValue(evaluatedValue, isDirect); + const std::vector evaluatedConditions; + for (const auto &cond : indexConditions) { + const auto *evaluatedCond = cond.evaluate(model); + evaluatedMeterValue->writeToIndex(evaluatedCond->getEvaluatedIndex(), + evaluatedCond->getEvaluatedValue()); + } + return evaluatedMeterValue; } -cstring Bmv2RegisterCondition::getObjectName() const { return "Bmv2RegisterCondition"; } +bool Bmv2V1ModelMeterValue::isDirectMeter() const { return isDirect; } /* ========================================================================================= * Bmv2_V1ModelActionProfile diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h index 1e94802a7ac..27ac04ce1ff 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h @@ -16,68 +16,112 @@ namespace P4Tools::P4Testgen::Bmv2 { /* ========================================================================================= - * Bmv2Register + * IndexExpression * ========================================================================================= */ - -class Bmv2RegisterCondition : public TestObject { - public: - /// The register index. +/// Associates an expression with a particular index. +/// This object is used by all extern object that depend on a particular index to retrieve a value. +/// Examples are registers, meters, or counters. +class IndexExpression : public TestObject { + private: + /// The index of the expression. const IR::Expression *index; - /// The register value. + /// The value of the expression. const IR::Expression *value; - explicit Bmv2RegisterCondition(const IR::Expression *index, const IR::Expression *value); + public: + explicit IndexExpression(const IR::Expression *index, const IR::Expression *value); - /// @returns the evaluated register index. This means it must be a constant. + /// @returns the evaluated expression index. This means it must be a constant. /// The function will throw a bug if this is not the case. [[nodiscard]] const IR::Constant *getEvaluatedIndex() const; - /// @returns the evaluated condition of the register. This means it must be a constant. + /// @returns the evaluated condition of the expression. This means it must be a constant. /// The function will throw a bug if this is not the case. [[nodiscard]] const IR::Constant *getEvaluatedValue() const; - [[nodiscard]] const Bmv2RegisterCondition *evaluate(const Model &model) const override; + /// @returns the index stored in the index expression. + [[nodiscard]] const IR::Expression *getIndex() const; + + /// @returns the value stored in the index expression. + [[nodiscard]] const IR::Expression *getValue() const; + + [[nodiscard]] const IndexExpression *evaluate(const Model &model) const override; [[nodiscard]] cstring getObjectName() const override; }; -/// This object tracks the list of writes that have been performed to a particular register. The -/// registerConditionList represents the pair of the index that was written, and the value that -/// was written to this index. When reading from a register, we can furl this list starting -/// from the first index into a set of nested Mux expressions (e.g., "readIndex == savedIndex ? -/// savedValue : defaultValue", where defaultValue may be another Mux expression). If the read index -/// matches with the index that was saved in this tuple, we return the value, otherwise we unroll -/// the nested MUX expressions. This implicitly handles overwrites too, as the latest writes to a -/// particular index appear the earliest in this unraveling phase.. -class Bmv2RegisterValue : public TestObject { - private: - /// A new Bmv2RegisterValue always requires an initial value. This can be a constant or taint. +/* ========================================================================================= + * IndexMap + * ========================================================================================= */ +/// Readable and writable symbolic map, which maps indices to particular values. +class IndexMap : public TestObject { + protected: + /// A new IndexMap always requires an initial value. This can be a constant or taint. const IR::Expression *initialValue; /// Each element is an API name paired with a match rule. - std::vector registerConditions; + std::vector indexConditions; public: - explicit Bmv2RegisterValue(const IR::Expression *initialValue); + explicit IndexMap(const IR::Expression *initialValue); - [[nodiscard]] cstring getObjectName() const override; - - /// Each element is an API name paired with a match rule. - void addRegisterCondition(Bmv2RegisterCondition cond); + /// Write @param value to @param index. + void writeToIndex(const IR::Expression *index, const IR::Expression *value); /// @returns the value with which this register has been initialized. [[nodiscard]] const IR::Expression *getInitialValue() const; /// @returns the current value of this register after writes have been performed according to a /// provided index. - const IR::Expression *getCurrentValue(const IR::Expression *index) const; + [[nodiscard]] const IR::Expression *getValueAtIndex(const IR::Expression *index) const; /// @returns the evaluated register value. This means it must be a constant. /// The function will throw a bug if this is not the case. - [[nodiscard]] const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedInitialValue() const; + + /// Return the "writes" to this index map as a + [[nodiscard]] std::map> unravelMap() const; +}; + +/* ========================================================================================= + * Bmv2V1ModelRegisterValue + * ========================================================================================= */ +/// This object tracks the list of writes that have been performed to a particular register. The +/// registerConditionList represents the pair of the index that was written, and the value that +/// was written to this index. When reading from a register, we can furl this list starting +/// from the first index into a set of nested Mux expressions (e.g., "readIndex == savedIndex ? +/// savedValue : defaultValue", where defaultValue may be another Mux expression). If the read +/// index matches with the index that was saved in this tuple, we return the value, otherwise we +/// unroll the nested MUX expressions. This implicitly handles overwrites too, as the latest +/// writes to a particular index appear the earliest in this unraveling phase.. +class Bmv2V1ModelRegisterValue : public IndexMap { + public: + explicit Bmv2V1ModelRegisterValue(const IR::Expression *initialValue); - [[nodiscard]] const Bmv2RegisterValue *evaluate(const Model &model) const override; + [[nodiscard]] cstring getObjectName() const override; + + [[nodiscard]] const Bmv2V1ModelRegisterValue *evaluate(const Model &model) const override; +}; + +/* ========================================================================================= + * Bmv2V1ModelMeterValue + * ========================================================================================= */ + +class Bmv2V1ModelMeterValue : public IndexMap { + private: + /// Whether the meter is a direct meter. + bool isDirect; + + public: + explicit Bmv2V1ModelMeterValue(const IR::Expression *initialValue, bool isDirect); + + [[nodiscard]] cstring getObjectName() const override; + + [[nodiscard]] const Bmv2V1ModelMeterValue *evaluate(const Model &model) const override; + + /// @returns whether the meter associated with this meter value object is a direct meter. + [[nodiscard]] bool isDirectMeter() const; }; /* ========================================================================================= @@ -148,8 +192,8 @@ class Bmv2_CloneInfo : public TestObject { /// The cloned packet will be emitted on this port. const IR::Expression *clonePort; - /// Whether this clone information is associated with the cloned packet (true) or the regular - /// packet (false). + /// Whether this clone information is associated with the cloned packet (true) or the + /// regular packet (false). bool isClone; public: @@ -166,7 +210,8 @@ class Bmv2_CloneInfo : public TestObject { /// @returns the clone port expression. [[nodiscard]] const IR::Expression *getClonePort() const; - /// @returns information whether we are dealing with the packet clone or the real output packet. + /// @returns information whether we are dealing with the packet clone or the real output + /// packet. [[nodiscard]] bool isClonedPacket() const; /// @returns the evaluated clone port. This means it must be a constant. @@ -243,8 +288,8 @@ class Range : public TableMatch { cstring getObjectName() const override; - /// @returns the inclusive start of the range. It is expected to be a constant at this point. - /// A BUG is thrown otherwise. + /// @returns the inclusive start of the range. It is expected to be a constant at this + /// point. A BUG is thrown otherwise. const IR::Constant *getEvaluatedLow() const; /// @returns the inclusive end of the range. It is expected to be a constant at this point. diff --git a/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp b/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp index b8b500b8b98..26a5f02e427 100644 --- a/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp +++ b/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp @@ -64,7 +64,7 @@ inja::json Metadata::getVerify(const TestSpec *testSpec) { inja::json verifyData = inja::json::object(); auto egressPacket = testSpec->getEgressPacket(); if (egressPacket.has_value()) { - const auto *const packet = egressPacket.value(); + const auto *packet = egressPacket.value(); verifyData["eg_port"] = packet->getPort(); const auto *payload = packet->getEvaluatedPayload(); const auto *payloadMask = packet->getEvaluatedPayloadMask(); @@ -92,7 +92,7 @@ statement_coverage: {{coverage}} ## endfor # The input packet in hexadecimal. -input_packet: \"{{send.pkt}}\" +input_packet: "{{send.pkt}}" # Parsed headers and their offsets. header_offsets: @@ -103,7 +103,7 @@ input_packet: \"{{send.pkt}}\" # Metadata results after this test has completed. metadata: ## for metadata_field in metadata_fields - {{metadata_field.name}}: [value: \"{{metadata_field.value}}\", offset: {{metadata_field.offset}}] + {{metadata_field.name}}: [value: "{{metadata_field.value}}", offset: {{metadata_field.offset}}] ## endfor )"""); return TEST_CASE;