Multiport Memory and ND-500 Communication - Part 2¶
Continuation: Interrupts, C# Implementation, and Protocol Details
4. Interrupt Mechanisms¶
4.1 Interrupt Architecture Overview¶
flowchart LR
subgraph ND100INT [ND100 Interrupt System]
direction TB
INT12[Level 12 Handler for ND500 Events]
KERNEL[SINTRAN Kernel]
RT[RT Programs]
end
subgraph INTERFACE [ND500 Interface Card]
direction TB
TAG[TAG Registers LTAG5 RTAG5]
CTRL[Control Registers LCON5 RCON5]
STAT[Status Register RSTA5]
MAR[MAR Register LMAR5]
end
subgraph ND500INT [ND500 Interrupt System]
direction TB
MICRO[Microcode Engine]
PROC[ND500 Processes]
end
MPM[5MPM Shared Memory]
RT -->|"1 MON DVIO"| KERNEL
KERNEL -->|"2 Write 5MPM"| MPM
KERNEL -->|"3 LTAG5 LCON5"| TAG
TAG -.->|"4 Hardware Int"| MICRO
MICRO -->|"5 Read 5MPM"| MPM
MICRO -->|"6 Process"| PROC
PROC -->|"7 Write 5MPM"| MPM
MICRO -.->|"8 Hardware Int"| INT12
INT12 -->|"9 Read 5MPM"| MPM
INT12 -->|"10 Resume"| RT
style MPM fill:#009688,stroke:#00695C,stroke-width:3px,color:#fff
style TAG fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
style INT12 fill:#F44336,stroke:#C62828,stroke-width:2px,color:#fff
4.2 ND-100 → ND-500 Interrupt (Activation)¶
Hardware Sequence:
From MP-P2-N500.NPL lines 3084-3094:
% Activate ND-500 - write to hardware
ACT50: 5MBBANK; T:=HDEV+LMAR5; *IOXT % Step 1: Load MAR
A:=X; *IOXT % Step 2: Write message address to MAR
A:=5; T+"LCON5-LMAR5"; *IOXT % Step 3: Write control value 5
% Alternative: Enable for interrupt
A:=10; T:=HDEV+LCON5; *IOXT % Control value 10
A:=0; T+"LSTA5-LCON5"; *IOXT % Clear status
A:=1; T+"LCON5-LSTA5"; *IOXT % Set control
T+"SLOC5-LCON5"; *IOXT % Lock sequence
Step-by-step:
-
Load MAR (Memory Address Register)
IOX write to HDEV+LMAR5 Data = Message buffer address in 5MPM -
Write Control Register
IOX write to HDEV+LCON5 Data = 5 (activate command) or Data = 10 (enable interrupt mode) -
Hardware Action
- ND-500 interface card detects write
- Generates interrupt to ND-500 CPU
- ND-500 microcode reads MAR
- ND-500 reads message from 5MPM at MAR address
4.3 ND-500 → ND-100 Interrupt (Completion)¶
Hardware Sequence:
When ND-500 completes a task:
- ND-500 writes result to message buffer in 5MPM
- ND-500 updates message flags (clear 5ITMQUEUE)
- ND-500 triggers interrupt via interface card
- ND-100 receives interrupt on Level 12
- Level 12 handler reads message from 5MPM
Level 12 Handler Entry:
From MP-P2-N500.NPL (interrupt handler):
% Level 12 - ND-500 interrupt handler
L12HANDLER:
*IOF % Interrupts off
T:=HDEV+RSTA5; *IOXT % Read status register
IF A BIT 5INTPEND THEN % Interrupt pending?
T:=5MBBANK; X:=MAILINK % Get mailbox link
*LINK@3 LDDTX % Read first message
% Process message...
CALL PROCESS_ND500_RESULT
FI
*ION % Interrupts on
MON 0 % Return from interrupt
4.4 Complete Interrupt Flow Diagram¶
sequenceDiagram
autonumber
participant RT as RT Program ND100
participant K as SINTRAN Kernel
participant MPM as 5MPM
participant HW as ND500 Interface
participant MC as ND500 Microcode
participant P as ND500 Process
participant L12 as Level 12 Handler
note over RT,K: ND100 to ND500 Direction
RT->>K: MON DVIO
K->>MPM: Allocate message buffer
K->>MPM: Write message (function addresses data)
K->>MPM: Set 5ITMQUEUE flag
K->>HW: LMAR5 = buffer address
K->>HW: LCON5 = 5 (activate)
HW-->>MC: Hardware Interrupt
MC->>HW: Read MAR
MC->>MPM: Read message at MAR address
MC->>P: Dispatch to ND500 process
note over P,L12: ND500 to ND100 Direction
P->>P: Process request (graphics DB compute)
P->>MPM: Write result to message
P->>MPM: Clear 5ITMQUEUE flag
MC->>HW: Trigger completion interrupt
HW-->>L12: Level 12 Interrupt
L12->>HW: Read RSTA5 (status)
L12->>MPM: Read message buffer
L12->>K: Process result
K->>RT: Resume RT program
4.5 Interrupt Priority and Handling¶
ND-100 Side: - Level 12 - Mass storage and ND-500 events - Shares level with disk controllers - Must be fast - quick read and dispatch
ND-500 Side: - Internal microcode - handles ND-100 requests - Priority scheduling - based on message priority (5PRIO field) - Can be preempted - by higher priority ND-500 tasks
5. Message Protocol¶
5.1 Complete Message Lifecycle¶
stateDiagram-v2
[*] --> Allocated: Kernel allocates buffer
Allocated --> Filled: Write function, addresses
Filled --> Queued: Set 5ITMQUEUE flag
Queued --> Sent: LTAG5/LCON5 interrupt
Sent --> Processing: ND-500 reads message
Processing --> Completed: ND-500 writes result
Completed --> Retrieved: ND-100 Level 12 reads
Retrieved --> Released: Free buffer
Released --> [*]
Processing --> Error: ND-500 error
Error --> Retrieved
note right of Queued
Message in 5MPM
Visible to both CPUs
end note
note right of Processing
ND-500 executing
May take milliseconds
end note
classDef activeState fill:#FFA726,stroke:#F57C00,stroke-width:2px,color:#000
class Queued,Processing activeState
5.2 Message States and Flags¶
| State | 5MSFL Flags | Description |
|---|---|---|
| Free | 0x0000 | Buffer available |
| Allocated | 0x0002 (5SYSRES) | Reserved by kernel |
| Ready | 0x0001 (5ITMQUEUE) | Ready for ND-500 |
| Processing | 0x0000 | ND-500 working (cleared 5ITMQUEUE) |
| Completed | 0x0000 + ErrorCode | Result ready |
| Error | 0x0000 + ErrorCode != 0 | Error occurred |
5.3 Function Codes¶
| Code | Name | Direction | Purpose |
|---|---|---|---|
| 1 | DVIO_OUT | ND-100 → ND-500 | Output operation via ND-500 |
| 2 | DVINST_IN | ND-100 → ND-500 | Input operation via ND-500 |
| 3 | FILE_OP | ND-100 → ND-500 | File system operation |
| 10 | GRAPHICS | ND-100 → ND-500 | Graphics processing |
| 20 | DATABASE | ND-100 → ND-500 | Database query |
| 30 | COMPUTE | ND-100 → ND-500 | Computation task |
| ... | (varies) | - | System-specific |
6. C# Implementation - Complete¶
6.1 Multiport Memory Access¶
/// <summary>
/// Multiport memory (5MPM) access interface.
/// Provides synchronized access to shared memory between ND-100 and ND-500.
/// </summary>
public class MultiportMemoryAccess : IMemoryAccess
{
private readonly byte[] _multiportRAM;
private readonly uint _baseAddress; // ND-100 physical base
private readonly uint _size;
private readonly object _lock = new object(); // Simulate arbitration
// Configuration from boot
private ushort _5mbBank; // 5MPM bank number (from 5MBBANK symbol)
public MultiportMemoryAccess(uint basePhysicalAddr, uint sizeBytes, ushort bank)
{
_baseAddress = basePhysicalAddr;
_size = sizeBytes;
_5mbBank = bank;
_multiportRAM = new byte[sizeBytes];
}
/// <summary>
/// Read word from 5MPM (ND-100 perspective).
/// Address is physical ND-100 address.
/// </summary>
public ushort ReadWord(uint physicalAddress)
{
lock (_lock) // Simulate arbitration
{
if (!IsInMultiportRange(physicalAddress))
throw new ArgumentException($"Address 0x{physicalAddress:X8} not in 5MPM");
uint offset = physicalAddress - _baseAddress;
uint byteOffset = offset * 2; // Word to byte
if (byteOffset + 1 >= _size)
throw new ArgumentOutOfRangeException(nameof(physicalAddress));
// Big-endian (ND-100 is big-endian)
return (ushort)((_multiportRAM[byteOffset] << 8) | _multiportRAM[byteOffset + 1]);
}
}
/// <summary>
/// Write word to 5MPM (ND-100 perspective).
/// </summary>
public void WriteWord(uint physicalAddress, ushort value)
{
lock (_lock)
{
if (!IsInMultiportRange(physicalAddress))
throw new ArgumentException($"Address 0x{physicalAddress:X8} not in 5MPM");
uint offset = physicalAddress - _baseAddress;
uint byteOffset = offset * 2;
if (byteOffset + 1 >= _size)
throw new ArgumentOutOfRangeException(nameof(physicalAddress));
_multiportRAM[byteOffset] = (byte)(value >> 8);
_multiportRAM[byteOffset + 1] = (byte)(value & 0xFF);
}
}
/// <summary>
/// Read from 5MPM (ND-500 perspective).
/// Address has bit 31 set.
/// </summary>
public byte[] ReadND500(uint nd500Address, int byteCount)
{
lock (_lock)
{
// Check bit 31
if ((nd500Address & 0x80000000) == 0)
throw new ArgumentException("ND-500 address must have bit 31 set for 5MPM access");
uint offset = nd500Address & 0x7FFFFFFF; // Clear bit 31
if (offset + byteCount > _size)
throw new ArgumentOutOfRangeException(nameof(byteCount));
byte[] result = new byte[byteCount];
Array.Copy(_multiportRAM, offset, result, 0, byteCount);
return result;
}
}
/// <summary>
/// Write from ND-500 perspective.
/// </summary>
public void WriteND500(uint nd500Address, byte[] data)
{
lock (_lock)
{
if ((nd500Address & 0x80000000) == 0)
throw new ArgumentException("ND-500 address must have bit 31 set for 5MPM access");
uint offset = nd500Address & 0x7FFFFFFF;
if (offset + data.Length > _size)
throw new ArgumentOutOfRangeException(nameof(data));
Array.Copy(data, 0, _multiportRAM, offset, data.Length);
}
}
/// <summary>
/// Check if address is in multiport range.
/// </summary>
private bool IsInMultiportRange(uint physAddr)
{
return physAddr >= _baseAddress && physAddr < _baseAddress + (_size / 2); // Word addresses
}
/// <summary>
/// Convert ND-100 physical to ND-500 address.
/// Implements CNVWADR logic.
/// </summary>
public uint ConvertToND500Address(uint nd100PhysicalAddr)
{
if (!IsInMultiportRange(nd100PhysicalAddr))
throw new ArgumentException("Address not in 5MPM");
uint wordOffset = nd100PhysicalAddr - _baseAddress;
uint byteOffset = wordOffset * 2;
// Set bit 31
return 0x80000000 | byteOffset;
}
}
6.2 Message Buffer Read/Write¶
/// <summary>
/// ND-500 message buffer manager.
/// Handles message allocation, reading, and writing in 5MPM.
/// </summary>
public class ND500MessageManager
{
private readonly MultiportMemoryAccess _5mpm;
private readonly ushort _messageBufferBase; // Start of message buffers in 5MPM
private readonly ushort _messageSize; // 55MESSIZE (from symbols)
public ND500MessageManager(MultiportMemoryAccess mpm, ushort bufferBase, ushort msgSize)
{
_5mpm = mpm;
_messageBufferBase = bufferBase;
_messageSize = msgSize;
}
/// <summary>
/// Read complete message from 5MPM.
/// </summary>
public ND500Message ReadMessage(ushort messageAddress)
{
var msg = new ND500Message
{
Address = messageAddress,
// Header fields (offsets 0-15)
ProcessLink = _5mpm.ReadWord(messageAddress + 0),
MessageFlags = _5mpm.ReadWord(messageAddress + 1),
Priority = _5mpm.ReadWord(messageAddress + 2),
FunctionCode = _5mpm.ReadWord(messageAddress + 3),
ErrorCode = _5mpm.ReadWord(messageAddress + 4),
ToDatafield = _5mpm.ReadDoubleWord(messageAddress + 5),
ByteCount = _5mpm.ReadDoubleWord(messageAddress + 7),
ND500LogicalAddr = _5mpm.ReadDoubleWord(messageAddress + 9),
ND100PhysicalAddr = _5mpm.ReadDoubleWord(messageAddress + 11),
MicrocodeFunction = _5mpm.ReadWord(messageAddress + 13),
DITNumber = _5mpm.ReadWord(messageAddress + 14),
CPUNumber = _5mpm.ReadWord(messageAddress + 15)
};
// Read variable data (if needed)
int dataSize = _messageSize - 16; // Remaining words
msg.Data = new ushort[dataSize];
for (int i = 0; i < dataSize; i++)
{
msg.Data[i] = _5mpm.ReadWord(messageAddress + 16 + (ushort)i);
}
return msg;
}
/// <summary>
/// Write message to 5MPM.
/// </summary>
public void WriteMessage(ushort messageAddress, ND500Message msg)
{
// Write header
_5mpm.WriteWord(messageAddress + 0, msg.ProcessLink);
_5mpm.WriteWord(messageAddress + 1, msg.MessageFlags);
_5mpm.WriteWord(messageAddress + 2, msg.Priority);
_5mpm.WriteWord(messageAddress + 3, msg.FunctionCode);
_5mpm.WriteWord(messageAddress + 4, msg.ErrorCode);
_5mpm.WriteDoubleWord(messageAddress + 5, msg.ToDatafield);
_5mpm.WriteDoubleWord(messageAddress + 7, msg.ByteCount);
_5mpm.WriteDoubleWord(messageAddress + 9, msg.ND500LogicalAddr);
_5mpm.WriteDoubleWord(messageAddress + 11, msg.ND100PhysicalAddr);
_5mpm.WriteWord(messageAddress + 13, msg.MicrocodeFunction);
_5mpm.WriteWord(messageAddress + 14, msg.DITNumber);
_5mpm.WriteWord(messageAddress + 15, msg.CPUNumber);
// Write data
if (msg.Data != null)
{
for (int i = 0; i < msg.Data.Length && i < _messageSize - 16; i++)
{
_5mpm.WriteWord(messageAddress + 16 + (ushort)i, msg.Data[i]);
}
}
}
/// <summary>
/// Set message ready flag (5ITMQUEUE).
/// </summary>
public void SetMessageReady(ushort messageAddress)
{
ushort flags = _5mpm.ReadWord(messageAddress + 1);
flags |= 0x0001; // 5ITMQUEUE bit
_5mpm.WriteWord(messageAddress + 1, flags);
}
/// <summary>
/// Clear message ready flag (when ND-500 starts processing).
/// </summary>
public void ClearMessageReady(ushort messageAddress)
{
ushort flags = _5mpm.ReadWord(messageAddress + 1);
flags &= unchecked((ushort)~0x0001); // Clear 5ITMQUEUE
_5mpm.WriteWord(messageAddress + 1, flags);
}
/// <summary>
/// Check if message is ready for processing.
/// </summary>
public bool IsMessageReady(ushort messageAddress)
{
ushort flags = _5mpm.ReadWord(messageAddress + 1);
return (flags & 0x0001) != 0; // 5ITMQUEUE bit
}
}
/// <summary>
/// Message structure for C# use.
/// </summary>
public class ND500Message
{
public ushort Address { get; set; }
public ushort ProcessLink { get; set; }
public ushort MessageFlags { get; set; }
public ushort Priority { get; set; }
public ushort FunctionCode { get; set; }
public ushort ErrorCode { get; set; }
public uint ToDatafield { get; set; }
public uint ByteCount { get; set; }
public uint ND500LogicalAddr { get; set; }
public uint ND100PhysicalAddr { get; set; }
public ushort MicrocodeFunction { get; set; }
public ushort DITNumber { get; set; }
public ushort CPUNumber { get; set; }
public ushort[] Data { get; set; }
public bool IsInQueue => (MessageFlags & 0x0001) != 0;
public bool IsSystemReserved => (MessageFlags & 0x0002) != 0;
public bool HasError => ErrorCode != 0;
public string FunctionName => FunctionCode switch
{
1 => "DVIO_OUT",
2 => "DVINST_IN",
3 => "FILE_OP",
10 => "GRAPHICS",
20 => "DATABASE",
30 => "COMPUTE",
_ => $"FUNC_{FunctionCode}"
};
}
Continue to Part 3 for DVIO/DVINST flow, code loading, and complete examples...
Total documentation will be ~4 parts due to the extensive detail required. Should I continue?