2381 lines
109 KiB
C
2381 lines
109 KiB
C
/* hp2100_mem.c: HP 21xx/1000 Main Memory/Memory Expansion Module/Memory Protect simulator
|
|
|
|
Copyright (c) 1993-2016, Robert M. Supnik
|
|
Copyright (c) 2017-2018, J. David Bryan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the names of the authors shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from the authors.
|
|
|
|
Main memory 12615A/12839A/12885A Core Memory Subsystems
|
|
2102B MOS Memory Subsystem
|
|
MEM 12731A Memory Expansion Module
|
|
MP 12581A/12892B Memory Protect
|
|
|
|
02-Aug-18 JDB Added MEM device
|
|
30-Jul-18 JDB Renamed "iop_sp" to "SPR" (stack pointer register)
|
|
20-Jul-18 JDB Split out from hp2100_cpu.c
|
|
|
|
References:
|
|
- HP 1000 M/E/F-Series Computers Technical Reference Handbook
|
|
(5955-0282, March 1980)
|
|
- HP 1000 M/E/F-Series Computers Engineering and Reference Documentation
|
|
(92851-90001, March 1981)
|
|
- HP 1000 M/E/F-Series Computers I/O Interfacing Guide
|
|
(02109-90006, September 1980)
|
|
- 12892B Memory Protect Installation Manual
|
|
(12892-90007, June 1978)
|
|
- 2100A Computer Reference Manual
|
|
(02100-90001, December 1971)
|
|
- Central Processor Options Computer Maintenance Course Student's Manual
|
|
Volumes V, VI, VII, VIII, & IX
|
|
(5950-8707, April 1969)
|
|
|
|
This module simulates the HP 12615A and 12839A core memory subsystems for the
|
|
2116 and 2115/2114, respectively, the 12885A core memory subsystem for the
|
|
2100, and the 2102B MOS memory subsystem for the 1000 M/E/F-Series CPUs.
|
|
Main memory is implemented as a dynamically allocated array, "M", of
|
|
MEMORY_WORD words. The MEMORY_WORD type is a 16-bit unsigned type,
|
|
corresponding with the 16-bit main memory word of the HP 21xx/1000. The
|
|
largest supported memory size (one megaword for the HP 1000) is allocated
|
|
when the simulator is started, while the configured memory size for the
|
|
current CPU is kept in the "mem_size" variable. Installed memory sizes may
|
|
range from 4K words to 1M words.
|
|
|
|
HP 21xx and 1000 CPUs address a maximum of 32K words with 15-bit addresses.
|
|
This is the logical address space. 1000-series machines may employ an
|
|
optional Memory Expansion Module to map the logical address space anywhere
|
|
with a 1M-word physical memory on a 1K-per-page basis. For all machines,
|
|
reads to addresses outside of installed memory return all-zeros worda, and
|
|
writes outside of memory are ignored. Neither operation causes an error.
|
|
|
|
The core memory machines (2114, 2115, 2116, and 2100) have a protected area
|
|
of memory where a binary loader program may be stored. The protected loader
|
|
area resides in the last 64 words of installed memory and is normally
|
|
protected against reading and writing; as with non-existent memory, reads
|
|
returns all zeros words, and writes are ignored. The loader is unprotected
|
|
by a switch on the CPU front panel so that it may be executed, typically to
|
|
bootstrap a system from paper tape, magnetic tape, or disc. The loader is
|
|
automatically protected when the machine executes a HLT instruction. It may
|
|
also be protected manually through the front panel. In simulation, loader
|
|
protection is controlled by the "mem_end" variable. When it is equal to
|
|
"mem_size", the loader is unprotected and available for execution. When it
|
|
is less than "mem_size", the loader is protected, and memory logically ends
|
|
at the "mem_end" address.
|
|
|
|
This module provides routines to read and write memory words and bytes. All
|
|
memory accesses are classified as to the type of the access, which determines
|
|
the mapping mode and protection applied. Utility routines to initialize,
|
|
zero, and copy loaders to and from memory are also supplied.
|
|
|
|
|
|
|
|
This module also simulates the 12731A Memory Expansion Module for the 1000
|
|
M/E/F-Series machines. The MEM provides mapping of the 32 1K-word logical
|
|
memory pages into a one-megaword physical memory. Four separate 32-page maps
|
|
are provided: system, user, DCPC port A (used by channel 1), and DCPC port B
|
|
(used by channel 2).
|
|
|
|
The MEM is controlled by the associated Dynamic Mapping System instructions.
|
|
While enabled, all programmed memory accesses are translated via the system
|
|
or user map, depending on which is currently enabled, and all DCPC accesses
|
|
are translated through one of the two port maps, depending on which channel
|
|
is making the access.
|
|
|
|
In addition, page 0 (the base page) accesses have an additional translation
|
|
step. A base page fence separates a mapped portion from an unmapped potion
|
|
in the system and user maps. The mapped portion is mapped to the physical
|
|
page that resides in the first map register. The unmapped portion is not
|
|
mapped and accesses physical page 0. A MEM setting controls whether the
|
|
mapped portion is above or below the fence.
|
|
|
|
Each map page may be protected against reading or writing. Write protection
|
|
also extends to executing jump instructions that target the page. Attempting
|
|
a protected access results in a MEM violation, which is handled by the Memory
|
|
Protect card. If MP is enabled, a MEM violation causes an interrupt on
|
|
select code 05; if MP is disabled, no violation occurs, and the read or write
|
|
proceeds normally. MP and MEM violations are distinguished by executing an
|
|
SFS 05 instruction, which skips for MEM violations but not for MP violations.
|
|
Read and write protections are ignored for DCPC accesses.
|
|
|
|
In addition, MEM violations also occur for attempts to write into the
|
|
unmapped portion of the base page (i.e., to physical page 0), as well as
|
|
attempts to execute privileged DMS instructions (i.e., those that load any of
|
|
the map registers). The MEM status and violation registers reflect the
|
|
current status of the MEM and the last violation, if any. They are formatted
|
|
as follows.
|
|
|
|
MEM Status Register:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| I | M | E | U | P | B | base page fence address |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Where:
|
|
|
|
I = MEM was disabled/enabled (0/1) at last interrupt
|
|
M = System/user map (0/1) was selected at last interrupt
|
|
E = MEM is disabled/enabled (0/1) currently
|
|
U = System/user map (0/1) is selected currently
|
|
P = Protected mode is disabled/enabled (0/1) currently
|
|
B = Base-page portion mapped is above/below (0/1) the fence
|
|
|
|
|
|
MEM Violation Register:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| R | W | B | P | - - - - | S | E | M | page address |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Where:
|
|
|
|
R = Read violation
|
|
W = Write violation
|
|
B = Base-page violation
|
|
P = Privileged instruction violation
|
|
S = ME bus disabled/enabled (0/1) at violation
|
|
E = MEM disabled/enabled (0/1) at violation
|
|
M = System/user map (0/1) selected at violation
|
|
|
|
|
|
The MEM card has four hardware configuration jumpers:
|
|
|
|
W1 - configure for 1000 M-Series (A)
|
|
or 1000 E/F-Series (B)
|
|
|
|
W2 - normal operation (IN)
|
|
or factory test (OUT)
|
|
|
|
W3 - factory test (IN)
|
|
or normal operation (OUT)
|
|
|
|
W4 (RME) - MEM remains in the system map after IAK for IOG trap instruction (A)
|
|
or returns to the prior map (B)
|
|
|
|
These jumpers are not simulated. Instead, the simulation behaves as though
|
|
W1 is set correctly for the current CPU type, W2 is IN, W3 is OUT, and W4 is
|
|
set to the A position.
|
|
|
|
|
|
|
|
This module also simulates the 12581A/12892B Memory Protect accessories for
|
|
the 2116 and 1000 M/E/F-Series, respectively, and the memory protect feature
|
|
that is standard equipment for the 2100. MP is addressed via select code 05
|
|
and provides a fence register that holds the address of the start of
|
|
unprotected memory and a violation register that holds the address of the
|
|
instruction that has caused a memory protect violation.
|
|
|
|
In hardware, if the Memory Protect accessory is installed and enabled, I/O
|
|
operations to select codes other than 01 are prohibited. Also, in
|
|
combination with the MPCK micro-order, MP validates the M-register contents
|
|
(memory address) against the memory protect fence. If a violation occurs, an
|
|
I/O instruction or memory write is inhibited, and a memory read returns
|
|
invalid data.
|
|
|
|
In simulation, MP violations are usually detected automatically when the
|
|
"mem_write" routine is called to write to memory or the "cpu_iog" routine is
|
|
called to execute an I/O instruction. A few instruction executors detect MP
|
|
violations explicitly and call the "mp_violation" routine. If MP is enabled,
|
|
the routine sets the MP flag and then calls "cpu_microcode_abort" to abort
|
|
the instruction. That routine executes a "longjmp" to the abort handler,
|
|
which is outside of and precedes the instruction execution loop.
|
|
|
|
An MP interrupt (SC 05) is qualified by "interrupt_system" but not by
|
|
"cpu_interrupt_enable". If the interrupt system is off when an MP violation
|
|
is detected, the violating instruction will be aborted, even though no
|
|
interrupt occurs. In this case, neither the flag nor the flag buffer are
|
|
set.
|
|
|
|
MP is controlled by I/O instructions directed to select code 05, as follows.
|
|
|
|
Output Data Word format (OTA and OTB):
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 | starting address of unprotected memory | fence
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
|
|
Input Data Word formats (LIA, LIB, MIA, and MIB):
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 | violating instruction address | MP violation
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 | violating instruction address | PE violation
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
After setting the fence regiater with an OTA 05 or OTB 05 instruction, MP is
|
|
enabled by an STC 05. MP cannot be disabled programmatically; it is disabled
|
|
only by a violation. The SFS 05 and SFC 05 instructions test the Memory
|
|
Expansion Violation flip-flop, not the MP flag flip-flop. The MEV flip-flop
|
|
is set for a MEM violation and is clear for an MP violation.
|
|
|
|
|
|
The 12892B card has six hardware configuration jumpers:
|
|
|
|
W3 (HLTPE) - parity violation register clocked when an error occurs (OUT)
|
|
or not clocked when error an occurs and the CPU switch is in
|
|
the HLT PE position (IN)
|
|
|
|
W4 (MX) - timing is for an E/F-Series (OUT)
|
|
or for an M-Series (IN)
|
|
|
|
W5 (JSB) - JSB to locations 0 and 1 are prohibited (OUT)
|
|
or permitted (IN)
|
|
|
|
W6 (INT) - interrupts are enabled immediately if MP is enabled (OUT)
|
|
or only after three levels of indirection (IN)
|
|
|
|
W7 (SEL1) - permit I/O only to select code 01 (OUT)
|
|
or to all select codes (IN)
|
|
|
|
W8 (RME) - MEM remains in the system map after IAK for IOG trap instruction (OUT)
|
|
or returns to the prior map (IN)
|
|
|
|
In simulation, jumpers W5, W6, and W7 may be set via the SCP command line;
|
|
the default (normal) positions are W5 IN, W6 IN, and W7 OUT. Jumpers W3, W4,
|
|
and W8 are not simulated. Instead, the simulation behaves as though W3 is
|
|
OUT, W4 is set correctly for the current CPU type, and W8 is OUT. The
|
|
jumpers designated as W1 and W2 do not exist.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The terms MEM (Memory Expansion Module), MEU (Memory Expansion Unit), DMI
|
|
(Dynamic Mapping Instructions), and DMS (Dynamic Mapping System) are used
|
|
somewhat interchangeably to refer to the logical-to-physical memory
|
|
address translation option provided on the 1000-Series. DMS consists of
|
|
the MEM card (12731A) and the DMI firmware (13307A). However, MEM and
|
|
MEU have been used interchangeably to refer to the mapping card, as have
|
|
DMI and DMS to refer to the firmware instructions.
|
|
|
|
In this module, MEM routines and state variables are prefixed "meu_"
|
|
rather than "mem_" to avoid confusion with the main memory symbols.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp2100_defs.h"
|
|
#include "hp2100_cpu.h"
|
|
#include "hp2100_cpu_dmm.h"
|
|
|
|
|
|
|
|
/* Main memory instruction masks */
|
|
|
|
#define IR_MRG (MRG | AB_MASK) /* MRG instructions mask */
|
|
|
|
#define IR_ISZ 0034000u /* ISZ instruction */
|
|
#define IR_STF 0102100u /* STF instruction */
|
|
|
|
|
|
/* Main memory access classification table */
|
|
|
|
typedef struct {
|
|
uint32 debug_flag; /* the debug flag for tracing */
|
|
const char *name; /* the classification name */
|
|
} ACCESS_PROPERTIES;
|
|
|
|
static const ACCESS_PROPERTIES mem_access [] = { /* indexed by ACCESS_CLASS */
|
|
/* debug_flag name */
|
|
/* ------------ ------------------- */
|
|
{ TRACE_FETCH, "instruction fetch" }, /* instruction fetch */
|
|
{ TRACE_DATA, "data" }, /* data access */
|
|
{ TRACE_DATA, "data" }, /* data access, alternate map */
|
|
{ TRACE_DATA, "unprotected" }, /* data access, system map */
|
|
{ TRACE_DATA, "unprotected" }, /* data access, user map */
|
|
{ TRACE_DATA, "dma" }, /* DMA channel 1, port A map */
|
|
{ TRACE_DATA, "dma" } /* DMA channel 2, port B map */
|
|
};
|
|
|
|
|
|
/* Main memory OS base page addresses */
|
|
|
|
static const uint32 m64 = 0000040u; /* (DOS) constant -64 address */
|
|
static const uint32 p64 = 0000067u; /* (DOS) constant +64 address */
|
|
|
|
static const uint32 xeqt = 0001717u; /* (RTE) XEQT address */
|
|
static const uint32 tbg = 0001674u; /* (RTE) TBG address */
|
|
|
|
|
|
/* Main memory tracing constants */
|
|
|
|
static const char * const register_values [] = { /* register values, indexed by EOI concatenation */
|
|
"e o i", /* E = 0, O = 0, interrupt_system = off */
|
|
"e o I", /* E = 0, O = 0, interrupt_system = on */
|
|
"e O i", /* E = 0, O = 1, interrupt_system = off */
|
|
"e O I", /* E = 0, O = 1, interrupt_system = on */
|
|
"E o i", /* E = 1, O = 0, interrupt_system = off */
|
|
"E o I", /* E = 1, O = 0, interrupt_system = on */
|
|
"E O i", /* E = 1, O = 1, interrupt_system = off */
|
|
"E O I" /* E = 1, O = 1, interrupt_system = on */
|
|
};
|
|
|
|
static const char mp_value [] = { /* memory protection value, indexed by mp_control */
|
|
'-', /* MP is off */
|
|
'P' /* MP is on */
|
|
};
|
|
|
|
static const char * const register_formats [] = { /* CPU register formats, indexed by is_1000 */
|
|
REGA_FORMAT " A %06o, B %06o, ", /* is_1000 = FALSE format */
|
|
REGA_FORMAT " A %06o, B %06o, X %06o, Y %06o, " /* is_1000 = TRUE format */
|
|
};
|
|
|
|
static const char * const mp_mem_formats [] = { /* MP/MEM register formats, indexed by is_1000 */
|
|
REGB_FORMAT " MPF %06o, MPV %06o\n", /* is_1000 = FALSE format */
|
|
REGB_FORMAT " MPF %06o, MPV %06o, MES %06o, MEV %06o\n" /* is_1000 = TRUE format */
|
|
};
|
|
|
|
|
|
/* Main memory global state declarations */
|
|
|
|
uint32 mem_size = 0; /* size of main memory in words */
|
|
uint32 mem_end = 0; /* address of the first word beyond installed memory */
|
|
|
|
|
|
/* Main memory local state declarations */
|
|
|
|
static MEMORY_WORD *M = NULL; /* the pointer to allocated memory */
|
|
static DIB *tbg_dibptr = NULL; /* a pointer to the time-base generator DIB (for RTE idle check) */
|
|
static t_bool is_1000 = FALSE; /* TRUE if the CPU is a 1000 M/E/F-Series */
|
|
|
|
|
|
/* Memory Expansion Unit command line switches */
|
|
|
|
#define ALL_MAPMODES (SWMASK ('S') | SWMASK ('U') | \
|
|
SWMASK ('P') | SWMASK ('Q'))
|
|
|
|
|
|
/* Memory Expansion Unit program limits */
|
|
|
|
#define MAP_COUNT 4 /* number of maps */
|
|
#define REG_COUNT 32 /* number of map registers per map */
|
|
|
|
|
|
/* Memory Expansion Unit program constants */
|
|
|
|
#define LWA_BASE_PAGE 0001777u /* address of the last word on the base page */
|
|
|
|
#define MAP_MASK (MAP_COUNT - 1) /* mask to the map selection bits */
|
|
|
|
#define ALTERNATE_MAP(m) ((MEU_MAP_SELECTOR) ((m) ^ 1)) /* switch to alternate map (user or system) */
|
|
|
|
|
|
/* Memory Expansion Unit state constant declarations */
|
|
|
|
static const char map_indicator [] = { /* MEU map indicator, indexed by MEU_MAP_SELECTOR */
|
|
'S', /* System_Map */
|
|
'U', /* User_Map */
|
|
'A', /* Port_A_Map */
|
|
'B' /* Port_B_Map */
|
|
};
|
|
|
|
|
|
/* MEU Page Map Registers.
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| R | W | - - - - | physical page address |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
#define READ_PROTECTED 0100000u /* (R) read protection bit */
|
|
#define WRITE_PROTECTED 0040000u /* (W) write protection bit */
|
|
#define MAP_RESERVED 0036000u /* reserved bits */
|
|
#define PAGE_MASK PP_MASK /* physical page address mask */
|
|
|
|
#define NO_PROTECTION 0000000u /* no read/write protection */
|
|
|
|
#define MAP_PAGE(r) ((r) & PAGE_MASK) /* extract the page number from a map register */
|
|
|
|
|
|
|
|
/* MEU status register.
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| I | M | E | U | P | B | base page fence address |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
#define MEST_ENBL_INT 0100000u /* (I) MEM was enabled at last interrupt */
|
|
#define MEST_UMAP_INT 0040000u /* (M) User map was selected at last interrupt */
|
|
#define MEST_ENABLED 0020000u /* (E) MEM is enabled currently */
|
|
#define MEST_USER_MAP 0010000u /* (U) User map is selected currently (set dynamically) */
|
|
#define MEST_PROTECTED 0004000u /* (P) Protected mode is enabled currently (set dynamically) */
|
|
#define MEST_BELOW 0002000u /* (B) Base page below fence is mapped */
|
|
#define MEST_FENCE_MASK 0001777u /* Base page fence mask */
|
|
|
|
#define MEST_DYNAMIC (MEST_USER_MAP | MEST_PROTECTED)
|
|
#define MEST_DYNAMIC_IAK (MEST_ENBL_INT | MEST_UMAP_INT)
|
|
|
|
|
|
/* MEU Violation Register.
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| R | W | B | P | - - - - | S | E | M | page index |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
#define MEVI_READ 0100000u /* (R) = Read violation */
|
|
#define MEVI_WRITE 0040000u /* (W) = Write violation */
|
|
#define MEVI_BASE_PAGE 0020000u /* (B) = Base page violation */
|
|
#define MEVI_PRIVILEGE 0010000u /* (P) = Privileged instruction violation */
|
|
#define MEVI_BUS_ENABLED 0000200u /* (S) = ME bus was enabled at violation */
|
|
#define MEVI_MEM_ENABLED 0000100u /* (E) = MEM was enabled at violation */
|
|
#define MEVI_USER_MAP 0000040u /* (M) = User map was selected at violation */
|
|
#define MEVI_INDEX_MASK 0000037u /* Page register index for which the violation occurred */
|
|
|
|
|
|
/* Memory Expansion Unit global state declarations */
|
|
|
|
char meu_indicator; /* last map access indicator (S | U | A | B | -) */
|
|
uint32 meu_page; /* last physical page number accessed */
|
|
|
|
|
|
/* Memory Expansion Unit local state declarations */
|
|
|
|
static MEU_MAP_SELECTOR meu_current_map = System_Map; /* the current map */
|
|
static t_bool meu_bus_enabled = FALSE; /* TRUE if the memory expansion bus is enabled */
|
|
static HP_WORD meu_status = 0; /* the MEM status register */
|
|
static HP_WORD meu_violation = 0; /* the MEM violation register */
|
|
static HP_WORD meu_maps [MAP_COUNT] [REG_COUNT]; /* the MEM map registers */
|
|
|
|
|
|
/* Memory Expansion Unit local SCP support routine declarations */
|
|
|
|
static t_stat meu_reset (DEVICE *dptr);
|
|
|
|
|
|
/* Memory Expansion Unit local utility routine declarations */
|
|
|
|
static void dm_violation (HP_WORD violation);
|
|
static t_bool is_mapped (HP_WORD address);
|
|
static uint32 map_address (HP_WORD address, MEU_MAP_SELECTOR map, HP_WORD protection);
|
|
|
|
|
|
/* Memory Expansion Unit SCP data declarations */
|
|
|
|
|
|
/* Unit list */
|
|
|
|
static UNIT meu_unit [] = {
|
|
/* Event Routine Unit Flags Capacity Delay */
|
|
/* ------------- ---------- -------- ----- */
|
|
{ UDATA (NULL, 0, 0) } /* dummy unit */
|
|
};
|
|
|
|
|
|
/* Register list.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The REG definitions for the maps must be 17 bits (not 16) to ensure that
|
|
the map entries are accessed as 32-bit HP_WORDs and not uint16s.
|
|
*/
|
|
|
|
static REG meu_reg [] = {
|
|
/* Macro Name Location Radix Width Offset Depth Flags */
|
|
/* ------ --------- ---------------------- ----- ----- -------- ---------- ----------------- */
|
|
{ FLDATA (ENABLED, meu_status, 13) },
|
|
{ FLDATA (CURMAP, meu_current_map, 0) },
|
|
{ ORDATA (STATUS, meu_status, 16) },
|
|
{ ORDATA (VIOL, meu_violation, 16) },
|
|
{ BRDATA (SMAP, meu_maps [System_Map], 8, 17, REG_COUNT) },
|
|
{ BRDATA (UMAP, meu_maps [User_Map], 8, 17, REG_COUNT) },
|
|
{ BRDATA (PAMAP, meu_maps [Port_A_Map], 8, 17, REG_COUNT) },
|
|
{ BRDATA (PBMAP, meu_maps [Port_B_Map], 8, 17, REG_COUNT) },
|
|
{ FLDATA (MEBEN, meu_bus_enabled, 0), REG_HRO },
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
|
|
/* Device descriptor */
|
|
|
|
DEVICE meu_dev = {
|
|
"MEM", /* device name */
|
|
meu_unit, /* unit array */
|
|
meu_reg, /* register array */
|
|
NULL, /* modifier array */
|
|
1, /* number of units */
|
|
8, /* address radix */
|
|
1, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&meu_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
NULL, /* attach routine */
|
|
NULL, /* detach routine */
|
|
NULL, /* device information block pointer */
|
|
DEV_DIS, /* device flags */
|
|
0, /* debug control flags */
|
|
NULL, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* Memory Protect unit flags */
|
|
|
|
#define UNIT_V_MP_JSB (UNIT_V_UF + 0) /* MP jumper W5 */
|
|
#define UNIT_V_MP_INT (UNIT_V_UF + 1) /* MP jumper W6 */
|
|
#define UNIT_V_MP_SEL1 (UNIT_V_UF + 2) /* MP jumper W7 */
|
|
|
|
#define UNIT_MP_JSB (1 << UNIT_V_MP_JSB) /* 1 = W5 is out */
|
|
#define UNIT_MP_INT (1 << UNIT_V_MP_INT) /* 1 = W6 is out */
|
|
#define UNIT_MP_SEL1 (1 << UNIT_V_MP_SEL1) /* 1 = W7 is out */
|
|
|
|
|
|
/* Memory Protect violation register.
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| P | violating instruction address |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
#define MPVR_PARITY_ERROR 0100000u /* (P) parity error violation */
|
|
|
|
|
|
/* Memory Protect global state declarations */
|
|
|
|
HP_WORD mp_fence = 0; /* MP fence register */
|
|
|
|
|
|
/* Memory Protect local state declarations */
|
|
|
|
static HP_WORD mp_VR = 0; /* MP violation register */
|
|
static FLIP_FLOP mp_control = CLEAR; /* MP control flip-flop */
|
|
static FLIP_FLOP mp_flag_buffer = CLEAR; /* MP flag buffer flip-flop */
|
|
static FLIP_FLOP mp_flag = CLEAR; /* MP flag flip-flop */
|
|
static FLIP_FLOP mp_mevff = CLEAR; /* memory expansion violation flip-flop */
|
|
static FLIP_FLOP mp_evrff = SET; /* enable violation register flip-flop */
|
|
static FLIP_FLOP mp_enabled = CLEAR; /* MP was enabled at interrupt */
|
|
static FLIP_FLOP mp_reenable = CLEAR; /* MP will be reenabled after IAK */
|
|
static t_bool mp_mem_changed = TRUE; /* TRUE if the MP or MEM registers have been altered */
|
|
static uint32 jsb_bound = 2; /* protected lower bound for JSB */
|
|
|
|
|
|
/* Memory Protect I/O interface routine declarations */
|
|
|
|
static INTERFACE mp_interface;
|
|
|
|
|
|
/* Memory Protect local SCP support routine declarations */
|
|
|
|
static t_stat mp_set_jsb (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
|
|
static t_stat mp_reset (DEVICE *dptr);
|
|
|
|
|
|
/* Memory Protect local utility routine declarations */
|
|
|
|
static t_stat mp_service (UNIT *uptr);
|
|
|
|
|
|
/* Memory Protect SCP data declarations */
|
|
|
|
/* Unit list.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The default flags correspond to the following jumper settings: JSB in,
|
|
INT in, SEL1 out.
|
|
*/
|
|
|
|
static UNIT mp_unit [] = {
|
|
/* Event Routine Unit Flags Capacity Delay */
|
|
/* ------------- ------------ -------- ----- */
|
|
{ UDATA (&mp_service, UNIT_MP_SEL1, 0) }
|
|
};
|
|
|
|
|
|
/* Device information block */
|
|
|
|
static DIB mp_dib = {
|
|
&mp_interface, /* the device's I/O interface function pointer */
|
|
MPPE, /* the device's select code (02-77) */
|
|
0, /* the card index */
|
|
NULL, /* the card description */
|
|
NULL /* the ROM description */
|
|
};
|
|
|
|
|
|
/* Register list */
|
|
|
|
static REG mp_reg [] = {
|
|
/* Macro Name Location Width Flags */
|
|
/* ------ -------- -------------- ----- ------- */
|
|
{ FLDATA (CTL, mp_control, 0) },
|
|
{ FLDATA (FLG, mp_flag, 0) },
|
|
{ FLDATA (FBF, mp_flag_buffer, 0) },
|
|
{ ORDATA (FR, mp_fence, 15) },
|
|
{ ORDATA (VR, mp_VR, 16) },
|
|
{ FLDATA (EVR, mp_evrff, 0) },
|
|
{ FLDATA (MEV, mp_mevff, 0) },
|
|
|
|
{ FLDATA (ENABLED, mp_enabled, 0), REG_HRO },
|
|
{ FLDATA (REENABLE, mp_reenable, 0), REG_HRO },
|
|
{ ORDATA (PLBOUND, jsb_bound, 16), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
|
|
/* Modifier list */
|
|
|
|
static MTAB mp_mod [] = {
|
|
/* Mask Value Match Value Print String Match String Validation Display Descriptor */
|
|
/* ------------- ------------ --------------- ------------ ----------- ------- ---------- */
|
|
{ UNIT_MP_JSB, UNIT_MP_JSB, "JSB (W5) out", "JSBOUT", &mp_set_jsb, NULL, NULL },
|
|
{ UNIT_MP_JSB, 0, "JSB (W5) in", "JSBIN", &mp_set_jsb, NULL, NULL },
|
|
|
|
{ UNIT_MP_INT, UNIT_MP_INT, "INT (W6) out", "INTOUT", NULL, NULL, NULL },
|
|
{ UNIT_MP_INT, 0, "INT (W6) in", "INTIN", NULL, NULL, NULL },
|
|
|
|
{ UNIT_MP_SEL1, UNIT_MP_SEL1, "SEL1 (W7) out", "SEL1OUT", NULL, NULL, NULL },
|
|
{ UNIT_MP_SEL1, 0, "SEL1 (W7) in", "SEL1IN", NULL, NULL, NULL },
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
/* Trace list */
|
|
|
|
static DEBTAB mp_deb [] = {
|
|
{ "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
|
|
/* Device descriptor */
|
|
|
|
DEVICE mp_dev = {
|
|
"MP", /* device name */
|
|
mp_unit, /* unit array */
|
|
mp_reg, /* register array */
|
|
mp_mod, /* modifier array */
|
|
1, /* number of units */
|
|
8, /* address radix */
|
|
1, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&mp_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
NULL, /* attach routine */
|
|
NULL, /* detach routine */
|
|
&mp_dib, /* device information block pointer */
|
|
DEV_DISABLE | DEV_DIS | DEV_DEBUG, /* device flags */
|
|
0, /* debug control flags */
|
|
mp_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* Main memory global utility routines */
|
|
|
|
|
|
/* Initialize main memory.
|
|
|
|
This routine allocates and zeros the array of MEMORY_WORDs that represent the
|
|
main memory of the CPU. It also obtains and saves a pointer to the DIB of
|
|
the Time Base Generator device for RTE idle detection.
|
|
|
|
On entry, the "memory_size" parameter will be the number of words to allocate
|
|
and will represent the largest possible memory supported by the most
|
|
expansive CPU model. If memory allocation failed, or the TBG device was not
|
|
found, the routine returns an appropriate error.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. This routine is called only once during simulator startup. If it returns
|
|
an error status, then the simulator exits.
|
|
|
|
2. The TBG device is initially named "CLK" (for backward compatibility).
|
|
The logical name "TBG" is assigned by the TBG device reset routine, but
|
|
we are called before that routine is executed, so the logical name does
|
|
not exist when we are called.
|
|
*/
|
|
|
|
t_stat mem_initialize (uint32 memory_size)
|
|
{
|
|
DEVICE *tbg_dptr;
|
|
|
|
M = (MEMORY_WORD *) calloc (memory_size, /* allocate the maximum amount of memory needed */
|
|
sizeof (MEMORY_WORD));
|
|
|
|
tbg_dptr = find_dev ("CLK"); /* get a pointer to the time-base generator device */
|
|
|
|
if (tbg_dptr == NULL) /* if the TBG device is not present */
|
|
return SCPE_IERR; /* then something is seriously wrong */
|
|
else /* otherwise */
|
|
tbg_dibptr = (DIB *) tbg_dptr->ctxt; /* set a pointer to the device's DIB */
|
|
|
|
if (M == NULL) /* if memory allocation failed */
|
|
return SCPE_MEM; /* then report the error */
|
|
else /* otherwise */
|
|
return SCPE_OK; /* report successful allocation */
|
|
}
|
|
|
|
|
|
/* Read a word from memory.
|
|
|
|
This routine reads and returns a word from memory at the indicated logical
|
|
address. On entry, "dptr" points to the DEVICE structure of the device
|
|
requesting access, "classification" is the type of access requested, and
|
|
"address" is the offset into the 32K logical address space implied by the
|
|
classification.
|
|
|
|
If memory expansion is enabled, the logical address is mapped into a physical
|
|
memory location; the map used is determined by the access classification.
|
|
The current map (user or system), alternate map (the map not currently
|
|
selected), or an explicit map (system, user, DCPC port A, or DCPC port B) may
|
|
be requested. Read protection is enabled for current or alternate map access
|
|
and disabled for the others. If memory expansion is disabled or not present,
|
|
the logical address directly accesses the first 32K of memory.
|
|
|
|
The Memory Protect (MP) and Memory Expansion Module (MEM) accessories provide
|
|
a protected mode that guards against improper accesses by user programs.
|
|
They may be enabled or disabled independently, although protection requires
|
|
that both be enabled. MEM checks that read protection rules on the target
|
|
page are compatible with the access desired. If the check fails, and MP is
|
|
enabled, then the request is aborted.
|
|
|
|
The 1000 family maps memory location 0 to the A-register and location 1 to
|
|
the B-register. CPU reads of these locations return the A- or B-register
|
|
values, while DCPC reads access physical memory locations 0 and 1 instead.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. A read beyond the limit of physical memory returns 0. This is handled by
|
|
allocating the maximum memory array and initializing memory beyond the
|
|
defined limit to zero, so no special handling is needed here.
|
|
|
|
2. A MEM read protection violation with MP enabled causes an MP abort
|
|
instead of a normal return from the "map_address" routine..
|
|
|
|
3. In hardware, a FTCH micro-order clocks the address on the MBUS into the
|
|
MP Violation Register if the EVR (Enable Violation Register) flip-flop is
|
|
set. An MP or MEM violation clears EVR, preserving the address of the
|
|
violating instruction until the Violation Register is read during abort
|
|
processing.
|
|
*/
|
|
|
|
HP_WORD mem_read (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address)
|
|
{
|
|
uint32 index;
|
|
MEU_MAP_SELECTOR map;
|
|
HP_WORD protection;
|
|
|
|
MR = address; /* save the logical memory address */
|
|
|
|
switch (classification) { /* dispatch on the access classification */
|
|
|
|
case Fetch:
|
|
if (mp_evrff) /* if the violation register is enabled */
|
|
mp_VR = address; /* then update it with the instruction address */
|
|
|
|
map = meu_current_map; /* use the currently selected map (user or system) */
|
|
protection = READ_PROTECTED; /* and enable read protection */
|
|
break;
|
|
|
|
|
|
case Data:
|
|
default: /* needed to quiet the compiler's anxiety */
|
|
map = meu_current_map; /* use the currently selected map (user or system) */
|
|
protection = READ_PROTECTED; /* and enable read protection */
|
|
break;
|
|
|
|
|
|
case Data_Alternate:
|
|
map = ALTERNATE_MAP (meu_current_map); /* use the alternate map (user or system) */
|
|
protection = READ_PROTECTED; /* and enable read protection */
|
|
break;
|
|
|
|
|
|
case Data_System:
|
|
map = System_Map; /* use the system map explicitly */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case Data_User:
|
|
map = User_Map; /* use the user map explicitly */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case DMA_Channel_1:
|
|
map = Port_A_Map; /* use the DCPC port A map */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case DMA_Channel_2:
|
|
map = Port_B_Map; /* use the DCPC port B map */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
} /* all cases are handled */
|
|
|
|
index = map_address (address, map, protection); /* translate the logical address to a physical address */
|
|
|
|
if (index > 1 || map >= Port_A_Map) /* if memory is referenced or this is a DCPC transfer */
|
|
TR = (HP_WORD) M [index]; /* then return the physical memory value */
|
|
else /* otherwise */
|
|
TR = ABREG [index]; /* return the selected register value */
|
|
|
|
tpprintf (dptr, mem_access [classification].debug_flag,
|
|
DMS_FORMAT " %s%s\n",
|
|
meu_indicator, meu_page, MR, TR,
|
|
mem_access [classification].name,
|
|
mem_access [classification].debug_flag == TRACE_FETCH ? "" : " read");
|
|
|
|
return TR; /* return the word that was read */
|
|
}
|
|
|
|
|
|
/* Write a word to memory.
|
|
|
|
This routine writes a word to memory at the indicated logical address. On
|
|
entry, "dptr" points to the DEVICE structure of the device requesting access,
|
|
"classification" is the type of access requested, "address" is the offset
|
|
into the 32K logical address space implied by the classification, and "value"
|
|
is the value to write.
|
|
|
|
If memory expansion is enabled, the logical address is mapped into a physical
|
|
memory location; the map used is determined by the access classification.
|
|
The current map (user or system), alternate map (the map not currently
|
|
selected), or an explicit map (system, user, DCPC port A, or port B) may be
|
|
requested. Write protection is enabled for current or alternate map access
|
|
and disabled for the others. If memory expansion is disabled or not present,
|
|
the logical address directly accesses the first 32K of memory.
|
|
|
|
The Memory Protect (MP) and Memory Expansion Module (MEM) accessories provide
|
|
a protected mode that guards against improper accesses by user programs.
|
|
They may be enabled or disabled independently, although protection requires
|
|
that both be enabled. MP checks that memory writes do not fall below the
|
|
Memory Protect Fence Register (MPFR) value, and MEM checks that write
|
|
protection rules on the target page are compatible with the access desired.
|
|
If either check fails, and MP is enabled, then the request is aborted (so, to
|
|
pass, a page must be writable AND the target must be above the MP fence). In
|
|
addition, a MEM write violation will occur if MP is enabled and the alternate
|
|
map is selected, regardless of the page protection.
|
|
|
|
The 1000 family maps memory location 0 to the A-register and location 1 to
|
|
the B-register. CPU writes to these locations store the values into the A or
|
|
B register, while DCPC writes access physical memory locations 0 and 1
|
|
instead. MP uses a lower bound of 2 for memory writes, allowing unrestricted
|
|
access to the A and B registers.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. if memoy expansion is disabled, a write beyond the limit of physical
|
|
memory is a no-operation. If expansion is enabled, it is a NOP if the
|
|
page is not write-protected.
|
|
|
|
2. When the alternate map is enabled, writes are permitted only in the
|
|
unprotected mode, regardless of page protections or the MP fence setting.
|
|
This behavior is not mentioned in the MEM documentation, but it is tested by
|
|
the MEM diagnostic and is evident from the MEM schematic. Referring to
|
|
Sheet 2 in the ERD, gates U125 and U127 provide this logic:
|
|
|
|
WTV = MPCNDB * MAPON * (WPRO + ALTMAP)
|
|
|
|
The ALTMAP signal is generated by the not-Q output of flip-flop U117,
|
|
which toggles on control signal -CL3 assertion (generated by the MESP
|
|
microorder) to select the alternate map. Therefore, a write violation is
|
|
indicated whenever a memory protect check occurs while the MEM is enabled
|
|
and either the page is write-protected or the alternate map is selected.
|
|
|
|
The hardware reference manuals that contain descriptions of those DMS
|
|
instructions that write to the alternate map (e.g., MBI) say, "This
|
|
instruction will always cause a MEM violation when executed in the
|
|
protected mode and no bytes [or words] will be transferred." However,
|
|
they do not state that a write violation will be indicated, nor does the
|
|
description of the write violation state that this is a potential cause.
|
|
*/
|
|
|
|
void mem_write (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address, HP_WORD value)
|
|
{
|
|
uint32 index;
|
|
MEU_MAP_SELECTOR map;
|
|
HP_WORD protection;
|
|
|
|
MR = address; /* save the logical memory address */
|
|
|
|
switch (classification) { /* dispatch on the access classification */
|
|
|
|
case Data:
|
|
default: /* needed to quiet the compiler's anxiety */
|
|
map = meu_current_map; /* use the currently selected map (user or system) */
|
|
protection = WRITE_PROTECTED; /* and enable write protection */
|
|
break;
|
|
|
|
|
|
case Data_Alternate:
|
|
map = ALTERNATE_MAP (meu_current_map); /* use the alternate map (user or system) */
|
|
protection = WRITE_PROTECTED; /* and enable write protection */
|
|
|
|
if (meu_status & MEST_ENABLED) /* if the MEM is enabled */
|
|
dm_violation (MEVI_WRITE); /* then a violation always occurs if in protected mode */
|
|
break;
|
|
|
|
|
|
case Data_System:
|
|
map = System_Map; /* use the system map explicitly */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case Data_User:
|
|
map = User_Map; /* use the user map explicitly */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case DMA_Channel_1:
|
|
map = Port_A_Map; /* use the DCPC port A map */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case DMA_Channel_2:
|
|
map = Port_B_Map; /* use the DCPC port B map */
|
|
protection = NO_PROTECTION; /* without protection */
|
|
break;
|
|
|
|
|
|
case Fetch: /* instruction fetches */
|
|
return; /* do not cause writes */
|
|
|
|
} /* all cases are handled */
|
|
|
|
index = map_address (address, map, protection); /* translate the logical address to a physical address */
|
|
|
|
if (protection == WRITE_PROTECTED /* if protection is wanted */
|
|
&& address >= 2 && address < mp_fence) /* and the MP check fails */
|
|
mp_violation (); /* then a memory protect violation occurs */
|
|
|
|
if (index <= 1 && map <= User_Map) /* if the A/B register is referenced in the system or user map */
|
|
ABREG [index] = value; /* then write the value to the selected register */
|
|
|
|
else if (index < mem_end) /* otherwise if the location is within defined memory */
|
|
M [index] = (MEMORY_WORD) value; /* then write the value to memory */
|
|
|
|
TR = value; /* save the value just written */
|
|
|
|
tpprintf (dptr, mem_access [classification].debug_flag,
|
|
DMS_FORMAT " %s write\n",
|
|
meu_indicator, meu_page, MR, TR,
|
|
mem_access [classification].name);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Read a byte from memory.
|
|
|
|
This routine reads and returns a byte from memory at the indicated logical
|
|
address. On entry, "dptr" points to the DEVICE structure of the device
|
|
requesting access, "classification" is the type of access requested, and
|
|
"byte_address" is the byte offset into the 32K logical address space implied
|
|
by the classification.
|
|
|
|
The HP 1000 is a word-oriented machine. To permit byte accesses, a logical
|
|
byte address is defined as two times the associated word address. The LSB of
|
|
the byte address designates the byte to access: 0 for the upper byte, and 1
|
|
for the lower byte. As all 16 bits are used, byte addresses cannot be
|
|
indirect.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Word buffering is not used to minimize memory reads, as the HP 1000
|
|
microcode does a full word read for each byte accessed.
|
|
*/
|
|
|
|
uint8 mem_read_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address)
|
|
{
|
|
const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */
|
|
HP_WORD word;
|
|
|
|
word = mem_read (dptr, classification, word_address); /* read the addressed word */
|
|
|
|
if (byte_address & LSB) /* if the byte address is odd */
|
|
return LOWER_BYTE (word); /* then return the right-hand byte */
|
|
else /* otherwise */
|
|
return UPPER_BYTE (word); /* return the left-hand byte */
|
|
}
|
|
|
|
|
|
/* Write a byte to memory.
|
|
|
|
This routine writes a byte to memory at the indicated logical address. On
|
|
entry, "dptr" points to the DEVICE structure of the device requesting access,
|
|
"classification" is the type of access requested, "byte_address" is the byte
|
|
offset into the 32K logical address space implied by the classification, and
|
|
"value" is the value to write.
|
|
|
|
The HP 1000 is a word-oriented machine. To permit byte accesses, a logical
|
|
byte address is defined as two times the associated word address. The LSB of
|
|
the byte address designates the byte to access: 0 for the upper byte, and 1
|
|
for the lower byte. As all 16 bits are used, byte addresses cannot be
|
|
indirect.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Word buffering is not used to minimize memory writes, as the HP 1000
|
|
base-set microcode does a full word write for each byte accessed. (The
|
|
DMS byte instructions, e.g., MBI, do full-word accesses for each pair of
|
|
bytes, but that is to minimize the number of map switches.)
|
|
*/
|
|
|
|
void mem_write_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address, uint8 value)
|
|
{
|
|
const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */
|
|
HP_WORD word;
|
|
|
|
word = mem_read (dptr, classification, word_address); /* read the addressed word */
|
|
|
|
if (byte_address & LSB) /* if the byte address is odd */
|
|
word = REPLACE_LOWER (word, value); /* then replace the right-hand byte */
|
|
else /* otherwise */
|
|
word = REPLACE_UPPER (word, value); /* replace the left-hand byte */
|
|
|
|
mem_write (dptr, classification, word_address, word); /* write the updated word back */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Fast read from memory.
|
|
|
|
This routine reads and returns a word from memory at the indicated logical
|
|
address using the specified map. Memory protection is not used, and tracing
|
|
is not available.
|
|
|
|
This routine is used when fast, unchecked access to mapped memory is
|
|
required.
|
|
*/
|
|
|
|
HP_WORD mem_fast_read (HP_WORD address, MEU_MAP_SELECTOR map)
|
|
{
|
|
if (map == Current_Map) /* if the current map is requested */
|
|
map = meu_current_map; /* then use it */
|
|
|
|
return mem_examine (map_address (address, map, NO_PROTECTION)); /* return the value from the selected map */
|
|
}
|
|
|
|
|
|
/* Zero a range of memory locations.
|
|
|
|
Main memory locations from a supplied starting address through the end of
|
|
defined memory are filled with the specified value. This routine is
|
|
typically called to zero non-existent memory when the main memory size is
|
|
reduced (so that non-existent locations will read as zero).
|
|
*/
|
|
|
|
void mem_zero (uint32 starting_address, uint32 fill_count)
|
|
{
|
|
memset (M + starting_address, 0, /* zero the words */
|
|
fill_count * sizeof (MEMORY_WORD)); /* in the specified memory range */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Check for a non-zero value within a memory address range.
|
|
|
|
This routine checks a range of memory locations for the presence of a
|
|
non-zero value. The starting address of the range is supplied, and the check
|
|
continues through the end of defined memory. The routine returns TRUE if the
|
|
memory range was empty (i.e., contained only zero values) and FALSE
|
|
otherwise.
|
|
*/
|
|
|
|
t_bool mem_is_empty (uint32 starting_address)
|
|
{
|
|
uint32 address;
|
|
|
|
for (address = starting_address; address < mem_size; address++) /* loop through the specified address range */
|
|
if (M [address] != 0) /* if this location is non-zero */
|
|
return FALSE; /* then indicate that memory is not empty */
|
|
|
|
return TRUE; /* return TRUE if all locations contain zero values */
|
|
}
|
|
|
|
|
|
/* Copy a binary loader to or from protected memory.
|
|
|
|
This routine is called to copy a 64-word binary loader from a buffer to
|
|
memory or vice versa. On entry, "buffer" points at an array of MEMORY_WORDs
|
|
sufficiently large to hold a 64-word binary loader, "starting_address" is the
|
|
address in memory corresponding to the loader target, and "mode" is
|
|
"To_Memory" to copy from the buffer to memory or "From_Memory" to copy from
|
|
memory to the buffer. If copying from memory, the copied memory area is
|
|
zeroed before returning (memory is zeroed in preparation to protecting the
|
|
reserved loader area).
|
|
*/
|
|
|
|
void mem_copy_loader (MEMORY_WORD *buffer, uint32 starting_address, COPY_DIRECTION mode)
|
|
{
|
|
if (mode == To_Memory) /* if copying into memory */
|
|
memcpy (M + starting_address, buffer, /* then transfer the loader */
|
|
IBL_SIZE * sizeof (MEMORY_WORD)); /* from the buffer to memory */
|
|
|
|
else { /* otherwise */
|
|
memcpy (buffer, M + starting_address, /* transfer the loader */
|
|
IBL_SIZE * sizeof (MEMORY_WORD)); /* from memory to the buffer */
|
|
|
|
mem_zero (starting_address, IBL_SIZE); /* zero the vacated memory area */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Determine if the CPU is idle.
|
|
|
|
This routine determines whether the CPU is executing an operating system idle
|
|
loop. It is called when a JMP or JMP,I instruction is executed with CPU
|
|
idling enabled and no interrupt pending.
|
|
|
|
The 21xx/1000 CPUs have no "wait for interrupt" instruction. Idling in HP
|
|
operating systems consists of sitting in "idle loops" that end with JMP
|
|
instructions. We test for certain known patterns when a JMP instruction is
|
|
executed to decide if the simulator should idle.
|
|
|
|
If execution is within a recognized idle loop, the routine returns TRUE; in
|
|
response, the simulator will call the "sim_idle" routine to suspend execution
|
|
until the next event service is due. If the CPU is not executing an idle
|
|
loop, the routine returns FALSE to continue normal execution.
|
|
|
|
On entry, MR contains the address of the jump target, and "err_PR" contains
|
|
the address of the jump instruction. The difference gives the jump
|
|
displacement. The recognized idle patterns are operating-system-specific, as
|
|
follows:
|
|
|
|
for RTE-6/VM:
|
|
- ISZ <n> / JMP *-1
|
|
- mp_fence = 0
|
|
- XEQT (address 1717B) = 0
|
|
- MEU on with system map enabled
|
|
- RTE verification: TBG (address 1674B) = TBG select code
|
|
|
|
for RTE though RTE-IVB:
|
|
- JMP *
|
|
- mp_fence = 0
|
|
- XEQT (address 1717B) = 0
|
|
- MEU on with user map enabled (RTE-III through RTE-IVB only)
|
|
- RTE verification: TBG (address 1674B) = TBG select code
|
|
|
|
for DOS through DOS-III:
|
|
- STF 0 / CCA / CCB / JMP *-3
|
|
- DOS verification: A = B = -1, address 40B = -64, address 67B = +64
|
|
|
|
Note that in DOS, the TBG is set to 100 milliseconds vs. 10 milliseconds for
|
|
RTE.
|
|
*/
|
|
|
|
t_bool mem_is_idle_loop (void)
|
|
{
|
|
const int32 displacement = (int32) MR - (int32) err_PR; /* the jump displacement */
|
|
|
|
if ((displacement == 0 /* if the jump target is * (RTE through RTE-IVB) */
|
|
|| displacement == -1 && (M [MR] & IR_MRG) == IR_ISZ) /* or the target is *-1 (RTE-6/VM) and *-1 is ISZ <n> */
|
|
&& mp_fence == 0 /* and the MP fence is zero */
|
|
&& M [xeqt] == 0 /* and no program is executing */
|
|
&& M [tbg] == tbg_dibptr->select_code /* and the TBG select code is correct */
|
|
|
|
|| displacement == -3 /* or the jump target is *-3 (DOS through DOS-III) */
|
|
&& M [MR] == IR_STF /* and *-3 is STF 0 */
|
|
&& AR == 0177777u /* and the A and B registers */
|
|
&& BR == 0177777u /* are both set to -1 */
|
|
&& M [m64] == 0177700u /* and the -64 and +64 base-page constants */
|
|
&& M [p64] == 0000100u) /* are set as expected */
|
|
return TRUE; /* then the system is executing an idle lop */
|
|
|
|
else /* otherwise */
|
|
return FALSE; /* the system is not executing an idle loop */
|
|
}
|
|
|
|
|
|
/* Trace the working and MP/MEM registers.
|
|
|
|
This routine is called when CPU register tracing is enabled. It reports the
|
|
content of the working registers (S. A, B, X, Y, E, and O), memory protection
|
|
status (on or off), interrupt system status (on or off), and the current MEU
|
|
base page fence value. If the MP or MEM working registers changed since the
|
|
last trace report, an additional line is printed to report the memory protect
|
|
fence and violation registers and the memory expansion status and violation
|
|
registers.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "is_1000" flag is used to include or omit, based on the CPU model,
|
|
the X and Y registers from the working register trace and the MEVR and
|
|
MESR from the memory protection trace.
|
|
*/
|
|
|
|
void mem_trace_registers (FLIP_FLOP interrupt_system)
|
|
{
|
|
hp_trace (&cpu_dev, TRACE_REG, /* output the working registers */
|
|
register_formats [is_1000], /* using a format appropriate for the CPU model */
|
|
mp_value [mp_control],
|
|
meu_status & MEST_FENCE_MASK,
|
|
SR, AR, BR, XR, YR);
|
|
|
|
fputs (register_values [E << 2 | O << 1 | interrupt_system], sim_deb); /* output E, O, and interrupt system */
|
|
fputc ('\n', sim_deb);
|
|
|
|
if (mp_mem_changed) { /* if the MP/MEM registers have been altered */
|
|
hp_trace (&cpu_dev, TRACE_REG, /* then output the register values */
|
|
mp_mem_formats [is_1000], /* using a format appropriate for the CPU model */
|
|
mp_value [mp_control],
|
|
mp_fence, mp_VR, meu_status, meu_violation);
|
|
|
|
mp_mem_changed = FALSE; /* clear the MP/MEM registers changed flag */
|
|
}
|
|
}
|
|
|
|
|
|
/* Examine a physical memory address.
|
|
|
|
This routine reads and returns a word from memory at the indicated physical
|
|
address. If the address lies outside of allocated memory, a zero value is
|
|
returned. There are no protections or error indications.
|
|
*/
|
|
|
|
HP_WORD mem_examine (uint32 address)
|
|
{
|
|
if (address <= 1 && !(sim_switches & SIM_SW_REST)) /* if the address is 0 or 1 and not restoring memory */
|
|
return ABREG [address]; /* then return the A or B register value */
|
|
|
|
else if (address <= PA_MAX) /* otherwise if the address is within allocated memory */
|
|
return (HP_WORD) M [address]; /* then return the memory value */
|
|
|
|
else /* otherwise the access is outside of memory */
|
|
return 0; /* which reads as zero */
|
|
}
|
|
|
|
|
|
/* Deposit into a physical memory address.
|
|
|
|
This routine writes a word into memory at the indicated physical address. If
|
|
the address lies outside of defined memory, the write is ignored. There are
|
|
no protections or error indications.
|
|
*/
|
|
|
|
void mem_deposit (uint32 address, HP_WORD value)
|
|
{
|
|
if (address <= 1 && !(sim_switches & SIM_SW_REST)) /* if the address is 0 or 1 and not restoring memory */
|
|
ABREG [address] = value & DV_MASK; /* then store into the A or B register */
|
|
|
|
else if (address < mem_end) /* otherwise if the address is within defined memory */
|
|
M [address] = (MEMORY_WORD) value & DV_MASK; /* then store the value */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Memory Expansion Unit global utility routines */
|
|
|
|
|
|
/* Configure the Memory Expansion Module.
|
|
|
|
This routine enables or disables the MEM, depending on the "configuration"
|
|
parameter. If the MEM is being enabled, the "device disabled" flag is
|
|
cleared. Otherwise, the flag is set, and mapping is disabled so that address
|
|
translation will not occur.
|
|
|
|
The routine is called when the DMS instruction set is enabled or disabled.
|
|
The MEM device state tracks the instruction state and cannot be set
|
|
independently, i.e., with a SET MEM DISABLED command.
|
|
*/
|
|
|
|
void meu_configure (MEU_STATE configuration)
|
|
{
|
|
if (configuration == ME_Enabled) /* if DMS instructions are enabled */
|
|
meu_dev.flags &= ~DEV_DIS; /* then enable the MEM device */
|
|
|
|
else { /* otherwise */
|
|
meu_dev.flags |= DEV_DIS; /* disable the MEM */
|
|
meu_set_state (ME_Disabled, System_Map); /* and disable mapping */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Read a map register.
|
|
|
|
This routine is called to read one map register from the specified map. The
|
|
map index may be from 0-31 to read from a specific map (System_Map, User_Map,
|
|
etc.) or may be from 0-127 to read a linear sequence of maps (Linear_Map).
|
|
The map content (the protection bits and a physical page number corresponding
|
|
to the logical page number specified by the index) is returned.
|
|
*/
|
|
|
|
HP_WORD meu_read_map (MEU_MAP_SELECTOR map, uint32 index)
|
|
{
|
|
if (map == Linear_Map) /* if linear access is specified */
|
|
return meu_maps [index / REG_COUNT & MAP_MASK] /* then use the upper index bits for the map */
|
|
[index % REG_COUNT]; /* and the lower index bits for the register */
|
|
|
|
else /* otherwise */
|
|
return meu_maps [map] [index]; /* read from the specified map and register */
|
|
}
|
|
|
|
|
|
/* Write a map register.
|
|
|
|
This routine is called to write a value into one map register of the
|
|
specified map. The map index may be from 0-31 to write to a specific map
|
|
(System_Map, User_Map, etc.) or may be from 0-127 to write a linear sequence
|
|
of maps (Linear_Map). The map content (the protection bits and a physical
|
|
page number corresponding to the logical page number specified by the index)
|
|
is stored in the indicated register.
|
|
*/
|
|
|
|
void meu_write_map (MEU_MAP_SELECTOR map, uint32 index, uint32 value)
|
|
{
|
|
if (map == Linear_Map) /* if linear access is specified */
|
|
meu_maps [index / REG_COUNT & MAP_MASK] /* then use the upper index bits for the map */
|
|
[index % REG_COUNT] = value & ~MAP_RESERVED; /* and the lower index bits for the register */
|
|
|
|
else /* otherwise */
|
|
meu_maps [map] [index] = value & ~MAP_RESERVED; /* write to the specified map and register */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Set the MEM fence register.
|
|
|
|
This routine sets a new value into the MEM base-page fence register. The
|
|
value must have the "portion mapped" flag in bit 10 and the fence address is
|
|
bits 9-0. No error checking is performed.
|
|
*/
|
|
|
|
void meu_set_fence (HP_WORD new_fence)
|
|
{
|
|
meu_status = meu_status & ~(MEST_BELOW | MEST_FENCE_MASK) /* mask off the old mapping and address */
|
|
| new_fence & (MEST_BELOW | MEST_FENCE_MASK); /* and merge in the new values */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Set the Memory Expansion Unit state.
|
|
|
|
This routine is called to enable or disable the MEM and to set the current
|
|
map.
|
|
*/
|
|
|
|
void meu_set_state (MEU_STATE operation, MEU_MAP_SELECTOR map)
|
|
{
|
|
if (operation == ME_Enabled) /* if the MEM is being enabled */
|
|
meu_status |= MEST_ENABLED; /* then set the MEM enabled status bit */
|
|
|
|
else { /* otherwise disable the MEM */
|
|
meu_status &= ~MEST_ENABLED; /* by clearing the MEM enabled status bit */
|
|
meu_bus_enabled = FALSE; /* and disabling the MEM expansion bus */
|
|
}
|
|
|
|
meu_current_map = map; /* set the current map in either case */
|
|
mp_mem_changed = TRUE; /* and set the MP/MEM registers changed flag */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Update the MEM violation register.
|
|
|
|
This routine is called to update the MEM violation register. This is done
|
|
whenever the value in the register might be examined.
|
|
|
|
In hardware, the MEM violation register (MEVR) is clocked on every memory
|
|
read, every JMP or memory write (actually, every use of the MPCK micro-order)
|
|
above the lower bound of protected memory, and every execution of a
|
|
privileged DMS instruction. The register is not clocked when MP is disabled
|
|
by an MP or MEM error (i.e., when MEVFF sets or CTL5FF clears), in order to
|
|
capture the state of the MEM. In other words, the MEVR continually tracks
|
|
the memory map register accessed plus the MEM state (MEBEN, MAPON, and USR)
|
|
until a violation occurs, and then it's "frozen."
|
|
|
|
Under simulation, we do not have to update the MEVR on every memory access,
|
|
because the visible state is only available via a programmed RVA/B
|
|
instruction or via the SCP interface. Therefore, it is sufficient if the
|
|
register is updated:
|
|
|
|
- at a MEM violation (when freezing)
|
|
- at an MP violation (when freezing)
|
|
- during RVA/B execution (if not frozen)
|
|
- before returning to SCP after a simulator stop (if not frozen)
|
|
|
|
The routine returns the updated content of the violation register.
|
|
*/
|
|
|
|
HP_WORD meu_update_violation (void)
|
|
{
|
|
if (mp_control && mp_mevff == CLEAR) { /* if the violation register is not frozen */
|
|
meu_violation = PAGE (MR); /* then set the last map addressed */
|
|
|
|
if (meu_status & MEST_ENABLED) /* if the MEM is currently enabled */
|
|
meu_violation |= MEVI_MEM_ENABLED; /* then add the status bit */
|
|
|
|
if (meu_current_map == User_Map) /* if the user map is currently enabled */
|
|
meu_violation |= MEVI_USER_MAP; /* then add the status bit */
|
|
|
|
if (meu_bus_enabled) /* if the last memory address was mapped */
|
|
meu_violation |= MEVI_BUS_ENABLED; /* then add the "ME bus is enabled" bit */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
}
|
|
|
|
return meu_violation; /* return the violation register content */
|
|
}
|
|
|
|
|
|
/* Update the MEM status register.
|
|
|
|
This routine is called to update the MEM status register. This is done
|
|
whenever the value in the register might be examined.
|
|
|
|
In hardware, the MEM status register (MESR) is not a physical register but
|
|
rather a set of tristate drivers that enable the base-page fence register,
|
|
the current state of the MEM (disabled or enabled, system or user map), and
|
|
the MEM state at last interrupt onto the CPU's S-bus.
|
|
|
|
Under simulation, we do not have to update the MESR each time the current map
|
|
changes, because the visible state is only available via programmed RSA/B and
|
|
SSM instructions, via an RTE OS trap cell instruction (where it is used to
|
|
save the MEM state), or via the SCP interface. Therefore, it is sufficient
|
|
if the register is updated:
|
|
|
|
- during RSA/B or SSM or RTE OS trap cell instruction execution
|
|
- before returning to SCP after a simulator stop
|
|
|
|
The routine returns the updated content of the status register.
|
|
*/
|
|
|
|
HP_WORD meu_update_status (void)
|
|
{
|
|
meu_status &= ~MEST_DYNAMIC; /* clear the current MEM state */
|
|
|
|
if (meu_current_map == User_Map) /* if the user map is enabled */
|
|
meu_status |= MEST_USER_MAP; /* then set the currently enabled bit */
|
|
|
|
if (mp_control) /* if MP is enabled */
|
|
meu_status |= MEST_PROTECTED; /* then set the protected mode bit */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
|
|
return meu_status; /* return the status register value */
|
|
}
|
|
|
|
|
|
/* Assert an Interrupt Acknowledge signal to the MEM.
|
|
|
|
This routine asserts the IAK signal to the Memory Expansion Module. It is
|
|
called when the CPU acknowledges an interrupt. In response, the MEM saves
|
|
its current state and switches to the system map for interrupt processing.
|
|
|
|
In addition, if the CPU is tracing instructions, the routine calls
|
|
"map_address" to set the current map indicator and the page number of the
|
|
next instruction to execute. This will be used by the CPU to print the
|
|
interrupt location.
|
|
*/
|
|
|
|
void meu_assert_IAK (void)
|
|
{
|
|
meu_status &= ~MEST_DYNAMIC_IAK; /* clear the MEM interrupt state */
|
|
|
|
if (meu_status & MEST_ENABLED) /* if the MEM is enabled */
|
|
meu_status |= MEST_ENBL_INT; /* then add the enabled-at-interrupt bit */
|
|
|
|
if (meu_current_map == User_Map) /* if the user map is enabled */
|
|
meu_status |= MEST_UMAP_INT; /* then add the user-map-at-interrupt bit */
|
|
|
|
if (TRACING (cpu_dev, TRACE_INSTR)) /* if instruction tracing is active */
|
|
map_address (PR, meu_current_map, NO_PROTECTION); /* then set the MEM page and indicator */
|
|
|
|
meu_current_map = System_Map; /* switch to the system map for the interrupt */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Generate a MEM privilege violation.
|
|
|
|
This routine conditionally generates a dynamic mapping violation. If the
|
|
condition is "Always", then a privilege violation is generated. If the
|
|
condition is "If_User_Map", then a violation occurs if the user map is the
|
|
current map; otherwise, no violation occurs.
|
|
|
|
|
|
Implmentation notes:
|
|
|
|
1. If the MEM is in the protected mode, i.e., memory protect is on, a DM
|
|
violation will cause a microcode abort, and this routine will not return.
|
|
*/
|
|
|
|
void meu_privileged (MEU_CONDITION condition)
|
|
{
|
|
if (condition == Always || meu_current_map == User_Map)
|
|
dm_violation (MEVI_PRIVILEGE);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get the current MEM breakpoint type.
|
|
|
|
This routine returns a command line switch value representing the breakpoint
|
|
type that corresponds to the current MEM configuration. It is used to get
|
|
the current default breakpoint type, as follows:
|
|
|
|
MEM State Current Map Breakpoint Type
|
|
--------- ----------- ---------------
|
|
disabled -- N
|
|
enabled System S
|
|
enabled User U
|
|
|
|
The "is_iak" parameter is used to qualify the "U" type. If the user map is
|
|
currently enabled but an interupt acknowledgement is pending, then the
|
|
returned type is "S", as the IAK will be handled in the system map.
|
|
*/
|
|
|
|
uint32 meu_breakpoint_type (t_bool is_iak)
|
|
{
|
|
if (meu_status & MEST_ENABLED) /* if MEM is currently enabled */
|
|
if (meu_current_map == User_Map && !is_iak) /* then if the user map is currently enabled */
|
|
return SWMASK ('U'); /* then return the user breakpoint switch */
|
|
else /* otherwise */
|
|
return SWMASK ('S'); /* return the system breakpoint switch */
|
|
else /* otherwise MEM is disabled */
|
|
return SWMASK ('N'); /* so return the non-MEM breakpoint switch */
|
|
}
|
|
|
|
|
|
/* Translate a logical address for console access.
|
|
|
|
This routine translates a logical address interpreted in the context of the
|
|
translation map implied by the specified switch to a physical address. It is
|
|
called to map addresses when the user is examining or depositing memory. It
|
|
is also called to restore a saved configuration, although mapping is not used
|
|
for restoration. All memory protection checks are off for console access.
|
|
|
|
Command line switches modify the interpretation of logical addresses as
|
|
follows:
|
|
|
|
Switch Meaning
|
|
------ --------------------------------------------------
|
|
-N Use the address directly with no mapping
|
|
-S If memory expansion is enabled, use the system map
|
|
-U If memory expansion is enabled, use the user map
|
|
-P If memory expansion is enabled, use the port A map
|
|
-Q If memory expansion is enabled, use the port B map
|
|
|
|
If no switch is specified, then the address is interpreted using the current
|
|
map if memory expansion is enabled; otherwise, the address is not mapped. If
|
|
the current or specified map is used, then the address must lie within the
|
|
32K logical address space; if not, then an address larger than the current
|
|
memory size is returned to indicate that a translation error occurred.
|
|
*/
|
|
|
|
uint32 meu_map_address (HP_WORD logical, int32 switches)
|
|
{
|
|
MEU_MAP_SELECTOR map;
|
|
|
|
if (switches & (SWMASK ('N') | SIM_SW_REST)) /* if no mapping is requested */
|
|
return logical; /* then the address is already a physical address */
|
|
|
|
else if ((meu_status & MEST_ENABLED) == 0 /* otherwise if the MEM is disabled */
|
|
&& switches & ALL_MAPMODES) /* but a mapping mode was given */
|
|
return D32_UMAX; /* then the command is not allowed */
|
|
|
|
else if ((meu_status & MEST_ENABLED || switches & ALL_MAPMODES) /* otherwise if mapping is enabled or requested */
|
|
&& logical > LA_MAX) /* and the address is not a logical address */
|
|
return mem_size; /* then report a memory overflow */
|
|
|
|
else if (switches & SWMASK ('S')) /* otherwise if the -S switch is specified */
|
|
map = System_Map; /* then use the system map */
|
|
|
|
else if (switches & SWMASK ('U')) /* otherwise if the -U switch is specified */
|
|
map = User_Map; /* then use the user map */
|
|
|
|
else if (switches & SWMASK ('P')) /* otherwise if the -P switch is specified */
|
|
map = Port_A_Map; /* then use the DCPC port A map */
|
|
|
|
else if (switches & SWMASK ('Q')) /* otherwise if the -Q switch is specified */
|
|
map = Port_B_Map; /* then use the DCPC port B map */
|
|
|
|
else /* otherwise */
|
|
map = meu_current_map; /* use the current map (system or user) */
|
|
|
|
return map_address (logical, map, NO_PROTECTION); /* translate the address without protection */
|
|
}
|
|
|
|
|
|
|
|
/* Memory Expansion Unit local SCP support routines */
|
|
|
|
|
|
/* Memory Expansion Unit reset.
|
|
|
|
The MEM processes POPIO but is not addressed by a select code and so does not
|
|
have an I/O interface. Therefore, we handle POPIO here.
|
|
*/
|
|
|
|
static t_stat meu_reset (DEVICE *dptr)
|
|
{
|
|
meu_current_map = System_Map; /* enable the system map */
|
|
|
|
meu_status = 0; /* disable MEM and clear the status register */
|
|
meu_violation = 0; /* clear the violation register */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
/* Memory Expansion Unit local utility routines */
|
|
|
|
|
|
/* Process a MEM violation.
|
|
|
|
A MEM violation will report the cause in the violation register. This occurs
|
|
even if the MEM is not in the protected mode (i.e., MP is not enabled). If
|
|
MP is enabled, an MP abort is taken with the MEV flip-flop set. Otherwise,
|
|
we return to the caller.
|
|
*/
|
|
|
|
static void dm_violation (HP_WORD violation)
|
|
{
|
|
meu_violation = violation | meu_update_violation (); /* set the cause in the violation register */
|
|
|
|
if (mp_control) { /* if memory protect is on */
|
|
mp_mem_changed = TRUE; /* then set the MP/MEM registers changed flag */
|
|
|
|
mp_mevff = SET; /* record a memory expansion violation */
|
|
mp_violation (); /* and a memory protect violation */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Determine whether an address is mapped.
|
|
|
|
This routine determines whether a logical address is mapped to a physical
|
|
address or represents a physical address itself. It corresponds to the
|
|
hardware MEBEN (Memory Expansion Bus Enable) signal and indicates that a
|
|
memory access is not in the unmapped portion of the base page. The routine
|
|
is called only if the MEM is enabled and returns TRUE if the address is
|
|
mapped or FALSE if it is unmapped. Before returning, "meu_bus_enabled" is
|
|
set to reflect the mapping state.
|
|
*/
|
|
|
|
static t_bool is_mapped (HP_WORD address)
|
|
{
|
|
HP_WORD dms_fence;
|
|
|
|
if (address <= 1) /* if the reference is to the A or B register */
|
|
meu_bus_enabled = FALSE; /* then the location is not mapped */
|
|
|
|
else if (address <= LWA_BASE_PAGE) { /* otherwise if the address is on the base page */
|
|
dms_fence = meu_status & MEST_FENCE_MASK; /* then get the base-page fence value */
|
|
|
|
if (meu_status & MEST_BELOW) /* if the lower portion is mapped */
|
|
meu_bus_enabled = (address < dms_fence); /* then mapping occurs if the address is below the fence */
|
|
else /* otherwise the upper portion is mapped */
|
|
meu_bus_enabled = (address >= dms_fence); /* so mapping occurs if the address is at or above the fence */
|
|
}
|
|
|
|
else /* otherwise the address is not on page 0 */
|
|
meu_bus_enabled = TRUE; /* so it is always mapped */
|
|
|
|
return meu_bus_enabled; /* return the mapping state */
|
|
}
|
|
|
|
|
|
/* Map a logical address to a physical address.
|
|
|
|
This routine translates logical to physical addresses. The logical address,
|
|
desired map, and desired access protection are supplied. If the access is
|
|
legal, the mapped physical address is returned; if it is not, then a MEM
|
|
violation occurs.
|
|
|
|
The current map may be specified by passing "meu_current_map" as the "map"
|
|
parameter, or a specific map may be used. Normally, read and write accesses
|
|
pass READ_PROTECTED or WRITE_PROTECTED, respectively, as the "protection"
|
|
parameter to request access checking. For DCPC accesses, NO_PROTECTION must
|
|
be passed to inhibit access checks.
|
|
|
|
This routine checks for read, write, and base-page violations and will call
|
|
"dm_violation" as appropriate. The latter routine will abort if MP is
|
|
enabled, or will return if protection is off.
|
|
*/
|
|
|
|
static uint32 map_address (HP_WORD address, MEU_MAP_SELECTOR map, HP_WORD protection)
|
|
{
|
|
uint32 map_register;
|
|
|
|
if (meu_status & MEST_ENABLED) { /* if the Memory Expansion Unit is enabled */
|
|
meu_indicator = map_indicator [map]; /* then set the map indicator to the applied map */
|
|
|
|
if (address > LWA_BASE_PAGE /* if the address is not on the base page */
|
|
|| map >= Port_A_Map /* or this is a DCPC transfer */
|
|
|| is_mapped (address)) { /* or it is to the mapped portion of the base page */
|
|
map_register = meu_maps [map] [PAGE (address)]; /* then get the map register for the logical page */
|
|
|
|
meu_page = MAP_PAGE (map_register); /* save the physical page number */
|
|
|
|
if (map_register & protection) /* if the desired access is not allowed */
|
|
dm_violation (protection); /* then a read or write protection violation occurs */
|
|
|
|
return TO_PA (meu_page, address); /* form the physical address from the mapped page and offset */
|
|
}
|
|
|
|
else { /* otherwise the address is unmapped */
|
|
meu_page = 0; /* so the physical page is page 0 */
|
|
|
|
if (address > 1 && protection == WRITE_PROTECTED) /* a write to the unmapped part of the base page */
|
|
dm_violation (MEVI_BASE_PAGE); /* causes a base-page violation if protection is enabled */
|
|
|
|
return address; /* the address is already physical */
|
|
}
|
|
}
|
|
|
|
else { /* otherwise the MEU is disabled */
|
|
meu_page = PAGE (address); /* so the physical page is the logical page */
|
|
meu_indicator = '-'; /* and no mapping occurs */
|
|
|
|
return address; /* the physical address is the logical address */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Memory Protect I/O interface routine */
|
|
|
|
|
|
/* Memory Protect/Parity Error interface (select code 05).
|
|
|
|
I/O operations directed to select code 5 manipulate the Memory Protect
|
|
accessory. They also affect main memory parity error and memory expansion
|
|
violation reporting.
|
|
|
|
STC turns on memory protect, which is turned off only by an MP violation or a
|
|
POPIO. CLC does nothing. STF and CLF turn parity error interrupts on and
|
|
off. SFS skips if a MEM violation occurred, while SFC skips if an MP
|
|
violation occurred. IOI reads the MP violation register; bit 15 of the
|
|
register is 1 for a parity error and 0 for an MP error. IOO outputs the
|
|
address of the start of unprotected memory to the MP fence. PRL and IRQ are
|
|
a function of the MP flag flip-flop only, not the flag and control flip-flops
|
|
as is usual.
|
|
|
|
IAK is asserted when any interrupt is acknowledged by the CPU. Normally, an
|
|
interface qualifies IAK with its own IRQ to ensure that it responds only to
|
|
an acknowledgement of its own request. The MP card does this to reset its
|
|
flag buffer and flag flip-flops, and to reset the parity error indication.
|
|
However, it also responds to an unqualified IAK (i.e., for any interface) by
|
|
clearing the MPV flip-flop, clearing the indirect counter, clearing the
|
|
control flip-flop, and setting the INTPT flip-flop.
|
|
|
|
The hardware INTPT flip-flop indicates an occurrence of an interrupt. If the
|
|
trap cell of the interrupting device contains an I/O instruction that is not
|
|
a HLT, action equivalent to STC 05 is taken, i.e., the interface sets the
|
|
control and EVR (Enable Violation Register) flip-flops and clears the MEV
|
|
(Memory Expansion Violation) and PARERR (Parity Error) flip-flops.
|
|
|
|
In simulation, this is handled during IAK processing by setting "mp_enabled"
|
|
to the state of the MP control flip-flop and scheduling the MP event service
|
|
routine to enter after the next instruction. If the next instruction, which
|
|
is the trap cell instruction, is an I/O instruction, "cpu_iog" will call
|
|
"mp_check_io" as part of its processing. If that routine is called for a
|
|
non-HLT instruction, it sets "mp_reenable" to the value saved in
|
|
"mp_enabled", i.e., "mp_reenable" will be SET if MP was enabled when the
|
|
interrupt occurred (it's initialized to CLEAR). When the service routine is
|
|
entered after the trap instruction executes, it sets "mp_control" to the
|
|
value of "mp_reenable", which reenables MP if MP was on.
|
|
|
|
The effect of all of this is to turn MP off when an interrupt occurs but then
|
|
to reenable it if the interrupt trap cell contained a non-HLT I/O
|
|
instruction. For example, consider a program executing with MP on and an
|
|
interrupt from an interface whose trap cell contains a CLF instruction. When
|
|
the interrupt occurs, MP is turned off, the CLF is executed, MP is turned on,
|
|
and the program continues. If the trap cell contained a HLT, MP would be
|
|
turned off, and then the CPU would halt. If the trap cell contained a JSB,
|
|
MP would be turned off and would remain off while the interrupt subroutine
|
|
executes.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Because the MP card uses IAK unqualified, this routine is called whenever
|
|
any interrupt occurs. It is also called when the MP card itself is
|
|
interrupting. The latter condition is detected by the MP flag flip-flop
|
|
being set. As MP has higher priority than all devices except power fail,
|
|
if the flag is set, the IAK must be for the MP card.
|
|
|
|
2. The MEV flip-flop records memory expansion violations. It is set when a
|
|
MEM violation is encountered and can be tested via SFC/SFS.
|
|
|
|
3. The Parity Error logic is not currently implemented.
|
|
*/
|
|
|
|
static SIGNALS_VALUE mp_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
|
|
{
|
|
INBOUND_SIGNAL signal;
|
|
INBOUND_SET working_set = inbound_signals;
|
|
SIGNALS_VALUE outbound = { ioNONE, 0 };
|
|
t_bool irq_enabled = FALSE;
|
|
|
|
while (working_set) { /* while signals remain */
|
|
signal = IONEXTSIG (working_set); /* isolate the next signal */
|
|
|
|
switch (signal) { /* dispatch the I/O signal */
|
|
|
|
case ioCLF: /* Clear Flag flip-flop */
|
|
break; /* turn parity error interrupts off */
|
|
|
|
|
|
case ioSTF: /* Set Flag flip-flop */
|
|
break; /* turn parity error interrupts on */
|
|
|
|
|
|
case ioENF: /* Enable Flag */
|
|
if (mp_flag_buffer == SET) /* if the flag buffer flip-flop is set */
|
|
if (inbound_signals & ioIEN) { /* then if the interrupt system is on */
|
|
mp_flag = SET; /* then set the flag flip-flop */
|
|
mp_evrff = CLEAR; /* and inhibit violation register updates */
|
|
}
|
|
|
|
else /* otherwise interrupts are off */
|
|
mp_flag_buffer = CLEAR; /* and the flag buffer does not set if IEN5 is denied */
|
|
break;
|
|
|
|
|
|
case ioSFC: /* Skip if Flag is Clear */
|
|
if (mp_mevff == CLEAR) /* if this is a memory protect violation */
|
|
outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */
|
|
break;
|
|
|
|
|
|
case ioSFS: /* Skip if Flag is Set */
|
|
if (mp_mevff == SET) /* if this is a memory expansion violation */
|
|
outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */
|
|
break;
|
|
|
|
|
|
case ioIOI: /* I/O Data Input */
|
|
outbound.value = mp_VR; /* return the MP violation register */
|
|
break;
|
|
|
|
|
|
case ioIOO: /* I/O Data Output */
|
|
mp_fence = inbound_value & LA_MASK; /* store the address in the MP fence register */
|
|
|
|
if (cpu_configuration & CPU_2100) /* the 2100 IOP instructions */
|
|
SPR = mp_fence; /* use the MP fence as a stack pointer */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
break;
|
|
|
|
|
|
case ioPOPIO: /* Power-On Preset to I/O */
|
|
mp_control = CLEAR; /* clear the control flip-flop */
|
|
mp_flag_buffer = CLEAR; /* and the flag buffer flip-flop */
|
|
mp_flag = CLEAR; /* and the flag flip-flop */
|
|
|
|
mp_mevff = CLEAR; /* clear the Memory Expansion Violation flip-flop */
|
|
mp_evrff = SET; /* and set the Enable Violation Fegister flip-flop */
|
|
|
|
mp_reenable = CLEAR; /* clear the MP reenable */
|
|
mp_enabled = CLEAR; /* and MP currently enabled flip-flops */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
break;
|
|
|
|
|
|
case ioSTC: /* Set Control flip-flop */
|
|
mp_control = SET; /* set the control flip-flop to turn on MP */
|
|
|
|
mp_mevff = CLEAR; /* clear the Memory Expansion Violation flip-flop */
|
|
mp_evrff = SET; /* and set the Enable Violation Register flip-flop */
|
|
break;
|
|
|
|
|
|
case ioSIR: /* Set Interrupt Request */
|
|
if (mp_flag) /* if the flag flip-flop is set */
|
|
outbound.signals |= cnIRQ | cnVALID; /* then deny PRL and conditionally assert IRQ */
|
|
else /* otherwise */
|
|
outbound.signals |= cnPRL | cnVALID; /* conditionally assert PRL */
|
|
break;
|
|
|
|
|
|
case ioIAK: /* Interrupt Acknowledge */
|
|
if (mp_flag) { /* if the MP itself is interrupting */
|
|
mp_flag_buffer = CLEAR; /* then clear the flag buffer */
|
|
mp_flag = CLEAR; /* and flag flip-flops */
|
|
}
|
|
|
|
mp_enabled = mp_control; /* set the MP interrupt flip-flop if MP was on */
|
|
mp_control = CLEAR; /* and then turn memory protection off */
|
|
|
|
sim_activate (mp_unit, mp_unit [0].wait); /* schedule a status check for the next instruction */
|
|
break;
|
|
|
|
|
|
case ioIEN: /* Interrupt Enable */
|
|
irq_enabled = TRUE; /* permit IRQ to be asserted */
|
|
break;
|
|
|
|
|
|
case ioPRH: /* Priority High */
|
|
if (irq_enabled && outbound.signals & cnIRQ) /* if IRQ is enabled and conditionally asserted */
|
|
outbound.signals |= ioIRQ | ioFLG; /* then assert IRQ and FLG */
|
|
|
|
if (!irq_enabled || outbound.signals & cnPRL) /* if IRQ is disabled or PRL is conditionally asserted */
|
|
outbound.signals |= ioPRL; /* then assert it unconditionally */
|
|
break;
|
|
|
|
|
|
case ioCRS: /* not used by this interface */
|
|
case ioCLC: /* not used by this interface */
|
|
case ioEDT: /* not used by this interface */
|
|
case ioPON: /* not used by this interface */
|
|
break;
|
|
}
|
|
|
|
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
|
|
} /* and continue until all signals are processed */
|
|
|
|
return outbound; /* return the outbound signals and value */
|
|
}
|
|
|
|
|
|
|
|
/* Memory Protect global utility routines */
|
|
|
|
|
|
/* Initialize memory protect.
|
|
|
|
This routine is called from the instruction execution prelude to set up the
|
|
internal state of the memory protect accessory. It returns the state of the
|
|
MP device (enabled or disabled) to avoid having to make the DEVICE structure
|
|
global.
|
|
*/
|
|
|
|
t_bool mp_initialize (void)
|
|
{
|
|
is_1000 = (cpu_configuration & CPU_1000) != 0; /* set the CPU model index */
|
|
|
|
mp_mem_changed = TRUE; /* request an initial MP/MEM trace */
|
|
|
|
return (mp_dev.flags & DEV_DIS) == 0; /* return TRUE if MP is enabled and FALSE if not */
|
|
}
|
|
|
|
|
|
/* Configure the Memory Protect accessory.
|
|
|
|
This routine enables or disables MP, depending on the "is_enabled" parameter,
|
|
and makes the MP configurable or non-configurable, depending on the
|
|
"is_optional" parameter. It adds or removes the DEV_DIS device flag to
|
|
disable or enable the device, and adds or removes the DEV_DISABLE device flag
|
|
to allow or deny the use of SET MP ENABLED/DISABLED SCP commands to change
|
|
the device state.
|
|
*/
|
|
|
|
void mp_configure (t_bool is_enabled, t_bool is_optional)
|
|
{
|
|
if (is_enabled) /* if MP is to be enabled */
|
|
mp_dev.flags &= ~DEV_DIS; /* then remove the "disabled" flag */
|
|
else /* otherwise */
|
|
mp_dev.flags |= DEV_DIS; /* add the flag to disable the device */
|
|
|
|
if (is_optional) /* if MP is to be made configurable */
|
|
mp_dev.flags |= DEV_DISABLE; /* then add the "can be disabled" flag */
|
|
else /* otherwise */
|
|
mp_dev.flags &= ~DEV_DISABLE; /* make the current setting unalterable */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Check a jump for memory protect or memory expansion violations.
|
|
|
|
This routine checks a jump target address for protection violations. On
|
|
entry, "address" is the logical address of the jump target, and "lower_bound"
|
|
is the lowest protected memory address. If a violation occurs, the routine
|
|
does not return; instead, a microcode abort is taken.
|
|
|
|
Program execution jumps are a special case of write validation. The target
|
|
address is treated as a write, even when no physical write takes place (e.g.,
|
|
when executing a JMP instead of a JSB), so jumping to a write-protected page
|
|
causes a MEM violation. In addition, a MEM violation occurs if the jump is
|
|
to the unmapped portion of the base page. Finally, jumping to a location
|
|
under the memory-protect fence causes an MP violation.
|
|
|
|
Because the MP and MEM hardware works in parallel, all three violations may
|
|
exist concurrently. For example, a JMP to the unmapped portion of the base
|
|
page that is write protected and under the MP fence will indicate a
|
|
base-page, a write, and an MP violation, whereas a JMP to the mapped portion
|
|
will indicate a write and an MP violation (BPV is inhibited by the MEBEN
|
|
signal). If MEM and MP violations occur concurrently, the MEM violation
|
|
takes precedence, as the SFS and SFC instructions test the MEV flip-flop.
|
|
|
|
The lower bound of protected memory must be either 0 or 2. All violations
|
|
are qualified by the MPCND signal, which responds to the lower bound.
|
|
Therefore, if the lower bound is 2, and if the part below the base-page fence
|
|
is unmapped, or if the base page is write-protected, then a MEM violation
|
|
will occur only if the access is not to locations 0 or 1. The instruction
|
|
set firmware uses a lower bound of 0 for JMP, JLY, and JPY (and for JSB with
|
|
W5 out), and of 2 for DJP, SJP, UJP, JRS, and .GOTO (and JSB with W5 in).
|
|
|
|
Finally, all violations are inhibited if MP is off (i.e., the MP control
|
|
flip-flop is clear), and MEM violations are inhibited if the MEM is disabled.
|
|
*/
|
|
|
|
void mp_check_jmp (HP_WORD address, uint32 lower_bound)
|
|
{
|
|
const uint32 lp = PAGE (address); /* the logical page number */
|
|
HP_WORD violation = 0; /* the MEM violation conditions */
|
|
|
|
if (mp_control) { /* if memory protect is enabled */
|
|
if (meu_status & MEST_ENABLED) { /* then if the MEM is enabled */
|
|
if (meu_maps [meu_current_map] [lp] & WRITE_PROTECTED) /* then if the page is write protected */
|
|
violation = MEVI_WRITE; /* then a write violation occurs */
|
|
|
|
if (address >= lower_bound && ! is_mapped (address)) /* if the unmapped base page is the target */
|
|
violation |= MEVI_BASE_PAGE; /* then a base page violation occurs */
|
|
|
|
if (violation) /* if a violation occurred */
|
|
dm_violation (violation); /* then assert a MEM violation */
|
|
}
|
|
|
|
if (address >= lower_bound && address < mp_fence) /* if the jump is under the memory protect fence */
|
|
mp_violation (); /* then a memory protect violation occurs */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Check a jump-to-subroutine for memory protect or memory expansion violations.
|
|
|
|
This routine checks a jump-to-subroutine target address for protection
|
|
violations. On entry, "address" is the logical address of the jump target.
|
|
If a violation occurs, the routine does not return; instead, a microcode
|
|
abort is taken.
|
|
|
|
The protected lower bound address for the JSB instruction depends on the W5
|
|
jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs to the
|
|
A and B registers. If W5 is out, then the lower bound is 0, just as with
|
|
JMP.
|
|
*/
|
|
|
|
void mp_check_jsb (HP_WORD address)
|
|
{
|
|
mp_check_jmp (address, jsb_bound); /* check the jump target with the selectex bound */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Check an I/O operation for memory protect violations.
|
|
|
|
This routine is called by the IOG instruction executor to verify that an I/O
|
|
instruction is allowed under the current protection settings. On entry,
|
|
"select_code" is set to the select code addressed by the instruction, and
|
|
"micro_op" is the IOG operation to be executed. The routine returns if the
|
|
operation is allowed. Otherwise, an MP abort is performed.
|
|
|
|
If MP is off, then all I/O instructions are allowed. MP will be off during
|
|
execution of an IOG instruction in an interrupt trap cell; in this case, MP
|
|
will be reenabled if the instruction is not a HLT and MP was enabled prior to
|
|
the interrupt.
|
|
|
|
If MP is on, then HLT instructions are illegal and will cause a memory
|
|
protect violation. If jumper W7 (SEL1) is in, then all other I/O
|
|
instructions are legal; if W7 is out, then only I/O instructions that address
|
|
select code 1 are legal, and I/O to other select codes will cause a
|
|
violation.
|
|
*/
|
|
|
|
void mp_check_io (uint32 select_code, IO_GROUP_OP micro_op)
|
|
{
|
|
if (mp_control == CLEAR) { /* if memory protect is off */
|
|
if (micro_op != iog_HLT && micro_op != iog_HLT_C) /* then if the instruction is not a HLT */
|
|
mp_reenable = mp_enabled; /* then set up to reenable if servicing an interrupt */
|
|
}
|
|
|
|
else if (micro_op == iog_HLT || micro_op == iog_HLT_C /* otherwise if checking a HLT instruction */
|
|
|| select_code != OVF && (mp_unit [0].flags & UNIT_MP_SEL1)) /* or SC is not 1 and the SEL1 jumper is out */
|
|
mp_violation (); /* then a memory protect violation occurs */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Process a memory protect violation.
|
|
|
|
If memory protect is on, this routine updates the MEM violation register (if
|
|
this is an MP and not a MEM violation), sets the MP flag buffer and flag
|
|
flip-flops (if interupts are enabled), and performs a microcode abort. The
|
|
latter does a "longjmp" back to the microcode abort handler just prior to the
|
|
CPU instruction execution loop.
|
|
|
|
If memory protect is off, MP violations are ignored.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "cpu_microcode_abort" routine is called both for MP and MEM
|
|
violations. The MEV flip-flop will be clear for the former and set for
|
|
the latter. The MEV violation register will be updated by
|
|
"meu_update_violation" only if the call is NOT for an MEM violation; if
|
|
it is, then the register has already been set and should not be
|
|
disturbed.
|
|
*/
|
|
|
|
void mp_violation (void)
|
|
{
|
|
if (mp_control) { /* if memory protect is on */
|
|
meu_update_violation (); /* then update the MEVR (if not a MEV) */
|
|
|
|
mp_flag_buffer = SET; /* set the MP flag buffer flip-flop (if IEN) */
|
|
io_assert (&mp_dev, ioa_ENF); /* and the flag flip-flop (if IEN) */
|
|
|
|
cpu_microcode_abort (Memory_Protect); /* abort the instruction */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Turn memory protect off.
|
|
|
|
This routine is called to disable memory protect. In hardware, MP cannot be
|
|
turned off, except by causing a violation. Microcode typically does this by
|
|
executing an IOG micro-order with a select code not equal to 1, followed by
|
|
an IAK to clear the interrupt, and a FTCH to clear the INTPT flip-flop.
|
|
Under simulation, clearing the MP control flip-flop produces the same effect.
|
|
|
|
This routine also cancels any scheduled MP event service, in case it's called
|
|
during execution of a microcoded trap cell instruction.
|
|
*/
|
|
|
|
void mp_disable (void)
|
|
{
|
|
mp_control = CLEAR; /* clear the control flip-flop to turn MP off */
|
|
|
|
mp_reenable = CLEAR; /* clear the MP reenable */
|
|
mp_enabled = CLEAR; /* and MP currently enabled flip-flops */
|
|
|
|
sim_cancel (mp_unit); /* cancel any pending MP reenable */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Report the memory protect state.
|
|
|
|
This routine returns TRUE if MP is on and FALSE otherwise. It is used by the
|
|
RTE OS microcode executors to check the protection state. In hardware, this
|
|
is done by reading the MEM status register and checking the protected mode
|
|
bit (bit 11). In simulation, the MP control flip-flop is checked, as the
|
|
MEM status register is not global.
|
|
*/
|
|
|
|
t_bool mp_is_on (void)
|
|
{
|
|
return (mp_control == SET); /* return TRUE if MP is on and FALSE if it is off */
|
|
}
|
|
|
|
|
|
/* Report the INT (W6) jumper position.
|
|
|
|
This routine returns TRUE if jumper W6 is not installed and MP is on, and
|
|
FALSE otherwise. It is called when an interrupt is pending but deferred
|
|
because the Interrupt Enable flip-flop is clear. If jumper W6 is installed,
|
|
instructions that reference memory will hold off pending but deferred
|
|
interrupts until three levels of indirection have been followed. If W6 is
|
|
removed, then deferred interrupts are recognized immediately if MP is on.
|
|
*/
|
|
|
|
t_bool mp_reenable_interrupts (void)
|
|
{
|
|
return mp_unit [0].flags & UNIT_MP_INT && mp_control; /* return TRUE if interrupts are always recognized */
|
|
}
|
|
|
|
|
|
/* Trace a memory protect violation.
|
|
|
|
This routine is called when CPU operand tracing is enabled and the microcoded
|
|
memory protect trap cell instruction is executed. It reports the reason for
|
|
the interrupt (MP, MEM, or PE violation).
|
|
|
|
The routine returns TRUE for a MP/MEM violation and FALSE for a PE violation.
|
|
This information is used by the instruction microcode.
|
|
*/
|
|
|
|
t_bool mp_trace_violation (void)
|
|
{
|
|
tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " entry is for a %s\n",
|
|
PR, IR,
|
|
(mp_VR & MPVR_PARITY_ERROR
|
|
? "parity error"
|
|
: (mp_mevff == SET
|
|
? "dynamic mapping violation"
|
|
: "memory protect violation")));
|
|
|
|
return (mp_VR & MPVR_PARITY_ERROR) == 0; /* return TRUE for MP, FALSE for PE */
|
|
}
|
|
|
|
|
|
|
|
/* Memory Protect local SCP support routines */
|
|
|
|
|
|
/* Service the memory protect accessory.
|
|
|
|
This routine is scheduled whenever IAK is asserted to the MP interface, and
|
|
the MP card itself is not interrupting. The purpose is to reenable memory
|
|
protection if the interrupt trap cell contains a non-HLT I/O instruction.
|
|
|
|
In hardware, the MP card responds to a "foreign" IAK (i.e., one acknowledging
|
|
another interface's interrupt request) by disabling memory protection while
|
|
the trap cell instruction is executed. If that instruction is a non-HLT IOG
|
|
instruction, MP is automatically reenabled before instruction resumes at the
|
|
point of interruption. Otherwise, MP remains off while the interrupt handler
|
|
executes.
|
|
|
|
In simulation, this is handled during IAK processing by setting "mp_enabled"
|
|
to the state of the MP control flip-flop and scheduling the MP event service
|
|
routine to enter after the next instruction. If the trap cell instruction is
|
|
an I/O instruction, "cpu_iog" will call "mp_check_io" as part of its
|
|
processing. If that routine is called for a non-HLT instruction, it sets
|
|
"mp_reenable" to the value saved in "mp_enabled", i.e., "mp_reenable" will be
|
|
SET if MP was enabled when the interrupt occurred (it's initialized to
|
|
CLEAR). When this routine is entered after the trap instruction executes, it
|
|
sets "mp_control" to the value of "mp_reenable", which reenables MP if MP was
|
|
on.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The two-level setting (mp_enabled -> mp_reenable -> mp_control) is
|
|
necessary to avoid having to clear the reenable flag on every instruction
|
|
execution. Consider if "mp_reenable" is set directly from "mp_control"
|
|
in the IAK processor. The "mp_check_io" routine would clear it if the
|
|
instruction is a HLT. But it would also have to be cleared for all other
|
|
non-IOG instructions, which means inserting a "mp_reenable = CLEAR"
|
|
statement in all other instruction execution paths. With the two-level
|
|
setting, "mp_reenable" is set from "mp_enabled" only in the "mp_check_io"
|
|
routine, and then only if the instruction is not a HLT instruction. This
|
|
saves the delay inherent in clearing "mp_reenable" in the 99.99% of the
|
|
cases where an IAK is not being serviced.
|
|
*/
|
|
|
|
static t_stat mp_service (UNIT *uptr)
|
|
{
|
|
mp_control = mp_reenable; /* reenable MP if a non-HLT I/O instruction was executed */
|
|
|
|
mp_reenable = CLEAR; /* clear the reenable */
|
|
mp_enabled = CLEAR; /* and enabled-at-interrupt flip-flops */
|
|
|
|
if (mp_control) { /* if MP was reenabled */
|
|
mp_mevff = CLEAR; /* then clear the Memory Expansion Violation flip-flop */
|
|
mp_evrff = SET; /* and set the Enable Violation Register flip-flop */
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Set the JSB (W5) jumper mode.
|
|
|
|
This validation routine is entered with the "value" parameter set to zero or
|
|
UNIT_MP_JSB, depending on whether jumper W5 is being installed or removed.
|
|
The unit, character, and descriptor pointers are not used.
|
|
|
|
The protected lower bound address for JSB instruction protection depends on
|
|
the W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs
|
|
to the A and B registers. If W5 is out, then the lower bound is 0, just as
|
|
with JMP.
|
|
*/
|
|
|
|
static t_stat mp_set_jsb (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
if (value == UNIT_MP_JSB) /* if jumper W5 is out */
|
|
jsb_bound = 0; /* then the protected lower bound is address 0 */
|
|
else /* otherwise W5 is installed */
|
|
jsb_bound = 2; /* so the protected bound is address 2 */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Reset memory protect.
|
|
|
|
This routine is called for a RESET, RESET MP, RUN, or BOOT command. It is
|
|
the simulation equivalent of an initial power-on condition (corresponding to
|
|
PON, POPIO, and CRS signal assertion) or a front-panel PRESET button press
|
|
(corresponding to POPIO and CRS assertion). SCP delivers a power-on reset to
|
|
all devices when the simulator is started.
|
|
*/
|
|
|
|
static t_stat mp_reset (DEVICE *dptr)
|
|
{
|
|
io_assert (dptr, ioa_POPIO); /* PRESET the device (does not use PON) */
|
|
|
|
mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */
|
|
|
|
return SCPE_OK;
|
|
}
|