OCTObus / OBCON - the documented protocol, and what the ACCP driver does¶
Date: 2026-07-27, substantially updated 2026-07-28 (sections 1a, 1b, 5c - the
information byte is now decoded from documentation)
Documented sources:
- E:\Dev\Ronny\NDInsight\Reference-Manuals\500\ND-14001-1-EN DOMINO Standard Hardware Description.md, chapter 4 "The OCTObus Adapter (OBA)" - the frame
- E:\Dev\Ronny\NDInsight\Reference-Manuals\500\ND-05.017.01 EN ND-5000 HARDWARE MAINTENANCE.md, chapter 3 "Octobus Communication" - the information byte encoding
Carved source: E:\Dev\Ronny\NDInsight\Installation\Communication\OctobusAccp\eprom\octo.bin
The manual and the firmware agree, and that agreement is what makes this document usable. Where only one of the two supports a claim, it says so.
The part is called OBCON - "An OCTObus Adapter (OBA) is implemented on the DIOC. Its design is centred upon the OBCON gate array chip." That settles the NDOBCON-vs-OCTC naming question in favour of OBCON.
1. The frame, as the software sees it¶
The wire frame is 32 bits (start + 30 + stop), but software never sees that. The driver interface is exactly 16 bits in each direction:
Transmit (written to the OCTObus output register):
15 14 13........8 7..............0
| C | B | Dest/Type | Information |
Receive (read from the OCTObus input register):
15 14 13........8 7..............0
| C | B | Source | Information |
- C = 1 - the information byte is a control byte.
- C = 0 - the information byte is pure data, i.e. "kick" information.
- B = 1 - broadcast; the Dest field is one of six node types.
- B = 0 - normal; the Dest field is one of 62 node numbers (0 and 63 illegal).
This is the direct explanation of two ACCP console commands. SEND-OCTOBUS <Data (16)>
writes one such word raw. SEND-KICK-OCTOBUS <DESTINATION><Kick value (process)> builds one
with C=0 - a kick. The 16 in "Data (16)" is the frame width, not an arbitrary field size.
1a. THE INFORMATION BYTE - DECODED [SOLVED 2026-07-28]¶
ND-14001 defers the meaning of the information byte four separate times, on printed page 110 (PDF page 126), to a document it calls the "OCTObus Protocol Specification". That document is not in this repository and carries no ND number in either manual's related-manuals list.
It is no longer needed. The encoding is documented in a different manual we already have:
E:\Dev\Ronny\NDInsight\Reference-Manuals\500\ND-05.017.01 EN ND-5000 HARDWARE MAINTENANCE.md,
chapter 3 "Octobus Communication", section 3.3 "Introduction to the protocol", 3.3.1 "Message
format".
The information byte splits into four flag bits and a 4-bit code:
bit 7 bit 6 bit 5 bit 4 bits 3..0
E K M S code
| C | E | K | M | S | code field | Message type |
|---|---|---|---|---|---|---|
| 1 | 1 | - | - | - | emergency code | Emergency - hardware only: power fail, master clear, reset |
| 1 | 0 | 1 | - | - | kick number | Kick |
| 1 | 0 | 0 | 0 | - | ident number | Ident |
| 1 | 0 | 0 | 1 | 1 | CMD number | Start of multibyte message |
| 1 | 0 | 0 | 1 | 0 | CMD number | End of multibyte message |
| 0 | - | - | - | - | data byte | Part of a multibyte message |
From the same manual, on what the three classes are for:
- IDENT activates a process in the destination station. Used to interrupt the ND-100 from the ND-5000 CPU. The destination must be prepared to receive idents from a specific station.
- KICK activates a process in the destination; accepted from any station. Kick number 1 in the ND-5000 CPU starts scanning the execution queue.
- MULTIBYTE messages are routed to the specified CMD routine by the destination process. In the ND-5000 CPU, multibyte messages with CMD number 3 are handled by the Access Processor (ACP).
CMD numbers run 0 to 15 - which is exactly the 4-bit code field. The ACCP console calls
this a "Subprocess"; the ND-5000 test program calls it a "CMD" and lists the connected ones
with LIST-SUBPROC-TABLE. They are the same field. A receiver must connect a CMD before
anything addressed to it will be delivered - which is the same requirement the ACCP driver
enforces through its registration table.
The ND-5000 octobus driver function set¶
From ND-05.017.01 chapter 8, the OCTOBUS-DRIVER test command:
| Fn | Function |
|---|---|
| 1 | Send multibytes |
| 2 | Broadcast multibytes |
| 3 | Send kick |
| 4 | Send ident |
| 5 | Send emergency (not implemented) |
| 6 | Read transmit status |
| 7 | Connect kick |
| 8 | Connect ident |
| 9 | Connect CMD |
| 10 | Access octobus registers (not implemented) |
The send-multibyte parameters are Destination, Cmd (destination CMD, 0-15), Own cmd
(0-15) and the message content. The receive side (LIST-SUBPROC-TABLE) reports Source and
Message size. Source comes from the frame's hardware source field, so size must travel
in-band - and own-cmd must too, or a reply could not be addressed.
That manual also names the one remaining document, for the per-function detail: "Octobus Driver Programming Guide (written by DVT - 15. Oct. 1986)". Also not in this repository. Between ND-14001 chapter 4 and ND-05.017.01 chapter 3 we no longer need it for framing - only for the private meaning of individual CMD payloads.
A field-layout conflict, resolved in favour of ND-14001¶
Section 3.3.1's own frame diagram renders the fields as C(15) | B+Dest(14..09) |
Information(08..00), which does not add up - that is a 9-bit information field. ND-14001
Figure 30 gives C(15) | B(14) | Dest(13..8) | Information(7..0), and the wire capture matches
ND-14001.
Verified against the original scan, not just the OCR:
F:\NDDOC\ND\14\ND-14001-1-EN Domino Standard Hardware Description.pdf, PDF page 125-128.
Figure 30 there reads 30..27 Priority | 26..21 Destination | 20 C | 19 B | 18..13 Source |
12..5 Information | 4..3 Parity | 2..1 Ack, with the driver views as the 16-bit forms above.
Treat the 3.3.1 diagram as an OCR misalignment. The sub-field decode in the table above is
unaffected - it operates on the 8-bit information byte and is confirmed by the capture.
PDF-to-markdown page mapping (do not re-derive this): the markdown's ## Page N markers
are 1:1 with PDF pages. The manual's own printed page number is N - 16. Confirmed
twice from the table of contents and once by reading the PDF directly.
1b. THE CAPTURED SCAN, FULLY DECODED [SOLVED 2026-07-28]¶
Applying section 1a to the six frames the ACCP sends per station (station 2 shown; stations 3-7 are identical with the destination changed):
| Frame | C | B | Dest | Info | Decode |
|---|---|---|---|---|---|
8235 |
1 | 0 | 2 | 0x35 = 0011 0101 |
E=0 K=0 M=1 S=1 code=5 -> start of multibyte, CMD 5 |
0205 |
0 | 0 | 2 | 0x05 |
data byte |
0202 |
0 | 0 | 2 | 0x02 |
data byte |
0203 |
0 | 0 | 2 | 0x03 |
data byte |
02NN |
0 | 0 | 2 | NN |
data byte |
8225 |
1 | 0 | 2 | 0x25 = 0010 0101 |
E=0 K=0 M=1 S=0 code=5 -> end of multibyte, CMD 5 |
So it is a 4-byte multibyte message addressed to CMD 5, and the four data bytes are:
[own cmd = 0x05] [length = 0x02] [content byte 0 = 0x03] [content byte 1 = own station]
Two independent derivations agree. Section 4y carved the request from the driver side and
found a message descriptor yielding exactly two content bytes, {0x03, own station}. The
wire shows four data bytes. The difference is the two header bytes the driver itself prepends -
own-cmd and length - which is precisely what the OCTOBUS-DRIVER parameter list and the
LIST-SUBPROC-TABLE display require to exist.
This supersedes the earlier reading in section 5b that called 0x05 a "process number" and
treated 0x35/0x25 as inferred "open/select" and "execute/close" control bytes. Those were
correct in shape and wrong in name; the values are now decoded from documentation, not guessed.
The one byte still unknown¶
0x03 - the first content byte, i.e. the request itself, inside CMD 5's private
vocabulary. Nothing in ND-14001, ND-05.017.01 or the ACCP ROM says what it means. It is the
only undecoded field in the entire outgoing message.
The reply, specified as frames¶
Everything below is derived from MFCRECEIVE's gates (section 4y) re-expressed in wire terms
using section 1a. The responder is the MFbus device at the station being probed; the ACCP is at
its own station.
| # | C | Info | Value | Why |
|---|---|---|---|---|
| 1 | 1 | 0x35 |
start of multibyte, CMD 5 | framing |
| 2 | 0 | 0x05 |
own cmd, must be 5 | MFCRECEIVE byte-3 gate |
| 3 | 0 | N |
length | lands in buffer byte 4 |
| 4 | 0 | 0x00 |
must be zero | caller's check at 0x12D4 |
| 5.. | 0 | ... | N-1 further bytes | content unknown |
| last | 1 | 0x25 |
end of multibyte, CMD 5 | framing |
Plus, outside the frame content:
- transmitted from the station that was probed, so the hardware source field satisfies
MFCRECEIVE's
byte1 & 0x3Ftest - OBCON status must come back
0x8300 - delivered within 10000 poll iterations
- CMD 5 must be connected on the ACCP side (registration-table entry index 5)
Still unknown: N, and every byte after the leading zero. What little we know: byte 0 is a
status/OK field (zero = good) and byte 1 at 0x001131E7 is consumed immediately afterwards.
A checkable prediction about the CPU-model probe¶
Section 4y flags as unproven the inference that A0 in DetectCpuModelBySignature points at
0x001131E6, the reply buffer. Section 1b gives a way to test it.
The probe looks for 0x7F55 at +0, +4 and +0x0C, selecting model classes 0x5200 and
0x5400, with 0x5800 (ND-5800) as the fall-through. But byte 0 of that buffer is
required to be zero. So the word at +0 is always 0x00xx and can never match 0x7F55.
Two possibilities, and they are distinguishable:
- A0 is the reply buffer - in which case the +0 branch is unreachable on this path, and the model can only ever be selected from +4 or +0x0C.
- A0 is not the reply buffer, and the probe reads something else entirely.
This is the cheapest available test of that inference. [PREDICTION, not yet run.]
The frame-to-buffer mapping - now READ, not fitted [SOLVED 2026-07-28]¶
Previously this was the last open item: the reply spec above was fitted to MFCRECEIVE's
gates rather than read from the assembler. It is now read, from a second, independent
consumer of the same buffer - which is stronger evidence than reading the producer would
have been, because the two agree without having been made to.
DOREC_MULTI_OCTO is not a routine name; it is a PLANC error-message string at
0x12554 ("in DOREC_MULTI_OCTO$", file offset 75092), part of a 12-byte descriptor at
0x12548 ({pointer, 0, length}). Its one referencing routine is 0x9B98, which prints
"$Unexpected multibyte message " - the driver's own diagnostic for a message it cannot
route. To print it, that routine must decode the buffer, and it does:
9BA8 lea (0x11635C).l,A1 ; D0=0x24 ; A0 := entry+0x06 <- registration entry index 3
9BC0 move SR,(0x18,A6) ; move #0x898,SR <- masks interrupts while reading
9BD4 tst.w (0x2,A0) / 9BDC tst.w (0x8,A0) <- same two flag words as MFCRECEIVE
9C1C D1=0 : move.b (0x14,A0,D1),D0b ; andi #0xFF ; lsl #8
9C2C D3=1 : move.b (0x14,A0,D3),D2b ; andi #0xFF ; add.w D0,D2 ; move.w D2,(0x16,A6)
9C3C D4=3 : move.b (0x14,A0,D4),(0x15,A6)
9C44 D5=4 : move.b (0x14,A0,D5),(0x14,A6)
Byte indices 0, 1, 3, 4 off (0x14,A0) - exactly the indices MFCRECEIVE gates on,
in a routine written for a different purpose against a different registration entry. The
layout is therefore a property of the driver, not of one call site.
The receive buffer, data area at buf+0x14:
| Byte | Content | Evidence |
|---|---|---|
| 0 | high half of the source station word - always 0x00 for a legal station |
written at 0x1041E from descriptor+4 |
| 1 | low half of the source station word = the source station | written at 0x10406; MFCRECEIVE 0x1570-0x157E gates it |
| 2 | FLAGS byte written by the driver - see below. Not payload. | cleared at 0x1042E, then bits set at 0x106CA / 0x10BAA |
| 3 | first data byte = own-cmd, must be 0x05 |
written at 0x10446; MFCRECEIVE 0x1582 |
| 4 | second data byte = length N | written at 0x10464; MFCRECEIVE 0x158E |
| 5.. | remaining N data bytes = content | copied to 0x001131E6 (below) |
CORRECTION [2026-07-30] - the previous version of this table was wrong¶
The earlier reading said byte 0 was the START frame's information byte (0x35), byte 1 its
control/source half, and byte 2 unknown. All three were wrong. They were inferred from
MFCRECEIVE's gates and from what the 0x9B98 printer displays, rather than read from the code
that writes the buffer. Measured live, a completed reply from station 2 gives
data area: 00 02 80 05 02 00 09 ...
^^^^^ = 0x0002, the source station as a 16-bit word - NOT 0x35
^^ = 0x80, a flags byte - NOT unknown payload
Reading the writer at 0x10402-0x10464 settles it. It fills the data area from the message descriptor, not from the frame:
0x10410/0x1041Estore the high byte of descriptor+4 at buffer byte 0, and0x103EA/0x10406store its low byte at byte 1. Descriptor+4 is set at 0x10AF4 from the already-masked 6-bit source station. So bytes 0-1 are simply the source station as a 16-bit big-endian word, which is why byte 0 is zero and byte 1 carries the station.- There is no "information-byte-first" convention. That whole explanation was invented to account for a byte-1 placement that has a much duller cause: it is the low half of a word.
- The 0x9B98 printer reads bytes 0 and 1 and prints them as one word precisely because they are one word - the source station - not because it is reassembling a split frame.
Byte 2 is a flags byte, cleared at 0x1042E when the header is written and then set bit by
bit:
| Bit | Meaning | Set at |
|---|---|---|
7 (0x80) |
message complete | bset #7 at 0x106CA, in the close path |
6 (0x40) |
overran the buffer - the write pointer met the end pointer | bset #6 at 0x10BAA |
5 (0x20) |
written conditionally; the condition is NOT carved | bclr/bset at 0x106A4-0x106B8 |
A completed, non-overrunning message therefore reads 0x80, which is exactly what the
emulator measures. This is pinned by
ReceiveBuffer_HeaderIsSourceWordThenFlagsByte in AccpMfBusDiscoveryTests.cs.
Data frames contribute one byte each, not two. Bytes 3,4,5... are the wire data bytes
05 02 03 NN in order - their C=0 control halves are discarded. Only the START frame keeps
its control half, because that is where the source station lives. So the buffer is
[2-byte start-frame record][1 unknown byte][data bytes verbatim].
The copy at 0x15B0-0x15E4 confirms the tail exactly. Two PLANC array descriptors are built:
source: origo = (0x14,A0) lower = 5 upper = 5 + N - 1
dest: origo = 0x001131E6 lower = 0 upper = N - 1
So bytes 5 .. 5+N-1 land at 0x001131E6, N bytes. (Section 4y's "N-1 bytes" was an
off-by-one in the earlier reading - the descriptor limits are inclusive, so it is N.)
Registration-table geometry, as a free result¶
Both routines index g_obconProcessRegTable (0x11635C) the same way, and the two entry
offsets pin the stride:
| Entry | Offset | Used by |
|---|---|---|
| 3 | 0x24 |
0x9B98, the unexpected-message printer |
| 5 | 0x3C |
MFCRECEIVE, the MFbus discovery reply |
0x3C - 0x24 = 0x18 across 2 entries -> entry size 0x0C (12 bytes), and 0x24/0x0C = 3,
0x3C/0x0C = 5. The table is indexed by CMD number directly.
That entry 3 is the one the printer watches is a documentation match: ND-05.017.01 3.3.1 states that in the ND-5000 CPU, multibyte messages with CMD number 3 are handled by the Access Processor. Entry 3 is the ACP channel; the ACCP logs anything arriving there that it cannot route. Two unrelated sources landing on the same number is a good sign the whole CMD model is right.
Fields within an entry, so far: +0x02 and +0x08 are word flags (both consumers poll
(0x02) and (0x08); (0x08) != 0 breaks out immediately, (0x02) must be 0 to proceed),
+0x06 is the buffer pointer.
Still unknown in the buffer: only the condition behind flags bit 5. Byte 2 itself is now accounted for.
1e. THE RECEIVE ISR TAKES ONE FRAME PER INTERRUPT [CARVED 2026-07-30]¶
This is the single most important implementation fact for anyone emulating the card, and it is not stated in either manual.
0x0510 is the OCTObus receive ISR. It does exactly this:
0x0510 movem.l {D0,D1},-(SP)
0x0514 move.w (0x00880000).l,D0w <- reads ONE frame. No drain loop.
0x051A ...classify D0...
0x05A6 movem.l (SP)+,{D0,D1} ; rte
Classification is a priority ladder on the information byte, matching section 1a:
| Test | Condition | Route |
|---|---|---|
| 0x051C | D0 & 0x80C0 == 0x8040 |
C=1, K=1 -> kick -> 0x05C0 |
| 0x0530 | D0 & 0x80A0 == 0x8000 |
C=1, M=0 -> ident -> 0x05C0 |
| 0x0542 | D0 & 0xC0FF == 0xC0FF |
broadcast, info 0xFF -> master clear, jmp 0x0C72 |
| 0x059C | D0 & 0x8080 == 0x8080 |
C=1, E=1 -> emergency -> ignored |
| else | multibyte -> jsr 0x00010832 |
Consequence: an N-frame message needs N separate interrupts. A ten-frame reply is not
delivered by one interrupt that drains the FIFO; it requires ten. An emulator that presents
the receive interrupt coarsely - once per instruction batch, or only when the level rises
above what is already pending - will silently lose frames, and MFCRECEIVE will time out
after its 10000 poll iterations with a half-assembled message.
That was a real defect in the RetroCore ACCP machine, fixed 2026-07-30: Run() presented
interrupts once per 1024 instructions and picked a single highest source, so while the DUART
asserted IRQ5 the octobus IRQ3 was never presented at all. The symptom was slice-size
dependent - single-stepping accepted the MFbus reply, Run(16K) did not.
The per-station reassembly record - 0x0011641C + station * 0x20¶
0x10832 keeps one record per source station, stride 0x20:
| Offset | Meaning |
|---|---|
| +0x02 | sticky abandon flag; ORed in at 0x10A88 and any data frame is discarded while non-zero |
| +0x06 | the START frame's information byte; bit 5 is the open flag tested at 0x109FC |
| +0x07 | the sender's own CMD, taken from the first data byte; 0xFF while unset |
| +0x08 | ring write pointer into the buffer data area |
| +0x0C | message base offset; 0xFFFFFFFF until the length byte is seen |
| +0x10 | ring end pointer |
Both pointers are wrapped by 0x1080E, which subtracts the ring size held in the buffer header at buffer+0x0A. The record is fully reset at 0x107F6-0x10804 when the message closes.
Beware when reading these live. The driver clears the record on close, and 0xF4E6 re-initialises all 64 station records and all 16 registration entries. Sampling after a run therefore shows a wiped structure regardless of what happened - which is exactly how a working receive path was misdiagnosed as "only the first content byte arrives".
1d. CONTENT BYTE 0x03 - a supported hypothesis, NOT proven [2026-07-30]¶
0x03 is the last undecoded field in the outgoing discovery request. Nothing in ND-14001,
ND-05.017.01 or the ACCP ROM states what it means, and it is hard-coded at 0x125A
(move.b #0x3,(0x16,A6)), so no experiment on the emulator can vary it.
HYPOTHESIS (unproven): it is not a command code at all, but the ACCP's own Access Processor CMD number, making the two content bytes an announcement of the form "reach me on CMD 3, at station N".
What supports it:
- ND-05.017.01 section 3.3.1 states that in the ND-5000 CPU, multibyte messages with CMD number 3 are handled by the Access Processor (ACP). The ACCP is the access processor, so 3 is its channel number by documentation, not by guesswork.
- The ACCP has exactly two CMDs connected, measured live from the registration table at
0x0011635C: CMD 3 (buffer 0x00112C00) and CMD 5 (buffer 0x00112D40). Every other
entry is empty. So CMD 3 is genuinely a live receive channel on this card - a prerequisite
for announcing it. Dumped by
Diag_WhichCmdsAreConnected. - The ACCP's "Unexpected multibyte message" logger at 0x9B98 watches entry 3, which is what you would expect the ACP channel to be wired to.
- The second content byte is already proven to be the sender's own station. A pair of "my CMD, my station" is a coherent message; "opcode 3, my station" is equally coherent, which is why this stays a hypothesis.
Corroboration found 2026-07-30 - three independent sources¶
1. Norsk Data's own symbol table defines 3 as the ACCP's OMD number.
SINTRAN\ND500\swapper\N500-SYMBOLS.SYMB line 6016: OMDAC=000003, mapped in
ND500-ND5000-INTERFACE-COMPREHENSIVE-GUIDE.md as "OMDACCP / OMDAC / 000003 / 3 / ACCP OMD
number". So the value the ACCP transmits is, by ND's own definition, its own OMD number.
That is documentation, not inference.
2. SINTRAN has a named routine for exactly this operation. SUBR MFPREPARE in
NPL-SOURCE\NPL\MP-P2-N500.NPL (~line 3586) builds a message to the MF controller and carries
the explicit source comment:
MFOMDNO =: X.MOCTOMD % OMD number
0 =: X.MBROADCAST % Not broadcast
3 =: X.MMSGLENGTH % Message length = 2 bytes
CMSYSPAR SHZ 10\/N100IDENT=:X.MCOMMAND % Send OMD numer to mf-controller
5OMDNO SH 10=:X.MDP1
"Send OMD numer to mf-controller" is ND's own wording. Announcing your own OMD number to the MF controller is a real, named operation in this protocol family - not something this document invented. It also uses a 2-byte message body, like the ACCP's.
3. The ACCP connects exactly the two CMDs the hypothesis needs, with sizes that fit their roles. Read from the two OBCON fn 0x49 ("connect CMD") requests at 0x0CF8 and 0x0D56:
| CMD | Buffer | Size | Role |
|---|---|---|---|
| 3 | 0x00112C00 |
0x13F = 319 bytes |
large - the ACCP command library channel SINTRAN drives on OMD 3 |
| 5 | 0x00112D40 |
0x3F = 63 bytes |
small - the MFbus control / discovery channel |
Confirmed live by Diag_WhichCmdsAreConnected: every other entry is empty.
4. There is no CMD-5 opcode vocabulary. The payload slot move.b #imm,(0x16,A6) has
exactly one site in the entire ROM (0x125A). If 0x03 were an opcode you would expect a
family of them; the ACCP only ever sends this one value.
Why this is still NOT proof¶
Two things genuinely do not line up, and are recorded rather than explained away:
- The field positions differ. In
MFPREPAREthe sender's own OMD goes in DP1 - the second content field - while the first carries a command code (CMSYSPAR|N100IDENT). In the ACCP's message the second content byte is proven to be the station number read from 0x900001, not an OMD. The two messages are therefore not the same shape, and the analogy is suggestive rather than binding. MFOMD=000004in N500-SYMBOLS, while the ACCP uses CMD 5 for its MF channel. Whether the symbolMFOMDis the same thing as the NPL variableMFOMDNOis UNVERIFIED. This discrepancy is unexplained.
Above all: nobody has yet observed an MF controller parsing that byte. All of the evidence above is about the sender.
What would settle it: the Octobus Driver Programming Guide (DVT, 15 Oct 1986), or an
MFbus-controller ROM showing what it does with the byte. Neither is in this repository -
OctobusAccp\eprom\51200J.bin and 51201J.bin are only the two EPROM halves of octo.bin
itself, not a second card.
Do not encode this hypothesis as fact in emulator code. The peer in
AccpMfBusControllerPeer.cs treats the byte as opaque and must continue to.
1f. MOCTOMD IS THE DESTINATION OMD - this reframes 1d [2026-07-31]¶
A re-read of the NPL octobus builders in
E:\Dev\Ronny\NDInsight\SINTRAN\NPL-SOURCE\NPL\MP-P2-N500.NPL corrects the field reading
that section 1d used as its main counter-argument.
There are four builders, not one, and they split cleanly by destination:
| Routine | Line | X.MOCTOMD written with |
Destination |
|---|---|---|---|
XRS5CPU (Reset CPU) |
3334 | OMDACCP |
the ACCP |
| (MF path) | 3525 | MFOMDNO |
the MF controller |
MFPREPARE |
3589 | MFOMDNO |
the MF controller |
CON5IDENT |
3623 | OMDACCP |
the ACCP |
X.MOCTOMD therefore holds the OMD number being addressed, not the sender's own. The
sender's own OMD travels in the payload - 5OMDNO SH 10 =: X.MDP1 in MFPREPARE,
5OMDNO SHZ 10 =: X.S5 in CON5IDENT.
Consequence for 1d: the counter-argument recorded there - "MFPREPARE puts own-OMD in DP1
while the ACCP's second byte is the station number, so the shapes differ" - was based on
reading MOCTOMD as the sender's OMD. It is not. Both ends do the same thing: address the
peer by its OMD in the header, and announce your own identity in the payload. The ACCP's
[0x03][station] is exactly that announcement, and 1d's hypothesis is stronger than it was.
And the OMD/CMD numbers line up. OMDAC=000003 (N500-SYMBOLS.SYMB:6016) is the number
SINTRAN addresses the ACCP by, and the ACCP connects a receive buffer on CMD 3 at ROM
0x0CF8. Same number, both sides, independently sourced. The acceptance gate at 0x10934 and
0x10A12 admits only CMD 0, 3 or 5 and calls the error path at 0x05C0 for anything else -
so the card really does listen on just those.
STILL NOT PROVEN, and one discrepancy survives. If CMD were universally the same namespace
as OMD, then the ACCP addressing the MF controller on CMD 5 would make the MF controller
OMD 5 - but the symbol table says MFOMD=000004 (line 5497). Either CMD is a per-station
connection number that merely coincides with OMD for the ACCP, or MFOMD is not what
MFOMDNO resolves to. MFOMD vs MFOMDNO remains UNVERIFIED - MBSEND is not in the
NPL sources present here, so the header-to-wire mapping cannot be carved from them either.
Full OMD namespace found in N500-SYMBOLS.SYMB:
OMDAC=000003 ACCP
OMDSO=000003
MFOMD=000004 MF controller
OMDTA=177767 (-9)
OMDEN=177766 (-10)
OMDNO=177777 (-1, "none")
Also worth noting from XRS5CPU: SINTRAN reaches this path only for
CPUAVAILABLE /\ 5CPUTYPE = SAMSON, i.e. it is the Samson-specific CPU-reset route, and
CON5IDENT's own header comment is "To be able to receive multi byte messages/kicks from the
ACCP/Samson". Both name our card explicitly.
1g. FLAGS-BYTE BIT 5 = BROADCAST - CARVED [2026-07-31]¶
The last uncarved bit of the driver flags byte (section 1e) is now settled. It is the START frame's broadcast bit, latched through the per-station record.
Set/clear on completion, at 0x106A4-0x106B8:
0001068e move.w (A2),D5w ; A2 = per-station record
00010690 move.w D5w,(0x50,A6)
000106a4 lea (0x14,A1,D0*0x1),A0 ; A1 = buffer base, 0x14 = data area
000106a8 move.b (A0),D1b
000106aa bclr.l #0x5,D1
000106ae tst.w (0x50,A6)
000106b2 beq.b 0x000106b8
000106b4 bset.l #0x5,D1
000106b8 move.b D1b,(A0)
Record +0x00 is written once, by the START handler at 0x10986-0x1099A, straight from frame bit 14:
00010986 move.b (A0),D1b ; A0 = frame; byte 0 is the HIGH byte
0001098c moveq #0x0,D2
0001098e btst.l #0x6,D1 ; bit 6 of high byte = frame bit 14 = B
00010992 beq.b 0x00010996
00010994 moveq #0x1,D2
00010996 movea.l (0x50,A6),A1
0001099a move.w D2w,(A1) ; record+0x00 := broadcast
Frame bit 14 is the B (broadcast) bit of the OBCON frame. Corroborated independently on the
SINTRAN side: every builder in 1f writes a dedicated X.MBROADCAST header field, set to
0 with the comment "Not broadcast". Both ends model broadcast as a per-message property.
Per-station record layout, refined¶
The START handler at 0x10996-0x109BA initialises the whole record, which pins several offsets that were previously only inferred:
| Offset | Written at | Meaning |
|---|---|---|
| +0x00 | 0x1099A | broadcast flag (word), from frame bit 14 |
| +0x02 | 0x109B4 | cleared (abandon) |
| +0x04 | 0x109B8 | cleared |
| +0x06 | 0x1099C | information byte of the START frame; low nibble = CMD |
| +0x07 | 0x109AE | own CMD, initialised to 0xFF, not 0 - so 0 is a legal value and 0xFF means "not yet received" |
| +0x08 | 0x109A2 | ring write pointer, clr.l |
| +0x0C | 0x109A6 | set to -1 |
Record base is 0x0011641C + station * 0x20; the scale is carved from
moveq #0x3f,D0 / move.b (A2),D1b / and.b D0b,D1b / asl.l #0x5,D1 at 0x10902-0x10914.
Bonus: a debug flag at 0x001143B4 gates a trace that prints the raw frame word followed
by the string " to ACCP" (descriptor at 0x123EA). Setting it makes the card narrate its own
octobus receive path.
Locked by tests: ObconMessageTests.ReceiveFlags_Bit5IsTheBroadcastBitOfTheStartFrame and
ObconMessageTests.StationRecord_LayoutMatchesTheStartHandler in
E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.Chips.NorskData\tests\ObconMessageTests.cs.
1h. THE CMD-3 COMMAND CHANNEL - the card answers, and 0x3E converts the model [2026-07-31]¶
Everything before this drove CMD 5 (MFbus discovery). CMD 3 - the 319-byte buffer connected at ROM 0x0CF8 - is the SINTRAN-to-ACCP command channel, and it round-trips.
The card narrates it itself¶
The ROM has a console command for this, in the command table at 0x13357:
TRACE-COMMUNICATION-DATA <Trace Octobus communication to consol? (y/n)>
(the firmware's own spelling of "consol"). YES writes 1 to 0x001143B4 at 0x9D84, NO clears it
at 0x9D8E. Eleven sites read the flag, covering both directions - Areceive at 0x1095A / 0x10A3A
inbound, OctoTxTracePrint_fromACCP_A / _B at 0x11078 / 0x110C0 outbound.
This is the card describing itself, not our instrumentation describing what we chose to model. It should be the first move on any future protocol question.
Observed, sending content 03 02 on CMD 3 with the tracer armed:
8233 0203 0202 0203 0202 8223 to ACCP
Undefined ACCP command received:
Octal: 003B Hexadecimal: 03H
8233 0203 0204 02FF 0206 0210 0211 8223 from ACCP
Content byte 0 on CMD 3 is an ACCP COMMAND NUMBER¶
The card echoed our 0x03 back in the complaint as 03H. The dispatcher is a compare chain
(PLANC CASE); its default arm is at 0x6746 and emits 0xFF 0x06.
This does NOT settle the section-1d question. That one is about an OUTBOUND message on CMD 5; this is an INBOUND message on CMD 3. Different channel, different direction. The two need not share a content layout, and nothing here shows they do. Do not let "byte 0 is a command on CMD 3" quietly become "byte 0 is a command on CMD 5".
What it does show is that 0x03 is not a defined ACCP command - so if the discovery request's
0x03 were an opcode in this same namespace, it would be an undefined one.
Reply shape¶
[status][code][...]. Status 0xFF is the failure marker on every arm carved so far:
| Condition | Emits | Carved at |
|---|---|---|
| undefined command | FF 06 |
0x6746 |
| model never confirmed (0x1131FA = 0) | FF 0B |
0x66E2 -> 0x6714 |
| CPU class is 0 | FF 31 |
0x66DA -> 0x672C |
| success (command 0x3E) | 00 <packed> |
0x66E4 -> 0x6712 |
UNVERIFIED: the trailing 10 11 seen after FF 06 in the observed reply. Not carved; probably
from the tail builder at 0x6A64, but that is a guess and is not asserted anywhere.
Command 0x3E is the CPU-model converter¶
Carved at 0x66BA-0x6712:
000066ee move.b (0x001131f6).l,D0b ; CPU class - a BYTE
000066f8 lsl.w #0x4,D0w ; class << 4
000066fa move.w (0x001131f8).l,D1w ; identity word, e.g. 0x5900
00006700 asr.w #0x8,D1w ; -> 0x59
00006702 moveq #0xf,D2
00006704 and.w D2w,D1w ; -> 0x09, bare model digit
00006706 or.w D0w,D1w ; (class << 4) | digit
This reconciles the two CPU-model encodings that ANSWER-CPU-MODEL-ENCODINGS-2026-07-30.md warned must never be plumbed into each other:
- the MFbus discovery reply carries the bare digit (
0x09), widened to an identity word by0x5000 | (digit << 8)at 0x12EC-0x12FA; - SINTRAN's WRSYSINFO byte is packed - bits 0-3 model digit, bits 4-5 CPU type.
They are not rival conventions needing a decision. Command 0x3E is the converter, and it lives on the card. The ACCP holds class and digit separately and packs them only when SINTRAN asks.
Class 3 + digit 9 packs to 0x39, which is also ASCII '9' - the documented collision. It is
not text.
Note also: the class at 0x001131F6 is a BYTE (move.b). Reading it as a word yields 0x0300
for class 3.
Locked by tests¶
In E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.Machines.Accp\tests\AccpOctobusTraceTests.cs:
TraceCommand_SetsTheFlagTheOctobusPathReads- the console command reaches 0x001143B4 both ways.Cmd3_IsAnAccpCommandChannelAndTheCardAnswers- inbound CMD 3 is parsed as a command and answered.Cmd3_Command3EReportsThePackedCpuModelByte- 0x3E is defined, and the packed byte is computed the way the firmware computes it.Diag_TraceOutputWhileTheBusIsBusy- diagnostic, prints the narrated trace.
The peer gained AccpMfBusControllerPeer.SendMessage(destinationCmd, ownCmd, content) for
unsolicited traffic; before this it could only answer the discovery scan.
1i. THE COMPLETE ACCP COMMAND SET - 46 commands, enumerated and verified [2026-07-31]¶
The CMD-3 dispatcher (section 1h) is a PLANC CASE compiled to a chain of
cmpi.b #imm,D0 + bne, each arm's bne pointing at the next.
Enumerated by walking the chain: head at 0x4D50, following every branch target, terminating at the default ("Undefined ACCP command received") arm at 0x6746. 46 arms.
Defined commands - three contiguous runs¶
0x0D - 0x18 (12)
0x1B - 0x2D (19)
0x30 - 0x3E (15)
---
46
Undefined in 0x00-0x3F (18)¶
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 19 1A 2E 2F 3F
0x03 is not a defined ACCP command. That is the one hard fact this enumeration
contributes to the section-1d question: if the discovery request's 0x03 were an opcode in the
CMD-3 namespace, it would have an arm here. It does not. (It remains true that CMD 5 outbound and
CMD 3 inbound need not share a content layout - see 1h.)
Verified against the running card¶
Cmd3_EveryUndefinedCommandIsRefusedIncluding0x03 sends all 18 undefined numbers to a booted
machine on CMD 3 and requires the card to print "Undefined ACCP command received" for every one.
It does. Only the undefined numbers are swept: the defined ones include microprogram start/stop
and control-store loads, so sweeping those would hang the card partway through and make every
later result meaningless.
Two methodology mistakes worth not repeating¶
Both were made here first, and both were caught by the running card rather than by reading:
-
A byte-pattern scan over a guessed address window missed an arm. Scanning for
0C 00 00 immfrom 0x4E00 upward found 46 arms but missed0x13at 0x4D50 - the chain starts lower than assumed. The sweep test caught it:0x13printed nothing at all where a refusal was expected, because it is a real, silently-handled command. -
The "cross-check" that appeared to confirm the scan was worthless. The scan's 46 hits were matched against 46 cross-references to the dispatcher's common exit at 0x6878 and the agreement was taken as proof of completeness. It proves nothing: arms reach the exit indirectly (the 0x13 arm branches to 0x4ED8, which branches to 0x6878), so the two counts can agree while the set is wrong - which is exactly what happened. Only walking the chain is sound.
Also: 0x01 is not a command. A cmpi.b #0x01 + beq at 0x63DC looks like a chain node to a
pattern scan, but it is an inner test inside another handler. The chain walk excludes it correctly
and the card refuses 0x01.
Reply convention - confirmed empirically¶
Decoded from the peer's side, not inferred:
| Command | Reply content | Reading |
|---|---|---|
0x3E |
00 39 |
status 0 = OK, then the packed model byte: class 3, digit 9 = ND-5900 |
0x30 |
00 07 7F |
status 0, then 0x077F - the selftest status word |
0x1F |
FF 07 10 11 |
status 0xFF = error, error code 07 |
| undefined | FF 06 10 11 |
error code 06 = undefined command |
So byte 0 is a status (00 OK / 0xFF error), byte 1 is an error code on the failure path, and
10 11 is a constant trailer on the error replies. The 0x3E carve in section 1h is confirmed
by execution: the card really does emit (class << 4) | digit.
Do ACCP command numbers share the console command table's numbering? MIXED - do not assume¶
The console command table (entries near 0x130FE: a leading word plus a PLANC string descriptor) carries codes 0x03-0x46. Almost every code in 0x1F-0x3E also has a CMD-3 dispatcher arm, and the console-only codes are exactly the interactive ones - HELP, VALUE, MAIN-FORMAT, SHOW-REGISTERS, DUMP-LOCAL-MEMORY, LOOP-ON-NEXT-COMMAND, SET-CLOCK-SPEED, RESET-CPU, TEST-MEMORY, TEST-BUSLOOP. That is what a shared namespace would look like.
But the evidence does not agree with itself:
0x30matches. Console nameREAD-ACCP-STATUS; the card replies00 07 7F, i.e. the selftest status word. Exactly the console name's operation.0x3Edoes not match. Console nameTEST-BUFFERS; the card replies with the packed CPU model byte. Not a buffer test by any reading.
SETTLED 2026-07-31 - they are SEPARATE enums that merely overlap.
The earlier hedge here blamed the console-table parse. That was wrong: ACCP-COMPLETE-REFERENCE.md
part 3 carries the same table as VERIFIED, with the same record layout
{word code, long origo, long lower, long upper} and the proof that
(0x13358 - 0x130FE) / 14 = 43 exactly. The parse was right and the mismatch is real.
The decisive probe is command 0x3C. In the console table 0x3C is
TRACE-COMMUNICATION-DATA, whose whole observable effect is the flag at 0x001143B4 - a single
bit that can be read directly. Sent over CMD 3 to a booted card:
--- ACCP command 0x3C ---
reply cmd=3 ownCmd=3 content=[ FF 01 10 11 ]
trace flag before : 0
trace flag after : 0
0x3C is a defined octobus command (it is inside the 0x30-0x3E run and returns error code 01,
not the "undefined command" complaint), it does something, and that something is not
toggling the trace flag. So the console command numbering and the CMD-3 command numbering are two
different enums.
0x3E is the clean confirming case: TEST-BUFFERS on the console, packed CPU model over CMD 3.
0x30 returning the selftest status 0x077F, which matches the console name READ-ACCP-STATUS,
is then either coincidence or two related enums independently assigning a status read to the same
number. Not resolved, and not important enough to guess about.
This answers an open question in ACCP-COMPLETE-REFERENCE.md¶
Part 3 records the console codes as sparse and asks what the holes mean:
Used: 03, 06, 07, 09, 0A, 0C, 1F, 20-2F, 30-3F, 40, 41, 42, 46. Absent: 04, 05, 08, 0B, 0D-1E, 43, 44, 45. This looks like a global ND command-code enum that the console shares with something else - most likely the ACCP-ND100 command set [...] UNVERIFIED.
The CMD-3 octobus command set is 0x0D-0x18, 0x1B-0x2D, 0x30-0x3E. It does cover the
console's largest hole (0x0D-0x1E), which is why the shared-enum reading is attractive. But the
0x3C probe rules it out for this consumer: the octobus command set is not the thing the
console enum is shared with. The holes remain unexplained, and the ACCP-ND100 guess is refuted for
the CMD-3 path specifically.
Still open¶
The 46 commands are enumerated but not named. The console command table at 0x13357 has a comparable number of entries (LOAD-CONTROL-STORE, READ-ACCP-STATUS, LOOK-AT-LOCAL-MEMORY, ...), so a mapping from ACCP command number to console command is plausible - but it is UNVERIFIED and has not been attempted. Naming them means carving each handler.
Known so far: 0x3E = report CPU model (section 1h).
2. Acknowledge and retries - and the ACCP's error strings¶
The receiver drives two acknowledge bits at the end of every frame:
| Ack | Normal transmission | Broadcast | Default retries |
|---|---|---|---|
| 0 0 | node not present | nodes not present | 15 |
| 0 1 | successful | successful | - |
| 1 0 | destination busy | destination busy | 255 |
| 1 1 | parity error | ambiguous response | 15 / 0 |
Every one of these has a matching message in the ACCP firmware, which is strong mutual confirmation:
| Ack | ACCP string |
|---|---|
| 0 0 | "$Octobus destination not present$" |
| 1 0 | "$Octobus retry timeout$" (the busy path, retried 255 times) |
| 1 1 | "$Octobus parity error$" |
The retry count is programmable in the OCTObus Transmitter Control Register, and the Transmit Status Register reports whether a message was retransmitted. Neither register's ACCP address is known yet.
3. Station numbering - all octal¶
- Global OCTObus (cable): stations 0-17B, set by thumbwheel switch.
- Local OCTObus (MFbus backwiring): stations 77B down to 20B, set by on-board registers written by the MFbus controller.
- Station numbers must be unique. 0 and 77B... (0 and 63 decimal) are illegal as destinations.
This is why the ACCP prints "not found at Octobus stations 2-7." and
"$Not prepared for message from station ", and it constrains what an emulated station
number may be.
Power-fail semantics depend on the number: a power-down message from stations 1-17B means real power fail; the same message from 20B-76B means "fatal controller hardware failure". The receiver distinguishes them purely by source.
4. Hardware-decoded messages - the CM* codes¶
These are acted on by the OBCON gate array without software reading them, and they can pull a hung processor out via a non-maskable level-7 interrupt.
| Octal | Name | Effect |
|---|---|---|
| 241 | RESTART | asserts RESET, restarts the controller after a total reset |
| 242 | CONTINUE | deasserts HALT |
| 243 | STOP | asserts HALT; stays asserted until CONTINUE arrives |
| 244 | INT7 | generates a level-7 interrupt (OCINT7), clearable by software |
| 245 | RESCOUNT | resets the time reference counter |
| 376 | POWERUP | |
| 377 | POWERDOWN |
RESET-CPU (ACCP command 0x46) and the START/STOP/CONTINUE-MICROPROGRAM commands map onto
this set conceptually. Whether the ACCP issues them as OCTObus messages or through a local
register has NOT been established - do not assume.
The INT7 reset register (OCINT7) is write-only, carries no data, and any write clears the INT7 condition. Its address is given as FF810E - but that is the DIOC processor's address map, not the ACCP's. The ACCP equivalent has not been found.
4y. THE MFBUS DISCOVERY EXCHANGE - payload and accepted reply [CARVED 2026-07-28]¶
This is the one octobus conversation the firmware has at boot, and it is fully specified. An
emulator that answers it correctly gets MFbus controller recognised and a real
CPU model: line instead of the ND-5800 fall-through.
What the ACCP SENDS - 2 bytes¶
MfBusControllerConfigCheck @0x121C builds an OBCON function 0x41 request per station
2..7. The message descriptor is {origo = &(0x16,A6), lower = 0, upper = 1} - so the payload
is exactly two bytes:
| Byte | Value | Set at |
|---|---|---|
| 0 | 0x03 |
0x125A move.b #0x3,(0x16,A6) |
| 1 | the ACCP's own station number = HW_ACCP_STATION_CONFIG (0x900001) & 0x1F |
0x1268 |
Request block: fn 0x41 at +0x00, status at +0x02, process 0x05 at +0x06, destination
station at +0x0C, the descriptor at +0x10.
What the ACCP ACCEPTS - MFCRECEIVE @0x14B4¶
Every one of these is a hard gate; fail any and the station is rejected.
- OBCON status must be
0x8300(checked at 0x12AE before MFCRECEIVE is even called). - The reply is picked up from registration-table entry index 5 -
g_obconProcessRegTable (0x11635C) + 0x3C, buffer pointer at entry+0x06. Index 5 matches the+0x06 = 5process number in the request, and the same tableObconFn49registers into. Register process 5 or nothing will ever be delivered. - Poll limit
0x2710= 10000 iterations, waiting for(0x08,buf) != 0or(0x02,buf) != 0. On timeout MFCRECEIVE returns 2. (0x02,buf)must be 0 - non-zero takes the error path at 0x1616.- Payload begins at
(0x14,buf):
| Offset | Test | Meaning |
|---|---|---|
| byte 1 | & 0x3F must equal the station addressed |
the 6-bit OCTObus source field (frame bits 13..8) |
| byte 3 | must be 0x05 |
process / subprocess, echoing what we sent |
| byte 4 | N - a length | payload runs from byte 5 |
| bytes 5.. | copied to 0x001131E6, N-1 bytes |
the actual reply data |
- Back in the caller (0x12D4):
(0x001131E6)byte 0 must be 0, else branch to 0x1308. Byte 1 at0x001131E7is consumed next.
Then the CPU model¶
[REFUTED - do not build on the inference below. It is kept only because the wrong version may already have been read.] A0 is NOT the reply buffer.
0x112Cloadslea (0x00114550).l,A0- the signature matrix in ACCP local SRAM. This file's own section 4b already says "ANSWERED: no", and the whole derivation is now carved and live-verified in part 5 ofACCP-COMPLETE-REFERENCE.md. Model detection is emphatically NOT a pure protocol matter: the reported model is cross-checked against a class the ACCP derives from its own hardware, and a responder built on this paragraph would be wrong.
DetectCpuModelBySignature (0x110A region) probes (A0)+0, +4, +0x0C for the signature
0x7F55 to pick the model class (see part 3 of ACCP-EMULATION-STATUS-AND-HANDOFF.md).
[INFERENCE - well supported,
not proven] A0 at that point is very likely 0x001131E6, the reply buffer - i.e. the
signature is looked for in the MFbus controller's reply, not in ND-5000 memory. That would
make model detection a pure protocol matter with no memory probing at all.
Why it is not proven: Ghidra reports no callers for the 0x110A entry - it is reached by fallthrough or branch from the enclosing code, so A0's origin has not been traced. Confirm before building a responder around it.
4z. THE DRIVER API - ObconRequestDispatch @0xF686 [FOUND 2026-07-28]¶
Above the raw registers there is a request-block driver with 17 function codes. Every octobus operation that is not a single raw frame goes through it. This is the interface an emulator has to satisfy, and it was invisible until the image was fully disassembled.
A0 -> request block; the word at (A0) is the function code. The routine is a linear
cmpi.w chain, codes 0x41-0x51, each tail-calling its handler, all converging on 0xF80E.
The request block¶
| Offset | Size | Field |
|---|---|---|
| +0x00 | word | function code |
| +0x02 | word | status returned. SUCCESS == 0x8300 - callers test cmpi.w #-0x7D00, which is that value |
| +0x06 | byte | kick value / process (fn 0x43) |
| +0x0C | word | destination station (fn 0x43) |
| +0x10 | 12 | PLANC array descriptor {origo, lower, upper} over the message buffer (fn 0x41) |
On failure the caller prints "$Octobus transmit error " (descriptor 0x124AA).
The 17 function codes¶
| Code | Handler | What |
|---|---|---|
| 0x41 | 0xF8E6 | multibyte message - used by Cmd3A_SendMultibyteOctobus (sets it at 0x99C2) and by the microprogram-command region (0x6AC6). Very likely DOSEND_MULTI_OCTO / DOREC_MULTI_OCTO |
| 0x42 | 0xF8E6 | same handler as 0x41 - it must branch internally on the code |
| 0x43 | 0xFADC | send kick - PROVEN, Cmd3B_SendKickOctobus sets it at 0x9B54 |
| 0x44 | 0xFB48 | unknown |
| 0x45 | 0xFBB4 | unknown |
| 0x46 | 0xFC20 | unknown |
| 0x47 | 0x1011C | unknown |
| 0x48 | 0x1018A | unknown |
| 0x49 | 0x101F8 | boot init - the only code issued at startup, twice (0x0D26, 0x0D82). Matches ND-14001 section 4.8.1: a node must be given station number, broadcast type, power-fail handler station and bus speed before use |
| 0x4A | 0x10154 | unknown |
| 0x4B | 0x101C2 | unknown |
| 0x4C | 0x102B4 | unknown |
| 0x4D | 0xF600 | unknown (reached by bsr.w) |
| 0x4E | 0xF620 | unknown (bsr.w) |
| 0x4F | 0xF5A2 | unknown (bsr.w) |
| 0x50 | 0xF5DC | unknown (bsr.w) |
| 0x51 | 0xF5EE | unknown (bsr.w) |
Five codes use bsr.w with a negative displacement rather than jsr abs - a scan that only
looks for jsr will miss them, as mine initially did.
Layering: OctobusTransmitWord / OctobusReceiveWord are the single-frame primitives the
SEND-OCTOBUS / RECEIVE-OCTOBUS console commands use directly. ObconRequestDispatch is
the layer above, for kicks, multibyte messages and initialisation.
The dispatcher is a SOFTWARE layer - it touches no hardware¶
A byte sweep of four handler bodies - 0xF8E6 (fn 0x41/0x42), 0xFADC (fn 0x43), 0x101F8 (fn 0x49) and 0x102B4 (fn 0x4C) - finds no access to any peripheral select in any of them. They are pure RAM.
That changes what an emulator has to do. The real octobus register I/O happens in exactly two places:
OctobusTransmitWord@0x7890 /OctobusReceiveWord@0x786C - the raw frame primitives- the IRQ3 (0x0510) and IRQ7 (0x0826) handlers - message arrival and FIFO drain
Model those and the dispatcher runs as ordinary code on top.
ObconFn49 @0x101F8 - what "boot init" actually is¶
It does not program the bus. It registers an entry in a table at 0x0011635C, 12 bytes per entry, indexed by the byte at request+0x06:
index = (byte)(request+0x06); entry = 0x0011635C + index*12
entry[0x00] = 1 ; word, in-use / state
entry[0x02] = request[0x08] ; longword copied straight through
Called twice at boot (0x0D26, 0x0D82). Two registrations - consistent with a card declaring
which processes may receive octobus messages, which is what the error string
"$Not prepared for message from station " complains about. The name BootInit describes
when it runs, not what it does; treat it as provisional.
The request block is NOT one uniform struct. Fn 0x49 reads its array descriptor at
+0x0E; Cmd3A (fn 0x41) writes one at +0x10. Read the layout from the handler you
care about.
Still unknown: which code, if any, exposes the Transmitter Control / Transmit Status registers of section 2 (retry count, retransmit indication). Thirteen of the seventeen handlers remain unread, and since none of the four read so far touches hardware, those registers may not be reachable through this API at all.
4a. SETTLED 2026-07-28 - the octobus data registers are PROVEN¶
Section 4b below retracted the octobus attribution of 0x880000 and 0x770004 as unsound,
because it rested on frame width alone. That retraction was right at the time and is now
superseded: the call chains from the console commands prove it outright.
After the noreturn flag was cleared and PlancFixFlow re-run headless, the command handler
bodies became readable:
Cmd38_SendOctobus @0x97CA console: SEND-OCTOBUS <Data (16)>
-> PromptAndReadParameter 0x295C read the operand
-> ParseNumberInCurrentBase 0x47C8 convert, base from 0x001131FC
-> OctobusTransmitWord @0x9854 <-- writes 0x00770004
loops while 0x0011313C != 0 (LOOP-ON-NEXT-COMMAND)
Cmd37_ReceiveOctobus @0x9748 console: RECEIVE-OCTOBUS
-> print descriptor 0x12400 = "$Octobus receive fifo: "
-> OctobusReceiveWord @0x9770 <-- reads 0x00880000
loops while 0x00113138 != 0
-> format in the current base and print
RECEIVE-OCTOBUS printing "Octobus receive fifo:" immediately before reading 0x880000
is the firmware naming the register itself.
| Address | Dir | Width | Role |
|---|---|---|---|
0x00770004 |
write | word | OCTObus transmit data |
0x00770007 |
read | byte | bit 3 = transmit ready |
0x00880000 |
read | word | OCTObus receive data (FIFO) |
0x00660001 |
read | byte | bit 2 = receive data available |
This is consistent with the interrupt paths rather than in conflict with them: IRQ3 takes one
message from 0x880000, IRQ7 drains it in a loop while 0x660001 bit 2 stays set. It is a
FIFO with a data-available flag, and both earlier readings were right.
Routines renamed accordingly: OctobusTransmitWord @0x7890, OctobusReceiveWord @0x786C.
Both are PLANC NATIVE shape (A6 == A7, plain rts); PlancApplyConvention correctly
left both alone when it set __planc on 117 other routines.
Still true and still important: neither ready-poll has a timeout. Both are unbounded
beq.b -10 spins. An emulator that never raises 0x770007 bit 3 or 0x660001 bit 2 will
hang the ACCP exactly as real hardware would.
Still open: the Transmitter Control Register and Transmit Status Register that ND-14001
section 4.4 refers to (retry count, retransmit indication) have not been located. 0x330000
/ 0x330001 and 0xAA0000 remain the candidates.
4b. CORRECTIONS to section 5, made the same day¶
Section 5 was written before I re-read the firmware write-up of record (then
ACCP-324716-FIRMWARE-RE-2026-07-27.md, now part 1 of ACCP-COMPLETE-REFERENCE.md), which
was already further along than I realised. Three things in section 5
are wrong or overstated. They are corrected here rather than quietly edited, because the
wrong version may already have been read.
-
0x770004/0x770007are NOT a new discovery. I wrote that select 0x77 "was not in any earlier hardware list" and that my sweep found it. False - the RE document's section 2.4b already recorded it, frommovea.l #0x770004,A1at 0x069E in the IRQ3 handler, including that data arrives into it from0x440000and that bits 3 and 4 of0x770007are a handshake with a retry count of 10. My sweep rediscovered it. -
The routines at 0x786C and 0x7890 sit inside
0x71F8-0x7C14, which the RE document identifies as the CONTROL STORE loader, not as the octobus driver - it is named by the firmware's own string"C O N T R O L S T O R E E R R O R in buffered CI-bits 35 or 40.". So calling them "the OCTObus transmit/receive path" on frame-width grounds alone was the exact mistake that document warns about twice: generalising a port model from a single routine. The mechanics I carved stand; the octobus attribution does not. -
What
0x880000actually is, per the RE document, is better than my guess and partly overlaps it: it is the message / kick read port, with0x660001bit 2 as its data-available flag. IRQ3 takes one message; IRQ7 drains it in a loop. So it is genuinely FIFO-like and genuinely the octobus message path - but that was established from the interrupt handlers and the" from SAMSON"/" to SAMSON"trace strings, which is real evidence, not from frame width.
Also relevant and already known there, which section 5 does not reflect:
0x220000is a general command/function port, not an MF-bus port. The function code selects which target the0x440000/0x550000data pair talks to:0x300F/0x400A/0x400C/0x000Fdrive MF-bus memory,0x0005drives the AOB,0x0018performs a control-store operation.0x330000and0x330001are write-only latches with RAM shadows at0x001144EEand0x001144EF. The firmware never reads them back.0x330000bit 6 is a write strobe, bit 2 gates a control-store operation.0x660001is a shared status byte whose bits belong to different functions: bit 1 = AOB busy, bit 2 = message available, bit 4 = MF-bus complete.0x00900007is real and breaks the nibble-replication rule (0x90is not a repeated nibble). Section 1 of this document should not be read as a law.
5. What the ACCP driver actually does - carved¶
Three leaf routines were recovered and named. All three are link/unlk with A6 == A7
and a plain rts - the PLANC NATIVE shape (ND-820026.1 Figure 9), or hand-written
assembler. Do not apply __planc to them.
HwWaitRxReadyThenReadWord_880000 @0x786C¶
btst.b #2,(0x00660001).l ; poll
beq.b -10 ; spin - NO TIMEOUT
move.w (0x00880000).l,... ; 16-bit read, result in D0
rts
Established: 0x00880000 is a 16-bit input data register gated by bit 2 of the status
byte at 0x00660001.
Inferred: this is the OCTObus receive path. The width matches the documented 16-bit input
frame exactly, the string "Octobus receive fifo: " exists, ND-14001 Figure 29 places a FIFO
inside OBCON, and the routine sits in the 0x6A74-0x7C14 driver region. Not proven.
HwWaitTxReadyThenWriteWord_770004 @0x7890¶
btst.b #3,(0x00770007).l ; poll ready
beq.b -10 ; spin - NO TIMEOUT
move.w D0w,(0x00770004).l ; 16-bit write
rts
Established: 0x00770004 is a 16-bit output data register; bit 3 of 0x00770007 is its
ready flag. Select 0x77 was not in any previous hardware list - the full-image sweep
found it.
Inferred: the transmit mirror of the above, matching the documented 16-bit output frame.
HwWaitComplete_660000_bit4 @0x78B2¶
Spins on bit 4 of 0x00660000 (the HIGH byte of the same status word) and returns. A
transaction-complete wait.
The timeout is not here¶
Neither poll has a timeout - both are unbounded beq.b -10 spins. Yet the firmware has
"K I C K T I M E O U T : " and "AOB not read by microprogram within timeout.". So the
timeout is imposed by a caller, not by these primitives. An emulator that stalls either
of these ready bits will hang the ACCP exactly as real hardware would.
5a. THERE ARE FOUR UNBOUNDED POLLS, NOT TWO [MEASURED 2026-07-28 on the emulator]¶
Section 5 says "neither poll has a timeout" of the two octobus primitives. Correct as far as it goes, but there are four, and the other two stop the boot just as hard. Found by actually running the firmware in RetroCore and following the PC each time it stopped:
| # | Routine | Polls | PC when stalled |
|---|---|---|---|
| 1 | OctobusTransmitWord @0x7890 |
0x770007 bit 3 |
- |
| 2 | OctobusReceiveWord @0x786C |
0x660001 bit 2 |
- |
| 3 | HwWaitComplete_660000_bit4 @0x78B2 |
0x660000 bit 4 |
0x78C2 |
| 4 | the 32-bit read path @0x7374 | 0x660001 bit 0 |
0x738C |
Poll 4 was not previously recorded at all: btst.b #0,(0x00660001) / beq.b -8 at 0x7384,
escapable only if 0x00113138 is non-zero. It guards the read of the 0x550000/0x440000
pair and the subsequent move.w #0x0005,(0x00220000).
How far the boot gets, measured:
| Bits held high | Console output | Stops at |
|---|---|---|
0x770007 bit 3 only |
513 chars, through "Start/stop microprogram test abc" | 0x78C2 |
+ 0x660000 bit 4 |
608 chars, "…abc failed at CSA: 00FFH" | 0x738C |
+ 0x660001 bit 0 |
1769 chars — the whole selftest suite | past ALU verify |
With all of them released the firmware runs BUS test, MIR test, Control Store sample test,
Start/stop microprogram test, A,MARG D,AIB test, "Loading control store with selftests…", and
the ALU verify series — every one failing with Result : 00000000H against a printed
Expected:, which is exactly right with no ND-5000 present.
Bits that must be left LOW — raising them corrupts the model:
0x660001bit 1 = AOB busy. High makes the AOB write path spin instead of proceeding.0x660000bit 0 = control-store operation OK. High would make the microprogram selftests print success without an ND-5000 — a fabricated pass.0x660000bit 5 steers the firmware into a restart path.
0x660001 bit 2 (octobus receive available) should also stay low. Measured: holding it
high gets the boot less far, not further — the console stops after "ACCP local ram test OK"
because the fabricated zero replies send the firmware down a different path.
Because polls 2 and 4 share the byte at 0x660001, an emulator has to compose that
status byte rather than let whichever handshake was wired last win.
The printed Expected: values are a ready-made Phase 6 oracle: 1C587698H for the BUS
test, and the 8-word microword pattern 7698H B027H 0AAAH 2C91H 0D8CH F58BH AFBEH 6195H for
the MIR and control-store sample tests. When an ND-5000 model can return those, the selftests
flip from failed to passed with no test rewriting.
5b. THE MFBUS PRESENCE SCAN, CAPTURED OFF THE WIRE [MEASURED 2026-07-28]¶
Section 4z lists 17 driver function codes and notes that most handlers are unread. It is no longer necessary to read them to know what the card actually sends at boot: the RetroCore NDOBCON model records every transmitted frame, so the boot traffic can simply be dumped.
With nothing answering, the ACCP transmits exactly 36 frames: six stations x six frames.
The stations are 2, 3, 4, 5, 6 and 7 - which is precisely the range named in the console line
MFbus controller not found at Octobus stations 2-7. Those stations are the MPM5 shared-memory
window shared with the octobus controllers, so the scan is looking for shared memory.
The per-station sequence, with the station number in bits 13-8:
| # | Frame | C | B | Dest | Info | Reading |
|---|---|---|---|---|---|---|
| 1 | 8235 |
1 | 0 | 2 | 0x35 |
control - open / select |
| 2 | 0205 |
0 | 0 | 2 | 0x05 |
data |
| 3 | 0202 |
0 | 0 | 2 | 0x02 |
data |
| 4 | 0203 |
0 | 0 | 2 | 0x03 |
data |
| 5 | 0200 |
0 | 0 | 2 | 0x00 |
data |
| 6 | 8225 |
1 | 0 | 2 | 0x25 |
control - execute / close |
So the probe is two control frames bracketing a four-byte payload 05 02 03 00. Station 3 gets
8335 0305 0302 0303 0300 8325, and so on to station 7. The "open/execute" bracketing matches the
shape already seen on the 0x220000 command port (§2.4 of the RE document), which is reassuring -
the same idiom at a different layer.
The wire frames and §4y are THE SAME MESSAGE - full decode [RECONCILED 2026-07-28]¶
§4y carves the request from the driver side: OBCON function 0x41, process 0x05 at request
+0x06, and a two-byte payload {0x03, own station}. §5b captured six frames per station off the
wire. Put side by side, every byte is accounted for - and the two derivations are independent, one
from the request block and one from the transmitted frames:
| Frame | C | Info | Role |
|---|---|---|---|
| 1 | 1 | 0x35 |
control - start of multibyte message |
| 2 | 0 | 0x05 |
process number (request +0x06) |
| 3 | 0 | 0x02 |
payload length in bytes |
| 4 | 0 | 0x03 |
payload byte 0 (0x125A) |
| 5 | 0 | NN |
payload byte 1 = our own station number (0x1268) |
| 6 | 1 | 0x25 |
control - end of multibyte message |
So the multibyte wire format is:
[C 0x35] [process] [length] [payload...] [C 0x25]
This corrects the first reading in this section, which called 05 02 03 a "fixed payload". It is
not: 05 is the process number, 02 is a length, and only 03 is payload. The distinction matters
because a responder has to parse the length rather than assume a shape.
The station-number byte was confirmed by experiment before §4y was available: setting the model's
station (the byte the firmware reads from 0x900001 low 5 bits at 0x122E) to 1 changed frame 5 from
0200 to 0201, and to 0x0B changed it to 020B, while the rest stayed fixed. Independent
agreement between the wire capture and the carve.
Before that byte was modelled it read 00 - an illegal OCTObus station - because the stub returned zero.
Before that byte was modelled it read 00 - an illegal OCTObus station - because the stub returned zero. Any emulator must supply a legal station number here BEFORE the scan, or the card identifies itself illegally in its very first transmission.
[INFERENCE] the readings "open / select" and "execute / close" for 0x35 / 0x25, and the meaning
of the fixed 05 02 03. What is ESTABLISHED is the frame values, the ordering, the six-station
sweep, the addressing, and the station-number byte.
What a peer must do to satisfy the scan: see §4y, which specifies it completely - status
0x8300, delivery via registration-table process 5, and the field tests MFCRECEIVE applies.
One gap remains between §4y and a working responder, and it is worth stating precisely. §4y
describes the accepted reply in terms of a RAM buffer - (0x14,buf) byte 1 = source, byte 3 = 0x05,
byte 4 = length, bytes 5.. copied to 0x001131E6. But an emulated peer does not write that buffer;
it puts frames on the wire, and the ACCP's own receive path assembles them into it. So the
missing piece is the frame-to-buffer mapping used by DOREC_MULTI_OCTO:
- at what offset the driver stores the received frames,
- whether it stores the whole 16-bit frame or splits it,
- and specifically why the SOURCE field lands at buffer byte 1 rather than byte 0.
That last point is the tell: a received frame is C|B|source<<8|info, so its low byte is the
information byte and its high byte carries the source. Byte 1 holding the source means the driver
is not storing frames verbatim in big-endian order. Until that mapping is read, a responder can only
be built by guessing the layout - which is exactly the kind of guess this document exists to
prevent.
Carve DOREC_MULTI_OCTO next. Everything else in the exchange is now specified.
A station that answers ends the scan. With the model's default loopback auto-reply enabled the
card transmits only the first six frames and never probes 3-7, because the echoed frame arrives with
the source field reading station 2. That is a lucky accident, not a correct reply - the loopback
carries none of the information a real MFbus controller would return, which is exactly why the card
still reports CPU model: ND-5800 as unconfirmed afterwards. Anything built on top of the loopback
should be treated as scaffolding.
This is the concrete specification for a peer model: recognise 0x35 addressed to your station,
absorb the four data bytes, act on 0x25, and reply with whatever confirms presence and reports the
CPU model. The last part is still unknown and is the next thing to carve - the reply content, not
the framing.
Both behaviours are locked in as regression tests
(Octobus_ProbesStations2Through7WhenNothingAnswers,
Octobus_ScanStopsAtTheFirstStationThatAnswers) in
Nuget\HackerCorpLabs.Emulation.Machines.Accp\tests\AccpConsoleTests.cs.
5c. THE ROM'S OWN VOCABULARY, AND WHERE A KICK GOES [CARVED 2026-07-28]¶
The ROM string table names the message classes itself, which is independent confirmation of
section 1a. Offsets are into
E:\Dev\Ronny\NDInsight\Installation\Communication\OctobusAccp\eprom\octo.bin.
Console commands (offset 78679):
SEND-KICK-OCTOBUS <DESTINATION><Kick value (process)>
SEND-MULTIBYTE-OCTOBUS <Destination><Subprocess><Message>
SEND-OCTOBUS <Data (16)>
RECEIVE-OCTOBUS / RECEIVE-MULTIBYTE-OCTOBUS
SET-KICK-TIMEOUT <Kick timeout (ms)>
TRACE-COMMUNICATION-DATA <Trace Octobus communication to consol? (y/n)>
Error strings:
| Offset | String |
|---|---|
| 75232 | Kick |
| 76232 | Illegal kick |
| 76194 | Illegal start of message |
| 76158 | Illegal end of message |
| 75158 | Unexpected multibyte message |
| 74603 | AOB full, previous message not read. Message lost! |
| 75092 | in DOREC_MULTI_OCTO |
| 75124 | in DOSEND_MULTI_OCTO |
| 74881 | Not prepared for message from station |
| 76519 | Octobus message to OMD 0 received. No tests implemented yet. |
"Illegal start of message" and "Illegal end of message" are the S bit being wrong; "Not
prepared for message from station" is the connect-CMD requirement. DOSEND_MULTI_OCTO and
DOREC_MULTI_OCTO are the driver's own names for the multibyte send and receive paths - "MULTI"
being the multibyte class of section 1a.
CORRECTION 2026-07-30 - the IRQ3 routine below is the TRANSMIT path, not receive¶
The subsection that follows is wrong and is kept only because it was read. It calls
Irq3KickServiceAndTrace @0x6C0 the octobus receive doorbell. It is not. The vector at
0x0698 sets the base registers explicitly:
0698 movea.l #0x00660000,A0 ; status base
069E movea.l #0x00770004,A1 ; octobus TRANSMIT data register
06A4 btst.b #3,(A0) ; 0x660000 bit 3
06AA bsr.w 0x06C0 ; the service loop below
...
06C0 btst.b #3,(0x3,A1) ; 0x770007 bit 3 = TX ready, spin up to 10
06EC btst.b #0,(0x1,A0) ; 0x660001 bit 0 = data available from the CPU
06FE move.w (0x00440000).l,(A1) ; read the DATA port, WRITE it to 0x770004
So IRQ3 is the CPU-to-octobus forwarding path: the ND-5000 writes a word to AIB
(0x440000), and the ACCP's IRQ3 handler transmits it on the octobus. The globals
0x00113116 and 0x00113144 are loaded from (0x2,A1) = 0x770006, which is
transmit-side, so the conclusion below that they are a receive doorbell does not follow.
The real receive path is OctobusReceiveWord @0x786C, reading 0x880000 gated by
0x660001 bit 2, with reassembly in OctobusMessageAssemble @0x6C02.
What survives from the subsection below: the negative result is unaffected - the octobus receive path still never calls the MFbus memory routines, and that was established by byte- searching the call sites, not from the IRQ3 reading.
A consequence worth carrying forward: a word the CPU writes to AIB is not necessarily consumed by the ACCP as data. With IRQ3 enabled it may be forwarded straight onto the octobus.
A kick is a doorbell for the octobus FIFO, not for shared memory [SUPERSEDED - see above]¶
Irq3KickServiceAndTrace @ 0x6c0 is the receive interrupt handler. It reads the octobus
data port into a caller-supplied buffer, optionally traces it, and sets two globals:
| Global | Written | Read by |
|---|---|---|
0x00113116 - the kick value |
0x764 | OctobusStatusErrorReport @ 0xa33a only |
0x00113144 - a flag |
0x76c | MFCRECEIVE @ 0x14e4 and OctobusMessageAssemble @ 0x6c5a |
So a received kick means "a message is available, come assemble it". It does not carry or imply a memory address.
The octobus receive path never touches MFbus memory¶
Tested both directions:
OctobusMessageAssemble@ 0x6c02 calls exactlyOctobusStatusErrorReport,MfBusMemoryErrorReport,PlancLeafRuntime_112DE,ConsPrintStringandMicroprogCmd_Helper_6940. The only MFbus item is an error reporter.- Every caller of the MFbus memory transaction routines (13 call sites to
0x7138, 9 to0x70aa) sits in console-command and selftest code -Cmd40_TestMemory,Cmd33_LookAtMemory,LookAtMfBusMainMemory_A/_B. None in the octobus message or kick path.
Conclusion for the ACCP: the protocol is genuinely on the wire, not hidden behind a doorbell into MPM.
[OPEN, and deliberately so] This does not settle it for SCSI or Ethernet III. ND-820026 states octobus is for short messages and synchronization, so those controllers must move their payloads through MFbus memory - and a kick plus a shared-memory descriptor is the natural way to do it. The ACCP is an ND-5000 CPU-support processor with no bulk data to move, so it is the one card that would not show the pattern. Testing that needs SCSI or Ethernet III firmware, or a live MPM dump.
XMSG is not on the octobus¶
E:\Dev\Ronny\NDInsight\Reference-Manuals\500\ND-820026-1c-EN DOMINO and NUCLEUS Software
Guide.md, Figures 2 and 5, both place XMSG on the ND-100 side:
DOMINO monitor --XMSG--> BOPCOM server (RT-program in ND-100) --OCTOBUS--> DOMINO controller
XMSG and OCTObus are two legs of one path, not the same messages. A 68k controller never sees XMSG, and the ACCP ROM string table contains no XMSG vocabulary at all.
[OPEN] Absence of XMSG strings does not prove absence of XMSG-shaped structures in MPM. The ND-100 could lay XMSG-format data in shared memory and use octobus kicks as the doorbell, with a controller ferrying bytes it never interprets. That remains untested for SCSI and Ethernet III.
How the ACCP reaches MFbus memory¶
MfBusMemoryTransaction @ 0x70cc takes a 32-bit MFbus address at (0x14,A6) and drives it
through the ND-5000 datapath - there is no direct memory-mapped MPM window on the 68k bus:
0x220000 <- 0x300F, 0x400A, 0x000F command port
0x440000 <- address low data low
0x550000 <- address high data high
0x220000 <- 0x300F, 0x400C, 0x000F
wait on 0x660001 bit 4, bounded by a D1 countdown
Note this poll is bounded, unlike the four unbounded ones in section 5a.
Ghidra reports "no callers" for 0x70cc - it is fallthrough-entered from
MfBusMemoryTransaction_VariantA @ 0x70aa. The caller counts above were obtained by
byte-searching for jsr patterns, not from the call graph, because of that same flow
artefact. See section 4y for the identical problem at DetectCpuModelBySignature.
1c. THE REPLY IS DECODED - AND THE MODEL IS CROSS-CHECKED [CARVED 2026-07-30]¶
A live MFbus-controller peer was built against the ACCP machine and the discovery
exchange now succeeds. MFbus controller not found at Octobus stations 2-7 is gone,
the scan stops at the first station that answers, and the firmware moves on to a second,
previously unreached failure: MFbus controller has incorrect CPU model setting.
What the reply's content byte 1 is: the CPU model digit¶
Success path at 0x12D4, entered when data-area byte 0 is zero:
12E4 move.b (0x001131e7),(0x18,A6) ; content byte 1
12EC move.b (0x18,A6),D2b
12F0 andi.w #0xff,D2w
12F4 lsl.w #0x8,D2w ; byte1 << 8
12F6 or.w #0x5000,D2w ; | 0x5000
12FA move.w D2w,D0w ; returned as the reported model
The reported model is 0x5000 | (content byte 1 << 8). The digit maps straight onto
the family the ACCP served: 2 -> ND-5200, 4 -> ND-5400, 5 -> ND-5500, 7 -> ND-5700,
8 -> ND-5800, 9 -> ND-5900. A zero byte gives 0x5000, which is not a model.
So content byte 0 is a status byte and content byte 1 is the model digit. That closes the "reply content unknown" item for the first two bytes.
The second channel: byte 0 = 0xFF is an error report¶
When byte 0 is non-zero the code at 0x1308 requires it to be 0xFF, then dispatches on byte 1 as an error code:
| byte 1 | Behaviour |
|---|---|
| 1 | bra.w 0x123C - back into the station loop, i.e. keep scanning |
| 2 | prints two strings (descriptors 0x12BA2 + 0x12BEE), then 0x1428 |
| 3 | prints 0x12BA2 + 0x12C12, then 0x1428 |
| 4 | continues the same pattern at 0x13AA |
Any other byte-0 value branches to 0x1428, the generic error exit.
The model is NOT believed - it is cross-checked against hardware¶
0x110A is the routine that calls discovery and then validates its answer:
1118 clr.w (0x1131FA) ; "model valid" = 0
111E jsr 0x121C ; discovery -> D0 = reported model
112C lea (0x00114550).l,A0 ; THE SIGNATURE TABLE
1132 cmpi.w #0x7F55,(A0,D1) D1=6 ; then +0x0C, then +4
| Class | Set at | 0x1131F6 |
Default 0x1131F8 |
Accepted reported models |
|---|---|---|---|---|
| 1 | 0x1150 | 1 | 0x5200 | 0x5200 only |
| 2 | 0x1184 | 2 | 0x5400 | 0x5400, 0x5500, 0x5700 |
| 3 | 0x11BE | 3 | 0x5800 | 0x5800, 0x5900 |
On a match the code sets 0x1131FA = 1 and stores the reported model into 0x1131F8. A
stray 0x7F55 at the wrong offset instead clears 0x1131F6 and leaves 0x1131FA at
zero. Finally:
1204 tst.w (0x001131fa) ; bne 0x1214
120C ori.w #0x8000,(0x001131e2) ; the "incorrect CPU model setting" flag
So a peer cannot claim an arbitrary model: it must agree with the class the ACCP derives from its own hardware.
CORRECTION - A0 is NOT the reply buffer¶
Section 4y flagged as "well supported, not proven" the inference that A0 in the
signature probe points at 0x001131E6. It is wrong. 0x112C loads
lea (0x00114550).l,A0 - a different RAM area entirely. The signature table is
hardware-filled state, not message content, and no assumption about the reply buffer is
needed. The "+0 can never match because byte 0 must be zero" prediction in section 1b is
therefore moot; disregard it.
CORRECTION - the copy is N bytes, not N-1¶
Section 4y says MFCRECEIVE copies "N-1 bytes". Wrong - that read the descriptor's upper limit as a count. The two PLANC descriptors built at 0x15A4/0x15C2 are:
- source: origo
(0x14,A0), lower 5, upper 5 + N - 1 - destination: origo
0x001131E6, lower 0, upper N - 1
Both spans are N bytes.
The buffer layout, dumped live¶
Read from the driver's own buffer (registration entry for CMD 5 at 0x00116398, buffer
pointer 0x00112D40, data area 0x00112D54) after a six-byte reply:
| Byte | Value | Reading |
|---|---|---|
| 0 | 0x00 |
not the START information byte - see below |
| 1 | 0x02 |
source station - the peer's station. Confirms the documented byte 1 |
| 2 | 0x00 |
unread by any consumer |
| 3 | 0x05 |
own CMD. Confirms the gate |
| 4 | 0x06 |
length N. Confirms the gate |
| 5 | 0x00 |
content byte 0 |
| 6+ | 0x00 |
content bytes 1 upward never arrived |
Two things this settles and one it opens:
- byte 1, byte 3 and byte 4 are confirmed live, not just from the gate tests.
ObconReceiveBuffer.StartFrameInformationOffsetlooks wrong. Byte 0 read0x00, not the0x35we transmitted. Either the START information byte is not stored there, or it is stored elsewhere. Do not rely on that field until it is re-read.- [OPEN] Only the first content byte reaches the buffer. A six-byte reply lands its length correctly but only content byte 0. That is why the model digit cannot get through and why the cross-check still fails. This is the next thing to chase, and it is a receive-path question - frame-level tracing of the ACCP's assembler, not more static reading.
RESOLVED 2026-07-30 - the model rule is PROVEN, and the "one content byte" report was wrong¶
Fixed in RetroCore commit dbdc291e5. The ACCP now completes discovery and the CPU-model
check, and Discovery_ReplyByte1SelectsCpuModel passes. model = 0x5000 | (byte1 << 8) is no
longer inference.
Root cause was interrupt presentation, not the receive path. Run() called
UpdatePendingInterrupt() once per 1024-instruction batch, and that function picks a single
highest source with an else-if chain - so while the DUART asserted IRQ5, octobus IRQ3 was never
presented at all. Two firmware facts make that fatal: the receive ISR takes exactly one frame
per interrupt (a ten-frame reply needs ten interrupts), and MFCRECEIVE abandons after 10000
poll iterations, losing anything still undelivered.
Two errors in the section above, corrected rather than edited away:
- "Only the first content byte arrives" never happened. The reassembly write pointer was
still at its initial value of 5, so zero content bytes had been appended. Byte 5 read
0x00because the buffer was untouched - and both replies tried happened to begin with0x00, which made an empty buffer look like a truncated one. - Dumping the buffer at end of run proves nothing here. The firmware clears the reassembly
record on closing a message, and re-initialises all 64 per-station records and 16 registration
entries at
0xF4E6. A late sample shows a wiped structure whatever happened.
Sampled at the right moment the message was assembling correctly all along - data area
00 02 80 05 06 00 08 A1 A2 A3 A4, all six content bytes present, every gate passed, payload
copied to 0x001131E6, model reaching the class check as 0x5900.
Test this with digit 9, not 8. 0x5800 is also the class-3 default written to 0x001131F8
at 0x11DA before any comparison, so digit 8 passes whether or not the reply was consumed.
0x5900 is reachable only through a consumed reply. A companion test pins that digits 2, 4, 5
and 7 are refused, so losing the cross-check cannot pass unnoticed.
Harness¶
E:\Dev\Repos\Ronny\RetroCore\Nuget\HackerCorpLabs.Emulation.Machines.Accp\tests\AccpMfBusControllerPeer.cs
is a peer that answers the scan, and
...\tests\AccpMfBusDiscoveryTests.cs holds the tests. Six pass; the three model-digit
cases are [Ignore]d with the open item above as the stated reason rather than deleted.
A direction trap worth repeating: a reply must be composed with the PEER's station in bits 13-8, because the guest reads that field as the SOURCE. Composing it with the ACCP's station looks right in a log and then silently fails the source test.
6. Still open¶
Updated 2026-07-28. The framing questions that dominated this list are now closed by section 1a. What remains:
Protocol
- The meaning of content byte
0x03in the CMD 5 discovery request. Still the single undecoded field in the outgoing message. Section 1d records a SUPPORTED BUT UNPROVEN hypothesis - that it is the ACCP's own Access Processor CMD number - backed by the live finding that CMD 3 and CMD 5 are the ONLY two CMDs this card connects. Settling it needs either the Octobus Driver Programming Guide or an MFbus-controller ROM. - ~~
Nand the reply content~~ - SOLVED 2026-07-30, section 1c. Content byte 0 is a status byte (0 = good, 0xFF = error report), content byte 1 is the CPU model digit, andNis not itself a gate. - ~~Only the first content byte of a reply reaches the driver buffer~~ - NOT A REAL
DEFECT, and never was. The observation was an artefact of when the buffer was sampled.
The write pointer was still at its initial value 5, so ZERO content bytes had been appended;
byte 5 read
0x00because the buffer was untouched, and both replies tried happened to begin with0x00. Sampling at the right moment shows the whole body assembling correctly. The real fault was interrupt presentation in the emulator - see section 1e. Fixed 2026-07-30; the model digit now reaches the cross-check and ND-5900 is accepted. - ~~Where the START information byte is really stored~~ - ANSWERED: it is not stored at
all. Data-area bytes 0-1 are the source station as a 16-bit word, written from descriptor+4
at 0x10410/0x1041E. The START frame's information byte goes into the per-station reassembly
record at
+0x06(section 1e), never into the buffer.StartFrameInformationOffsetwas wrong and has been removed from the code. - ~~What fills the signature table at
0x00114550~~ - ANSWERED 2026-07-31, and any class is now selectable. The builder at0x7D26has FOUR phases: clear, sixteen reads of0x00220000, a 16x16 transpose, and then a rewrite pass at0x7DD0(bit11:=bit10, field moves, a 7-bit Gray decode at0x7CA2) whose output is what the class chain reads. Feeding a computed sequence to the0x220000port - which must be modelled as ARMED by a write of0x0007, not as a free-running counter - establishes class 2 and gets ND-5500 accepted, live verified. Full carve in part 5 ofACCP-COMPLETE-REFERENCE.md. The remaining unknown is narrower than "what fills it": it is what the real ND-5000 datapath physically presents at0x220000. All-zero reads still give class 3 (ND-5800), which is correct behaviour for a machine with no datapath. - ~~Buffer byte 2~~ - ANSWERED: it is a driver FLAGS byte, not payload. Cleared at
0x1042E; bit 7 = message complete (
bsetat 0x106CA), bit 6 = buffer overrun (bsetat 0x10BAA). Only the condition behind bit 5 (0x106A4-0x106B8) is still uncarved. See the correction in section 1b, which also fixes the earlier wrong reading of bytes 0 and 1. - ~~Whether A0 in
DetectCpuModelBySignatureis the reply buffer~~ - ANSWERED: no.0x112Cloadslea (0x00114550).l,A0. See the correction in section 1c. - Whether SCSI / Ethernet III use kick-plus-MPM-descriptor where the ACCP does not. See section 5c.
ACCP hardware
- The Transmitter Control Register and Transmit Status Register addresses on the ACCP.
Candidates from the sweep:
0x330000/0x330001(heavily accessed, in the driver region) and0xAA0000(three word writes of a routine parameter at 0x7AE6/0x7AFA/0x7B0E). 0x00770000, loaded withleaat five sites in 0x11030-0x11230 - far from the transmit routine. Either a second use of select 0x77, or the same device accessed as a window.- Whether the ACCP's START/STOP/CONTINUE/RESET commands go out as hardware-decoded OCTObus messages (241-245) or through local registers.
- The ACCP's OCINT7 equivalent.
Provenance¶
Sections 1-4 are transcribed from ND-14001-1-EN chapter 4 and are documentation, not inference. Section 5 was read from the image on 2026-07-27; each claim there is marked ESTABLISHED (read from the instruction bytes) or INFERRED (consistent with the manual but not traced). The cross-matching of acknowledge codes to ACCP error strings in section 2 is my correlation, not something either source states.
Added 2026-07-28. Sections 1a and 1b rest on a second documented source:
E:\Dev\Ronny\NDInsight\Reference-Manuals\500\ND-05.017.01 EN ND-5000 HARDWARE MAINTENANCE.md- chapter 3 "Octobus Communication", especially 3.3 / 3.3.1 (message format), 3.4 (octobus hardware on the ND-5000 CPU), 3.6 (ACCP)
- chapter 8, the
OCTOBUS-DRIVER,LIST-SUBPROC-TABLE,READ-OCTOBUS-RECEIVEandTRANSMIT-OCTOBUStest commands
F:\NDDOC\ND\14\ND-14001-1-EN Domino Standard Hardware Description.pdf- the original scan, read directly to confirm that the four "see OCTObus Protocol Specification" deferrals are in the source and are not an OCR loss, and to settle Figure 30's bit layout.
The information-byte decode in section 1a is documentation, transcribed from 3.3.1. The application of it to the captured frames in section 1b is arithmetic on documented fields, and it agrees with an independent carve (section 4y) that was made before either source was found.
Section 5c is read from the image and from the ROM string table. Its negative results - that the
octobus receive path never calls the MFbus memory routines, and that no XMSG vocabulary exists
in the ROM - were obtained by byte-searching for jsr patterns rather than from Ghidra's call
graph, because several routines in this image report "no callers" due to the fallthrough-entry
artefact. A negative from a byte search is strong but not absolute: a path reached only
through an unresolved indirect jump would not appear.
Two documents remain unlocated and are named here so nobody re-derives the search: "OCTObus Protocol Specification" (cited four times by ND-14001, no ND number given anywhere) and "Octobus Driver Programming Guide, written by DVT, 15. Oct. 1986" (cited by ND-05.017.01 chapter 8). ND-14.002 is unclaimed in both manuals' related-manuals lists and is a plausible slot for the former - that is a guess from numbering alone, not evidence.