Writing a Simulator for the SIMH System Revised 15-May-2003 for V3.0 1. Overview 2. Data Types 3. VM Organization 3.1 CPU Organization 3.1.1 Time Base 3.1.2 Memory Organization 3.1.3 Interrupt Organization 3.1.4 I/O Dispatching 3.1.5 Instruction Execution 3.2 Peripheral Device Organization 3.2.1 Device Timing 3.2.2 Clock Calibration 3.2.3 Data I/O 4. Data Structures 4.1 sim_device Structure 4.1.1 Device Flags 4.1.2 Context 4.1.3 Examine and Deposit Routines 4.1.4 Reset Routine 4.1.5 Boot Routine 4.1.6 Attach and Detach Routines 4.1.7 Memory Size Change Routine 4.2 sim_unit Structure 4.2.1 Unit Flags 4.2.2 Service Routine 4.3 sim_reg Structure 4.3.1 Register Flags 4.4 sim_mtab Structure 4.4.1 Validation Routine 4.4.2 Display Routine 4.5 Other Data Structures 5. VM Provided Routines 5.1 Instruction Execution 5.2 Binary Load and Dump 5.3 Symbolic Examination and Deposit 5.4 Optional Interfaces 5.4.1 Once Only Initialization Routine 5.4.2 Command Input and Post-Processing 5.4.3 VM -Specific Commands 6. Other SCP Facilities 6.1 Multi-Terminal Support (Telnet) 6.2 Magnetic Tape Emulation Library 6.3 Breakpoint Support
SIMH (history simulators) is a set of portable programs, written in C,
which simulate various historically interesting computers.
This document describes how to design, write,
and check out a new simulator for SIMH.
It is not an introduction to either the philosophy or external operation of SIMH,
and the reader should be familiar with both of those topics before proceeding.
Nor is it a guide to the internal design or operation of SIMH,
except insofar as those areas interact with simulator design.
Instead, this manual presents and explains the form,
meaning, and operation of the interfaces between simulators
and the SIMH simulator control package.
It also of fers some suggestions for utilizing the services SIMH offers
and explains the constraints that all simulators operating
within SIMH will experience.
Some terminology:
Each simulator consists of a standard simulator control package
(SCP and related librari es),
which provides a control framework and utility routines for a simulator;
and a unique virtual machine (VM),
which implements the simulated processor and selected peripherals.
A VM consists of multiple devices,
such as the CPU, paper tape reader, disk controller, etc.
Each controller consists of a named state space (called registers) and one or more units.
Each unit consists of a numbered state space (called a data set ).
The host computer is the system on which SIMH runs;
the target computer is the system being simulated.
SIMH is unabashedly based on the MIMIC simulation system,
designed in the late 1960's by Len Fehskens, Mike McCarthy, and Bob Supnik.
This document is based on MIMIC's published interface specification,
"How to Write a Virtual Machine for the MIMIC Simulation System",
by Len Fehskens and Bob Supnik.
SIMH is written in C. The host system must support (at least) 32 -bit data types (64-bit data types for the PDP -10 and other large-word target systems). To cope with the vagaries of C data types, SIMH defines some unambiguous data types for its interfaces:
SIMH data type | interpretation in typical 32-bit C |
int8, uint8 | char, unsigned char |
int16, uint16 | short, unsigned short |
int32, uint32 | int, unsigned int |
t_int64, t_uint64 | long long, _int64 (system specific) |
t_addr | simulated address, unsigned int32 or uint64 |
t_value | simulated value, unsigned int32 or int64 |
t_svalue | simulated signed value, int32 or int64 |
t_mtrec | mag tape record length, int32 |
t_stat | status code, int |
t_bool | true/false value, int |
[The inconsistency in naming t_int64 and t_uint64 is due to Microsoft VC++, which uses int64 as a structure name member in the master Windows definitions file.]
In addition, SIMH defines structures for each of its major data elements
DEVICE | device definition structure |
UNIT | unit definition structure |
REG | register definition structure |
MTAB | modifier definition structure |
CTAB | command definition structure |
A virtual machine (VM) is a collection of devices bound together through their internal logic. Each device is named and corresponds more or less to a hunk of hardware on the real machine; for example:
VM device | Real machine hardware |
CPU | central processor and main memory |
PTR | paper tape reader controller and paper tape reader |
TTI | console keyboard |
TTO | console output |
DKP | disk pack controller and drives |
There may be more than one device per physical hardware entity, as for the console; but for each user-accessible device there must be at least one. One of these devices will have the preeminent responsibility for directing simulated operations. Normally, this is the CPU, but it could be a higher-level entity, such as a bus master.
The VM actually runs as a subroutine of the simulator control package (SCP). It provides a master routine for running simulated programs and other routines and data structures to implement SCP's command and control functions. The interfaces between a VM an d SCP are relatively few:
Interface | Function |
char sim_name[] | simulator name string |
REG *sim_pc | pointer to simulated program counter |
int32 sim_emax | maximum number of words in an instruction |
DEVICE * sim_devices[] | table of pointers to simulated devices, NULL terminated |
char * sim_stop_messages[] | table of pointers to error messages |
t_stat sim_load (...) | binary loader subroutine |
t_stat sim_inst (void) | instruction execution subroutine |
t_stat parse_sym (...) | symbolic instruction parse subroutine (optional) |
t_stat fprint_sym (...) | symbolic instruction print subroutine (optional) |
In addition, there are four optional interfaces, which can be used for special situations, such as GUI implementations:
Interface | Function |
void (* sim_vm_init) (void) | pointer to once-only initialization routine for VM |
char (*sim_vm_read) (...) | pointer to command input routine |
void (* sim_vm_post) (...) | pointer to command post-processing routine |
CTAB *sim_vm_cmd | pointer to simulator -specific command table |
There is no required organization for VM code. The following convention has been used so far. Let name be the name of the real system (i1401 for the IBM 1401; i1620 for the IBM 1620; pdp1 for the PDP -1; pdp18b for the other 18 -bit PDP's; pdp8 for the PDP -8; pdp11 for t he PDP-11; nova for Nova; hp2100 for the HP 21XX; h316 for the Honeywell 315/516; gri for the GRI -909; pdp10 for the PDP -10; vax for the VAX; sds for the SDS -940):
The SIMH standard definitions are in sim_defs.h, the simulator control package in scp.c, and the operating-system dependent terminal routines in scp_tty.c. Additional libraries include sim_tmxr.c (header file sim_tmxr.h) for terminal multiplexors, sim_sock.c (header file sim_sock.h) for network processing, and sim_tape.c (header file sim_tape.h) for magtapes.
Most CPU's perform at least the following functions:
Instruction execution is actually the least complicated part of the design; memory and I/O organization should be tackled first.
In order to simulate asynchronous events, such as I/O completion, the VM must define and keep a time base. This can be accurate (for example, nanoseconds of execution) or arbitrary (for example, number of instructions executed), but it must be consistently used throughout the VM. All existing VM's count time in instructions.
The CPU is responsible for counting down the event counter sim_interval and calling the asynchronous event controller sim_process_event. SCP does the record keeping for timing.
The criterion for memory layout is very simple: use the SIMH data type that is as large as (or if necessary, larger than), the word length of the real machine. Note that the criterion is word length, not addressability: the PDP-11 has byte addressable memory, but it is a 16 -bit machine, and its memory is defined as uint16 M[]. It may seem tempting to define memory as a union of int8 and int16 data types, but this would make the resulting VM endian-dependent. Instead, the VM should be based on the underlying word size of the real machine, and byte manipulation should be done explicitly. Examples:
Simulator | memory size | memory declaration |
IBM 1620 | 5-bit | uint8 |
IBM 1401 | 7-bit | uint8 |
PDP-8 | 12-bit | uint16 |
PDP-11, Nova | 16-bit | uint16 |
PDP-1 | 18-bit | uint32 |
VAX | 32-bit | uint32 |
PDP-10, IBM 7094 | 36-bit | t_uint64 |
The design of the VM's interrupt structure is a complex interaction between efficiency and fidelity to the hardware. If the VM's interrupt structure is too abstract, interrupt driven software may not run. On the other hand, if it follows the hardware too literally, it may significantly reduce simulation speed. One rule I can offer is to minimize the fetch-phase cost of interrupts, even if this complicates the (much less frequent) evaluation of the interrupt system following an I/O operation or asynchronous event. Another is not to over-generalize; even if the real hardware could support 64 or 256 interrupting devices, the simulators will be running much smaller configurations. I'll start with a simple interrupt structure and then offer suggestions for generalization.
In the simplest structure, interrupt requests correspond to device flags and are kept in an interrupt request variable, with one flag per bit. The fetch-phase evaluation of interrupts consists of two steps: are interrupts enabled, and is there an interrupt outstanding? If all the interrupt requests are kept as single-bit flags in a variable, the fetch-phase test is very fast:
Indeed, the interrupt enable flag can be made the highest bit in the interrupt request variable, and the two tests combined:
Setting or clearing device flags direc tly sets or clears the appropriate interrupt request flag:
set: | int_requests = int_requests | DEVICE_FLAG; |
clear: | int_requests = int_requests & ~DEVICE_FLAG; |
At a slightly higher complexity, interrupt requests do not correspond directly to device flags but are based on masking the device flags with an enable (or disable) mask. There are now two parallel variables: device flags and interrupt enable mask. The fetch-phase test is now:
As a next step, the VM may keep a summary interrupt request variable, which is updated by any change to a device flag or interrupt enable/disable:
enable: | int_requests = device_flags & int_enables; |
disable: | int_requests = device_flags & ~int_disables; |
This simplifies the fetch phase test slightly.
At yet higher complexity, the interrupt system may be too complex or too large to evaluate during the fetch-phase. In this case, an interrupt pending flag is created, and it is evaluated by subroutine call whenever a change could occur (start of execution, I/O instruction issued, device time out occurs). This makes fetch-phase evaluation simple and isolates interrupt evaluation to a common subroutine.
If required for interrupt processing, the highest pri ority interrupting device can be determined by scanning the interrupt request variable from high priority to low until a set bit is found. The bit position can then be back-mapped through a table to determine the address or interrupt vector of the interrupting device.
I/O dispatching consists of four steps:
Analyzing an I/O command is usually easy. Most systems have one or more explicit I/O instructions containing an I/O command and a device address. Memory mapped I/O is more complicated; the identification of a reference to I/O space becomes part of memory addressing. This usually requires centralizing memory reads and writes into subroutines, rather than as inline code.
Once an I/O command has been analyzed, the CPU must locate the device subroutine. The simplest way is a lar ge switch statement with hardwired subroutine calls. More modular is to call through a dispatch table, with NULL entries representing non-existent devices; this also simplifies support for modifiable device addresses and configurable devices. Before calli ng the device routine, the CPU usually breaks down the I/O command into standard fields. This simplifies writing the peripheral simulator.
Instruction execution is the responsibility of VM subroutine sim_instr. It is called from SCP as a result of a RUN, GO, CONT, or BOOT command. It begins executing instructions at the current PC (sim_PC points to its register description block) and continues until halted by an error or an external event.
When called, the CPU needs to account for any state changes that the user made. For example, it may need to re-evaluate whether an interrupt is pending, or restore frequently used state to local register variables for efficiency. The actual instruction fetch and execute cycle is usually structured as a loop controlled by an error variable, e.g.,
reason = 0; do { ... } while (reason == 0); or while (reason == 0) { ... }
Within this loop, the usual order of events is:
if (sim_interval <= 0) { if (reason = sim_process_event ()) break; }
A few guidelines for implementation:
The basic elements of a VM are devices, each corresponding roughly to a real chunk of hardware. A device consists of register-based state and one or more units. Thus, a multi-drive disk subsystem is a single device (representing the hardware of the real controller) and one or more units (each representing a single disk drive). Sometimes the device and its unit are the same entity as, for example, in the case of a paper tape reader. However, a single physical device, such as the console, may be broken up for convenience into separate input and output devices.
In general, units correspond to individual sources of input or output (one tape transport, one AtoD channel). Units are the basic medium for both device timing and device I/O. Except for the console, all I/O devices are simulated as host -resident files. SCP allows the user to make an explicit association between a host-resident file and a simulated hardware entity.
Both devices and units have state. Devices operate on registers, which contain information about the state of the device, and indirectly, about the state of the units. Units operate on data sets, which may be thought of as individual instances of input or output, such as a disk pack or a punched paper tape. In a typical multi-unit device, all units are the same, and the device performs similar operations on all of them, depending on which one has been selected by the program being simulated.
(Note: SIMH, like MIMIC, restricts registers to devices. Replicated registers, for example, disk drive current state, are handled via register arrays.)
For each structural level, SIMH defines, and the VM must supply, a corresponding data structure. device structures correspond to devices, reg structures to registers, and unit structures to units. These structures are described in detail in section 4.
The primary functions of a peripheral are:
Command decoding is fairly obvious. At least one section of the peripheral code module will be devoted to processing directives issued by the CPU. Typically, the command decoder will be responsible for register and flag manipulation, and for issuing or canceling I/O requests. The former is easy, but the later requires a thorough understanding of device timing.
The principal problem in I/O device simulation is imitating asynchronous operations in a sequential simulation environment. Fort unately, the timing characteristics of most I/O devices do not vary with external circumstances. The distinction between devices whose timing is externally generated (e.g., console keyboard) and those whose timing is externally generated (disk, paper tape reader) is crucial. With an externally timed device, there is no way to know when an in progress operation will begin or end; with an internally timed device, given the time when an operation starts, the end time can be calculated.
For an internally timed device, the elapsed time between the start and conclusion of an operation is called the wait time. Some typical internally timed devices and their wait times include:
PTR (300 char/sec) | 3.3 msec |
PTP (50 char/sec) | 20 msec |
CLK (line frequency) | 16.6 msec |
TTO (30 char/sec) | 33 msec |
Mass storage devices, such as disks and tapes, do not have a fixed response time, but a start-to-finish time can be calculated based on current versus desired position, state of motion, etc.
For an externally timed device, there is no portable mechanism by which a VM can be notified of an external event (for example, a key stroke). Accordingly, all current VM's poll for keyboard input, thus converting the externally timed keyboard to a pseudo-internally timed device. A more general restriction is that SIMH is single-threaded. Threaded operations must be done by polling using the unit timing mechanism, either with real units or fake units created expressly for polling.
SCP provides the supporting routines for device timing. SCP maintains a list of devices (called active devices) that are in the process of timing out. It also provides routines for querying or manipulating this list (called the active queue). Lastly, it provides a routine for checking for timed out units and executing a VM-specified action when a time-out occurs.
Device timing is done with the UNIT structure, described in section 4. To set up a timed operation, the peripheral calculates a waiting period for a unit and places that unit on the active queue. The CPU counts down the waiting period. When the waiting period has expired, sim_process_event removes the unit from the active queue and calls a device subroutine. A device may also cancel an outstanding timed operation and query the state of the queue. The timing subroutines are:
The timing mechanism described in the previous section is approximate. Devices, such as real time clocks, which track wall time will be inaccurate. SCP provides routines to synchronize multiple simulated clocks (to a max imum of 8) to wall time.
The VM must call sim_rtcn_init for each simulated clock in two places: in the prolog of sim_instr, before instruction execution starts, and whenever the real-time clock is started. The simulator calls sim_rtcn_calb to calculate the actual interval delay when the real-time clock is serviced:
/* clock start */ if (!sim_is_active (&clk_unit)) sim_activate (&clk_unit, sim_rtcn_init (clk _delay, clkno)); etc. /* clock service */ sim_activate (&clk_unit, sim_rtcb_calb (clk_ticks_per_second, clkno);
The real-time clock is usually simulated clock 0; other clocks are used for polling asynchronous multiplexors or intervals timers.
For most devices, timing is half the battle (for clocks it is the entire war); the other half is I/O. Except for the console and other terminals, all I/O devices are simulated as files on the host file system in little-endian format. SCP provides facilities for associating files with units (ATTACH command) and for reading and writing data from and to devices in a endian - and size-independent way.
For most devices, the VM designer does not have to be concerned about the formatting of simulated device files. I/O occurs in 1, 2, 4, or 8 byte quantities; SCP automatically chooses the correct data size and corrects for byte ordering. Specific issues:
Data I/O varies between fixed and variable capacity devices, and between buffered and non-buffered devices. A fixed capacity device differs from a variable capacity device in that the file attached to the former has a maximum size, while the file attached to the latter may expand indefinitely. A buffered device differs from a non-buffered device in that the former buffers its data set in host memory, while the latter maintains it as a file. Most variable capacity devices (such as the paper tape reader and punch) are sequential; all buffered devices are fixed ca pacity.
The ATTACH command creates an association between a host file and an I/O unit. For non buffered devices, ATTACH stores the file pointer for the host file in the fileref field of the UNIT structure. For buffered devices, ATTACH reads the entire host file into a buffer pointed to by the filebuf field of the UNIT structure. If unit flag UNIT_MUSTBUF is set, the buffer is allocated dynamically; otherwise, it must be statically allocated.
For non-buffered devices, I/O is done with standard C subroutines plus the SCP routines fxread and fxwrite. fxread and fxwrite are identical in calling sequence and function to fread and fwrite, respectively, but will correct for endian dependencies. For buffered devices, I/O is done by copying data to or from the allocated buffer. The device code must maintain the number (+1) of the highest address modified in the hwmark field of the UNIT structure. For both the non buffered and buffered cases, the device must perform all address calculations and positioning operations.
SIMH provides capabilities to access files >2GB (the int32 position limit). If a VM is compiled with flags USE_INT64 and USE_ADDR64 defined, then t_addr is defined as t_uint64 rather than uint32. Routine fseek_ext allows simulated devices to perform random access in large files:
Except for the width of the position argument, fseek_ext is identical to standard C fseek.
The DETACH command breaks the association between a host file and an I/O unit. For buffered devices, DETACH writes the allocated buffer back to the host file.
SCP provides two routines for console I/O.
The devices, units, and registers that make up a VM are formally described through a set of data structures which interface the VM to the control portions of SCP. The devices themselves are pointed to by the device list array sim_devices[]. Within a device, both units and registers are allocated contiguously as arrays of structures. In addition, many devices allow the user to set or clear options via a modifications table.
Devices are defined by the sim_device structure (typedef DEVICE):
struct sim_device { char *name; /* name */ struct sim_unit *units; /* units */ struct sim_reg *registers; /* registers */ struct sim_mtab *modifiers; /* modifiers */ int32 numunits; /* #units */ uint32 aradix; /* address radix */ uint32 awidth; /* address width */ uint32 aincr; /* addr increment */ uint32 dradix; /* data radix */ uint32 dwidth; /* data width */ t_stat (*examine)(); /* examine routine */ t_stat (*deposit)(); /* deposit routine */ t_stat (*reset)(); /* reset routine */ t_stat (*boot)(); /* boot routine */ t_stat (*attach)(); /* attach routine */ t_stat (*detach)(); /* detach routine */ void *ctxt /* context */ uint32 flags; /* flags */ t_stat (*msize)(); /* memory size change */ };
The fields are the following:
name | device name, string of all capital alphanumeric characters. |
units | pointer to array of sim_unit structures, or NULL if none. |
registers | pointer to array of sim_reg structures, or NULL if none. |
modifiers | pointer to array of sim_mtab structures, or NULL if none. |
numunits | number of units in this device. |
aradix | radix for input and display of device addresses, 2 to 16 inclusive. |
awidth | width in bits of a device address, 1 to 31 inclusive. |
aincr | increment between device addresses, normally 1; however, byte addressed devices with 16-bit words specify 2, with 32-bit words 4. |
dradix | radix for input and display of device data, 2 to 16 inclusive. |
dwidth | width in bits of device data, 1 to 32 inclusive. |
examine | address of special device data read routine, or NULL if none is required. |
deposit | address of special device data write routine, or NULL if none is required. |
reset | address of device reset routine, or NULL if none is required. |
boot | address of device bootstrap routine, or NULL if none is required. |
attach | address of special device attach routine, or NULL if none is required. |
detach | address of special device detach routine, or NULL if none is required. |
ctxt | address of VM-specific device context table, or N ULL if none is required. |
flags | device flags. |
msize | address of memory size change routine, or NULL if none is required. |
The flags field contains indicators of current device status. SIMH defines 2 flags:
flag name | meaning if set |
DEV_DISABLE | device can be set enabled or disabled |
DEV_DIS | device is currently disabled |
DEV_DYNM | device requires call on msize routine to change memory size |
DEV_NET | device attaches to the network rather than a file |
Starting at bit position DEV_V_UF, the remaining flags are device-specific. Device flags are automatically saved and restored; the device need not supply a register for these bits.
The field contains a pointer to a VM-specific device context table, if required. SIMH never accesses this field. The context field allows VM-specific code to walk VM-specific data structures from the sim_devices root pointer.
For devices which maintain their data sets as host files, SCP implements t he examine and deposit data functions. However, devices which maintain their data sets as private state (for example, the CPU) must supply special examine and deposit routines. The calling sequences are:
The reset routine implements the device reset function for the RESET, RUN, and BOOT commands. Its calling sequence is:
A typical reset routine clears all device flags and c ancels any outstanding timing operations.
If a device responds to a BOOT command, the boot routine implements the bootstrapping function. Its calling sequence is:
A typical bootstrap routine copies a bootstrap loader into main memory and sets the PC to the starting address of the loader. SCP then starts simulation at the specified address.
Normally, the ATTACH and DETACH commands are handled by SCP. However, devices which need to pre- or post-process these commands must supply special attach and detach routines. The calling sequences are:
In practice, these routines usually invoke the standard SCP routines, attach_unit and detach_unit, respectively. For example, here are special attach and detach routines to update line printer error state:
t_stat lpt_attach (UNIT *uptr, char *cptr) { t_stat r; if ((r = attach_unit (uptr, cptr)) != SCPE_OK) return r; lpt_error = 0; return SCPE_OK; } t_stat lpt_detach (UNIT *uptr) { lpt_error = 1; return detach_unit (uptr); }
If the attach routine does not call attach_unit, it must explicitly check to see if the unit is currently attached and detach it before proceeding.
SCP executes a DETACH ALL command as part of simulator exit. Normally, DETACH ALL only calls a unit's detach routine if the unit's UNIT_ ATTABLE flag is set. During simulator exit, the detach routine is called unconditionally. This allows the detach routine of a non-attachable unit to function as a simulator-specific cleanup routine for the unit, device, or entire simulator.
Most units instantiate any memory array at the maximum size possible. This allows apparent memory size to be changed by varying the capac field in the unit structure. For some devices (like the VAX CPU), instantiating the maximum memory size would impose a significant resource burden less memory was actually needed. These devices must provide a routine, the memory size change routine, for RESTORE to use if memory size must be changed:
Units are allocated as contiguous array. Each unit is defined with a sim_unit structure (typedef UNIT):
struct sim_unit { struct sim_unit *next; /* next active */ t_stat (*action)(); /* action routine */ char *filename; /* open file name */ FILE *fileref; /* file reference */ void *filebuf; /* memory buffer */ uint32 hwmark; /* high water mark */ int32 time; /* time out */ uint32 flags; /* flags */ t_addr capac; /* capacity */ t_addr pos; /* file position */ int32 buf; /* buffer */ int32 wait; /* wait */ int32 u3; /* device specific */ int32 u4; /* device specific */ int32 u5; /* device specific */ int32 u6; /* device specific */ };
The fields are the following:
next | pointer to next unit in active queue, NULL if none. |
action | address of unit time-out service routine. |
filename | pointer to name of attached file, NULL if none. |
fileref | pointer to FILE structure of attached file, NULL if none. |
hwmark | buffered devices only; highest modified address, + 1. |
time | increment until time-out beyond previous unit in active queue. |
flags | unit flags. |
capac | unit capacity, 0 if variable. |
pos | sequential devices only; next device address to be read or written. |
buf | by convention, the unit buffer, but can be used for other purposes. |
wait | by convention, the unit wait time, but can be used for other purposes. |
u3 | user-defined. |
u4 | user-defined. |
u5 | user-defined. |
u6 | user-defined. |
buf, wait, u3, u4, u5, u6, and parts of flags are all saved and restored by the SAVE and RESTORE commands and thus can be used for unit state which must be preserved.
Macro UDATA is available to fill in the common fields of a UNIT. It is invoked by
UNIT lpt_unit = { UDATA (&lpt_svc, UNIT_SEQ+UNIT_ATTABLE, 0), 500 };
defines the line printer as a sequential unit with a wait time of 500.
The flags field contains indicators of current unit status. SIMH defines 1 2 flags:
flag name | meaning if set |
UNIT_ATTABLE | the unit responds to ATTACH and DETACH. |
UNIT_RO | the unit is currently read only. |
UNIX_FIX | the unit is fixed capacity. |
UNIT_SEQ | the unit is sequential. |
UNIT_ATT | the unit is currently attached to a file. |
UNIT_BINK | the unit measures "K" as 1024, rather than 1000. |
UNIT_BUFABLE | the unit buffers its data set in memory. |
UNIT_MUSTBUF | the unit allocates its data buffer dynamically. |
UNIT_BUF | the unit is currently buffering its data set in memory. |
UNIT_ROABLE | the unit can be ATTACHed read only. |
UNIT_DISABLE | the unit responds to ENABLE and DISABLE. |
UNIT_DIS | the unit is currently disabled. |
Starting at bit position UNIT_V_UF, the remainin g flags are unit-specific. Unit-specific flags are set and cleared with the SET and CLEAR commands, which reference the MTAB array (see below). Unit-specific flags and UNIT_DIS are automatically saved and restored; the device need not supply a register for these bits.
This routine is called by sim_process_event when a unit times out. Its calling sequence is:
t_stat service_routine (UNIT *uptr)
The status returned by the service routine is passed by sim_process_event back to the CPU.
Registers are allocated as contiguous array,
with a NULL register at the end.
Each register is defined with a sim_reg structure (typedef REG ):
レジスタは末尾が NULL の連続した配列として割り付けられます。
各レジスタは
sim_reg
構造体
(typedef REG)
と定義されます:
struct reg { char *name; /* name */ void *loc; /* location */ uint32 radix; /* radix */ uint32 width; /* width */ uint32 offset; /* starting bit */ uint32 depth; /* save depth */ uint32 flags; /* flags */ uint32 qptr; /* current queue pointer */ };
The fields are the following:
フィールドは次のものがあります:
name |
device name, string of all capital alphanumeric characters.
デバイス名 (すべてが大文字英数字の文字列) |
loc |
pointer to location of the register value.
レジスタの値を保持する場所(変数)へのポインター |
radix |
radix for input and display of data, 2 to 16 inclusive.
データの入力/表示のための基数 (2から16までの値) |
width |
width in bits of data, 1 to 32 inclusive.
データのビット長 (1から32までの値) |
offset |
bit offset (from right end of data).
ビットオフセット (データの右端から) |
depth |
size of data array (normally 1).
データ配列の大きさ (通常は1) |
flags |
flags and formatting information.
フラグおよびフォーマットに関する情報 |
qptr |
for a circular queue, the entry number for the first entry
循環型キューの最初のエントリのエントリ番号 |
The depth field is used with "arrayed registers".
Arrayed registers are used to represent structures with multiple data values,
such as the locations in a transfer buffer;
or structures which are replicated in every unit,
such as a drive status register.
The qptr field is used with "queued registers".
Queued registers are arrays that are organized as circular queues,
such as the PC change queue.
depth
フィールドは配列構造を持ったレジスタに使用されます。
配列構造を持ったレジスタとは
transfer buffer
の中の配置情報のような
複数のデータ値を持つ構造、
あるいは
drive status register
のような
ユニット毎に複製される構造を
表現するために使用されます。
qptr
フィールドはキュー構造をもったレジスタに使用されます。
キュー構造をもったレジスタとは
PC 変更キューのような
循環型キューとして構成されている配列です。
Macros ORDATA, DRDATA, and HRDATA define
right-justified octal, decimal, and hexidecimal registers, respectively.
They are invoked by:
マクロ
ORDATA、
DRDATA、
HRDATA
はそれぞれ
右揃えの8進、10進、および16進レジスタを定義します。
これらは次のように記述されます:
Macro FLDATA defines a one-bit binary flag
at an arbitrary offset in a 32-bit word.
It is invoked by:
マクロ
FLDATA
は32ビットワードの中の
任意のオフセットを持つ
1ビットのフラグを定義します。
これは次のように記述されます:
Macro GRDATA defines a register with arbitrary location and radix.
It is invoked by:
マクロ
GRDATA
は任意の配置や基数を示すレジスタを定義します。
これは次のように記述されます:
Macro BRDATA defines an arrayed register
whose data is kept in a standard C array.
It is invoked by:
マクロ
BRDATA
は
標準のCの配列としてそのデータが保持される、
配列化されたレジスタを定義します。
これは次のように記述されます:
For all of these macros, the flag field
can be filled in manually, e.g.,
これらのマクロはすべて、
flag
フィールドに直接記述することができます。
例えば、
REG lpt_reg = { { DRDATA(POS, lpt_unit.pos, 31), PV_LFT }, ... }
Finally, macro URDATA defines an arrayed register
whose data is part of the UNIT structure.
This macro must be used with great care.
If the fields are set up wrong,
or the data is actually kept somewhere else,
storing through this register declaration can trample over memory.
The macro is invoked by:
最後に、
マクロ
URDATA
はそのデータが UNIT 構造体の一部である、
配列化されたレジスタを定義します。
このマクロは十分注意して使用しなければなりません。
フィールドが誤って設定された場合、
あるいは
データがどこか他の場所に保持されている場合、
このレジスタの宣言に基づいて格納することは
メモリを無視するかもしれません。
これは次のように記述されます:
The location should be an offset in the UNIT structure for unit 0.
The width should be 32 for an int32 or uint32 field,
and T_ADDR_W for a t_addr filed.
The flags can be any of the normal register flags;
REG_UNIT will be OR'd in automatically.
For example,
the following declares an arrayed register of all the UNIT position fields
in a device with 4 units:
location
は
unit 0
の
UNIT
構造体のオフセットです。
width
は
int32
および
uint32
のフィールドを示す32、
あるいは
t_addr
を示す
T_ADDR_W
です。
flags
は正常なレジスタフラグのいずれかでありえます;
REG_UNIT は自動的に論理和が取られるでしょう。
例えば、
下記は4ユニットを備えたデバイスの中の
すべての UNIT 位置フィールドの配列化されたレジスタを宣言します:
{ URDATA(POS, dev_unit[0].pos, 8, T_ADDR_W, 0, 4, 0) }
The flags field contains indicators
that control register examination and deposit.
flag name | meaning if specified |
---|---|
PV_RZRO |
print register right justified with leading zeroes.
|
PV_RSPC |
print register right justified with leading spaces.
|
PV_LEFT |
print register left justified.
|
REG_RO |
register is read only.
|
REG_HIDDEN |
register is hidden (will not appear in EXAMINE STATE).
|
REG_HRO |
register is read only and hidden.
|
REG_NZ |
new register values must be non-zero.
|
REG_UNIT |
register resides in the UNIT structure.
|
REG_CIRC |
register is a circular queue.
|
Device-specific SHOW and SET commands are processed using the modifications array, which is allocated as contiguous array, with a NULL at the end. Each possible modification is defined with a sim_mtab structure (synonym MTAB), which has the following fields:
struct sim_mtab { uint32 mask; /* mask */ uint32 match; /* match */ char *pstring; /* print string */ char *mstring; /* match string */ t_stat (*valid)(); /* validation routine */ t_stat (*disp)(); /* display routine */ void *desc; /* location descriptor */ };
MTAB supports two different structure interpretations: regular and extended. A regular MTAB entry modifies flags in the UNIT flags word; the descriptor entry is not used. The fields are the following:
mask | bit mask for testing the unit. flags field |
match | value to be stored (SET) or compared (SHOW) |
pstring | pointer to character string printed on a match (SHOW), or NULL |
mstring | pointer to character string to be matched (SET), or NULL |
valid | address of validation routine (SET), or NULL |
disp | address of display routine (SHOW), or NULL |
For SET, a regular MTAB entry is interpreted as follows:
For SHOW, a regular MTAB entry is interpreted as follows:
Extended MTAB entries have a different interpretation:
mask | entry flags | ||||||||||||
| |||||||||||||
match | value to be stored (SET) | ||||||||||||
pstring | pointer to character string printed on a match (SHOW), or NULL | ||||||||||||
mstring | pointer to character string to be matched (SET), or NULL | ||||||||||||
valid | address of validation routine (SET), or NULL | ||||||||||||
disp | address of display routine (SHOW), or NULL | ||||||||||||
desc | pointer to a REG structure (MTAB_VAL set) or an int32 (MTAB_VAL clear) |
For SET, an ex tended MTAB entry is interpreted as follows:
For SHOW, an extended MTAB entry is interpreted as follows:
MTAB cpu_tab[] = { { mask, value, "normal", "NORMAL", NULL, NULL, NULL }, { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SPECIAL", NULL, NULL, NULL, &spec_disp }, { 0 } };
A SHOW CPU command will display only the modifier named NORMAL; but SHOW CPU SPECIAL will invoke the special display routine.
The validation routine can be used to validate input during SET processing. It can make other state changes required by the modification or initiate additional dialogs needed by the modifier. Its calling sequence is:
The display routine is called during SHOW processing to display device- or unit-specific state. Its calling sequence is:
When the display routine is called for a regular MTAB entry, SHOW has output the pstring argument but has not appended a newline. When it is called for an extended MTAB entry, SHOW hasn't output anything. SHOW will append a newline after the display routine returns, except for entries with the MTAB_NMO flag set.
char sim_name[] is a character array containing the VM name.
int32 sim_emax contains the maximum number of words needed to hold the largest instruction or data item in the VM. Examine and deposit will process up to sim_emax words.
DEVICE *sim_devices[] is an array of pointers to all the devices in the VM. It is terminated by a NULL. By convention, the CPU is always the first device in the array.
REG *sim_PC points to the reg structure for the program counter. By convention, the PC is always the first register in the CPU's register array.
char *sim_stop_messages[] is an array of pointers to character strings, corresponding to error status returns greater than zero. If sim_instr returns status code n > 0, then sim_stop_message[n] is printed by SCP.
Instruction execution is performed by routine sim_instr. Its calling sequence is:
If the VM responds to the LOAD (or DUMP) command, the load routine (dump routine) is implemented by routine sim_load. Its calling sequence is:
If LOAD or DUMP is not implemented, sim_load should simply return SCPE_ARG. The LOAD and DUMP commands open and close the specified file for sim_load.
If the VM provides symbolic examination and deposit of data, it must provide two routines, fprint_sym for output and parse_sym for input. Their calling sequences are:
If symbolic processing is not implemented, or the output value or input string cannot be parsed, these routines should return SCPE_ARG. If the processing was successful and consumed more than a single word, then these routines should return extra number of words (not bytes) consumed as a negative number. If the processing was successful and consumed a single word, then these routines should return SCPE_OK. For example, PDP-11 parse_sym would respond as follows to various inputs:
input | return value |
XYZGH | SCPE_ARG |
MOV R0,R1 | SCPE_OK |
MOV #4,R5 | -1 |
MOV 1234,5670 | -2 |
The interpretation of switch values is arbitrary, but the following are used by existing VM's:
switch | interpretation |
-a | single character |
-c | character string |
-m | instruction mnemonic |
In addition, on input, a leading ` (apostrophe) is interpreted to mean a single character, and a leading " (double quote) is interpreted to mean a character string.
For greater flexibility, SCP provides some optional interfaces that can be used to extend its command input, command processing, and command post-processing capabilities. These interfaces are strictly optional and are off by default. Using them requires intimate knowledge of how SCP functions internally and is not recommended to the novice VM writer.
SCP defines a pointer (*sim_vm_init)(void). This is a "weak global"; if no other module defines this value, it will default to NULL. A VM requiring special initialization should fill in this pointer with the address of its special initialization routine:
void sim_special_init (void); void (*sim_vm_init)(void) = &sim_special_init;
The special initialization routine can perform any actions required by the VM. If the other optional interfaces are to be used, the initialization routine must fill in the appropriate pointers.
SCP defines a pointer char* (sim_vm_read)(char *, int32 *, FILE *). This is initialized to NULL. If it is filled in by the VM, SCP will use the specified routine to obtain command input in place of its standard routine, read_line. The calling sequence for the vm_read routine is:
The routine is expected to strip off leading whitespace characters and to return NULL on end of file.
SCP defines a pointer void *(sim_vm_post)(t_bool from_scp). This is initialized to NULL. If filled in by the VM, SCP will call the specified routine at the end of every command. This allows the VM to update any local state, such as a GUI console display. The calling sequence for the vm_post routine is:
SCP defines a pointer CTAB *sim_vm_cmd. This is initialized to NULL. If filled in by the VM, SCP interprets it as a pointer to SCP command table. This command table is checked if a user input is not found in the standard command table.
A command table is allocated as a contiguous array. Each entry is defined with a sim_ctab structure (typedef CTAB):
struct sim_ctab { char *name; /* name */ t_stat (*action)(); /* action routine */ int32 arg; /* argument */ char *help; /* help string */ };
If the first word of a command line matches ctab.name, then the action routine is called with the following arguments:
The string passed to the action routine starts at the first non -blank character past the command name.
SIMH supports the use of multiple terminals. All terminals except the console are accessed via Telnet. SIMH provides two supporting libraries for implementing multiple terminals: sim_tmxr.c (and its header file, sim_tmxr.h), which provide OS-independent support routines for terminal multiplexors; and sim_sock.c (and its header file, sim_sock.h), which provide OS-dependent socket routines. Sim_sock.c is implemented under Windows, VMS, UNIX, and MacOS.
Two basic data structures define the multiple terminals. Individual lines are defined by the tmln structure (typedef TMLN):
struct tmln { SOCKET conn; /* line conn */ uint32 ipad; /* IP address */ uint32 cnms; /* connect time ms */ int32 tsta; /* Telnet state */ int32 rcve; /* rcv enable */ int32 xmte; /* xmt enable */ int32 dstb; /* disable Tlnt bin */ int32 rxbpr; /* rcv buf remove */ int32 rxbpi; /* rcv buf insert */ int32 rxcnt; /* rcv count */ int32 txbpr; /* xmt buf remove */ int32 txbpi; /* xmt buf insert */ int32 txcnt; /* xmt count */ uint8 rxb[TMXR_MAXBUF]; /* rcv buffer */ uint8 txb[TMXR_MAXBUF]; /* xmt buffer */ };
The fields are the following:
conn | connection socket (0 = disconnected) |
tsta | Telnet state |
rcve | receive enable flag (0 = disabled) |
xmte | transmit flow control flag (0 = transmit disabled) |
dstb | Telnet bin mode disabled |
rxbpr | receive buffer remove pointer |
rxbpi | receive buffer insert pointer |
rxcnt | receive count |
txbpr | transmit buffer remove pointer |
txbpi | transmit buffer insert pointer |
txcnt | transmit count |
rxb | receive buffer |
txb | transmit buffer |
The overall set of extra terminals is defined by the tmxr structure (typedef TMXR):
struct tmxr { int32 lines; /* # lines */ SOCKET master; /* master socket */ TMLN *ldsc[TMXR_MAXLIN]; /* line descriptors */ };
The fields are the following:
lines | number of lines (constant) |
master | master listening socket (specified by ATTACH command) |
ldsc | array of line descriptors |
Library sim_tmxr.c provides the following routines to support Telnet-based terminals:
(1 << TMXR_V_VALID) | characterIf no character is avai lable, the return variable is 0.
The OS-dependent socket routines should not need to be accessed by the terminal simulators.
SIMH supports the use of emulated magnetic tapes. Magnetic tapes are emulated as disk files containing both data records and metadata markers; the format is fully described in the paper "SIMH Magtape Representation and Handling". SIMH provides a supporting library, sim_tape.c (and its header file, sim_tape.h), that abstracts handling of magnetic tapes. This allows support for multiple tape formats, without change to magnetic device simulators.
The magtape library does not require any special data structures. However, it does define some additional unit flags:
MTUF_WLK | unit is write locked |
If magtape simulators need to define private unit flags, those flags should begin at bit number MTUF_V_UF instead of UNIT_V_UF. The magtape library maintains the current magtape position in the pos field of the UNIT structure.
Library sim_tape.c provides the following routines to support emulated magnetic tapes:
Sim_tape_attach, sim_tape_detach, sim_tape_set_fmt, and sim_tape_show_fmt return standard SCP status codes; the other magtape library routines return return private codes for success and failure. The currently defined magtape status codes are:
MTSE_OK | operation successful |
MTSE_UNATT | unit is not attached to a file |
MTSE_FMT | unit specifies an unsupported tape file format |
MTSE_IOERR | host operating system I/O error during operation |
MTSE_INVRL | invalid record length (exceeds maximum allowed) |
MTSE_RECE | record header contains error flag |
MTSE_TMK | tape mark encountered |
MTSE_BOT | beginning of tape encountered during reverse operation |
MTSE_EOM | end of medium encountered |
MTSE_WRP | write protected unit during write operation |
Sim_tape_set_fmt and sim_tape_show_fmt should be referenced by an entry in the tape device's modifier list, as follows :
MTAB tape_mod[] = { { MTAB_XTD|MTAB_VDV, 0, "FORMAT", "FORMAT", &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, ... };
SCP provides underlying mechanisms to track multiple breakpoints of different types. Most VM's implement at least instruction execution breakpoints (type E); but a VM might also allow for break on read (type R), write (type W), and so on. Up to 26 differ ent breakpoint types, identified by the letters A through Z, are supported.
The VM interface to the breakpoint package consists of three variables and one subroutine:
If the VM only implements one type of breakpoint, then sim_brk_summ is non-zero if any breakpoints are set.
To test whether a breakpoint of particular type is set for an address, the VM calls
Because sim_brk_test can be a lengthy procedure, it is usually prefaced with a test of sim_brk_summ:
if (sim_brk_summ && sim_brk_test (PC, SWMASK (`E'))) { <execution break> }