1. Writing a simulator for the SIMH system¶
- Date:
2020-03-01
- Version:
4.0
- Revision:
$Format:%H$
- Copyright:
See LICENSE.txt for terms of use.
1.1. Overview¶
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 offers 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 libraries), 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 1960s 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.
1.2. Data types¶
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 |
|---|---|
|
|
|
|
|
|
|
|
|
Simulated address, |
|
Simulated value, |
|
Simulated signed value, |
|
Mag tape record length, |
|
Status code, |
|
True/false value, |
Note
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 definition structure |
|
Unit definition structure |
|
Register definition structure |
|
Modifier definition structure |
|
Command definition structure |
|
Debug table entry structure |
1.3. VM organization¶
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 |
|---|---|
|
Central processor and main memory |
|
Paper tape reader controller and paper tape reader |
|
Console keyboard |
|
Console output |
|
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 pre-eminent 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 and SCP are relatively few:
Interface |
Function |
|---|---|
|
Simulator name string |
|
Pointer to simulated program counter |
|
Maximum number of words in an instruction or data item |
|
Table of pointers to simulated devices, |
|
Table of pointers to error messages |
|
Binary loader subroutine |
|
Instruction execution subroutine |
|
Symbolic instruction/data parse subroutine |
|
Symbolic instruction/data print subroutine |
In addition, there are several optional interfaces, which can be used for special situations, such as GUI implementations:
Interface |
Function |
|---|---|
|
Pointer to address parsing routine |
|
Pointer to address output routine |
|
Pointer to address format routine |
|
Pointer to command input routine |
|
Pointer to command post-processing routine |
|
Pointer to stop message format routine |
|
Pointer to routine returning the VM PC value |
|
Pointer to routine that determines if the current instruction is a subroutine call |
|
Pointer to string specifying the simulator specific release version |
|
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 PDPs;
pdp8 for the PDP-8;
pdp11 for the 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):
name.hcontains definitions for the particular simulatorname_sys.ccontains all the SCP interfaces except the instruction simulatorname_cpu.ccontains the instruction simulator and CPU data structuresname_stddev.ccontains the peripherals which were standard with the real system.name_lp.ccontains the line printer.name_mt.ccontains the mag tape controller and drives, etc.
The SIMH standard definitions are in sim_defs.h.
Most definitions required by a VM can be obtained simply by including that file.
A few require additional header files; those are called out below.
The base components of SIMH are:
Source module |
Header file |
Module |
|---|---|---|
|
|
Control package |
|
|
Terminal I/O library |
|
|
File I/O library |
|
|
Timer library |
|
Socket I/O library |
|
|
|
Ethernet I/O library |
|
Serial Port I/O library |
|
|
Terminal multiplexer simulation library |
|
|
Disk simulation library |
|
|
Magtape simulation library |
1.3.1. CPU organization¶
Most CPUs perform at least the following functions:
Time keeping
Instruction fetching
Address decoding
Execution of non-I/O instructions
I/O command processing
Interrupt processing
Instruction execution is actually the least complicated part of the design; memory and I/O organization should be tackled first.
1.3.1.1. Time base¶
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 used consistently throughout the VM. Many existing VMs count time in instructions, some count time in cycles that may align with cycles in the original hardware that may reflect different instructions and/or combinations of memory references.
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.
SCP will display pending events or other activities and report the number these event times reflect using the string sim_vm_interval_units.
The sim_vm_interval_units defaults to "instructions",
but the simulator may change this to "cycles" if the simulator tracks machine state updates internally based on cycles.
A simulator’s time base needs to be specifically considered when writing simulated devices.
The correct choice that a DEVICE may use depends on,
not only the sim_interval decrement strategy,
but also the nature of how long the DEVICE being simulated completed various activities actually being simulated.
Usually, DEVICEs simulate some physical interaction that the CPU made with mechanical components
(tape drives, card readers, disk drives, etc.).
The time that activities on these mechanical devices was many times the instruction execution rate of the processor (100s, 1000s or more).
A DEVICE author usually chooses DEVICE delays that are something between:
The amount of
sim_intervaldecrements that relates to how long the activities on that particularDEVICEactually took.The absolute minimum that software (operating systems, applications, or diagnostics) running within the simulator were capable of receiving a completion notification for the particular
DEVICEactivity.
The absolute minimum case would often reflect that the software in question (device driver, or other) may setup some sort of I/O to the device
but not actually be prepared to realize the operation’s completion one instruction after whatever the CPU did to initiate the operation
(for instance the interrupt handler for device’s I/O completion).
It probably would have been smarter if the original software author established the interrupt handler before initiating the I/O activity,
but real hardware never responded in one instruction time,
so that software always worked on hardware.
Since the goal of the simulation is to have the simulator work with existing software,
the DEVICE simulation should reflect this goal.
This minimum value is usually observed during DEVICE simulator development and thus adjusted by the failure of such software.
1.3.1.2. Step function¶
SCP implements a stepping function using the STEP command.
STEP counts down a specified number of time units (as described in section 3.1.1) and then stops simulation.
The VM can override the STEP command’s counts by calling routine sim_cancel_step:
t_stat sim_cancel_step (void) /* cancel STEP count down */
The VM can then inspect variable sim_step to see if a STEP command is in progress.
If sim_step is non-zero,
it represents the number of steps to execute.
The VM can count down sim_step using its own counting method,
such as cycles, instructions, or memory references.
If the VM counts steps in units other than instructions,
it can set the sim_vm_step_unit string pointer to reflect this.
1.3.1.3. Memory organization¶
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 |
|
IBM 1401 |
7-bit |
|
PDP-8 |
12-bit |
|
PDP-11, Nova |
16-bit |
|
PDP-1 |
18-bit |
|
VAX |
32-bit |
|
PDP-10, IBM 7094 |
36-bit |
|
1.3.1.4. Interrupt organization¶
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:
if (int_enable && int_requests) {
/* …process interrupt… */
}
Indeed, the interrupt enable flag can be made the highest bit in the interrupt request variable, and the two tests combined:
if (int_requests > INT_ENABLE) {
/* …process interrupt… */
}
Setting or clearing device flags directly 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:
if (int_enable && (dev_flags & int_enables)) {
/* …process interrupt… */
}
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 priority 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.
1.3.1.5. I/O dispatching¶
I/O dispatching consists of four steps:
Identify the I/O command and analyze for the device address.
Locate the selected device.
Break down the I/O command into standard fields.
Call the device processor.
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 large 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 calling the device routine,
the CPU usually breaks down the I/O command into standard fields.
This simplifies writing the peripheral simulator.
1.3.1.6. Instruction execution¶
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 the event timer
sim_intervalhas reached zero, process any timed events. This is done by SCP subroutinesim_process_event. Because this is the polling mechanism for user-generated processor halts (^E), errors must be recognized immediately:if (sim_interval <= 0) { if (reason = sim_process_event ()) break; }
Check for outstanding interrupts and process if required.
Check for other processor-unique events, such as wait-state outstanding or traps outstanding.
Check for an instruction breakpoint. SCP has a comprehensive breakpoint facility. It allows a VM to define many different kinds of breakpoints. The VM checks for execution (type E) breakpoints during instruction fetch.
Fetch the next instruction, increment the PC, optionally decode the address, and dispatch (via a
switchstatement) for execution.
A few guidelines for implementation:
In general, code should reflect the hardware being simulated. This is usually simplest and easiest to debug.
The VM should provide some debugging aids. The existing CPU’s all provide multiple instruction breakpoints, a PC change queue, error stops on invalid instructions or operations, and symbolic examination and modification of memory.
1.3.2. Peripheral device organization¶
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 A-to-D channel). Units are the basic medium for both device timing and device I/O. Except for the console, terminals, and network devices, all other 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 and execution
device timing
data transmission
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.
1.3.2.1. Device timing¶
The principal problem in I/O device simulation is imitating asynchronous operations in a sequential simulation environment. Fortunately, 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 internally 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 VMs 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 units (called active units) 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:
t_stat sim_activate (UNIT *uptr, int32 wait)This routine places the specified unit on the active queue with the specified waiting period. A waiting period of 0 is legal; negative waits cause an error. If the unit is already active, the active queue is not changed, and no error occurs.
t_stat sim_activate_abs (UNIT *uptr, int32 wait)This routine places the specified unit on the active queue with the specified waiting period. A waiting period of 0 is legal; negative waits cause an error. If the unit is already active, the specified waiting period overrides the currently pending waiting period.
t_stat sim_activate_after (UNIT *uptr, uint32 usec_delay)This routine places the specified unit on the active queue with the specified delay based on the simulator’s calibrated clock. The specified delay must be greater than 0 µsecs. If the unit is already active, the active queue is not changed, and no error occurs.
t_stat sim_activate_after_d (UNIT *uptr, double usec_delay)This routine places the specified unit on the active queue with the specified delay based on the simulator’s calibrated clock. The specified delay must be greater than 0 µsecs. If the unit is already active, the active queue is not changed, and no error occurs.
t_stat sim_activate_after_abs (UNIT *uptr, uint32 usec_delay)This routine places the specified unit on the active queue with the specified delay based on the simulator’s calibrated clock. The specified delay must be greater than 0 µsecs. If the unit is already active, the specified delay overrides the currently pending waiting period.
t_stat sim_activate_after_abs_d (UNIT *uptr, double usec_delay)This routine places the specified unit on the active queue with the specified delay based on the simulator’s calibrated clock. The specified delay must be greater than 0 µsecs. If the unit is already active, the specified delay overrides the currently pending waiting period.
t_stat sim_cancel (UNIT *uptr)This routine removes the specified unit from the active queue. If the unit is not on the queue, no error occurs.
t_bool sim_is_active (UNIT *uptr)This routine tests whether a unit is in the active queue. If it is, the routine returns
TRUE(1); if it is not, the routine returnsFALSE(0).
int32 sim_activate_time (UNIT *uptr)This routine returns the time the device has remaining in the queue\(\ + 1\). If it is not pending, the routine returns 0.
double sim_activate_time_usecs (UNIT *uptr)This routine returns the wall clock time in µsecs the device has remaining in the queue + 1. If the unit is not pending, the routine returns 0.
double sim_gtime (void)This routine returns the time elapsed since the last
RUNorBOOTcommand.
uint32 sim_grtime (void)This routine returns the low-order 32b of the time elapsed since the last
RUNorBOOTcommand.
int32 sim_qcount (void)This routine returns the number of entries on the clock queue.
t_stat sim_process_event (void)This routine removes all timed out units from the active queue and calls the appropriate device subroutine to service the time-out.
int32 sim_intervalThis variable represents the time until the first unit on the event queue that is scheduled to happen.
sim_instcounts down this value (usually by 1 for each instruction executed). If there are no timed events outstanding, SCP counts down a “null interval” of 10,000 time units.
1.3.2.2. Clock calibration¶
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 maximum of 8) to wall time.
int32 sim_rtcn_init_unit_ticks (UNIT *uptr, int32 clock_interval, int32 clk, int32 ticksper)This routine initializes the clock calibration mechanism for simulated clock
clkanduptridentifies which unit’s service routine performs clock tick activities. The argumentclock_intervalis returned as the result. Theticksperargument specifies the clock ticks per second.
int32 sim_rtcn_init_unit (UNIT *uptr, int32 clock_interval, int32 clk, int32 ticksper)This routine initializes the clock calibration mechanism for simulated clock
clkanduptridentifies which unit’s service routine performs clock tick activities. The argumentclock_intervalis returned as the result.
int32 sim_rtcn_calb (int32 tickspersecond, int32 clk)This routine calibrates simulated clock
clk. The argument is the number of clock ticks expected per second. The return value is the calibrated interval for the next tick.
int32 sim_rtcn_calb_tick (int32 clk)This routine calibrates simulated clock
clk. The return value is the calibrated interval for the next tick.
Some host computers have relatively poor resolution clock ticks (\(>= 10ms\)) and/or variable or high minimum sleep times (\(> 2ms\)). Some simulators have clocks which may need to tick faster than the clock resolution or minimum sleep times. In order to provide accurate time services, a simulator should notify the timing services that the simulated system has digested a previously generated clock tick.
t_stat sim_rtcn_tick_ack (int32 delay, int32 clk)This routine informs the timing subsystem that the most recent clock tick for the simulated clock has been digested by the simulated system and the timing subsystem can potentially schedule a catchup ticks if necessary. If a catchup clock tick is necessary, the delay value indicates how soon the tick can be generated.
The VM must call sim_rtcn_init_unit_ticks for each simulated clock in two places:
in the reset routine of the DEVICE which implements a clock device
(all reset routines are executed at simulator startup and when a DEVICE is enabled or disabled)
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_unit_ticks (&clk_unit, clk_delay, clkno, clk_ticks_per_second));
/* etc. */
/* clock service */
sim_rtcn_calb_tick (clkno);
sim_activate_after (&clk_unit, 1000000 / clk_ticks_per_second);
/* clock register access */
sim_rtcn_tick_ack (20, clkno);
The real-time clock is usually simulated clock 0; other clocks are used for polling asynchronous multiplexers or intervals timers.
The underlying timer services will automatically run a calibrated clock whenever the simulator doesn’t have one registered and running or when the registered timer is running too fast for accurate clock calibration.
This will allow the sim_activate_after API to provide proper wall clock relative timing delays.
Some simulated systems use programmatic interval timers to implement clock ticks.
If a simulated system or simulated operating system uses a constant interval to provide the system clock ticks,
then clock device is a candidate to be a calibrated timer.
If the simulated operating system dynamically changes the programmatic interval more than once,
then such a device is not a calibrated timer,
but it certainly should use sim_activate_after and sim_activate_time to implement the programmatic interval delays.
1.3.2.3. Pre-calibration¶
Some simulator situations expect that instruction execution rates be immediately close to the rate that the host system is capable of executing instructions at and for wall clock delays to be immediately precise. The simulator framework provides a means of pre-calibrating the instruction execution rate. A sequence of three to four instructions that run in a tight loop can be used at simulator startup time to compute the execution rate.
The following line in and around the CPU device reset routine will serve to facilitate the precalibration:
static const char *vax_clock_precalibrate_commands[] = {
"-m 100 INCL 120",
"-m 103 INCL 124",
"-m 106 MULL3 120,124,128",
"-m 10D BRW 100",
"PC 100",
NULL
};
t_stat cpu_reset (DEVICE *dptr)
{
sim_clock_precalibrate_commands = vax_clock_precalibrate_commands;
sim_vm_initial_ips = SIM_INITIAL_IPS;
/* [...] */
}
Once pre-calibration has been done,
some wall clock delays on some (very slow) host systems may be unreasonably off with respect to actual instruction execution.
To accommodate for this the simulator may provide an initial expectation of how fast it can execute the pre-calibration instruction loop.
This estimate can be specified by the simulator in the CPU device reset routine.
An appropriate value for the sim_vm_initial_ips is best determined by comparing the VAX simulator pre-calibrated result
(displayed in the SHOW CLOCK command)
to your simulator’s pre-calibrated value.
If your value is approximately N times the VAX’s value,
then sim_vm_initial_ips should be set to N times SIM_INITIAL_IPS.
1.3.2.4. Idling¶
Idling is a way of pausing simulation when no real work is happening,
without losing clock calibration.
The VM must detect when it is idle;
it can then inform the host of this situation by calling sim_idle:
t_bool sim_idle (int32 clk, int tick_decrement)Attempt to idle the VM until the next scheduled I/O event, using simulated clock
clkas the time base, and decrementsim_intervalby an appropriate number of cycles. If a calibrated timer is not available, or the time until the next event is less than 1ms, decrementsim_intervalbytick_decrement; otherwise, leavesim_intervalunchanged.
sim_idle returns TRUE if the VM actually idled,
FALSE if it did not.
In order for idling to be well-behaved on the host system, simulated devices which poll for input (console and terminal multiplexors are examples), the polling that these devices perform should be done at the same time as when the simulator will unavoidably be executing instructions. The most common time this happens is when click tick interrupts are generated. As such, these devices should schedule their polling activities to be aligned with the clock ticks which are happening anyway or some multiple of the clock tick value.
t_stat sim_clock_coschedule (UNIT *uptr, int32 interval)This routine places the specified unit on the active queue behind the default timer at the specified interval rounded up to a whole number of timer ticks. An interval value 0 is legal; negative intervals cause an error. If the unit is already active, the active queue is not changed, and no error occurs.
t_stat sim_clock_coschedule_abs (UNIT *uptr, int32 interval)This routine places the specified unit on the active queue behind the default timer at the specified interval rounded up to a whole number of timer ticks. An interval value 0 is legal; negative intervals cause an error. If the unit is already active, the specified waiting period overrides the currently pending waiting period.
t_stat sim_clock_coschedule_tmr (UNIT *uptr, int32 tmr, int32 ticks)This routine places the specified unit on the active queue behind the specified timer with the specified number of clock ticks between invocations. A tick count of 0 is legal; negative ticks cause an error. If the unit is already active, the active queue is not changed, and no error occurs. Events scheduled for 0 or 1 tick will fire on the next clock tick.
t_stat sim_clock_coschedule_tmr_abs (UNIT *uptr, int32 tmr, int32 ticks)This routine places the specified unit on the active queue behind the specified timer with the specified number of clock ticks between invocations. A tick count of 0 is legal; negative ticks cause an error. If the unit is already active, the specified waiting period overrides the currently pending waiting period. Events scheduled for 0 or 1 tick will fire on the next clock tick.
Because idling and throttling are mutually exclusive, the VM must inform SCP when idling is turned on or off:
t_stat sim_set_idle (UNIT *uptr, int32 val, const char *cptr, void *desc)Inform SCP that idling is enabled.
t_stat sim_clr_idle (UNIT *uptr, int32 val, const char *cptr, void *desc)Inform SCP that idling is disabled.
t_stat sim_show_idle (FILE *st, UNIT *uptr, int32 val, const void *desc)Display whether idling is enabled or disabled, as seen by SCP.
1.3.2.5. Data I/O¶
For most devices, timing is half the battle
(for clocks it is the entire war);
the other half is I/O.
Some devices are simulated on real hardware
(for example, Ethernet controllers).
Most 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 an 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:
Line printers should write data as 7-bit ASCII, with newlines replacing carriage-return/line-feed sequences.
Disks should be viewed as linear data sets, from sector 0 of surface 0 of cylinder 0 to the last sector on the disk. This allows easy transcription of real disks to files usable by the simulator.
Magtapes, by convention, use a record based format. Each record consists of a leading 32-bit record length, the record data (padded with a byte of 0 if the record length is odd), and a trailing 32-bit record length. File marks are recorded as one record length of 0.
Cards have 12 bits of data per column, but the data is most conveniently viewed as (ASCII) characters. Column binary can be implemented using two successive characters per card column.
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 capacity.
1.3.2.5.1. Reading and writing data¶
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 UNIT.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 sim_fread and sim_fwrite.
sim_fread and sim_fwrite 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 sim_fseek allows simulated devices to perform random access in large files:
int sim_fseek (FILE *handle, t_addr position, int where)Identical to the standard C
fseek, with two exceptions:SEEK_ENDis not supported.The
positionargument can be 64b wide.
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.
1.3.2.5.2. Console I/O¶
SCP provides three routines for console I/O.
t_stat sim_poll_kbd (void)This routine polls for keyboard input. If there is a character, it returns
SCPE_KFLAG+ the character. If the console is attached to a Telnet connection, and the connection is lost, the routine returnsSCPE_LOST. If there is no input, it returnsSCPE_OK.
t_stat sim_putchar (int32 char)This routine types the specified ASCII character to the console. If the console is attached to a Telnet connection, and the connection is lost, the routine returns
SCPE_LOST.
t_stat sim_putchar_s (int32 char)This routine outputs the specified ASCII character to the console. If the console is attached to a Telnet connection, and the connection is lost, the routine returns
SCPE_LOST; if the connection is backlogged, the routine returnsSCPE_STALLand the output should retried at a later time.
1.3.2.5.3. Simulators for computers without a console port¶
If a computer being simulated doesn’t have a console port,
SCP will call sim_poll_kbd periodically to detect when a user types
^E (Control-E)
in the session running the simulator and they will be returned to the sim> prompt.
1.4. Data structures¶
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.
Note that a device must always have at least one unit,
even if that unit is not needed for simulation purposes.
A device that does not need registers need not provide a register table,
instead the registers field is set to NULL.
Device registers serve two purposes:
Provide a means of letting the simulator user (more often the developer) have visibility to examine and potentially change arbitrary state variables within the simulator from the
sim>prompt rather than having to use a debugger.Provide all of the information in the internal state of a simulated device so that a
SAVEcommand can capture that state and a subsequentRESTORE(after exiting and restarting the same simulator) will be able to proceed without any information being missing.
A device unit serves two fundamental purposes in a simulator:
It acts as an entity which can generate events which are handled in the simulated instruction stream (via one of the
sim_activateAPIs)It provides a place which holds an open file pointer for simulated devices which have content bound to file contents (via
ATTACHcommands).
For example:
A UNIT can be mapped to real units in a simulated device
(i.e., disk drives),
or it might serve merely to perform timing related activities,
or both of these might be present.
The pdp11_rq simulation has a combination of both of these.
There are 4 units which map one to one directly to simulated disk drives,
and there are 2 additional units.
One is used to time various things and one is used to provide instruction delays while walking through the MSCP initialization and command processing sequence.
1.4.1. DEVICE structure¶
Devices are defined by the DEVICE structure (typedef DEVICE):
struct DEVICE {
const char *name; /* Name */
struct UNIT *units; /* Units */
struct REG *registers; /* Registers */
struct MTAB *modifiers; /* Modifiers */
int32 numunits; /* Number of 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 */
uint32 dctrl; /* Debug control flags */
struct DEBTAB *debflags; /* Debug flag names */
t_stat (*msize)(); /* Memory size change */
char *lname; /* Logical name */
t_stat (*help)(); /* Help routine */
t_stat (*attach_help)(); /* Attach help routine */
void *help_ctxt; /* Help context */
const char *(*description)(); /* Device description */
};
The fields are the following:
nameDevice name, string of all capital alphanumeric characters.
unitsPointer to array of
UNITstructures, orNULLif none.
registersPointer to array of
REGstructures, orNULLif none.
modifiersPointer to array of
MTABstructures, orNULLif none.
numunitsNumber of units in this device.
aradixRadix for input and display of device addresses, 2 to 16 inclusive.
awidthWidth in bits of a device address, 1 to 64 inclusive. See also: section 1.4.1.1.
aincrIncrement between device addresses, normally 1; however, byte-addressed devices with 16-bit words specify 2, with 32-bit words 4. See also: section 1.4.1.1.
dradixRadix for input and display of device data, 2 to 16 inclusive.
dwidthWidth in bits of device data, 1 to 64 inclusive.
examineAddress of special device data read routine, or
NULLif none is required.
depositAddress of special device data write routine, or
NULLif none is required.
resetAddress of device reset routine, or
NULLif none is required.
bootAddress of device bootstrap routine, or
NULLif none is required.
attachAddress of special device attach routine, or
NULLif none is required.
detachAddress of special device detach routine, or
NULLif none is required.
ctxtAddress of VM-specific device context table, or
NULLif none is required. See also: section 1.4.1.3.
flagsDevice flags. See section 1.4.1.2.
dctrlDebug control flags.
debflagsPointer to array of
DEBTABstructures, orNULLif none.
msizeAddress of memory size change routine, or
NULLif none is required.
lnamePointer to logical name string, or
NULLif not assigned.
helpAddress of help routine, or
NULLif none is required.
attach_helpAddress of attach help routine, or
NULLif none is required.
help_ctxtAddress of device-specific context which might be useful while displaying help for the current device, or
NULLif none is required.
descriptionAddress of device description function, or
NULLif not implemented. The function returns a string which displays the description of the device being simulated. This is part of the output of theSHOW FEATUREScommand. It also is available (when provided) for dynamic insertion in the information produced by theDEVICEhelp routine.
1.4.1.1. awidth and aincr¶
The awidth field specifies the width of the VM’s fundamental computer “word”.
For example, on the PDP-11, awidth is 16b, even though memory is byte-addressable.
The aincr field specifies how many addressing units comprise the fundamental “word”.
For example, on the PDP-11, aincr is 2 (2 bytes per word).
If aincr is greater than 1,
SCP assumes that data is naturally aligned on addresses that are multiples of aincr.
VMs that support arbitrary byte alignment of data (like the VAX) can follow one of two strategies:
Set
awidth = 8andaincr = 1and support only byte access in the examine/deposit routines.Set
awidthandaincrto the fundamental sizes and support unaligned data access in the examine/deposit routines.
In a byte-addressable VM, SAVE and RESTORE will require \((\mathtt{memory\_size\_bytes} / \mathtt{aincr})\) iterations to save or restore memory.
Thus, it is significantly more efficient to use word-wide rather than byte-wide memory;
but requirements for unaligned access can add significantly to the complexity of the examine and deposit routines.
1.4.1.2. Device flags¶
The flags field contains indicators of current device status.
SIMH defines several flags:
Flag name |
Meaning if set |
|---|---|
|
Device can be set enabled or disabled |
|
Device is currently disabled |
|
Device requires call on msize routine to change memory size |
|
Device supports |
|
Device capacity is in units of 512 byte sectors |
|
Do not automatically detach already attached units |
|
Use traditional (unstructured) help |
|
Don’t save device state |
The flags field also contains an optional device type specification.
One of these may be specified when initializing the flags field:
|
Device uses |
|
Device uses |
|
Device uses |
|
Device uses |
|
Device uses |
Starting at bit position DEV_V_UF up to but not including DEV_V_RSV,
the remaining flags are device-specific.
Device flags are automatically saved and restored;
the device need not supply a register for these bits.
1.4.1.3. Context¶
The ctxt 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.
1.4.1.4. Examine and deposit routines¶
For devices which maintain their data sets as host files, SCP implements the 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:
t_stat examine_routine (t_val *eval_array, t_addr addr, UNIT *uptr, int32 switches)Copy
sim_emaxconsecutive addresses for unituptr, starting ataddr, intoeval_array. The switch variable hasbit<n>set if thenth letter was specified as a switch to the examine command.
t_stat deposit_routine (t_val value, t_addr addr, UNIT *uptr, int32 switches)Store the specified
valuein the specifiedaddrfor unituptr. The switch variable is the same as for the examine routine.
1.4.1.5. Reset routine¶
The reset routine implements the device reset function for the RESET, RUN, and BOOT commands.
Its calling sequence is:
t_stat reset_routine (DEVICE *dptr) /* Reset the specified device to its initial state */
A typical reset routine clears all device flags and cancels any outstanding timing operations.
Switch -p (available via global variable sim_switches) specifies a reset to power-up state.
The reset routine is a reasonable place to perform one time initialization activities specific to the device keeping a static variable indicating that the one time initialization has been performed.
1.4.1.6. Boot routine¶
If a device responds to a BOOT command,
the boot routine implements the bootstrapping function.
Its calling sequence is:
t_stat boot_routine (int32 unit_num, DEVICE *dptr)Bootstrap unit
unit_numon the devicedptr.
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.
1.4.1.7. Attach and detach routines¶
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:
t_stat attach_routine (UNIT *uptr, const char *file)Attach the specified
fileto the unituptr.sim_switchescontains the command switch; bitSIM_SW_RESTindicates that attach is being called by theRESTOREcommand rather than theATTACHcommand.
t_stat detach_routine (UNIT *uptr)Detach unit
uptr.
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, const 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 VM specifies an ATTACH or DETACH routine,
SCP bypasses its normal tests before calling the VM routine.
Thus, a VM DETACH routine cannot be assured that the unit is actually attached and must test the unit flags if required.
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_ATT flag is set.
During simulator exit, the detach routine is also called if the unit is not flagged as attachable (UNIT_ATTABLE is not set).
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.
1.4.1.8. Memory size change routine¶
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 if 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:
t_stat change_mem_size (UNIT *uptr, int32 val, const char *cptr, void *desc)Change the capacity (memory size) of unit
uptrtoval. Thecptranddescarguments are included for compatibility with theSETcommand’s validation routine calling sequence.
1.4.1.9. Debug controls¶
Devices can support debug printouts.
Debug printouts are controlled by the SET {NO}DEBUG command,
which specifies where debug output should be printed;
and by the SET <device> {NO}DEBUG command,
which enables or disables individual debug printouts.
If a device supports debug printouts, device flag DEV_DEBUG must be set.
Field dctrl is used for the debug control flags.
If a device supports only a single debug on/off flag,
then the debflags field should be set to NULL.
If a device supports multiple debug on/off flags,
then the correspondence between bit positions in dctrl and debug flag names is specified by table debflags.
debflags points to a contiguous array of DEBTAB structures (typedef DEBTAB).
Each DEBTAB structure specifies a single debug flag:
struct DEBTAB {
const char name; /* Flag name */
uint32 mask; /* Control bit */
const char *desc; /* Description */
};
The fields are the following:
nameName of the debug flag.
maskBit mask of the debug flag.
descDescription of the debug flag.
The array is terminated with a NULL entry.
The use and definition of debug mask values is up to the particular simulator device. Some simulator support libraries define their own debug mask values that can be used to display various details about the internal activities of the respective library. Libraries defined debug masks a defined starting at the high bits in 32-bit the mask word, so device specific masks should start their mask definitions with the low bits to avoid unexpected debug output if the definitions collide.
Simulator code can produce debug output by calling sim_debug which is declared
(in header file scp.h):
void sim_debug (uint32 dbits, DEVICE* dptr, const char* fmt, /* … */);
The dbits is a flag which matches a mask in a sim_debtab structure,
and the the dptr is the DEVICE which has the corresponding dctrl field.
Additionally support exists for displaying bit and bitfield values.
Bit field values are defined using the BITFIELD structure and the BIT macros to declare the bits and bitfields.
|
Single bit definition |
|
Don’t care bit definition |
|
Bitfield definition |
|
Don’t care bitfield definition |
|
Bitfield definition with output format |
|
Bitfield definition with |
|
For example:
static const char *rp_fname[CS1_N_FNC] = {
"NOP", "UNLD", "SEEK", "RECAL", "DCLR", "RLS",
"OFFS", "RETN","PRESET", "PACK", "SEARCH",
"WRCHK", "WRITE", "WRHDR", "READ", "RDHDR"
};
BITFIELD xx_csr_bits[] = {
BIT(GO), /* Go */
BITFNAM(FUNC,5,rp_fname), /* Function code */
BIT(IE), /* Interrupt enable */
BIT(RDY), /* Drive ready */
BIT(DVA), /* Drive available */
BITNCF(1), /* 12 reserved */
BIT(TRE), /* Transfer error */
BIT(SC), /* Special condition */
ENDBITS
};
The fields in a register can be displayed (along with transition indicators) by calling sim_debug_bits_hdr or sim_debug_bits.
void sim_debug_bits_hdr (uint32 dbits, DEVICE* dptr,
const char *header, BITFIELD* bitdefs,
uint32 before, uint32 after, int terminate);
void sim_debug_bits (uint32 dbits, DEVICE* dptr, BITFIELD* bitdefs,
uint32 before, uint32 after, int terminate);
1.4.1.10. Device-specific help support¶
A device declaration may specify a device type or class in the flags field by providing one of
DEV_DISK,
DEV_TAPE,
DEV_MUX,
DEV_ETHER
or DEV_DISPLAY values when initializing the flags.
The device type allows the SCP HELP command routine to provide some default help information for devices which don’t otherwise specify a device specific help routine or an attach_help routine.
1.4.1.11. Help routine¶
A device declaration may provide a routine which will display help about that device when a user enters a HELP dev command.
t_stat help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)Write help information about the device and/or unit usage. The
flagandcptrarguments are included for compatibility with theHELPcommand’s validation routine calling sequence.
1.4.1.12. Attach help routine¶
A device declaration may provide a routine which will display help about the attach command for this device.
t_stat attach_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)Write help information about the device and/or unit attach usage. The
flagandcptrarguments are included for compatibility with theHELPcommand’s validation routine calling sequence.
1.4.2. UNIT structure¶
Units are allocated as contiguous arrays.
Each unit is defined with a UNIT structure (typedef UNIT):
struct UNIT {
struct UNIT *next; /* Next active */
t_stat (*action)(); /* Action routine */
char *filename; /* Open filename */
FILE *fileref; /* File reference */
void *filebuf; /* Memory buffer */
uint32 hwmark; /* High water mark */
int32 time; /* Time out */
uint32 flags; /* Flags */
uint32 dynflags; /* Dynamic flags */
t_addr capac; /* Capacity */
t_addr pos; /* File position */
void (*io_flush)(); /* I/O flush routine */
uint32 iostarttime; /* I/O start time */
int32 buf; /* Buffer */
int32 wait; /* Wait */
int32 u3; /* Device-specific */
int32 u4; /* Device-specific */
int32 u5; /* Device-specific */
int32 u6; /* Device-specific */
void *up7; /* Device-specific */
void *up8; /* Device-specific */
};
The simulator accessible fields are the following:
nextPointer to next unit in active queue,
NULLif none.
actionAddress of unit time-out service routine.
filenamePointer to name of attached file,
NULLif none.
filerefPointer to FILE structure of attached file,
NULLif none.
hwmarkBuffered devices only; highest modified address\(\ + 1\).
timeIncrement until time-out beyond previous unit in active queue.
flagsUnit flags.
dynflagsDynamic flags.
capacUnit capacity, 0 if variable.
posSequential devices only; next device address to be read or written.
io_flushI/O flush routine,
NULLif none.
iostarttimeSimulation time (from
sim_grtime()) for use withsim_activate_notbefore().
bufBy convention, the unit buffer, but can be used for other purposes.
waitBy convention, the unit wait time, but can be used for other purposes.
u3User-defined.
u4User-defined.
u5User-defined.
u6User-defined.
up7User-defined void pointer (useful for a unit-specific context).
up8User-defined void pointer (useful for a unit-specific context).
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.
The values of up7,
up8 and any device specific internal pointer variables must be established in the device reset or attach routines to be properly behaved across SAVE/RESTORE activities
Macro UDATA is available to fill in the common fields of a UNIT.
It is invoked by
UDATA (action_routine, flags, capacity)
Fields after buf can be filled in manually, e.g,
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.
1.4.2.1. Unit flags¶
The flags field contains indicators of current unit status.
SIMH defines 13 flags:
Flag name |
Meaning if set |
|---|---|
|
The unit responds to |
|
The unit is currently read-only. |
|
The unit is fixed capacity. |
|
The unit is sequential. |
|
The unit is currently attached to a file. |
|
The unit measures “K” as 1024, rather than 1000. |
|
The unit buffers its data set in memory. |
|
The unit allocates its data buffer dynamically. |
|
The unit is currently buffering its data set in memory. |
|
The unit can be |
|
The unit responds to |
|
The unit is currently disabled. |
|
The unit is idle eligible. |
A unit is “active” when it is in the SIMH event queue.
Units are made active by a call to sim_activate or a similar routine.
A request to idle SIMH is not performed unless the unit at the head of the event queue
(the unit with the shortest remaining time)
has the UNIT_IDLE flag set.
In VMs that want to support idling,
devices that poll for data
(such as console and mux terminals)
and clocks should use a multiple of the clock period as their poll interval,
they should use clock co-scheduling to properly align their servicing with clock ticks,
and all these units should be marked with UNIT_IDLE.
Other devices
(like disk)
usually have shorter service times,
and would not typically be marked with UNIT_IDLE.
Units for sequential devices (UNIT_SEQ) must update the unit structure pos member to reflect the position in the attached sequential device file as data is read or written to that file.
The pos value is used to position the attached file whenever simulation execution starts or resumes from the sim> prompt.
Starting at bit position UNIT_V_UF up to but not including UNIT_V_RSV,
the remaining 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.
1.4.2.2. Service routine¶
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.
If the user has typed the interrupt character (^E),
it returns SCPE_STOP.
1.4.3. REG structure¶
Registers are allocated as contiguous array, with a NULL register at the end.
Each register is defined with a REG structure (typedef REG):
struct REG {
const char *name; /* Name */
void *loc; /* Location */
uint32 radix; /* Radix */
uint32 width; /* Width */
uint32 offset; /* Starting bit */
uint32 depth; /* Save depth */
const char *desc; /* Description */
struct bitfield *fields; /* Bit fields */
uint32 flags; /* Flags */
uint32 qptr; /* Current queue pointer */
size_t str_size; /* Structure size */
};
The fields are the following:
nameRegister name, string of all capital alphanumeric characters.
locPointer to location of the register value.
radixRadix for input and display of data, 2 to 16 inclusive.
widthWidth in bits of data, 1 to 64 inclusive.
offsetBit offset (from right-end of data).
depthSize of data array (normally 1).
descRegister description.
fieldsBit fields and formatting information.
qptrFor a circular queue, the entry number for the first entry
str_sizeStructure size.
flagsFlags and formatting information.
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.
The desc field (if present) is displayed by the HELP dev REGISTER command to enumerate the device registers and describe them.
The fields field (if present) is used to display details of a register’s content according to the respective field descriptions.
A register that is 32b or less keeps its data in a 32b scalar variable (signed or unsigned). A register that is 33b or more keeps its data in a 64b scalar variable (signed or unsigned). There are several exceptions to this rule:
An arrayed register keeps its data in a C-array whose SIMH data type is as large as (or if necessary, larger than), the width of a register element. For example, an array of 6b registers would keep its data in a
uint8(orint8) array; an array of 16b registers would keep its data in auint16(orint16) array; an array of 24b registers would keep its data in auint32(orint32) array.A register flagged with
REG_FITobeys the sizing rules of an arrayed register, rather than a normal scalar register. This is useful for aliasing registers into memory or into structures.
Macros ORDATA, DRDATA, HRDATA and BINDATA define right-justified octal, decimal, hexadecimal, and binary registers, respectively.
They are invoked by:
xRDATA (name, location, width)
Macro FLDATA defines a one-bit binary flag at an arbitrary offset in a 32-bit word.
It is invoked by:
FLDATA (name, location, bit_position)
Macro GRDATA defines a register with arbitrary location and radix.
It is invoked by:
GRDATA (name, location, radix, width, bit_position)
Macro BRDATA defines an arrayed register whose data is kept in a standard C array.
It is invoked by:
BRDATA (name, location, radix, width, depth)
Macro VBRDATA defines an arrayed register whose scalar data elements are accessed as if they were in a standard C array,
but the location is an arbitrary pointer to memory.
It is invoked by:
VBRDATA (name, location, radix, width, depth)
For all of these macros, the flag field can be filled in manually, e.g.,
REG lpt_reg = {
{ DRDATA (POS, lpt_unit.pos, 31), PV_LEFT },
/* … */
};
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 setup wrong,
or the data is actually kept somewhere else,
storing through this register declaration can trample over memory.
The macro is invoked by:
URDATA (name, location, radix, width, offset, depth, flags)
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 field.
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:
{ URDATA (POS, dev_unit[0].pos, 8, T_ADDR_W, 0, 4, 0) }
Finally, macro STRDATA defines an arrayed register whose data is part of an arbitrary structure array.
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:
STRDATA (name, location, radix, width, offset, depth, size, flags)
The location should be the address in the structure for the first element (0) of the structure array.
The width should be 32 for an int32 or uint32 field,
and T_ADDR_W for a t_addr field.
The flags can be any of the normal register flags;
REG_STRUCT 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:
{ STRDATA (POS, dev_unit[0].pos, 8, T_ADDR_W, 0, 4, sizeof(dev_unit[0]), 0) }
Each of the ORDATA,
DRDATA,
FLDATA,
GRDATA,
BRDATA,
VBRDATA,
URDATA and STRDATA macros have corresponding D and DF macros
(ORDATAD,
DRDATAD,
FLDATAD,
GRDATAD,
BRDATAD,
VBRDATAD,
URDATAD,
STRDATAD,
ORDATADF,
DRDATADF,
FLDATADF,
GRDATADF,
BRDATADF,
VBRDATADF,
URDATADF and STRDATADF)
which can be used to provide initialization values to the desc fields in the REG structure.
Macro SAVEDATA defines an object which will be stored and restored from a saved simulator image without any consideration for the format it contains.
SAVEDATA REGisters cannot be examined or deposited to.
This macro must be used with great care.
The data being saved and restored may not be meaningfully correct if the save environment has a different host architecture than the restoring one.
The macro is invoked by:
SAVEDATA (name, location)
The location can be anywhere,
but should name an object (scalar, array, structure, etc)
that will be saved and restored in its entirety.
For example, the following declares an arrayed register of all the UNIT position fields in a device with 4 units:
{ BLOBDATA (SETUP, xs_dev.setup) }
A generic register population macro exists called REGDATA.
Simulators using this macro will be protected against future changes to the REG structure.
If new fields are added to this structure a new initialization macro will be provided,
but all uses of the prior macro will continue to work correctly.
All REG variables should be initialized with one of the register initialization macros.
Using these macros protects these declarations from any changes that may occur to the REG structure in the future,
since if any changes are made to the REG structure,
the macros will be changed to reflect the necessary changes.
REGDATA (name, location, radix, width, offset, depth, desc, fields, flags, qptr, size)
1.4.3.1. Register flags¶
The flags field contains indicators that control register examination and deposit.
Flag name |
Meaning if specified |
|---|---|
|
Print register right-justified with leading zeroes. |
|
Print register right-justified with leading spaces. |
|
Print register right-justified space fill commas every 3. |
|
Print register left-justified. |
|
Register is read-only. |
|
Register is hidden (will not appear in |
|
Register is read-only and hidden. |
|
New register values must be non-zero. |
|
Register resides in the |
|
Register resides in an arbitrary structure. |
|
Register is a circular queue. |
|
Register is displayed and parsed using VM data routines. |
|
Register is displayed and parsed using VM address routines. |
|
Register container uses arrayed rather than scalar size rules. |
|
Register updates invoke VM register update routine. |
The PV flags are mutually exclusive.
PV_RZRO is the default if no formatting flag is specified.
Starting at bit position REG_V_UF,
the remaining flags are user-defined.
These flags are passed to the VM-defined fprint_sym and parse_sym routines in the upper bits of the addr parameter;
they are merged with the lower 16 bits containing the register radix value.
If a user-defined flag or the REG_VMIO flag is specified in a register’s flag field,
the EXAMINE and DEPOSIT commands will call fprint_sym and parse_sym instead of the standard print and parse routines.
The user-defined flags passed in the addr parameter may be used to identify the register or determine how it is to be handled.
If the REG_DEPOSIT flag is specified in a register’s flags field,
register deposits will call VM-defined reg_update after the register contents have been changed.
The VM-defined reg_update routine may reference the user-defined flags specified in the register definition to identify the register or determine any consequences related to updating that register.
If REG_UNIT is clear,
the register data is located at the address specified by the loc pointer.
If REG_UNIT is set,
the register name is used to refer to a field in a UNIT structure,
and loc points to that field in the UNIT struct for unit 0.
The examine and deposit commands will adjust that address by the unit number times the size of the UNIT struct to determine the actual data address.
1.4.4. BITFIELD structure¶
Bitfields are allocated as contiguous array, with a NULL bitfield at the end.
Each bitfield is defined with a BITFIELD structure (typedef BITFIELD):
struct BITFIELD {
const char *name; /* Field name */
uint32 offset; /* Starting bit */
uint32 width; /* Width */
const char **valuenames; /* Map of values to strings */
const char *format; /* Value format string */
};
The fields are the following:
nameField name, string of alphanumeric characters.
offsetStarting bit (normally populated automatically).
widthWidth in bits of data, 1 to 32 inclusive.
valuenamesPointer to a string array which maps fields to values.
formatValue format string.
Macros BIT and BITF define single-bit and multi-bit fields, respectively.
They are invoked by:
BIT (name)
BITF (name, width)
Macros BITNC and BITNCF define single-bit and multi-bit “don’t care” fields, respectively.
They are invoked by:
BITNC
BITFNCF (width)
Macro BITFFMT defines a bitfield with an output format specifier.
It is invoked by:
BITFFMT (name, width, fmt)
Macro BITFNAM defines a bitfield with a value to name string map.
It is invoked by:
BITFFMT (name, width, maparray)
Macro STARTBIT resets fields to the beginning of the register.
This is useful when other conditions redefine the structure of a register’s contents.
It is invoked by:
STARTBIT
1.4.5. MTAB structure¶
Device-specific SHOW and SET commands are processed using the modifications array,
which is allocated as a contiguous array,
with a NULL at the end.
Each possible modification is defined with an MTAB structure (typedef MTAB),
which has the following fields:
struct MTAB {
uint32 mask; /* Mask */
uint32 match; /* Match */
const char *pstring; /* Print string */
const char *mstring; /* Match string */
t_stat (*valid)(); /* Validation routine */
t_stat (*disp)(); /* Display routine */
void *desc; /* Location descriptor */
const char *help; /* Help string */
};
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:
maskBit mask for testing the
unit.flagsfield.
matchValue to be stored (
SET) or compared (SHOW).
pstringPointer to character string printed on a match (
SHOW), orNULL.
mstringPointer to character string to be matched (
SET), orNULL.
validAddress of validation routine (
SET), orNULL.
dispAddress of display routine (
SHOW), orNULL.
For SET, a regular MTAB entry is interpreted as follows:
Test to see if the
mstringentry exists.Test to see if the
SETparameter matches themstring.Call the validation routine, if any.
Apply the
maskvalue to theUNITflagsword and then or in thematchvalue.
For SHOW, a regular MTAB entry is interpreted as follows:
Test to see if the
mstringentry exists.Test to see if the
UNITflagsword, masked with themaskvalue, equals thematchvalue.If a display routine exists, call it, otherwise
Print the
pstring.
Extended MTAB entries have a different interpretation:
Mask |
Entry flags |
|---|---|
|
Value to be stored ( |
|
Pointer to character string printed on a match ( |
|
Pointer to character string to be matched ( |
|
Address of validation routine ( |
|
Address of display routine ( |
|
Pointer to data address (valid clear) or a validation-specific structure (valid set) |
|
Extended entry |
|
Valid for devices |
|
Valid for units |
|
Requires a value |
|
Optionally accepts a value |
|
Valid only in named |
|
Do not convert option value to uppercase |
|
|
For SET, an extended MTAB entry is interpreted as follows:
Test to see if the
mstringentry exists.Test to see if the
SETparameter matches themstring.Test to see if the entry is valid for the type of
SETbeing done (SET deviceorSET unit).If a validation routine exists, call it and return its status. The validation routine is responsible for storing the result.
If
descisNULL, exit.Otherwise, store the
matchvalue in theint32pointed to bydesc.
For SHOW, an extended MTAB entry is interpreted as follows:
Test to see if the
mstringentry exists.Test to see if the entry is valid for the type of
SHOWbeing done (device or unit).If a display routine exists, call it, otherwise,
Print the
pstring.
SHOW [dev|unit] <modifier>{=<value>} is a special case.
Only two kinds of modifiers can be displayed individually:
an extended MTAB entry that takes a value;
and any MTAB entry with both a display routine and a pstring.
Recall that if a display routine exists,
SHOW does not use the pstring entry.
For displaying a named modifier,
pstring is used as the string match.
This allows implementation of complex display routines that are only invoked by name, e.g.,
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.
1.4.5.1. Validation 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:
t_stat validation_routine (UNIT *uptr, int32 value, const char *cptr, void *desc)Test that
uptr.flagscan be set tovalue.cptrpoints to the value portion of the parameter string (any characters after the=sign); ifcptrisNULL, no value was given.descpoints to theREGorint32used to store the parameter.
1.4.5.2. Display routine¶
The display routine is called during SHOW processing to display device- or unit-specific state.
Its calling sequence is:
t_stat display_routine (FILE *st, UNIT *uptr, int32 value, const void *desc)Output device- or unit-specific state for
uptrto streamst. If the modifier is a regularMTABentry, or an extended entry withoutMTAB_SHPset,descpoints to the structure in theMTABentry. If the modifier is an extended MTAB entry withMTAB_SHPset,descpoints to the optional value string orNULLif no value was supplied.valueis the value field of the matchedMTABentry.
When the display routine is called,
SHOW hasn’t output anything.
SHOW will append a newline after the display routine returns,
except for extended entries with the MTAB_NMO flag set.
1.4.5.3. Help flags¶
The flags MTAB_VALR and MTAB_VALO are used to construct command syntax examples when displaying help for SET and SHOW commands.
These flags do not otherwise influence the actions taken during processing of SET or SHOW commands.
1.4.5.4. Example arguments in the mstring¶
The value of the mstring field may contain examples of valid additional parameters which may be specified as values.
For example:
MTAB cr_mod[] = {
{ mask, value, "normal", "NORMAL", NULL, NULL, NULL },
{ MTAB_XTD | MTAB_VDV, 0, "TRANSLATION", "TRANSLATION={DEFAULT|026|026FTN|029|EBCDIC}",
NULL, &cr_set_trans, &cr_show_trans },
{ 0 }
};
This entry has an mstring value of TRANSLATION={DEFAULT|026|026FTN|029|EBCDIC}.
When comparisons are made against this string,
everything starting at the equal sign and beyond is irrelevant to the match activity since the input being compared has already been parsed with a delimiter of =.
The remaining parts of the mstring value are ignored, but are available when constructing HELP dev SET output.
1.4.5.5. Help field¶
The MTAB entry’s help field is used when constructing HELP dev SHOW or HELP dev SHOW output.
It serves to describe the purpose or effect of the particular SET dev or SHOW dev command.
The help field is ignored when constructing HELP dev SET output for MTAB entries which have an equal sign in the mstring field.
1.4.6. Other data structures¶
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[SCPE_BASE] is an array of pointers to character strings,
corresponding to error status returns greater than zero.
If sim_instr returns status code \(n > 0\) but less than SCPE_BASE,
then sim_stop_message[n] is printed by SCP.
This array must have valid character pointers for all values \(< SCPE_BASE\) which sim_instr returns.
Declaring the array with dimension SCPE_BASE will properly allow the array to be filled in as needed with appropriate message text for any messages that are needed while also providing NULL pointers for the remaining possibilities.
1.5. VM-provided routines¶
1.5.1. Instruction execution¶
Instruction execution is performed by routine sim_instr.
Its calling sequence is:
t_stat sim_instr (void)Execute from current PC until error or halt.
1.5.2. Binary load and dump¶
If the VM responds to the LOAD (or DUMP) command,
the load (or dump) routine is implemented by routine sim_load.
Its calling sequence is:
t_stat sim_load (FILE *fptr, const char *buf, const char *fnam, t_bool flag)If
flag\(\ = 0\), load data from binary filefptr. Ifflag\(\ = 1\), dump data to binary filefptr. For either command,bufcontains any VM-specific arguments, andfnamcontains the filename.
If LOAD or DUMP is not implemented,
sim_load should simply return SCPE_ARG.
The LOAD and DUMP commands open the specified file before calling sim_load,
and close it on return.
sim_load may optionally load or dump data in different formats based on flag options specified in the sim_switches variable.
If or how this is done or what any switches mean are completely up to the simulator’s implementation in the sim_load function.
1.5.3. Symbolic examination and deposit¶
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:
t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 switch)Based on the
switchvariable, symbolically output to streamofilethe data in arrayvalat the specifiedaddrin unituptr.
t_stat parse_sym (const char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 switch)Based on the
switchvariable, parse character stringcptrfor a symbolic valuevalat the specifiedaddrin unituptr.
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 addressing units consumed as a negative number.
If the processing was successful and consumed a single addressing unit,
then these routines should return SCPE_OK.
For example, PDP-11 parse_sym would respond as follows to various inputs:
Input |
Return value |
|---|---|
|
|
|
|
|
|
|
|
There is an implicit relationship between the addr and val arguments and the device’s aincr fields.
Each entry in val is assumed to represent aincr addressing units,
starting at addr:
|
|
|
|
|
|
|
|
⋮ |
⋮ |
Because val is typically filled-in and stored by calls on the device’s examine and deposit routines,
respectively,
the examine and deposit routines and fprint_sym and fparse_sym must agree on the expected width of items in val,
and on the alignment of addr.
Further, if fparse_sym wants to modify a storage unit narrower than awidth,
it must insert the new data into the appropriate entry in val without destroying surrounding fields.
The number of words in the val array is given by the global variable sim_emax.
The interpretation of switch values is arbitrary (except in the cases noted below),
but the following are provided by existing VMs in their fprint_sym implementations:
Switch |
Interpretation |
|---|---|
|
Single character |
|
Character string |
|
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.
fprint_sym is called to print the instruction at the program counter value for the simulation stop message,
for registers containing user-defined or REG_VMIO flags in their flags fields and memory values printed by the EXAMINE command,
and for printing the values printed by the EVAL command.
These cases are differentiated by the presence of special flags in the switch parameter.
For a simulation stop, the “M” switch and the SIM_SW_STOP switch are passed.
For examining registers, the SIM_SW_REG switch is passed.
In addition, the user-defined flags and register radix are passed in the addr parameter.
Register radix is taken from the radix specified in the register definition,
or overridden by -d, -o, or -x switches in the command.
For examining memory and the EVAL command,
no special switch flags are passed.
parse_sym is called to parse memory,
register,
and the logical and relational search specifier values for the DEPOSIT command and the symbolic expression for the EVAL command.
As with fprint_sym,
these cases are differentiated by the presence of special flags in the switch parameter.
For registers, the SIM_SW_REG switch is passed.
For all other cases, no special switch flags are passed.
1.5.4. Optional interfaces¶
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.
1.5.4.1. Once only initialization routine¶
SCP previously defined a pointer (*sim_vm_init)(void).
This was a “weak global”;
the intention of this routine was that if no other module defines this value,
it will default to NULL.
A VM requiring special initialization would fill in this pointer with the address of its special initialization routine:
WEAK void sim_special_init (void);
WEAK void (*sim_vm_init)(void) = &sim_special_init;
The special initialization routine could perform any actions required by the VM. If the other optional interfaces are to be used, the initialization routine could also fill in the appropriate pointers.
Due to the lack of reliable functionality across all different supported host platforms,
this “weak global” paradigm has been removed.
These activities must now be done in the CPU reset routine
(since that is called during SCP initialization,
as well as when a RESET command is issued later on).
When these are done in the CPU reset routine,
care should be taken to only perform them once if they have any side-effects
(like clearing all memory).
1.5.4.2. Address input and display¶
SCP defines a pointer t_addr *(sim_vm_parse_addr)(DEVICE *, const char *, const char **). This is initialized to NULL.
If it is filled in by the VM,
SCP will use the specified routine to parse addresses in place of its standard numerical input routine.
The current command switches,
if needed,
may be read from the global variable sim_switches.
The calling sequence for the sim_vm_parse_addr routine is:
t_addr sim_vm_parse_addr (DEVICE *dptr, const char *cptr, const char **optr)Parse the string pointed to by
cptras an address for the device pointed to bydptr.optrpoints to the first character not successfully parsed. Ifcptr == optr, parsing failed.
SCP defines a pointer void *(sim_vm_fprint_addr)(FILE *, DEVICE *, t_addr).
This is initialized to NULL.
If it is filled in by the VM,
SCP will use the specified routine to print addresses in place of its standard numerical output routine.
The calling sequence for the sim_vm_fprint_addr routine is:
t_addr sim_vm_fprint_addr (FILE *stream, DEVICE *dptr, t_addr addr)Output address
addrtostreamin the format required by the device pointed to bydptr.
SCP defines a pointer void *(sim_vm_sprint_addr)(FILE *, DEVICE *, t_addr).
This is initialized to NULL.
If it is filled in by the VM,
SCP will use the specified routine to print addresses in place of its standard numerical output routine.
The calling sequence for the sim_vm_sprint_addr routine is:
t_addr sim_vm_sprint_addr (char *buf, DEVICE *dptr, t_addr addr)Output address
addrtobufin the format required by the device pointed to bydptr.
1.5.4.3. Command input and post-processing¶
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 sim_vm_read routine is:
char sim_vm_input (char *buf, int32 *max, FILE *stream)Read the next command line from
streamand store it inbuf, up to a maximum ofmaxcharacters.
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:
void sim_vm_postupdate (t_bool from_scp)
If called from SCP,
the argument from_scp is TRUE;
otherwise, it is FALSE.
1.5.4.4. Simulator stop message formatting¶
SCP defines a pointer,
sim_vm_fprint_stopped,
to a function taking parameters of type FILE * and t_stat and returning a value of type t_bool.
It is initialized to NULL but may be reset by the VM to point at a routine that will be called when a simulator stop occurs.
The calling sequence is:
t_bool vm_fprint_stopped (FILE *stream, t_stat reason)Write a simulator stop message to
streamfor thereasonspecified, and returnTRUEif SCP should append the program counter value orFALSEif SCP should not.
When the instruction loop is exited, SCP regains control and prints a simulator stop message. By default, the message is printed with this format:
<reason>, <program counter label>: <address> (<instruction mnemonic>)
For example:
SCPE_STOP prints "Simulation stopped, P: 24713 (LOAD 1)"
SCPE_STEP prints "Step expired, P: 24713 (LOAD 1)"
For VM stops,
this routine is called after the reason has been printed and before the comma,
program counter label,
address,
and instruction mnemonic are printed.
Depending on the reason for the stop,
the routine may insert additional information,
and it may request omission of the PC value by returning FALSE instead of TRUE.
For example, a VM may define these stops and their associated formats:
STOP_SYSHALT prints "System halt 3, P: 24713 (LOAD 1)"
STOP_HALT prints "Programmed halt, CIR: 030365 (HALT 5), P: 24713 (LOAD 1)"
STOP_CDUMP prints "Cold dump complete, CIR: 000020"
For these examples,
the VM’s vm_fprint_stopped routine prints " 3" and returns TRUE for STOP_SYSHALT,
prints ", CIR: 030365 (HALT 5)" and returns TRUE for STOP_HALT,
prints ", CIR: 000020" and returns FALSE for STOP_CDUMP,
and prints nothing and returns TRUE for all other VM stops.
1.5.4.5. VM-specific commands¶
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 before user input is looked up in the standard command table.
It may be used to override or otherwise arbitrarily extend the functionality of a normal SCP command.
A command table is allocated as a contiguous array.
Each entry is defined with a sim_ctab structure (typedef CTAB):
struct sim_ctab {
const char *name; /* Name */
t_stat (*action)(); /* Action routine */
int32 arg; /* Argument */
const 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:
t_stat action_routine (int32 arg, const char *buf)Process input string
bufbased on optional argumentarg.
The string passed to the action routine starts at the first non-blank character past the command name.
When looking for a matching command,
SCP scans the command table from first to last entry,
looking for a command name that begins with the command supplied by the user.
The first one found is considered the matching command.
If no match is found,
the SCP standard command table is scanned next,
using the same “first match” rule.
You may need to adjust command names for VM-specific commands to avoid conflicting with commonly used standard commands.
For example, if a VM defined the single VM-specific command NORMAL_START,
SCP would accept N as an abbreviation for this command.
This might confuse users who expect N to be an abbreviation of the NEXT command.
The “first match is used” rule is useful when a VM needs to redefine a standard SCP command with a different syntax.
For example, the VAX simulators do this in several different ways to redefine the BOOT command.
1.5.4.6. VM-support for stepping over subroutine calls¶
SCP can provide the ability to step over subroutine calls with the NEXT command.
In order for the NEXT command to work,
the simulator must provide a VM-specific routine which will identify whether the next instruction to be executed is a subroutine call and, if so,
to identify where to dynamically insert breakpoint(s) to stop instruction execution when the subroutine returns.
SCP defines a pointer t_bool *(sim_vm_is_subroutine_call)(t_addr **ret_addrs).
This is initialized to NULL.
If filled in by the VM,
SCP will call the specified routine to determine where to dynamically place breakpoints to support the NEXT command.
The function return value is TRUE if the next instruction is a subroutine call,
and argument ret_addrs is used to return the address of a zero-terminated array of addresses where breakpoints are to be set
(i.e., the possible return addresses for the subroutine being called).
The function return value is FALSE and ret_addrs is unused if the next instruction is not a subroutine call.
1.5.4.7. Displaying the simulator PC value in debug output¶
Some simulators expose the PC as a register,
some don’t expose it or expose a register which is not a variable that is updated during instruction execution
(i.e., only upon exit of sim_instr()).
For the -P debug option to be effective,
such a simulator should provide a routine which returns the value of the current PC and set the sim_vm_pc_value routine pointer to that routine.
SCP defines a pointer t_value *(sim_vm_pc_value)(void).
This is initialized to NULL.
If filled in by the VM,
SCP will call the specified routine to determine active PC value when generating debug output containing the execution PC
(if debug is enabled with the -P flag).
1.6. Other SCP facilities¶
1.6.1. Terminal input/output formatting library¶
SIMH provides routines to convert ASCII input characters to the format expected VM, and to convert VM-supplied ASCII characters to C-standard format. The routines are
int32 sim_tt_inpcvt (int32 c, uint32 mode)Convert input character
caccording to themodespecification and return the converted result (-1if the character is not valid in the specified mode).
int32 sim_tt_outcvt (int32 c, uint32 mode)Convert output character
caccording to themodespecification and return the converted result (-1if the character is not valid in the specified mode).
The supported modes are:
|
8b mode; no conversion |
|
7b mode; the high-order bit is masked off |
|
7b printable mode; the high-order bit is masked off. In addition, on output, if the character is not printable, -1 is returned. |
|
7b upper case mode; the high-order bit is masked off. In addition, lowercase is converted to uppercase If the character is not printable, -1 is returned. |
On input, TTUF_MODE_UC has an additional modifier, TTUF_MODE_KSR,
which forces the high-order bit to be set rather than cleared.
The set of printable control characters is contained in the global bit-vector variable sim_tt_pchar.
Each bit represents the character corresponding to the bit number
(e.g., bit 0 represents NUL, bit 1 represents SOH, etc).
If a bit is set, the corresponding control character is considered printable.
It initially contains the following characters: BEL, BS, HT, LF, and CR.
The set may be manipulated with these routines:
t_stat sim_set_pchar (int32 flag, const char *cptr)Set
sim_tt_pcharto the value pointed to bycptr; returnSCPE_2FARGifcptris null or points to a null string, orSCPE_ARGif the value cannot be converted or does not contain at leastCRandLF. The string argument must be in the default radix of the current simulator.
t_stat sim_show_pchar (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)Output the
sim_tt_pcharvalue to the streamst. Thesim_tt_pcharvalue will be displayed in the default radix of the current simulator and character mnemonics for each set bit will also be displayed,
Note that the DEL character is always considered non-printable and will be suppressed in the UC and 7P modes.
A simulator which will always want a specific set of printable characters defined should explicitly call sim_set_pchar the first time the simulator’s cpu_reset routine is called.
1.6.1.1. Terminal multiplexer emulation library¶
SIMH supports the use of multiple terminals.
All terminals except the console are accessed via Telnet or serial ports on the host machine.
SIMH provides three supporting libraries for implementing multiple terminals:
sim_tmxr.c (and its header file, sim_tmxr.h),
which provide OS-independent support routines for terminal multiplexers;
sim_serial.c (and its header file sim_serial.h),
which provide OS-dependent serial I/O routines;
and sim_sock.c (and its header file, sim_sock.h),
which provide OS-dependent socket routines.
sim_sock.c and sim_serial.c are implemented under Windows, VMS, Unix, and macOS.
Two basic data structures define the multiple terminals.
Individual lines are defined by an array of tmln structures (typedef TMLN):
struct tmln {
int conn; /* Line connected flag */
SOCKET sock; /* Connection socket */
char *ipad; /* IP address */
SOCKET master; /* Line specific master socket */
char *port; /* Line specific listening port */
int32 sessions; /* Count of TCP connections received */
uint32 cnms; /* Connect time ms */
int32 tsta; /* Telnet state */
int32 rcve; /* rcv enable */
int32 xmte; /* xmt enable */
int32 dstb; /* Disable Tlnt bin */
int32 notelnet; /* Raw binary data (no Telnet interpret) */
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 */
int32 txdrp; /* xmt drop count */
int32 txbsz; /* xmt buffer size */
int32 txbfd; /* xmt buffered flag */
t_bool modem_control; /* Line modem control support */
t_bool port_speed_control; /* Line programmatically sets port speed */
int32 modembits; /* Modem bits which are set */
FILE *txlog; /* xmt log file */
FILEREF *txlogref; /* xmt log file reference */
char *txlogname; /* xmt log filename */
char rxb[TMXR_MAXBUF]; /* rcv buffer */
char rbr[TMXR_MAXBUF]; /* rcv break */
char *txb; /* xmt buffer */
TMXR *mp; /* Back pointer to mux */
char *serconfig; /* Line config */
SERHANDLE serport; /* Serial port handle */
t_bool ser_connect_pending; /* Serial connection notice pending */
SOCKET connecting; /* Outgoing socket while connecting */
char *destination; /* Outgoing destination address:port */
UNIT *uptr; /* Input polling unit -default to mp->uptr */
UNIT *o_uptr; /* Output polling unit -default to lp->uptr */
};
The fields are the following:
|
Connection flag (\(0 = \ \)disconnected) |
|
Connection socket |
|
IP address of remote end of connection |
|
Optional line specific listening socket |
|
Optional line specific listening port |
|
Count of tcp connections received |
|
Connect time |
|
Telnet state |
|
Receive enable flag (\(0 = \ \)disabled) |
|
Transmit flow control flag (\(0 = \ \)transmit disabled) |
|
Telnet bin mode disabled |
|
Receive buffer remove pointer |
|
Receive buffer insert pointer |
|
Receive count |
|
Transmit buffer remove pointer |
|
Transmit buffer insert pointer |
|
Transmit count |
|
Pointer to log file descriptor |
|
Pointer to log filename |
|
Receive buffer |
|
Receive buffer break flags |
|
Transmit buffer |
The overall set of extra terminals is defined by the tmxr structure (typedef TMXR):
struct tmxr {
int32 lines; /* Number of lines */
char *port; /* Listening port */
SOCKET master; /* Master socket */
TMLN *ldsc; /* Pointer to line descriptors */
int32 *lnorder; /* Line connection order */
DEVICE *dptr; /* Multiplexer device */
UNIT *uptr; /* Polling unit (connection) */
char logfiletmpl[FILENAMEMAX]; /* Template logfile name */
int23 buffered; /* Buffered line behavior and buffer size*/
int32 sessions; /* Count of TCP connections received */
uint32 last_poll_time; /* Time of last connection poll */
t_bool notelnet; /* Default Telnet capability for incoming connections */
t_bool modem_control; /* Multiplexer supports modem control behaviors */
t_bool port_speed_control; /* Multiplexer programmatically sets port speed */
};
The fields are the following:
|
Number of lines (constant) |
|
Master listening port (specified by |
|
Master listening socket (filled in by |
|
Array of line descriptors |
|
Array of line numbers in order of connection sequence,
or |
|
Pointer to the multiplexer’s |
|
The |
|
Template logfile name used to create names for per-line log files. |
|
Buffered line behaviors enabled flag and the size of the line buffer. |
|
Count of TCP connections received on the master socket. |
|
Time of last connection poll. |
|
Default Telnet capability for TCP connections. |
|
Flag indicating that multiplexer supports full modem control behaviors. |
The number of elements in the ldsc and lnorder arrays must equal the value of the lines field.
Set lnorder to NULL if the connection order feature is not needed.
If the first element of the lnorder array is -1,
then the default ascending sequential connection order is used.
Set dptr to NULL if the device should be derived from the unit passed to the tmxr_attach call.
Library sim_tmxr.c provides the following routines to support Telnet and serial port-based terminals:
int32 tmxr_poll_conn (TMXR *mp)Poll for a new connection to the terminals described by
mp. If there is a new connection, the routine resets all the line descriptor state (including receive enable) and returns the line number (index to line descriptor) for the new connection. If there isn’t a new connection, the routine returns -1.
void tmxr_reset_ln (TMLN *lp)Reset the line described by
lp. The connection is closed and all line descriptor state is reset.
int32 tmxr_getc_ln (TMLN *lp)Return the next available character from the line described by
lp. If a character is available, the return value is:(1 << TMXR_V_VALID) | character
If a
BREAKoccurred on the line,SCPE_BREAKwill be OR’ed into the return variable. If no character is available, the return value is 0.
void tmxr_poll_rx (TMXR *mp)Poll for input available on the terminals described by
mp.
int32 tmxr_rqln (TMLN *lp)Return the number of characters in the receive queue of the line described by
lpwhich are ready to be read now.
t_stat tmxr_putc_ln (TMLN *lp, int32 chr)Output character
chrto the line described bylp. Possible errors areSCPE_LOST(connection lost) andSCPE_STALL(connection backlogged). If executed directly in instruction simulation code (as opposed to during event processing) and line output rate limiting is in effect, then inter-character delays will occur before this routine returns.
void tmxr_poll_tx (TMXR *mp)Poll for output complete on the terminals described by
mp.
int32 tmxr_tqln (TMLN *lp)Return the number of characters in the transmit queue of the line described by
lp.
int32 tmxr_txdone_ln (TMLN *lp)Return the transmit complete indicator for the the line described by
lp:0Not done
1Just now done
-1Previously done
When the
1return value is returned would be a good time to pass an interrupt or other status information into the system being simulated.
void tmxr_send_buffered_data (TMLN *lp)Flush any buffered data for the line described by
lp.
t_stat tmxr_attach (TMXR *mp, UNIT *uptr, const char *cptr)Attach the port contained in character string
cptrto the terminals described bympand unituptr.
t_stat tmxr_open_master (TMXR *mp, const char *cptr)Associate the port contained in character string
cptrto the terminals described bymp. This routine is a subset oftmxr_attach.
t_stat tmxr_detach (TMXR *mp, UNIT *uptr)Detach all connections for the terminals described by
mpand unituptr.
t_stat tmxr_close_master (TMXR *mp)Close the master port for the terminals described by
mp. This routine is a subset oftmxr_detach.
t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)Stub examine routine, needed because the extra terminals are marked as attached; always returns an error.
t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)Stub deposit routine, needed because the extra terminals are marked as detached; always returns an error.
void tmxr_msg (SOCKET sock, const char *msg)Output character string
msgto socketsock.
void tmxr_linemsg (TMLN *lp, const char *msg)Output character string
msgto linelp.
void tmxr_linemsgf (TMLN *lp, const char *fmt, ...)Output formatted
msgto linelp.
void tmxr_fconns (FILE *st, TMLN *lp, int32 ln)Output connection status to stream
stfor the line described bylp. Iflnis \(>= 0\), preface the output with the specified line number.
void tmxr_fstats (FILE *st, TMLN *lp, int32 ln)Output connection statistics to stream
stfor the line described bylp. Iflnis \(>= 0\), preface the output with the specified line number.
tstat tmxr_set_log (UNIT *uptr, int32 val, char *cptr, void *mp)Enable logging of a line of the multipleser described by
mpto the filename pointed to bycptr. IfuptrisNULL, thenvalindicates the line number; otherwise, the unit number within the associated device implies the line number. This function may be used as anMTABvalidation routine.
tstat tmxr_set_nolog (UNIT *uptr, int32 val, const char *cptr, const void *mp)Disable logging of a line of the multipleser described by
mpto the filename pointed to bycptr. IfuptrisNULL, thenvalindicates the line number; otherwise, the unit number within the associated device implies the line number. This function may be used as anMTABvalidation routine.
tstat tmxr_show_log (FILE *st, UNIT *uptr, int32 val, const void *mp)Output the logging status of a line of the multiplexer described by
mpto streamst. IfuptrisNULL, thenvalindicates the line number; otherwise, the unit number within the associated device implies the line number. This function may be used as anMTABdisplay routine.
t_stat tmxr_dscln (UNIT *uptr, int32 val, const char *cptr, const void *mp)Parse the string pointed to by
cptrfor a decimal line number. If the line number is valid, disconnect the specified line in the terminal multiplexer described bymp. The calling sequence allowstmxr_dsclnto be used as anMTABprocessing routine. A line connected via a TCP session will be disconnected, a line connected to a serial port will be closed if thesim_switches-Cflag is enabled when the routine is called, otherwise a serial port will have DTR dropped for 500ms and raised again.
t_stat tmxr_set_lnorder (UNIT *uptr, int32 val, const char *cptr, const void *desc)Set the line connection order array associated with the
TMXRstructure pointed to bydesc. The string pointed to bycptris parsed for a semicolon-delimited list of ranges. Ranges are of the form:line1-line2Ascending sequence from
line1toline2line1/lengthAscending sequence from
line1toline1+length-1ALLAscending sequence of all lines defined by the multiplexer
The line order array must provide an
int32element for each line. The calling sequence allowstmxr_set_lnorderto be used as anMTABprocessing routine.
t_stat tmxr_show_lnorder (FILE *st, UNIT *uptr, int32 val, void *desc)Output the line connection order associated multiplexer
(TMXR *) descto streamst. The order is rendered as a semicolon-delimited list of ranges. The calling sequence allowstmxr_show_lnorderto be used as anMTABprocessing routine.
t_stat tmxr_show_summ (FILE *st, UNIT *uptr, int32 val, const void *desc)Output the summary status of the multiplexer
(TMXR *) descto streamst.
t_stat tmxr_show_cstat (FILE *st, UNIT *uptr, int32 val, const void *desc)Output either the connections (
val\(\ = 1\)) or the statistics (val\(\ = 0\)) of the multiplexer(TMXR *) descto streamst. Also check for multiplexer not attached, or all lines disconnected.
t_stat tmxr_show_lines (FILE *st, UNIT *uptr, int32 val, const void *desc)Output the number of lines in the terminal multiplexer
(TMXR *) Ito streamI.
t_stat tmxr_set_modem_control_passthru (TMXR *mp)Enable modem control passthru behaviors, and disable internal manipulation of DTR (&RTS) by
tmxrAPIs. Enable thetmxr_set_get_modem_bitsandtmxr_set_config_lineAPIs.
t_stat tmxr_clear_modem_control_passthru (TMXR *mp)Disable modem control passthru behaviors, and enable internal manipulation of DTR (&RTS) by
tmxrAPIs. Disable thetmxr_set_get_modem_bitsandtmxr_set_config_lineAPIs.
t_stat tmxr_set_port_speed_control (TMXR *mp)Declares that the device which interfaces the specified
TMXRuses thetmxr_set_config_lineAPI to set the port line speed. This declaration should be made in the device reset routine and called before any line attachments are made.
t_stat tmxr_clear_port_speed_control (TMXR *mp)Declares that the device which interfaces the specified
TMXRdoes not use thetmxr_set_config_lineAPI to set the port line speed. This declaration should only be necessary iftmxr_set_port_speed_controlhad been called previously. It will fail if any line attachments are active.
t_stat tmxr_set_line_port_speed_control (TMXR *mp, int line)Declares that the device which interfaces the specified
TMXRuses thetmxr_set_config_lineAPI to set the port line speed for the specified line. This declaration should be made in the device reset routine and called before any line attachments are made.
t_stat tmxr_clear_line_port_speed_control (TMXR *mp, int line)Declares that the device which interfaces the specified
TMXRdoes not use thetmxr_set_config_lineAPI to set the port line speed for the specified line. This declaration should only be necessary iftmxr_set_port_speed_controlhad been called previously. It will fail if any line attachments are active.
t_stat tmxr_set_get_modem_bits (TMLN *lp, int32 bits_to_set, int32 bits_to_clear, int32 *incoming_bits)For a line connected to a serial port on a
TMXRdevice withmodem_control_passthruenabled, then thebits_to_setand/orbits_to_clear(DTR and RTS) are changed and ifincoming_bitsis notNULL, then the current modem bits are returned (DCD, RNG, CTS, DSR).
t_stat tmxr_set_config_line (TMLN *lp, const char *config)Sets the line configuration (speed, parity, character size, stopbits) on a serial port. Config is a string of the form:
9600-8N1.
t_stat tmxr_set_line_unit (TMXR *mp, int line, UNIT *uptr)Declare which unit polls for input on a given line. Only needed if the input polling unit is different than the unit provided when the multiplexer was attached.
t_stat tmxr_set_line_output_unit (TMXR *mp, int line, UNIT *uptr)Declare which unit polls for output on a given line. Only needed if the output polling unit is different than the unit provided when the multiplexer was attached.
The OS dependent serial I/O and socket routines should not be accessed by the terminal simulators.
The routines provided by sim_sock.c and sim_serial.c are for internal use by the TMXR library only and should be not be used directly by any simulator.
1.6.2. Magnetic tape emulation library¶
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_WLKUnit 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.
These are declared in header file sim_tape.h.
t_stat sim_tape_attach (UNIT *uptr, const char *cptr)Attach tape unit
uptrto filecptr. Tape simulators must call this routine, rather than the standardattach_unitroutine. This allows for future expansion of format support and proper functionality with all other tape APIs.
t_stat sim_tape_detach (UNIT *uptr)Detach tape unit
uptrfrom its current file.
t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, const char *cptr, void *desc)Set the tape format for unit
uptrto the format specified by stringcptr.
t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, const void *desc)Write the tape format for unit
uptrto the file specified by descriptorst.
t_stat sim_tape_set_capac (UNIT *uptr, int32 val, const char *cptr, void *desc)Set the tape capacity for unit
uptrto the capacity, in MB, specified by stringcptr.
t_stat sim_tape_show_capac (FILE *st, UNIT *uptr, int32 val, const void *desc)Write the capacity for unit
uptrto the file specified by descriptorst.
t_stat sim_tape_set_dens (UNIT *uptr, int32 val, const char *cptr, void *desc)Set the tape density for unit
uptrto the density, in bits-per-inch, specified by stringcptr. Only specific densities are supported;descmust point at anint32value consisting of one or moreMT_*_VALIDconstants logically OR’ed together that specifies the densities allowed. Alternately,descmay be set toNULLandvalmay specify one of theMT_DENS_*constants to set the density directly; in this case,cptris ignored.
t_stat sim_tape_show_dens (FILE *st, UNIT *uptr, int32 val, const void *desc)Write the density for unit
uptrto the file specified by descriptorst.
t_stat sim_tape_rdrecf (UNIT *uptr, uint8 *buf, t_mtrlnt *tbc, t_mtrlnt max)Forward read the next record on unit
uptrinto bufferbufof sizemax. Return the actual record size intbc.
t_stat sim_tape_rdrecf_a (UNIT *uptr, uint8 *buf, t_mtrlnt *tbc, t_mtrlnt max, TAPE_PCALLBACK callback)Forward read the next record on unit
uptrinto bufferbufof sizemax. Return the actual record size intbcand callcallbackroutine on completion.
t_stat sim_tape_rdrecr (UNIT *uptr, uint8 *buf, t_mtrlnt *tbc, t_mtrlnt max)Reverse read the next record on unit
uptrinto bufferbufof sizemax. Return the actual record size intbc. Note that the record is returned in forward order, that is, byte 0 of the record is stored inbuf[0], and so on.
t_stat sim_tape_rdrecr_a (UNIT *uptr, uint8 *buf, t_mtrlnt *tbc, t_mtrlnt max, TAPE_PCALLBACK callback)Reverse read the next record on unit
uptrinto bufferbufof sizemax. Return the actual record size intbcand callcallbackroutine on completion. Note that the record is returned in forward order, that is, byte 0 of the record is stored inbuf[0], and so on.
t_stat sim_tape_wrrecf (UNIT *uptr, uint8 buf, t_mtrlnt tbc)Write buffer
uptrof sizetbcas the next record on unituptr.
t_stat sim_tape_wrrecf_a (UNIT *uptr, uint8 buf, t_mtrlnt tbc, TAPE_PCALLBACK callback)Write buffer
uptrof sizetbcas the next record on unituptrand callcallbackroutine on completion.
t_stat sim_tape_errecf (UNIT *uptr, t_mtrlnt tbc)Starting at the current tape position, write an erase gap in the forward direction on unit
uptrfor a length corresponding to a record containing the number of bytes specified bytbc. Iftbcis 0, then the tape mark at the current position is erased. If the tape is not positioned at a record of the specified length or at a tape mark, the routine returnsMTSE_INVRL.
t_stat sim_tape_errecr (UNIT *uptr, t_mtrlnt tbc)Starting at the current tape position, write an erase gap in the reverse direction on unit
uptrfor a length corresponding to a record containing the number of bytes specified bytbc. Iftbcis 0, then the tape mark preceding the current position is erased. If the tape is not positioned at the end of a record of the specified length or at a tape mark, the routine returnsMTSE_INVRL.
t_stat sim_tape_sprecf (UNIT *uptr, t_mtrlnt *tbc)Space unit
uptrforward one record. The size of the record is returned intbc.
t_stat sim_tape_sprecf_a (UNIT *uptr, t_mtrlnt *tbc, TAPE_PCALLBACK callback)Space unit
uptrforward one record. The size of the record is returned intbcand callcallbackroutine on completion.
t_stat sim_tape_sprecsf (UNIT *uptr, uint32 count, uint32 *skipped)Space unit
uptrforwardcountrecords. The number of records actually skipped is returned inskipped.
t_stat sim_tape_sprecsf_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)Space unit
uptrforwardcountrecords. The number of records actually skipped is returned inskippedand callcallbackroutine on completion.
t_stat sim_tape_spfilef (UNIT *uptr, uint32 count, uint32 *skipped)Space unit
uptrforwardcountfiles. The number of files actually skipped is returned inskipped.
t_stat sim_tape_spfilef_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)Space unit
uptrforwardcountfiles. The number of files actually skipped is returned inskippedand callcallbackroutine on completion.
t_stat sim_tape_spfilebyrecf (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped, t_bool check_leot)Space unit
uptrforwardcountfiles. The number of files actually skipped is returned inskipped. The number of records skipped is returned inrecsskipped.
t_stat sim_tape_spfilebyrecf_a (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped, t_bool check_leot, TAPE_PCALLBACK callback)Space unit
uptrforwardcountfiles. The number of files actually skipped is returned inskipped. The number of records skipped is returned inrecsskippedand callcallbackroutine on completion.
t_stat sim_tape_position (UNIT *uptr, uint32 flags, uint32 recs, uint32 *recsskipped, uint32 files, uint32 *filesskipped, uint32 *objectsskipped)Space unit
uptrforwardrecsrecords andfilesfiles. The number of records actually skipped is returned inrecsskipped. The number of files actually skipped is returned infilesskipped. The number of records skipped is returned inrecsskipped. The number of objects skipped is returned inobjectssskipped.
t_stat sim_tape_position_a (UNIT *uptr, uint32 flags, uint32 recs, uint32 *recsskipped, uint32 files, uint32 *filesskipped, uint32 *objectsskipped, TAPE_PCALLBACK callback)Space unit
uptrforwardrecsrecords andfilesfiles. The number of records actually skipped is returned inrecsskipped. The number of files actually skipped is returned infilesskipped. The number of records skipped is returned inrecsskipped. The number of objects skipped is returned inobjectssskippedand callcallbackroutine on completion.
t_stat sim_tape_sprecr (UNIT *uptr, t_mtrlnt *tbc)Space unit
uptrreverse one record. The size of the record is returned intbc.
t_stat sim_tape_sprecr_a (UNIT *uptr, t_mtrlnt *tbc, TAPE_PCALLBACK callback)Space unit
uptrreverse one record. The size of the record is returned intbcand callcallbackroutine on completion.
t_stat sim_tape_wrtmk (UNIT *uptr)Write a tape mark on unit
uptr.
t_stat sim_tape_wrtmk_a (UNIT *uptr, TAPE_PCALLBACK callback)Write a tape mark on unit
uptrand callcallbackroutine on completion.
t_stat sim_tape_wreom (UNIT *uptr)Write an end-of-medium marker on unit
uptr(this effectively erases the rest of the tape).
t_stat sim_tape_wreom_a (UNIT *uptr, TAPE_PCALLBACK callback)Write an end-of-medium marker on unit
uptr(this effectively erases the rest of the tape) and callcallbackroutine on completion.
t_stat sim_tape_wreomrw (UNIT *uptr)Write an end-of-medium marker on unit
uptrand rewind (this effectively erases the rest of the tape).
t_stat sim_tape_wreomrw_a (UNIT *uptr, TAPE_PCALLBACK callback)Write an end-of-medium marker on unit
uptrand rewind (this effectively erases the rest of the tape) and callcallbackroutine on completion.
t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen)Write an erase gap on unit
uptrofgaplentenths of an inch in length at a tape density specified by a precedingsim_tape_set_denscall.
t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, TAPE_PCALLBACK callback)Write an erase gap on unit
uptrofgaplentenths of an inch in length at a tape density specified by a precedingsim_tape_set_denscall and callcallbackroutine on completion.
t_stat sim_tape_rewind (UNIT *uptr)Rewind unit
uptr. This operation succeeds whether or not the unit is attached to a file.
t_stat sim_tape_rewind_a (UNIT *uptr, TAPE_PCALLBACK callback)Rewind unit
uptr. This operation succeeds whether or not the unit is attached to a file and callcallbackroutine on completion.
t_stat sim_tape_reset (UNIT *uptr)Reset unit
uptr. This routine should be called when a tape unit is reset.
t_bool sim_tape_bot (UNIT *uptr)Return
TRUEif unituptris at beginning-of-tape.
t_bool sim_tape_wrp (UNIT *uptr)Return
TRUEif unituptris write-protected.
t_bool sim_tape_eot (UNIT *uptr)Return
TRUEif unituptrhas exceeded the capacity specified of the specified unit (kept inuptr->capac).
The library supports reading and writing erase gaps in standard (SIMH) tape format image files.
Before writing a gap with sim_tape_wrgap,
the tape unit density must be set by calling sim_tape_set_dens;
failure to do so will result in an error.
For reading,
if the tape density has been set,
then the length is monitored when skipping over erase gaps.
If the gap length reaches 25 feet
(the maximum allowed by the ANSI/ECMA standards),
motion is terminated,
and “tape runaway” status is returned.
Runaway status is also returned if an end-of-medium marker or the physical end of file is encountered while spacing over a gap.
If the density has not been set,
then a gap of any length is skipped,
and tape runaway status is never returned;
in effect, any erase gaps present in the tape image file will be transparent to the calling simulator.
The library supports writing erase gaps over existing data records and writing records over existing gaps. If the end of a gap overlays part of a data record, the record will be truncated, but the tape image will remain valid.
An attempt to write an erase gap in an unsupported tape format results in no action and no error.
This allows a device simulator that supports writing erase gaps to call sim_tape_wrgap without concern for the tape format currently selected by the user.
sim_tape_attach,
sim_tape_detach,
sim_tape_set_fmt,
sim_tape_show_fmt,
sim_tape_set_capac,
sim_tape_show_capac,
sim_tape_set_dens,
and sim_tape_show_dens 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:
|
Operation successful |
|
Unit is not attached to a file |
|
Unit specifies an unsupported tape file format |
|
Host operating system I/O error during operation |
|
Invalid record length (exceeds maximum allowed) |
|
Record header contains error flag |
|
Tape mark encountered |
|
Beginning of tape encountered during reverse operation |
|
End-of-medium encountered |
|
Write protected unit during write operation |
|
Tape runaway occurred |
sim_tape_set_fmt,
sim_tape_show_fmt,
sim_tape_set_capac,
and sim_tape_show_capac
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 },
{ MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY",
&sim_tape_set_capac, &sim_tape_show_capac, NULL },
/* … */
};
sim_tape_set_dens and sim_tape_show_dens may be referenced in the modifier list,
or sim_tape_set_dens may be called directly to set the density,
as indicated above.
1.6.3. Disk emulation library¶
SIMH supports the use of disk drives.
Disk drives as disk files containing both data and potentially additional metadata which describes various aspects of the disk container and the disk drive it emulates.
SIMH provides a supporting library,
sim_disk.c (and its header file, sim_disk.h),
that abstracts handling of disk drives which have sectors which are a multiple of 512 bytes.
This allows support for alternate disk formats or disk access to physical devices on the host system,
without change to disk device simulators.
The disk library does not require any special data structures.
If disk drive simulators need to define private unit flags,
those flags should begin at bit number DKUF_V_UF instead of UNIT_V_UF.
The disk library maintains the current disk position in the pos field of the UNIT structure.
Library sim_disk.c provides the following routines to support emulated disk drives.
These are declared in include file sim_disk.h.
t_stat sim_disk_attach (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontautosize, uint32 debugbit, const char *drivetype, uint32 pdp11_tracksize, int completion_delay)Attach disk unit
uptrto filecptr. Disk simulators should call this routine, rather than the standardattach_unitroutine.
t_stat sim_disk_attach_ex (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac, uint32 debugbit, const char *drivetype, uint32 pdp11_tracksize, int completion_delay, const char **drivetypes)Attach disk unit
uptrto filecptr. Disk simulators should call this routine, rather than the standardattach_unitroutine. Thedrivetypesis aNULL-terminated list of drive types available to autosize.
t_stat sim_disk_attach_ex2 (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac, uint32 debugbit, const char *drivetype, uint32 pdp11_tracksize, int completion_delay, const char **drivetypes, size_t reserved_sectors)Attach disk unit
uptrto filecptr. Disk simulators should call this routine, rather than the standardattach_unitroutine. Thedrivetypesis aNULL-terminated list of drive types available to autosize.
t_stat sim_disk_detach (UNIT *uptr)Detach disk unit
uptrfrom its current file.
t_stat sim_disk_set_fmt (UNIT *uptr, int32 val, const char *cptr, void *desc)Set the disk format for unit
uptrto the format specified by stringcptr.
t_stat sim_disk_show_fmt (FILE *st, UNIT *uptr, int32 val, const void *desc)Write the disk format for unit
uptrto the file specified by descriptorst.
t_stat sim_disk_set_capac (UNIT *uptr, int32 val, const char *cptr, void *desc)Set the disk capacity for unit
uptrto the capacity, in MB, specified by stringcptr.
t_stat sim_disk_show_capac (FILE *st, UNIT *uptr, int32 val, const void *desc)Write the capacity for unit
uptrto the file specified by descriptorst.
t_stat sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt *sectstoread)Read up to
sectstoreadsectors from sector numberlbaon unituptrinto bufferbuf. Return the number of sectors read insectsread.
t_stat sim_disk_rdsect_a (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt *sectstoread, DISK_PCALLBACK callback)Read up to
sectstoreadsectors from sector numberlbaon unituptrinto bufferbufasynchronously. Return the number of sectors read insectsread, and callcallbackroutine on completion.
t_stat sim_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt *sectstowrite)Write
sectstowritesectors from bufferbufto disk sector numberlbaon unituptr. Return the number of sectors written insectswritten.
t_stat sim_disk_wrsect_a (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt *sectstowrite, DISK_PCALLBACK callback)Write
sectstowritesectors from bufferbufto disk sector numberlbaon unituptrasynchronously. Return the number of sectors written insectswritten, and callcallbackroutine on completion.
t_stat sim_disk_unload (UNIT *uptr)Unload or detach a disk as needed.
t_stat sim_disk_set_asynch (UNIT *uptr, int latency)Enable asynchronous operation for I/O to disk unit
uptr.
t_stat sim_disk_clr_asynch (UNIT *uptr, int latency)Disable asynchronous operation for I/O to disk unit
uptr.
t_stat sim_disk_reset (UNIT *uptr)Reset unit
uptr. This routine should be called when a tape unit is reset.
t_bool sim_disk_isavailable (UNIT *uptr)Check to see if disk is available for I/O, return
TRUEif so.
t_bool sim_disk_isavailable_a (UNIT *uptr, DISK_PCALLBACK callback)Check to see if disk is available for I/O asynchronously. Return
TRUEif so.
t_bool sim_disk_wrp (UNIT *uptr)Return
TRUEif unituptris write-protected.
t_addr sim_disk_size (UNIT *uptr)Get disk size.
sim_disk_attach,
sim_disk_detach,
sim_disk_set_fmt,
sim_disk_show_fmt,
and sim_disk_set_capac return standard SCP status codes;
the other disk library routines return private codes for success and failure.
Success status is DKSE_OK and any other value is an error.
errno usually will have the appropriate error code:
|
Operation successful |
sim_disk_set_fmt,
sim_disk_show_fmt,
sim_disk_set_capac,
and sim_disk_show_capac should be referenced by an entry in the disk device’s modifier list, as follows:
MTAB disk_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "FORMAT", "FORMAT",
&sim_disk_set_fmt, &sim_disk_show_fmt, NULL },
{ MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY",
&sim_disk_set_capac, &sim_disk_show_capac, NULL },
/* … */
};
1.6.4. Breakpoint support¶
SIMH provides a highly flexible and extensible breakpoint subsystem to assist in debugging simulated code. Its features include:
Up to 26 different kinds of breakpoints [1]
Unlimited numbers of breakpoints
Proceed counts for each breakpoint
Automatic execution of commands when a breakpoint is taken
If debugging is going to be a major activity on a simulator, implementation of a full-featured breakpoint facility will be of immense help to users.
1.6.4.1. Breakpoint basics¶
SIMH breakpoints are characterized by a type, an address, a class, a proceed count, and an action string. Breakpoint types are arbitrary and are defined by the virtual machine. Each breakpoint type is assigned a unique letter. All simulators to date provide execution (“E”) breakpoints. A useful extension would be to provide breakpoints on read (“R”) and write (“W”) data access. Even finer gradations are possible, e.g., physical versus virtual addressing, DMA versus CPU access, and so on.
Breakpoints can be assigned to devices other than the CPU, but breakpoints don’t contain a device pointer. Thus, each device must have its own unique set of breakpoint types. For example, if a simulator contained a programmable graphics processor, it would need a separate instruction breakpoint type (e.g., type G rather than E).
The virtual machine defines the valid breakpoint types to SIMH through two variables:
sim_brk_typesInitialized by the VM (usually in the CPU reset routine) to a mask of all supported breakpoints; bit 0 (low-order bit) corresponds to type “A”, bit 1 to type “B”, etc.
sim_brk_dfltInitialized by the VM to the mask for the default breakpoint type.
SIMH in turn provides the virtual machine with a summary of all the breakpoint types that currently have active breakpoints:
sim_brk_summMaintained by SIMH; provides a bitmask summary of whether any breakpoints of a particular type have been defined.
When the virtual machine reaches the point in its execution cycle corresponding to a breakpoint type,
it tests to see if any breakpoints of that type are active.
If so, it calls sim_brk_test to see if a breakpoint of a specified type (or types) is set at the current address.
Here is an example from the fetch phase,
testing for an execution breakpoint:
/* Test for breakpoint before fetching next instruction */
if ((sim_brk_summ & SWMASK ('E')) &&
sim_brk_test (PC, SWMASK ('E'))) <execution break>
If the virtual machine implements only one kind of breakpoint,
then testing sim_brk_summ for non-zero suffices.
Even if there are multiple breakpoint types,
a simple non-zero test distinguishes the no-breakpoints case
(normal run mode)
from debugging mode and provides sufficient efficiency.
When a breakpoint match is detected by sim_brk_test,
the global variables sim_brk_match_type and sim_brk_match_addr are set to reflect the details of the match that was found.
Simulator code can use this information directly or SIMH provides internal facilities to report the details of breakpoints which have been matched.
For example:
BRKTYPTAB cpu_breakpoints [] = {
BRKTYPE('E', "Execute Instruction at Virtual Address"),
BRKTYPE('P', "Execute Instruction at Physical Address"),
BRKTYPE('R', "Read from Virtual Address"),
BRKTYPE('S', "Read from Physical Address"),
BRKTYPE('W', "Write to Virtual Address"),
BRKTYPE('X', "Write to Physical Address"),
{ 0 }
};
t_stat cpu_reset (DEVICE *dptr)
{
/* ... */
sim_brk_dflt = SWMASK ('E');
sim_brk_types = sim_brk_dflt | SWMASK ('P') |
SWMASK ('R') | SWMASK ('S') |
SWMASK ('W') | SWMASK ('X');
sim_brk_type_desc = cpu_breakpoints;
/* ... */
}
In the breakpoint dispatch code, something like:
reason = STOP_IBKPT;
sim_messagef (reason, "%s", sim_brk_message());
/* and then sim_instr() returns with: */
return reason;
Or, if it is desirable to suppress the standard message produced when returning to SCP, the following may be used:
reason = STOP_IBKPT;
reason = sim_messagef (reason, "%s\n", sim_brk_message());
/* and then sim_instr() returns with: */
return reason;
sim_messagef produces a message which contains either the breakpoint type and the matched breakpoint address
(if sim_brk_type_desc is not set),
or the type mapped to it related description as indicated in the BRKTYPTAB pointed to by sim_brk_typ_desc.
1.6.4.2. Testing for breakpoints¶
Breakpoint testing must be done at every point in the instruction decode and execution cycle where an event relating to a breakpoint type occurs. If a virtual machine implements data breakpoints, it simplifies implementation if data reads and writes are centralized in subroutines, rather than scattered throughout the code. For this reason (among others), it is good practice to perform memory access through subroutines, rather than by direct access to the memory array.
As an example, consider a virtual machine with a central memory read subroutine. This routine takes an additional parameter, the type of read (often required for memory protection):
#define IF 0 /* Fetch */
#define ID 1 /* Indirect */
#define RD 2 /* Data read */
#define WR 3 /* Data write */
t_stat Read (uint32 addr, uint32 *dat, uint32 acctyp)
{
static uint32 bkpt_type[4] = {
SWMASK ('E'), SWMASK ('N'),
SWMASK ('R'), SWMASK ('W')
};
if ((sim_brk_summ & bkpt_type[acctyp]) &&
sim_brk_test (addr, bkpt_type[acctyp])){
return STOP_BKPT;
}
else *dat = M[addr];
return SCPE_OK;
}
This routine provides differentiated breakpoints for execution, indirect addresses, and data reads, with a single test.
1.6.4.3. The replay problem¶
When a breakpoint is taken, control returns to the SIMH control package. Depending on the code structure of the simulated system and the particular type of breakpoint, a breakpoint may be taken before or after a specific activity has completed. If it is taken before the operation has actually been performed, when execution resumes, the same breakpoint will be reached and taken again immediately. This could result in an endless loop, with the simulator never progressing beyond a breakpoint.
To address this problem, when a breakpoint is taken, SIMH remembers the breakpoint that was taken and the instruction executed count when that particular breakpoint was taken. If the next breakpoint test for that breakpoint type is to the same address and the instruction execution count is the same, SIMH suppresses the breakpoint. Thus, the simulator can make progress past the breakpoint but will take the breakpoint again if control returns to the same address.
In order to properly suppress replay breakpoints it is important that the bookkeeping that a simulator does to record the instructions actually executed not be done when a breakpoint is taken.
This bookkeeping is done by adjustments to sim_interval and subsequent calls to sim_process_event.
If a simulator returns from sim_instr() due to a breakpoint, either the adjustment to sim_interval should be done after all breakpoint checking,
or the return logic that handles breakpoints should unwind any sim_interval adjustment that may have happened.
Most simulators will implement a CPU execution breakpoint concept such that the breakpoint is taken prior to the instruction at the breakpoint address having executed.
This allows for the user to continue execution from breakpoint and the simulator will produce precisely the same results as if the breakpoint hadn’t been there.
In order for this to be true,
when a breakpoint is taken,
not only must sim_interval be restored to its value prior to the breakpoint,
but all other simulator specific state must also be retained.
This state includes program counter,
the contents of registers,
condition codes and memory that may have already changed prior to the call to sim_brk_test that causes the breakpoint to be taken.
Achieving this is simplest with basic PC based execution breakpoints and gets more complicated with breakpoints based on various memory reference activities.
1.6.4.4. Breakpoint classes¶
SIMH implements up to 8 breakpoint classes. Each breakpoint class has its own state. Thus, if the E, R, and W breakpoints are assigned to separate classes, each will be suppressed in turn until the next breakpoint test on that class that fails or that uses a different address.
Breakpoint classes are arbitrary identifiers and can be assigned by the simulator writer as desired.
The class is specified as part of the breakpoint type in the call to sim_brk_test:
<31:29> = Class number (0 by default)
<25:0> = Bitmask of breakpoint types
Note that breakpoint classes and breakpoint types are orthogonal.
Thus, classes can be used to distinguish different cases of the same breakpoint type.
For example, in an SMP system with n processors,
classes 0..n-1 could be used for E-breakpoints for processors 0..n-1.
Or in a VAX, classes 1..6 could be used for data breakpoints on operands 1..6,
with 0 reserved for the CPU’s E-breakpoints.