Skip to content

S19k Pro (BHB56902 / BM1366) hashboard support#4

Closed
Schnitzel wants to merge 2 commits into
skot:amlogic-s19jprofrom
Schnitzel:amlogic-s19kpro-support
Closed

S19k Pro (BHB56902 / BM1366) hashboard support#4
Schnitzel wants to merge 2 commits into
skot:amlogic-s19jprofrom
Schnitzel:amlogic-s19kpro-support

Conversation

@Schnitzel

Copy link
Copy Markdown

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-s19jpro base as #3, so the diffs don't overlap; you can review them independently. (S19k touches bm13xx core + new board driver; #3 touches only board/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 UartRelayPerDomain spec 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 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 chips have applied their internal change. The in-place tcsetattr(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 (always 0x00..0x12, closest to host) while hashrate math showed them contributing; retries find them. Logs missing_addrs on final failure.

Per-chip TicketMask (post_perchip_ticket_zero_bits: Some(2)): mirrors LuxOS's final reg 0x14 = 0x000000c0 per-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 left None on BM1362 / BM1370 boards (no behavior change there).

Existing boards: EmberOne, S19j Pro Amlogic, S19j Pro Bitcrane each get two additive None initializations (chip_uart_baud, post_broadcast_chip_baud) to keep compiling with the new ChainPeripherals / ChainConfig fields. No behavior change for those boards.

The existing BM1362 job_id fix HACK in handle_chip_response is required for BM1366 too (not just BM1362) — empirically confirmed by gating it to chip_id == 0x1362 and 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-aware Response::Nonce decoder in protocol.rs.

Validation

  • Hardware: 1× BHB56902 hashboard, Amlogic A113D control board.
  • 77/77 chips alive (verified after multi-pass verify_chain).
  • 575 MHz / 13.9 V (matching LuxOS's auto-detuned operating point on this hashboard; the 645 MHz EEPROM ATE setpoint runs less stably and yields 38.35 TH/s vs 39.68 at 575).
  • Sustains 39.68 TH/s on the dummy block-881423 source after 6 minutes.
  • LuxOS on the same hashboard: 39.15 TH/s 5m-avg, 39.33 nominal — parity.

Test plan

  • cargo build --release --target aarch64-unknown-linux-musl --bin mujina-minerd succeeds
  • cargo test --lib passes (test code updated to include new None fields and a drain_sink() helper for the now-Error = std::io::Error Sink bound)
  • Flash on an S19k Pro and watch for the "Frequency ramp complete" + "Mining status … hashrate=" lines in the daemon log; the chain should land at 575 MHz / 13.9 V and hold ≥ 38 TH/s.

🤖 Generated with Claude Code

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>
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>
@Schnitzel

Copy link
Copy Markdown
Author

Superseded by #6, which has been refreshed to our current integration branch and now carries the complete, current board-support stack (this PR's code had diverged as development continued). Consolidating into #6 rather than maintaining diverged branches. Closing in its favor.

@Schnitzel Schnitzel closed this Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant