Files
Yaya48andClaude Opus 5.5 9ad22f00bd StarletBootstrapProbe: --broadway-h1a's report and budget, from the final review
Two findings of the review of 2df9dfc909 (StarOS H-1A, D2):
- The report reads the bench window too (hw0c_report(): HW-M4-0D's
  area, and H-1A's record if it was never read), but a refused access
  there did not fail the run, as one during the run does. It does now:
  the failure on stderr, and the run fails.
- --cycles' bound of 2^34 was raised whenever "--broadway-h1a" appeared
  among the arguments, even as the value of --ipc-script or --stage-spk
  (a file of that name), the run then not in the client's mode.
  ClientRequested reads the options with their grammar: the word after
  --ipc-script, --stage-spk or --cycles is a value, never an option.

StarletProbeBroadwayTest: ClientRequested.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 21:59:34 +01:00

146 lines
6.1 KiB
C++

// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <string_view>
#include "Common/CommonTypes.h"
#include "StarOSPanicCheck.h"
// StarletBootstrapProbe's --broadway-h1a: the probe stands in for Broadway with StarOS's own bench
// loader code, so that the H-1A bench (StarOS docs/architecture/h-1a.md, D-H1A-7) runs whole: its
// Starlet image here, on the ARM core; its Broadway side, StarOS's boot/wii/loader hw0c.c, hw0d.c
// and h1a.c, vendored unchanged in StarOSLoader/ and built over the host HAL of BroadwayHAL/.
//
// The client does what StarOS's loader does around the jump: hw0c_setup() before the Starlet's
// first instruction, then one hw0c_step() between each two slices, as the loader's monitor calls
// it, until it says 1; then the report, between the status and END lines main.c prints. It reaches
// PPC memory only in the bench window, by whole words; it knows nothing of the image and answers
// nothing for it: what it reads, the ARM code wrote. Dolphin's memory is coherent, so the run says
// nothing of the console's caches: it rehearses the protocol, its 3000 rounds, the capture and the
// judge, not the hardware.
namespace StarletBootstrapProbe
{
// The bench window, StarOS boot/contract/staros_hw0c.h's STAROS_HW0C_BASE and _SIZE: the only PPC
// memory the client reads or writes.
constexpr u32 BENCH_WINDOW_BASE = 0x13500000;
// Exclusive.
constexpr u32 BENCH_WINDOW_END = 0x13510000;
// Broadway's timebase runs at 243 MHz / 4, the Starlet at 243 MHz: four cycles a tick.
constexpr u64 CYCLES_PER_TIMEBASE_TICK = 4;
// The client's first timebase. Any value would do (the loader only subtracts); a nonzero one wider
// than 32 bits keeps the 64-bit arithmetic honest.
constexpr u64 CLIENT_TIMEBASE_BASE = u64{1} << 32;
// The lines StarOS's main.c prints around hw0c_report(): its status line once the Starlet is done
// or timed out, and its END line. There is no Gecko here, so nothing was dropped.
constexpr std::string_view CLIENT_STATUS_DONE = "SBL:STARLET DONE";
constexpr std::string_view CLIENT_STATUS_TIMEOUT = "SBL:TIMEOUT";
constexpr std::string_view CLIENT_END = "SBL:END GECKO DROPPED AFTER JUMP 0 BEFORE 0";
// Whether StarOS's loader would take the Starlet for done, as main.c's monitor does: the SBR1
// record's state IDLE or after, with EXI given back to Broadway. The client's mode takes
// --sbr1-record so that the record is there. The trace's own terminal line also counts; it can be
// missing, the trace holding 1 KiB, which H-1A's image fills before its IDLE line.
constexpr bool StarletDone(bool trace_terminal, u32 sbr1_state, u32 sbr1_exi_owner)
{
return trace_terminal || (sbr1_state >= SBR1_STATE_IDLE && sbr1_exi_owner == SBR1_EXI_PPC);
}
// Whether --broadway-h1a is among the probe's options (argv[2] on), read with their grammar: the
// word after --ipc-script, --stage-spk or --cycles is that option's value, never an option. It
// raises --cycles' bound, which is read where it stands, before the other options are known.
inline bool ClientRequested(int argc, const char* const* argv)
{
for (int index = 2; index < argc; ++index)
{
const std::string_view option = argv[index];
if (option == "--ipc-script" || option == "--stage-spk" || option == "--cycles")
++index;
else if (option == "--broadway-h1a")
return true;
}
return false;
}
// Whether a whole word at `address` lies in the bench window.
constexpr bool InBenchWindow(u64 address)
{
return address >= BENCH_WINDOW_BASE && address + 4 <= BENCH_WINDOW_END && address % 4 == 0;
}
// What the client reaches: PPC memory, by whole words.
class BroadwayMemory
{
public:
virtual ~BroadwayMemory() = default;
virtual u32 LoadWord(u32 address) = 0;
virtual void StoreWord(u32 address, u32 word) = 0;
};
// The HAL the vendored loader sources call (BroadwayHAL/board.h and gecko.h): the window's bounds,
// the clock and the capture. A word outside the window is never touched: the first such access
// fails the client, and a read gives 0.
class BroadwayHal
{
public:
BroadwayHal(BroadwayMemory& memory, u64 timebase) : m_memory(memory), m_timebase(timebase) {}
u32 Read(u32 address);
void Write(u32 address, u32 value);
// dcbf and dcbi: a line of the window; nothing to do in Dolphin's coherent memory.
void CacheLine(u32 address);
u64 Timebase() const { return m_timebase; }
// `cycles` Starlet cycles went by.
void Elapse(u64 cycles) { m_timebase += cycles / CYCLES_PER_TIMEBASE_TICK; }
void Put(std::string_view text) { m_capture += text; }
void Hex(u32 value);
void Dec(u32 value);
const std::string& Capture() const { return m_capture; }
u64 Accesses() const { return m_accesses; }
bool Failed() const { return !m_failure.empty(); }
const std::string& Failure() const { return m_failure; }
private:
bool Allowed(u32 address, std::string_view what);
BroadwayMemory& m_memory;
u64 m_timebase;
u64 m_accesses = 0;
std::string m_capture;
std::string m_failure;
};
// The client. The vendored sources keep their state in globals, so a process has one client, and
// it starts once.
class BroadwayClient
{
public:
// hw0c_setup() over `memory`, at CLIENT_TIMEBASE_BASE; false, with the reason in Failure(), if a
// client already started in this process.
bool Start(BroadwayMemory& memory);
// `elapsed` Starlet cycles went by: one hw0c_step() at the timebase they make, unless done.
void Advance(u64 elapsed);
bool Started() const { return m_started; }
bool Done() const { return m_done; }
bool Failed() const;
std::string Failure() const;
u64 Steps() const { return m_steps; }
u64 Timebase() const;
// The capture main.c would print from its status line on: `status`, hw0c_report()'s lines
// (hw0d_report()'s and h1a_report()'s among them), the END line. Once.
std::string Report(std::string_view status);
// The vendored sources, as built: "<file> <sha256>" lines (CMake computes them at configure).
static std::string_view Sources();
private:
bool m_started = false;
bool m_done = false;
bool m_reported = false;
u64 m_steps = 0;
std::string m_refusal;
};
} // namespace StarletBootstrapProbe