ND-500 Domain Setup and Memory Mapping¶
Complete Guide to How SINTRAN Sets Up ND-500 Domains
Version: 1.0
Last Updated: October 17, 2025
Purpose: Document exactly how SINTRAN configures ND-500 domains, memory mapping, and prepares for execution
Table of Contents¶
- Overview
- Domain Initialization Sequence
- Memory Mapping on ND-500
- Multiport Memory Setup
- Process Descriptor Creation
- Communication Structures
- C# Emulation Implementation
1. Overview¶
1.1 The Components¶
ND-100 Side ND-500 Side
├─ 3022 Bus Interface ←→ 5015 Control Board
├─ Multiport Memory (5MPM)←→ Direct Memory Access
├─ SINTRAN Monitor ←→ ND-500 Microcode
└─ Message Buffers ←→ Process Descriptors
1.2 Setup Phases¶
flowchart TB
A[1. Boot: Detect ND-500]
B[2. Allocate 5MPM]
C[3. Create Process Descriptors]
D[4. Load Domain Files]
E[5. Setup MMU on ND-500]
F[6. Initialize Message Buffers]
G[7. Start Execution]
A --> B --> C --> D --> E --> F --> G
style B fill:#009688,stroke:#00695C,stroke-width:2px,color:#fff
style E fill:#FFA726,stroke:#F57C00,stroke-width:2px,color:#000
style F fill:#009688,stroke:#00695C,stroke-width:2px,color:#fff
2. Domain Initialization Sequence¶
2.1 Phase 1: PLACE-DOMAIN¶
From NPL source RP-P2-N500.NPL (lines approximate):
% PLACE-DOMAIN command for ND-500
PLACE500:
% 1. Read domain from DESCRIPTION-FILE
CALL GETDOMAIN % Get domain descriptor
% 2. Allocate ND-500 process descriptor
CALL GET5PROC % Allocate process slot
% 3. Setup multiport memory mapping
CALL SETUP5MPM % Configure 5MPM access
% 4. Load segment files to ND-500 memory
CALL LOAD5SEGS % Load :PSEG/:DSEG files
% 5. Initialize ND-500 MMU
CALL INIT5MMU % Setup segment capabilities
% 6. Create message buffer
CALL ALLOC5MSG % Allocate in 5MPM
% 7. Link to process descriptor
CALL LINK5PROC % Connect all structures
2.2 Phase 2: Memory Allocation¶
Physical memory layout after PLACE:
ND-100 Physical Memory:
┌─────────────────────────────────────┐
│ 0x000000 - 0x03FFFF: Normal RAM │
├─────────────────────────────────────┤
│ 0x040000 - 0x05FFFF: 5MPM │ ← Shared with ND-500
│ Process Descriptors │
│ Message Buffers │
│ XMSG Kernel │
│ Shared Data │
├─────────────────────────────────────┤
│ 0x060000 - 0xFFFFFF: More RAM │
└─────────────────────────────────────┘
ND-500 Physical Memory (separate):
┌─────────────────────────────────────┐
│ 0x00000000 - 0x0003FFFF: Domain 0 │
│ :PSEG code │
│ :DSEG data │
├─────────────────────────────────────┤
│ 0x00040000 - 0x0005FFFF: 5MPM │ ← Shared with ND-100
│ (maps to ND-100 0x040000) │
├─────────────────────────────────────┤
│ 0x00060000 - 0xFFFFFFFF: More │
└─────────────────────────────────────┘
3. Memory Mapping on ND-500¶
3.1 ND-500 Segment Capabilities¶
From documentation (ND-60.136.04A):
Program Segment Capability:
Direct segment:
┌───┬──────┬────────────────┐
│ 0 │ 000 │Physical Seg(12)│
└───┴──────┴────────────────┘
15 14-12 11-0
Indirect segment (Monitor calls to ND-100):
┌───┬───┬───┬────────┬────────┐
│ 1 │ O │ 0 │Domain │Segment │
└───┴───┴───┴────────┴────────┘
15 14 13 12-5 4-0
O = Other CPU (1 = ND-100)
Data Segment Capability:
┌───┬───┬───┬───┬────────────────┐
│ W │ P │ S │ 0 │Physical Seg(12)│
└───┴───┴───┴───┴────────────────┘
15 14 13 12 11-0
W = Write allowed
P = Parameter access allowed
S = Shared (bypass cache, for 5MPM!)
3.2 Setup on PLACE-DOMAIN¶
From NPL source analysis:
INIT5MMU:
% For each segment in domain
DO I:=0 TO 31
IF DOMAIN.SEGMENTS(I) THEN
% Allocate physical segment on ND-500
PHYSSEG:=ALLOC5PHYSSEG
% Set program capability
% Direct segment, no special flags
PROGCAP:=PHYSSEG % Bits 11-0 = physical seg
% Set data capability
% Write allowed (bit 15), Shared if 5MPM (bit 13)
DATACAP:=0x8000 OR PHYSSEG % W=1, phys seg
IF IS_5MPM(PHYSSEG) THEN
DATACAP:=DATACAP OR 0x2000 % S=1 (bypass cache)
FI
% Store in domain descriptor
DOMAIN.PROGCAPS(I):=PROGCAP
DOMAIN.DATACAPS(I):=DATACAP
FI
OD
% Set up segment 31 as indirect (monitor calls)
DOMAIN.PROGCAPS(31):=0x8000 OR 0x4000 % Indirect, Other CPU
4. Multiport Memory Setup¶
4.1 Allocation¶
From NPL (lines ~500-600 in MP-P2-N500.NPL):
SETUP5MPM:
% Calculate 5MPM size needed
SIZE:=5PROCSIZE * MAXPROCS + % Process descriptors
5MSGSIZE * MAXPROCS + % Message buffers
5XMSGSIZE + % XMSG kernel
5SHARESIZE % Shared data area
% Allocate contiguous physical pages
FIRSTPAGE:=ALLOCPHYS(SIZE/PAGESIZE)
% Save 5MPM base
5MBBANK:=FIRSTPAGE / 256 % Bank number
ADRZERO:=FIRSTPAGE * PAGESIZE % Physical address
% Initialize 5MPM structures
CALL INIT5PROCS % Create process descriptor table
CALL INIT5MSGS % Create message buffer pool
CALL LOAD5XMSG % Load XMSG kernel segment
4.2 Process Descriptor Table¶
Located at start of 5MPM:
5MPM Layout:
┌─────────────────────────────────────┐ 5MPM Base (S500S)
│ Process Descriptor 0 (5PRDSIZE) │
│ XADPROC, MESSBUFF, Status, etc. │
├─────────────────────────────────────┤
│ Process Descriptor 1 │
├─────────────────────────────────────┤
│ Process Descriptor 2 │
├─────────────────────────────────────┤
│ ... │
├─────────────────────────────────────┤
│ Process Descriptor N (S500E) │
├─────────────────────────────────────┤
│ Message Buffer Pool │
│ Each buffer: 55MESSIZE words │
├─────────────────────────────────────┤
│ XMSG Kernel Code │
├─────────────────────────────────────┤
│ Shared Data Area │
└─────────────────────────────────────┘ 5MPM End
5. Process Descriptor Creation¶
5.1 Structure¶
Process Descriptor (in 5MPM):
┌─────────────────────────────────────┐ Offset 0
│ XADPROC: Address of this descriptor│ 16 bits
├─────────────────────────────────────┤ Offset 1
│ MESSBUFF: Address of message buffer │ 16 bits
├─────────────────────────────────────┤ Offset 2
│ Status: Process status flags │ 16 bits
├─────────────────────────────────────┤ Offset 3
│ SENDE: Send enable (0=inactive) │ 16 bits
├─────────────────────────────────────┤ Offset 4
│ RECE: Receive state │ 16 bits
├─────────────────────────────────────┤ Offset 5+
│ Extended: Domain-specific data │ Variable
└─────────────────────────────────────┘
5.2 Initialization Code¶
From NPL:
CREATE5PROC:
% Allocate process descriptor in 5MPM
PROCADDR:=S500S + (PROCNUM * 5PRDSIZE)
% Allocate message buffer
MSGADDR:=ALLOCMSG()
% Initialize descriptor
*5MBBANK; T:=PROCADDR
PROCADDR; *IOXT XADPROC % Self pointer
MSGADDR; *IOXT MESSBUFF % Message buffer
0; *IOXT STATUS % Inactive
0; *IOXT SENDE % Not enabled
0; *IOXT RECE % No receive
% Load domain info
CALL LOADDOMAIN5
6. Communication Structures¶
6.1 Message Buffer Format¶
Message Buffer (55MESSIZE words in 5MPM):
┌─────────────────────────────────────┐ Offset 0
│ PLINK: Process link │ 16 bits
├─────────────────────────────────────┤ Offset 1
│ 5MSFL: Message flags │ 16 bits
│ Bit 0: 5ITMQUEUE (in queue) │
│ Bit 1: 5SYSRES (system reserved) │
├─────────────────────────────────────┤ Offset 2
│ 5PRIO: Priority │ 16 bits
├─────────────────────────────────────┤ Offset 3
│ MICFU: Microcode function │ 16 bits
├─────────────────────────────────────┤ Offset 4
│ 5ERRC: Error code │ 16 bits
├─────────────────────────────────────┤ Offset 5-6
│ TODF: To datafield address │ 32 bits
├─────────────────────────────────────┤ Offset 7-8
│ NRBYT: Number of bytes │ 32 bits
├─────────────────────────────────────┤ Offset 9-10
│ N500A: ND-500 address (byte) │ 32 bits
├─────────────────────────────────────┤ Offset 11-12
│ N100A: ND-100 address (byte) │ 32 bits
├─────────────────────────────────────┤ Offset 13
│ XMICF: Extended function │ 16 bits
├─────────────────────────────────────┤ Offset 14
│ 5DITN: DIT number │ 16 bits
├─────────────────────────────────────┤ Offset 15
│ 5CPUN: CPU number │ 16 bits
├─────────────────────────────────────┤ Offset 16+
│ Data: Variable data area │ Variable
└─────────────────────────────────────┘
6.2 Communication Flow¶
sequenceDiagram
participant N5 as ND-500 Process
participant MPM as 5MPM Memory
participant IF as 3022/5015 Interface
participant N1 as ND-100 Monitor
Note over N5,N1: Domain Setup Phase
N1->>MPM: Allocate process descriptor
N1->>MPM: Allocate message buffer
N1->>IF: Setup ADRZERO (5MPM base)
N1->>IF: Write LCON5: Activate
IF->>N5: Interrupt: Start domain
Note over N5,N1: Execution Phase
N5->>MPM: Fill message buffer
N5->>MPM: Set 5ITMQUEUE flag
N5->>IF: Write LMAR5: Message address
N5->>IF: Write LCON5: Send message
IF->>N1: Interrupt Level 12
N1->>MPM: Read message
N1->>N1: Process I/O request
N1->>MPM: Write result
N1->>IF: Write LMAR5: Reply address
N1->>IF: Write LCON5: Send reply
IF->>N5: Interrupt: Message complete
N5->>MPM: Read result
N5->>N5: Continue execution
7. C# Emulation Implementation¶
7.1 Multiport Memory Manager¶
/// <summary>
/// Manages multiport memory (5MPM) shared between ND-100 and ND-500.
/// This memory is accessible by both CPUs.
/// </summary>
public class MultiportMemory
{
private readonly byte[] _memory;
private readonly uint _baseAddress; // Physical address in ND-100 space
private readonly uint _size;
private readonly object _accessLock = new object();
// 5MPM structure pointers
private uint _processDescriptorTable; // S500S
private uint _processDescriptorTableEnd; // S500E
private uint _messageBufferPool;
private ushort _maxProcesses;
public const int PROCESS_DESCRIPTOR_SIZE = 32; // 5PRDSIZE words
public const int MESSAGE_BUFFER_SIZE = 128; // 55MESSIZE words
public uint BaseAddress => _baseAddress;
public uint Size => _size;
public MultiportMemory(uint baseAddress, uint sizeBytes)
{
_baseAddress = baseAddress;
_size = sizeBytes;
_memory = new byte[sizeBytes];
// Calculate structure locations
_maxProcesses = 16; // Typical
_processDescriptorTable = 0;
_processDescriptorTableEnd = (uint)(_maxProcesses * PROCESS_DESCRIPTOR_SIZE * 2);
_messageBufferPool = _processDescriptorTableEnd;
}
/// <summary>
/// Read word from 5MPM (both CPUs can call this).
/// Thread-safe for concurrent ND-100/ND-500 access.
/// </summary>
public ushort ReadWord(uint offset)
{
lock (_accessLock)
{
if (offset >= _size - 1)
throw new ArgumentOutOfRangeException(nameof(offset));
// Big-endian word read
return (ushort)((_memory[offset] << 8) | _memory[offset + 1]);
}
}
/// <summary>
/// Write word to 5MPM (both CPUs can call this).
/// Thread-safe for concurrent ND-100/ND-500 access.
/// </summary>
public void WriteWord(uint offset, ushort value)
{
lock (_accessLock)
{
if (offset >= _size - 1)
throw new ArgumentOutOfRangeException(nameof(offset));
// Big-endian word write
_memory[offset] = (byte)(value >> 8);
_memory[offset + 1] = (byte)(value & 0xFF);
}
}
/// <summary>
/// Read double word (32-bit) from 5MPM.
/// </summary>
public uint ReadDoubleWord(uint offset)
{
lock (_accessLock)
{
ushort high = ReadWordNoLock(offset);
ushort low = ReadWordNoLock(offset + 2);
return ((uint)high << 16) | low;
}
}
/// <summary>
/// Write double word (32-bit) to 5MPM.
/// </summary>
public void WriteDoubleWord(uint offset, uint value)
{
lock (_accessLock)
{
WriteWordNoLock(offset, (ushort)(value >> 16));
WriteWordNoLock(offset + 2, (ushort)(value & 0xFFFF));
}
}
// Internal non-locking versions (when already locked)
private ushort ReadWordNoLock(uint offset)
{
return (ushort)((_memory[offset] << 8) | _memory[offset + 1]);
}
private void WriteWordNoLock(uint offset, ushort value)
{
_memory[offset] = (byte)(value >> 8);
_memory[offset + 1] = (byte)(value & 0xFF);
}
/// <summary>
/// Allocate a process descriptor slot.
/// </summary>
public uint AllocateProcessDescriptor(byte processNumber)
{
if (processNumber >= _maxProcesses)
throw new ArgumentOutOfRangeException(nameof(processNumber));
uint addr = _processDescriptorTable + (uint)(processNumber * PROCESS_DESCRIPTOR_SIZE * 2);
return addr;
}
/// <summary>
/// Allocate a message buffer.
/// </summary>
public uint AllocateMessageBuffer(byte processNumber)
{
uint addr = _messageBufferPool + (uint)(processNumber * MESSAGE_BUFFER_SIZE * 2);
return addr;
}
}
7.2 ND-500 Process Descriptor¶
/// <summary>
/// ND-500 process descriptor in 5MPM.
/// Represents one ND-500 domain/process.
/// </summary>
public class ND500ProcessDescriptor
{
public byte ProcessNumber { get; set; }
public uint DescriptorAddress { get; set; } // XADPROC
public uint MessageBufferAddress { get; set; } // MESSBUFF
public ushort Status { get; set; }
public ushort SendEnable { get; set; } // SENDE (0=inactive, >0=active)
public ushort ReceiveState { get; set; } // RECE
// Domain information
public string DomainName { get; set; }
public uint StartAddress { get; set; } // PC initial value
public ushort[] ProgramCapabilities { get; set; } = new ushort[32];
public ushort[] DataCapabilities { get; set; } = new ushort[32];
/// <summary>
/// Write descriptor to 5MPM.
/// </summary>
public void WriteTo5MPM(MultiportMemory mpm)
{
uint addr = DescriptorAddress;
// Write descriptor fields
mpm.WriteWord(addr + 0, (ushort)(DescriptorAddress & 0xFFFF)); // XADPROC
mpm.WriteWord(addr + 2, (ushort)(MessageBufferAddress & 0xFFFF)); // MESSBUFF
mpm.WriteWord(addr + 4, Status);
mpm.WriteWord(addr + 6, SendEnable);
mpm.WriteWord(addr + 8, ReceiveState);
// Extended fields (domain-specific)
// Start address, capabilities, etc.
}
/// <summary>
/// Read descriptor from 5MPM.
/// </summary>
public void ReadFrom5MPM(MultiportMemory mpm)
{
uint addr = DescriptorAddress;
// Read descriptor fields
Status = mpm.ReadWord(addr + 4);
SendEnable = mpm.ReadWord(addr + 6);
ReceiveState = mpm.ReadWord(addr + 8);
}
/// <summary>
/// Check if process is active.
/// </summary>
public bool IsActive => SendEnable != 0;
}
7.2 ND-500 Side Implementation¶
7.2.1 ND-500 Process Startup¶
When ND-500 process starts (after PLACE-DOMAIN):
/// <summary>
/// Start ND-500 process execution.
/// Called after PLACE-DOMAIN completes.
/// </summary>
public void StartND500Process(byte processNumber)
{
if (_processDescriptors == null || processNumber >= _processDescriptors.Count)
{
Log($"ERROR: Invalid process number {processNumber}");
return;
}
var proc = _processDescriptors[processNumber];
if (!proc.IsPlaced)
{
Log($"ERROR: Process {processNumber} not placed");
return;
}
// Setup ND-500 CPU state
if (nd500Cpu != null)
{
// Set PC to start address
WriteCpuRegister(ND500Register.PC, proc.StartAddress);
// Setup segment capabilities
SetupSegmentCapabilities(proc);
// Initialize trap handlers
SetupTrapHandlers(proc);
// Clear status flags
proc.Status = 0;
proc.IsRunning = true;
proc.WriteTo5MPM(_multiportMemory);
Log($"[3022] Started ND-500 process {processNumber} at PC=0x{proc.StartAddress:X8}");
}
// Send activation interrupt to ND-500
if (_nd5015Controller != null)
{
_nd5015Controller.OnInterruptToND500?.Invoke(12);
}
}
/// <summary>
/// Setup segment capabilities for ND-500 process.
/// Configures the ND-500 MMU for this domain.
/// </summary>
private void SetupSegmentCapabilities(ND500ProcessDescriptor proc)
{
// Load program capabilities into ND-500 CPU
// (Implementation depends on your ND-500 CPU emulator)
for (int seg = 0; seg < 32; seg++)
{
ushort progCap = proc.ProgramCapabilities[seg];
ushort dataCap = proc.DataCapabilities[seg];
// Write to ND-500 capability registers
// This would be CPU-specific implementation
Log($" Seg {seg}: ProgramCap=0x{progCap:X4}, DataCap=0x{dataCap:X4}");
}
}
/// <summary>
/// Setup trap handlers for ND-500 process.
/// </summary>
private void SetupTrapHandlers(ND500ProcessDescriptor proc)
{
// Setup OTE (Own Trap Enable) register
// Setup CTE (Child Trap Enable) register
// Setup THA (Trap Handler Address) register
// Default: Enable all traps, handler at segment 31 (ND-100 monitor)
WriteCpuRegister(ND500Register.OTE1, 0xFFFFFFFF);
WriteCpuRegister(ND500Register.OTE2, 0xFFFFFFFF);
WriteCpuRegister(ND500Register.THA, 0xF8000000); // Segment 31, offset 0
}
7.2.2 Message Handling on ND-500 Side¶
ND-500 code to send message to ND-100:
/// <summary>
/// Simulate ND-500 code sending a message.
/// This is what happens inside the ND-500 CPU when it calls DVIO.
/// </summary>
public class ND500_MessageSend_Simulation
{
private readonly ND5015Controller _controller;
private readonly MultiportMemory _mpm;
private readonly uint _processDescriptorAddr;
/// <summary>
/// Simulate DVIO call from ND-500.
/// Example: CALL DVIO(terminal, buffer, count)
/// </summary>
public void SimulateDVIO_TerminalOutput(string text)
{
// Read message buffer address from process descriptor
uint msgAddr = _mpm.ReadWord(_processDescriptorAddr + 2); // MESSBUFF offset
// Fill message buffer
var msg = new ND500MessageBuffer
{
BufferAddress = msgAddr,
MicrocodeFunction = 0x01, // DVIO OUT
ND500Address = 0x80001000, // Buffer in ND-500 memory
ByteCount = (uint)(text.Length * 2),
Priority = 5,
ErrorCode = 0
};
// Write text to 5MPM buffer area
uint bufferOffset = msgAddr + 64; // After header
for (int i = 0; i < text.Length; i++)
{
_mpm.WriteWord(bufferOffset + (uint)(i * 2), text[i]);
}
// Set "in queue" flag
msg.IsInQueue = true;
msg.WriteTo5MPM(_mpm);
// Write LMAR5: Message address
_controller.WriteRegister(0x04, (ushort)(msgAddr & 0xFFFF));
_controller.WriteRegister(0x05, (ushort)((msgAddr >> 16) & 0xFF));
// Write LCON5: Send message operation (bit 2 + opcode 0x04 in bits 8-14)
_controller.WriteRegister(0x01, 0x0404); // Activate + Send Message
// This triggers interrupt to ND-100!
Console.WriteLine($"[ND-500] DVIO sent: '{text}' (msgAddr=0x{msgAddr:X})");
}
/// <summary>
/// Wait for reply from ND-100.
/// </summary>
public ND500MessageBuffer WaitForReply()
{
// Read message buffer address
uint msgAddr = _mpm.ReadWord(_processDescriptorAddr + 2);
var msg = new ND500MessageBuffer { BufferAddress = msgAddr };
// Poll until message is no longer in queue (ND-100 processed it)
int timeout = 1000;
while (timeout-- > 0)
{
msg.ReadFrom5MPM(_mpm);
if (!msg.IsInQueue)
{
// ND-100 has processed the message
Console.WriteLine($"[ND-500] Reply received: Error=0x{msg.ErrorCode:X4}");
return msg;
}
System.Threading.Thread.Sleep(1); // Wait 1ms
}
Console.WriteLine("[ND-500] Timeout waiting for reply");
return null;
}
}
7.2.3 Complete Operation Codes¶
ND-100 to ND-500 (via LCON5):
| OpCode | Name | Description |
|---|---|---|
| 0x00 | NOP | No operation |
| 0x01 | READ_5MPM | Read word from 5MPM to LDAT5/LDAX5 |
| 0x02 | WRITE_5MPM | Write word from LDAT5/LDAX5 to 5MPM |
| 0x03 | READ_MSG | Read message from 5MPM |
| 0x04 | WRITE_MSG | Write message to 5MPM and set flag |
| 0x05 | INTERRUPT_ND100 | Trigger interrupt to ND-100 |
| 0x06 | START_PROCESS | Start ND-500 process execution |
| 0x07 | STOP_PROCESS | Stop ND-500 process |
| 0x08 | READ_REGISTER | Read ND-500 CPU register |
| 0x09 | WRITE_REGISTER | Write ND-500 CPU register |
| 0x0A | DMA_TRANSFER | Perform DMA transfer (5MPM ↔ ND-500 RAM) |
| 0x10 | ACTIVATE_DOMAIN | Activate placed domain |
| 0x11 | DEACTIVATE_DOMAIN | Deactivate domain |
ND-500 to ND-100 (via TAG-OUT):
| Code | Name | Description |
|---|---|---|
| 0 | READ_MAR | Read Memory Address Register |
| 1 | WRITE_MAR | Write Memory Address Register |
| 2 | READ_STATUS | Read Status Register |
| 3 | WRITE_STATUS | Write Status Register |
| 4 | READ_CONTROL | Read Control Register |
| 5 | RESET_ACTIVATE | Reset/Activate interface |
| 6 | READ_DATA | Read from ND-100 memory (via MAR) |
| 7 | WRITE_DATA | Write to ND-100 memory (via MAR) |
7.2.4 DMA Transfer Implementation¶
/// <summary>
/// Perform DMA transfer between 5MPM and ND-500 RAM.
/// High-speed block transfer.
/// </summary>
public void PerformDMATransfer(uint sourceAddr, uint destAddr, uint byteCount, bool nd100ToNd500)
{
if (_multiportMemory == null)
{
Log("ERROR: 5MPM not initialized");
return;
}
Log($"[DMA] Transfer {byteCount} bytes from 0x{sourceAddr:X8} to 0x{destAddr:X8}");
// Determine if addresses are in 5MPM
bool sourceIn5MPM = (sourceAddr >= _adrzero && sourceAddr < _adrzero + _multiportMemory.Size);
bool destIn5MPM = (destAddr >= _adrzero && destAddr < _adrzero + _multiportMemory.Size);
if (nd100ToNd500)
{
// ND-100 → ND-500 (typically 5MPM → ND-500 RAM)
if (sourceIn5MPM)
{
// Read from 5MPM
uint offset = sourceAddr - _adrzero;
byte[] buffer = new byte[byteCount];
_multiportMemory.ReadBytes(offset, buffer, (int)byteCount);
// Write to ND-500 RAM (CPU-specific)
// nd500Cpu.Memory.WriteBytes(destAddr, buffer);
Log($"[DMA] Copied {byteCount} bytes from 5MPM to ND-500");
}
}
else
{
// ND-500 → ND-100 (typically ND-500 RAM → 5MPM)
if (destIn5MPM)
{
// Read from ND-500 RAM (CPU-specific)
// byte[] buffer = nd500Cpu.Memory.ReadBytes(sourceAddr, byteCount);
// Write to 5MPM
uint offset = destAddr - _adrzero;
// _multiportMemory.WriteBytes(offset, buffer, (int)byteCount);
Log($"[DMA] Copied {byteCount} bytes from ND-500 to 5MPM");
}
}
// Trigger completion interrupt
SetOperationComplete();
}
8. Real-World Scenario: Terminal I/O¶
8.1 Complete Flow: ND-500 Program Writes to Terminal¶
sequenceDiagram
participant User as User Program (ND-500)
participant N5 as ND-500 CPU
participant C15 as 5015 Controller
participant MPM as 5MPM
participant C22 as 3022 Interface
participant N1 as ND-100 CPU
participant TERM as Terminal Driver
participant HW as Terminal Hardware
Note over User,HW: 1. User Program Requests Output
User->>N5: CALL DVIO("TERM:", buffer, 80)
N5->>MPM: Fill message buffer
N5->>MPM: Set ITMQUEUE flag
N5->>C15: LMAR5 := msgAddr
N5->>C15: LCON5 := 0x0404 (Send)
Note over User,HW: 2. Interrupt ND-100
C15->>C22: Signal message ready
C22->>N1: Interrupt Level 12
Note over User,HW: 3. ND-100 Processes Request
N1->>C22: Read interrupt
N1->>MPM: Read message buffer
N1->>N1: Decode: DVIO OUT to TERM:
N1->>MPM: Read data from buffer
N1->>TERM: Send to terminal driver
TERM->>HW: Output to terminal
Note over User,HW: 4. Complete Operation
N1->>MPM: Clear ITMQUEUE flag
N1->>MPM: Set ErrorCode = 0 (success)
N1->>C22: Send reply
C22->>C15: TAG-IN: Message complete
C15->>N5: Interrupt Level 14
N5->>MPM: Read result
N5->>User: Return from DVIO
8.2 C# Implementation of Complete Scenario¶
/// <summary>
/// Complete example: ND-500 program writes to terminal.
/// </summary>
public class CompleteTerminalOutputExample
{
private readonly NDBusND500IF _interface3022;
private readonly ND5015Controller _controller5015;
private readonly MultiportMemory _mpm;
private readonly IND500Cpu _cpu500;
public void RunExample()
{
Console.WriteLine("=== ND-500 Terminal Output Example ===\n");
// Step 1: PLACE-DOMAIN
Console.WriteLine("Step 1: PLACE-DOMAIN");
byte procNum = _interface3022.PlaceDomain("TERMINAL-TEST", 0x00010000);
Console.WriteLine($" Domain placed as process {procNum}\n");
// Step 2: Start process
Console.WriteLine("Step 2: Start ND-500 process");
_interface3022.StartND500Process(procNum);
Console.WriteLine($" Process {procNum} started\n");
// Step 3: ND-500 calls DVIO (simulated)
Console.WriteLine("Step 3: ND-500 calls DVIO");
SimulateND500_DVIO(procNum, "Hello from ND-500!");
// Step 4: ND-100 receives interrupt
Console.WriteLine("\nStep 4: ND-100 receives interrupt level 12");
var msg = _interface3022.ReceiveMessageFromND500(procNum);
if (msg != null)
{
Console.WriteLine($" Message received:");
Console.WriteLine($" Function: 0x{msg.MicrocodeFunction:X4}");
Console.WriteLine($" ByteCount: {msg.ByteCount}");
Console.WriteLine($" ND500Addr: 0x{msg.ND500Address:X8}");
// Step 5: Process I/O request
Console.WriteLine("\nStep 5: Process I/O (terminal output)");
string text = ExtractTextFromMessage(msg);
Console.WriteLine($" OUTPUT TO TERMINAL: '{text}'");
// Step 6: Send reply
Console.WriteLine("\nStep 6: Send reply to ND-500");
msg.ErrorCode = 0; // Success
msg.IsInQueue = false;
_interface3022.SendMessageToND500(procNum, msg);
Console.WriteLine(" Reply sent, ND-500 will continue\n");
}
Console.WriteLine("=== Example Complete ===");
}
private void SimulateND500_DVIO(byte procNum, string text)
{
// Get process descriptor
var proc = GetProcessDescriptor(procNum);
// Fill message buffer
var msg = new ND500MessageBuffer
{
BufferAddress = proc.MessageBufferAddress,
MicrocodeFunction = 0x01, // DVIO OUT
Priority = 5,
ByteCount = (uint)(text.Length * 2),
ND500Address = 0x80001000,
ND100Address = _interface3022.ADRZERO,
ErrorCode = 0
};
// Write text to message data area
byte[] textBytes = System.Text.Encoding.Unicode.GetBytes(text);
msg.Data = textBytes;
// Send message
msg.IsInQueue = true;
msg.WriteTo5MPM(_mpm);
Console.WriteLine($" ND-500 filled message buffer at 0x{msg.BufferAddress:X}");
Console.WriteLine($" Text: '{text}'");
// Trigger interrupt via 5015
_controller5015.WriteRegister(0x04, (ushort)(msg.BufferAddress & 0xFFFF));
_controller5015.WriteRegister(0x01, 0x0404); // Send message
}
private string ExtractTextFromMessage(ND500MessageBuffer msg)
{
if (msg.Data == null || msg.Data.Length == 0)
return "";
return System.Text.Encoding.Unicode.GetString(msg.Data, 0, (int)msg.ByteCount);
}
private ND500ProcessDescriptor GetProcessDescriptor(byte procNum)
{
// Get from _interface3022._processDescriptors[procNum]
// (Implementation depends on your access pattern)
return null;
}
}
9. Performance Considerations¶
9.1 5MPM Access Patterns¶
Thread-safe locking overhead:
// Every 5MPM access requires lock
lock (_accessLock)
{
ushort value = ReadWordNoLock(offset);
}
Optimization: Batch operations
// GOOD: Read entire message in one lock
lock (_accessLock)
{
for (int i = 0; i < 16; i++)
{
fields[i] = ReadWordNoLock(offset + (uint)(i * 2));
}
}
// BAD: Lock per field
for (int i = 0; i < 16; i++)
{
fields[i] = ReadWord(offset + (uint)(i * 2)); // 16 locks!
}
9.2 Interrupt Frequency¶
Typical interrupt rates: - Terminal I/O: ~10-100 interrupts/second - Disk I/O: ~100-1000 interrupts/second - Network: ~1000-10000 interrupts/second
Monitor overhead: - Each interrupt: ~50-100 instructions - Message processing: ~200-500 instructions - Total per I/O: ~300-700 instructions
9.3 Caching and Coherency¶
5MPM MUST bypass cache (bit 13 = S flag):
Data Capability for 5MPM:
0x8000 | 0x2000 | physSeg = Write + Shared + physical segment
W S
Why? Both CPUs access same physical RAM: - ND-100 writes → Must be visible to ND-500 immediately - ND-500 writes → Must be visible to ND-100 immediately - Cache would violate this coherency
10. Debugging Tips¶
10.1 Common Issues¶
Problem 1: "Message not received"
// Check:
1. Is ITMQUEUE flag set? (bit 0 of 5MSFL)
2. Is interrupt enabled? (LCON5 bit 0)
3. Is process descriptor SENDE field > 0?
4. Is message address correct?
// Debug:
Console.WriteLine($"Message flags: 0x{msg.MessageFlags:X4}");
Console.WriteLine($"ITMQUEUE: {msg.IsInQueue}");
Console.WriteLine($"Process SENDE: {proc.SendEnable}");
Problem 2: "5MPM access violation"
// Check:
1. Is address within 5MPM range?
(addr >= _adrzero && addr < _adrzero + size)
2. Is segment capability S flag set? (bit 13)
3. Is 5MPM contiguous?
// Debug:
Console.WriteLine($"Access addr: 0x{addr:X8}");
Console.WriteLine($"5MPM range: 0x{_adrzero:X8} - 0x{_adrzero + size:X8}");
Console.WriteLine($"Data capability: 0x{dataCap:X4}, S={((dataCap & 0x2000) != 0)}");
Problem 3: "ND-500 hangs waiting for reply"
// Check:
1. Did ND-100 clear ITMQUEUE flag?
2. Did ND-100 send interrupt back to ND-500?
3. Is ND-500 interrupt handler working?
// Debug:
Console.WriteLine($"ITMQUEUE after processing: {msg.IsInQueue}");
Console.WriteLine($"Interrupt sent to ND-500: {interruptSent}");
10.2 Logging Best Practices¶
// Add detailed logging with timestamps
public class ND500DebugLogger
{
private static readonly object _logLock = new object();
public static void Log(string source, string message)
{
lock (_logLock)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
Console.WriteLine($"[{timestamp}] [{source}] {message}");
}
}
public static void LogMessage(string source, ND500MessageBuffer msg)
{
Log(source, $"Message: Func=0x{msg.MicrocodeFunction:X4}, " +
$"Flags=0x{msg.MessageFlags:X4}, " +
$"Error=0x{msg.ErrorCode:X4}, " +
$"InQueue={msg.IsInQueue}");
}
}
// Usage:
ND500DebugLogger.Log("3022", "Interrupt level 12");
ND500DebugLogger.LogMessage("ND-500", msg);
Summary¶
You now have complete documentation and implementation for:
- ✅ Domain setup (7 phases from boot to execution)
- ✅ Memory mapping (5MPM layout and structure)
- ✅ Message passing (complete message buffer format)
- ✅ ND-500 side (5015 controller with all operations)
- ✅ Real-world scenarios (terminal I/O example)
- ✅ Performance tips (optimization and caching)
- ✅ Debugging guide (common issues and solutions)
Key takeaways: - 5MPM is physically shared RAM (requires thread-safe access) - Segment capability bit 13 (S) MUST be set for 5MPM - Message passing uses interrupt-driven communication - PLACE-DOMAIN allocates process descriptor and message buffer - Complete operation code tables for both directions
Ready to integrate into your emulator!