Commit | Line | Data |
---|---|---|
196ba1fc PH |
1 | /* ibm1130_cpu.c: IBM 1130 CPU simulator\r |
2 | \r | |
3 | Based on the SIMH package written by Robert M Supnik\r | |
4 | \r | |
5 | * (C) Copyright 2002, Brian Knittel.\r | |
6 | * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN\r | |
7 | * RISK basis, there is no warranty of fitness for any purpose, and the rest of the\r | |
8 | * usual yada-yada. Please keep this notice and the copyright in any distributions\r | |
9 | * or modifications.\r | |
10 | *\r | |
11 | * This is not a supported product, but I welcome bug reports and fixes.\r | |
12 | * Mail to simh@ibm1130.org\r | |
13 | \r | |
14 | 25-Jun-01 BLK Written\r | |
15 | 10-May-02 BLK Fixed bug in MDX instruction\r | |
16 | 27-Mar-02 BLK Made BOSC work even in short form\r | |
17 | 16-Aug-02 BLK Fixed bug in multiply instruction; didn't work with negative values\r | |
18 | 18-Mar-03 BLK Fixed bug in divide instruction; didn't work with negative values\r | |
19 | 23-Jul-03 BLK Prevented tti polling in CGI mode\r | |
20 | 24-Nov-03 BLK Fixed carry bit error in subtract and subtract double, found by Bob Flanders\r | |
21 | 20-Oct-04 BLK Changed "(unsigned int32)" to "(uint32)" to accomodate improved definitions of simh types\r | |
22 | Also commented out my echo command as it's now a standard simh command\r | |
23 | 27-Nov-05 BLK Added Arithmetic Factor Register support per Carl Claunch (GUI only)\r | |
24 | 06-Dec-06 BLK Moved CGI stuff out of ibm1130_cpu.c\r | |
25 | \r | |
26 | >> To do: verify actual operands stored in ARF, need to get this from state diagrams in the schematic set\r | |
27 | Also: determine how many bits are actually stored in the IAR in a real 1130, by forcing wraparound\r | |
28 | and storing the IAR.\r | |
29 | \r | |
30 | IBM 1800 support is just beginning. Mode set is done (SET CPU 1800 or SET CPU 1130).\r | |
31 | Index registers are handled (1800 has real registers, 1130 uses core locations 1, 2 and 3 -- \r | |
32 | but does the 1800 make its hardware index registers appear in the address space?)\r | |
33 | Need to add: memory protect feature, more interrupt levels, GUI mods, IO device mods, timers, watchdog.\r | |
34 | Memory protect was interesting -- they borrowed one of the two parity bits. XIO(0) on 1800 is used for\r | |
35 | interval timers, console data switches, console sense/program select/CE switches, interrupt mask register,\r | |
36 | programmed interrupt, console interrupt and operations monitor (watchdog)\r | |
37 | very interesting stuff.\r | |
38 | \r | |
39 | The register state for the IBM 1130 CPU is:\r | |
40 | \r | |
41 | IAR instruction address register\r | |
42 | ACC accumulator\r | |
43 | EXT accumulator extension\r | |
44 | Oflow overflow bit\r | |
45 | Carry carry bit\r | |
46 | CES console entry switches\r | |
47 | ipl current interrupt level, -1 = non interrupt\r | |
48 | iplpending bitmap of pending interrupts\r | |
49 | wait_state current CPU state: running or waiting\r | |
50 | DSW console run/stop switch device status word\r | |
51 | RUNMODE processor step/run mode (may also imply IntRun)\r | |
52 | BREAK breakpoint address\r | |
53 | WRU simulator-break character\r | |
54 | IntRun Int Run flag (causes level 5 interrupt after every instruction)\r | |
55 | ILSW0..5 interrupt level status words\r | |
56 | XR1, 2, 3 for IBM 1800 only, index registers 1, 2, and 3\r | |
57 | \r | |
58 | The SAR (storage address register) and SBR (storage buffer register) are updated\r | |
59 | but not saved in the CPU state; they matter only to the GUI.\r | |
60 | \r | |
61 | Interrupt handling: interrupts occur when any device on any level has an\r | |
62 | active interrupt. XIO commands can clear specific IRQ bits. When this\r | |
63 | happens, we have to evaluate all devices on the same IRQ level for remaining\r | |
64 | indicators. The flag int_req is set with a bit corresponding to the IRQ level\r | |
65 | when any interrupt indicator is activated.\r | |
66 | \r | |
67 | The 1130 console has a switch that controls several run modes: SS (single processor\r | |
68 | step), SCLK (single clock step), SINST (single instruction step), INT_RUN\r | |
69 | (IRQ 5 after each non interrupt-handler instruction) and RUN (normal operation).\r | |
70 | This simulator does not implement SS and SCLK. The simulator GUI console handles\r | |
71 | SINST, so we only have to worry about INT_RUN. The console command SET CPU IntRun sets\r | |
72 | the tmode (trace mode) flag; this causes a level 5 interrupt after each\r | |
73 | instruction.\r | |
74 | \r | |
75 | The IBM 1130 instruction formats are\r | |
76 | \r | |
77 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ \r | |
78 | | opcode | F| T | | general format\r | |
79 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
80 | \r | |
81 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
82 | | opcode | 0| T | DISPLACEMENT | short instruction\r | |
83 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
84 | \r | |
85 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
86 | | opcode | 1| T | I| MODIFIER | long instruction\r | |
87 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
88 | | ADDRESS | \r | |
89 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r | |
90 | \r | |
91 | opcode in MSBits\r | |
92 | \r | |
93 | F = format. 0 = short (1 word), 1 = long (2 word) instruction\r | |
94 | \r | |
95 | T = Tag 00 = no index register (e.g. IAR relative)\r | |
96 | 01 = use index register 1 (e.g. core address 1 = M[1])\r | |
97 | 02 = use index register 2 (e.g. core address 2 = M[2])\r | |
98 | 03 = use index register 3 (e.g. core address 3 = M[3])\r | |
99 | \r | |
100 | DISPLACEMENT = two's complement (must be sign-extended)\r | |
101 | \r | |
102 | I = Indirect\r | |
103 | \r | |
104 | Note that IAR = instruction address+1 when instruction is being decoded.\r | |
105 | \r | |
106 | In normal addressing mode, effective address (EA) is computed as follows:\r | |
107 | \r | |
108 | F = 0 T = 0 EA = IAR + DISPLACEMENT\r | |
109 | 0 1 IAR + DISPLACEMENT + M[1] \r | |
110 | 0 2 IAR + DISPLACEMENT + M[2]\r | |
111 | 0 3 IAR + DISPLACEMENT + M[3]\r | |
112 | \r | |
113 | F = 1 T = 0 I = 0 EA = ADDRESS\r | |
114 | 1 1 0 ADDRESS + M[1]\r | |
115 | 1 2 0 ADDRESS + M[2]\r | |
116 | 1 3 0 ADDRESS + M[3]\r | |
117 | 1 0 1 M[ADDRESS]\r | |
118 | 1 1 1 M[ADDRESS + M[1]]\r | |
119 | 1 2 1 M[ADDRESS + M[2]]\r | |
120 | 1 3 1 M[ADDRESS + M[3]]\r | |
121 | \r | |
122 | Loads or stores are then made to/from MEM[EA]. Some instructions have special\r | |
123 | weird addressing modes. Simulator code precomputes standard addressing for\r | |
124 | all instructions though it's not always used.\r | |
125 | \r | |
126 | General notes:\r | |
127 | \r | |
128 | Adding I/O devices requires modifications to three modules:\r | |
129 | \r | |
130 | ibm1130_defs.h add interrupt request definitions\r | |
131 | ibm1130_cpu.c add XIO command linkages\r | |
132 | ibm1130_sys.c add to sim_devices array\r | |
133 | */\r | |
134 | \r | |
135 | /* ------------------------------------------------------------------------\r | |
136 | * Definitions\r | |
137 | * ------------------------------------------------------------------------ */\r | |
138 | \r | |
139 | #include <stdarg.h>\r | |
140 | \r | |
141 | #include "ibm1130_defs.h"\r | |
142 | \r | |
143 | #define save_ibkpt (cpu_unit.u3) /* will be SAVEd */\r | |
144 | \r | |
145 | #define UPDATE_BY_TIMER\r | |
146 | #define ENABLE_BACKTRACE\r | |
147 | /* #define USE_MY_ECHO_CMD */ /* simh now has echo command built in */\r | |
148 | #define ENABLE_1800_SUPPORT /* define to enable support for 1800 CPU simulation mode */\r | |
149 | \r | |
150 | static void cgi_start(void);\r | |
151 | static void cgi_stop(t_stat reason);\r | |
152 | static int simh_status_to_stopcode (int status);\r | |
153 | \r | |
154 | /* hook pointers from scp.c */\r | |
155 | void (*sim_vm_init) (void) = &sim_init;\r | |
156 | extern char* (*sim_vm_read) (char *ptr, int32 size, FILE *stream);\r | |
157 | extern void (*sim_vm_post) (t_bool from_scp);\r | |
158 | extern CTAB *sim_vm_cmd;\r | |
159 | \r | |
160 | /* space to store extra simulator-specific commands */\r | |
161 | #define MAX_EXTRA_COMMANDS 10\r | |
162 | CTAB x_cmds[MAX_EXTRA_COMMANDS];\r | |
163 | \r | |
164 | #ifdef _WIN32\r | |
165 | # define CRLF "\r\n"\r | |
166 | #else\r | |
167 | # define CRLF "\n"\r | |
168 | #endif\r | |
169 | \r | |
170 | /* ------------------------------------------------------------------------\r | |
171 | * initializers for globals\r | |
172 | * ------------------------------------------------------------------------ */\r | |
173 | \r | |
174 | #define SIGN_BIT(v) ((v) & 0x8000)\r | |
175 | #define DWSIGN_BIT(v) ((v) & 0x80000000)\r | |
176 | \r | |
177 | uint16 M[MAXMEMSIZE]; /* core memory, up to 32Kwords (note: don't even think about trying 64K) */\r | |
178 | uint16 ILSW[6] = {0,0,0,0,0,0}; /* interrupt level status words */\r | |
179 | uint16 XR[3] = {0,0,0}; /* IBM 1800 index registers */\r | |
180 | int32 IAR; /* instruction address register */\r | |
181 | int32 prev_IAR; /* instruction address register at start of current instruction */\r | |
182 | int32 SAR, SBR; /* storage address/buffer registers */\r | |
183 | int32 OP, TAG, CCC; /* instruction decoded pieces */\r | |
184 | int32 CES; /* console entry switches */\r | |
185 | int32 ACC, EXT; /* accumulator and extension */\r | |
186 | int32 ARF; /* arithmetic factor, a non-addressable internal CPU register */\r | |
187 | int32 RUNMODE; /* processor run/step mode */\r | |
188 | int32 ipl = -1; /* current interrupt level (-1 = not handling irq) */\r | |
189 | int32 iplpending = 0; /* interrupted IPL's */\r | |
190 | int32 tbit = 0; /* trace flag (causes level 5 IRQ after each instr) */\r | |
191 | int32 V = 0, C = 0; /* condition codes */\r | |
192 | int32 wait_state = 0; /* wait state (waiting for an IRQ) */\r | |
193 | int32 wait_lamp = TRUE; /* alternate indicator to light the wait lamp on the GUI */\r | |
194 | int32 int_req = 0; /* sum of interrupt request levels active */\r | |
195 | int32 int_lamps = 0; /* accumulated version of int_req - gives lamp persistence */\r | |
196 | int32 int_mask; /* current active interrupt mask (ipl sensitive) */\r | |
197 | int32 mem_mask; /* mask for memory address bits based on current memory size */\r | |
198 | int32 cpu_dsw = 0; /* CPU device status word */\r | |
199 | int32 ibkpt_addr = -1; /* breakpoint addr */\r | |
200 | int32 sim_gui = TRUE; /* enable gui */\r | |
201 | t_bool running = FALSE; /* TRUE if CPU is running */\r | |
202 | t_bool power = TRUE; /* TRUE if CPU power is on */\r | |
203 | t_bool cgi = FALSE; /* TRUE if we are running as a CGI program */\r | |
204 | t_bool cgiwritable = FALSE; /* TRUE if we can write the disk images back to the image file in CGI mode */\r | |
205 | t_bool is_1800 = FALSE; /* TRUE if we are simulating an IBM 1800 processor */\r | |
206 | t_stat reason; /* CPU execution loop control */\r | |
207 | \r | |
208 | static int32 int_masks[6] = {\r | |
209 | 0x00, 0x20, 0x30, 0x38, 0x3C, 0x3E /* IPL 0 is highest prio (sees no other interrupts) */\r | |
210 | };\r | |
211 | \r | |
212 | /* ------------------------------------------------------------------------\r | |
213 | * Function declarations\r | |
214 | * ------------------------------------------------------------------------ */\r | |
215 | \r | |
216 | t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw);\r | |
217 | t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw);\r | |
218 | t_stat cpu_reset (DEVICE *dptr);\r | |
219 | t_stat cpu_svc (UNIT *uptr);\r | |
220 | t_stat cpu_set_size (UNIT *uptr, int32 value, char *cptr, void *desc);\r | |
221 | t_stat cpu_set_type (UNIT *uptr, int32 value, char *cptr, void *desc);\r | |
222 | void calc_ints (void);\r | |
223 | \r | |
224 | extern t_stat ts_wr (int32 data, int32 addr, int32 access);\r | |
225 | extern t_stat detach_cmd (int flags, char *cptr);\r | |
226 | extern UNIT cr_unit;\r | |
227 | extern int32 sim_switches;\r | |
228 | \r | |
229 | #ifdef ENABLE_BACKTRACE\r | |
230 | static void archive_backtrace(char *inst);\r | |
231 | static void reset_backtrace (void);\r | |
232 | static void show_backtrace (int nshow);\r | |
233 | static t_stat backtrace_cmd (int flag, char *cptr);\r | |
234 | #else\r | |
235 | #define archive_backtrace(inst)\r | |
236 | #define reset_backtrace()\r | |
237 | #define show_backtrace(ntrace)\r | |
238 | #endif\r | |
239 | \r | |
240 | #ifdef GUI_SUPPORT\r | |
241 | # define ARFSET(v) ARF = (v) & 0xFFFF /* set Arithmetic Factor Register (used for display purposes only) */\r | |
242 | #else\r | |
243 | # define ARFSET(v) /* without GUI, no need for setting ARF */\r | |
244 | #endif\r | |
245 | \r | |
246 | static void init_console_window (void);\r | |
247 | static void destroy_console_window (void);\r | |
248 | static t_stat view_cmd (int flag, char *cptr);\r | |
249 | static t_stat cpu_attach (UNIT *uptr, char *cptr);\r | |
250 | static t_bool bsctest (int32 DSPLC, t_bool reset_V);\r | |
251 | static void exit_irq (void);\r | |
252 | static void trace_instruction (void);\r | |
253 | \r | |
254 | /* ------------------------------------------------------------------------\r | |
255 | * CPU data structures:\r | |
256 | * cpu_dev CPU device descriptor\r | |
257 | * cpu_unit CPU unit descriptor\r | |
258 | * cpu_reg CPU register list\r | |
259 | * cpu_mod CPU modifier list\r | |
260 | *\r | |
261 | * The CPU is attachable; attaching a file to it write a log of instructions\r | |
262 | * and registers\r | |
263 | * ------------------------------------------------------------------------ */\r | |
264 | \r | |
265 | #define UNIT_MSIZE (1 << (UNIT_V_UF + 7)) /* flag for memory size setting */\r | |
266 | #define UNIT_1800 (1 << (UNIT_V_UF + 0)) /* flag for 1800 mode */\r | |
267 | \r | |
268 | UNIT cpu_unit = { UDATA (&cpu_svc, UNIT_FIX | UNIT_BINK | UNIT_ATTABLE | UNIT_SEQ, INIMEMSIZE) };\r | |
269 | \r | |
270 | REG cpu_reg[] = {\r | |
271 | { HRDATA (IAR, IAR, 32) },\r | |
272 | { HRDATA (ACC, ACC, 32) },\r | |
273 | { HRDATA (EXT, EXT, 32) },\r | |
274 | { FLDATA (Oflow, V, 1) },\r | |
275 | { FLDATA (Carry, C, 1) },\r | |
276 | { HRDATA (CES, CES, 32) },\r | |
277 | { HRDATA (ipl, ipl, 32), REG_RO },\r | |
278 | { HRDATA (iplpending, iplpending, 32), REG_RO },\r | |
279 | { HRDATA (wait_state, wait_state, 32)},\r | |
280 | { HRDATA (DSW, cpu_dsw, 32), REG_RO },\r | |
281 | { HRDATA (RUNMODE, RUNMODE, 32) },\r | |
282 | { HRDATA (BREAK, ibkpt_addr, 32) },\r | |
283 | { ORDATA (WRU, sim_int_char, 8) },\r | |
284 | { FLDATA (IntRun, tbit, 1) },\r | |
285 | \r | |
286 | { HRDATA (ILSW0, ILSW[0], 32), REG_RO },\r | |
287 | { HRDATA (ILSW1, ILSW[1], 32), REG_RO },\r | |
288 | { HRDATA (ILSW2, ILSW[2], 32), REG_RO },\r | |
289 | { HRDATA (ILSW3, ILSW[3], 32), REG_RO },\r | |
290 | { HRDATA (ILSW4, ILSW[4], 32), REG_RO },\r | |
291 | { HRDATA (ILSW5, ILSW[5], 32), REG_RO },\r | |
292 | \r | |
293 | #ifdef ENABLE_1800_SUPPORT\r | |
294 | { HRDATA (IS_1800, is_1800, 32), REG_RO|REG_HIDDEN}, /* is_1800 flag is part of state, but hidden */\r | |
295 | { HRDATA (XR1, XR[0], 16), REG_RO|REG_HIDDEN}, /* index registers are unhidden if CPU set to 1800 mode */\r | |
296 | { HRDATA (XR2, XR[1], 16), REG_RO|REG_HIDDEN},\r | |
297 | { HRDATA (XR3, XR[2], 16), REG_RO|REG_HIDDEN},\r | |
298 | #endif\r | |
299 | \r | |
300 | { HRDATA (ARF, ARF, 32) },\r | |
301 | { NULL}\r | |
302 | };\r | |
303 | \r | |
304 | MTAB cpu_mod[] = {\r | |
305 | { UNIT_MSIZE, 4096, NULL, "4KW", &cpu_set_size},\r | |
306 | { UNIT_MSIZE, 8192, NULL, "8KW", &cpu_set_size},\r | |
307 | { UNIT_MSIZE, 16384, NULL, "16KW", &cpu_set_size},\r | |
308 | { UNIT_MSIZE, 32768, NULL, "32KW", &cpu_set_size},\r | |
309 | #ifdef ENABLE_1800_SUPPORT\r | |
310 | { UNIT_1800, 0, "1130", "1130", &cpu_set_type},\r | |
311 | { UNIT_1800, UNIT_1800, "1800", "1800", &cpu_set_type},\r | |
312 | #endif\r | |
313 | { 0 } };\r | |
314 | \r | |
315 | DEVICE cpu_dev = {\r | |
316 | "CPU", &cpu_unit, cpu_reg, cpu_mod,\r | |
317 | 1, 16, 16, 1, 16, 16,\r | |
318 | &cpu_ex, &cpu_dep, &cpu_reset,\r | |
319 | NULL, cpu_attach, NULL}; /* attaching to CPU creates cpu log file */\r | |
320 | \r | |
321 | /* ------------------------------------------------------------------------ \r | |
322 | * Memory read/write -- save SAR and SBR on the way in and out\r | |
323 | *\r | |
324 | * (It can be helpful to set breakpoints on a = 1, 2, or 3 in these routines\r | |
325 | * to detect attempts to read/set index registers using normal memory addessing.\r | |
326 | * APL\1130 does this in some places, I think these are why it had to be modified\r | |
327 | * to run on the 1800. Of course not all read/write to 1, 2 or implies an attempt\r | |
328 | * to read/set and index register -- they could using the address in the normal way).\r | |
329 | * ------------------------------------------------------------------------ */\r | |
330 | \r | |
331 | int32 ReadW (int32 a)\r | |
332 | {\r | |
333 | SAR = a;\r | |
334 | SBR = (int32) M[(a) & mem_mask];\r | |
335 | return SBR;\r | |
336 | }\r | |
337 | \r | |
338 | void WriteW (int32 a, int32 d)\r | |
339 | {\r | |
340 | SAR = a;\r | |
341 | SBR = d;\r | |
342 | M[a & mem_mask] = (int16) d;\r | |
343 | }\r | |
344 | \r | |
345 | /* ------------------------------------------------------------------------ \r | |
346 | * read and write index registers. On the 1130, they're in core addresses 1, 2, 3.\r | |
347 | * on the 1800, they're separate registers\r | |
348 | * ------------------------------------------------------------------------ */\r | |
349 | \r | |
350 | static uint16 ReadIndex (int32 tag)\r | |
351 | {\r | |
352 | #ifdef ENABLE_1800_SUPPORT\r | |
353 | if (is_1800)\r | |
354 | return XR[tag-1]; /* 1800: fetch from register */\r | |
355 | #endif\r | |
356 | \r | |
357 | SAR = tag; /* 1130: ordinary read from memory (like ReadW) */\r | |
358 | SBR = (int32) M[(tag) & mem_mask];\r | |
359 | return SBR;\r | |
360 | }\r | |
361 | \r | |
362 | static void WriteIndex (int32 tag, int32 d)\r | |
363 | {\r | |
364 | #ifdef ENABLE_1800_SUPPORT\r | |
365 | if (is_1800) {\r | |
366 | XR[tag-1] = d; /* 1800: store in register */\r | |
367 | return;\r | |
368 | }\r | |
369 | #endif\r | |
370 | \r | |
371 | SAR = tag; /* 1130: ordinary write to memory (same as WriteW) */\r | |
372 | SBR = d;\r | |
373 | M[tag & mem_mask] = (int16) d;\r | |
374 | }\r | |
375 | \r | |
376 | /* ------------------------------------------------------------------------ \r | |
377 | * upcase - force a string to uppercase (ASCII)\r | |
378 | * ------------------------------------------------------------------------ */\r | |
379 | \r | |
380 | char *upcase (char *str)\r | |
381 | {\r | |
382 | char *s;\r | |
383 | \r | |
384 | for (s = str; *s; s++) {\r | |
385 | if (*s >= 'a' && *s <= 'z')\r | |
386 | *s -= 32;\r | |
387 | } \r | |
388 | \r | |
389 | return str;\r | |
390 | }\r | |
391 | \r | |
392 | /* ------------------------------------------------------------------------ \r | |
393 | * calc_ints - set appropriate bits in int_req if any interrupts are pending on given levels\r | |
394 | *\r | |
395 | * int_req:\r | |
396 | * bit 5 4 3 2 1 0\r | |
397 | * \ \ \ \ \ \\r | |
398 | * \ \ \ \ \ interrupt level 5 pending (lowest priority)\r | |
399 | * \ . . .\r | |
400 | * interrupt level 0 pending (highest priority)\r | |
401 | *\r | |
402 | * int_mask is set according to current interrupt level (ipl)\r | |
403 | *\r | |
404 | * 0 0 0 0 0 0 ipl = 0 (currently servicing highest priority interrupt)\r | |
405 | * 1 0 0 0 0 0 1\r | |
406 | * 1 1 0 0 0 0 2\r | |
407 | * 1 1 1 0 0 0 3\r | |
408 | * 1 1 1 1 0 0 4\r | |
409 | * 1 1 1 1 1 0 5 (currently servicing lowest priority interrupt)\r | |
410 | * 1 1 1 1 1 1 -1 (not servicing an interrupt)\r | |
411 | * ------------------------------------------------------------------------ */\r | |
412 | \r | |
413 | void calc_ints (void)\r | |
414 | {\r | |
415 | register int i;\r | |
416 | register int32 newbits = 0;\r | |
417 | \r | |
418 | GUI_BEGIN_CRITICAL_SECTION /* using critical section here so we don't mislead the GUI thread */\r | |
419 | \r | |
420 | for (i = 6; --i >= 0; ) {\r | |
421 | newbits >>= 1;\r | |
422 | if (ILSW[i])\r | |
423 | newbits |= 0x20;\r | |
424 | }\r | |
425 | \r | |
426 | int_req = newbits;\r | |
427 | int_lamps |= int_req;\r | |
428 | int_mask = (ipl < 0) ? 0xFFFF : int_masks[ipl]; /* be sure this is set correctly */\r | |
429 | \r | |
430 | GUI_END_CRITICAL_SECTION\r | |
431 | }\r | |
432 | \r | |
433 | /* ------------------------------------------------------------------------\r | |
434 | * instruction processor\r | |
435 | * ------------------------------------------------------------------------ */\r | |
436 | \r | |
437 | #define INCREMENT_IAR IAR = (IAR + 1) & mem_mask\r | |
438 | #define DECREMENT_IAR IAR = (IAR - 1) & mem_mask\r | |
439 | \r | |
440 | void bail (char *msg)\r | |
441 | {\r | |
442 | printf("%s\n", msg);\r | |
443 | exit(1);\r | |
444 | }\r | |
445 | \r | |
446 | static void weirdop (char *msg, int offset)\r | |
447 | {\r | |
448 | printf("Weird opcode: %s at %04x\n", msg, IAR+offset);\r | |
449 | }\r | |
450 | \r | |
451 | static char *xio_devs[] = {\r | |
452 | "0?", "console", "1142card", "1134papertape",\r | |
453 | "dsk0", "1627plot", "1132print", "switches",\r | |
454 | "1231omr", "2501card", "comm", "b?",\r | |
455 | "sys7", "d?", "e?", "f?",\r | |
456 | "10?", "dsk1", "dsk2", "dsk3",\r | |
457 | "dsk4", "dsk5", "dsk6", "dsk7+",\r | |
458 | "18?", "2250disp", "2741attachment", "1b",\r | |
459 | "1c?", "1d?", "1e?", "1f?"\r | |
460 | };\r | |
461 | \r | |
462 | static char *xio_funcs[] = {\r | |
463 | "0?", "write", "read", "sense_irq",\r | |
464 | "control", "initw", "initr", "sense"\r | |
465 | };\r | |
466 | \r | |
467 | t_stat sim_instr (void)\r | |
468 | {\r | |
469 | extern int32 sim_interval;\r | |
470 | extern UNIT *sim_clock_queue;\r | |
471 | int32 i, eaddr, INDIR, IR, F, DSPLC, word2, oldval, newval, src, src2, dst, abit, xbit;\r | |
472 | int32 iocc_addr, iocc_op, iocc_dev, iocc_func, iocc_mod;\r | |
473 | char msg[50];\r | |
474 | int cwincount = 0, status;\r | |
475 | static long ninstr = 0;\r | |
476 | static char *intlabel[] = {"INT0","INT1","INT2","INT3","INT4","INT5"};\r | |
477 | \r | |
478 | if (cgi) /* give CGI hook function a chance to do something */\r | |
479 | cgi_start();\r | |
480 | \r | |
481 | if (running) /* this is definitely not reentrant */\r | |
482 | return -1;\r | |
483 | \r | |
484 | if (! power) /* this matters only to the GUI */\r | |
485 | return STOP_POWER_OFF;\r | |
486 | \r | |
487 | running = TRUE;\r | |
488 | \r | |
489 | mem_mask = MEMSIZE - 1; /* set other useful variables */\r | |
490 | calc_ints();\r | |
491 | \r | |
492 | /* Main instruction fetch/decode loop */\r | |
493 | \r | |
494 | reason = 0;\r | |
495 | wait_lamp = 0; /* release lock on wait lamp */\r | |
496 | \r | |
497 | #ifdef GUI_SUPPORT\r | |
498 | update_gui(TRUE);\r | |
499 | gui_run(TRUE);\r | |
500 | #endif\r | |
501 | \r | |
502 | while (reason == 0) {\r | |
503 | IAR &= mem_mask;\r | |
504 | \r | |
505 | #ifdef GUI_SUPPORT\r | |
506 | #ifndef UPDATE_BY_TIMER\r | |
507 | #if (UPDATE_INTERVAL > 0)\r | |
508 | if (--cwincount <= 0) {\r | |
509 | update_gui(FALSE); /* update console lamps only every so many instructions */\r | |
510 | cwincount = UPDATE_INTERVAL + (rand() % MIN(UPDATE_INTERVAL, 32));\r | |
511 | }\r | |
512 | #else\r | |
513 | update_gui(FALSE);\r | |
514 | #endif /* ifdef UPDATE_INTERVAL */\r | |
515 | #endif /* ifndef UPDATE_BY_TIMER */\r | |
516 | #endif /* ifdef GUI_SUPPORT */\r | |
517 | \r | |
518 | if (sim_interval <= 0) { /* any events timed out? */\r | |
519 | if (sim_clock_queue != NULL) {\r | |
520 | if ((status = sim_process_event()) != 0)\r | |
521 | reason = simh_status_to_stopcode(status);\r | |
522 | \r | |
523 | calc_ints();\r | |
524 | continue;\r | |
525 | }\r | |
526 | }\r | |
527 | \r | |
528 | if (int_req & int_mask) { /* any pending interrupts? */\r | |
529 | for (i = 0; i <= 5; i++) /* find highest pending interrupt */\r | |
530 | if ((int_req & int_mask) & (0x20 >> i))\r | |
531 | break;\r | |
532 | \r | |
533 | if (i >= 6) { /* nothing to do? */\r | |
534 | calc_ints(); /* weird. recalculate */\r | |
535 | continue; /* back to fetch */\r | |
536 | }\r | |
537 | \r | |
538 | GUI_BEGIN_CRITICAL_SECTION\r | |
539 | \r | |
540 | if (ipl >= 0) /* save previous IPL in bit stack */\r | |
541 | iplpending |= (0x20 >> ipl);\r | |
542 | \r | |
543 | ipl = i; /* set new interrupt level */\r | |
544 | int_mask = int_masks[i]; /* set appropriate mask */\r | |
545 | \r | |
546 | GUI_END_CRITICAL_SECTION\r | |
547 | \r | |
548 | wait_state = 0; /* exit wait state */\r | |
549 | eaddr = ReadW(8+i); /* get IRQ vector */\r | |
550 | archive_backtrace(intlabel[i]);\r | |
551 | WriteW(eaddr, IAR); /* save IAR */\r | |
552 | IAR = (eaddr+1) & mem_mask; /* go to next address */\r | |
553 | continue; /* now continue processing */\r | |
554 | } /* end if int_req */\r | |
555 | \r | |
556 | if (wait_state) { /* waiting? */\r | |
557 | sim_interval = 0; /* run the clock out */\r | |
558 | \r | |
559 | if (sim_qcount() <= (cgi ? 0 : 1)) { /* one routine queued? we're waiting for keyboard only */\r | |
560 | if (keyboard_is_busy()) { /* we are actually waiting for a keystroke */\r | |
561 | if ((status = sim_process_event()) != SCPE_OK) /* get it with wait_state still set */\r | |
562 | reason = simh_status_to_stopcode(status);\r | |
563 | }\r | |
564 | else { /* CPU is not expecting a keystroke (keyboard interrupt) */\r | |
565 | if (wait_state == WAIT_OP)\r | |
566 | reason = STOP_WAIT; /* end the simulation */\r | |
567 | else\r | |
568 | reason = STOP_INVALID_INSTR;\r | |
569 | }\r | |
570 | }\r | |
571 | \r | |
572 | if (gdu_active()) /* but don't stop simulator if 2250 GDU is running */\r | |
573 | reason = 0;\r | |
574 | \r | |
575 | continue;\r | |
576 | }\r | |
577 | \r | |
578 | if (IAR == ibkpt_addr) { /* simulator breakpoint? */\r | |
579 | save_ibkpt = ibkpt_addr; /* save bkpt */\r | |
580 | ibkpt_addr = ibkpt_addr | ILL_ADR_FLAG; /* disable */\r | |
581 | sim_activate(&cpu_unit, 1); /* sched re-enable after next instruction */\r | |
582 | reason = STOP_IBKPT; /* stop simulation */\r | |
583 | cwincount = 0;\r | |
584 | continue;\r | |
585 | }\r | |
586 | \r | |
587 | ninstr++;\r | |
588 | if (cpu_unit.flags & UNIT_ATT)\r | |
589 | trace_instruction(); /* log CPU details if logging is enabled */\r | |
590 | \r | |
591 | prev_IAR = IAR; /* save IAR before incrementing it */\r | |
592 | \r | |
593 | IR = ReadW(IAR); /* fetch 1st word of instruction */\r | |
594 | INCREMENT_IAR;\r | |
595 | sim_interval = sim_interval - 1; /* this constitutes one tick of the simulation clock */\r | |
596 | \r | |
597 | OP = (IR >> 11) & 0x1F; /* opcode */\r | |
598 | F = IR & 0x0400; /* format bit: 1 = long instr */\r | |
599 | TAG = IR & 0x0300; /* tag bits: index reg x */\r | |
600 | if (TAG)\r | |
601 | TAG >>= 8;\r | |
602 | \r | |
603 | /* here I compute the usual effective address on the assumption that the instruction will need it. Some don't. */\r | |
604 | \r | |
605 | if (F) { /* long instruction, ASSUME it's valid (have to decrement IAR if not) */\r | |
606 | INDIR = IR & 0x0080; /* indirect bit */\r | |
607 | DSPLC = IR & 0x007F; /* displacement or modifier */\r | |
608 | if (DSPLC & 0x0040)\r | |
609 | DSPLC |= ~ 0x7F; /* sign extend */\r | |
610 | \r | |
611 | word2 = ReadW(IAR); /* get reference address */\r | |
612 | INCREMENT_IAR; /* bump the instruction address register */\r | |
613 | \r | |
614 | eaddr = word2; /* assume standard addressing & compute effective address */\r | |
615 | if (TAG) /* if indexed */\r | |
616 | eaddr += ReadIndex(TAG); /* add index register value */\r | |
617 | if (INDIR) /* if indirect addressing */\r | |
618 | eaddr = ReadW(eaddr); /* pick up referenced address */\r | |
619 | }\r | |
620 | else { /* short instruction, use displacement */\r | |
621 | INDIR = 0; /* never indirect */\r | |
622 | DSPLC = IR & 0x00FF; /* get displacement */\r | |
623 | if (DSPLC & 0x0080)\r | |
624 | DSPLC |= ~ 0xFF;\r | |
625 | \r | |
626 | if (TAG) /* if indexed */\r | |
627 | eaddr = ReadIndex(TAG) + DSPLC; /* add index register value */\r | |
628 | else\r | |
629 | eaddr = IAR + DSPLC; /* otherwise relative to IAR after fetch */\r | |
630 | }\r | |
631 | \r | |
632 | switch (OP) { /* decode instruction */\r | |
633 | case 0x01: /* --- XIO --- */\r | |
634 | iocc_addr = ReadW(eaddr); /* get IOCC packet */\r | |
635 | iocc_op = ReadW(eaddr|1); /* note 'or' not plus, address must be even for proper operation */\r | |
636 | \r | |
637 | iocc_dev = (iocc_op >> 11) & 0x001F;\r | |
638 | iocc_func = (iocc_op >> 8) & 0x0007;\r | |
639 | iocc_mod = iocc_op & 0x00FF;\r | |
640 | \r | |
641 | if (cpu_unit.flags & UNIT_ATT)\r | |
642 | trace_io("* XIO %s %s mod %02x addr %04x", xio_funcs[iocc_func], xio_devs[iocc_dev], iocc_mod, iocc_addr);\r | |
643 | \r | |
644 | /* fprintf(stderr, "* XIO %s %s mod %02x addr %04x\n", xio_funcs[iocc_func], xio_devs[iocc_dev], iocc_mod, iocc_addr); */\r | |
645 | \r | |
646 | ACC = 0; /* ACC is destroyed, and default XIO_SENSE_DEV result is 0 */\r | |
647 | \r | |
648 | switch (iocc_func) {\r | |
649 | case XIO_UNUSED:\r | |
650 | sprintf(msg, "Unknown op %x on device %02x", iocc_func, iocc_dev);\r | |
651 | xio_error(msg);\r | |
652 | break;\r | |
653 | \r | |
654 | case XIO_SENSE_IRQ: /* examine current Interrupt Level Status Word */\r | |
655 | ACC = (ipl >= 0) ? ILSW[ipl] : 0;\r | |
656 | break;\r | |
657 | \r | |
658 | default: /* perform device-specific operation */\r | |
659 | switch (iocc_dev) {\r | |
660 | case 0x01: /* console keyboard and printer */\r | |
661 | xio_1131_console(iocc_addr, iocc_func, iocc_mod);\r | |
662 | break;\r | |
663 | case 0x02: /* 1142 card reader/punch */\r | |
664 | xio_1142_card(iocc_addr, iocc_func, iocc_mod);\r | |
665 | break;\r | |
666 | case 0x03: /* 1134 paper tape reader/punch */\r | |
667 | xio_1134_papertape(iocc_addr, iocc_func, iocc_mod);\r | |
668 | break;\r | |
669 | case 0x04: /* CPU disk storage */\r | |
670 | xio_disk(iocc_addr, iocc_func, iocc_mod, 0);\r | |
671 | break;\r | |
672 | case 0x05: /* 1627 plotter */\r | |
673 | xio_1627_plotter(iocc_addr, iocc_func, iocc_mod);\r | |
674 | break;\r | |
675 | case 0x06: /* 1132 Printer */\r | |
676 | xio_1132_printer(iocc_addr, iocc_func, iocc_mod);\r | |
677 | break;\r | |
678 | case 0x07: /* console switches, stop key, run mode */\r | |
679 | xio_1131_switches(iocc_addr, iocc_func, iocc_mod);\r | |
680 | break;\r | |
681 | case 0x08: /* 1231 optical mark reader */\r | |
682 | xio_1231_optical(iocc_addr, iocc_func, iocc_mod);\r | |
683 | break;\r | |
684 | case 0x09: /* 2501 card reader */\r | |
685 | xio_2501_card(iocc_addr, iocc_func, iocc_mod);\r | |
686 | break;\r | |
687 | case 0x0a: /* synchronous comm adapter */\r | |
688 | xio_sca(iocc_addr, iocc_func, iocc_mod);\r | |
689 | break;\r | |
690 | case 0x0c: /* IBM System/7 interprocessor link */\r | |
691 | xio_system7(iocc_addr, iocc_func, iocc_mod);\r | |
692 | break;\r | |
693 | case 0x11: /* 2310 Disk Storage, Drive 1, or 2311 Disk Storage Drive. Drive 1, Disk 1 */\r | |
694 | xio_disk(iocc_addr, iocc_func, iocc_mod, 1);\r | |
695 | break;\r | |
696 | case 0x12: /* 2310 Disk Storage, Drive 2, or 2311 Disk Storage Drive. Drive 1, Disk 2 */\r | |
697 | xio_disk(iocc_addr, iocc_func, iocc_mod, 2);\r | |
698 | break;\r | |
699 | case 0x13: /* 2310 Disk Storage, Drive 3, or 2311 Disk Storage Drive. Drive 1, Disk 3 */\r | |
700 | xio_disk(iocc_addr, iocc_func, iocc_mod, 3);\r | |
701 | break;\r | |
702 | case 0x14: /* 2310 Disk Storage, Drive 4, or 2311 Disk Storage Drive. Drive 1, Disk 4 */\r | |
703 | xio_disk(iocc_addr, iocc_func, iocc_mod, 4);\r | |
704 | break;\r | |
705 | case 0x15: /* 1403 Printer */\r | |
706 | xio_1403_printer(iocc_addr, iocc_func, iocc_mod);\r | |
707 | break;\r | |
708 | case 0x16: /* 2311 Disk Storage Drive. Drive 1, Disk 5 */\r | |
709 | xio_disk(iocc_addr, iocc_func, iocc_mod, -1);\r | |
710 | break;\r | |
711 | case 0x17: /* 2311 Disk Storage Drive, Drive 2, Disk 1 through 5 */\r | |
712 | xio_disk(iocc_addr, iocc_func, iocc_mod, -1);\r | |
713 | break;\r | |
714 | case 0x19: /* 2250 Display Unit */\r | |
715 | xio_2250_display(iocc_addr, iocc_func, iocc_mod);\r | |
716 | break;\r | |
717 | case 0x1a: /* 2741 Attachment (nonstandard serial interface used by APL\1130 */\r | |
718 | xio_t2741_terminal(iocc_addr, iocc_func, iocc_mod);\r | |
719 | break;\r | |
720 | default:\r | |
721 | sprintf(msg, "unknown device %02x", iocc_dev);\r | |
722 | xio_error(msg);\r | |
723 | break;\r | |
724 | }\r | |
725 | }\r | |
726 | \r | |
727 | calc_ints(); /* after every XIO, reset int_mask just in case */\r | |
728 | break;\r | |
729 | \r | |
730 | case 0x02: /* --- SLA,SLT,SLC,SLCA,NOP - Shift Left family --- */\r | |
731 | if (F) {\r | |
732 | weirdop("Long Left Shift", -2);\r | |
733 | DECREMENT_IAR;\r | |
734 | }\r | |
735 | \r | |
736 | CCC = ((TAG == 0) ? DSPLC : ReadIndex(TAG)) & 0x003F;\r | |
737 | ARFSET(CCC);\r | |
738 | if (CCC == 0)\r | |
739 | break; /* shift of zero is a NOP */\r | |
740 | \r | |
741 | switch (IR & 0x00C0) {\r | |
742 | case 0x0040: /* SLCA */\r | |
743 | if (TAG) {\r | |
744 | while (CCC > 0 && (ACC & 0x8000) == 0) {\r | |
745 | ACC <<= 1;\r | |
746 | CCC--;\r | |
747 | }\r | |
748 | C = (CCC != 0);\r | |
749 | WriteIndex(TAG, (ReadIndex(TAG) & 0xFF00) | CCC); /* put low 6 bits back into index register and zero bits 8 and 9 */\r | |
750 | break;\r | |
751 | }\r | |
752 | /* if TAG == 0, fall through and treat like normal shift SLA */\r | |
753 | \r | |
754 | case 0x0000: /* SLA */\r | |
755 | while (CCC > 0) {\r | |
756 | C = (ACC & 0x8000);\r | |
757 | ACC = (ACC << 1) & 0xFFFF;\r | |
758 | CCC--;\r | |
759 | }\r | |
760 | break;\r | |
761 | \r | |
762 | case 0x00C0: /* SLC */\r | |
763 | if (TAG) {\r | |
764 | while (CCC > 0 && (ACC & 0x8000) == 0) {\r | |
765 | abit = (EXT & 0x8000) >> 15;\r | |
766 | ACC = ((ACC << 1) & 0xFFFF) | abit;\r | |
767 | EXT = (EXT << 1);\r | |
768 | CCC--;\r | |
769 | }\r | |
770 | C = (CCC != 0);\r | |
771 | WriteIndex(TAG, ReadIndex(TAG) & 0xFF00 | CCC); /* put 6 bits back into low byte of index register */\r | |
772 | break;\r | |
773 | }\r | |
774 | /* if TAG == 0, fall through and treat like normal shift SLT */\r | |
775 | \r | |
776 | case 0x0080: /* SLT */\r | |
777 | while (CCC > 0) {\r | |
778 | C = (ACC & 0x8000);\r | |
779 | abit = (EXT & 0x8000) >> 15;\r | |
780 | ACC = ((ACC << 1) & 0xFFFF) | abit;\r | |
781 | EXT = (EXT << 1) & 0xFFFF;\r | |
782 | CCC--;\r | |
783 | }\r | |
784 | break;\r | |
785 | \r | |
786 | default:\r | |
787 | bail("SLA switch, can't happen");\r | |
788 | break;\r | |
789 | }\r | |
790 | break;\r | |
791 | \r | |
792 | case 0x03: /* --- SRA, SRT, RTE - Shift Right family --- */\r | |
793 | if (F) {\r | |
794 | weirdop("Long Right Shift", -2);\r | |
795 | DECREMENT_IAR;\r | |
796 | }\r | |
797 | \r | |
798 | CCC = ((TAG == 0) ? DSPLC : ReadIndex(TAG)) & 0x3F;\r | |
799 | ARFSET(CCC);\r | |
800 | if (CCC == 0)\r | |
801 | break; /* NOP */\r | |
802 | \r | |
803 | switch (IR & 0x00C0) {\r | |
804 | case 0x0000: /* SRA */\r | |
805 | ACC = (CCC < 16) ? ((ACC & 0xFFFF) >> CCC) : 0;\r | |
806 | CCC = 0;\r | |
807 | break;\r | |
808 | \r | |
809 | case 0x0040: /* invalid */\r | |
810 | wait_state = WAIT_INVALID_OP;\r | |
811 | break;\r | |
812 | \r | |
813 | case 0x0080: /* SRT */\r | |
814 | while (CCC > 0) {\r | |
815 | xbit = (ACC & 0x0001) << 15;\r | |
816 | abit = (ACC & 0x8000);\r | |
817 | ACC = (ACC >> 1) & 0x7FFF | abit;\r | |
818 | EXT = (EXT >> 1) & 0x7FFF | xbit;\r | |
819 | CCC--;\r | |
820 | }\r | |
821 | break;\r | |
822 | \r | |
823 | case 0x00C0: /* RTE */\r | |
824 | while (CCC > 0) {\r | |
825 | abit = (EXT & 0x0001) << 15;\r | |
826 | xbit = (ACC & 0x0001) << 15;\r | |
827 | ACC = (ACC >> 1) & 0x7FFF | abit;\r | |
828 | EXT = (EXT >> 1) & 0x7FFF | xbit;\r | |
829 | CCC--;\r | |
830 | }\r | |
831 | break;\r | |
832 | \r | |
833 | default:\r | |
834 | bail("SRA switch, can't happen");\r | |
835 | break;\r | |
836 | }\r | |
837 | break;\r | |
838 | \r | |
839 | case 0x04: /* --- LDS - Load Status --- */\r | |
840 | if (F) { /* never fetches second word? */\r | |
841 | weirdop("Long LDS", -2);\r | |
842 | DECREMENT_IAR;\r | |
843 | }\r | |
844 | \r | |
845 | V = (DSPLC & 1);\r | |
846 | C = (DSPLC & 2) >> 1;\r | |
847 | break;\r | |
848 | \r | |
849 | case 0x05: /* --- STS - Store Status --- */\r | |
850 | newval = ReadW(eaddr) & 0xFF00;\r | |
851 | if (C)\r | |
852 | newval |= 2;\r | |
853 | if (V)\r | |
854 | newval |= 1;\r | |
855 | \r | |
856 | WriteW(eaddr, newval);\r | |
857 | C = V = 0; /* clear flags after storing */\r | |
858 | break;\r | |
859 | \r | |
860 | case 0x06: /* --- WAIT --- */\r | |
861 | wait_state = WAIT_OP;\r | |
862 | if (F) { /* what happens if we use long format? */\r | |
863 | weirdop("Long WAIT", -2);\r | |
864 | DECREMENT_IAR; /* assume it wouldn't have fetched 2nd word? */\r | |
865 | }\r | |
866 | break;\r | |
867 | \r | |
868 | case 0x08: /* --- BSI - Branch and store IAR --- */\r | |
869 | if (F) {\r | |
870 | if (bsctest(IR, F)) /* do standard BSC long format testing */\r | |
871 | break; /* if any condition is true, do nothing */\r | |
872 | }\r | |
873 | WriteW(eaddr, IAR); /* do subroutine call */\r | |
874 | archive_backtrace("BSI"); /* save info in back-trace buffer */\r | |
875 | IAR = (eaddr + 1) & mem_mask;\r | |
876 | break;\r | |
877 | \r | |
878 | case 0x09: /* --- BSC - Branch and skip on Condition --- */\r | |
879 | if (F) {\r | |
880 | if (bsctest(IR, F)) /* long format; any indicator cancels branch */\r | |
881 | break;\r | |
882 | \r | |
883 | archive_backtrace((DSPLC & 0x40) ? "BOSC" : "BSC"); /* save info in back-trace buffer */\r | |
884 | IAR = eaddr; /* no indicator means branch taken */\r | |
885 | }\r | |
886 | else { /* short format: skip if any indicator hits */\r | |
887 | if (bsctest(IR, F)) {\r | |
888 | archive_backtrace((DSPLC & 0x40) ? "BOSC" : "BSC"); /* save info in back-trace buffer */\r | |
889 | INCREMENT_IAR;\r | |
890 | }\r | |
891 | }\r | |
892 | /* 27Mar02: moved this test out of the (F) condition; BOSC works even in the\r | |
893 | * short form. The displacement field in this instruction is always the set of\r | |
894 | * condition bits, and the interrupt clear bit doesn't collide. */\r | |
895 | \r | |
896 | if (DSPLC & 0x40) { /* BOSC = exit from interrupt handler */\r | |
897 | exit_irq();\r | |
898 | cwincount = 0;\r | |
899 | }\r | |
900 | break;\r | |
901 | \r | |
902 | case 0x0c: /* --- LDX - Load Index --- */\r | |
903 | if (F)\r | |
904 | eaddr = (INDIR) ? ReadW(word2) : word2;\r | |
905 | else\r | |
906 | eaddr = DSPLC;\r | |
907 | \r | |
908 | if (TAG)\r | |
909 | WriteIndex(TAG, eaddr);\r | |
910 | else {\r | |
911 | archive_backtrace("LDX"); /* save info in back-trace buffer */\r | |
912 | IAR = eaddr; /* what happens in short form? can onlyjump to low addresses? */\r | |
913 | }\r | |
914 | break;\r | |
915 | \r | |
916 | case 0x0d: /* --- STX - Store Index --- */\r | |
917 | if (F) { /* compute EA without any indexing */\r | |
918 | eaddr = (INDIR) ? ReadW(word2) : word2;\r | |
919 | }\r | |
920 | else {\r | |
921 | eaddr = IAR + DSPLC;\r | |
922 | }\r | |
923 | WriteW(eaddr, TAG ? ReadIndex(TAG) : IAR);\r | |
924 | break;\r | |
925 | \r | |
926 | case 0x0e: /* --- MDX - Modify Index and Skip --- */\r | |
927 | if (F) { /* long format: adjust memory location */\r | |
928 | if (TAG) {\r | |
929 | oldval = ReadIndex(TAG); /* add word2 to index */\r | |
930 | newval = oldval + (INDIR ? ReadW(word2) : word2);\r | |
931 | WriteIndex(TAG, newval);\r | |
932 | }\r | |
933 | else {\r | |
934 | oldval = ReadW(word2);\r | |
935 | DSPLC = IR & 0x00FF; /* use extended displacement (no INDIR bit, it's is part of displacement in this op) */\r | |
936 | if (DSPLC & 0x0080)\r | |
937 | DSPLC |= ~ 0xFF;\r | |
938 | newval = oldval + DSPLC; /* add modifier to @word2 */\r | |
939 | WriteW(word2, newval);\r | |
940 | }\r | |
941 | }\r | |
942 | else { /* short format: adust IAR or index */\r | |
943 | if (TAG) {\r | |
944 | oldval = ReadIndex(TAG); /* add displacement to index */\r | |
945 | newval = oldval + DSPLC;\r | |
946 | WriteIndex(TAG, newval);\r | |
947 | }\r | |
948 | else {\r | |
949 | oldval = IAR; /* add displacement to IAR */\r | |
950 | newval = IAR + DSPLC;\r | |
951 | archive_backtrace("MDX");\r | |
952 | IAR = newval & mem_mask;\r | |
953 | }\r | |
954 | }\r | |
955 | \r | |
956 | if ((F || TAG) && (((newval & 0xFFFF) == 0) || ((oldval & 0x8000) != (newval & 0x8000)))) {\r | |
957 | archive_backtrace("SKP");\r | |
958 | INCREMENT_IAR; /* skip if index sign change or zero */\r | |
959 | }\r | |
960 | break;\r | |
961 | \r | |
962 | case 0x10: /* --- A - Add --- */\r | |
963 | /* in adds and subtracts, carry is set or cleared, overflow is set only */\r | |
964 | src = ReadW(eaddr);\r | |
965 | ARFSET(src);\r | |
966 | src2 = ACC;\r | |
967 | ACC = (ACC + src) & 0xFFFF;\r | |
968 | \r | |
969 | C = ACC < src;\r | |
970 | if (! V)\r | |
971 | V = SIGN_BIT((~src ^ src2) & (src ^ ACC));\r | |
972 | break;\r | |
973 | \r | |
974 | case 0x11: /* --- AD - Add Double --- */\r | |
975 | src = ((ACC << 16) | (EXT & 0xFFFF));\r | |
976 | ARFSET(EXT);\r | |
977 | src2 = (ReadW(eaddr) << 16) + ReadW(eaddr|1);\r | |
978 | dst = src + src2;\r | |
979 | ACC = (dst >> 16) & 0xFFFF;\r | |
980 | EXT = dst & 0xFFFF;\r | |
981 | \r | |
982 | C = (uint32) dst < (uint32) src;\r | |
983 | if (! V)\r | |
984 | V = DWSIGN_BIT((~src ^ src2) & (src ^ dst));\r | |
985 | break;\r | |
986 | \r | |
987 | case 0x12: /* --- S - Subtract --- */\r | |
988 | src = ACC;\r | |
989 | ARFSET(src);\r | |
990 | src2 = ReadW(eaddr);\r | |
991 | ACC = (ACC-src2) & 0xFFFF;\r | |
992 | \r | |
993 | C = src < src2;\r | |
994 | if (! V)\r | |
995 | V = SIGN_BIT((src ^ src2) & (src ^ ACC));\r | |
996 | break;\r | |
997 | \r | |
998 | case 0x13: /* --- SD - Subtract Double --- */\r | |
999 | src = ((ACC << 16) | (EXT & 0xFFFF));\r | |
1000 | ARFSET(EXT);\r | |
1001 | src2 = (ReadW(eaddr) << 16) + ReadW(eaddr|1);\r | |
1002 | dst = src - src2;\r | |
1003 | ACC = (dst >> 16) & 0xFFFF;\r | |
1004 | EXT = dst & 0xFFFF;\r | |
1005 | \r | |
1006 | C = (uint32) src < (uint32) src2;\r | |
1007 | if (! V)\r | |
1008 | V = DWSIGN_BIT((src ^ src2) & (src ^ dst));\r | |
1009 | break;\r | |
1010 | \r | |
1011 | case 0x14: /* --- M - Multiply --- */\r | |
1012 | if ((src = ACC & 0xFFFF) & 0x8000) /* sign extend the values */\r | |
1013 | src |= ~0xFFFF;\r | |
1014 | if ((src2 = ReadW(eaddr)) & 0x8000)\r | |
1015 | src2 |= ~0xFFFF;\r | |
1016 | \r | |
1017 | ARFSET(src2);\r | |
1018 | dst = src * src2;\r | |
1019 | ACC = (dst >> 16) & 0xFFFF; /* split the results */\r | |
1020 | EXT = dst & 0xFFFF;\r | |
1021 | break;\r | |
1022 | \r | |
1023 | case 0x15: /* --- D - Divide --- */\r | |
1024 | src = ((ACC << 16) | (EXT & 0xFFFF));\r | |
1025 | if ((src2 = ReadW(eaddr)) & 0x8000)\r | |
1026 | src2 |= ~0xFFFF; /* oops: sign extend was missing, fixed 18Mar03 */\r | |
1027 | \r | |
1028 | ARFSET(src2);\r | |
1029 | \r | |
1030 | if (src2 == 0)\r | |
1031 | V = 1; /* divide by zero just sets overflow, ACC & EXT are undefined */\r | |
1032 | else {\r | |
1033 | ACC = (src / src2) & 0xFFFF;\r | |
1034 | EXT = (src % src2) & 0xFFFF;\r | |
1035 | }\r | |
1036 | break;\r | |
1037 | \r | |
1038 | case 0x18: /* --- LD - Load ACC --- */\r | |
1039 | ACC = ReadW(eaddr);\r | |
1040 | break;\r | |
1041 | \r | |
1042 | case 0x19: /* --- LDD - Load Double --- */\r | |
1043 | ACC = ReadW(eaddr);\r | |
1044 | EXT = ReadW(eaddr|1); /* notice address is |1 not +1 */\r | |
1045 | break;\r | |
1046 | \r | |
1047 | case 0x1a: /* --- STO - Store ACC --- */\r | |
1048 | WriteW(eaddr, ACC);\r | |
1049 | break;\r | |
1050 | \r | |
1051 | case 0x1b: /* --- STD - Store Double --- */\r | |
1052 | WriteW(eaddr|1, EXT);\r | |
1053 | WriteW(eaddr, ACC); /* order is important: if odd addr, only ACC is stored */\r | |
1054 | break;\r | |
1055 | \r | |
1056 | case 0x1c: /* --- AND - Logical AND --- */\r | |
1057 | src = ReadW(eaddr); \r | |
1058 | ARFSET(src);\r | |
1059 | ACC &= src;\r | |
1060 | break;\r | |
1061 | \r | |
1062 | case 0x1d: /* --- OR - Logical OR --- */\r | |
1063 | src = ReadW(eaddr); \r | |
1064 | ARFSET(src);\r | |
1065 | ACC |= src;\r | |
1066 | break;\r | |
1067 | \r | |
1068 | case 0x1e: /* --- EOR - Logical Excl OR --- */\r | |
1069 | src = ReadW(eaddr); \r | |
1070 | ARFSET(src);\r | |
1071 | ACC ^= src;\r | |
1072 | break;\r | |
1073 | \r | |
1074 | case 0x16:\r | |
1075 | case 0x17:\r | |
1076 | #ifdef ENABLE_1800_SUPPORT\r | |
1077 | if (is_1800) {\r | |
1078 | if (OP == 0x16) { /* --- CMP - Compare --- */\r | |
1079 | src = ACC; /* like subtract but result isn't stored */\r | |
1080 | src2 = ReadW(eaddr);\r | |
1081 | dst = (ACC-src2) & 0xFFFF;\r | |
1082 | C = src < src2;\r | |
1083 | \r | |
1084 | if (dst & 0x8000) /* if ACC < operand, skip 1 instruction */\r | |
1085 | IAR = IAR+1;\r | |
1086 | else if ((dst & 0xFFFF) == 0) /* if ACC == operand, skip 2 instructions */\r | |
1087 | IAR = IAR+2;\r | |
1088 | }\r | |
1089 | else { /* --- DCMP - Compare Double --- */\r | |
1090 | src = ((ACC << 16) | (EXT & 0xFFFF));\r | |
1091 | src2 = (ReadW(eaddr) << 16) + ReadW(eaddr|1);\r | |
1092 | dst = src - src2;\r | |
1093 | C = (uint32) src < (uint32) src2;\r | |
1094 | \r | |
1095 | if (dst & 0x80000000) /* if ACC_EXT < operand, skip 1 instruction */\r | |
1096 | IAR = IAR+1;\r | |
1097 | else if (dst == 0) /* if ACC_EXT == operand, skip 2 instructions */\r | |
1098 | IAR = IAR+2;\r | |
1099 | }\r | |
1100 | \r | |
1101 | break; /* these are legal instructions on the 1800 */\r | |
1102 | }\r | |
1103 | #endif\r | |
1104 | /* 1130: these are not legal instructions, fall through */\r | |
1105 | \r | |
1106 | default:\r | |
1107 | /* all invalid instructions act like waits */\r | |
1108 | /* case 0x00: */\r | |
1109 | /* case 0x07: */\r | |
1110 | /* case 0x0a: */\r | |
1111 | /* case 0x0b: */\r | |
1112 | /* case 0x0e: */\r | |
1113 | /* case 0x0f: */\r | |
1114 | /* case 0x1f: */\r | |
1115 | wait_state = WAIT_INVALID_OP;\r | |
1116 | if (F)\r | |
1117 | DECREMENT_IAR; /* assume it wouldn't have fetched 2nd word? */\r | |
1118 | \r | |
1119 | break;\r | |
1120 | } /* end instruction decode switch */\r | |
1121 | \r | |
1122 | if (RUNMODE != MODE_RUN && RUNMODE != MODE_INT_RUN)\r | |
1123 | reason = STOP_WAIT;\r | |
1124 | \r | |
1125 | if (tbit && (ipl < 0)) { /* if INT_RUN mode, set IRQ5 after this instr */\r | |
1126 | GUI_BEGIN_CRITICAL_SECTION\r | |
1127 | SETBIT(cpu_dsw, CPU_DSW_INT_RUN);\r | |
1128 | SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);\r | |
1129 | int_req |= INT_REQ_5;\r | |
1130 | GUI_END_CRITICAL_SECTION\r | |
1131 | }\r | |
1132 | } /* end main loop */\r | |
1133 | \r | |
1134 | #ifdef GUI_SUPPORT\r | |
1135 | gui_run(FALSE);\r | |
1136 | #endif\r | |
1137 | \r | |
1138 | running = FALSE;\r | |
1139 | int_lamps = 0; /* display only currently active interrupts while halted */\r | |
1140 | \r | |
1141 | if (reason == STOP_WAIT || reason == STOP_INVALID_INSTR) {\r | |
1142 | wait_state = 0; /* on resume, don't wait */\r | |
1143 | wait_lamp = TRUE; /* but keep the lamp lit on the GUI */\r | |
1144 | \r | |
1145 | CLRBIT(cpu_dsw, CPU_DSW_PROGRAM_STOP); /* and on resume, reset program start bit */\r | |
1146 | if ((cpu_dsw & CPU_DSW_PROGRAM_STOP) == 0)\r | |
1147 | CLRBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);\r | |
1148 | }\r | |
1149 | \r | |
1150 | if (cgi) /* give CGI hook function a chance to do something */\r | |
1151 | cgi_stop(reason);\r | |
1152 | \r | |
1153 | return reason;\r | |
1154 | }\r | |
1155 | \r | |
1156 | /*\r | |
1157 | * simh_status_to_stopcode - convert a SCPE_xxx value from sim_process_event into a STOP_xxx code\r | |
1158 | */\r | |
1159 | \r | |
1160 | static int simh_status_to_stopcode (int status)\r | |
1161 | {\r | |
1162 | return (status == SCPE_BREAK) ? STOP_BREAK :\r | |
1163 | (status == SCPE_STOP) ? STOP_IMMEDIATE :\r | |
1164 | (status == SCPE_STEP) ? STOP_STEP : STOP_OTHER;\r | |
1165 | }\r | |
1166 | \r | |
1167 | /* ------------------------------------------------------------------------ \r | |
1168 | * bsctest - perform standard set of condition tests. We return TRUE if any\r | |
1169 | * of the condition bits specified in DSPLC test positive, FALSE if none are true.\r | |
1170 | * If reset_V is TRUE, we reset the oVerflow flag after testing it.\r | |
1171 | * ------------------------------------------------------------------------ */\r | |
1172 | \r | |
1173 | static t_bool bsctest (int32 DSPLC, t_bool reset_V)\r | |
1174 | {\r | |
1175 | if (DSPLC & 0x01) { /* Overflow off (note inverted sense) */\r | |
1176 | if (! V)\r | |
1177 | return TRUE;\r | |
1178 | else if (reset_V) /* reset after testing */\r | |
1179 | V = 0;\r | |
1180 | }\r | |
1181 | \r | |
1182 | if (DSPLC & 0x02) { /* Carry off (note inverted sense) */\r | |
1183 | if (! C)\r | |
1184 | return TRUE;\r | |
1185 | }\r | |
1186 | \r | |
1187 | if (DSPLC & 0x04) /* Even */\r | |
1188 | if ((ACC & 1) == 0)\r | |
1189 | return TRUE;\r | |
1190 | \r | |
1191 | if (DSPLC & 0x08) /* Positive */\r | |
1192 | if ((ACC & 0x8000) == 0 && ACC != 0)\r | |
1193 | return TRUE;\r | |
1194 | \r | |
1195 | if (DSPLC & 0x10) /* Negative */\r | |
1196 | if (ACC & 0x8000)\r | |
1197 | return TRUE;\r | |
1198 | \r | |
1199 | if (DSPLC & 0x20) /* Zero */\r | |
1200 | if ((ACC & 0xFFFF) == 0)\r | |
1201 | return TRUE;\r | |
1202 | \r | |
1203 | return FALSE;\r | |
1204 | }\r | |
1205 | \r | |
1206 | /* ------------------------------------------------------------------------ \r | |
1207 | * exit_irq - pop interrupt stack as part of return from subroutine (BOSC) \r | |
1208 | * ------------------------------------------------------------------------ */\r | |
1209 | \r | |
1210 | static void exit_irq (void)\r | |
1211 | {\r | |
1212 | int i, bit;\r | |
1213 | \r | |
1214 | GUI_BEGIN_CRITICAL_SECTION\r | |
1215 | \r | |
1216 | if (ipl == 5 && tbit) { /* if we are exiting an INT_RUN interrupt, clear it for the next instruction */\r | |
1217 | CLRBIT(cpu_dsw, CPU_DSW_INT_RUN);\r | |
1218 | if ((cpu_dsw & CPU_DSW_PROGRAM_STOP) == 0)\r | |
1219 | CLRBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);\r | |
1220 | }\r | |
1221 | \r | |
1222 | ipl = -1; /* default: return to main processor level */\r | |
1223 | int_mask = 0xFFFF;\r | |
1224 | \r | |
1225 | if (iplpending) { /* restore previous interrupt status */\r | |
1226 | for (i = 0, bit = 0x20; i < 6; i++, bit >>= 1) {\r | |
1227 | if (iplpending & bit) {\r | |
1228 | iplpending &= ~bit;\r | |
1229 | ipl = i;\r | |
1230 | int_mask = int_masks[i];\r | |
1231 | break;\r | |
1232 | }\r | |
1233 | }\r | |
1234 | }\r | |
1235 | GUI_END_CRITICAL_SECTION\r | |
1236 | \r | |
1237 | calc_ints(); /* recompute pending interrupt mask */\r | |
1238 | } /* because we probably cleared some ILSW bits before this instruction */\r | |
1239 | \r | |
1240 | /* let a device halt the simulation */\r | |
1241 | \r | |
1242 | void break_simulation (t_stat stopreason)\r | |
1243 | {\r | |
1244 | reason = stopreason;\r | |
1245 | }\r | |
1246 | \r | |
1247 | /* ------------------------------------------------------------------------ \r | |
1248 | * SIMH required routines\r | |
1249 | * ------------------------------------------------------------------------ */\r | |
1250 | \r | |
1251 | /* ------------------------------------------------------------------------ \r | |
1252 | * Reset routine\r | |
1253 | * ------------------------------------------------------------------------ */\r | |
1254 | \r | |
1255 | t_stat cpu_reset (DEVICE *dptr)\r | |
1256 | {\r | |
1257 | wait_state = 0; /* cancel wait */\r | |
1258 | wait_lamp = TRUE; /* but keep the wait lamp lit on the GUI */\r | |
1259 | \r | |
1260 | if (cpu_unit.flags & UNIT_ATT) { /* record reset in CPU log */\r | |
1261 | fseek(cpu_unit.fileref, 0, SEEK_END);\r | |
1262 | fprintf(cpu_unit.fileref, "---RESET---" CRLF);\r | |
1263 | }\r | |
1264 | \r | |
1265 | GUI_BEGIN_CRITICAL_SECTION\r | |
1266 | \r | |
1267 | CLRBIT(cpu_dsw, CPU_DSW_PROGRAM_STOP|CPU_DSW_INT_RUN);\r | |
1268 | CLRBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);\r | |
1269 | \r | |
1270 | reset_backtrace();\r | |
1271 | \r | |
1272 | ipl = -1;\r | |
1273 | int_mask = 0xFFFF;\r | |
1274 | int_req = 0; /* hmmm, it SHOULD reset the int req, right? */\r | |
1275 | int_lamps = 0;\r | |
1276 | iplpending = 0;\r | |
1277 | memset(ILSW, 0, sizeof(ILSW));\r | |
1278 | \r | |
1279 | cpu_dsw = 0; /* clear int req and prot stop bits */\r | |
1280 | tbit = 0; /* cancel INT_RUN mode */\r | |
1281 | \r | |
1282 | C = V = 0; /* clear processor flags */\r | |
1283 | IAR = SAR = SBR = 0; /* clear IAR and other registers */\r | |
1284 | ACC = EXT = OP = TAG = CCC = C = V = 0;\r | |
1285 | \r | |
1286 | mem_mask = MEMSIZE - 1; /* wraparound mask */\r | |
1287 | \r | |
1288 | GUI_END_CRITICAL_SECTION\r | |
1289 | \r | |
1290 | return cpu_svc(&cpu_unit); /* reset breakpoint */\r | |
1291 | }\r | |
1292 | \r | |
1293 | /* ------------------------------------------------------------------------ \r | |
1294 | * Memory examine\r | |
1295 | * ------------------------------------------------------------------------ */\r | |
1296 | \r | |
1297 | t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)\r | |
1298 | {\r | |
1299 | if (vptr == NULL) return SCPE_ARG;\r | |
1300 | \r | |
1301 | /* check this out -- save command hits it in weird way */\r | |
1302 | /* I wish I remembered what I meant when I wrote that */\r | |
1303 | if (addr < MEMSIZE) {\r | |
1304 | *vptr = M[addr] & 0xFFFF;\r | |
1305 | return SCPE_OK;\r | |
1306 | }\r | |
1307 | return SCPE_NXM;\r | |
1308 | }\r | |
1309 | \r | |
1310 | /* ------------------------------------------------------------------------ \r | |
1311 | * Memory deposit\r | |
1312 | * ------------------------------------------------------------------------ */\r | |
1313 | \r | |
1314 | t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)\r | |
1315 | {\r | |
1316 | if (addr < MEMSIZE) {\r | |
1317 | M[addr] = (uint16) (val & 0xFFFF);\r | |
1318 | return SCPE_OK;\r | |
1319 | }\r | |
1320 | return SCPE_NXM;\r | |
1321 | }\r | |
1322 | \r | |
1323 | /* ------------------------------------------------------------------------ \r | |
1324 | * Breakpoint service\r | |
1325 | * ------------------------------------------------------------------------ */\r | |
1326 | \r | |
1327 | t_stat cpu_svc (UNIT *uptr)\r | |
1328 | {\r | |
1329 | if ((ibkpt_addr & ~ILL_ADR_FLAG) == save_ibkpt)\r | |
1330 | ibkpt_addr = save_ibkpt;\r | |
1331 | \r | |
1332 | save_ibkpt = -1;\r | |
1333 | return SCPE_OK;\r | |
1334 | }\r | |
1335 | \r | |
1336 | /* ------------------------------------------------------------------------ \r | |
1337 | * Memory allocation\r | |
1338 | * ------------------------------------------------------------------------ */\r | |
1339 | \r | |
1340 | t_stat cpu_set_size (UNIT *uptr, int32 value, char *cptr, void *desc)\r | |
1341 | {\r | |
1342 | t_bool used;\r | |
1343 | int32 i;\r | |
1344 | \r | |
1345 | if ((value <= 0) || (value > MAXMEMSIZE) || ((value & 0xFFF) != 0))\r | |
1346 | return SCPE_ARG;\r | |
1347 | \r | |
1348 | for (i = value, used = FALSE; i < (int32) MEMSIZE; i++) {\r | |
1349 | if (M[i] != 0) {\r | |
1350 | used = TRUE;\r | |
1351 | break;\r | |
1352 | }\r | |
1353 | }\r | |
1354 | \r | |
1355 | if (used && ! get_yn ("Really truncate memory [N]?", FALSE))\r | |
1356 | return SCPE_OK;\r | |
1357 | \r | |
1358 | for (i = MEMSIZE; i < value; i++) /* clear expanded area */\r | |
1359 | M[i] = 0;\r | |
1360 | \r | |
1361 | MEMSIZE = value;\r | |
1362 | mem_mask = MEMSIZE - 1;\r | |
1363 | \r | |
1364 | return SCPE_OK;\r | |
1365 | }\r | |
1366 | \r | |
1367 | /* processor type */\r | |
1368 | \r | |
1369 | t_stat cpu_set_type (UNIT *uptr, int32 value, char *cptr, void *desc)\r | |
1370 | {\r | |
1371 | REG *r;\r | |
1372 | \r | |
1373 | is_1800 = (value & UNIT_1800) != 0; /* set is_1800 mode flag */\r | |
1374 | \r | |
1375 | for (r = cpu_reg; r->name != NULL; r++) { /* unhide or hide 1800-specific registers & state */\r | |
1376 | if (strnicmp(r->name, "XR", 2) == 0) {\r | |
1377 | if (value & UNIT_1800)\r | |
1378 | CLRBIT(r->flags, REG_HIDDEN|REG_RO);\r | |
1379 | else\r | |
1380 | SETBIT(r->flags, REG_HIDDEN|REG_RO);\r | |
1381 | }\r | |
1382 | }\r | |
1383 | \r | |
1384 | return SCPE_OK;\r | |
1385 | }\r | |
1386 | \r | |
1387 | /* ------------------------------------------------------------------------ \r | |
1388 | * IO function for console switches\r | |
1389 | * ------------------------------------------------------------------------ */\r | |
1390 | \r | |
1391 | void xio_1131_switches (int32 addr, int32 func, int32 modify)\r | |
1392 | {\r | |
1393 | char msg[80];\r | |
1394 | \r | |
1395 | switch (func) {\r | |
1396 | case XIO_READ:\r | |
1397 | WriteW(addr, CES);\r | |
1398 | break;\r | |
1399 | \r | |
1400 | case XIO_SENSE_DEV:\r | |
1401 | ACC = cpu_dsw;\r | |
1402 | break;\r | |
1403 | \r | |
1404 | default:\r | |
1405 | sprintf(msg, "Invalid console switch function %x", func);\r | |
1406 | xio_error(msg);\r | |
1407 | }\r | |
1408 | }\r | |
1409 | \r | |
1410 | /* ------------------------------------------------------------------------ \r | |
1411 | * Illegal IO operation. Not yet sure what the actual CPU does in this case\r | |
1412 | * ------------------------------------------------------------------------ */\r | |
1413 | \r | |
1414 | void xio_error (char *msg)\r | |
1415 | {\r | |
1416 | printf("*** XIO error at %04x: %s\n", prev_IAR, msg);\r | |
1417 | if (cgi) /* if this happens in CGI mode, probably best to halt */\r | |
1418 | break_simulation(STOP_CRASH);\r | |
1419 | }\r | |
1420 | \r | |
1421 | /* ------------------------------------------------------------------------ \r | |
1422 | * register_cmd - add a command to the extensible command table\r | |
1423 | * ------------------------------------------------------------------------ */\r | |
1424 | \r | |
1425 | t_stat register_cmd (char *name, t_stat (*action)(int32 flag, char *ptr), int arg, char *help)\r | |
1426 | {\r | |
1427 | int i;\r | |
1428 | \r | |
1429 | for (i = 0; i < MAX_EXTRA_COMMANDS; i++) { /* find end of command table */\r | |
1430 | if (x_cmds[i].action == action)\r | |
1431 | return SCPE_OK; /* command is already there, just return */\r | |
1432 | if (x_cmds[i].name == NULL)\r | |
1433 | break;\r | |
1434 | }\r | |
1435 | \r | |
1436 | if (i >= (MAX_EXTRA_COMMANDS-1)) { /* no more room (we need room for the NULL) */\r | |
1437 | fprintf(stderr, "The command table is full - rebuild the simulator with more free slots\n");\r | |
1438 | return SCPE_ARG;\r | |
1439 | }\r | |
1440 | \r | |
1441 | x_cmds[i].action = action; /* add new command */\r | |
1442 | x_cmds[i].name = name;\r | |
1443 | x_cmds[i].arg = arg;\r | |
1444 | x_cmds[i].help = help;\r | |
1445 | \r | |
1446 | i++;\r | |
1447 | x_cmds[i].action = NULL; /* move the NULL terminator */\r | |
1448 | x_cmds[i].name = NULL;\r | |
1449 | \r | |
1450 | return SCPE_OK;\r | |
1451 | }\r | |
1452 | \r | |
1453 | #ifdef USE_MY_ECHO_CMD\r | |
1454 | /* ------------------------------------------------------------------------ \r | |
1455 | * echo_cmd - just echo the command line\r | |
1456 | * ------------------------------------------------------------------------ */\r | |
1457 | \r | |
1458 | static t_stat echo_cmd (int flag, char *cptr)\r | |
1459 | {\r | |
1460 | printf("%s\n", cptr);\r | |
1461 | return SCPE_OK;\r | |
1462 | }\r | |
1463 | #endif\r | |
1464 | \r | |
1465 | /* ------------------------------------------------------------------------ \r | |
1466 | * sim_init - initialize simulator upon startup of scp, before reset\r | |
1467 | * ------------------------------------------------------------------------ */\r | |
1468 | \r | |
1469 | void sim_init (void)\r | |
1470 | {\r | |
1471 | sim_gui = ! (sim_switches & SWMASK('G')); /* -g means no GUI */\r | |
1472 | \r | |
1473 | sim_vm_cmd = x_cmds; /* provide list of additional commands */\r | |
1474 | \r | |
1475 | #ifdef GUI_SUPPORT\r | |
1476 | /* set hook routines for GUI command processing */\r | |
1477 | if (sim_gui) {\r | |
1478 | sim_vm_read = &read_cmdline;\r | |
1479 | sim_vm_post = &update_gui;\r | |
1480 | }\r | |
1481 | #endif\r | |
1482 | \r | |
1483 | #ifdef ENABLE_BACKTRACE\r | |
1484 | /* add the BACKTRACE command */\r | |
1485 | register_cmd("BACKTRACE", &backtrace_cmd, 0, "ba{cktrace} {n} list last n branches/skips/interrupts\n");\r | |
1486 | #endif\r | |
1487 | \r | |
1488 | register_cmd("VIEW", &view_cmd, 0, "v{iew} filename view a text file with notepad\n");\r | |
1489 | \r | |
1490 | #ifdef USE_MY_ECHO_CMD\r | |
1491 | register_cmd("ECHO", &echo_cmd, 0, "echo args... echo arguments passed to command\n");\r | |
1492 | #endif\r | |
1493 | }\r | |
1494 | \r | |
1495 | /* ------------------------------------------------------------------------ \r | |
1496 | * archive_backtrace - record a jump, skip, branch or whatever\r | |
1497 | * ------------------------------------------------------------------------ */\r | |
1498 | \r | |
1499 | #ifdef ENABLE_BACKTRACE\r | |
1500 | \r | |
1501 | #define MAXARCHIVE 16\r | |
1502 | \r | |
1503 | static struct tag_arch {\r | |
1504 | int iar;\r | |
1505 | char *inst;\r | |
1506 | } arch[MAXARCHIVE];\r | |
1507 | int narchived = 0, archind = 0;\r | |
1508 | \r | |
1509 | static void archive_backtrace (char *inst)\r | |
1510 | {\r | |
1511 | static int prevind;\r | |
1512 | \r | |
1513 | if (narchived < MAXARCHIVE)\r | |
1514 | narchived++;\r | |
1515 | \r | |
1516 | if (narchived > 0 && arch[prevind].iar == prev_IAR)\r | |
1517 | return;\r | |
1518 | \r | |
1519 | arch[archind].iar = prev_IAR;\r | |
1520 | arch[archind].inst = inst;\r | |
1521 | \r | |
1522 | prevind = archind;\r | |
1523 | archind = (archind+1) % MAXARCHIVE;\r | |
1524 | }\r | |
1525 | \r | |
1526 | static void reset_backtrace (void)\r | |
1527 | {\r | |
1528 | narchived = 0;\r | |
1529 | archind = 0;\r | |
1530 | }\r | |
1531 | \r | |
1532 | void void_backtrace (int afrom, int ato)\r | |
1533 | {\r | |
1534 | int i;\r | |
1535 | \r | |
1536 | afrom &= mem_mask;\r | |
1537 | ato &= mem_mask;\r | |
1538 | \r | |
1539 | for (i = 0; i < narchived; i++)\r | |
1540 | if (arch[i].iar >= afrom && arch[i].iar <= ato)\r | |
1541 | arch[i].inst = "OVERWRITTEN";\r | |
1542 | }\r | |
1543 | \r | |
1544 | static void show_backtrace (int nshow)\r | |
1545 | {\r | |
1546 | int n = narchived, i = archind;\r | |
1547 | \r | |
1548 | if (n > nshow) n = nshow;\r | |
1549 | \r | |
1550 | while (--n >= 0) {\r | |
1551 | i = (i > 0) ? (i-1) : (MAXARCHIVE-1);\r | |
1552 | printf("from %04x (%s) ", arch[i].iar, arch[i].inst);\r | |
1553 | }\r | |
1554 | \r | |
1555 | if (narchived)\r | |
1556 | putchar('\n');\r | |
1557 | }\r | |
1558 | \r | |
1559 | static t_stat backtrace_cmd (int flag, char *cptr)\r | |
1560 | {\r | |
1561 | int n;\r | |
1562 | \r | |
1563 | if ((n = atoi(cptr)) <= 0)\r | |
1564 | n = 6;\r | |
1565 | \r | |
1566 | show_backtrace(n);\r | |
1567 | return SCPE_OK;\r | |
1568 | }\r | |
1569 | #else\r | |
1570 | \r | |
1571 | /* stub this for the disk routine */\r | |
1572 | \r | |
1573 | void void_backtrace (int afrom, int ato)\r | |
1574 | {\r | |
1575 | }\r | |
1576 | \r | |
1577 | #endif\r | |
1578 | \r | |
1579 | /*************************************************************************************\r | |
1580 | * CPU log routines -- attaching a file to the CPU creates a trace of instructions and register values\r | |
1581 | *\r | |
1582 | * Syntax is WEIRD:\r | |
1583 | *\r | |
1584 | * attach cpu logfile log instructions and registers to file "logfile"\r | |
1585 | * attach -f cpu cpu.log log instructions, registers and floating point acc\r | |
1586 | * attach -m cpu mapfile logfile read addresses from "mapfile", log instructions to "logfile"\r | |
1587 | * attach -f -m cpu mapfile logfile same and log floating point stuff too\r | |
1588 | *\r | |
1589 | * mapfile if specified is a list of symbols and addresses of the form:\r | |
1590 | * symbol hexval\r | |
1591 | *\r | |
1592 | * e.g.\r | |
1593 | * FSIN 082E\r | |
1594 | * FARC 09D4\r | |
1595 | * FMPY 09A4\r | |
1596 | * NORM 0976\r | |
1597 | * XMDS 095A\r | |
1598 | * START 021A\r | |
1599 | *\r | |
1600 | * These values are easily obtained from a load map created by\r | |
1601 | * XEQ L\r | |
1602 | *\r | |
1603 | * The log output is of the form\r | |
1604 | *\r | |
1605 | * IAR ACC EXT (flt) XR1 XR2 XR3 CVI FAC OPERATION\r | |
1606 | * --------------- ---- ---- -------- ---- ---- ---- --- ------------- -----------------------\r | |
1607 | * 002a 002a 1234 5381 0.14222 00b3 0236 3f7e CV 1.04720e+000 4c80 BSC I ,0028 \r | |
1608 | * 081d PAUSE+000d 1234 5381 0.14222 00b3 0236 3f7e CV 1.04720e+000 7400 MDM L 00f0,0 (0) \r | |
1609 | * 0820 PAUSE+0010 1234 5381 0.14222 00b3 0236 3f7e CV 1.04720e+000 7201 MDX 2 0001 \r | |
1610 | * 0821 PAUSE+0011 1234 5381 0.14222 00b3 0237 3f7e CV 1.04720e+000 6a03 STX 2 0003 \r | |
1611 | * 0822 PAUSE+0012 1234 5381 0.14222 00b3 0237 3f7e CV 1.04720e+000 6600 LDX L2 0231 \r | |
1612 | * 0824 PAUSE+0014 1234 5381 0.14222 00b3 0231 3f7e CV 1.04720e+000 4c00 BSC L ,0237 \r | |
1613 | * 0237 START+001d 1234 5381 0.14222 00b3 0231 3f7e CV 1.04720e+000 4480 BSI I ,3fff \r | |
1614 | * 082f FSIN +0001 1234 5381 0.14222 00b3 0231 3f7e CV 1.04720e+000 4356 BSI 3 0056 \r | |
1615 | * 3fd5 ILS01+35dd 1234 5381 0.14222 00b3 0231 3f7e CV 1.04720e+000 4c00 BSC L ,08de \r | |
1616 | *\r | |
1617 | * IAR - instruction address register value, optionally including symbol and offset\r | |
1618 | * ACC - accumulator\r | |
1619 | * EXT - extension\r | |
1620 | * flt - ACC+EXT interpreted as the mantissa of a floating pt number (value 0.5 -> 1)\r | |
1621 | * XR* - index registers\r | |
1622 | * CVI - carry, overflow and interrupt indicators\r | |
1623 | * FAC - floating point accumulator (exponent at 125+XR3, mantissa at 126+XR3 and 127+XR3)\r | |
1624 | * OP - opcode value and crude disassembly\r | |
1625 | *\r | |
1626 | * flt and FAC are displayed only when the -f flag is specified in the attach command\r | |
1627 | * The label and offset and displayed only when the -m flag is specified in the attach command\r | |
1628 | *\r | |
1629 | * The register values shown are the values BEFORE the instruction is executed.\r | |
1630 | *************************************************************************************/\r | |
1631 | \r | |
1632 | t_stat fprint_sym (FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw);\r | |
1633 | \r | |
1634 | typedef struct tag_symentry {\r | |
1635 | struct tag_symentry *next;\r | |
1636 | int addr;\r | |
1637 | char sym[6];\r | |
1638 | } SYMENTRY, *PSYMENTRY;\r | |
1639 | \r | |
1640 | static PSYMENTRY syms = NULL;\r | |
1641 | static t_bool new_log, log_fac;\r | |
1642 | \r | |
1643 | static t_stat cpu_attach (UNIT *uptr, char *cptr)\r | |
1644 | {\r | |
1645 | char mapfile[200], buf[200], sym[100];\r | |
1646 | int addr;\r | |
1647 | PSYMENTRY n, prv, s;\r | |
1648 | FILE *fd;\r | |
1649 | \r | |
1650 | remove(cptr); /* delete old log file, if present */\r | |
1651 | new_log = TRUE;\r | |
1652 | log_fac = sim_switches & SWMASK ('F'); /* display the FAC and the ACC/EXT as fixed point. */\r | |
1653 | \r | |
1654 | for (s = syms; s != NULL; s = n) { /* free any old map entries */\r | |
1655 | n = s->next;\r | |
1656 | free(s);\r | |
1657 | }\r | |
1658 | syms = NULL;\r | |
1659 | \r | |
1660 | if (sim_switches & SWMASK('M')) { /* use a map file to display relative addresses */\r | |
1661 | cptr = get_glyph(cptr, mapfile, 0);\r | |
1662 | if (! *mapfile) {\r | |
1663 | printf("/m must be followed by a filename\n");\r | |
1664 | return SCPE_ARG;\r | |
1665 | }\r | |
1666 | if ((fd = fopen(mapfile, "r")) == NULL) {\r | |
1667 | perror(mapfile);\r | |
1668 | return SCPE_OPENERR;\r | |
1669 | }\r | |
1670 | \r | |
1671 | while (fgets(buf, sizeof(buf), fd) != NULL) { /* read symbols & addresses, link in descending address order */\r | |
1672 | if (sscanf(buf, "%s %x", sym, &addr) != 2)\r | |
1673 | continue;\r | |
1674 | if (*buf == ';')\r | |
1675 | continue;\r | |
1676 | \r | |
1677 | for (prv = NULL, s = syms; s != NULL; prv = s, s = s->next) {\r | |
1678 | if (s->addr < addr)\r | |
1679 | break;\r | |
1680 | }\r | |
1681 | \r | |
1682 | if ((n = malloc(sizeof(SYMENTRY))) == NULL) {\r | |
1683 | printf("out of memory reading map!\n");\r | |
1684 | break;\r | |
1685 | }\r | |
1686 | \r | |
1687 | sym[5] = '\0';\r | |
1688 | strcpy(n->sym, sym);\r | |
1689 | upcase(n->sym);\r | |
1690 | n->addr = addr;\r | |
1691 | \r | |
1692 | if (prv == NULL) {\r | |
1693 | n->next = syms;\r | |
1694 | syms = n;\r | |
1695 | }\r | |
1696 | else {\r | |
1697 | n->next = prv->next;\r | |
1698 | prv ->next = n;\r | |
1699 | }\r | |
1700 | }\r | |
1701 | fclose(fd);\r | |
1702 | }\r | |
1703 | \r | |
1704 | return attach_unit(uptr, quotefix(cptr)); /* fix quotes in filenames & attach */\r | |
1705 | }\r | |
1706 | \r | |
1707 | static void trace_instruction (void)\r | |
1708 | {\r | |
1709 | t_value v[2];\r | |
1710 | float fac;\r | |
1711 | short exp;\r | |
1712 | int addr;\r | |
1713 | PSYMENTRY s;\r | |
1714 | long mant, sign;\r | |
1715 | char facstr[20], fltstr[20];\r | |
1716 | \r | |
1717 | if ((cpu_unit.flags & UNIT_ATT) == 0)\r | |
1718 | return;\r | |
1719 | \r | |
1720 | if (new_log) {\r | |
1721 | fseek(cpu_unit.fileref, 0, SEEK_END);\r | |
1722 | new_log = FALSE;\r | |
1723 | \r | |
1724 | fprintf(cpu_unit.fileref, " IAR%s ACC EXT %s XR1 XR2 XR3 CVI %sOPERATION" CRLF,\r | |
1725 | syms ? " " : "", log_fac ? " (flt) " : "", log_fac ? " FAC " : "");\r | |
1726 | fprintf(cpu_unit.fileref, "----%s ---- ---- %s---- ---- ---- --- %s-----------------------" CRLF,\r | |
1727 | syms ? "-----------" : "", log_fac ? "-------- " : "", log_fac ? "------------- " : "");\r | |
1728 | }\r | |
1729 | \r | |
1730 | if (! log_fac) \r | |
1731 | facstr[0] = fltstr[0] = '\0';\r | |
1732 | else {\r | |
1733 | mant = ((ACC & 0xFFFF) << 16) | (EXT & 0xFFFF);\r | |
1734 | if (mant == 0x80000000) {\r | |
1735 | sign = TRUE;\r | |
1736 | fac = 1.f;\r | |
1737 | }\r | |
1738 | else {\r | |
1739 | if ((sign = mant & 0x80000000) != 0)\r | |
1740 | mant = -mant;\r | |
1741 | fac = (float) mant * ((float) 1./ (float) (unsigned long) 0x80000000);\r | |
1742 | }\r | |
1743 | sprintf(fltstr, "%c%.5f ", sign ? '-' : ' ', fac);\r | |
1744 | \r | |
1745 | if (BETWEEN(M[3], 0x300, MEMSIZE-128)) {\r | |
1746 | exp = (short) ((M[M[3]+125] & 0xFF) - 128);\r | |
1747 | mant = (M[M[3]+126] << 8) | ((M[M[3]+127] >> 8) & 0xFF);\r | |
1748 | if ((sign = (mant & 0x00800000)) != 0)\r | |
1749 | mant = (-mant) & 0x00FFFFFF;\r | |
1750 | \r | |
1751 | fac = (float) mant * ((float) 1. / (float) 0x00800000);\r | |
1752 | \r | |
1753 | if (exp > 30) {\r | |
1754 | fac *= (float) (1 << 30);\r | |
1755 | exp -= 30;\r | |
1756 | while (exp > 0)\r | |
1757 | fac *= 2;\r | |
1758 | }\r | |
1759 | else if (exp > 0)\r | |
1760 | fac *= (float) (1 << exp);\r | |
1761 | else if (exp < -30) {\r | |
1762 | fac /= (float) (1 << 30);\r | |
1763 | exp += 30;\r | |
1764 | while (exp < 0)\r | |
1765 | fac /= 2;\r | |
1766 | }\r | |
1767 | else if (exp < 0)\r | |
1768 | fac /= (float) (1 << -exp);\r | |
1769 | \r | |
1770 | sprintf(facstr, "%c%.5e ", sign ? '-' : ' ', fac);\r | |
1771 | }\r | |
1772 | else\r | |
1773 | strcpy(facstr, " ");\r | |
1774 | }\r | |
1775 | \r | |
1776 | addr = IAR & 0xFFFF;\r | |
1777 | fprintf(cpu_unit.fileref, "%04x ", addr);\r | |
1778 | \r | |
1779 | if (syms) {\r | |
1780 | for (s = syms; s != NULL; s = s->next)\r | |
1781 | if (s->addr <= addr)\r | |
1782 | break;\r | |
1783 | \r | |
1784 | if (s == NULL)\r | |
1785 | fprintf(cpu_unit.fileref, " %04x ", addr);\r | |
1786 | else\r | |
1787 | fprintf(cpu_unit.fileref, "%-5s+%04x ", s->sym, addr - s->addr);\r | |
1788 | }\r | |
1789 | \r | |
1790 | fprintf(cpu_unit.fileref, "%04x %04x %s%04x %04x %04x %c%c%c %s",\r | |
1791 | ACC & 0xFFFF, EXT & 0xFFFF, fltstr, M[1] & 0xFFFF, M[2] & 0xFFFF, M[3] & 0xFFFF,\r | |
1792 | C ? 'C' : ' ', V ? 'V' : ' ', (ipl < 0) ? ' ' : (ipl+'0'), facstr);\r | |
1793 | \r | |
1794 | v[0] = M[ IAR & mem_mask];\r | |
1795 | v[1] = M[(IAR+1) & mem_mask];\r | |
1796 | fprint_sym(cpu_unit.fileref, IAR & mem_mask, v, NULL, SWMASK('M')); /* disassemble instruction */\r | |
1797 | \r | |
1798 | fputs(CRLF, cpu_unit.fileref);\r | |
1799 | }\r | |
1800 | \r | |
1801 | void trace_io (char *fmt, ...)\r | |
1802 | {\r | |
1803 | va_list args;\r | |
1804 | \r | |
1805 | if ((cpu_unit.flags & UNIT_ATT) == 0)\r | |
1806 | return;\r | |
1807 | \r | |
1808 | va_start(args, fmt); /* get pointer to argument list */\r | |
1809 | vfprintf(cpu_unit.fileref, fmt, args); /* write errors to cpu log file */\r | |
1810 | va_end(args);\r | |
1811 | \r | |
1812 | fputs(CRLF, cpu_unit.fileref);\r | |
1813 | }\r | |
1814 | \r | |
1815 | void trace_both (char *fmt, ...)\r | |
1816 | {\r | |
1817 | va_list args;\r | |
1818 | \r | |
1819 | if (cpu_unit.flags & UNIT_ATT) {\r | |
1820 | va_start(args, fmt); /* get pointer to argument list */\r | |
1821 | vfprintf(cpu_unit.fileref, fmt, args);\r | |
1822 | va_end(args);\r | |
1823 | fputs(CRLF, cpu_unit.fileref);\r | |
1824 | }\r | |
1825 | \r | |
1826 | va_start(args, fmt); /* get pointer to argument list */\r | |
1827 | vfprintf(stdout, fmt, args);\r | |
1828 | va_end(args);\r | |
1829 | putchar('\n');\r | |
1830 | }\r | |
1831 | \r | |
1832 | /* debugging */\r | |
1833 | \r | |
1834 | void debug_print (char *fmt, ...)\r | |
1835 | {\r | |
1836 | va_list args;\r | |
1837 | \r | |
1838 | va_start(args, fmt);\r | |
1839 | vprintf(fmt, args);\r | |
1840 | if (cpu_unit.flags & UNIT_ATT)\r | |
1841 | vfprintf(cpu_unit.fileref, fmt, args);\r | |
1842 | va_end(args);\r | |
1843 | \r | |
1844 | if (strchr(fmt, '\n') == NULL) { /* be sure to emit a newline */\r | |
1845 | putchar('\n');\r | |
1846 | if (cpu_unit.flags & UNIT_ATT)\r | |
1847 | putc('\n', cpu_unit.fileref);\r | |
1848 | }\r | |
1849 | }\r | |
1850 | \r | |
1851 | #ifdef _WIN32\r | |
1852 | #include <windows.h>\r | |
1853 | #endif\r | |
1854 | \r | |
1855 | /* view_cmd - let user view and/or edit a file (e.g. a printer output file, script, or source deck) */\r | |
1856 | \r | |
1857 | static t_stat view_cmd (int flag, char *cptr)\r | |
1858 | {\r | |
1859 | #ifdef _WIN32\r | |
1860 | char cmdline[256];\r | |
1861 | \r | |
1862 | sprintf(cmdline, "notepad %s", cptr);\r | |
1863 | WinExec(cmdline, SW_SHOWNORMAL);\r | |
1864 | #endif\r | |
1865 | return SCPE_OK;\r | |
1866 | }\r | |
1867 | \r | |
1868 | /* web server version - hooks for CGI mode. These function pointer can be set by the CGI version's main() routine */\r | |
1869 | \r | |
1870 | void (*cgi_start_hook)(void) = NULL; /* these can be defined by a CGI wrapper to do things on start and stop of simulation */\r | |
1871 | void (*cgi_end_hook)(void) = NULL;\r | |
1872 | \r | |
1873 | static void cgi_start (void)\r | |
1874 | {\r | |
1875 | if (cgi_start_hook != NULL)\r | |
1876 | (*cgi_start_hook)();\r | |
1877 | }\r | |
1878 | \r | |
1879 | static void cgi_stop (t_stat reason)\r | |
1880 | {\r | |
1881 | if (cgi_end_hook != NULL)\r | |
1882 | (*cgi_end_hook)();\r | |
1883 | }\r |