S19k Pro (BHB56902 / BM1366) hashboard support#4
Closed
Schnitzel wants to merge 2 commits into
Closed
Conversation
Brings up the Antminer S19k Pro on the Amlogic A113D control board.
Single hashboard (BHB56902) running BM1366 chips: 77 chips across 11
voltage domains, sustains 39.68 TH/s on the dummy job source at 575
MHz / 13.9V (matches LuxOS's 39.33 TH/s nominal on the same hardware).
New board driver:
- mujina-miner/src/board/s19k_pro_amlogic.rs: PSU bring-up
(no PIC variant), per-domain DC-DC, chain enable/reset, factory
voltage range (13.9–14.5V), telemetry via per-chip temp reads on
i2c-0, multi-fan PWM/tach control.
New BM1366 chip family in bm13xx core:
- chip_config.rs::bm1366(): chip_id 0x1366, PllParams BM1366/BM1368
(fb_div 144–235, postdiv1 > postdiv2), nonce_range 0x5a10_0000,
init_regs with the wire bytes confirmed against a LuxOS UART
capture on BHB56902:
* Reg 0x18 broadcast `ff 0f c1 00`, per-chip `f0 00 c1 00`
* Reg 0x3c broadcasts `80 00 85 40` + `80 00 80 20`, per-chip
adds finalizer `80 00 82 aa`
* Reg 0x58 (IoDriverStrength) `02 11 41 11`
* Reg 0xa8 broadcast `00 07 00 00`, per-chip `00 07 01 f0`
target_frequency_mhz Some(575.0): LuxOS auto-detunes the 645 MHz
EEPROM setpoint to 565 MHz adjusted / 575 MHz operating on this
exact hashboard; mujina at 575 sustains 39.68 TH/s vs 38.35 at
645 (chips are at the edge of stability at 645).
post_perchip_ticket_zero_bits Some(2): mirrors LuxOS's final
`reg 0x14 = 0x000000c0` per-chip TicketMask write. Without it
mujina sees ~4 shares/sec; with it ~1400 shares/sec, and reported
hashrate jumps from 28 to 38+ TH/s on a 77-chip chain.
Per-domain UartRelay support (reg 0x2C):
- chip_config.rs::UartRelayPerDomain spec encodes the 22-write
pattern LuxOS uses on BHB56902 (11 domains x 2 boundary chips
each, byte1 stepping by 0x07). bm1362 boards keep per-chip
uart_relay_perchip; BHB56902 uses per-domain.
Mid-init UART baud bump to 3.125 Mbaud:
- chain_config.rs::ChipUartBaudControl trait: two-phase
prepare_new_stream / finalize_baud_switch so the original fd
carries the chip-side `UartBaud` broadcast at the current rate
and a second fd is retuned to the target rate only after the
broadcast has been settled by the chips. On meson_uart the
in-place tcsetattr(Drain) path dropped chips at 3 Mbaud.
- thread_v2.rs: `BaudRate::Baud3M` maps to 3_125_000 numeric
(matches the chip-side register `0x00003011` actual rate, not
3_000_000). Holds the original SerialControl alive via
SerialControlAdapter._original_keepalive so the first /dev/ttyS2
fd never closes during the session — LuxOS pattern.
Multi-pass verify_chain:
- thread_v2.rs: poll all chips, retry up to two more passes on
missing addresses. On BHB56902 at 3.125 Mbaud the first sweep
consistently shadowed the first ~10 chips (always 0x00..0x12,
closest to host) while hashrate math showed them contributing.
Retries find them; logs missing_addrs on final failure.
PER_CHIP_TIMEOUT bumped 100ms -> 500ms for the same reason.
Two-step nonce result_header layout (BM1362 / BM1366 hack):
- thread_v2.rs: existing HACK that re-extracts job_id from bits
6-3 (5-bit job_id / 3-bit subcore packing) is documented as
required for BM1366 too, not just BM1362. Gating it to BM1362
only collapsed BHB56902 hashrate to ~3 TH/s. Proper fix is
chip-aware Response::Nonce decoding in protocol.rs; the
comment now points at that future refactor.
Config / daemon plumbing:
- config.rs: HashboardModel::S19kPro variant, label "S19k Pro
(Amlogic control board)" / chip "BM1366".
- daemon.rs / backplane.rs: dispatch S19kPro to the new
s19k_pro_amlogic board driver alongside the existing S19jPro
path.
- transport/amlogic.rs: factory wiring for the new board.
Existing boards (EmberOne, S19j Pro Amlogic/Bitcrane) get two
additive None initializations (chip_uart_baud, post_broadcast_chip_baud)
to keep them compiling with the new ChainPeripherals / ChainConfig
fields. No behavior change for those boards.
Validated on hardware:
- One BHB56902 hashboard, Amlogic A113D control board.
- 77/77 chips verified mining (after multi-pass verify) at 575 MHz
/ 13.9V, sustains 39.68 TH/s on the dummy block-881423 source,
matching the LuxOS reported rate on the same hardware.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4 tasks
The previous commit on this branch picked the first present
hashboard out of the three configured slots and ignored the rest;
on a fully-populated Amlogic control board (BHB56902 x3) that's
1/3 of the available hashrate. This commit brings up every present
slot in parallel on the shared APW12 / Amlogic SoC, with three
coordinated concerns:
1. Detection.
`select_present_hashboards` walks every configured slot and
returns every one whose detect GPIO reads "present". Order is
preserved so per-thread names (HB0/HB1/HB2) match config order.
A missing required slot is still fatal; a missing optional
slot is still skipped.
2. Lockstep voltage ramp.
All chains share one APW12. If each chain runs its own
coordinated voltage-frequency ramp independently, the rail
will oscillate between the chains' step values -- chain N
commanding `set_voltage(12V)` for its step-0 ramp would brown
out chain 0 mid-mining at 13.9V. New `ChainCoordinator` in
chain_config.rs wraps a `tokio::sync::Barrier`: every chain
calls `sync_voltage_step` at the top of every ramp step, the
elected leader drives the shared regulator on behalf of the
cohort, and all chains then wait the same VOLTAGE_SETTLE_DELAY
before sending their per-chain PLL command. All chains compute
the same target voltage from the same step index, so it's safe
for only the leader to actually touch the rail.
`ramp_coordinator` is `Option<Arc<ChainCoordinator>>` on
`ChainPeripherals` and only set when there's more than one
chain; single-chain boards (EmberOne, BitAxe, S19j Pro
Bitcrane, and S19k/S19j Pro running on a single hashboard)
keep the legacy "each actor drives its own regulator" path.
3. Concurrent dispatch.
The scheduler dispatches the first UpdateTask to each thread
sequentially with `.await`. Without the ramp barrier this was
fine -- chain 0 fully ramped before chain 1 ever started --
but with the barrier it deadlocks: chain 0 sits at step 0
waiting for chains 1 and 2 to arrive while they're still
waiting for their first UpdateTask to clear chain 0's
blocking init.
Fix at the actor level: `handle_command` ACKs UpdateTask /
ReplaceTask *before* running `handle_work_assignment`, so the
scheduler unblocks and dispatches to the next thread while the
current actor's initialize_chips runs in the background. Cold
init can take ~2 minutes (enumeration + register config +
84-step frequency ramp); without this, chain 0 holds chains 1
and 2 out of init for that whole window. The scheduler in
`process_template_event` already discards the `Some(old)` it
gets back, so returning `Ok(self.current_task.clone())`
instead of waiting for the real result is safe.
`S19kProAmlogic::initialize` now health-gates and PIC-handshakes
every selected slot before the PSU comes up, then
`create_hash_threads` spawns one `BoardStateHashThread` per board
sharing the APW12 + `ChainCoordinator`.
Telemetry consolidated.
`native_telemetry_task` now takes a `Vec<AmlogicHashboardConfig>`
and walks every present hashboard each tick. Spawning N independent
tasks would have each one call `state_tx.send_modify` with only its
own hashboard's temperatures, clobbering the others. The overtemp
gate sums across every sensor on every board so any one hot board
still kills the shared rail.
Verified on a BHB56902 x3 control board:
* cold boot: all 3 chains enumerate 77/77 (231 chips total),
all 3 ramp to 575 MHz, 113 TH/s aggregate sustained
(45 + 35 + 40 per chain).
* single-board bring-up still works (`ramp_coordinator: None`
when only one slot is populated).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings up the Antminer S19k Pro on the Amlogic A113D control board (single BHB56902 hashboard, 77 BM1366 chips across 11 voltage domains). Sustains 39.68 TH/s on the dummy job source at 575 MHz / 13.9 V — matches LuxOS's 39.33 TH/s nominal on the exact same hardware.
This is built on top of the same
amlogic-s19jprobase as #3, so the diffs don't overlap; you can review them independently. (S19k touchesbm13xxcore + new board driver; #3 touches onlyboard/s19j_pro_amlogic.rs. The two PRs merge cleanly in either order.)What's new
New board driver (
board/s19k_pro_amlogic.rs): PSU bring-up (noPIC variant), per-domain DC-DC, chain enable/reset, factory voltage range 13.9–14.5 V, per-chip temp telemetry on i2c-0, multi-fan PWM/tach.New BM1366 chip family in bm13xx core:
chip_id 0x1366,PllParams BM1366/BM1368(fb_div 144–235, postdiv1 > postdiv2),nonce_range 0x5a10_0000, and init register values byte-for-byte from a LuxOS UART capture on BHB56902.Per-domain UartRelay support (reg 0x2C): new
UartRelayPerDomainspec encodes the 22-write pattern LuxOS uses on BHB56902 (11 domains × 2 boundary chips each).Mid-init UART baud bump to 3.125 Mbaud via the new
ChipUartBaudControltrait: two-phaseprepare_new_stream/finalize_baud_switchso the original fd carries the chip-sideUartBaudbroadcast at the current rate and a second fd is retuned to the target rate only after the chips have applied their internal change. The in-placetcsetattr(Drain)path drops chips at 3 Mbaud on meson_uart; this pattern matches LuxOS.Multi-pass
verify_chain: poll all chips, retry up to two more passes on missing addresses. At 3.125 Mbaud the first sweep consistently shadowed the first ~10 chips on the chain (always0x00..0x12, closest to host) while hashrate math showed them contributing; retries find them. Logsmissing_addrson final failure.Per-chip TicketMask (
post_perchip_ticket_zero_bits: Some(2)): mirrors LuxOS's finalreg 0x14 = 0x000000c0per-chip write. Without it mujina sees ~4 shares/sec and reports 28 TH/s; with it ~1400 shares/sec and reports 38+ TH/s on a 77-chip chain. Same setting is leftNoneon BM1362 / BM1370 boards (no behavior change there).Existing boards: EmberOne, S19j Pro Amlogic, S19j Pro Bitcrane each get two additive
Noneinitializations (chip_uart_baud,post_broadcast_chip_baud) to keep compiling with the newChainPeripherals/ChainConfigfields. No behavior change for those boards.The existing
BM1362 job_id fixHACK inhandle_chip_responseis required for BM1366 too (not just BM1362) — empirically confirmed by gating it tochip_id == 0x1362and watching BHB56902 hashrate collapse from 29 TH/s to 3 TH/s. The comment is now updated to call this out, with a TODO to move the fix-up into a chip-awareResponse::Noncedecoder inprotocol.rs.Validation
verify_chain).Test plan
cargo build --release --target aarch64-unknown-linux-musl --bin mujina-minerdsucceedscargo test --libpasses (test code updated to include newNonefields and adrain_sink()helper for the now-Error = std::io::ErrorSinkbound)🤖 Generated with Claude Code