Minimal Synthetic :NRF Test File (ND-500 Loader Fixture)¶
Purpose: the smallest possible ND Relocatable Format file that NLL will accept with LOAD-SEGMENT and that produces a runnable program which immediately terminates (MON 0 / LEAVE). Fixture for the domain-handling unit tests (see ND500-DOMAIN-HANDLING-TEST-COMMAND-SEQUENCE.md, Phase H).
Sources:
- NRF format: ND-60.136.04A chapter 12 (sections 12.1-12.3) - VERIFIED against the manual text.
- CALL instruction encoding + segment-31 MON convention: RetroCore emulator sources (E:\Dev\Repos\Ronny\RetroCore\Emulated.HW\ND\CPU\ND500\Instructions\CALL\Call.cs opcode 0xC3, CpuND500.IndirectSegments.cs segment = bits 31:27, MON n = offset n on segment 31, i.e. address 0xF8000000+n; Sintran\MON_0_LEAVE.cs MON 0 = program exit).
- Open items are marked DERIVED or TO-VALIDATE below. Nothing here has run against real NLL yet.
1. NRF group encoding (manual section 12.1)¶
Each NRF group = one control byte, optionally followed by a numeric field and/or symbolic field:
control byte = | control number (5 bits) | numeric length NL (3 bits) |
numeric field = NL bytes (0-7), signed, 2's complement
symbol field = 1 length byte + 1-255 ASCII chars (only for some controls)
CONFIRMED (bit packing): control_byte = (ctrl << 3) | NL - VERIFIED 2026-08-10 against
a real compiler-produced NRF file, E:\Dev\Ronny\ND500\Microcode\nd-500-apf-lib-e.nrf (an
APF vector-math library). Three independent exact matches (MSG group's 17-char message,
LIB group's 7-char symbol VADDXXX, DEF group's 10-char hidden symbol #(+PROG0+)), each
confirmed by the following byte matching its expected symbol-length exactly - see
../File-Formats/NRF-FILE-FORMAT.md for the full byte-by-byte table. This closes item 1 of
the validation checklist below.
Control numbers used here (manual section 12.2, numbers are octal):
| Ctrl | Oct | Dec | Meaning | Key property we exploit |
|---|---|---|---|---|
| BEG | 1 | 1 | Start of module; numeric bytes = priority, language (0=assembly), ADL | After BEG, load mode = program (PMO) |
| MSA | 3 | 3 | Main start address := current byte address (+NV) | NL=0 -> start = here |
| LDI | 21 | 17 | Load NL immediate bytes at current pointer | Raw machine-code injection, max 7 bytes/group |
| END | 2 | 2 | End of module, checksum in numeric field | NL=0 -> NO checksum test (manual: "If numeric length is 0, no checksum test is performed") |
| EOF | 26 | 22 | End of NRF file |
2. The payload program¶
One instruction: terminate via MON 0 (LEAVE). On the ND-500 a monitor call is an ordinary subroutine call into indirect segment 37B (31 decimal); MON n lives at byte offset n (EQU 37B9+n). Segment sits in address bits 31:27, so MON 0 = address 0xF8000000.
CALL 0xF8000000, 0 ; call MON 0 (LEAVE) with zero arguments
Encoding (opcode from Call.cs; address is a direct 4-byte operand; ND-500 is big-endian):
C3 F8 00 00 00 00
^ ^---------- ^-- argument count = 0
| 4-byte subroutine address 0xF8000000 (MSB first)
CALL opcode
VALIDATED against the emulator decoder (2026-07-19): the arg-count byte 0x00 is an operand ADDRESS CODE that decodes as CONSTANT_SHORT with value 0 (address codes 0x00-0x3F = constant short, value in bits 5:0) - so the byte VALUE 0x00 is correct, and its interpretation is a specifier-encoded constant, not a bare byte. Unit test E:\Dev\Repos\Ronny\RetroCore\Emulated.Tests.ND500\TestND500_NrfMinimalFixture.cs (4 tests, all green) proves via CpuND500.FetchInstruction/Disassemble: opcode 0x00C3 CALL, TotalLength 6, OperandCount 2, operand 0 = big-endian direct 0xF8000000 (segment 31, offset 0 = MON 0), operand 1 = constant 0, and the disassembly text renders CALL. The test also pins BuildMinimalNrf() to the documented 14-byte stream.
Note: segment-31 calls skip entry-point validation (handled before the ENTS/ENTM check in Callg/Call), so no entry instruction is needed at the target - and MON 0 never returns, so nothing is needed after the CALL either.
3. The complete file - 14 bytes¶
| Offset | Bytes (hex) | Group | Meaning |
|---|---|---|---|
| 0 | 0B |
BEG, NL=3 | (1<<3)|3 - module start |
| 1 | 00 00 04 |
priority=0, language=0 (assembly), ADL=4 (32-bit word addresses) | |
| 4 | 18 |
MSA, NL=0 | (3<<3)|0 - main start address := PP (= 0, start of program segment) |
| 5 | 8E |
LDI, NL=6 | (17<<3)|6 - load 6 immediate bytes to program segment |
| 6 | C3 F8 00 00 00 00 |
CALL 0xF8000000, 0 args = MON 0 LEAVE | |
| 12 | 10 |
END, NL=0 | (2<<3)|0 - end of module, NO checksum (NL=0) |
| 13 | B0 |
EOF, NL=0 | (22<<3)|0 - end of NRF file |
Full stream:
0B 00 00 04 18 8E C3 F8 00 00 00 00 10 B0
Design choices, all chosen to minimize format risk:
- END with NL=0 avoids computing the checksum at all (explicitly allowed by section 12.2).
- LDI groups carry max 7 bytes; the 6-byte payload fits one group. A larger payload = a sequence of LDI groups.
- No DEF/DDF/REF symbols, no data segment (mode stays PMO from BEG), no library controls.
- ADL=4: BEG's third numeric byte; default would be 1, but REF/APA/ADA sizing and address alignment work in ADL units, and 4 matches the 32-bit word. TO-VALIDATE against a real compiler NRF's BEG bytes.
4. C# fixture builder (unit-test snippet)¶
/// <summary>
/// Builds the minimal synthetic :NRF fixture: one module whose program is
/// a single CALL to segment 37B offset 0 = MON 0 (LEAVE / exit program).
/// Byte layout documented in NRF-MINIMAL-SYNTHETIC-TEST-FILE.md - keep the
/// two files in sync. Wrapper format: ND-60.136.04A chapter 12.
/// </summary>
public static byte[] BuildMinimalNrf()
{
// control byte = (ctrl << 3) | NL -- DERIVED packing, see doc section 1
byte[] nrf = new byte[14];
int i = 0;
nrf[i++] = (1 << 3) | 3; // BEG, NL=3
nrf[i++] = 0; // priority 0
nrf[i++] = 0; // language 0 = assembly
nrf[i++] = 4; // ADL = 4 (32-bit addresses)
nrf[i++] = (3 << 3) | 0; // MSA, NL=0: start address = current PP = 0
nrf[i++] = (17 << 3) | 6; // LDI, NL=6: six immediate program bytes follow
nrf[i++] = 0xC3; // CALL (direct 4-byte address operand)
nrf[i++] = 0xF8; // address 0xF8000000 = segment 31 (37B), offset 0
nrf[i++] = 0x00; // ... big-endian (ND-500: MSB at lowest address)
nrf[i++] = 0x00;
nrf[i++] = 0x00;
nrf[i++] = 0x00; // argument count = 0 (TO-VALIDATE: bare byte vs specifier)
nrf[i++] = (2 << 3) | 0; // END, NL=0: no checksum test performed
nrf[i++] = (22 << 3) | 0; // EOF
return nrf;
}
Write the bytes to a SINTRAN file named e.g. TEST-CODE:NRF on the test disk image (LOAD-SEGMENT's default file type is :NRF, section 6.3.1).
5. Expected behavior in the Phase H tests¶
NLL: LOAD-SEGMENT TEST-CODE-> load report; program bytes land at P:0-5; MSA registers start address 0.NLL: EXIT(H2) orNLL: RUN(H1) -> the program executes CALL -> segment-31 indirect -> MON 0 -> normal termination -> back to the NLL:/N500: prompt.- Any trap (ISE/IOS) instead of clean exit points at the arg-count encoding or the packing assumption - fix per the TO-VALIDATE notes and update this file.
6. Validation checklist before trusting the fixture¶
- [ ] Bit packing: first byte of a real compiler :NRF decodes as BEG under
(ctrl<<3)|NL. - [x] Payload: RetroCore decoder/disassembler renders the 6 payload bytes as CALL 0xF8000000 with 0 args, length 6. DONE 2026-07-19 - unit test
TestND500_NrfMinimalFixtureinE:\Dev\Repos\Ronny\RetroCore\Emulated.Tests.ND500, 4/4 passing (decode structure, segment/MON derivation, disassembly text, byte-stream pin). - [ ] BEG numeric bytes (priority/language/ADL) match a real compiler NRF's BEG group.
- [ ] Live NLL (once available): LOAD-SEGMENT accepts the file with no error and WRITE-SEGMENT-STATUS shows a 6-byte program segment with start address 0.