Hardware mystery solved:
Ever since I've been working on reverse-engineering the Woodstock/Spice era hardware, and figured out how the bank-switching works on Spice (and probably on Woodstock, though I haven't yet confirmed that), I've wondered how the multiple ROM chips all make sure they agree on the bank, and how they're forced to start in bank 0 at power-up. The mystery is that there is no reset signal going to the ROM chips (or ROM/RAM chips).
In theory it would be possible for the chips to each have their own internal power-up-reset logic, but in practice this would increase chip area and might not reliably get them all reset properly. Using 1970s technology, this would have required external RC components. It would have made even less sense for HP to put those in for each ROM than just to bus a reset signal from the ACT to the ROMs. But they didn't do either.
Studying a logic analyzer trace, I think I now know the answer. Each word cycle consists of 56 bit cycles, which are defined by the two-phase clock pulses. The various chips have to know when a word cycle begins, and they do that by watching the SYNC line, which is generated by the ACT. The SYNC line is low most of the time, but goes high for ten bit cycles of each word. Those are the ten cycles in which a ROM instruction is read out. Each chip on the bus other than the ACT has an internal counter that tracks the current bit time, and it uses the SYNC line to initialize that.
There are some two-word instructions, and the ACT does not raise the SYNC line while fetching the second word, so the counters in the chips are designed to "ride through" the missing SYNC period. They also know that they shouldn't try to interpret the ROM words bits that are read out when SYNC is not present as an instruction, because it's really a branch target.
I've captured a lot of logic analyzer traces in the past, but tonight I captured one from an HP-97 during initial power-up, and I think I now understand how the system-wide reset is implemented. At powerup, the ACT starts generating the clocks, and it holds the SYNC line low for about four milliseconds to allow various things in the system to stabilize. Then it starts generating narrow SYNC pulses once per clock. It generates about 30 of those, with somewhat variable width, then starts holding SYNC high. It keeps SYNC high for about 22 ms. Finally, it lowers SYNC, then begins pulsing it at the usual rate (10 bit times high out of every 56, except for the second word of two-word instructions).
So it appears that the ROMs and other "peripheral chips" are designed to reset themselves if they seen SYNC high for more than ten bits consecutively. Quite a clever solution to having a hardware reset without any extra pins.
I think the narrow SYNC pulses before the 22 ms high time are probably just an artifact of the ACT implementation; the ACT likely is not yet fully initialized yet. I doubt that any of the other chips notice these pulses at all, because they happen after the phase 2 pulse, rather than staying high throughout the phase 2 pulse.
I've also spent a little time recently on infrastructure improvements to Nonpareil, like making the word size architecture-specific (56 bits for most HPs, but 48 bits for the HP-01 watch, 64 bits for more recent Saturn-based calculators, and 8 bits for the HP-75 and Series-80 computers). I've come up with a new API to deal with memory reads and writes from the GUI task, a new API to manage multiple sparse address spaces with bank switching, and what I call a "pseudo-reflection" API for the GUI task to ask the simulator task what processor state can be accessed.
I still haven't decided whether to change the internals from using an array of 14 bytes (uint8_t) to represent a 56 bit calculator word (4 bits per byte) to using a packed value (uint64_t). The latter makes the memory API more elegant, but adds code to the simulation task.
I've got Altogether using libgsf to read the microcode files. It works great. I wish the ISO C committee had added some way to hook into the stdio interface from the underside, i.e., to supply an alternate implementation of the functions needed to implement the FILE abstraction. Some platforms do provide this but not in a portable way. If it could be done, libgsf could allow you to use stdio calls on the components of ZIP files. As it is, libgsf provides its own read and write calls, rather than working through the GLib I/O channel API. Sigh.