pw_hil: Add uart_blaster for testing dhalsim uart block Change-Id: I2b0d34674cb925a9e2e0c318582d5e963dd11eea Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/sandbox/+/459065
diff --git a/pw_hil/BUILD.bazel b/pw_hil/BUILD.bazel index 712bd71..0ce42f6 100644 --- a/pw_hil/BUILD.bazel +++ b/pw_hil/BUILD.bazel
@@ -13,6 +13,7 @@ # the License. load("@com_google_protobuf//bazel:py_proto_library.bzl", "py_proto_library") +load("@rules_platform//platform_data:defs.bzl", "platform_data") load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") load("//pw_build:compatibility.bzl", "incompatible_with_mcu") load( @@ -57,50 +58,43 @@ deps = [":hil_proto"], ) -cc_library( - name = "pw_hil", - srcs = ["hil.cc"], - hdrs = ["public/pw_hil/hil.h"], - strip_include_prefix = "public", - visibility = ["//visibility:public"], - deps = [ - ":hil_nanopb_rpc", - "//pw_allocator", - "//pw_async2", - "//pw_async2:time_provider", - "//pw_chrono:system_clock", - "//pw_containers:vector", - "//pw_log", - "//pw_status", - "//pw_string:format", - "//pw_string:string", - "//pw_sync:mutex", - "@pico-sdk//src/rp2_common/hardware_gpio", - ], -) - pw_cc_test( - name = "hil_test", - srcs = ["hil_test.cc"], - visibility = ["//visibility:public"], + name = "uart_test", + srcs = ["uart_test.cc"], deps = [ - ":pw_hil", - "//pw_async2", - "//pw_async2:simulated_time_provider", - "//pw_async2:testing", + "//pw_thread:sleep", + "@pico-sdk//src/rp2_common/hardware_gpio", + "@pico-sdk//src/rp2_common/hardware_uart", ], ) -rp2350_binary( - name = "hil_test.elf", - testonly = True, - binary = ":hil_test", +cc_binary( + name = "uart_blaster", + srcs = [ + "rp2xxx_main.cc", + "uart_blaster.cc", + ], + deps = [ + "//pw_allocator:best_fit", + "//pw_async2:system_time_provider", + "//pw_channel:rp2_stdio_channel", + "//pw_multibuf", + "//pw_system:async", + "//pw_thread:sleep", + "@pico-sdk//src/rp2_common/hardware_gpio", + "@pico-sdk//src/rp2_common/hardware_uart", + ], +) + +platform_data( + name = "pigweed_hil_board_uart_blaster", + platform = "//targets/rp2040:pigweed_hil_board", + target = ":uart_blaster", ) flash_rp2350( - name = "flash_hil_test", - testonly = True, - rp2350_binary = ":hil_test.elf", + name = "flash_uart_blaster", + rp2350_binary = ":pigweed_hil_board_uart_blaster", ) sphinx_docs_library(
diff --git a/pw_hil/dhalsim/BUILD.bazel b/pw_hil/dhalsim/BUILD.bazel index 071769d..01433d8 100644 --- a/pw_hil/dhalsim/BUILD.bazel +++ b/pw_hil/dhalsim/BUILD.bazel
@@ -89,6 +89,7 @@ "-v", "-b", "115200", + "--no-device-tracing", "--token-databases", "$(rootpath :pigweed_hil_board_dhalsim)", "--config-file",
diff --git a/pw_hil/dhalsim/dhalsim_app.cc b/pw_hil/dhalsim/dhalsim_app.cc index ac460af..d464512 100644 --- a/pw_hil/dhalsim/dhalsim_app.cc +++ b/pw_hil/dhalsim/dhalsim_app.cc
@@ -33,21 +33,13 @@ event_buffer, log_buffer, pw::chrono::VirtualSystemClock::RealClock()); static DhalsimService dhalsim_service; -// inline constexpr ThreadPriority kThreadPriorityBlinky = ThreadPriority(); - -// inline constexpr pw::ThreadAttrs kBlinkyThread = -// pw::ThreadAttrs() -// .set_stack_size_bytes(1024) -// .set_name("BlinkyThread") -// .set_priority(kThreadPriorityBlinky); - class BlinkyTask : public pw::async2::Task { public: BlinkyTask(pw::async2::TimeProvider<chrono::SystemClock>& time_provider) : time_provider_(time_provider) { - gpio_init(26); - gpio_set_dir(26, GPIO_OUT); - gpio_put(26, 0); + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + gpio_put(LED_PIN, 0); } protected: @@ -59,11 +51,12 @@ } PW_AWAIT(time_future_, cx); - gpio_put(26, !gpio_get(26)); + gpio_put(LED_PIN, !gpio_get(LED_PIN)); } } private: + static constexpr uint32_t LED_PIN = 26; chrono::SystemClock::duration period_ = std::chrono::milliseconds(500); async2::TimeProvider<chrono::SystemClock>& time_provider_; async2::TimeFuture<chrono::SystemClock> time_future_; @@ -80,23 +73,12 @@ pw::System().allocator(), pw::hil::event_allocator); pw::System().rpc_server().RegisterService(pw::hil::dhalsim_service); + auto service_id = pw::hil::dhalsim_service.service_id(); + PW_LOG_WARN("DhalsimService: %08x", + *reinterpret_cast<uint32_t*>(&service_id)); static pw::hil::BlinkyTask blinky(pw::async2::GetSystemTimeProvider()); pw::System().dispatcher().Post(blinky); - - // PW_CONSTINIT static ThreadContextFor<::pw::hil::kBlinkyThread> - // blinky_thread; Thread(blinky_thread, []() { - // gpio_init(24); - // gpio_set_dir(24, GPIO_OUT); - // gpio_put(24, 0); - // while (true) { - // gpio_put(24, 1); - // pw::this_thread::sleep_for(std::chrono::milliseconds(500)); - // gpio_put(24, 0); - // pw::this_thread::sleep_for(std::chrono::milliseconds(500)); - // // PW_LOG_INFO("blink"); - // } - // }).detach(); } } // namespace pw::system
diff --git a/pw_hil/hil.cc b/pw_hil/hil.cc deleted file mode 100644 index 82989fe..0000000 --- a/pw_hil/hil.cc +++ /dev/null
@@ -1,274 +0,0 @@ -// Copyright 2026 The Pigweed Authors -// -// 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 -// -// https://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. - -#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO - -#include "pw_hil/hil.h" - -#include <algorithm> -#include <cstdio> -#include <cstring> - -#include "public/pw_hil/hil.h" -#include "pw_log/log.h" - -namespace pw::hil { - -static EventStream* g_event_stream = nullptr; - -void LogGlobalEvent(uint64_t signal_id, const Value& value) { - if (g_event_stream != nullptr) { - g_event_stream->Log(signal_id, value); - } -} - -void HilTesterService::SetGlobalEventStream(EventStream* stream) { - g_event_stream = stream; -} - -constexpr uint64_t Fnv1aHash(std::string_view str) { - uint64_t hash = 14695981039346656037ULL; - for (char c : str) { - hash ^= static_cast<uint64_t>(c); - hash *= 1099511628211ULL; - } - return hash; -} - -uint64_t GetSlotOrSignalId(uint64_t owner_id, std::string_view name) { - pw::InlineString<64> str; - (void)pw::string::Format( - str, "%llu_%s", static_cast<unsigned long long>(owner_id), name.data()); - return Fnv1aHash(str); -} - -pw::Status HilTesterService::CreateBlocks( - const pw_hil_CreateBlocksRequest& request, pw_hil_Empty&) { - PW_LOG_INFO("CreateBlocks: count=%d", request.blocks_count); - for (size_t i = 0; i < request.blocks_count; ++i) { - const auto& b = request.blocks[i]; - pw::UniquePtr<Block> block; - - switch (b.type) { - case pw_hil_BlockType_BLOCK_TYPE_UNSPECIFIED: - PW_LOG_WARN("CreateBlocks: Invalid Block Type"); - return pw::Status::InvalidArgument(); - case pw_hil_BlockType_BLOCK_TYPE_TIMER: { - uint64_t tick_id = GetSlotOrSignalId(b.id, "tick"); - block = allocator_.MakeUnique<TimerBlock>( - b.id, - b.name, - std::chrono::milliseconds(b.config.timer_period_ms), - time_provider_, - tick_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_COUNTER: { - PW_LOG_INFO("CreateBlocks: Counter"); - uint64_t inc_id = GetSlotOrSignalId(b.id, "increment"); - uint64_t dec_id = GetSlotOrSignalId(b.id, "decrement"); - uint64_t change_id = GetSlotOrSignalId(b.id, "change"); - block = allocator_.MakeUnique<CounterBlock>( - b.id, b.name, inc_id, dec_id, change_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_MODULO: { - PW_LOG_INFO("CreateBlocks: Modulo"); - uint64_t input_id = GetSlotOrSignalId(b.id, "input"); - uint64_t output_id = GetSlotOrSignalId(b.id, "output"); - block = allocator_.MakeUnique<ModuloBlock>( - b.id, b.name, b.config.modulo_divisor, input_id, output_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_EQUAL: { - PW_LOG_INFO("CreateBlocks: Equal"); - uint64_t input_id = GetSlotOrSignalId(b.id, "input"); - uint64_t true_id = GetSlotOrSignalId(b.id, "true"); - uint64_t false_id = GetSlotOrSignalId(b.id, "false"); - uint64_t output_id = GetSlotOrSignalId(b.id, "output"); - block = allocator_.MakeUnique<EqualBlock>(b.id, - b.name, - b.config.equal_value, - input_id, - true_id, - false_id, - output_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_AND: { - PW_LOG_INFO("CreateBlocks: And"); - uint64_t output_id = GetSlotOrSignalId(b.id, "output"); - block = allocator_.MakeUnique<AndBlock>( - b.id, b.name, output_id, allocator_); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_NOT: { - PW_LOG_INFO("CreateBlocks: Not"); - uint64_t input_id = GetSlotOrSignalId(b.id, "input"); - uint64_t output_id = GetSlotOrSignalId(b.id, "output"); - block = allocator_.MakeUnique<NotBlock>( - b.id, b.name, input_id, output_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_STRING_CONSTANT: { - PW_LOG_INFO("CreateBlocks: StringConstant"); - uint64_t input_id = GetSlotOrSignalId(b.id, "input"); - uint64_t output_id = GetSlotOrSignalId(b.id, "output"); - block = allocator_.MakeUnique<StringConstantBlock>( - b.id, b.name, b.config.string_constant, input_id, output_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_PRINT: { - PW_LOG_INFO("CreateBlocks: Print"); - uint64_t print_id = GetSlotOrSignalId(b.id, "print"); - uint64_t printed_id = GetSlotOrSignalId(b.id, "printed"); - block = allocator_.MakeUnique<PrintBlock>( - b.id, b.name, print_id, printed_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_GPIO: { - PW_LOG_INFO("CreateBlocks: Gpio"); - uint64_t toggle_id = GetSlotOrSignalId(b.id, "toggle"); - uint64_t change_id = GetSlotOrSignalId(b.id, "change"); - block = allocator_.MakeUnique<GpioBlock>(b.id, - b.name, - b.config.gpio.pin, - b.config.gpio.dir, - toggle_id, - change_id); - break; - } - case pw_hil_BlockType_BLOCK_TYPE_END_TEST: { - PW_LOG_INFO("CreateBlocks: EndTest"); - uint64_t input_id = GetSlotOrSignalId(b.id, "input"); - block = allocator_.MakeUnique<EndTestBlock>( - b.id, b.name, input_id, [this]() { - for (auto& blk : blocks_) { - blk->Disable(); - } - }); - break; - } - - default: - return pw::Status::InvalidArgument(); - } - - if (block == nullptr) { - PW_LOG_ERROR("CreateBlocks: Failed to allocate block"); - return pw::Status::ResourceExhausted(); - } - - blocks_.push_back(std::move(block)); - dispatcher_.Post(*blocks_.back()); - } - return pw::OkStatus(); -} - -pw::Status HilTesterService::CreateConnections( - const pw_hil_CreateConnectionsRequest& request, pw_hil_Empty&) { - for (size_t i = 0; i < request.connections_count; ++i) { - const auto& conn = request.connections[i]; - Signal* signal = nullptr; - Slot* slot = nullptr; - - for (auto& block : blocks_) { - signal = block->GetSignal(conn.signal_id); - if (signal != nullptr) { - break; - } - } - - for (auto& block : blocks_) { - slot = block->GetOrCreateSlot(conn.slot_id); - if (slot != nullptr) { - break; - } - } - - if (signal == nullptr || slot == nullptr) { - return pw::Status::NotFound(); - } - - signal->Connect(slot); - } - return pw::OkStatus(); -} - -pw::Status HilTesterService::Start(const pw_hil_Empty&, pw_hil_Empty&) { - running_ = true; - for (auto& block : blocks_) { - block->Enable(); - block->Wake(); - } - return pw::OkStatus(); -} - -pw::Status HilTesterService::GetEvents(const pw_hil_Empty&, - pw_hil_EventsResponse& response) { - size_t i = 0; - response.events_count = 0; - - event_stream_.ReadEvents([&response, &i](const Event& event) { - if (i >= 128) { - return; - } - auto& res_evt = response.events[i++]; - res_evt.signal_id = event.signal_id; - std::visit( - [&res_evt](const auto& v) { - using T = std::decay_t<decltype(v)>; - if constexpr (std::is_same_v<T, std::monostate>) { - res_evt.which_value = pw_hil_Event_null_value_tag; - res_evt.value.null_value = true; - } else if constexpr (std::is_same_v<T, bool>) { - res_evt.which_value = pw_hil_Event_bool_value_tag; - res_evt.value.bool_value = v; - } else if constexpr (std::is_same_v<T, int64_t>) { - res_evt.which_value = pw_hil_Event_int_value_tag; - res_evt.value.int_value = v; - } else if constexpr (std::is_same_v<T, double>) { - res_evt.which_value = pw_hil_Event_float_value_tag; - res_evt.value.float_value = static_cast<float>(v); - } else if constexpr (std::is_same_v<T, pw::InlineString<64>>) { - res_evt.which_value = pw_hil_Event_string_value_tag; - snprintf(res_evt.value.string_value, - sizeof(res_evt.value.string_value), - "%s", - v.c_str()); - } - }, - event.value); - }); - response.events_count = static_cast<pb_size_t>(i); - return pw::OkStatus(); -} - -pw::Status HilTesterService::Reset(const pw_hil_Empty&, pw_hil_Empty&) { - ResetService(); - return pw::OkStatus(); -} - -void HilTesterService::ResetService(bool clear_events) { - running_ = false; - for (auto& block : blocks_) { - block->Deregister(); - } - blocks_.clear(); - if (clear_events) { - event_stream_.Clear(); - } -} - -} // namespace pw::hil
diff --git a/pw_hil/hil_test.cc b/pw_hil/hil_test.cc deleted file mode 100644 index 1ef35e5..0000000 --- a/pw_hil/hil_test.cc +++ /dev/null
@@ -1,380 +0,0 @@ -// Copyright 2026 The Pigweed Authors -// -// 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 -// -// https://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. - -#include "pw_hil/hil.h" - -#include <chrono> - -#include "public/pw_hil/hil.h" -#include "pw_async2/dispatcher.h" -#include "pw_async2/dispatcher_for_test.h" -#include "pw_async2/simulated_time_provider.h" -#include "pw_unit_test/framework.h" - -namespace pw::hil { -namespace { - -using namespace std::chrono_literals; - -class TestSlot : public Slot { - public: - TestSlot(uint64_t id, Block& owner) : Slot(id, owner), calls_(0) {} - - void Trigger(const Value& value) override { - calls_++; - last_value_ = value; - } - - int calls() const { return calls_; } - const Value& last_value() const { return last_value_; } - - void Reset() { calls_ = 0; } - - private: - int calls_; - Value last_value_; -}; - -class DummyBlock : public Block { - public: - DummyBlock() : Block(999, "dummy") {} - Slot* GetOrCreateSlot(uint64_t) override { return nullptr; } - Signal* GetSignal(uint64_t) override { return nullptr; } - - protected: - async2::Poll<> DoPend(async2::Context&) override { return async2::Pending(); } -}; - -TEST(TimerBlock, Ticks) { - async2::DispatcherForTest dispatcher; - async2::SimulatedTimeProvider<chrono::SystemClock> time_provider; - - TimerBlock timer(1, "timer", 10ms, time_provider, 100); - DummyBlock dummy; - TestSlot slot(200, dummy); - timer.GetSignal(100)->Connect(&slot); - - timer.Enable(); - dispatcher.Post(timer); - dispatcher.RunUntilStalled(); - - EXPECT_EQ(slot.calls(), 0); - - // Advance to first tick - EXPECT_TRUE(time_provider.AdvanceUntilNextExpiration()); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - - // Advance to second tick - EXPECT_TRUE(time_provider.AdvanceUntilNextExpiration()); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 2); - - timer.Deregister(); -} - -TEST(CounterBlock, IncrementsAndDecrements) { - async2::DispatcherForTest dispatcher; - CounterBlock counter(1, "counter", 100, 200, 300); - - DummyBlock dummy; - TestSlot slot(400, dummy); - counter.GetSignal(300)->Connect(&slot); - - counter.Enable(); - dispatcher.Post(counter); - dispatcher.RunUntilStalled(); - - EXPECT_EQ(slot.calls(), 0); - - // Trigger increment - counter.GetOrCreateSlot(100)->Trigger(std::monostate{}); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<int64_t>(slot.last_value()), 1); - - // Trigger decrement - counter.GetOrCreateSlot(200)->Trigger(std::monostate{}); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 2); - EXPECT_EQ(std::get<int64_t>(slot.last_value()), 0); - - counter.Deregister(); -} - -TEST(ModuloBlock, ComputesModulo) { - async2::DispatcherForTest dispatcher; - ModuloBlock modulo(1, "modulo", 3, 100, 200); - - DummyBlock dummy; - TestSlot slot(300, dummy); - modulo.GetSignal(200)->Connect(&slot); - - modulo.Enable(); - dispatcher.Post(modulo); - - modulo.GetOrCreateSlot(100)->Trigger(int64_t(7)); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<int64_t>(slot.last_value()), 1); // 7 % 3 = 1 - - modulo.GetOrCreateSlot(100)->Trigger(int64_t(9)); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 2); - EXPECT_EQ(std::get<int64_t>(slot.last_value()), 0); // 9 % 3 = 0 - - modulo.Deregister(); -} - -TEST(EqualBlock, ComparesEquality) { - async2::DispatcherForTest dispatcher; - EqualBlock eq(1, "eq", 5, 100, 200, 300, 400); - - DummyBlock dummy; - TestSlot slot_true(500, dummy); - TestSlot slot_false(600, dummy); - TestSlot slot_output(700, dummy); - - eq.GetSignal(200)->Connect(&slot_true); - eq.GetSignal(300)->Connect(&slot_false); - eq.GetSignal(400)->Connect(&slot_output); - - eq.Enable(); - dispatcher.Post(eq); - - // Trigger unequal - eq.GetOrCreateSlot(100)->Trigger(int64_t(4)); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot_true.calls(), 0); - EXPECT_EQ(slot_false.calls(), 1); - EXPECT_EQ(slot_output.calls(), 1); - EXPECT_EQ(std::get<bool>(slot_output.last_value()), false); - - // Trigger equal - eq.GetOrCreateSlot(100)->Trigger(int64_t(5)); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot_true.calls(), 1); - EXPECT_EQ(slot_false.calls(), 1); - EXPECT_EQ(slot_output.calls(), 2); - EXPECT_EQ(std::get<bool>(slot_output.last_value()), true); - - eq.Deregister(); -} - -TEST(AndBlock, LatchingLogic) { - async2::DispatcherForTest dispatcher; - AndBlock and_block(1, "and", 300); - - DummyBlock dummy; - TestSlot slot(400, dummy); - and_block.GetSignal(300)->Connect(&slot); - - // Connect 2 input slots - Slot* in0 = and_block.GetOrCreateSlot(100); - Slot* in1 = and_block.GetOrCreateSlot(200); - - and_block.Enable(); - dispatcher.Post(and_block); - - // Trigger in0 true. Not all are true yet. - in0->Trigger(true); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<bool>(slot.last_value()), false); - - // Trigger in1 true. All are true. - in1->Trigger(true); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 2); - EXPECT_EQ(std::get<bool>(slot.last_value()), true); - - // Trigger in0 false. Not all true. - in0->Trigger(false); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 3); - EXPECT_EQ(std::get<bool>(slot.last_value()), false); - - and_block.Deregister(); -} - -TEST(NotBlock, InvertsInput) { - async2::DispatcherForTest dispatcher; - NotBlock not_block(1, "not", 100, 200); - - DummyBlock dummy; - TestSlot slot(300, dummy); - not_block.GetSignal(200)->Connect(&slot); - - not_block.Enable(); - dispatcher.Post(not_block); - - not_block.GetOrCreateSlot(100)->Trigger(true); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<bool>(slot.last_value()), false); - - not_block.GetOrCreateSlot(100)->Trigger(false); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 2); - EXPECT_EQ(std::get<bool>(slot.last_value()), true); - - not_block.Deregister(); -} - -TEST(StringConstantBlock, EmitsConstantString) { - async2::DispatcherForTest dispatcher; - StringConstantBlock sc(1, "sc", "hello", 100, 200); - - DummyBlock dummy; - TestSlot slot(300, dummy); - sc.GetSignal(200)->Connect(&slot); - - sc.Enable(); - dispatcher.Post(sc); - - sc.GetOrCreateSlot(100)->Trigger(std::monostate{}); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<pw::InlineString<64>>(slot.last_value()), "hello"); - - sc.Deregister(); -} - -TEST(PrintBlock, PrintsAndEmitsString) { - async2::DispatcherForTest dispatcher; - PrintBlock pb(1, "print", 100, 200); - - DummyBlock dummy; - TestSlot slot(300, dummy); - pb.GetSignal(200)->Connect(&slot); - - pb.Enable(); - dispatcher.Post(pb); - - pb.GetOrCreateSlot(100)->Trigger(pw::InlineString<64>("test-output")); - dispatcher.RunUntilStalled(); - EXPECT_EQ(slot.calls(), 1); - EXPECT_EQ(std::get<pw::InlineString<64>>(slot.last_value()), "test-output"); - - pb.Deregister(); -} - -TEST(EndTestBlock, CallbackDisables) { - async2::DispatcherForTest dispatcher; - bool ended = false; - EndTestBlock end_test(1, "end", 100, [&ended]() { ended = true; }); - - end_test.Enable(); - dispatcher.Post(end_test); - - end_test.GetOrCreateSlot(100)->Trigger(std::monostate{}); - dispatcher.RunUntilStalled(); - EXPECT_TRUE(ended); - - end_test.Deregister(); -} - -TEST(HilTesterService, CompleteRPCWorkflow) { - async2::DispatcherForTest dispatcher; - async2::SimulatedTimeProvider<chrono::SystemClock> time_provider; - HilTesterService service(dispatcher, time_provider); - - // 1. Create Blocks - pw_hil_CreateBlocksRequest create_blocks_req = {}; - create_blocks_req.blocks_count = 3; - - // Timer Block (ID 1) - create_blocks_req.blocks[0].id = 1; - create_blocks_req.blocks[0].type = pw_hil_BlockType_BLOCK_TYPE_TIMER; - snprintf(create_blocks_req.blocks[0].name, - sizeof(create_blocks_req.blocks[0].name), - "timer"); - create_blocks_req.blocks[0].which_config = - pw_hil_BlockConfig_timer_period_ms_tag; - create_blocks_req.blocks[0].config.timer_period_ms = 10; - - // Counter Block (ID 2) - create_blocks_req.blocks[1].id = 2; - create_blocks_req.blocks[1].type = pw_hil_BlockType_BLOCK_TYPE_COUNTER; - snprintf(create_blocks_req.blocks[1].name, - sizeof(create_blocks_req.blocks[1].name), - "counter"); - - // EndTest Block (ID 3) - create_blocks_req.blocks[2].id = 3; - create_blocks_req.blocks[2].type = pw_hil_BlockType_BLOCK_TYPE_END_TEST; - snprintf(create_blocks_req.blocks[2].name, - sizeof(create_blocks_req.blocks[2].name), - "end"); - - pw_hil_Empty empty_resp = {}; - EXPECT_EQ(service.CreateBlocks(create_blocks_req, empty_resp), - pw::OkStatus()); - EXPECT_EQ(service.blocks().size(), 3u); - - // Get standard generated IDs - uint64_t tick_id = GetSlotOrSignalId(1, "tick"); - uint64_t inc_id = GetSlotOrSignalId(2, "increment"); - uint64_t change_id = GetSlotOrSignalId(2, "change"); - uint64_t end_slot_id = GetSlotOrSignalId(3, "input"); - - // 2. Connect: Timer.tick -> Counter.increment, Counter.change -> - // EndTest.input - pw_hil_CreateConnectionsRequest conn_req = {}; - conn_req.connections_count = 2; - conn_req.connections[0].signal_id = tick_id; - conn_req.connections[0].slot_id = inc_id; - conn_req.connections[1].signal_id = change_id; - conn_req.connections[1].slot_id = end_slot_id; - - EXPECT_EQ(service.CreateConnections(conn_req, empty_resp), pw::OkStatus()); - - // 3. Start - pw_hil_Empty empty_req = {}; - EXPECT_EQ(service.Start(empty_req, empty_resp), pw::OkStatus()); - - // Run. Time = 0 - dispatcher.RunUntilStalled(); - - // Advance time to first tick (10ms) -> triggers Timer tick -> triggers - // Counter inc -> triggers Counter change -> triggers EndTest - EXPECT_TRUE(time_provider.AdvanceUntilNextExpiration()); - dispatcher.RunUntilStalled(); - - // EndTest disables all blocks. We then call ResetService(false) to safely - // clean up blocks but preserve events. - service.ResetService(false); - - EXPECT_EQ(service.blocks().size(), 0u); - - // 4. Retrieve logged events - pw_hil_EventsResponse events_resp = {}; - EXPECT_EQ(service.GetEvents(empty_req, events_resp), pw::OkStatus()); - - // Expected events: - // 1. Timer tick (ID = tick_id) - // 2. Counter change (ID = change_id, value = 1) - EXPECT_EQ(events_resp.events_count, 2u); - EXPECT_EQ(events_resp.events[0].signal_id, tick_id); - EXPECT_EQ(events_resp.events[0].which_value, pw_hil_Event_null_value_tag); - - EXPECT_EQ(events_resp.events[1].signal_id, change_id); - EXPECT_EQ(events_resp.events[1].which_value, pw_hil_Event_int_value_tag); - EXPECT_EQ(events_resp.events[1].value.int_value, 1); - - service.ResetService(); -} - -} // namespace -} // namespace pw::hil
diff --git a/pw_hil/public/pw_hil/hil.h b/pw_hil/public/pw_hil/hil.h deleted file mode 100644 index 095caa4..0000000 --- a/pw_hil/public/pw_hil/hil.h +++ /dev/null
@@ -1,790 +0,0 @@ -// Copyright 2026 The Pigweed Authors -// -// 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 -// -// https://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. -#pragma once - -#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO - -#include <array> -#include <chrono> -#include <functional> -#include <mutex> -#include <string_view> -#include <variant> -#include <vector> - -#include "hardware/gpio.h" -#include "pw_allocator/allocator.h" -#include "pw_allocator/unique_ptr.h" -#include "pw_async2/dispatcher.h" -#include "pw_async2/task.h" -#include "pw_async2/time_provider.h" -#include "pw_async2/waker.h" -#include "pw_chrono/system_clock.h" -#include "pw_containers/vector.h" -#include "pw_hil/hil.rpc.pb.h" -#include "pw_log/log.h" -#include "pw_status/status.h" -#include "pw_string/format.h" -#include "pw_string/string.h" -#include "pw_sync/mutex.h" - -namespace pw::hil { - -using Value = - std::variant<std::monostate, bool, int64_t, double, pw::InlineString<64>>; - -class Block; - -class Slot { - public: - Slot(uint64_t id, Block& owner) : id_(id), owner_(owner) {} - virtual ~Slot() = default; - - uint64_t id() const { return id_; } - Block& owner() { return owner_; } - - virtual void Trigger(const Value& value) = 0; - - private: - uint64_t id_; - Block& owner_; -}; - -struct Event { - uint64_t signal_id; - Value value; -}; - -class EventStream { - public: - void Log(uint64_t signal_id, const Value& value) { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - events_.push_back({signal_id, value}); - } - - template <typename Func> - void ReadEvents(Func&& func) const { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - for (const auto& event : events_) { - func(event); - } - } - - void Clear() { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - events_.clear(); - } - - private: - mutable pw::sync::Mutex mutex_; - pw::Vector<Event, 128> events_; -}; - -// Global event stream pointer or callback. -void LogGlobalEvent(uint64_t signal_id, const Value& value); - -uint64_t GetSlotOrSignalId(uint64_t owner_id, std::string_view name); - -class Signal { - public: - Signal(uint64_t id) : id_(id) {} - - uint64_t id() const { return id_; } - - void Connect(Slot* slot) { slots_.push_back(slot); } - - void Emit(const Value& value) { - LogGlobalEvent(id_, value); - for (auto* slot : slots_) { - slot->Trigger(value); - } - } - - private: - uint64_t id_; - pw::Vector<Slot*, 32> slots_; -}; - -class Block : public pw::async2::Task { - public: - Block(uint64_t id, std::string_view name) - : id_(id), name_(name), enabled_(false) {} - virtual ~Block() = default; - - uint64_t id() const { return id_; } - std::string_view name() const { return name_; } - - void Enable() { enabled_ = true; } - void Disable() { enabled_ = false; } - bool enabled() const { return enabled_; } - - virtual Slot* GetOrCreateSlot(uint64_t id) = 0; - virtual Signal* GetSignal(uint64_t id) = 0; - - void Wake() { waker_.Wake(); } - - protected: - void StoreWaker(pw::async2::Context& cx) { - PW_ASYNC_STORE_WAKER(cx, waker_, "Block Waker"); - } - - private: - uint64_t id_; - std::string name_; - bool enabled_; - pw::async2::Waker waker_; -}; - -class InputSlot : public Slot { - public: - InputSlot(uint64_t id, Block& owner) : Slot(id, owner) {} - - void Trigger(const Value& value) override { - { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - queue_.push_back(value); - } - owner().Wake(); - } - - bool PopTrigger(Value& out_value) { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - if (queue_.empty()) { - return false; - } - out_value = std::move(queue_.front()); - queue_.erase(queue_.begin()); - return true; - } - - bool has_pending() { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - return !queue_.empty(); - } - - void Clear() { - std::lock_guard<pw::sync::Mutex> lock(mutex_); - queue_.clear(); - } - - private: - pw::sync::Mutex mutex_; - pw::Vector<Value, 64> queue_; -}; - -class TimerBlock : public Block { - public: - TimerBlock(uint64_t id, - std::string_view name, - chrono::SystemClock::duration period, - async2::TimeProvider<chrono::SystemClock>& time_provider, - uint64_t tick_signal_id) - : Block(id, name), - period_(period), - time_provider_(time_provider), - tick_signal_(tick_signal_id) {} - ~TimerBlock() { PW_LOG_INFO("TimerBlock: Dtor"); } - - Slot* GetOrCreateSlot(uint64_t) override { return nullptr; } - Signal* GetSignal(uint64_t id) override { - if (id == tick_signal_.id()) { - return &tick_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - PW_LOG_INFO("TimerBlock: DoPend"); - // if (!enabled()) { - // return async2::Pending(); - // } - - // StoreWaker(cx); - - while (true) { - if (!timer_active_) { - timer_future_ = time_provider_.WaitFor(period_); - timer_active_ = true; - } - - if (timer_future_.Pend(cx).IsPending()) { - return async2::Pending(); - } - - timer_active_ = false; - tick_signal_.Emit(std::monostate{}); - } - } - - private: - chrono::SystemClock::duration period_; - async2::TimeProvider<chrono::SystemClock>& time_provider_; - Signal tick_signal_; - async2::TimeFuture<chrono::SystemClock> timer_future_; - bool timer_active_ = false; -}; - -class CounterBlock : public Block { - public: - CounterBlock(uint64_t id, - std::string_view name, - uint64_t increment_slot_id, - uint64_t decrement_slot_id, - uint64_t change_signal_id) - : Block(id, name), - increment_slot_(increment_slot_id, *this), - decrement_slot_(decrement_slot_id, *this), - change_signal_(change_signal_id), - value_(0) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == increment_slot_.id()) { - return &increment_slot_; - } - if (id == decrement_slot_.id()) { - return &decrement_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == change_signal_.id()) { - return &change_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - bool changed = false; - Value val; - while (increment_slot_.PopTrigger(val)) { - value_++; - changed = true; - } - while (decrement_slot_.PopTrigger(val)) { - value_--; - changed = true; - } - - if (changed) { - change_signal_.Emit(value_); - } - - return async2::Pending(); - } - - private: - InputSlot increment_slot_; - InputSlot decrement_slot_; - Signal change_signal_; - int64_t value_; -}; - -class GpioBlock : public Block { - public: - GpioBlock(uint64_t id, - std::string_view name, - uint64_t pin, - bool is_out, - uint64_t toggle_slot_id, - uint64_t change_signal_id) - : Block(id, name), - toggle_slot_(toggle_slot_id, *this), - change_signal_(change_signal_id), - pin_(pin), - is_out_(is_out) { - gpio_init(pin_); - gpio_set_dir(pin_, is_out_); - if (is_out_) { - gpio_put(pin_, 0); - } - } - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == toggle_slot_.id()) { - return &toggle_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == change_signal_.id()) { - return &change_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - bool changed = false; - Value val; - while (toggle_slot_.PopTrigger(val)) { - gpio_put(pin_, !gpio_get_out_level(pin_)); - changed = true; - } - - if (changed) { - change_signal_.Emit(gpio_get_out_level(pin_)); - } - - return async2::Pending(); - } - - private: - InputSlot toggle_slot_; - Signal change_signal_; - int64_t pin_; - bool is_out_; -}; - -class ModuloBlock : public Block { - public: - ModuloBlock(uint64_t id, - std::string_view name, - int64_t divisor, - uint64_t input_slot_id, - uint64_t output_signal_id) - : Block(id, name), - divisor_(divisor), - input_slot_(input_slot_id, *this), - output_signal_(output_signal_id) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == output_signal_.id()) { - return &output_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - while (input_slot_.PopTrigger(val)) { - if (auto* pval = std::get_if<int64_t>(&val)) { - int64_t result = (*pval) % divisor_; - output_signal_.Emit(result); - } - } - - return async2::Pending(); - } - - private: - int64_t divisor_; - InputSlot input_slot_; - Signal output_signal_; -}; - -class EqualBlock : public Block { - public: - EqualBlock(uint64_t id, - std::string_view name, - int64_t compare_value, - uint64_t input_slot_id, - uint64_t true_signal_id, - uint64_t false_signal_id, - uint64_t output_signal_id) - : Block(id, name), - compare_value_(compare_value), - input_slot_(input_slot_id, *this), - true_signal_(true_signal_id), - false_signal_(false_signal_id), - output_signal_(output_signal_id) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == true_signal_.id()) { - return &true_signal_; - } - if (id == false_signal_.id()) { - return &false_signal_; - } - if (id == output_signal_.id()) { - return &output_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - while (input_slot_.PopTrigger(val)) { - if (auto* pval = std::get_if<int64_t>(&val)) { - bool eq = (*pval == compare_value_); - output_signal_.Emit(eq); - if (eq) { - true_signal_.Emit(std::monostate{}); - } else { - false_signal_.Emit(std::monostate{}); - } - } - } - - return async2::Pending(); - } - - private: - int64_t compare_value_; - InputSlot input_slot_; - Signal true_signal_; - Signal false_signal_; - Signal output_signal_; -}; - -class AndBlock : public Block { - public: - AndBlock(uint64_t id, - std::string_view name, - uint64_t output_signal_id, - pw::Allocator& allocator) - : Block(id, name), - output_signal_(output_signal_id), - allocator_(allocator) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - for (size_t i = 0; i < slots_.size(); ++i) { - if (slots_[i]->id() == id) { - return slots_[i].get(); - } - } - if (slots_.size() < 8) { - auto slot = allocator_.MakeUnique<InputSlot>(id, *this); - if (slot == nullptr) { - return nullptr; - } - slots_.push_back(std::move(slot)); - latched_values_[slots_.size() - 1] = false; - return slots_.back().get(); - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == output_signal_.id()) { - return &output_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - bool trigger_processed = false; - for (size_t i = 0; i < slots_.size(); ++i) { - Value val; - while (slots_[i]->PopTrigger(val)) { - if (auto* pval = std::get_if<bool>(&val)) { - latched_values_[i] = *pval; - trigger_processed = true; - } - } - } - - if (trigger_processed) { - bool all_true = true; - for (size_t i = 0; i < slots_.size(); ++i) { - if (!latched_values_[i]) { - all_true = false; - break; - } - } - output_signal_.Emit(all_true); - } - - return async2::Pending(); - } - - private: - pw::Vector<pw::UniquePtr<InputSlot>, 8> slots_; - - Signal output_signal_; - pw::Allocator& allocator_; - std::array<bool, 8> latched_values_{}; -}; - -class NotBlock : public Block { - public: - NotBlock(uint64_t id, - std::string_view name, - uint64_t input_slot_id, - uint64_t output_signal_id) - : Block(id, name), - input_slot_(input_slot_id, *this), - output_signal_(output_signal_id) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == output_signal_.id()) { - return &output_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - while (input_slot_.PopTrigger(val)) { - if (auto* pval = std::get_if<bool>(&val)) { - output_signal_.Emit(!*pval); - } - } - - return async2::Pending(); - } - - private: - InputSlot input_slot_; - Signal output_signal_; -}; - -class StringConstantBlock : public Block { - public: - StringConstantBlock(uint64_t id, - std::string_view name, - std::string_view constant, - uint64_t input_slot_id, - uint64_t output_signal_id) - : Block(id, name), - constant_(constant), - input_slot_(input_slot_id, *this), - output_signal_(output_signal_id) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == output_signal_.id()) { - return &output_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - while (input_slot_.PopTrigger(val)) { - output_signal_.Emit(constant_); - } - - return async2::Pending(); - } - - private: - pw::InlineString<64> constant_; - InputSlot input_slot_; - Signal output_signal_; -}; - -class PrintBlock : public Block { - public: - PrintBlock(uint64_t id, - std::string_view name, - uint64_t input_slot_id, - uint64_t printed_signal_id) - : Block(id, name), - input_slot_(input_slot_id, *this), - printed_signal_(printed_signal_id) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t id) override { - if (id == printed_signal_.id()) { - return &printed_signal_; - } - return nullptr; - } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - while (input_slot_.PopTrigger(val)) { - pw::InlineString<64> str; - if (auto* pval = std::get_if<pw::InlineString<64>>(&val)) { - str = *pval; - } else if (auto* pb = std::get_if<bool>(&val)) { - str = *pb ? "true" : "false"; - } else if (auto* pi = std::get_if<int64_t>(&val)) { - (void)pw::string::Format(str, "%lld", static_cast<long long>(*pi)); - } else if (auto* pf = std::get_if<double>(&val)) { - (void)pw::string::Format(str, "%f", *pf); - } else { - str = "null"; - } - printf("%s\n", str.c_str()); - printed_signal_.Emit(str); - } - - return async2::Pending(); - } - - private: - InputSlot input_slot_; - Signal printed_signal_; -}; - -class EndTestBlock : public Block { - public: - EndTestBlock(uint64_t id, - std::string_view name, - uint64_t input_slot_id, - std::function<void()> on_end_test) - : Block(id, name), - input_slot_(input_slot_id, *this), - on_end_test_(std::move(on_end_test)) {} - - Slot* GetOrCreateSlot(uint64_t id) override { - if (id == input_slot_.id()) { - return &input_slot_; - } - return nullptr; - } - - Signal* GetSignal(uint64_t) override { return nullptr; } - - protected: - async2::Poll<> DoPend(async2::Context& cx) override { - if (!enabled()) { - return async2::Pending(); - } - - StoreWaker(cx); - - Value val; - if (input_slot_.PopTrigger(val)) { - on_end_test_(); - } - - return async2::Pending(); - } - - private: - InputSlot input_slot_; - std::function<void()> on_end_test_; -}; - -class HilTesterService final - : public pw::hil::pw_rpc::nanopb::HilTester::Service<HilTesterService> { - public: - HilTesterService(async2::Dispatcher& dispatcher, - async2::TimeProvider<chrono::SystemClock>& time_provider, - pw::Allocator& allocator) - : dispatcher_(dispatcher), - time_provider_(time_provider), - allocator_(allocator) { - SetGlobalEventStream(&event_stream_); - } - - ~HilTesterService() { - SetGlobalEventStream(nullptr); - ResetService(); - } - - pw::Status CreateBlocks(const pw_hil_CreateBlocksRequest& request, - pw_hil_Empty& response); - pw::Status CreateConnections(const pw_hil_CreateConnectionsRequest& request, - pw_hil_Empty& response); - pw::Status Start(const pw_hil_Empty& request, pw_hil_Empty& response); - pw::Status GetEvents(const pw_hil_Empty& request, - pw_hil_EventsResponse& response); - pw::Status Reset(const pw_hil_Empty& request, pw_hil_Empty& response); - - void ResetService(bool clear_events = true); - - EventStream& event_stream() { return event_stream_; } - const pw::Vector<pw::UniquePtr<Block>>& blocks() const { return blocks_; } - - static void SetGlobalEventStream(EventStream* stream); - - private: - async2::Dispatcher& dispatcher_; - async2::TimeProvider<chrono::SystemClock>& time_provider_; - pw::Allocator& allocator_; - EventStream event_stream_; - pw::Vector<pw::UniquePtr<Block>, 128> blocks_; - - bool running_ = false; -}; - -} // namespace pw::hil
diff --git a/pw_hil/py/BUILD.bazel b/pw_hil/py/BUILD.bazel index 909e39a..33f53b5 100644 --- a/pw_hil/py/BUILD.bazel +++ b/pw_hil/py/BUILD.bazel
@@ -34,6 +34,7 @@ deps = [ ":dhalsim", "//pw_cli/py:pw_cli", + "//pw_hil/debug_probe/py:debug_probe", "//pw_hil/protos:hil_py_pb2", ], )
diff --git a/pw_hil/py/dhalsim_test.py b/pw_hil/py/dhalsim_test.py index f5f748f..efff7f4 100644 --- a/pw_hil/py/dhalsim_test.py +++ b/pw_hil/py/dhalsim_test.py
@@ -27,46 +27,9 @@ _LOG = logging.getLogger("DhalsimTest") -class PrettyFormatter(logging.Formatter): - """A logging formatter that tunes logging for this script.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._colors = color.colors() - self._color_enabled = color.is_enabled() - - def _gray(self, msg: str) -> str: - if self._color_enabled: - return self._colors.gray(msg) - return msg - - def format(self, record: logging.LogRecord) -> str: - """Formats the log record.""" - message = record.getMessage() - if record.levelno >= logging.ERROR: - level_prefix = f"❌ [{record.name}] " - message = self._colors.red(message) - elif record.levelno >= logging.WARNING: - level_prefix = f"⚠️ [{record.name}] " - message = self._colors.yellow(message) - elif record.levelno == logging.DEBUG: - level_prefix = f"[{record.name}] " - message = self._gray(message) - else: - level_prefix = f"[{record.name}] " - return f"{level_prefix}{message}" - - -def _setup_logging(log_level: int): - handler = logging.StreamHandler() - handler.setFormatter(PrettyFormatter()) - _LOG.addHandler(handler) - _LOG.setLevel(log_level) - - class DhalsimTest(hiltest.TestCase): def __init__(self): - _setup_logging(logging.INFO) + pass def setUp(self): _LOG.debug("DhalsimTest.setUp") @@ -75,12 +38,16 @@ _LOG.debug("DhalsimTest.tearDown") def test_blinky(self): - _LOG.info("Hello World!") + _LOG.info("Testing Blinky") timer_block = dhalsim.create(timer.PeriodicTimer("timer", 1000)) - gpio_block = dhalsim.create(gpio.Gpio("gpio", pin=25, direction=gpio.Direction.OUT)) - uart_block = dhalsim.create(uart.Uart("uart", baud_rate=115200)) + gpio_block = dhalsim.create(gpio.Gpio("gpio", pin=14, direction=gpio.Direction.OUT)) dhalsim.connect(timer_block.tick(), gpio_block.toggle()) + + def test_uart_loopback(self): + _LOG.info("Testing UART Loopback") + uart_block = dhalsim.create(uart.Uart("uart", baud_rate=115200)) + dhalsim.connect(uart_block.rxd_byte(), uart_block.tx_byte())
diff --git a/pw_hil/py/hil_framework/hiltest.py b/pw_hil/py/hil_framework/hiltest.py index ea09cc9..8c7d363 100644 --- a/pw_hil/py/hil_framework/hiltest.py +++ b/pw_hil/py/hil_framework/hiltest.py
@@ -37,7 +37,7 @@ compiled_protos = [ hil_pb2, ] -_LOG = logging.getLogger() +_LOG = logging.getLogger('hiltest') class TestCase(object): def __init__(self): @@ -49,7 +49,7 @@ def tearDown(self): pass -def create_device_connection(args: argparse.Namespace) -> DeviceConnection: +def create_dhalsim_connection(args: argparse.Namespace) -> DeviceConnection: return create_device_serial_or_socket_connection( device=args.device, baudrate=args.baudrate, @@ -125,7 +125,7 @@ parser = add_device_args(parser) parser.add_argument("--timeout", type=int, default=10, help="Test timeout in seconds (Default: 10 seconds)") args = parser.parse_args() - with create_device_connection(args) as dhalsim_device: + with create_dhalsim_connection(args) as dhalsim_device: dhalsim.set_device(dhalsim_device) suite = HilTestSuite(args.timeout) if not suite.run_tests():
diff --git a/pw_hil/rp2xxx_main.cc b/pw_hil/rp2xxx_main.cc new file mode 100644 index 0000000..04d737c --- /dev/null +++ b/pw_hil/rp2xxx_main.cc
@@ -0,0 +1,45 @@ +// Copyright 2026 The Pigweed Authors +// +// 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 +// +// https://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. + +#define PW_LOG_MODULE_NAME "rp2xxx_main" + +#include "hardware/gpio.h" +#include "pico/stdlib.h" +#include "pw_allocator/best_fit.h" +#include "pw_channel/rp2_stdio_channel.h" +#include "pw_multibuf/simple_allocator.h" +#include "pw_system/system.h" + +static std::array<std::byte, 4096> mb_data; +static std::array<std::byte, 2048> meta_data; + +inline void pause(uint32_t delay_loops = 10) { + volatile uint32_t loop = 0; + for (loop = 0; loop < delay_loops;) { + loop = loop + 1; + } +} + +int main() { + // PICO_SDK Inits + stdio_init_all(); + setup_default_uart(); + stdio_usb_init(); + + static pw::allocator::BestFitAllocator alloc{meta_data}; + static pw::multibuf::SimpleAllocator mb_alloc{mb_data, alloc}; + pw::system::StartAndClobberTheStack( + pw::channel::Rp2StdioChannelInit(mb_alloc, mb_alloc)); + PW_UNREACHABLE; +}
diff --git a/pw_hil/uart_blaster.cc b/pw_hil/uart_blaster.cc new file mode 100644 index 0000000..1b4197f --- /dev/null +++ b/pw_hil/uart_blaster.cc
@@ -0,0 +1,135 @@ +// Copyright 2026 The Pigweed Authors +// +// 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 +// +// https://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. + +#include <chrono> + +#include "hardware/gpio.h" +#include "hardware/uart.h" +#include "pw_system/config.h" +#include "pw_system/system.h" +#include "pw_thread/attrs.h" +#include "pw_thread/detached_thread.h" +#include "pw_thread/sleep.h" + +namespace pw::hil { +namespace { + +inline constexpr ThreadPriority kThreadPriorityUartBlaster = ThreadPriority(); + +inline constexpr pw::ThreadAttrs kUartBlasterThread = + pw::ThreadAttrs() + .set_stack_size_bytes(4096) + .set_name("UartBlasterThread") + .set_priority(kThreadPriorityUartBlaster); + +static constexpr char ABC_LUT[26] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + +} // namespace +} // namespace pw::hil + +namespace pw { +template <typename T> +class Debouncer { + public: + Debouncer(const T& initial_state, size_t threshold) + : state_(initial_state), + last_state_(initial_state), + threshold_(threshold), + count_(0) {} + + const T& state() const { return state_; } + + const T& Update(const T& new_state) { + if (new_state == state_) { + count_ = 0; + } else if (new_state == last_state_) { + if (count_ >= threshold_) { + state_ = last_state_; + count_ = 0; + } else { + count_++; + } + } else { + last_state_ = new_state; + count_ = 1; + } + return state_; + } + + private: + T state_; + T last_state_; + size_t threshold_; + size_t count_; +}; +} // namespace pw + +inline void pause(uint32_t delay_loops = 10) { + volatile uint32_t loop = 0; + for (loop = 0; loop < delay_loops;) { + loop = loop + 1; + } +} + +namespace pw::system { + +void UserAppInit() { + PW_CONSTINIT static ThreadContextFor<::pw::hil::kUartBlasterThread> + uart_blaster_thread; + + pw::Thread(uart_blaster_thread, []() { + static constexpr uint32_t TRIGGER_GPIO = 17; + static constexpr uint32_t LED_GPIO = 24; + + static constexpr uint32_t UART1_RX_PIN = 5; + static constexpr uint32_t UART1_TX_PIN = 4; + + static constexpr uint32_t LOOP_TIME_MS = 10; + static constexpr uint32_t LED_PERIOD_MS = 500; + + gpio_init(TRIGGER_GPIO); + gpio_set_dir(TRIGGER_GPIO, GPIO_IN); + gpio_init(LED_GPIO); + gpio_set_dir(LED_GPIO, GPIO_OUT); + + uart_init(uart1, 115200); + uart_set_format(uart1, 8, 1, UART_PARITY_NONE); + gpio_set_function(UART1_RX_PIN, + UART_FUNCSEL_NUM(uart1, UART1_RX_PIN)); // TEST_UART_RX + gpio_set_function(UART1_TX_PIN, + UART_FUNCSEL_NUM(uart1, UART1_TX_PIN)); // TEST_UART_TX + + pw::Debouncer<bool> debouncer(false, 10); + size_t loop_counts = 0; + while (true) { + loop_counts++; + debouncer.Update(gpio_get(TRIGGER_GPIO)); + + // While GPIO is high, blast UART + if (debouncer.state() == true) { + uart_putc(uart1, pw::hil::ABC_LUT[loop_counts % 26]); + } + + if (loop_counts % (LED_PERIOD_MS / LOOP_TIME_MS) == 0) { + gpio_put(LED_GPIO, !gpio_get(LED_GPIO)); + } + + pw::this_thread::sleep_for(std::chrono::milliseconds(LOOP_TIME_MS)); + } + }).detach(); +} + +} // namespace pw::system