From 88916c7bf17b8cf34fe04379b0741a21956cb39e Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Mon, 7 Nov 2022 10:49:45 -1000 Subject: [PATCH] 3B2-700 Initial Public Release This commit introduces dozens of changes to make the 3B2-700 simulator fully functional and ready for wider use. In addition to 3B2-700 availability, this commit includes a tremendous amount of refactoring of the 3B2-400 and common code to make the project structure easier to maintain and reason about. --- 3B2/3b2_cpu.c | 1493 ++++++++++++----- 3B2/3b2_cpu.h | 58 +- 3B2/3b2_csr.h | 6 +- 3B2/3b2_ctc.c | 332 +--- 3B2/3b2_ctc.h | 10 +- 3B2/3b2_defs.h | 16 +- 3B2/3b2_dmac.c | 143 +- 3B2/3b2_dmac.h | 21 +- 3B2/3b2_id.c | 208 ++- 3B2/3b2_id.h | 4 +- 3B2/3b2_if.c | 25 +- 3B2/3b2_if.h | 6 +- 3B2/3b2_io.c | 532 +++--- 3B2/3b2_io.h | 67 +- 3B2/3b2_iu.c | 996 ++++++----- 3B2/3b2_iu.h | 94 +- 3B2/{3b2_rev2_mau.c => 3b2_mau.c} | 260 ++- 3B2/3b2_mau.h | 366 +++- 3B2/3b2_mem.c | 236 +-- 3B2/3b2_mem.h | 61 +- 3B2/3b2_mmu.h | 2 +- 3B2/3b2_ni.c | 402 ++--- 3B2/3b2_ni.h | 22 +- 3B2/3b2_ports.c | 563 +++---- 3B2/3b2_ports.h | 15 +- 3B2/3b2_rev2_csr.c | 24 +- 3B2/3b2_rev2_csr.h | 8 +- 3B2/3b2_rev2_defs.h | 6 +- 3B2/3b2_rev2_mau.h | 383 ----- 3B2/3b2_rev2_mmu.c | 158 +- 3B2/3b2_rev2_mmu.h | 29 +- 3B2/3b2_rev2_sys.c | 4 +- 3B2/3b2_rev2_timer.c | 401 ----- 3B2/3b2_rev2_timer.h | 72 - 3B2/3b2_rev3_csr.c | 56 +- 3B2/3b2_rev3_csr.h | 8 +- 3B2/3b2_rev3_defs.h | 16 +- 3B2/3b2_rev3_mau.c | 31 - 3B2/3b2_rev3_mau.h | 36 - 3B2/3b2_rev3_mmu.c | 281 ++-- 3B2/3b2_rev3_mmu.h | 12 +- 3B2/3b2_rev3_sys.c | 6 +- 3B2/3b2_rev3_timer.h | 79 - 3B2/3b2_stddev.c | 664 +++++--- 3B2/3b2_stddev.h | 98 +- 3B2/3b2_sys.c | 54 +- 3B2/3b2_sys.h | 4 +- 3B2/{3b2_rev3_timer.c => 3b2_timer.c} | 408 ++--- 3B2/3b2_timer.h | 52 +- 3B2/README.md | 127 +- 3B2/rom_rev2.bin | Bin 32768 -> 0 bytes 3B2/rom_rev2_demon.bin | Bin 65536 -> 0 bytes 3B2/rom_rev3.bin | Bin 131072 -> 0 bytes 3B2/tests/3b2_test.ini | 34 - 3B2/tests/rev2_diags.dsk | Bin 737280 -> 0 bytes .../{3B2.vcproj => 3B2-400.vcproj} | 86 +- .../{3B2-600.vcproj => 3B2-700.vcproj} | 66 +- Visual Studio Projects/Simh.sln | 4 +- makefile | 40 +- sim_BuildROMs.c | 3 - 60 files changed, 4595 insertions(+), 4593 deletions(-) rename 3B2/{3b2_rev2_mau.c => 3b2_mau.c} (91%) delete mode 100644 3B2/3b2_rev2_mau.h delete mode 100644 3B2/3b2_rev2_timer.c delete mode 100644 3B2/3b2_rev2_timer.h delete mode 100644 3B2/3b2_rev3_mau.c delete mode 100644 3B2/3b2_rev3_mau.h delete mode 100644 3B2/3b2_rev3_timer.h rename 3B2/{3b2_rev3_timer.c => 3b2_timer.c} (55%) delete mode 100644 3B2/rom_rev2.bin delete mode 100644 3B2/rom_rev2_demon.bin delete mode 100644 3B2/rom_rev3.bin delete mode 100644 3B2/tests/3b2_test.ini delete mode 100644 3B2/tests/rev2_diags.dsk rename Visual Studio Projects/{3B2.vcproj => 3B2-400.vcproj} (89%) rename Visual Studio Projects/{3B2-600.vcproj => 3B2-700.vcproj} (91%) diff --git a/3B2/3b2_cpu.c b/3B2/3b2_cpu.c index c7799d14..b86d2df4 100644 --- a/3B2/3b2_cpu.c +++ b/3B2/3b2_cpu.c @@ -1,6 +1,6 @@ -/* 3b2_cpu.c: AT&T 3B2 CPU (WE32100 and WE32200) Implementation +/* 3b2_cpu.c: WE32100 and WE32200 CPU - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -29,12 +29,17 @@ */ /* - * This is an implementation of the WE32100 and WE32200 CPUs, used in - * the Rev 2 (e.g. 3B2/400) and Rev 3 (e.g. 3B2/600G) AT&T 3B2 - * systems, respectively. + * This is an implementation of the WE 32100 and WE 32200 CPUs, used + * in the Rev 2 (e.g. 3B2/400) and Rev 3 (e.g. 3B2/700) AT&T 3B2 + * computers, respectively. * - * The WE32000 series of microprocessors were a fully 32-bit general - * purpose CISC architecture purposely designed to run UNIX. + * The WE 32K series of microprocessors were fully 32-bit, general + * purpose CISC architecture microprocessors with features designed to + * support UNIX as a primary operating system. In addition to the CPU, + * other members of the WE 32K chipset included: + * + * - WE 32101 and WE 32201 Memory Management Units + * - WE 32106 and WE 32206 Math Accelerator Units * * The architecture is fully described in the following books: * @@ -46,18 +51,9 @@ #include "3b2_cpu.h" #if defined(REV3) -#include "rom_rev3_bin.h" #include "3b2_if.h" -#define ROM_ARRAY BOOT_CODE_ARRAY -#define ROM_SIZE BOOT_CODE_SIZE #else -#include "rom_rev2_bin.h" -#include "rom_rev2_demon_bin.h" #include "3b2_id.h" -#define ROM_ARRAY BOOT_CODE_ARRAY_1 -#define ROM_SIZE BOOT_CODE_SIZE_1 -#define DEMON_ROM_ARRAY BOOT_CODE_ARRAY_2 -#define DEMON_ROM_SIZE BOOT_CODE_SIZE_2 #endif /* defined(REV3) */ #include "3b2_csr.h" @@ -70,9 +66,17 @@ #include "3b2_stddev.h" #include "3b2_timer.h" +/* Up to 128KB ROM allowed */ #define MAX_SUB_RETURN_SKIP 9 -uint32 rom_size = 0; +/* Kernel-privileged registers on write */ +#if defined(REV3) +#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ + (V) == NUM_ISP || (V) == NUM_PC || (V) >= 24) +#else +#define PRIVREG(V) ((V) == NUM_PSW || (V) == NUM_PCBP || \ + (V) == NUM_ISP || (V) == NUM_PC) +#endif /* Static function declarations */ static uint32 cpu_effective_address(operand * op); @@ -109,12 +113,16 @@ static SIM_INLINE void cpu_context_switch_3(uint32 pcbp); static SIM_INLINE t_bool op_is_psw(operand *op); static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst); static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst); +#if defined(REV3) +static SIM_INLINE uint8 add_bcd(uint8 a, uint8 b); +static SIM_INLINE uint8 sub_bcd(uint8 a, uint8 b); +#endif /* RO memory. */ -uint32 *ROM = NULL; +uint8 *ROM = NULL; /* Main memory. */ -uint32 *RAM = NULL; +uint8 *RAM = NULL; /* Save environment for setjmp/longjmp */ jmp_buf save_env; @@ -142,6 +150,8 @@ uint32 R[NUM_REGISTERS]; /* Other global CPU state */ +t_bool rom_loaded = FALSE; /* True if ROM has been loaded, false otherwise */ + /* Interrupt request bitfield */ /* Note: Only the lowest 8 bits are used by Rev 2, and only the lowest 12 bits are used by Rev 3 */ @@ -154,6 +164,9 @@ int32 pc_incr = 0; /* Length (in bytes) of instruction t_bool cpu_ex_halt = FALSE; /* Flag to halt on exceptions / traps */ t_bool cpu_km = FALSE; /* If true, kernel mode has been forced for memory access */ +uint16 cpu_int_ack; /* The most recently acknowledged + interrupt */ + CTAB sys_cmd[] = { { "BOOT", &sys_boot, RU_BOOT, "bo{ot} boot simulator\n", NULL, &run_cmd_message }, @@ -340,15 +353,6 @@ static const char *att3b2_clock_precalibrate_commands[] = { NULL }; -CONST cio_device cio_entries[] = { - {0x0001, "SBD"}, /* System Board */ - {0x0002, "NI"}, /* Network Interface Card (ethernet) */ - {0x0003, "PORTS"}, /* Serial I/O Card */ - {0x0005, "CTC"}, /* Cartridge Tape Controller */ - {0x0100, "SCSI"}, /* SCSI disk and tape controller */ - {0} /* END */ -}; - MTAB cpu_mod[] = { #if defined(REV2) { UNIT_MSIZE, (1u << 20), NULL, "1M", @@ -375,7 +379,7 @@ MTAB cpu_mod[] = { { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "STACK", NULL, NULL, &cpu_show_stack, NULL, "Display the current stack with optional depth" }, { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CIO", NULL, - NULL, &cpu_show_cio, NULL, "Display CIO configuration" }, + NULL, &cpu_show_cio, NULL, "Display backplane configuration" }, { MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle }, { MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL }, { UNIT_EXBRK, UNIT_EXBRK, "Break on exceptions", "EXBRK", @@ -448,15 +452,23 @@ mnemonic ops[256] = { {0x08, 0, OP_NONE, NA, "RET", -1, -1, -1, -1}, #if defined(REV3) {0x09, 3, OP_DESC, WD, "CASWI", 0, 1, -1, 2}, + {0x0a, 0, OP_NONE, NA, "SETX", -1, -1, -1, -1}, + {0x0b, 0, OP_NONE, NA, "CLRX", -1, -1, -1, -1}, #else {0x09, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, -#endif {0x0a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x0b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x0c, 2, OP_DESC, WD, "MOVTRW", 0, -1, -1, 1}, +#if defined(REV3) + {0x0d, 2, OP_DESH, HW, "TEDTH", 1, -1, -1, 0}, + {0x0e, 2, OP_DESC, HW, "PACKB", 0, -1, -1, 1}, + {0x0f, 3, OP_DESC, HW, "UNPACKB", 0, 1, -1, 2}, +#else {0x0d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x0e, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x0f, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x10, 1, OP_DESC, WD, "SAVE", 0, -1, -1, -1}, {0x11, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x12, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, @@ -466,13 +478,21 @@ mnemonic ops[256] = { {0x16, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x17, 2, OP_COPR, WD, "SPOPWT", -1, -1, -1, 1}, {0x18, 1, OP_DESC, WD, "RESTORE", 0, -1, -1, -1}, +#if defined(REV3) + {0x19, 2, OP_DESH, HW, "DTH", 1, -1, -1, 0}, +#else {0x19, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x1a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x1b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x1c, 1, OP_DESC, WD, "SWAPWI", -1, -1, -1, 0}, /* 3-122 252 */ + {0x1c, 1, OP_DESC, WD, "SWAPWI", -1, -1, -1, 0}, +#if defined(REV3) + {0x1d, 2, OP_DESH, HW, "TGEDTH", 1, -1, -1, 0}, +#else {0x1d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x1e, 1, OP_DESC, HW, "SWAPHI", -1, -1, -1, 0}, /* 3-122 252 */ - {0x1f, 1, OP_DESC, BT, "SWAPBI", -1, -1, -1, 0}, /* 3-122 252 */ +#endif + {0x1e, 1, OP_DESC, HW, "SWAPHI", -1, -1, -1, 0}, + {0x1f, 1, OP_DESC, BT, "SWAPBI", -1, -1, -1, 0}, {0x20, 1, OP_DESC, WD, "POPW", -1, -1, -1, 0}, {0x21, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x22, 2, OP_COPR, WD, "SPOPRS", 1, -1, -1, -1}, @@ -482,12 +502,20 @@ mnemonic ops[256] = { {0x26, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x27, 0, OP_NONE, NA, "CFLUSH", -1, -1, -1, -1}, {0x28, 1, OP_DESC, WD, "TSTW", 0, -1, -1, -1}, +#if defined(REV3) + {0x29, 2, OP_DESB, BT, "DTB", 1, -1, -1, 0}, +#else {0x29, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x2a, 1, OP_DESC, HW, "TSTH", 0, -1, -1, -1}, {0x2b, 1, OP_DESC, BT, "TSTB", 0, -1, -1, -1}, {0x2c, 2, OP_DESC, WD, "CALL", 0, -1, -1, 1}, +#if defined(REV3) + {0x2d, 2, OP_DESH, HW, "TGDTH", 1, -1, -1, 0}, +#else {0x2d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0x2e, 0, OP_NONE, NA, "BPT", -1, -1, -1, -1}, +#endif + {0x2e, 0, OP_NONE, NA, "BPT", -1, -1, -1, -1}, /* TODO: Verify */ {0x2f, 0, OP_NONE, NA, "WAIT", -1, -1, -1, -1}, {0x30, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, /* Two-byte instructions */ {0x31, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, @@ -502,7 +530,11 @@ mnemonic ops[256] = { {0x3a, 2, OP_DESC, HW, "BITH", 0, 1, -1, -1}, {0x3b, 2, OP_DESC, BT, "BITB", 0, 1, -1, -1}, {0x3c, 2, OP_DESC, WD, "CMPW", 0, 1, -1, -1}, +#if defined(REV3) + {0x3d, 2, OP_DESH, HW, "TNEDTH", 1, -1, -1, 0}, +#else {0x3d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x3e, 2, OP_DESC, HW, "CMPH", 0, 1, -1, -1}, {0x3f, 2, OP_DESC, BT, "CMPB", 0, 1, -1, -1}, {0x40, 0, OP_NONE, NA, "RGEQ", -1, -1, -1, -1}, @@ -518,39 +550,51 @@ mnemonic ops[256] = { {0x4a, 1, OP_HALF, NA, "BLH", -1, -1, -1, 0}, {0x4b, 1, OP_BYTE, NA, "BLB", -1, -1, -1, 0}, {0x4c, 0, OP_NONE, NA, "RLEQ", -1, -1, -1, -1}, +#if defined(REV3) + {0x4d, 2, OP_DESB, BT, "TEDTB", 1, -1, -1, 0}, +#else {0x4d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x4e, 1, OP_HALF, NA, "BLEH", -1, -1, -1, 0}, {0x4f, 1, OP_BYTE, NA, "BLEB", -1, -1, -1, 0}, - {0x50, 0, OP_NONE, NA, "BGEQU", -1, -1, -1, 0}, /* aka BCC */ + {0x50, 0, OP_NONE, NA, "RGEQU", -1, -1, -1, 0}, /* aka RCC */ {0x51, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x52, 1, OP_HALF, NA, "BGEUH", -1, -1, -1, 0}, /* aka BCCH */ {0x53, 1, OP_BYTE, NA, "BGEUB", -1, -1, -1, 0}, /* aka BCCB */ - {0x54, 0, OP_NONE, NA, "RGTRU", -1, -1, -1, -1}, + {0x54, 0, OP_NONE, NA, "RGTRU", -1, -1, -1, -1}, /* TODO: Implement */ {0x55, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x56, 1, OP_HALF, NA, "BGUH", -1, -1, -1, 0}, {0x57, 1, OP_BYTE, NA, "BGUB", -1, -1, -1, 0}, - {0x58, 0, OP_NONE, NA, "RLSSU", -1, -1, -1, 0}, /* aka BCS */ + {0x58, 0, OP_NONE, NA, "RLSSU", -1, -1, -1, 0}, /* aka RCS */ /* TODO: Implement */ {0x59, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x5a, 1, OP_HALF, NA, "BLUH", -1, -1, -1, 0}, /* aka BCSH */ {0x5b, 1, OP_BYTE, NA, "BLUB", -1, -1, -1, 0}, /* aka BCSB */ {0x5c, 0, OP_NONE, NA, "RLEQU", -1, -1, -1, -1}, +#if defined(REV3) + {0x5d, 2, OP_DESB, HW, "TGEDTB", 1, -1, -1, 0}, +#else {0x5d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x5e, 1, OP_HALF, NA, "BLEUH", -1, -1, -1, 0}, {0x5f, 1, OP_BYTE, NA, "BLEUB", -1, -1, -1, 0}, - {0x60, 0, OP_NONE, NA, "RVC", -1, -1, -1, -1}, + {0x60, 0, OP_NONE, NA, "RVC", -1, -1, -1, -1}, /* TODO: Implement */ {0x61, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x62, 1, OP_HALF, NA, "BVCH", -1, -1, -1, 0}, {0x63, 1, OP_BYTE, NA, "BVCB", -1, -1, -1, 0}, {0x64, 0, OP_NONE, NA, "RNEQU", -1, -1, -1, -1}, {0x65, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x66, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 76 */ - {0x67, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 77*/ - {0x68, 0, OP_NONE, NA, "RVS", -1, -1, -1, -1}, + {0x67, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 77 */ + {0x68, 0, OP_NONE, NA, "RVS", -1, -1, -1, -1}, /* TODO: Implement */ {0x69, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x6a, 1, OP_HALF, NA, "BVSH", -1, -1, -1, 0}, {0x6b, 1, OP_BYTE, NA, "BVSB", -1, -1, -1, 0}, {0x6c, 0, OP_NONE, NA, "REQLU", -1, -1, -1, -1}, +#if defined(REV3) + {0x6d, 2, OP_DESB, BT, "TGDTB", 1, -1, -1, 0}, +#else {0x6d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x6e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 7e */ {0x6f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 7f */ {0x70, 0, OP_NONE, NA, "NOP", -1, -1, -1, -1}, @@ -566,7 +610,11 @@ mnemonic ops[256] = { {0x7a, 1, OP_HALF, NA, "BRH", -1, -1, -1, 0}, {0x7b, 1, OP_BYTE, NA, "BRB", -1, -1, -1, 0}, {0x7c, 0, OP_NONE, NA, "REQL", -1, -1, -1, -1}, +#if defined(REV3) + {0x7d, 2, OP_DESB, BT, "TNEDTB", 1, -1, -1, 0}, +#else {0x7d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x7e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 6e */ {0x7f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 6f */ {0x80, 1, OP_DESC, WD, "CLRW", -1, -1, -1, 0}, @@ -593,10 +641,18 @@ mnemonic ops[256] = { {0x95, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x96, 1, OP_DESC, HW, "DECH", -1, -1, -1, 0}, {0x97, 1, OP_DESC, BT, "DECB", -1, -1, -1, 0}, +#if defined(REV3) + {0x98, 0, OP_NONE, NA, "RETQINT",-1, -1, -1, -1}, /* TODO: Implement */ +#else {0x98, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x99, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x9a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0x9b, 2, OP_DESC, BT, "SUBPB2", 0, -1, -1, 1}, +#else {0x9b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0x9c, 2, OP_DESC, WD, "ADDW2", 0, -1, -1, 1}, {0x9d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0x9e, 2, OP_DESC, HW, "ADDH2", 0, -1, -1, 1}, @@ -604,7 +660,11 @@ mnemonic ops[256] = { {0xa0, 1, OP_DESC, WD, "PUSHW", 0, -1, -1, -1}, {0xa1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xa2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xa3, 2, OP_DESC, BT, "ADDPB2", 0, -1, -1, 1}, +#else {0xa3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0xa4, 2, OP_DESC, WD, "MODW2", 0, -1, -1, 1}, {0xa5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xa6, 2, OP_DESC, HW, "MODH2", 0, -1, -1, 1}, @@ -657,10 +717,14 @@ mnemonic ops[256] = { {0xd5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xd6, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xd7, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, - {0xd8, 3, OP_DESC, WD, "ROTW", 0, 1, -1, 2}, /* 3-108 238 */ + {0xd8, 3, OP_DESC, WD, "ROTW", 0, 1, -1, 2}, {0xd9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xda, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xdb, 3, OP_DESC, BT, "SUBPB3", 0, 1, -1, 2}, +#else {0xdb, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0xdc, 3, OP_DESC, WD, "ADDW3", 0, 1, -1, 2}, {0xdd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xde, 3, OP_DESC, HW, "ADDH3", 0, 1, -1, 2}, @@ -668,7 +732,11 @@ mnemonic ops[256] = { {0xe0, 1, OP_DESC, WD, "PUSHAW", 0, -1, -1, -1}, {0xe1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xe2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#if defined(REV3) + {0xe3, 3, OP_DESC, BT, "ADDPB3", 0, 1, -1, 2}, +#else {0xe3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, +#endif {0xe4, 3, OP_DESC, WD, "MODW3", 0, 1, -1, 2}, {0xe5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, {0xe6, 3, OP_DESC, HW, "MODH3", 0, 1, -1, 2}, @@ -772,55 +840,21 @@ t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc) t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { - uint32 i, j; + uint32 slot; fprintf(st, " SLOT DEVICE\n"); fprintf(st, "---------------------\n"); - for (i = 0; i < CIO_SLOTS; i++) { - /* Find the matching entry for this slot */ - for (j = 0; cio_entries[j].id != 0; j++) { - if (cio_entries[j].id == cio[i].id) { - break; - } - } - - if (cio_entries[j].id == 0) { - fprintf(st, " %d\n", i); + for (slot = 0; slot < CIO_SLOTS; slot++) { + if (cio[slot].populated) { + fprintf(st, " %2d %s\n", slot, cio[slot].name); } else { - fprintf(st, " %d %s\n", i, cio_entries[j].name); + fprintf(st, " %2d -\n", slot); } } return SCPE_OK; } -t_stat cpu_load_rom(uint8 *arrayp, uint32 len) -{ - uint32 i, index, sc, mask, val; - - /* Update global state */ - rom_size = len; - - if (ROM != NULL) { - free(ROM); - } - ROM = (uint32 *) calloc((size_t)(len >> 2), sizeof(uint32)); - if (ROM == NULL) { - return SCPE_MEM; - } - - for (i = 0; i < len; i++) { - val = arrayp[i]; - sc = (~(i & 3) << 3) & 0x1f; - mask = 0xffu << sc; - index = i >> 2; - - ROM[index] = (ROM[index] & ~mask) | (val << sc); - } - - return SCPE_OK; -} - #if defined(REV3) t_stat sys_boot(int32 flag, CONST char *ptr) { @@ -840,11 +874,8 @@ t_stat sys_boot(int32 flag, CONST char *ptr) #else t_stat sys_boot(int32 flag, CONST char *ptr) { - char gbuf[CBUFSIZE] = { 0 }; - int gnum = 0; - uint8 *srcp = ROM_ARRAY; - uint32 len = ROM_SIZE; - t_stat r; + char gbuf[CBUFSIZE]; + size_t len = ROM_SIZE; if ((ptr = get_sim_sw(ptr)) == NULL) { return SCPE_INVSW; @@ -853,20 +884,11 @@ t_stat sys_boot(int32 flag, CONST char *ptr) do { ptr = get_glyph(ptr, gbuf, 0); - if (gbuf[0] && (strcmp(gbuf, "CPU") && strcmp(gbuf, "DEMON"))) { + if (gbuf[0] && (strcmp(gbuf, "CPU"))) { return SCPE_ARG; } - - if (strcmp(gbuf, "DEMON") == 0) { - srcp = DEMON_ROM_ARRAY; - len = DEMON_ROM_SIZE; - } } while (gbuf[0]); - if ((r = cpu_load_rom(srcp, len)) != SCPE_OK) { - return r; - } - return run_cmd(flag, "CPU"); } #endif /* Rev 2 boot */ @@ -889,16 +911,21 @@ t_stat cpu_boot(int32 unit_num, DEVICE *dptr) * in the PCB, if bit I in PSW is set. */ + if (!rom_loaded) { + sim_messagef(SCPE_NXM, "Cannot boot, ROM not loaded.\n"); + return SCPE_STOP; + } + sim_debug(EXECUTE_MSG, &cpu_dev, "CPU Boot/Reset Initiated. PC=%08x SP=%08x\n", R[NUM_PC], R[NUM_SP]); mmu_disable(); - R[NUM_PCBP] = pread_w(0x80); - R[NUM_PSW] = pread_w(R[NUM_PCBP]); - R[NUM_PC] = pread_w(R[NUM_PCBP] + 4); - R[NUM_SP] = pread_w(R[NUM_PCBP] + 8); + R[NUM_PCBP] = pread_w(0x80, BUS_CPU); + R[NUM_PSW] = pread_w(R[NUM_PCBP], BUS_CPU); + R[NUM_PC] = pread_w(R[NUM_PCBP] + 4, BUS_CPU); + R[NUM_SP] = pread_w(R[NUM_PCBP] + 8, BUS_CPU); if (R[NUM_PSW] & PSW_I_MASK) { R[NUM_PSW] &= ~PSW_I_MASK; @@ -927,8 +954,8 @@ t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) *vptr = value; return succ; } else { - if (addr_is_rom(uaddr) || addr_is_mem(uaddr)) { - *vptr = (uint32) pread_b(uaddr); + if (IS_ROM(uaddr) || IS_RAM(uaddr)) { + *vptr = (uint32) pread_b(uaddr, BUS_CPU); return SCPE_OK; } else { *vptr = 0; @@ -944,8 +971,8 @@ t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) if (sw & EX_V_FLAG) { return deposit(uaddr, (uint8) val); } else { - if (addr_is_mem(uaddr)) { - pwrite_b(uaddr, (uint8) val); + if (IS_RAM(uaddr)) { + pwrite_b(uaddr, (uint8) val, BUS_CPU); return SCPE_OK; } else { return SCPE_NXM; @@ -964,8 +991,8 @@ static void build_int_map() for (i = 0; i < INT_MAP_LEN; i++) { #if defined(REV3) if (i & (INT_PWRDWN|INT_BUS_OP|INT_SBERR| - INT_MBERR|INT_BUS_RXF|INT_BUS_TMO| - INT_CLOCK)) { + INT_MBERR|INT_BUS_RXF|INT_BUS_TMO| + INT_CLOCK)) { ipl = CPU_IPL_15; } else if (i & (INT_UART|INT_UART_DMA)) { ipl = CPU_IPL_13; @@ -1004,19 +1031,13 @@ static void build_int_map() t_stat cpu_reset(DEVICE *dptr) { int i; - t_stat r; - /* Link in our special "boot" command so we can boot with both - * "BO{OT}" and "BO{OT} CPU" */ - sim_vm_cmd = sys_cmd; - - /* Set up the pre-calibration routine */ - sim_clock_precalibrate_commands = att3b2_clock_precalibrate_commands; - - /* Populate the interrupt->IPL map */ - build_int_map(); + sim_debug(EXECUTE_MSG, &cpu_dev, "CPU Reset.\n"); if (!sim_is_running) { + /* Populate the interrupt->IPL map */ + build_int_map(); + /* Clear registers */ for (i = 0; i < NUM_REGISTERS; i++) { R[i] = 0; @@ -1024,29 +1045,34 @@ t_stat cpu_reset(DEVICE *dptr) /* Allocate ROM if needed */ if (ROM == NULL) { - if ((r = cpu_load_rom(ROM_ARRAY, ROM_SIZE)) != SCPE_OK) { - return r; - } + ROM = (uint8 *) calloc((size_t)ROM_SIZE, sizeof(uint8)); + } + if (ROM == NULL) { + return SCPE_MEM; } - /* Always re-allocate RAM */ - if (RAM != NULL) { - free(RAM); + /* Allocate RAM if needed */ + if (RAM == NULL) { + RAM = (uint8 *) calloc((size_t)MEM_SIZE, sizeof(uint8)); } - RAM = (uint32 *) calloc((size_t)(MEM_SIZE >> 2), sizeof(uint32)); if (RAM == NULL) { return SCPE_MEM; } sim_vm_is_subroutine_call = cpu_is_pc_a_subroutine_call; + + /* Link in our special "boot" command so we can boot with both + * "BO{OT}" and "BO{OT} CPU" */ + sim_vm_cmd = sys_cmd; + + /* Set up the pre-calibration routine */ + sim_clock_precalibrate_commands = att3b2_clock_precalibrate_commands; + + abort_context = C_NONE; + + cpu_in_wait = FALSE; } - abort_context = C_NONE; - cpu_nmi = FALSE; - - cpu_hist_p = 0; - cpu_in_wait = FALSE; - sim_brk_types = SWMASK('E'); sim_brk_dflt = SWMASK('E'); @@ -1085,6 +1111,9 @@ t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs) case JSB: case CALL: case CALLPS: +#if defined(REV3) + case UCALLPS: +#endif returns[0] = R[NUM_PC] + (unsigned int) (1 - fprint_sym(stdnul, R[NUM_PC], sim_eval, &cpu_unit, SWMASK ('M'))); @@ -1107,6 +1136,10 @@ t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val) int32 vp, inst, i; mnemonic *mn; char reg_name[8]; +#if defined(REV3) + uint8 reg2 = 0; + char reg2_name[8]; +#endif desc = 0; mn = NULL; @@ -1138,10 +1171,10 @@ t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val) for (i = 0; i < mn->op_count; i++) { /* Special cases for non-descriptor opcodes */ - if (mn->mode == OP_BYTE) { + if ((mn->mode == OP_BYTE) || (mn->mode == OP_DESB && i > 0)) { mode = 6; reg = 15; - } else if (mn->mode == OP_HALF) { + } else if (mn->mode == OP_HALF || (mn->mode == OP_DESH && i > 0)) { mode = 5; reg = 15; } else if (mn->mode == OP_COPR) { @@ -1149,8 +1182,49 @@ t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val) reg = 15; } else { desc = (uint8) val[vp++]; + +#if defined(REV3) /* WE 32200 only */ + switch (desc) { + case 0x5b: + /* Get next byte */ + desc = (uint8) val[vp++]; + /* + * Mode 0x10: Auto pre-decrement -(%rx) + * Mode 0x12: Auto post-decrement (%rx)- + * Mode 0x14: Auto pre-increment +(%rx) + * Mode 0x16: Auto post-increment (%rx)- + */ + mode = ((desc >> 5) & 0x7) | 0x10; + reg = desc & 0x1f; + break; + case 0xab: + case 0xbb: + mode = 0xab; + desc = (uint8) val[vp++]; + reg = (desc >> 4) & 0xf; + reg2 = (desc & 0xf) + 16; + break; + case 0xcb: + /* Get next byte */ + desc = (uint8) val[vp++]; + mode = (desc >> 4) & 0xf; + reg = (desc & 0xf) + 16; + break; + case 0xdb: + mode = 0xdb; + desc = (uint8) val[vp++]; + reg = (desc >> 4) & 0xf; + reg2 = (desc & 0xf) + 16; + break; + default: + mode = (desc >> 4) & 0xf; + reg = desc & 0xf; + break; + } +#else /* WE 32100 only */ mode = (desc >> 4) & 0xf; reg = desc & 0xf; +#endif /* Find the expanded data type, if any */ if (mode == 14 && @@ -1280,6 +1354,41 @@ t_stat fprint_sym_m(FILE *of, t_addr addr, t_value *val) fprintf(of, "*$0x%x", w); } break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "-(%s)", reg_name); + break; + case 0x12: /* Auto post-decrement */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "(%s)-", reg_name); + break; + case 0x14: /* Auto pre-increment */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "+(%s)", reg_name); + break; + case 0x16: /* Auto post-increment */ + cpu_register_name(reg, reg_name, 8); + fprintf(of, "(%s)+", reg_name); + break; + case 0xab: + OP_R_B(w, val, vp); + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "%d(%s,%s)", (int8) w, reg2_name, reg_name); + break; + case 0xbb: + OP_R_H(w, val, vp); + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "0x%x(%s,%s)", w, reg2_name, reg_name); + break; + case 0xdb: + cpu_register_name(reg, reg_name, 8); + cpu_register_name(reg2, reg2_name, 8); + fprintf(of, "%s[%s]", reg2_name, reg_name); + break; +#endif default: fprintf(of, ""); break; @@ -1485,6 +1594,9 @@ void cpu_register_name(uint8 reg, char *buf, size_t len) { void cpu_show_operand(FILE *st, operand *op) { char reg_name[8]; +#if defined(REV3) + char reg2_name[8]; +#endif if (op->etype != -1) { switch(op->etype) { @@ -1578,21 +1690,53 @@ void cpu_show_operand(FILE *st, operand *op) case 15: fprintf(st, "&0x%x", (int32)op->embedded.w); break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "-(%s)", reg_name); + break; + case 0x12: /* Auto post-decrement */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "(%s)-", reg_name); + break; + case 0x14: /* Auto pre-increment */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "+(%s)", reg_name); + break; + case 0x16: /* Auto post-increment */ + cpu_register_name(op->reg, reg_name, 8); + fprintf(st, "(%s)+", reg_name); + break; + case 0xab: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "%d(%s,%s)", (int8)op->embedded.b, reg2_name, reg_name); + break; + case 0xbb: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "0x%x(%s,%s)", op->embedded.h, reg2_name, reg_name); + break; + case 0xdb: + cpu_register_name(op->reg, reg_name, 8); + cpu_register_name(op->reg2, reg2_name, 8); + fprintf(st, "%s[%s]", reg2_name, reg_name); + break; +#endif } } t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) { - uint32 *nRAM = NULL; uint32 uval = (uint32) val; + uint8 *nRAM = NULL; if ((val <= 0) || (val > MAXMEMSIZE)) { return SCPE_ARG; } /* Do (re-)allocation for memory. */ - - nRAM = (uint32 *) calloc(uval >> 2, sizeof(uint32)); + nRAM = (uint8 *) calloc(uval, sizeof(uint32)); if (nRAM == NULL) { return SCPE_MEM; @@ -1600,11 +1744,10 @@ t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) free(RAM); RAM = nRAM; + memset(RAM, 0, (size_t)uval * sizeof(uint32)); MEM_SIZE = uval; - memset(RAM, 0, (size_t)(MEM_SIZE >> 2)); - return SCPE_OK; } @@ -1629,7 +1772,7 @@ static SIM_INLINE void clear_instruction(instr *inst) /* * Decode a single descriptor-defined operand from the instruction - * stream. Returns the number of bytes consumed during decode.type + * stream. Returns the number of bytes consumed during decode. */ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etype) { @@ -1638,10 +1781,61 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp operand *oper = &instr->operands[op_number]; /* Read in the descriptor byte */ - desc = read_b(pa + offset++, ACC_IF); + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); +#if defined(REV3) + /* + * Handle addressing modes specific to the WE 32200, not + * implemented in the WE 32100 + */ + switch (desc) { + case 0x5b: /* Auto post / pre-increment */ + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + /* + * Mode 0x10: Auto pre-decrement -(%rx) + * Mode 0x12: Auto post-decrement (%rx)- + * Mode 0x14: Auto pre-increment +(%rx) + * Mode 0x16: Auto post-increment (%rx)- + */ + oper->mode = ((desc >> 5) & 0x7) | 0x10; + oper->reg = desc & 0x1f; + break; + case 0xab: /* Indexed with byte displacement */ + oper->mode = 0xab; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + case 0xbb: /* Indexed with halfword displacement */ + oper->mode = desc; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + case 0xcb: /* Extended format 1: r16 - r31 */ + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->mode = (desc >> 4) & 0xf; + oper->reg = (desc & 0xf) + 16; + break; + case 0xdb: /* Indexed with scaling */ + oper->mode = desc; + desc = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->reg = (desc >> 4) & 0xf; + oper->reg2 = (desc & 0xf) + 16; + break; + default: + oper->mode = (desc >> 4) & 0xf; + oper->reg = desc & 0xf; + break; + } +#else + /* + * WE 32100 only + */ oper->mode = (desc >> 4) & 0xf; oper->reg = desc & 0xf; +#endif + oper->dtype = instr->mn->dtype; oper->etype = *etype; @@ -1657,10 +1851,10 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp case 4: /* Word Immediate, Register Mode */ switch (oper->reg) { case 15: /* Word Immediate */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 24u; + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; oper->data = oper->embedded.w; break; default: /* Register mode */ @@ -1671,8 +1865,8 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp case 5: /* Halfword Immediate, Register Deferred Mode */ switch (oper->reg) { case 15: /* Halfword Immediate */ - oper->embedded.h = (uint16) read_b(pa + offset++, ACC_IF); - oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF)) << 8u; + oper->embedded.h = (uint16) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; oper->data = oper->embedded.h; break; case 11: /* INVALID */ @@ -1686,7 +1880,7 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp case 6: /* Byte Immediate, FP Short Offset */ switch (oper->reg) { case 15: /* Byte Immediate */ - oper->embedded.b = read_b(pa + offset++, ACC_IF); + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); oper->data = oper->embedded.b; break; default: /* FP Short Offset */ @@ -1698,10 +1892,10 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp case 7: /* Absolute, AP Short Offset */ switch (oper->reg) { case 15: /* Absolute */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 24u; + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; oper->data = oper->embedded.w; break; default: /* AP Short Offset */ @@ -1712,30 +1906,30 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp break; case 8: /* Word Displacement */ case 9: /* Word Displacement Deferred */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 24u; + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; oper->data = oper->embedded.w; break; case 10: /* Halfword Displacement */ case 11: /* Halfword Displacement Deferred */ - oper->embedded.h = read_b(pa + offset++, ACC_IF); - oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF)) << 8u; + oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; oper->data = oper->embedded.h; break; case 12: /* Byte Displacement */ case 13: /* Byte Displacement Deferred */ - oper->embedded.b = read_b(pa + offset++, ACC_IF); + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); oper->data = oper->embedded.b; break; case 14: /* Absolute Deferred, Extended-Operand */ switch (oper->reg) { case 15: /* Absolute Deferred */ - oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF); - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 8u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 16u; - oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF)) << 24u; + oper->embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 16u; + oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 24u; break; case 0: case 2: @@ -1754,6 +1948,42 @@ static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number, int8 *etyp break; } break; +#if defined(REV3) + case 0x10: /* Auto pre-decrement */ + case 0x12: /* Auto post-decrement */ + case 0x14: /* Auto pre-increment */ + case 0x16: /* Auto post-increment */ + oper->data = R[oper->reg]; + break; + case 0xab: /* Indexed with byte displacement */ + oper->embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->data = oper->embedded.b; + break; + case 0xbb: /* Indexed with halfword displacement */ + oper->embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8u; + oper->data = oper->embedded.h; + break; + case 0xdb: /* Indexed with scaling */ + switch (op_type(oper)) { + case BT: + case SB: + oper->data = R[oper->reg]; + break; + case HW: + case UH: + oper->data = R[oper->reg] * 2; + break; + case WD: + case UW: + oper->data = R[oper->reg] * 4; + break; + } + + oper->data += R[oper->reg2]; + + break; +#endif default: cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); } @@ -1833,24 +2063,22 @@ uint8 decode_instruction(instr *instr) } switch (mn->mode) { - case OP_NONE: - break; case OP_BYTE: - instr->operands[0].embedded.b = read_b(pa + offset++, ACC_IF); + instr->operands[0].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); instr->operands[0].mode = 6; instr->operands[0].reg = 15; break; case OP_HALF: - instr->operands[0].embedded.h = read_b(pa + offset++, ACC_IF); - instr->operands[0].embedded.h |= read_b(pa + offset++, ACC_IF) << 8; + instr->operands[0].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[0].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; instr->operands[0].mode = 5; instr->operands[0].reg = 15; break; case OP_COPR: - instr->operands[0].embedded.w = (uint32) read_b(pa + offset++, ACC_IF); - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF) << 8; - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF) << 16; - instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF) << 24; + instr->operands[0].embedded.w = (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 8; + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 16; + instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_IF, BUS_CPU) << 24; instr->operands[0].mode = 4; instr->operands[0].reg = 15; @@ -1865,6 +2093,23 @@ uint8 decode_instruction(instr *instr) offset += decode_operand(pa + offset, instr, (uint8) i, &etype); } break; +#if defined(REV3) + case OP_DESB: + /* Operand 0 is a descriptor byte */ + offset += decode_operand(pa + offset, instr, 0, &etype); + /* Operand 1 is a signed byte offset */ + instr->operands[1].embedded.b = read_b(pa + offset++, ACC_IF, BUS_CPU); + break; + case OP_DESH: + /* Operand 0 is a descriptor byte */ + offset += decode_operand(pa + offset, instr, 0, &etype); + /* Operand 1 is a signed byte offset */ + instr->operands[1].embedded.h = read_b(pa + offset++, ACC_IF, BUS_CPU); + instr->operands[1].embedded.h |= (uint16)(read_b(pa + offset++, ACC_IF, BUS_CPU)) << 8; + break; +#endif + default: + break; } return offset; @@ -1875,22 +2120,22 @@ static SIM_INLINE void cpu_context_switch_3(uint32 new_pcbp) if (R[NUM_PSW] & PSW_R_MASK) { R[0] = R[NUM_PCBP] + 64; - R[2] = read_w(R[0], ACC_AF); + R[2] = read_w(R[0], ACC_AF, BUS_CPU); R[0] += 4; while (R[2] != 0) { - R[1] = read_w(R[0], ACC_AF); + R[1] = read_w(R[0], ACC_AF, BUS_CPU); R[0] += 4; /* Execute MOVBLW instruction inside this loop */ while (R[2] != 0) { - write_w(R[1], read_w(R[0], ACC_AF)); + write_w(R[1], read_w(R[0], ACC_AF, BUS_CPU), BUS_CPU); R[2]--; R[0] += 4; R[1] += 4; } - R[2] = read_w(R[0], ACC_AF); + R[2] = read_w(R[0], ACC_AF, BUS_CPU); R[0] += 4; } @@ -1903,10 +2148,10 @@ static SIM_INLINE void cpu_context_switch_2(uint32 new_pcbp) R[NUM_PCBP] = new_pcbp; /* Put new PSW, PC and SP values from PCB into registers */ - R[NUM_PSW] = read_w(R[NUM_PCBP], ACC_AF); + R[NUM_PSW] = read_w(R[NUM_PCBP], ACC_AF, BUS_CPU); R[NUM_PSW] &= ~PSW_TM_MASK; /* Clear TM */ - R[NUM_PC] = read_w(R[NUM_PCBP] + 4, ACC_AF); - R[NUM_SP] = read_w(R[NUM_PCBP] + 8, ACC_AF); + R[NUM_PC] = read_w(R[NUM_PCBP] + 4, ACC_AF, BUS_CPU); + R[NUM_SP] = read_w(R[NUM_PCBP] + 8, ACC_AF, BUS_CPU); /* If i-bit is set, increment PCBP past initial context area */ if (R[NUM_PSW] & PSW_I_MASK) { @@ -1918,29 +2163,29 @@ static SIM_INLINE void cpu_context_switch_2(uint32 new_pcbp) static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) { /* Save the current PC in PCB */ - write_w(R[NUM_PCBP] + 4, R[NUM_PC]); + write_w(R[NUM_PCBP] + 4, R[NUM_PC], BUS_CPU); /* Copy the 'R' flag from the new PSW to the old PSW */ R[NUM_PSW] &= ~PSW_R_MASK; - R[NUM_PSW] |= (read_w(new_pcbp, ACC_AF) & PSW_R_MASK); + R[NUM_PSW] |= (read_w(new_pcbp, ACC_AF, BUS_CPU) & PSW_R_MASK); /* Save current PSW and SP in PCB */ - write_w(R[NUM_PCBP], R[NUM_PSW]); - write_w(R[NUM_PCBP] + 8, R[NUM_SP]); + write_w(R[NUM_PCBP], R[NUM_PSW], BUS_CPU); + write_w(R[NUM_PCBP] + 8, R[NUM_SP], BUS_CPU); /* If R is set, save current R0-R8/FP/AP in PCB */ if (R[NUM_PSW] & PSW_R_MASK) { - write_w(R[NUM_PCBP] + 24, R[NUM_FP]); - write_w(R[NUM_PCBP] + 28, R[0]); - write_w(R[NUM_PCBP] + 32, R[1]); - write_w(R[NUM_PCBP] + 36, R[2]); - write_w(R[NUM_PCBP] + 40, R[3]); - write_w(R[NUM_PCBP] + 44, R[4]); - write_w(R[NUM_PCBP] + 48, R[5]); - write_w(R[NUM_PCBP] + 52, R[6]); - write_w(R[NUM_PCBP] + 56, R[7]); - write_w(R[NUM_PCBP] + 60, R[8]); - write_w(R[NUM_PCBP] + 20, R[NUM_AP]); + write_w(R[NUM_PCBP] + 24, R[NUM_FP], BUS_CPU); + write_w(R[NUM_PCBP] + 28, R[0], BUS_CPU); + write_w(R[NUM_PCBP] + 32, R[1], BUS_CPU); + write_w(R[NUM_PCBP] + 36, R[2], BUS_CPU); + write_w(R[NUM_PCBP] + 40, R[3], BUS_CPU); + write_w(R[NUM_PCBP] + 44, R[4], BUS_CPU); + write_w(R[NUM_PCBP] + 48, R[5], BUS_CPU); + write_w(R[NUM_PCBP] + 52, R[6], BUS_CPU); + write_w(R[NUM_PCBP] + 56, R[7], BUS_CPU); + write_w(R[NUM_PCBP] + 60, R[8], BUS_CPU); + write_w(R[NUM_PCBP] + 20, R[NUM_AP], BUS_CPU); R[NUM_FP] = R[NUM_PCBP] + 52; } @@ -1948,11 +2193,17 @@ static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) void cpu_on_interrupt(uint16 vec) { - uint32 new_pcbp, new_pcbp_ptr; + uint32 new_psw_ptr, new_psw, new_pc; /* Quick-Interrupt */ + uint32 new_pcbp, new_pcbp_ptr; /* Full-Interrupt */ + t_bool quick; + + cpu_int_ack = vec; + + quick = (R[NUM_PSW] & PSW_QIE_MASK) != 0; sim_debug(IRQ_MSG, &cpu_dev, - "[%08x] [cpu_on_interrupt] vec=%02x (%d) sbd_int_req = %x, csr_data = %x\n", - R[NUM_PC], vec, vec, sbd_int_req, csr_data); + "[%08x] [cpu_on_interrupt] vec=%02x (%d), quick=%d, sbd_int_req = %x, csr_data = %x\n", + R[NUM_PC], vec, vec, quick, sbd_int_req, csr_data); /* * "If a nonmaskable interrupt request is received, an auto-vector @@ -1962,39 +2213,93 @@ void cpu_on_interrupt(uint16 vec) */ if (cpu_nmi) { vec = 0; + cpu_nmi = FALSE; } - cpu_nmi = FALSE; cpu_km = TRUE; - if (R[NUM_PSW] & PSW_QIE_MASK) { - /* TODO: Maybe implement quick interrupts at some point, but - the 3B2 ROM and SVR3 don't appear to use them. */ - stop_reason = STOP_ERR; - return; + if (quick) { + /* + * Quick interrupt microsequence + */ + new_psw_ptr = (uint32)0x48c + (8 * (uint32)vec); + + /* Set ISC, TM, and ET to 2, 0, 0 before saving */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (2 << PSW_ISC); + + abort_context = C_RESET_INT_STACK; + + write_w(R[NUM_ISP], R[NUM_PC], BUS_CPU); + write_w(R[NUM_ISP] + 4, R[NUM_PSW], BUS_CPU); + + /* Set ISC, TM, and ET to 1, 0, 0 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (2 << PSW_ISC); + + abort_context = C_RESET_SYSTEM_DATA; + + new_psw = read_w(new_psw_ptr, ACC_AF, BUS_CPU); + + /* Clear out PSW state */ + R[NUM_PSW] &= ~(QIE_PSW_MASK); + + /* Set PM to CM and clear out old CM */ + R[NUM_PSW] |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; + R[NUM_PSW] &= ~(PSW_CM_MASK); + + /* Copy from new PSW */ + R[NUM_PSW] |= (new_psw & QIE_PSW_MASK); + + /* Grab the new PC */ + new_pc = read_w(new_psw_ptr + 4, ACC_AF, BUS_CPU); + + /* Finish ISP stack push */ + R[NUM_ISP] = R[NUM_ISP] + 8; + + /* Set new PSW ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + /* Set new PC */ + R[NUM_PC] = new_pc; + + /* Done */ + abort_context = C_NONE; + } else { + /* + * Full interrupt microsequence + */ + new_pcbp_ptr = (uint32)0x8c + (4 * (uint32)vec); + + abort_context = C_RESET_SYSTEM_DATA; + + new_pcbp = read_w(new_pcbp_ptr, ACC_AF, BUS_CPU); + + abort_context = C_RESET_INT_STACK; + + /* Save the old PCBP */ + irq_push_word(R[NUM_PCBP]); + + /* Set ISC, TM, and ET to 0, 0, 1 before saving */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (1 << PSW_ET); + + /* Context switch */ + cpu_context_switch_1(new_pcbp); + cpu_context_switch_2(new_pcbp); + + /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (7 << PSW_ISC); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_context_switch_3(new_pcbp); + + abort_context = C_NONE; } - new_pcbp_ptr = (uint32)0x8c + (4 * (uint32)vec); - new_pcbp = read_w(new_pcbp_ptr, ACC_AF); - - /* Save the old PCBP */ - irq_push_word(R[NUM_PCBP]); - - /* Set ISC, TM, and ET to 0, 0, 1 before saving */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (1 << PSW_ET); - - /* Context switch */ - cpu_context_switch_1(new_pcbp); - cpu_context_switch_2(new_pcbp); - - /* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ - R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); - R[NUM_PSW] |= (7 << PSW_ISC); - R[NUM_PSW] |= (3 << PSW_ET); - - cpu_context_switch_3(new_pcbp); - cpu_km = FALSE; } @@ -2141,14 +2446,6 @@ t_stat sim_instr(void) cpu_nmi = FALSE; cpu_in_wait = FALSE; cpu_on_interrupt(0); - } else if (sbd_int_req) { - ipl = int_map[sbd_int_req]; - if (PSW_CUR_IPL < ipl) { - /* For the system board, interrupt vector is always - equal to IPL */ - cpu_in_wait = FALSE; - cpu_on_interrupt(ipl); - } } else if (cio_int_req) { for (i = 0; i < CIO_SLOTS; i++) { if ((cio_int_req & (1 << i)) && (PSW_CUR_IPL < cio[i].ipl)) { @@ -2158,12 +2455,18 @@ t_stat sim_instr(void) break; } } + } else if (sbd_int_req) { + ipl = int_map[sbd_int_req]; + if (PSW_CUR_IPL < ipl) { + /* For the system board, interrupt vector is always + equal to IPL */ + cpu_in_wait = FALSE; + cpu_on_interrupt(ipl); + } } if (cpu_in_wait) { - if (sim_idle_enab) { - sim_idle(TMR_CLK, TRUE); - } + sim_idle(TMR_CLK, TRUE); continue; } @@ -2373,9 +2676,7 @@ t_stat sim_instr(void) } break; case BPT: - case HALT: trap = BREAKPOINT_TRAP; - stop_reason = STOP_IBKPT; break; case BRH: pc_incr = sign_extend_h(dst->embedded.h); @@ -2419,32 +2720,13 @@ t_stat sim_instr(void) case CALL: a = cpu_effective_address(src1); b = cpu_effective_address(dst); - write_w(R[NUM_SP] + 4, R[NUM_AP]); - write_w(R[NUM_SP], R[NUM_PC] + pc_incr); + write_w(R[NUM_SP] + 4, R[NUM_AP], BUS_CPU); + write_w(R[NUM_SP], R[NUM_PC] + pc_incr, BUS_CPU); R[NUM_SP] += 8; R[NUM_PC] = b; R[NUM_AP] = a; pc_incr = 0; break; -#if defined(REV3) - case CASWI: - a = cpu_read_op(src1); - b = cpu_read_op(src2); - c = cpu_read_op(dst); - result = c - b; - - if (result == 0) { - cpu_write_op(src2, c); - } else { - cpu_write_op(dst, a); - } - - cpu_set_n_flag((int32)result < 0); - cpu_set_z_flag(result == 0); - cpu_set_c_flag((uint32)b > (uint32)c); - cpu_set_v_flag_op(result, dst); - break; -#endif case CFLUSH: break; case CALLPS: @@ -2469,6 +2751,7 @@ t_stat sim_instr(void) R[NUM_PSW] |= (1 << PSW_ET); cpu_context_switch_1(a); + abort_context = C_PROCESS_NEW_PCB; cpu_context_switch_2(a); R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); @@ -2769,16 +3052,16 @@ t_stat sim_instr(void) break; case GATE: cpu_km = TRUE; - if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF) || - R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF)) { + if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || + R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { sim_debug(EXECUTE_MSG, &cpu_dev, "[%08x] STACK OUT OF BOUNDS IN GATE. " "SP=%08x, R[NUM_PCBP]+12=%08x, " "R[NUM_PCBP]+16=%08x\n", R[NUM_PC], R[NUM_SP], - read_w(R[NUM_PCBP] + 12, ACC_AF), - read_w(R[NUM_PCBP] + 16, ACC_AF)); + read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), + read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); cpu_abort(STACK_EXCEPTION, STACK_BOUND); } cpu_km = FALSE; @@ -2786,7 +3069,7 @@ t_stat sim_instr(void) abort_context = C_STACK_FAULT; /* Push PC+2 onto stack */ - write_w(R[NUM_SP], R[NUM_PC] + 2); + write_w(R[NUM_SP], R[NUM_PC] + 2, BUS_CPU); /* Write 1, 0, 2 to ISC, TM, ET */ R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); @@ -2794,7 +3077,7 @@ t_stat sim_instr(void) R[NUM_PSW] |= (2 << PSW_ET); /* Push PSW onto stack */ - write_w(R[NUM_SP] + 4, R[NUM_PSW]); + write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); abort_context = C_NONE; @@ -2808,12 +3091,13 @@ t_stat sim_instr(void) #if defined(REV3) /* - * NB: The WE32200 processor manual claims that this is - * not a privileged instruction, and that it can be run - * from any processor level. This is not true. Tests in - * the Rev 3 off-line processor diagnostics check to - * ensure that there is an exception raised when calling - * GATE in a non-privileged context. + * Both the WE 32100 and the WE 32200 processor manuals + * state that this is not a privileged instruction, and + * that it can be run from any processor level. This is + * true for the WE 32100, but not true for the WE + * 32200. Tests in the Rev 3 off-line processor + * diagnostics check to ensure that there is an exception + * raised when calling GATE in a non-privileged context. */ if (cpu_execution_level() != EX_LVL_KERN) { cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); @@ -2840,15 +3124,15 @@ t_stat sim_instr(void) break; case MOVBLW: while (R[2] != 0) { - a = read_w(R[0], ACC_AF); - write_w(R[1], a); + a = read_w(R[0], ACC_AF, BUS_CPU); + write_w(R[1], a, BUS_CPU); R[2]--; R[0] += 4; R[1] += 4; } break; case STREND: - while (read_b(R[0], ACC_AF) != '\0') { + while (read_b(R[0], ACC_AF, BUS_CPU) != '\0') { R[0]++; } break; @@ -3059,7 +3343,7 @@ t_stat sim_instr(void) are indeterminate". The ordering here is important. If we decrement SP before writing the results, we end up in a weird, bad state. */ - a = read_w(R[NUM_SP] - 4, ACC_AF); + a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); cpu_write_op(dst, a); R[NUM_SP] -= 4; cpu_set_nz_flags(a, dst); @@ -3107,8 +3391,8 @@ t_stat sim_instr(void) break; case RET: a = R[NUM_AP]; - b = read_w(R[NUM_SP] - 4, ACC_AF); - c = read_w(R[NUM_SP] - 8, ACC_AF); + b = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); + c = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); R[NUM_AP] = b; R[NUM_PC] = c; R[NUM_SP] = a; @@ -3116,8 +3400,8 @@ t_stat sim_instr(void) break; case RETG: abort_context = C_STACK_FAULT; - a = read_w(R[NUM_SP] - 4, ACC_AF); /* PSW */ - b = read_w(R[NUM_SP] - 8, ACC_AF); /* PC */ + a = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); /* PSW */ + b = read_w(R[NUM_SP] - 8, ACC_AF, BUS_CPU); /* PC */ abort_context = C_NONE; if ((a & PSW_CM_MASK) < (R[NUM_PSW] & PSW_CM_MASK)) { sim_debug(EXECUTE_MSG, &cpu_dev, @@ -3166,7 +3450,7 @@ t_stat sim_instr(void) a = irq_pop_word(); /* New process PCBP */ abort_context = C_PROCESS_OLD_PCB; - b = read_w(a, ACC_AF); /* New PSW */ + b = read_w(a, ACC_AF, BUS_CPU); /* New PSW */ abort_context = C_PROCESS_NEW_PCB; /* Copy the 'R' flag from the new PSW to the old PSW */ @@ -3181,17 +3465,17 @@ t_stat sim_instr(void) /* Restore registers if R bit is set */ if (R[NUM_PSW] & PSW_R_MASK) { - R[NUM_FP] = read_w(a + 24, ACC_AF); - R[0] = read_w(a + 28, ACC_AF); - R[1] = read_w(a + 32, ACC_AF); - R[2] = read_w(a + 36, ACC_AF); - R[3] = read_w(a + 40, ACC_AF); - R[4] = read_w(a + 44, ACC_AF); - R[5] = read_w(a + 48, ACC_AF); - R[6] = read_w(a + 52, ACC_AF); - R[7] = read_w(a + 56, ACC_AF); - R[8] = read_w(a + 60, ACC_AF); - R[NUM_AP] = read_w(a + 20, ACC_AF); + R[NUM_FP] = read_w(a + 24, ACC_AF, BUS_CPU); + R[0] = read_w(a + 28, ACC_AF, BUS_CPU); + R[1] = read_w(a + 32, ACC_AF, BUS_CPU); + R[2] = read_w(a + 36, ACC_AF, BUS_CPU); + R[3] = read_w(a + 40, ACC_AF, BUS_CPU); + R[4] = read_w(a + 44, ACC_AF, BUS_CPU); + R[5] = read_w(a + 48, ACC_AF, BUS_CPU); + R[6] = read_w(a + 52, ACC_AF, BUS_CPU); + R[7] = read_w(a + 56, ACC_AF, BUS_CPU); + R[8] = read_w(a + 60, ACC_AF, BUS_CPU); + R[NUM_AP] = read_w(a + 20, ACC_AF, BUS_CPU); } abort_context = C_NONE; @@ -3200,6 +3484,15 @@ t_stat sim_instr(void) cpu_km = FALSE; pc_incr = 0; break; + case INTACK: + R[0] = cpu_int_ack << 2; + break; + case EXTOP: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] EXTOP instruction.\n", + R[NUM_PC]); + cpu_abort(NORMAL_EXCEPTION, RESERVED_OPCODE); + break; case SPOP: /* Memory fault is signaled when no support processor is active */ @@ -3248,17 +3541,23 @@ t_stat sim_instr(void) break; case RESTORE: a = R[NUM_FP] - 28; /* Old FP */ - b = read_w(a, ACC_AF); /* Old FP */ + b = read_w(a, ACC_AF, BUS_CPU); /* Old FP */ c = R[NUM_FP] - 24; /* Old save point */ for (d = src1->reg; d < NUM_FP; d++) { - R[d] = read_w(c, ACC_AF); + R[d] = read_w(c, ACC_AF, BUS_CPU); c += 4; } R[NUM_FP] = b; /* Restore FP */ R[NUM_SP] = a; /* Restore SP */ break; + case RGTRU: + if ((cpu_c_flag() & cpu_z_flag()) == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; case RLEQ: if ((cpu_n_flag() | cpu_z_flag()) == 1) { R[NUM_PC] = cpu_pop_word(); @@ -3277,6 +3576,12 @@ t_stat sim_instr(void) pc_incr = 0; } break; + case RLSSU: + if (cpu_c_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; case REQL: if (cpu_z_flag() == 1) { R[NUM_PC] = cpu_pop_word(); @@ -3293,14 +3598,26 @@ t_stat sim_instr(void) R[NUM_PC] = cpu_pop_word(); pc_incr = 0; break; + case RVC: + if (cpu_v_flag() == 0) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; + case RVS: + if (cpu_v_flag() == 1) { + R[NUM_PC] = cpu_pop_word(); + pc_incr = 0; + } + break; case SAVE: /* Save the FP register */ - write_w(R[NUM_SP], R[NUM_FP]); + write_w(R[NUM_SP], R[NUM_FP], BUS_CPU); /* Save all the registers from the one identified by the src operand up to FP (exclusive) */ for (a = src1->reg, b = 4; a < NUM_FP; a++, b += 4) { - write_w(R[NUM_SP] + b, R[a]); + write_w(R[NUM_SP] + b, R[a], BUS_CPU); } R[NUM_SP] = R[NUM_SP] + 28; @@ -3312,8 +3629,8 @@ t_stat sim_instr(void) * terminator never increments the source or destination * pointer! */ while (1) { - a = read_b(R[0], ACC_AF); - write_b(R[1], (uint8) a); + a = read_b(R[0], ACC_AF, BUS_CPU); + write_b(R[1], (uint8) a, BUS_CPU); if (a == '\0') { break; } @@ -3367,15 +3684,238 @@ t_stat sim_instr(void) cpu_set_c_flag(0); cpu_set_v_flag_op(a, dst); break; - default: - if (cpu_unit.flags & UNIT_OPBRK) { - stop_reason = STOP_OPCODE; +#if defined(REV3) + case ADDPB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + result = add_bcd(a, b); /* sets flags */ + cpu_write_op(dst, result); + break; + case ADDPB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + result = add_bcd(a, b); /* sets flags */ + cpu_write_op(dst, result); + break; + case DTB: + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); } sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] Illegal Opcode 0x%x\n", - R[NUM_PC], inst.mn->opcode); - cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + "[%08x] DTB: dst=%08x r=%08x emb=%04x\n", + R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); break; + case DTH: + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] DTH: dst=%08x r=%08x emb=%04x\n", + R[NUM_PC], a, (uint32)(result & WORD_MASK), src1->embedded.h); + break; + case TEDTB: + if (cpu_z_flag() == 0) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TEDTH: + if (cpu_z_flag() == 0) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TGDTB: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TGDTH: + if ((cpu_n_flag() | cpu_z_flag()) == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TGEDTB: + if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TGEDTH: + if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case TNEDTB: + if (cpu_z_flag() == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_b(src1->embedded.b); + } + } + break; + case TNEDTH: + if (cpu_z_flag() == 1) { + a = cpu_read_op(dst); + result = a - 1; + cpu_write_op(dst, (uint32)(result & WORD_MASK)); + if ((int32)result > -1) { + pc_incr = sign_extend_h(src1->embedded.h); + } + } + break; + case SUBPB2: + a = cpu_read_op(src1); + b = cpu_read_op(dst); + result = sub_bcd(b, a); /* sets flags */ + cpu_write_op(dst, result); + break; + case SUBPB3: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + result = sub_bcd(b, a); /* sets flags */ + cpu_write_op(dst, result); + break; + case PACKB: + a = cpu_read_op(src1); + b = ((a & 0x0f00) >> 4) | (a & 0xf); + cpu_write_op(dst, b); + break; + case UNPACKB: + a = cpu_read_op(src1); /* d1, d0 */ + b = cpu_read_op(src2); /* d3, d2 */ + c = ((b & 0xf0) << 8) | ((a & 0xf0) << 4) | + ((b & 0xf) << 4) | ((a & 0xf)); /* d3, d1, d2, d0 */ + cpu_write_op(dst, c); + break; + case CASWI: + a = cpu_read_op(src1); + b = cpu_read_op(src2); + c = cpu_read_op(dst); + result = c - b; + + if (result == 0) { + cpu_write_op(dst, a); + } else { + cpu_write_op(src2, c); + } + + cpu_set_n_flag((int32)result < 0); + cpu_set_z_flag(result == 0); + cpu_set_c_flag((uint32)b > (uint32)c); + cpu_set_v_flag_op(result, dst); + break; + case SETX: + R[NUM_PSW] |= (1 << PSW_X); + break; + case CLRX: + R[NUM_PSW] &= ~(1 << PSW_X); + break; + case RETQINT: + abort_context = C_RESET_INT_STACK; + + /* Get old PSW value from interrupt stack */ + a = read_w(R[NUM_ISP] - 4, ACC_AF, BUS_CPU); + + /* Update PSW */ + R[NUM_PSW] &= ~(QIE_PSW_MASK); + R[NUM_PSW] |= (a & QIE_PSW_MASK); + + /* Get old PC value from interrupt stack */ + b = read_w(R[NUM_ISP] - 8, ACC_AF, BUS_CPU); + + /* Set new PC */ + R[NUM_PC] = b; + + /* Finish interrupt stack pop */ + R[NUM_ISP] = R[NUM_ISP] - 8; + + /* Set new PSW ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + /* Done */ + abort_context = C_NONE; + break; + case UCALLPS: + if ((R[NUM_PSW] & PSW_EXUC_MASK) == 0) { + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); + } + + cpu_km = TRUE; + + /* Get the new PCBP */ + abort_context = C_RESET_SYSTEM_DATA; + a = read_w(0x488, ACC_AF, BUS_CPU); + + /* Save the existing PCBP */ + abort_context = C_RESET_INT_STACK; + irq_push_word(R[NUM_PCBP]); + + /* Prepare the new PC to be pushed on the stack */ + R[NUM_PC] = R[NUM_PC] + 2; + + /* Set ISC/TM/ET to 0/0/1 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 1 << PSW_ET; + + cpu_context_switch_1(a); + abort_context = C_PROCESS_NEW_PCB; + cpu_context_switch_2(a); + + /* Set ISC/TM/ET to 7/0/3 */ + R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= 7 << PSW_ISC; + R[NUM_PSW] |= 3 << PSW_ET; + + cpu_context_switch_3(a); + + abort_context = C_NONE; + cpu_km = FALSE; + + break; +#endif + default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] Illegal Opcode 0x%x\n", + R[NUM_PC], cpu_instr->mn->opcode); + cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); }; /* Increment the PC appropriately */ @@ -3401,11 +3941,28 @@ t_stat sim_instr(void) static SIM_INLINE void cpu_on_process_exception(uint8 isc) { - /* TODO: Handle */ - sim_debug(ERR_MSG, &cpu_dev, - "[%08x] CPU_ON_PROCESS_EXCEPTION not yet implemented.\n", - R[NUM_PC]); - stop_reason = STOP_EX; + uint32 new_pcbp; + + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cpu_on_process_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + + cpu_km = TRUE; + + abort_context = C_RESET_SYSTEM_DATA; + new_pcbp = read_w(0x84, ACC_AF, BUS_CPU); + + abort_context = C_RESET_INT_STACK; + irq_push_word(R[NUM_PCBP]); + + cpu_context_switch_2(new_pcbp); + + /* Set TM and ET to 0 and 3 in new PSW */ + R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); + R[NUM_PSW] |= (3 << PSW_ET); + + cpu_km = FALSE; + abort_context = C_NONE; return; } @@ -3414,8 +3971,8 @@ static SIM_INLINE void cpu_on_reset_exception(uint8 isc) uint32 new_pcbp; sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] [cpu_on_reset_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", - R[NUM_PC], isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + "[cpu_on_reset_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); if (isc == EXTERNAL_RESET) { R[NUM_PSW] &= ~(PSW_R_MASK); @@ -3426,7 +3983,7 @@ static SIM_INLINE void cpu_on_reset_exception(uint8 isc) mmu_disable(); abort_context = C_RESET_SYSTEM_DATA; - new_pcbp = read_w(0x80, ACC_AF); + new_pcbp = read_w(0x80, ACC_AF, BUS_CPU); abort_context = C_RESET_NEW_PCB; cpu_context_switch_2(new_pcbp); @@ -3440,12 +3997,12 @@ static SIM_INLINE void cpu_on_stack_exception(uint8 isc) uint32 new_pcbp; sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] [cpu_on_stack_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", - R[NUM_PC], isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); + "[cpu_on_stack_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", + isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); abort_context = C_RESET_SYSTEM_DATA; cpu_km = TRUE; - new_pcbp = read_w(0x88, ACC_AF); + new_pcbp = read_w(0x88, ACC_AF, BUS_CPU); abort_context = C_RESET_INT_STACK; irq_push_word(R[NUM_PCBP]); @@ -3470,20 +4027,19 @@ static SIM_INLINE void cpu_on_stack_exception(uint8 isc) static SIM_INLINE void cpu_on_normal_exception(uint8 isc) { sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] [cpu_on_normal_exception %d] %%sp=%08x abort_context=%d\n", - R[NUM_PC], isc, R[NUM_SP], abort_context); + "[cpu_on_normal_exception %d] %%sp=%08x abort_context=%d\n", + isc, R[NUM_SP], abort_context); cpu_km = TRUE; - if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF) || - R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF)) { + if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU) || + R[NUM_SP] > read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)) { sim_debug(EXECUTE_MSG, &cpu_dev, - "[%08x] STACK OUT OF BOUNDS IN EXCEPTION HANDLER. " + "STACK OUT OF BOUNDS IN EXCEPTION HANDLER. " "SP=%08x, R[NUM_PCBP]+12=%08x, " "R[NUM_PCBP]+16=%08x\n", - R[NUM_PC], R[NUM_SP], - read_w(R[NUM_PCBP] + 12, ACC_AF), - read_w(R[NUM_PCBP] + 16, ACC_AF)); + read_w(R[NUM_PCBP] + 12, ACC_AF, BUS_CPU), + read_w(R[NUM_PCBP] + 16, ACC_AF, BUS_CPU)); cpu_abort(STACK_EXCEPTION, STACK_BOUND); } cpu_km = FALSE; @@ -3491,14 +4047,14 @@ static SIM_INLINE void cpu_on_normal_exception(uint8 isc) /* Set context for STACK (FAULT) */ abort_context = C_STACK_FAULT; /* Save address of next instruction to stack */ - write_w(R[NUM_SP], R[NUM_PC]); + write_w(R[NUM_SP], R[NUM_PC], BUS_CPU); /* Write 0, 3 to TM, ET fields of PSW */ R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); R[NUM_PSW] |= (3 << PSW_ET); /* Save PSW to stack */ - write_w(R[NUM_SP] + 4, R[NUM_PSW]); + write_w(R[NUM_SP] + 4, R[NUM_PSW], BUS_CPU); /* Set context for RESET (GATE VECTOR) */ abort_context = C_RESET_GATE_VECTOR; @@ -3516,10 +4072,10 @@ static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) abort_context = C_NORMAL_GATE_VECTOR; cpu_km = TRUE; - gate_l2 = read_w(index1, ACC_AF) + index2; + gate_l2 = read_w(index1, ACC_AF, BUS_CPU) + index2; /* Get new PSW from second-level table */ - new_psw = read_w(gate_l2, ACC_AF); + new_psw = read_w(gate_l2, ACC_AF, BUS_CPU); /* Clear state in PSW */ new_psw &= ~(PSW_PM_MASK|PSW_IPL_MASK|PSW_R_MASK| @@ -3535,7 +4091,7 @@ static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) new_psw |= (1 << PSW_TM); /* TM */ new_psw |= (3 << PSW_ET); /* ET */ - R[NUM_PC] = read_w(gate_l2 + 4, ACC_AF); + R[NUM_PC] = read_w(gate_l2 + 4, ACC_AF, BUS_CPU); R[NUM_PSW] = new_psw; cpu_km = FALSE; @@ -3551,6 +4107,10 @@ static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) */ static uint32 cpu_effective_address(operand *op) { +#if defined(REV3) + uint32 tmp; +#endif + /* Register Deferred */ if (op->mode == 5 && op->reg != 11) { return R[op->reg]; @@ -3564,7 +4124,7 @@ static uint32 cpu_effective_address(operand *op) /* Absolute Deferred */ if (op->mode == 14 && op->reg == 15) { /* May cause exception */ - return read_w(op->embedded.w, ACC_AF); + return read_w(op->embedded.w, ACC_AF, BUS_CPU); } /* FP Short Offset */ @@ -3584,7 +4144,7 @@ static uint32 cpu_effective_address(operand *op) /* Word Displacement Deferred */ if (op->mode == 9) { - return read_w(R[op->reg] + op->embedded.w, ACC_AF); + return read_w(R[op->reg] + op->embedded.w, ACC_AF, BUS_CPU); } /* Halfword Displacement */ @@ -3594,7 +4154,7 @@ static uint32 cpu_effective_address(operand *op) /* Halfword Displacement Deferred */ if (op->mode == 11) { - return read_w(R[op->reg] + sign_extend_h(op->embedded.h), ACC_AF); + return read_w(R[op->reg] + sign_extend_h(op->embedded.h), ACC_AF, BUS_CPU); } /* Byte Displacement */ @@ -3604,9 +4164,129 @@ static uint32 cpu_effective_address(operand *op) /* Byte Displacement Deferred */ if (op->mode == 13) { - return read_w(R[op->reg] + sign_extend_b(op->embedded.b), ACC_AF); + return read_w(R[op->reg] + sign_extend_b(op->embedded.b), ACC_AF, BUS_CPU); } +#if defined(REV3) + /* Auto pre-decrement */ + if (op->mode == 0x10) { + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] -= 1; + break; + case HW: + case UH: + R[op->reg] -= 2; + break; + case WD: + case UW: + R[op->reg] -= 4; + break; + } + return R[op->reg]; + } + + /* Auto post-decrement */ + if (op->mode == 0x12) { + tmp = R[op->reg]; + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] -= 1; + break; + case HW: + case UH: + R[op->reg] -= 2; + break; + case WD: + case UW: + R[op->reg] -= 4; + break; + } + return tmp; + } + + /* Auto pre-increment */ + if (op->mode == 0x14) { + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] += 1; + break; + case HW: + case UH: + R[op->reg] += 2; + break; + case WD: + case UW: + R[op->reg] += 4; + break; + } + return R[op->reg]; + } + + /* Auto post-increment */ + if (op->mode == 0x16) { + tmp = R[op->reg]; + switch(op_type(op)) { + case BT: + case SB: + R[op->reg] += 1; + break; + case HW: + case UH: + R[op->reg] += 2; + break; + case WD: + case UW: + R[op->reg] += 4; + break; + } + return tmp; + } + + /* Indexed with byte displacement */ + if (op->mode == 0xab) { + tmp = sign_extend_b(op->embedded.b); + tmp += R[op->reg]; + tmp += R[op->reg2]; + return tmp; + } + + /* Indexed with halfword displacement */ + if (op->mode == 0xbb) { + tmp = sign_extend_h(op->embedded.h); + tmp += R[op->reg]; + tmp += R[op->reg2]; + return tmp; + } + + /* Indexed with scaling */ + if (op->mode == 0xdb) { + switch(op_type(op)) { + case BT: + case SB: + tmp = R[op->reg]; + break; + case HW: + case UH: + tmp = R[op->reg] * 2; + break; + case WD: + case UW: + tmp = R[op->reg] * 4; + break; + default: + /* We should never reach this */ + tmp = 0; + break; + } + + return tmp + R[op->reg2]; + } +#endif + if (cpu_unit.flags & UNIT_OPBRK) { stop_reason = STOP_OPCODE; } @@ -3685,7 +4365,7 @@ static uint32 cpu_read_op(operand * op) uint32 data; /* Register */ - if (op->mode == 4 && op->reg < 15) { + if (op->mode == 4 && op->reg != 15) { switch (op_type(op)) { case WD: case UW: @@ -3704,6 +4384,9 @@ static uint32 cpu_read_op(operand * op) data = sign_extend_b(R[op->reg] & BYTE_MASK); break; default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (1): %d\n", + R[NUM_PC], op_type(op)); stop_reason = STOP_ERR; data = 0; break; @@ -3748,26 +4431,29 @@ static uint32 cpu_read_op(operand * op) switch (op_type(op)) { case WD: /* Signed Word */ case UW: /* Unsigned Word */ - data = read_w(eff, ACC_OF); + data = read_w(eff, ACC_OF, BUS_CPU); op->data = data; return data; case HW: /* Signed Halfword */ - data = sign_extend_h(read_h(eff, ACC_OF)); + data = sign_extend_h(read_h(eff, ACC_OF, BUS_CPU)); op->data = data; return data; case UH: /* Unsigned Halfword */ - data = read_h(eff, ACC_OF); + data = read_h(eff, ACC_OF, BUS_CPU); op->data = data; return data; case SB: /* Signed Byte */ - data = sign_extend_b(read_b(eff, ACC_OF)); + data = sign_extend_b(read_b(eff, ACC_OF, BUS_CPU)); op->data = data; return data; case BT: /* Unsigned Byte */ - data = read_b(eff, ACC_OF); + data = read_b(eff, ACC_OF, BUS_CPU); op->data = data; return data; default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (2): %d\n", + R[NUM_PC], op_type(op)); stop_reason = STOP_ERR; return 0; } @@ -3780,9 +4466,8 @@ static void cpu_write_op(operand * op, t_uint64 val) op->data = (uint32) val; /* Writing to a register. */ - if (op->mode == 4 && op->reg < 15) { - if ((op->reg == NUM_PSW || op->reg == NUM_PCBP || op->reg == NUM_ISP) && - cpu_execution_level() != EX_LVL_KERN) { + if (op->mode == 4 && op->reg != 15) { + if (PRIVREG(op->reg) && cpu_execution_level() != EX_LVL_KERN) { cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_REGISTER); return; } @@ -3816,17 +4501,20 @@ static void cpu_write_op(operand * op, t_uint64 val) switch (op_type(op)) { case UW: case WD: - write_w(eff, (uint32) val); + write_w(eff, (uint32) val, BUS_CPU); break; case HW: case UH: - write_h(eff, val & HALF_MASK); + write_h(eff, val & HALF_MASK, BUS_CPU); break; case SB: case BT: - write_b(eff, val & BYTE_MASK); + write_b(eff, val & BYTE_MASK, BUS_CPU); break; default: + sim_debug(EXECUTE_MSG, &cpu_dev, + "[%08x] cpu_read_op: unknown op type (3): %d\n", + R[NUM_PC], op_type(op)); stop_reason = STOP_ERR; break; } @@ -3890,6 +4578,13 @@ static SIM_INLINE t_bool cpu_v_flag() return (R[NUM_PSW] & PSW_V_MASK) != 0; } +#if defined(REV3) +static SIM_INLINE t_bool cpu_x_flag() +{ + return (R[NUM_PSW] & PSW_X_MASK) != 0; +} +#endif + static SIM_INLINE void cpu_set_z_flag(t_bool val) { if (val) { @@ -3917,6 +4612,17 @@ static SIM_INLINE void cpu_set_c_flag(t_bool val) } } +#if defined(REV3) +static SIM_INLINE void cpu_set_x_flag(t_bool val) +{ + if (val) { + R[NUM_PSW] |= PSW_X_MASK; + } else { + R[NUM_PSW] &= ~PSW_X_MASK; + } +} +#endif + static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op) { switch(op_type(op)) { @@ -3973,7 +4679,7 @@ static void cpu_set_nz_flags(t_uint64 data, operand *dst) static SIM_INLINE void cpu_push_word(uint32 val) { - write_w(R[NUM_SP], val); + write_w(R[NUM_SP], val, BUS_CPU); R[NUM_SP] += 4; } @@ -3982,21 +4688,21 @@ static SIM_INLINE uint32 cpu_pop_word() uint32 result; /* We always read fromthe stack first BEFORE decrementing, in case this causes a fault. */ - result = read_w(R[NUM_SP] - 4, ACC_AF); + result = read_w(R[NUM_SP] - 4, ACC_AF, BUS_CPU); R[NUM_SP] -= 4; return result; } static SIM_INLINE void irq_push_word(uint32 val) { - write_w(R[NUM_ISP], val); + write_w(R[NUM_ISP], val, BUS_CPU); R[NUM_ISP] += 4; } static SIM_INLINE uint32 irq_pop_word() { R[NUM_ISP] -= 4; - return read_w(R[NUM_ISP], ACC_AF); + return read_w(R[NUM_ISP], ACC_AF, BUS_CPU); } static SIM_INLINE t_bool op_is_psw(operand *op) @@ -4055,6 +4761,68 @@ static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst) } } +#if defined(REV3) +/* + * Return the packed BCD byte result of adding two packed BCD input + * bytes. This will set the C and X carry flags appropraitely if there + * is a carry. + */ +static SIM_INLINE uint8 add_bcd(uint8 packed_a, uint8 packed_b) +{ + uint16 l, h, result; + + l = (packed_a & 0x0f) + (packed_b & 0x0f) + (cpu_x_flag() ? 1 : 0); + if ((l & 0xff) > 9) { + l += 6; + } + + h = ((packed_a >> 4) & 0x0f) + ((packed_b >> 4) & 0x0f) + (l > 15 ? 1 : 0); + if ((h & 0xff) > 9) { + h += 6; + } + + result = ((l & 0x0f) | (h << 4)) & 0xff; + + cpu_set_c_flag(h > 15); + cpu_set_x_flag(h > 15); + cpu_set_z_flag(result == 0); + cpu_set_n_flag(0); + cpu_set_v_flag(0); + + return (uint8)result; +} + +/* + * Return the packed BCD byte result of subtracting two packed BCD + * input bytes. This will set the C and X carry flags appropraitely if + * there is a carry. + */ +static SIM_INLINE uint8 sub_bcd(uint8 packed_a, uint8 packed_b) +{ + uint16 l, h, result; + + l = (packed_a & 0x0f) - (packed_b & 0x0f) - (cpu_x_flag() ? 1 : 0); + if ((l & 0x10) != 0) { + l -= 6; + } + + h = ((packed_a >> 4) & 0x0f) - ((packed_b >> 4) & 0x0f) - ((l & 0x10) != 0 ? 1 : 0); + if ((h & 0x10) != 0) { + h -= 6; + } + + result = ((l & 0x0f)|(h << 4)) & 0xff; + + cpu_set_c_flag(h > 15); + cpu_set_x_flag(h > 15); + cpu_set_z_flag(result == 0); + cpu_set_n_flag(0); + cpu_set_v_flag(0); + + return (uint8)result; +} +#endif + /* * Set PSW's ET and ISC fields, and store global exception or fault * state appropriately. @@ -4077,7 +4845,7 @@ void cpu_abort(uint8 et, uint8 isc) CONST char *cpu_description(DEVICE *dptr) { #if defined(REV3) - return "3B2/600G CPU (WE 32200)"; + return "3B2/700 CPU (WE 32200)"; #else return "3B2/400 CPU (WE 32100)"; #endif @@ -4086,44 +4854,21 @@ CONST char *cpu_description(DEVICE *dptr) t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { #if defined(REV3) - fprintf(st, "3B2/600G CPU Help\n\n"); - fprintf(st, "The 3B2/600G CPU simulates a WE 32200 at 24 MHz.\n\n"); + fprintf(st, "3B2/700 CPU Help\n\n"); + fprintf(st, "The 3B2/700 CPU simulates a WE 32200 at 22 MHz.\n\n"); #else fprintf(st, "3B2/400 CPU Help\n\n"); fprintf(st, "The 3B2/400 CPU simulates a WE 32100 at 10 MHz.\n\n"); #endif - fprintf(st, "CPU options include the size of main memory.\n\n"); - if (dptr->modifiers) { - MTAB *mptr; - for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) { - if (mptr->valid == &cpu_set_size) { - fprintf(st, " sim> SET CPU %3s set memory size = %sB\n", - mptr->mstring, mptr->mstring); - } - } - fprintf(st, "\n"); - } - fprintf(st, "The CPU also implements a command to display a virtual to physical address\n"); - fprintf(st, "translation:\n\n"); - fprintf(st, " sim> SHOW CPU VIRTUAL=n show translation for address n\n\n"); - fprintf(st, "The CPU attempts to detect when the simulator is idle. When idle, the\n"); - fprintf(st, "simulator does not use any resources on the host system. Idle detetion is\n"); - fprintf(st, "controlled by the SET CPU IDLE and SET CPU NOIDLE commands:\n\n"); - fprintf(st, " sim> SET CPU IDLE enable idle detection\n"); - fprintf(st, " sim> SET CPU NOIDLE disable idle detection\n\n"); - fprintf(st, "Idle detection is disabled by default.\n\n"); - fprintf(st, "The CPU can maintain a history of the most recently executed instructions.\n"); - fprintf(st, "This is controlled by the SET CPU HISTORY and SHOW CPU HISTORY commands:\n\n"); - fprintf(st, " sim> SET CPU HISTORY clear history buffer\n"); - fprintf(st, " sim> SET CPU HISTORY=0 disable history\n"); - fprintf(st, " sim> SET CPU HISTORY=n enable history, length = n\n"); - fprintf(st, " sim> SHOW CPU HISTORY print CPU history\n"); - fprintf(st, " sim> SHOW CPU HISTORY=n print last n entries of CPU history\n\n"); + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + #if defined(REV3) - fprintf(st, "Additional documentation for the 3B2/600G Simulator is available on the web:\n\n"); + fprintf(st, "\nAdditional documentation for the 3B2/700 Simulator is available on the web:\n\n"); #else - fprintf(st, "Additional documentation for the 3B2/400 Simulator is available on the web:\n\n"); + fprintf(st, "\nAdditional documentation for the 3B2/400 Simulator is available on the web:\n\n"); #endif fprintf(st, " https://loomcom.com/3b2/emulator.html\n\n"); diff --git a/3B2/3b2_cpu.h b/3B2/3b2_cpu.h index b6f9dfa7..d7b0314b 100644 --- a/3B2/3b2_cpu.h +++ b/3B2/3b2_cpu.h @@ -1,6 +1,6 @@ -/* 3b2_cpu.h: AT&T 3B2 CPU (WE32100 and WE32200) Header +/* 3b2_cpu.h: WE32100 and WE32200 CPU - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -127,6 +127,29 @@ #define PSW_CFD_MASK (1u << PSW_CFD) #define PSW_CUR_IPL (((R[NUM_PSW] & PSW_IPL_MASK) >> PSW_IPL) & 0xf) +#if defined(REV3) +#define PSW_X 26 +#define PSW_AR 27 +#define PSW_EXUC 28 +#define PSW_EA 29 + +#define PSW_X_MASK (1u << PSW_X) +#define PSW_AR_MASK (1u << PSW_AR) +#define PSW_EXUC_MASK (1u << PSW_EXUC) +#define PSW_EA_MASK (1u << PSW_EA) +#endif + +#if defined(REV3) +#define QIE_PSW_MASK (PSW_EA_MASK|PSW_EXUC_MASK|PSW_X_MASK| \ + PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ + PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ + PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) +#else +#define QIE_PSW_MASK (PSW_CFD_MASK|PSW_QIE_MASK|PSW_CD_MASK|PSW_OE_MASK| \ + PSW_N_MASK|PSW_Z_MASK|PSW_V_MASK|PSW_C_MASK| \ + PSW_TE_MASK|PSW_IPL_MASK|PSW_PM_MASK|PSW_I_MASK) +#endif + /* A helper to set the PSW, preserving read-only fields */ #define PSW_RO_MASK 0x17f /* ET, TM, ISC, and R are read-only! */ #define WRITE_PSW(V) (R[NUM_PSW] = ((R[NUM_PSW] & PSW_RO_MASK) | ((V) & ~PSW_RO_MASK))) @@ -225,7 +248,6 @@ * Opcodes */ typedef enum { - HALT = 0x00, /* Undocumented instruction */ SPOPRD = 0x02, SPOPD2 = 0x03, MOVAW = 0x04, @@ -254,7 +276,7 @@ typedef enum { SWAPWI = 0x1C, #if defined(REV3) TGEDTH = 0x1D, -#endif +#endif SWAPHI = 0x1E, SWAPBI = 0x1F, POPW = 0x20, @@ -310,7 +332,7 @@ typedef enum { RGTRU = 0x54, BGUH = 0x56, BGUB = 0x57, - BLSSU = 0x58, + RLSSU = 0x58, BLUH = 0x5A, BLUB = 0x5B, RLEQU = 0x5C, @@ -455,6 +477,9 @@ typedef enum { RETG = 0x3045, GATE = 0x3061, CALLPS = 0x30ac, +#if defined(REV3) + UCALLPS = 0x30c0, +#endif RETPS = 0x30c8 } opcode; @@ -498,6 +523,8 @@ typedef enum { typedef enum { OP_NONE, /* NULL type */ OP_DESC, /* Descriptor byte */ + OP_DESB, /* Descriptor with byte displacement (WE32200 only) */ + OP_DESH, /* Descriptor with halfword displacement (WE32200 only) */ OP_BYTE, /* 8-bit signed value */ OP_HALF, /* 16-bit signed value */ OP_COPR /* Coprocessor instruction */ @@ -536,6 +563,9 @@ typedef struct { typedef struct { uint8 mode; /* Embedded data addressing mode */ uint8 reg; /* Operand register (0-15) */ +#if defined(REV3) + uint8 reg2; /* Operand register 2 (16-31) */ +#endif int8 dtype; /* Default type for the operand */ int8 etype; /* Expanded type (-1 if none) */ union { @@ -562,18 +592,6 @@ typedef struct { operand operands[4]; } instr; -/* - * A mapping of CIO identifier word to CIO device name. - * - * Each CIO expansion card in a 3B2 system identifies itself with a - * well-knon 16-bit value, used by drivers to identify the type of - * card installed in each slot. - */ -typedef struct { - uint16 id; - const char name[8]; -} cio_device; - /* Function prototypes */ t_stat sys_boot(int32 flag, CONST char *ptr); t_stat cpu_svc(UNIT *uptr); @@ -650,13 +668,13 @@ void cpu_abort(uint8 et, uint8 isc); #define CPU_SET_INT(flags) (sbd_int_req |= flags) #define CPU_CLR_INT(flags) (sbd_int_req &= ~(flags)) +extern t_bool rom_loaded; extern volatile int32 stop_reason; extern uint16 sbd_int_req; -extern uint32 rom_size; extern instr *cpu_instr; extern t_bool cpu_nmi; -extern uint32 *ROM; -extern uint32 *RAM; +extern uint8 *ROM; +extern uint8 *RAM; extern uint32 R[NUM_REGISTERS]; extern REG cpu_reg[]; extern UNIT cpu_unit; diff --git a/3B2/3b2_csr.h b/3B2/3b2_csr.h index c7cf27a5..d3b1837a 100644 --- a/3B2/3b2_csr.h +++ b/3B2/3b2_csr.h @@ -1,6 +1,6 @@ -/* 3b2_csr.h: Common CSR header +/* 3b2_csr.h: Common CSR/CSER header - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -41,4 +41,6 @@ #define CLR_CSR(FLAGS) (csr_data &= ~(FLAGS)) #define CSR(FLAGS) ((csr_data & FLAGS) != 0) +extern CSR_DATA csr_data; + #endif /* _3B2_CSR_H_ */ diff --git a/3B2/3b2_ctc.c b/3B2/3b2_ctc.c index 3bc4b08d..657e427c 100644 --- a/3B2/3b2_ctc.c +++ b/3B2/3b2_ctc.c @@ -1,6 +1,6 @@ -/* 3b2_ctc.c: AT&T 3B2 Model 400 "CTC" feature card +/* 3b2_ctc.c: CM195H 23MB Cartridge Tape Controller CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -61,12 +61,7 @@ #define VTOC_BLOCK 0 -/* Static function declarations */ -static t_stat ctc_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -static t_stat ctc_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -static t_stat ctc_show_queue_common(FILE *st, UNIT *uptr, int32 val, CONST void *desc, t_bool rq); - -static uint8 int_cid; /* Interrupting card ID */ +static uint8 int_slot; /* Interrupting card ID */ static uint8 int_subdev; /* Interrupting subdevice */ static t_bool ctc_conf = FALSE; /* Has a CTC card been configured? */ static uint32 ctc_crc; /* CRC32 of downloaded memory */ @@ -101,14 +96,10 @@ UNIT ctc_unit = { }; MTAB ctc_mod[] = { - { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", + { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", &set_writelock, &show_writelock, NULL, "Write enable tape drive" }, - { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", + { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", &set_writelock, NULL, NULL, "Write lock tape drive" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "RQUEUE=n", NULL, - NULL, &ctc_show_rqueue, NULL, "Display Request Queue for card n" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL, - NULL, &ctc_show_cqueue, NULL, "Display Completion Queue for card n" }, { 0 } }; @@ -147,9 +138,9 @@ DEVICE ctc_dev = { NULL, /* device description */ }; -static void cio_irq(uint8 cid, uint8 dev, int32 delay) +static void cio_irq(uint8 slot, uint8 dev, int32 delay) { - int_cid = cid; + int_slot = slot; int_subdev = dev & 0x3f; sim_activate_after(&ctc_unit, delay); } @@ -204,49 +195,49 @@ static void ctc_update_vtoc(uint32 maxpass, { uint32 i; - pwrite_w(vtoc_addr + 12, VTOC_VALID); - pwrite_w(vtoc_addr + 16, vtoc->version); + pwrite_w(vtoc_addr + 12, VTOC_VALID, BUS_PER); + pwrite_w(vtoc_addr + 16, vtoc->version, BUS_PER); for (i = 0; i < 8; i++) { - pwrite_b(vtoc_addr + 20 + i, (uint8)(vtoc->volume[i])); + pwrite_b(vtoc_addr + 20 + i, (uint8)(vtoc->volume[i]), BUS_PER); } - pwrite_h(vtoc_addr + 28, vtoc->sectorsz); - pwrite_h(vtoc_addr + 30, vtoc->nparts); + pwrite_h(vtoc_addr + 28, vtoc->sectorsz, BUS_PER); + pwrite_h(vtoc_addr + 30, vtoc->nparts, BUS_PER); for (i = 0; i < VTOC_PART; i++) { - pwrite_h(vtoc_addr + 72 + (i * 12) + 0, vtoc_table[i].id); - pwrite_h(vtoc_addr + 72 + (i * 12) + 2, vtoc_table[i].flag); - pwrite_w(vtoc_addr + 72 + (i * 12) + 4, vtoc_table[i].sstart); - pwrite_w(vtoc_addr + 72 + (i * 12) + 8, vtoc_table[i].ssize); + pwrite_h(vtoc_addr + 72 + (i * 12) + 0, vtoc_table[i].id, BUS_PER); + pwrite_h(vtoc_addr + 72 + (i * 12) + 2, vtoc_table[i].flag, BUS_PER); + pwrite_w(vtoc_addr + 72 + (i * 12) + 4, vtoc_table[i].sstart, BUS_PER); + pwrite_w(vtoc_addr + 72 + (i * 12) + 8, vtoc_table[i].ssize, BUS_PER); } /* Write the pdinfo */ - pwrite_w(pdinfo_addr, pdinfo->driveid); - pwrite_w(pdinfo_addr + 4, pdinfo->sanity); - pwrite_w(pdinfo_addr + 8, pdinfo->version); + pwrite_w(pdinfo_addr, pdinfo->driveid, BUS_PER); + pwrite_w(pdinfo_addr + 4, pdinfo->sanity, BUS_PER); + pwrite_w(pdinfo_addr + 8, pdinfo->version, BUS_PER); for (i = 0; i < 12; i++) { - pwrite_b(pdinfo_addr + 12 + i, pdinfo->serial[i]); + pwrite_b(pdinfo_addr + 12 + i, pdinfo->serial[i], BUS_PER); } - pwrite_w(pdinfo_addr + 24, pdinfo->cyls); - pwrite_w(pdinfo_addr + 28, pdinfo->tracks); - pwrite_w(pdinfo_addr + 32, pdinfo->sectors); - pwrite_w(pdinfo_addr + 36, pdinfo->bytes); - pwrite_w(pdinfo_addr + 40, pdinfo->logicalst); - pwrite_w(pdinfo_addr + 44, pdinfo->errlogst); - pwrite_w(pdinfo_addr + 48, pdinfo->errlogsz); - pwrite_w(pdinfo_addr + 52, pdinfo->mfgst); - pwrite_w(pdinfo_addr + 56, pdinfo->mfgsz); - pwrite_w(pdinfo_addr + 60, pdinfo->defectst); - pwrite_w(pdinfo_addr + 64, pdinfo->defectsz); - pwrite_w(pdinfo_addr + 68, pdinfo->relno); - pwrite_w(pdinfo_addr + 72, pdinfo->relst); - pwrite_w(pdinfo_addr + 76, pdinfo->relsz); - pwrite_w(pdinfo_addr + 80, pdinfo->relnext); + pwrite_w(pdinfo_addr + 24, pdinfo->cyls, BUS_PER); + pwrite_w(pdinfo_addr + 28, pdinfo->tracks, BUS_PER); + pwrite_w(pdinfo_addr + 32, pdinfo->sectors, BUS_PER); + pwrite_w(pdinfo_addr + 36, pdinfo->bytes, BUS_PER); + pwrite_w(pdinfo_addr + 40, pdinfo->logicalst, BUS_PER); + pwrite_w(pdinfo_addr + 44, pdinfo->errlogst, BUS_PER); + pwrite_w(pdinfo_addr + 48, pdinfo->errlogsz, BUS_PER); + pwrite_w(pdinfo_addr + 52, pdinfo->mfgst, BUS_PER); + pwrite_w(pdinfo_addr + 56, pdinfo->mfgsz, BUS_PER); + pwrite_w(pdinfo_addr + 60, pdinfo->defectst, BUS_PER); + pwrite_w(pdinfo_addr + 64, pdinfo->defectsz, BUS_PER); + pwrite_w(pdinfo_addr + 68, pdinfo->relno, BUS_PER); + pwrite_w(pdinfo_addr + 72, pdinfo->relst, BUS_PER); + pwrite_w(pdinfo_addr + 76, pdinfo->relsz, BUS_PER); + pwrite_w(pdinfo_addr + 80, pdinfo->relnext, BUS_PER); /* Now something horrible happens. We sneak RIGHT off the end of * the pdinfo struct and reach deep into the pdsector struct that * it is part of. */ - pwrite_w(pdinfo_addr + 128, maxpass); + pwrite_w(pdinfo_addr + 128, maxpass, BUS_PER); } /* @@ -257,7 +248,7 @@ static void ctc_update_vtoc(uint32 maxpass, * expects response parameters to be placed in specific fields of the * Completion Queue entry. It can be confusing to follow. */ -static void ctc_cmd(uint8 cid, +static void ctc_cmd(uint8 slot, cio_entry *rqe, uint8 *rapp_data, cio_entry *cqe, uint8 *capp_data) { @@ -283,7 +274,7 @@ static void ctc_cmd(uint8 cid, switch(rqe->opcode) { case CIO_DLM: for (i = 0; i < rqe->byte_count; i++) { - ctc_crc = cio_crc32_shift(ctc_crc, pread_b(rqe->address + i)); + ctc_crc = cio_crc32_shift(ctc_crc, pread_b(rqe->address + i, BUS_PER)); } sim_debug(TRACE_DBG, &ctc_dev, "[ctc_cmd] CIO Download Memory: bytecnt=%04x " @@ -311,11 +302,11 @@ static void ctc_cmd(uint8 cid, if (ctc_crc == CTC_DIAG_CRC1 || ctc_crc == CTC_DIAG_CRC2 || ctc_crc == CTC_DIAG_CRC3) { - pwrite_h(0x200f000, 0x1); /* Test success */ - pwrite_h(0x200f002, 0x0); /* Test Number */ - pwrite_h(0x200f004, 0x0); /* Actual */ - pwrite_h(0x200f006, 0x0); /* Expected */ - pwrite_b(0x200f008, 0x1); /* Success flag again */ + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ } /* An interesting (?) side-effect of FORCE FUNCTION CALL is @@ -323,7 +314,7 @@ static void ctc_cmd(uint8 cid, * required in order for new commands to work. In fact, an * INT0/INT1 combo _without_ a RESET can sysgen the board. So, * we reset the command bits here. */ - cio[cid].sysgen_s = 0; + cio[slot].sysgen_s = 0; cqe->opcode = CTC_SUCCESS; break; case CIO_DOS: @@ -339,9 +330,9 @@ static void ctc_cmd(uint8 cid, rqe->opcode); delay = DELAY_DSD; /* Write subdevice information to the host. */ - pwrite_h(rqe->address, CTC_NUM_SD); - pwrite_h(rqe->address + 2, CTC_SD_FT25); - pwrite_h(rqe->address + 4, CTC_SD_FD5); + pwrite_h(rqe->address, CTC_NUM_SD, BUS_PER); + pwrite_h(rqe->address + 2, CTC_SD_FT25, BUS_PER); + pwrite_h(rqe->address + 4, CTC_SD_FD5, BUS_PER); cqe->opcode = CTC_SUCCESS; break; case CTC_FORMAT: @@ -503,7 +494,7 @@ static void ctc_cmd(uint8 cid, ctc_state[dev].time += 10; for (j = 0; j < VTOC_SECSZ; j++) { /* Fill the buffer */ - sec_buf[j] = pread_b(rqe->address + (b * VTOC_SECSZ) + j); + sec_buf[j] = pread_b(rqe->address + (b * VTOC_SECSZ) + j, BUS_PER); } lba = blkno + b; result = sim_disk_wrsect(&ctc_unit, lba, sec_buf, &secrw, 1); @@ -601,7 +592,7 @@ static void ctc_cmd(uint8 cid, offset = j; } c = sec_buf[offset]; - pwrite_b(dest++, c); + pwrite_b(dest++, c, BUS_PER); ctc_state[dev].bytnum++; } } else { @@ -634,10 +625,10 @@ static void ctc_cmd(uint8 cid, break; } - cio_irq(cid, rqe->subdevice, delay); + cio_irq(slot, rqe->subdevice, delay); } -void ctc_sysgen(uint8 cid) +void ctc_sysgen(uint8 slot) { cio_entry cqe = {0}; uint8 rapp_data[12] = {0}; @@ -645,23 +636,23 @@ void ctc_sysgen(uint8 cid) ctc_crc = 0; sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] Handling Sysgen.\n"); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqp=%08x\n", cio[cid].rqp); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqp=%08x\n", cio[cid].cqp); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqs=%d\n", cio[cid].rqs); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqs=%d\n", cio[cid].cqs); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] ivec=%d\n", cio[cid].ivec); - sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] no_rque=%d\n", cio[cid].no_rque); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqp=%08x\n", cio[slot].rqp); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqp=%08x\n", cio[slot].cqp); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqs=%d\n", cio[slot].rqs); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqs=%d\n", cio[slot].cqs); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] ivec=%d\n", cio[slot].ivec); + sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] no_rque=%d\n", cio[slot].no_rque); cqe.opcode = 3; /* Sysgen success! */ - cio_cexpress(cid, CTQCESIZE, &cqe, rapp_data); - cio_cqueue(cid, CIO_STAT, CTQCESIZE, &cqe, rapp_data); + cio_cexpress(slot, CTQCESIZE, &cqe, rapp_data); + cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, rapp_data); - int_cid = cid; + int_slot = slot; sim_activate_after(&ctc_unit, DELAY_SYSGEN); } -void ctc_express(uint8 cid) +void ctc_express(uint8 slot) { cio_entry rqe, cqe; uint8 rapp_data[12] = {0}; @@ -669,16 +660,13 @@ void ctc_express(uint8 cid) sim_debug(TRACE_DBG, &ctc_dev, "[ctc_express] Handling Express Request\n"); - cio_rexpress(cid, CTQRESIZE, &rqe, rapp_data); - ctc_cmd(cid, &rqe, rapp_data, &cqe, capp_data); + cio_rexpress(slot, CTQRESIZE, &rqe, rapp_data); + ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); - dump_entry(TRACE_DBG, &ctc_dev, "COMPLETION", - CTQCESIZE, &cqe, capp_data); - - cio_cexpress(cid, CTQCESIZE, &cqe, capp_data); + cio_cexpress(slot, CTQCESIZE, &cqe, capp_data); } -void ctc_full(uint8 cid) +void ctc_full(uint8 slot) { cio_entry rqe, cqe; uint8 rapp_data[12] = {0}; @@ -686,69 +674,35 @@ void ctc_full(uint8 cid) sim_debug(TRACE_DBG, &ctc_dev, "[ctc_full] Handling Full Request\n"); - while (cio_cqueue_avail(cid, CTQCESIZE) && - cio_rqueue(cid, TAPE_DEV, CTQRESIZE, &rqe, rapp_data) == SCPE_OK) { - ctc_cmd(cid, &rqe, rapp_data, &cqe, capp_data); + while (cio_cqueue_avail(slot, CTQCESIZE) && + cio_rqueue(slot, TAPE_DEV, CTQRESIZE, &rqe, rapp_data) == SCPE_OK) { + ctc_cmd(slot, &rqe, rapp_data, &cqe, capp_data); } - cio_cqueue(cid, CIO_STAT, CTQCESIZE, &cqe, capp_data); + cio_cqueue(slot, CIO_STAT, CTQCESIZE, &cqe, capp_data); } t_stat ctc_reset(DEVICE *dptr) { - uint8 cid; + uint8 slot; + t_stat r; ctc_crc = 0; - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_reset] Resetting CTC device\n"); - memset(ctc_state, 0, 2 * sizeof(CTC_STATE)); if (dptr->flags & DEV_DIS) { - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_reset] REMOVING CARD\n"); - - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == CTC_ID) { - break; - } - } - - if (cid == CIO_SLOTS) { - /* No card was ever attached */ - return SCPE_OK; - } - - cio[cid].id = 0; - cio[cid].ipl = 0; - cio[cid].ivec = 0; - cio[cid].exp_handler = NULL; - cio[cid].full_handler = NULL; - cio[cid].sysgen = NULL; - + cio_remove_all(CTC_ID); ctc_conf = FALSE; - } else if (!ctc_conf) { - sim_debug(TRACE_DBG, &ctc_dev, - "[ctc_reset] ATTACHING CARD\n"); + return SCPE_OK; + } - /* Find the first avaialable slot */ - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == 0) { - break; - } + if (!ctc_conf) { + r = cio_install(CTC_ID, "CTC", CTC_IPL, + &ctc_express, &ctc_full, &ctc_sysgen, NULL, + &slot); + if (r != SCPE_OK) { + return r; } - - /* Do we have room? */ - if (cid == CIO_SLOTS) { - return SCPE_NXM; - } - - cio[cid].id = CTC_ID; - cio[cid].ipl = CTC_IPL; - cio[cid].exp_handler = &ctc_express; - cio[cid].full_handler = &ctc_full; - cio[cid].sysgen = &ctc_sysgen; - ctc_conf = TRUE; } @@ -759,20 +713,20 @@ t_stat ctc_svc(UNIT *uptr) { uint16 lp, ulp; - if (cio[int_cid].ivec > 0) { + if (cio[int_slot].ivec > 0) { sim_debug(TRACE_DBG, &ctc_dev, "[cio_svc] IRQ for board %d (VEC=%d)\n", - int_cid, cio[int_cid].ivec); - CIO_SET_INT(int_cid); + int_slot, cio[int_slot].ivec); + CIO_SET_INT(int_slot); } /* Check to see if the completion queue has more work in it. We * need to schedule an interrupt for each job if we've fallen * behind (this should be rare) */ - lp = cio_c_lp(int_cid, CTQCESIZE); - ulp = cio_c_ulp(int_cid, CTQCESIZE); + lp = cio_c_lp(int_slot, CTQCESIZE); + ulp = cio_c_ulp(int_slot, CTQCESIZE); - if ((ulp + CTQCESIZE) % (CTQCESIZE * cio[int_cid].cqs) != lp) { + if ((ulp + CTQCESIZE) % (CTQCESIZE * cio[int_slot].cqs) != lp) { sim_debug(TRACE_DBG, &ctc_dev, "[cio_svc] Completion queue has fallen behind (lp=%04x ulp=%04x)\n", lp, ulp); @@ -792,117 +746,3 @@ t_stat ctc_detach(UNIT *uptr) { return sim_disk_detach(uptr); } - -t_stat ctc_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ctc_show_queue_common(st, uptr, val, desc, TRUE); -} - -t_stat ctc_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ctc_show_queue_common(st, uptr, val, desc, FALSE); -} - - -static t_stat ctc_show_queue_common(FILE *st, UNIT *uptr, int32 val, - CONST void *desc, t_bool rq) -{ - uint8 cid; - char *cptr = (char *) desc; - t_stat result; - uint32 ptr, size, no_rque, i, j; - uint8 op, dev, seq, cmdstat; - - if (cptr) { - cid = (uint8) get_uint(cptr, 10, 12, &result); - if (result != SCPE_OK) { - return SCPE_ARG; - } - } else { - return SCPE_ARG; - } - - /* If the card is not sysgen'ed, give up */ - if (cio[cid].sysgen_s != CIO_SYSGEN) { - fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid); - return SCPE_ARG; - } - - if (rq) { - ptr = cio[cid].rqp; - size = cio[cid].rqs; - no_rque = cio[cid].no_rque; - fprintf(st, "Dumping %d Request Queues\n", no_rque); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "EXPRESS ENTRY:\n"); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2)); - fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3)); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += CTQRESIZE; - - for (i = 0; i < no_rque; i++) { - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "REQUEST QUEUE %d\n", i); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / CTQRESIZE); - fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / CTQRESIZE); - fprintf(st, "---------------------------------------------------------\n"); - ptr += 4; - for (j = 0; j < size; j++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "REQUEST ENTRY %d\n", j); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x 0x%08x 0x%08x\n", - pread_w(ptr + 8), pread_w(ptr + 12), pread_w(ptr + 16)); - ptr += CTQRESIZE; - } - } - } else { - ptr = cio[cid].cqp; - size = cio[cid].cqs; - no_rque = 0; /* Not used */ - fprintf(st, "Dumping Completion Queue\n"); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "EXPRESS ENTRY:\n"); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2)); - fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3)); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += CTQCESIZE; - - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / CTQCESIZE); - fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / CTQCESIZE); - fprintf(st, "---------------------------------------------------------\n"); - ptr += 4; - for (i = 0; i < size; i++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "COMPLETION ENTRY %d\n", i); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x 0x%08x\n", - pread_w(ptr + 8), pread_w(ptr + 12)); - ptr += CTQCESIZE; - } - } - - return SCPE_OK; -} diff --git a/3B2/3b2_ctc.h b/3B2/3b2_ctc.h index 67f0b884..0d3cb700 100644 --- a/3B2/3b2_ctc.h +++ b/3B2/3b2_ctc.h @@ -1,6 +1,6 @@ -/* 3b2_ctc.h: AT&T 3B2 Model 400 "CTC" feature card +/* 3b2_ctc.h: CM195H 23MB Cartridge Tape Controller CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -146,8 +146,8 @@ t_stat ctc_reset(DEVICE *dptr); t_stat ctc_svc(UNIT *uptr); t_stat ctc_attach(UNIT *uptr, CONST char *cptr); t_stat ctc_detach(UNIT *uptr); -void ctc_sysgen(uint8 cid); -void ctc_express(uint8 cid); -void ctc_full(uint8 cid); +void ctc_sysgen(uint8 slot); +void ctc_express(uint8 slot); +void ctc_full(uint8 slot); #endif /* _3B2_CTC_H_ */ diff --git a/3B2/3b2_defs.h b/3B2/3b2_defs.h index 9358e4bf..88587126 100644 --- a/3B2/3b2_defs.h +++ b/3B2/3b2_defs.h @@ -1,6 +1,6 @@ /* 3b2_defs.h: AT&T 3B2 Shared Simulator Definitions - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -81,6 +81,9 @@ #define PCHAR(c) (((char) (c) >= 0x20 && (char) (c) < 0x7f) ? (char) (c) : '.') +#define ROM_SIZE (128 * 1024) +#define POLL_WAIT 70000 + #define UNIT_V_EXBRK (UNIT_V_UF + 0) #define UNIT_V_OPBRK (UNIT_V_UF + 1) #define UNIT_EXBRK (1u << UNIT_V_EXBRK) @@ -88,6 +91,7 @@ #define EX_V_FLAG 1 << 21 +#define ROM_BASE 0 #define PHYS_MEM_BASE 0x2000000 #define MSIZ_512K 0x80000 @@ -131,10 +135,8 @@ #define TIMER_INTERVAL 1 #define TIMER_BUS 2 -/* Timer */ -#define TMR_CLK 0 /* The clock responsible for IPL 15 interrupts */ -#define TPS_CLK 100 /* 100 ticks per second */ - +/* Timers */ +#define TMR_CLK 0 /* Calibrated 100Hz timer */ /* Global symbols */ @@ -159,6 +161,6 @@ extern DEVICE tto_dev; #if defined(REV3) extern DEVICE flt_dev; extern DEVICE ha_dev; -#endif +#endif /* defined(REV3) */ -#endif +#endif /* _3B2_DEFS_H_ */ diff --git a/3B2/3b2_dmac.c b/3B2/3b2_dmac.c index 08273b99..85a44aa5 100644 --- a/3B2/3b2_dmac.c +++ b/3B2/3b2_dmac.c @@ -1,6 +1,6 @@ -/* 3b2_dmac.c: AT&T 3B2 DMA Controller Implementation +/* 3b2_dmac.c: AM9517 DMA Controller - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -38,6 +38,8 @@ #include "3b2_if.h" #include "3b2_iu.h" #include "3b2_mem.h" +#include "3b2_stddev.h" +#include "3b2_csr.h" DMA_STATE dma_state; @@ -71,9 +73,13 @@ dmac_dma_handler device_dma_handlers[] = { {0, 0, NULL, NULL, NULL } }; -uint32 dma_address(uint8 channel, uint32 offset, t_bool r) { +uint32 dma_address(uint8 channel, uint32 offset) { uint32 addr, page; - addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) + offset); + if (DMA_DECR(channel)) { + addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) - offset); + } else { + addr = (PHYS_MEM_BASE + (uint32)(dma_state.channels[channel].addr) + offset); + } #if defined (REV3) page = (uint32)dma_state.channels[channel].page; #else @@ -94,6 +100,7 @@ t_stat dmac_reset(DEVICE *dptr) for (i = 0; i < 4; i++) { dma_state.channels[i].page = 0; dma_state.channels[i].addr = 0; + dma_state.channels[i].mode = 0; dma_state.channels[i].wcount = 0; dma_state.channels[i].addr_c = 0; dma_state.channels[i].wcount_c = -1; @@ -111,83 +118,83 @@ uint32 dmac_read(uint32 pa, size_t size) reg = pa & 0xff; switch (base) { - case DMA_C: /* 0x48xxx */ + case DMA_C: switch (reg) { case 0: /* channel 0 current address reg */ data = ((dma_state.channels[0].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 0 Addr Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 0 Addr Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 1: /* channel 0 current address reg */ data = ((dma_state.channels[0].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 0 Addr Count Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 0 Addr Count Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 2: /* channel 1 current address reg */ data = ((dma_state.channels[1].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 1 Addr Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 1 Addr Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 3: /* channel 1 current address reg */ data = ((dma_state.channels[1].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 1 Addr Count Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 1 Addr Count Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 4: /* channel 2 current address reg */ data = ((dma_state.channels[2].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 2 Addr Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 2 Addr Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 5: /* channel 2 current address reg */ data = ((dma_state.channels[2].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 2 Addr Count Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 2 Addr Count Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 6: /* channel 3 current address reg */ data = ((dma_state.channels[3].addr_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 3 Addr Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 3 Addr Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 7: /* channel 3 current address reg */ data = ((dma_state.channels[3].wcount_c) >> (dma_state.bff * 8)) & 0xff; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading Channel 3 Addr Count Reg: %08x\n", - R[NUM_PC], data); + "Reading Channel 3 Addr Count Reg: %08x\n", + data); dma_state.bff ^= 1; break; case 8: data = dma_state.status; sim_debug(READ_MSG, &dmac_dev, - "[%08x] Reading DMAC Status %08x\n", - R[NUM_PC], data); + "Reading DMAC Status %08x\n", + data); dma_state.status = 0; break; default: sim_debug(READ_MSG, &dmac_dev, - "[%08x] DMAC READ %lu B @ %08x\n", - R[NUM_PC], size, pa); + "DMAC READ %lu B @ %08x\n", + size, pa); data = 0; } return data; default: sim_debug(READ_MSG, &dmac_dev, - "[%08x] [BASE: %08x] DMAC READ %lu B @ %08x\n", - R[NUM_PC], base, size, pa); + "[BASE: %08x] DMAC READ %lu B @ %08x\n", + base, size, pa); return 0; } } @@ -200,6 +207,12 @@ void dmac_program(uint8 reg, uint8 val) uint8 channel_id, i, chan_num; dma_channel *channel; +#if defined(REV3) + /* TODO: More general DMA interrupt clearing */ + CPU_CLR_INT(INT_UART_DMA); + CLR_CSR(CSRDMA); +#endif + if (reg < 8) { switch (reg) { case 0: @@ -259,13 +272,13 @@ void dmac_program(uint8 reg, uint8 val) case 8: /* Command */ dma_state.command = val; sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Command: val=%02x\n", - R[NUM_PC], val); + "Command: val=%02x\n", + val); break; case 9: /* Request */ sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Request set: val=%02x\n", - R[NUM_PC], val); + "Request set: val=%02x\n", + val); dma_state.request = val; break; case 10: /* Write Single Mask Register Bit */ @@ -281,14 +294,15 @@ void dmac_program(uint8 reg, uint8 val) } sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Write Single Mask Register Bit. channel=%d set/clear=%02x\n", - R[NUM_PC], channel_id, (val >> 2) & 1); + "Write Single Mask Register Bit. channel=%d set/clear=%02x\n", + channel_id, (val >> 2) & 1); break; case 11: /* Mode */ + channel_id = val & 3; sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Mode Set. val=%02x\n", - R[NUM_PC], val); - dma_state.mode = val; + "Mode Set. channel=%d val=%02x\n", + channel_id, val); + dma_state.channels[channel_id].mode = val; break; case 12: /* Clear Byte Pointer Flip/Flop */ dma_state.bff = 0; @@ -308,19 +322,19 @@ void dmac_program(uint8 reg, uint8 val) break; case 15: /* Write All Mask Register Bits */ sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Write DMAC mask (all bits). Val=%02x\n", - R[NUM_PC], val); + "Write DMAC mask (all bits). Val=%02x\n", + val); dma_state.mask = val & 0xf; break; case 16: /* Clear DMAC Interrupt */ sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Clear DMAC Interrupt in DMAC. val=%02x\n", - R[NUM_PC], val); + "Clear DMA Interrupt in DMAC. val=%02x\n", + val); break; default: sim_debug(WRITE_MSG, &dmac_dev, - "[%08x] Unhandled DMAC write. reg=%x val=%02x\n", - R[NUM_PC], reg, val); + "Unhandled DMAC write. reg=%x val=%02x\n", + reg, val); break; } } @@ -398,46 +412,45 @@ void dmac_generic_dma(uint8 channel, uint32 service_address) i = chan->wcount_c; - /* TODO: This does not handle decrement-mode transfers, - which don't seem to be used in SVR3 */ + /* TODO: This assumes every transfer is a block mode, which is not + guaranteed to be valid, but is likely safe? */ - switch ((dma_state.mode >> 2) & 0xf) { - case DMA_MODE_VERIFY: + switch (DMA_XFER(channel)) { + case DMA_XFER_VERIFY: sim_debug(EXECUTE_MSG, &dmac_dev, - "[%08x] [dmac_generic_dma channel=%d] unhandled VERIFY request.\n", - R[NUM_PC], channel); + "[dmac_generic_dma channel=%d] unhandled VERIFY request.\n", + channel); break; - case DMA_MODE_WRITE: + case DMA_XFER_WRITE: sim_debug(EXECUTE_MSG, &dmac_dev, - "[%08x] [dmac_generic_dma channel=%d] write: %d bytes to %08x from %08x (page=%04x addr=%08x)\n", - R[NUM_PC], channel, + "[dmac_generic_dma channel=%d] write: %d bytes to %08x from %08x (page=%04x addr=%08x)\n", + channel, chan->wcount + 1, - dma_address(channel, 0, TRUE), + dma_address(channel, 0), service_address, dma_state.channels[channel].page, dma_state.channels[channel].addr); for (; i >= 0; i--) { chan->wcount_c--; - addr = dma_address(channel, chan->ptr, TRUE); - chan->addr_c = dma_state.channels[channel].addr + chan->ptr; - chan->ptr++; - data = pread_b(service_address); - write_b(addr, data); + addr = dma_address(channel, chan->ptr++); + chan->addr_c = addr; + data = pread_b(service_address, BUS_PER); + write_b(addr, data, BUS_PER); } break; - case DMA_MODE_READ: + case DMA_XFER_READ: sim_debug(EXECUTE_MSG, &dmac_dev, - "[%08x] [dmac_generic_dma channel=%d] read: %d bytes from %08x to %08x\n", - R[NUM_PC], channel, + "[dmac_generic_dma channel=%d] read: %d bytes from %08x to %08x\n", + channel, chan->wcount + 1, - dma_address(channel, 0, TRUE), + dma_address(channel, 0), service_address); for (; i >= 0; i--) { chan->wcount_c = i; - addr = dma_address(channel, chan->ptr++, TRUE); - chan->addr_c = dma_state.channels[channel].addr + chan->ptr; - data = pread_b(addr); - write_b(service_address, data); + addr = dma_address(channel, chan->ptr++); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + write_b(service_address, data, BUS_PER); } break; } diff --git a/3B2/3b2_dmac.h b/3B2/3b2_dmac.h index a5fe8e79..cda90371 100644 --- a/3B2/3b2_dmac.h +++ b/3B2/3b2_dmac.h @@ -1,6 +1,6 @@ -/* 3b2_dmac.h: AT&T 3B2 DMA Controller Header +/* 3b2_dmac.h: AM9517 DMA Controller - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,14 +33,20 @@ #include "3b2_defs.h" -#define DMA_MODE_VERIFY 0 -#define DMA_MODE_WRITE 1 /* Write to memory from device */ -#define DMA_MODE_READ 2 /* Read from memory to device */ +#define DMA_XFER_VERIFY 0 +#define DMA_XFER_WRITE 1 /* Write to memory from device */ +#define DMA_XFER_READ 2 /* Read from memory to device */ #define DMA_IF_READ (IFBASE + IF_DATA_REG) +#define DMA_MODE(C) ((dma_state.channels[(C)].mode >> 6) & 3) +#define DMA_DECR(C) ((dma_state.channels[(C)].mode >> 5) & 1) +#define DMA_AUTOINIT(C) ((dma_state.channels[(C)].mode >> 4) & 1) +#define DMA_XFER(C) ((dma_state.channels[(C)].mode >> 2) & 3) + typedef struct { - uint16 page; + uint8 mode; /* Channel mode */ + uint16 page; /* Memory page */ uint16 addr; /* Original addr */ uint16 wcount; /* Original wcount */ uint16 addr_c; /* Current addr */ @@ -57,7 +63,6 @@ typedef struct { /* DMAC programmable registers */ uint8 command; - uint8 mode; uint8 request; uint8 mask; uint8 status; @@ -77,7 +82,7 @@ uint32 dmac_read(uint32 pa, size_t size); void dmac_write(uint32 pa, uint32 val, size_t size); void dmac_service_drqs(); void dmac_generic_dma(uint8 channel, uint32 service_address); -uint32 dma_address(uint8 channel, uint32 offset, t_bool r); +uint32 dma_address(uint8 channel, uint32 offset); extern DMA_STATE dma_state; diff --git a/3B2/3b2_id.c b/3B2/3b2_id.c index 71762d6d..3b83d96a 100644 --- a/3B2/3b2_id.c +++ b/3B2/3b2_id.c @@ -1,6 +1,6 @@ -/* 3b2_d.h: AT&T 3B2 Model 400 Hard Disk (uPD7261) Implementation +/* 3b2_d.c: uPD7261 Integrated Disk Controller - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -167,9 +167,9 @@ MTAB id_mod[] = { &id_set_type, NULL, NULL, "Set HD161 Disk Type" }, { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, NULL, &id_show_type, NULL, "Display device type" }, - { ID_AUTOSIZE, ID_AUTOSIZE, "autosize", "AUTOSIZE", + { ID_AUTOSIZE, ID_AUTOSIZE, "autosize", "AUTOSIZE", NULL, NULL, NULL, "Set type based on file size at attach" }, - { ID_AUTOSIZE, 0, "noautosize", "NOAUTOSIZE", + { ID_AUTOSIZE, 0, "noautosize", "NOAUTOSIZE", NULL, NULL, NULL, "Disable disk autosize on attach" }, { 0 } }; @@ -245,15 +245,14 @@ t_stat id_ctlr_svc(UNIT *uptr) switch (cmd) { case ID_CMD_SIS: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING Sense Interrupt Status.\n", - R[NUM_PC]); + "INTR\t\tCOMPLETING Sense Interrupt Status.\n"); id_data[0] = id_int_status; id_int_status = 0; break; default: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING OTHER COMMAND 0x%x (CONTROLLER)\n", - R[NUM_PC], cmd); + "INTR\t\tCOMPLETING OTHER COMMAND 0x%x (CONTROLLER)\n", + cmd); break; } @@ -299,15 +298,15 @@ t_stat id_unit_svc(UNIT *uptr) case ID_SEEK_0: id_set_status(ID_STAT_CEH); sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING Recal/Seek SEEK_0 UNIT %d\n", - R[NUM_PC], unit); + "INTR\t\tCOMPLETING Recal/Seek SEEK_0 UNIT %d\n", + unit); id_seek_state[unit] = ID_SEEK_1; id_activate(uptr, 8000); /* TODO: Correct Delay based on steps */ break; case ID_SEEK_1: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING Recal/Seek SEEK_1 UNIT %d\n", - R[NUM_PC], unit); + "INTR\t\tCOMPLETING Recal/Seek SEEK_1 UNIT %d\n", + unit); id_seek_state[unit] = ID_SEEK_NONE; id_set_status(ID_STAT_SRQ); uptr->u4 = 0; /* Only clear out the command on a SEEK_1, never a SEEK_0 */ @@ -319,14 +318,14 @@ t_stat id_unit_svc(UNIT *uptr) break; default: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tERROR, NOT SEEK_0 OR SEEK_1, UNIT %d\n", - R[NUM_PC], unit); + "INTR\t\tERROR, NOT SEEK_0 OR SEEK_1, UNIT %d\n", + unit); break; } } else { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING NON-POLLING Recal/Seek UNIT %d\n", - R[NUM_PC], unit); + "INTR\t\tCOMPLETING NON-POLLING Recal/Seek UNIT %d\n", + unit); id_set_status(ID_STAT_CEH); uptr->u4 = 0; if (uptr->flags & UNIT_ATT) { @@ -339,8 +338,8 @@ t_stat id_unit_svc(UNIT *uptr) break; case ID_CMD_SUS: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING Sense Unit Status UNIT %d\n", - R[NUM_PC], unit); + "INTR\t\tCOMPLETING Sense Unit Status UNIT %d\n", + unit); id_set_status(ID_STAT_CEH); uptr->u4 = 0; if ((uptr->flags & UNIT_ATT) == 0) { @@ -357,8 +356,8 @@ t_stat id_unit_svc(UNIT *uptr) break; default: sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tINTR\t\tCOMPLETING OTHER COMMAND 0x%x UNIT %d\n", - R[NUM_PC], cmd, unit); + "INTR\t\tCOMPLETING OTHER COMMAND 0x%x UNIT %d\n", + cmd, unit); id_set_status(ID_STAT_CEH); uptr->u4 = 0; break; @@ -399,7 +398,7 @@ t_stat id_attach(UNIT *uptr, CONST char *cptr) { static const char *drives[] = {"HD30", "HD72", "HD72C", "HD135", "HD161", NULL}; - return sim_disk_attach_ex(uptr, cptr, 512, 1, TRUE, 0, id_dtab[ID_GET_DTYPE(uptr->flags)].name, + return sim_disk_attach_ex(uptr, cptr, 512, 1, TRUE, 0, id_dtab[ID_GET_DTYPE(uptr->flags)].name, 0, 0, (uptr->flags & ID_AUTOSIZE) ? drives : NULL); } @@ -485,8 +484,7 @@ uint32 id_read(uint32 pa, size_t size) * that's an error state. */ if (id_scnt == 0) { sim_debug(READ_MSG, &id_dev, - "[%08x] ERROR\tid_scnt = 0 but still in dma\n", - R[NUM_PC]); + "ERROR\tid_scnt = 0 but still in dma\n"); id_end_rw(ID_EST_OVR); return 0; } @@ -501,24 +499,21 @@ uint32 id_read(uint32 pa, size_t size) if (sim_disk_rdsect(id_sel_unit, lba, id_buf, §sread, 1) == SCPE_OK) { if (sectsread !=1) { sim_debug(READ_MSG, &id_dev, - "[%08x]\tERROR: ASKED TO READ ONE SECTOR, READ: %d\n", - R[NUM_PC], sectsread); + "ERROR: ASKED TO READ ONE SECTOR, READ: %d\n", + sectsread); } id_update_chs(); } else { /* Uh-oh! */ sim_debug(READ_MSG, &id_dev, - "[%08x]\tRDATA READ ERROR. Failure from sim_disk_rdsect!\n", - R[NUM_PC]); + "RDATA READ ERROR. Failure from sim_disk_rdsect!\n"); id_end_rw(ID_EST_DER); return 0; } } data = id_buf[id_buf_ptr++]; - sim_debug(READ_MSG, &id_dev, - "[%08x]\tDATA\t%02x\n", - R[NUM_PC], data); + sim_debug(READ_MSG, &id_dev, "DATA\t%02x\n", data); /* Done with this current sector, update id_scnt */ if (id_buf_ptr >= ID_SEC_SIZE) { @@ -538,8 +533,8 @@ uint32 id_read(uint32 pa, size_t size) data = id_idfield[id_idfield_ptr++]; sim_debug(READ_MSG, &id_dev, - "[%08x]\tID DATA\t%02x\n", - R[NUM_PC], data); + "ID DATA\t%02x\n", + data); if (id_idfield_ptr >= ID_IDFIELD_LEN) { if (id_scnt-- > 0) { @@ -562,13 +557,12 @@ uint32 id_read(uint32 pa, size_t size) } else { if (id_dpr < ID_FIFO_LEN) { sim_debug(READ_MSG, &id_dev, - "[%08x]\tDATA\t%02x\n", - R[NUM_PC], id_data[id_dpr]); + "DATA\t%02x\n", + id_data[id_dpr]); return id_data[id_dpr++]; } else { sim_debug(READ_MSG, &id_dev, - "[%08x] ERROR\tFIFO OVERRUN\n", - R[NUM_PC]); + "ERROR\tFIFO OVERRUN\n"); return 0; } } @@ -576,14 +570,14 @@ uint32 id_read(uint32 pa, size_t size) break; case ID_CMD_STAT_REG: /* Status Register */ sim_debug(READ_MSG, &id_dev, - "[%08x]\tSTATUS\t%02x\n", - R[NUM_PC], id_status|id_drq); + "STATUS\t%02x\n", + id_status|id_drq); return id_status|(id_drq ? 1u : 0); } sim_debug(READ_MSG, &id_dev, - "[%08x] Read of unsuported register %x\n", - R[NUM_PC], id_status); + "Read of unsuported register %x\n", + id_status); return 0; } @@ -607,8 +601,7 @@ void id_write(uint32 pa, uint32 val, size_t size) * that's an error state. */ if (id_scnt == 0) { sim_debug(WRITE_MSG, &id_dev, - "[%08x] ERROR\tid_scnt = 0 but still in dma\n", - R[NUM_PC]); + "ERROR\tid_scnt = 0 but still in dma\n"); id_end_rw(ID_EST_OVR); return; } @@ -617,12 +610,11 @@ void id_write(uint32 pa, uint32 val, size_t size) if (id_buf_ptr < ID_SEC_SIZE) { id_buf[id_buf_ptr++] = (uint8)(val & 0xff); sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tDATA\t%02x\n", - R[NUM_PC], (uint8)(val & 0xff)); + "DATA\t%02x\n", + (uint8)(val & 0xff)); } else { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tERROR\tWDATA OVERRUN\n", - R[NUM_PC]); + "ERROR\tWDATA OVERRUN\n"); id_end_rw(ID_EST_OVR); return; } @@ -637,8 +629,8 @@ void id_write(uint32 pa, uint32 val, size_t size) if (sim_disk_wrsect(id_sel_unit, lba, id_buf, §swritten, 1) == SCPE_OK) { if (sectswritten !=1) { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n", - R[NUM_PC], sectswritten); + "ERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n", + sectswritten); } id_update_chs(); if (--id_scnt == 0) { @@ -647,8 +639,8 @@ void id_write(uint32 pa, uint32 val, size_t size) } else { /* Uh-oh! */ sim_debug(WRITE_MSG, &id_dev, - "[%08x] ERROR\tWDATA WRITE ERROR. lba=%04x\n", - R[NUM_PC], lba); + "ERROR\tWDATA WRITE ERROR. lba=%04x\n", + lba); id_end_rw(ID_EST_DER); return; } @@ -656,14 +648,13 @@ void id_write(uint32 pa, uint32 val, size_t size) return; } else { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tDATA\t%02x\n", - R[NUM_PC], val); + "DATA\t%02x\n", + val); if (id_dpw < ID_FIFO_LEN) { id_data[id_dpw++] = (uint8) val; } else { sim_debug(WRITE_MSG, &id_dev, - "[%08x] ERROR\tFIFO OVERRUN\n", - R[NUM_PC]); + "ERROR\tFIFO OVERRUN\n"); } } return; @@ -691,29 +682,29 @@ void id_handle_command(uint8 val) if (aux_cmd & ID_AUX_CLCE) { sim_debug(WRITE_MSG, &id_dev, - "[%08x] \tCOMMAND\t%02x\tAUX:CLCE\n", - R[NUM_PC], val); + "COMMAND\t%02x\tAUX:CLCE\n", + val); id_clr_status(ID_STAT_CEH|ID_STAT_CEL); } if (aux_cmd & ID_AUX_HSRQ) { sim_debug(WRITE_MSG, &id_dev, - "[%08x] \tCOMMAND\t%02x\tAUX:HSRQ\n", - R[NUM_PC], val); + "COMMAND\t%02x\tAUX:HSRQ\n", + val); id_set_srqm(TRUE); } if (aux_cmd & ID_AUX_CLB) { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tAUX:CLBUF\n", - R[NUM_PC], val); + "COMMAND\t%02x\tAUX:CLBUF\n", + val); id_clear_fifo(); } if (aux_cmd & ID_AUX_RST) { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tAUX:RESET\n", - R[NUM_PC], val); + "COMMAND\t%02x\tAUX:RESET\n", + val); id_clear_fifo(); sim_cancel(id_sel_unit); sim_cancel(id_ctlr_unit); @@ -765,15 +756,15 @@ void id_handle_command(uint8 val) switch(cmd) { case ID_CMD_SIS: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tSense Int. Status\n", - R[NUM_PC], val); + "COMMAND\t%02x\tSense Int. Status\n", + val); id_clr_status(ID_STAT_SRQ); /* SIS immediately de-asserts SRQ */ id_activate(id_ctlr_unit, ID_SIS_WAIT); break; case ID_CMD_SPEC: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tSpecify - ETN=%02x ESN=%02x\n", - R[NUM_PC], val, id_data[3], id_data[4]); + "COMMAND\t%02x\tSpecify - ETN=%02x ESN=%02x\n", + val, id_data[3], id_data[4]); id_dtlh = id_data[1]; id_etn = id_data[3]; id_esn = id_data[4]; @@ -782,14 +773,14 @@ void id_handle_command(uint8 val) break; case ID_CMD_SUS: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tSense Unit Status - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tSense Unit Status - %d\n", + val, id_ua); id_activate(id_sel_unit, ID_SUS_WAIT); break; case ID_CMD_DERR: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tDetect Error\n", - R[NUM_PC], val); + "COMMAND\t%02x\tDetect Error\n", + val); id_activate(id_ctlr_unit, ID_CMD_WAIT); break; case ID_CMD_RECAL: @@ -798,13 +789,13 @@ void id_handle_command(uint8 val) id_seek_state[id_unit_num] = ID_SEEK_0; if (id_polling) { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tRecalibrate - %d - POLLING\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tRecalibrate - %d - POLLING\n", + val, id_ua); id_activate(id_sel_unit, 1000); } else { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tRecalibrate - %d - NORMAL\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tRecalibrate - %d - NORMAL\n", + val, id_ua); id_activate(id_sel_unit, (ID_RECAL_WAIT + (time * ID_SEEK_WAIT))); } break; @@ -818,20 +809,20 @@ void id_handle_command(uint8 val) if (id_polling) { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tSeek - %d - POLLING\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tSeek - %d - POLLING\n", + val, id_ua); id_activate(id_sel_unit, 4000); } else { sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tSeek - %d - NORMAL\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tSeek - %d - NORMAL\n", + val, id_ua); id_activate(id_sel_unit, ID_SEEK_BASE + (time * ID_SEEK_WAIT)); } break; case ID_CMD_FMT: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tFormat - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tFormat - %d\n", + val, id_ua); id_phn = id_data[0]; id_scnt = id_data[1]; @@ -850,12 +841,12 @@ void id_handle_command(uint8 val) lba = id_lba(id_cyl[id_unit_num], id_phn, sec++); if (sim_disk_wrsect(id_sel_unit, lba, id_buf, NULL, 1) == SCPE_OK) { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tFORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", - R[NUM_PC], id_phn, id_scnt, pattern, lba); + "FORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", + id_phn, id_scnt, pattern, lba); } else { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tFORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", - R[NUM_PC], id_phn, id_scnt, pattern, lba); + "FORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", + id_phn, id_scnt, pattern, lba); break; } } @@ -872,16 +863,16 @@ void id_handle_command(uint8 val) break; case ID_CMD_VID: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tVerify ID - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tVerify ID - %d\n", + val, id_ua); id_data[0] = 0; id_data[1] = 0x05; /* What do we put here? */ id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_RID: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tRead ID - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tRead ID - %d\n", + val, id_ua); if (id_sel_unit->flags & UNIT_ATT) { id_drq = TRUE; @@ -894,21 +885,21 @@ void id_handle_command(uint8 val) id_lsn = 0; } else { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ ID.\n", - R[NUM_PC], id_ua); + "UNIT %d NOT ATTACHED, CANNOT READ ID.\n", + id_ua); } id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_RDIAG: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tRead Diag - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tRead Diag - %d\n", + val, id_ua); id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_RDATA: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tRead Data - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tRead Data - %d\n", + val, id_ua); if (id_sel_unit->flags & UNIT_ATT) { id_drq = TRUE; id_buf_ptr = 0; @@ -922,33 +913,33 @@ void id_handle_command(uint8 val) id_scnt = id_data[5]; } else { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ DATA.\n", - R[NUM_PC], id_ua); + "UNIT %d NOT ATTACHED, CANNOT READ DATA.\n", + id_ua); } id_activate(id_sel_unit, ID_RW_WAIT); break; case ID_CMD_CHECK: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tCheck - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tCheck - %d\n", + val, id_ua); id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_SCAN: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tScan - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tScan - %d\n", + val, id_ua); id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_VDATA: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tVerify Data - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tVerify Data - %d\n", + val, id_ua); id_activate(id_sel_unit, ID_CMD_WAIT); break; case ID_CMD_WDATA: sim_debug(WRITE_MSG, &id_dev, - "[%08x]\tCOMMAND\t%02x\tWrite Data - %d\n", - R[NUM_PC], val, id_ua); + "COMMAND\t%02x\tWrite Data - %d\n", + val, id_ua); if (id_sel_unit->flags & UNIT_ATT) { id_drq = TRUE; id_buf_ptr = 0; @@ -962,8 +953,8 @@ void id_handle_command(uint8 val) id_scnt = id_data[5]; } else { sim_debug(EXECUTE_MSG, &id_dev, - "[%08x]\tUNIT %d NOT ATTACHED, CANNOT WRITE.\n", - R[NUM_PC], id_ua); + "UNIT %d NOT ATTACHED, CANNOT WRITE.\n", + id_ua); } id_activate(id_sel_unit, ID_RW_WAIT); break; @@ -996,5 +987,10 @@ t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) fprintf(st, " HD161 161.4 MB 11 1224 15 18 512 Maxtor XT1190 (SVR3+)\n\n"); fprintf(st, "The drive ID and geometry values are used when low-level formatting a\n"); fprintf(st, "drive using the AT&T 'idtools' utility.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; } diff --git a/3B2/3b2_id.h b/3B2/3b2_id.h index cecc4830..81f78e31 100644 --- a/3B2/3b2_id.h +++ b/3B2/3b2_id.h @@ -1,6 +1,6 @@ -/* 3b2_id.h: AT&T 3B2 Model 400 Hard Disk (uPD7261) Header +/* 3b2_id.h: uPD7261 Integrated Disk Controller - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/3B2/3b2_if.c b/3B2/3b2_if.c index 6512d5b3..cad717e1 100644 --- a/3B2/3b2_if.c +++ b/3B2/3b2_if.c @@ -1,6 +1,6 @@ -/* 3b2_if.c: AT&T 3B2 Floppy Controller (TMS2797NL) Implementation +/* 3b2_if.c: TMS2797 Integrated Floppy Controller - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -51,12 +51,12 @@ static SIM_INLINE uint32 if_lba(); * */ -#define IF_STEP_DELAY 3000 /* us */ -#define IF_R_DELAY 65000 /* us */ -#define IF_W_DELAY 70000 /* us */ -#define IF_VERIFY_DELAY 20000 /* us */ -#define IF_HLD_DELAY 60000 /* us */ -#define IF_HSW_DELAY 40000 /* us */ +#define IF_STEP_DELAY 300 /* us */ +#define IF_R_DELAY 6500 /* us */ +#define IF_W_DELAY 7000 /* us */ +#define IF_VERIFY_DELAY 2000 /* us */ +#define IF_HLD_DELAY 6000 /* us */ +#define IF_HSW_DELAY 4000 /* us */ #if defined(REV3) #define SET_INT CPU_SET_INT(INT_FLOPPY) @@ -97,9 +97,9 @@ uint32 if_sec_ptr = 0; /* Function implementation */ -static SIM_INLINE void if_activate(uint32 delay) +static SIM_INLINE void if_activate(uint32 delay_us) { - sim_activate_abs(&if_unit, delay); + sim_activate_after(&if_unit, delay_us); } t_stat if_svc(UNIT *uptr) @@ -618,6 +618,11 @@ t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) fprintf(st, " ------ ----- ----------- ------------- -----------\n"); fprintf(st, " 720 KB 2 80 9 512\n\n"); fprintf(st, "Physical media is Double Sided/Quad Density, 96 tpi, 250kbps MFM encoding.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; } diff --git a/3B2/3b2_if.h b/3B2/3b2_if.h index a6d40e58..256c084b 100644 --- a/3B2/3b2_if.h +++ b/3B2/3b2_if.h @@ -1,6 +1,6 @@ -/* 3b2_if.h: AT&T 3B2 Model Floppy Controller (TMS2797NL) Header +/* 3b2_if.h: TMS2797 Integrated Floppy Controller - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -44,7 +44,7 @@ typedef struct { uint8 read_addr_ptr; int8 step_dir; t_bool drq; -#if defined(REV3) +#if defined(REV3) uint8 csr; #endif } IF_STATE; diff --git a/3B2/3b2_io.c b/3B2/3b2_io.c index eba4bf54..80d2df51 100644 --- a/3B2/3b2_io.c +++ b/3B2/3b2_io.c @@ -1,6 +1,6 @@ -/* 3b2_io.c: AT&T 3B2 Model 400 IO and CIO feature cards +/* 3b2_io.c: Common I/O (CIO) Feature Card Support - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -45,7 +45,6 @@ #endif CIO_STATE cio[CIO_SLOTS] = {{0}}; - uint16 cio_int_req = 0; /* Bitset of card slots requesting interrupts */ #if defined(REV3) @@ -53,8 +52,8 @@ iolink iotable[] = { { MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write }, { IFBASE, IFBASE+IFSIZE, &if_read, &if_write }, { IFCSRBASE, IFCSRBASE+IFCSRSIZE, &if_csr_read, &if_csr_write }, - { FLTLBASE, FLTLSIZE, &flt_read, &flt_write }, - { FLTHBASE, FLTHSIZE, &flt_read, &flt_write }, + { FLTLBASE, FLTLBASE+FLTLSIZE, &flt_read, &flt_write }, + { FLTHBASE, FLTHBASE+FLTHSIZE, &flt_read, &flt_write }, { NVRBASE, NVRBASE+NVRSIZE, &nvram_read, &nvram_write }, { TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write }, { CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write }, @@ -85,24 +84,73 @@ iolink iotable[] = { }; #endif -void cio_clear(uint8 cid) +/* + * Insert a CIO card into the backplane. + * + * If a space could be found, SPCE_OK is returned, and the slot the + * card was installed in is placed in `slot`. + * + * If no room is availalbe, return SCPE_NXM. + */ +t_stat cio_install(uint16 id, + CONST char *name, + uint8 ipl, + void (*exp_handler)(uint8 slot), + void (*full_handler)(uint8 slot), + void (*sysgen)(uint8 slot), + void (*reset_handler)(uint8 slot), + uint8 *slot) { - cio[cid].id = 0; - cio[cid].exp_handler = NULL; - cio[cid].full_handler = NULL; - cio[cid].reset_handler = NULL; - cio[cid].sysgen = NULL; - cio[cid].rqp = 0; - cio[cid].cqp = 0; - cio[cid].rqs = 0; - cio[cid].cqs = 0; - cio[cid].ivec = 0; - cio[cid].no_rque = 0; - cio[cid].ipl = 0; - cio[cid].sysgen_s = 0; - cio[cid].seqbit = 0; - cio[cid].op = 0; - CIO_CLR_INT(cid); + uint8 s; + + for (s = 0; s < CIO_SLOTS; s++) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cio_install] cio[%d]: populated=%d, id=%d\n", + s, cio[s].populated, cio[s].id); + if (!cio[s].populated) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[cio_install] >>> I found a free slot! Slot #%d has nothing\n", s); + *slot = s; + /* Ensure the slot is in a clean state */ + cio_remove(s); + /* Populate the slot */ + cio[s].populated = TRUE; + cio[s].id = id; + cio[s].ipl = ipl; + strncpy(cio[s].name, name, CIO_NAME_LEN); + cio[s].exp_handler = exp_handler; + cio[s].full_handler = full_handler; + cio[s].sysgen = sysgen; + cio[s].reset_handler = reset_handler; + return SCPE_OK; + } + } + + return SCPE_NXM; +} + +/* + * Remove a CIO card from the specified backplane slot. + */ +void cio_remove(uint8 slot) +{ + memset(&cio[slot], 0, sizeof(CIO_STATE)); + /* cio[slot].populated = FALSE; */ + CIO_CLR_INT(slot); +} + +/* + * Remove all CIO cards of the matching type. + */ +void cio_remove_all(uint16 id) +{ + int i; + + for (i = 0; i < CIO_SLOTS; i++) { + if (cio[i].populated && cio[i].id == id) { + cio_remove(i); + } + } } /* @@ -130,81 +178,80 @@ uint32 cio_crc32_shift(uint32 crc, uint8 data) return ~crc; } -void cio_sysgen(uint8 cid) +void cio_sysgen(uint8 slot) { uint32 sysgen_p; - sysgen_p = pread_w(SYSGEN_PTR); + sysgen_p = pread_w(SYSGEN_PTR, BUS_PER); sim_debug(CIO_DBG, &cpu_dev, - "[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n", - R[NUM_PC], cid, sysgen_p); + "[SYSGEN] Starting sysgen for card %d (%s). sysgen_p=%08x\n", + slot, cio[slot].name, sysgen_p); /* seqbit is always reset to 0 on completion */ - cio[cid].seqbit = 0; + cio[slot].seqbit = 0; - cio[cid].rqp = pread_w(sysgen_p); - cio[cid].cqp = pread_w(sysgen_p + 4); - cio[cid].rqs = pread_b(sysgen_p + 8); - cio[cid].cqs = pread_b(sysgen_p + 9); - cio[cid].ivec = pread_b(sysgen_p + 10); - cio[cid].no_rque = pread_b(sysgen_p + 11); + cio[slot].rqp = pread_w(sysgen_p, BUS_PER); + cio[slot].cqp = pread_w(sysgen_p + 4, BUS_PER); + cio[slot].rqs = pread_b(sysgen_p + 8, BUS_PER); + cio[slot].cqs = pread_b(sysgen_p + 9, BUS_PER); + cio[slot].ivec = pread_b(sysgen_p + 10, BUS_PER); + cio[slot].no_rque = pread_b(sysgen_p + 11, BUS_PER); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen rqp = %08x\n", - cio[cid].rqp); + cio[slot].rqp); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen cqp = %08x\n", - cio[cid].cqp); + cio[slot].cqp); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen rqs = %02x\n", - cio[cid].rqs); + cio[slot].rqs); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen cqs = %02x\n", - cio[cid].cqs); + cio[slot].cqs); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen ivec = %02x\n", - cio[cid].ivec); + cio[slot].ivec); sim_debug(CIO_DBG, &cpu_dev, "[SYSGEN] sysgen no_rque = %02x\n", - cio[cid].no_rque); + cio[slot].no_rque); /* If the card has a custom sysgen handler, run it */ - if (cio[cid].sysgen != NULL) { - cio[cid].sysgen(cid); + if (cio[slot].sysgen != NULL) { + cio[slot].sysgen(slot); } else { sim_debug(CIO_DBG, &cpu_dev, - "[%08x] [cio_sysgen] Not running custom sysgen.\n", - R[NUM_PC]); + "[cio_sysgen] Not running custom sysgen.\n"); } } -void cio_cexpress(uint8 cid, uint32 esize, cio_entry *cqe, uint8 *app_data) +void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data) { uint32 i, cqp; - cqp = cio[cid].cqp; + cqp = cio[slot].cqp; sim_debug(CIO_DBG, &cpu_dev, - "[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n", - R[NUM_PC], cqp, cio[cid].seqbit); + "[cio_cexpress] [%s] cqp = %08x seqbit = %d\n", + cio[slot].name, cqp, cio[slot].seqbit); - cio[cid].seqbit ^= 1; + cio[slot].seqbit ^= 1; - cqe->subdevice |= (cio[cid].seqbit << 6); + cqe->subdevice |= (cio[slot].seqbit << 6); - pwrite_h(cqp, cqe->byte_count); - pwrite_b(cqp + 2, cqe->subdevice); - pwrite_b(cqp + 3, cqe->opcode); - pwrite_w(cqp + 4, cqe->address); + pwrite_h(cqp, cqe->byte_count, BUS_PER); + pwrite_b(cqp + 2, cqe->subdevice, BUS_PER); + pwrite_b(cqp + 3, cqe->opcode, BUS_PER); + pwrite_w(cqp + 4, cqe->address, BUS_PER); /* Write application-specific data. */ for (i = 0; i < (esize - QESIZE); i++) { - pwrite_b(cqp + 8 + i, app_data[i]); + pwrite_b(cqp + 8 + i, app_data[i], BUS_PER); } } -void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, +void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data) { uint32 i, cqp, top; @@ -215,7 +262,7 @@ void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, /* Get the physical address of the completion queue * in main memory */ - cqp = cio[cid].cqp; + cqp = cio[slot].cqp; /* Get the physical address of the first entry in * the completion queue */ @@ -223,47 +270,47 @@ void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, /* Get the load pointer. This is a 16-bit absolute offset * from the top of the queue to the start of the entry. */ - lp = pread_h(cqp + esize); + lp = pread_h(cqp + esize, BUS_PER); /* Load the entry at the supplied address */ - pwrite_h(top + lp, cqe->byte_count); - pwrite_b(top + lp + 2, cqe->subdevice); - pwrite_b(top + lp + 3, cqe->opcode); - pwrite_w(top + lp + 4, cqe->address); + pwrite_h(top + lp, cqe->byte_count, BUS_PER); + pwrite_b(top + lp + 2, cqe->subdevice, BUS_PER); + pwrite_b(top + lp + 3, cqe->opcode, BUS_PER); + pwrite_w(top + lp + 4, cqe->address, BUS_PER); /* Write application-specific data. */ for (i = 0; i < (esize - QESIZE); i++) { - pwrite_b(top + lp + 8 + i, app_data[i]); + pwrite_b(top + lp + 8 + i, app_data[i], BUS_PER); } /* Increment the load pointer to the next queue location. * If we go past the end of the queue, wrap around to the * start of the queue */ - if (cio[cid].cqs > 0) { - lp = (lp + esize) % (esize * cio[cid].cqs); + if (cio[slot].cqs > 0) { + lp = (lp + esize) % (esize * cio[slot].cqs); /* Store it back to the correct location */ - pwrite_h(cqp + esize, lp); + pwrite_h(cqp + esize, lp, BUS_PER); } } /* * Retrieve the Express Entry from the Request Queue */ -void cio_rexpress(uint8 cid, uint32 esize, cio_entry *rqe, uint8 *app_data) +void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data) { uint32 i; uint32 rqp; - rqp = cio[cid].rqp; + rqp = cio[slot].rqp; /* Unload the express entry from the request queue */ - rqe->byte_count = pread_h(rqp); - rqe->subdevice = pread_b(rqp + 2); - rqe->opcode = pread_b(rqp + 3); - rqe->address = pread_w(rqp + 4); + rqe->byte_count = pread_h(rqp, BUS_PER); + rqe->subdevice = pread_b(rqp + 2, BUS_PER); + rqe->opcode = pread_b(rqp + 3, BUS_PER); + rqe->address = pread_w(rqp + 4, BUS_PER); for (i = 0; i < (esize - QESIZE); i++) { - app_data[i] = pread_b(rqp + 8 + i); + app_data[i] = pread_b(rqp + 8 + i, BUS_PER); } } @@ -276,19 +323,19 @@ void cio_rexpress(uint8 cid, uint32 esize, cio_entry *rqe, uint8 *app_data) * Returns SCPE_OK on success, or SCPE_NXM if no entry was found. * */ -t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, +t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data) { uint32 i, rqp, top; uint16 lp, ulp; /* Get the physical address of the request queue in main memory */ - rqp = cio[cid].rqp + + rqp = cio[slot].rqp + esize + - (qnum * (LUSIZE + (esize * cio[cid].rqs))); + (qnum * (LUSIZE + (esize * cio[slot].rqs))); - lp = pread_h(rqp); - ulp = pread_h(rqp + 2); + lp = pread_h(rqp, BUS_PER); + ulp = pread_h(rqp + 2, BUS_PER); /* Check to see if the request queue is empty. If it is, there's * nothing to take. */ @@ -299,22 +346,22 @@ t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, top = rqp + LUSIZE; /* Retrieve the entry at the supplied address */ - rqe->byte_count = pread_h(top + ulp); - rqe->subdevice = pread_b(top + ulp + 2); - rqe->opcode = pread_b(top + ulp + 3); - rqe->address = pread_w(top + ulp + 4); + rqe->byte_count = pread_h(top + ulp, BUS_PER); + rqe->subdevice = pread_b(top + ulp + 2, BUS_PER); + rqe->opcode = pread_b(top + ulp + 3, BUS_PER); + rqe->address = pread_w(top + ulp + 4, BUS_PER); /* Read application-specific data. */ for (i = 0; i < (esize - QESIZE); i++) { - app_data[i] = pread_b(top + ulp + 8 + i); + app_data[i] = pread_b(top + ulp + 8 + i, BUS_PER); } /* Increment the unload pointer to the next queue location. If we * go past the end of the queue, wrap around to the start of the * queue */ - if (cio[cid].rqs > 0) { - ulp = (ulp + esize) % (esize * cio[cid].rqs); - pwrite_h(rqp + 2, ulp); + if (cio[slot].rqs > 0) { + ulp = (ulp + esize) % (esize * cio[slot].rqs); + pwrite_h(rqp + 2, ulp, BUS_PER); } return SCPE_OK; @@ -323,70 +370,70 @@ t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, /* * Return the Load Pointer for the given request queue */ -uint16 cio_r_lp(uint8 cid, uint32 qnum, uint32 esize) +uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize) { uint32 rqp; - rqp = cio[cid].rqp + + rqp = cio[slot].rqp + esize + - (qnum * (LUSIZE + (esize * cio[cid].rqs))); + (qnum * (LUSIZE + (esize * cio[slot].rqs))); - return pread_h(rqp); + return pread_h(rqp, BUS_PER); } /* * Return the Unload Pointer for the given request queue */ -uint16 cio_r_ulp(uint8 cid, uint32 qnum, uint32 esize) +uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize) { uint32 rqp; - rqp = cio[cid].rqp + + rqp = cio[slot].rqp + esize + - (qnum * (LUSIZE + (esize * cio[cid].rqs))); + (qnum * (LUSIZE + (esize * cio[slot].rqs))); - return pread_h(rqp + 2); + return pread_h(rqp + 2, BUS_PER); } -uint16 cio_c_lp(uint8 cid, uint32 esize) +uint16 cio_c_lp(uint8 slot, uint32 esize) { uint32 cqp; - cqp = cio[cid].cqp + esize; - return pread_h(cqp); + cqp = cio[slot].cqp + esize; + return pread_h(cqp, BUS_PER); } -uint16 cio_c_ulp(uint8 cid, uint32 esize) +uint16 cio_c_ulp(uint8 slot, uint32 esize) { uint32 cqp; - cqp = cio[cid].cqp + esize; - return pread_h(cqp + 2); + cqp = cio[slot].cqp + esize; + return pread_h(cqp + 2, BUS_PER); } /* * Returns true if there is room in the completion queue * for a new entry. */ -t_bool cio_cqueue_avail(uint8 cid, uint32 esize) +t_bool cio_cqueue_avail(uint8 slot, uint32 esize) { uint32 lp, ulp; - lp = pread_h(cio[cid].cqp + esize); - ulp = pread_h(cio[cid].cqp + esize + 2); + lp = pread_h(cio[slot].cqp + esize, BUS_PER); + ulp = pread_h(cio[slot].cqp + esize + 2, BUS_PER); - return(((lp + esize) % (cio[cid].cqs * esize)) != ulp); + return(((lp + esize) % (cio[slot].cqs * esize)) != ulp); } -t_bool cio_rqueue_avail(uint8 cid, uint32 qnum, uint32 esize) +t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize) { uint32 rqp, lp, ulp; /* Get the physical address of the request queue in main memory */ - rqp = cio[cid].rqp + + rqp = cio[slot].rqp + esize + - (qnum * (LUSIZE + (esize * cio[cid].rqs))); + (qnum * (LUSIZE + (esize * cio[slot].rqs))); - lp = pread_h(rqp); - ulp = pread_h(rqp + 2); + lp = pread_h(rqp, BUS_PER); + ulp = pread_h(rqp + 2, BUS_PER); return(lp != ulp); } @@ -394,67 +441,23 @@ t_bool cio_rqueue_avail(uint8 cid, uint32 qnum, uint32 esize) uint32 io_read(uint32 pa, size_t size) { iolink *p; - uint8 cid, reg, data; + uint8 slot, reg, data; #if defined (REV3) - /* - * NOTE: Not Yet Implemented, but: If 0x4BF00 is accessed and does - * not result in an error, the system assumes there are two MMUs - * installed. I think 0x4b000 is where a second MMU would live in - * IO space if there were multiple MMUs. - */ - if ((pa == MADDR_SLOT_0) || - (pa == MADDR_SLOT_1) || - (pa == MADDR_SLOT_2) || - (pa == MADDR_SLOT_3)) { - switch(MEM_SIZE) { - case MSIZ_4M: - /* Configure with one 4MB boards */ - if (pa < MADDR_SLOT_1) { - return MEMID_4M; - } - break; - case MSIZ_8M: - /* Configure with two 4MB boards */ - if (pa < MADDR_SLOT_2) { - return MEMID_4M; - } - break; - case MSIZ_16M: - /* Configure with four 4MB boards */ - return MEMID_4M; - case MSIZ_32M: - /* Configure with two 16MB boards */ - if (pa < MADDR_SLOT_2) { - return MEMID_16M; - } - break; - case MSIZ_64M: - /* Configure with four 16MB boards */ - return MEMID_16M; - default: - return 0; - } - - return 0; - } - if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[UBUB] (VCACHE) Read addr %08x\n", pa); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return 0; } if (pa >= BUB_BOTTOM && pa < BUB_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[BUB] Read addr %08x\n", pa); CSRBIT(CSRTIMO, TRUE); - - /* TODO: I don't remember why we do this! */ - if ((pa & 0xfff) == 3) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - - /* TODO: Implement BUB */ - return 1; + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + return 0; } #else if (pa == MEMSIZE_REG) { @@ -482,14 +485,14 @@ uint32 io_read(uint32 pa, size_t size) /* CIO board area */ if (pa >= CIO_BOTTOM && pa < CIO_TOP) { - cid = CID(pa); - reg = pa - CADDR(cid); + slot = SLOT(pa); + reg = pa - CADDR(slot); - if (cio[cid].id == 0) { + if (!cio[slot].populated) { /* Nothing lives here */ sim_debug(IO_DBG, &cpu_dev, - "[READ] [%08x] No card at cid=%d reg=%d\n", - R[NUM_PC], cid, reg); + "[READ] No card at slot=%d reg=%d\n", + slot, reg); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return 0; @@ -504,96 +507,96 @@ uint32 io_read(uint32 pa, size_t size) switch (reg) { case IOF_ID: case IOF_VEC: - switch(cio[cid].sysgen_s) { + switch(cio[slot].sysgen_s) { case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ case CIO_INT0: /* We've seen an INT0 but not an INT1. */ - cio[cid].sysgen_s |= CIO_INT0; + cio[slot].sysgen_s |= CIO_INT0; sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT0) ID\n", - R[NUM_PC], cid); + "[READ] [%s] (%d INT0) ID\n", + cio[slot].name, slot); /* Return the correct byte of our board ID */ if (reg == IOF_ID) { - data = (cio[cid].id >> 8) & 0xff; + data = (cio[slot].id >> 8) & 0xff; } else { - data = (cio[cid].id & 0xff); + data = (cio[slot].id & 0xff); } break; case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ - cio[cid].sysgen_s |= CIO_INT0; + cio[slot].sysgen_s |= CIO_INT0; sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT0) SYSGEN\n", - R[NUM_PC], cid); - cio_sysgen(cid); - data = cio[cid].ivec; + "[READ] [%s] (%d INT0) SYSGEN\n", + cio[slot].name, slot); + cio_sysgen(slot); + data = cio[slot].ivec; break; case CIO_SYSGEN: /* We've already sysgen'ed */ - cio[cid].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */ + cio[slot].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */ sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT0) EXPRESS JOB\n", - R[NUM_PC], cid); - cio[cid].exp_handler(cid); - data = cio[cid].ivec; + "[READ] [%s] (%d INT0) EXPRESS JOB\n", + cio[slot].name, slot); + cio[slot].exp_handler(slot); + data = cio[slot].ivec; break; default: /* This should never happen */ stop_reason = STOP_ERR; sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", - R[NUM_PC], cid, cio[cid].sysgen_s); + "[READ] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); data = 0; break; } return data; case IOF_CTRL: - switch(cio[cid].sysgen_s) { + switch(cio[slot].sysgen_s) { case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ case CIO_INT1: /* We've seen an INT1 but not an INT0 */ /* There's nothing to do in this instance */ sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT1) IGNORED\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; + "[READ] [%s] (%d INT1) IGNORED\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; break; case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT1) SYSGEN\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; - cio_sysgen(cid); + "[READ] [%s] (%d INT1) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio_sysgen(slot); break; case CIO_SYSGEN: /* We've already sysgen'ed */ sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT1) FULL\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */ - cio[cid].full_handler(cid); + "[READ] [%s] (%d INT1) FULL\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */ + cio[slot].full_handler(slot); break; default: /* This should never happen */ stop_reason = STOP_ERR; sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", - R[NUM_PC], cid, cio[cid].sysgen_s); + "[READ] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); break; } return 0; /* Data returned is arbitrary */ case IOF_STAT: sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] (%d RESET)\n", - R[NUM_PC], cid); - if (cio[cid].reset_handler) { - cio[cid].reset_handler(cid); + "[READ] [%s] (%d RESET)\n", + cio[slot].name, slot); + if (cio[slot].reset_handler) { + cio[slot].reset_handler(slot); } - cio[cid].sysgen_s = 0; + cio[slot].sysgen_s = 0; return 0; /* Data returned is arbitrary */ default: /* We should never reach here, but if we do, there's * nothing listening. */ sim_debug(CIO_DBG, &cpu_dev, - "[READ] [%08x] No card at cid=%d reg=%d\n", - R[NUM_PC], cid, reg); + "[READ] No card at slot=%d reg=%d\n", + slot, reg); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return 0; @@ -609,8 +612,8 @@ uint32 io_read(uint32 pa, size_t size) /* Not found. */ sim_debug(IO_DBG, &cpu_dev, - "[%08x] [io_read] ADDR=%08x: No device found.\n", - R[NUM_PC], pa); + "[io_read] ADDR=%08x: No device found.\n", + pa); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return 0; @@ -619,36 +622,38 @@ uint32 io_read(uint32 pa, size_t size) void io_write(uint32 pa, uint32 val, size_t size) { iolink *p; - uint8 cid, reg; + uint8 slot, reg; #if defined(REV3) if (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[UBUB] (VCACHE) Write addr %08x val 0x%x\n", + pa, val); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return; } if (pa >= BUB_BOTTOM && pa < BUB_TOP) { + sim_debug(EXECUTE_MSG, &cpu_dev, + "[BUB] Write addr %08x val 0x%x\n", + pa, val); CSRBIT(CSRTIMO, TRUE); - /* TODO: I don't remember why we do this! */ - if ((pa & 0xfff) == 3) { - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); - } - /* TODO: Implement BUB */ + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return; } -#endif +#endif /* Feature Card Area */ if (pa >= CIO_BOTTOM && pa < CIO_TOP) { - cid = CID(pa); - reg = pa - CADDR(cid); + slot = SLOT(pa); + reg = pa - CADDR(slot); - if (cio[cid].id == 0) { + if (!cio[slot].populated) { /* Nothing lives here */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] No card at cid=%d reg=%d\n", - R[NUM_PC], cid, reg); + "[WRITE] No card at slot=%d reg=%d\n", + slot, reg); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return; @@ -663,87 +668,87 @@ void io_write(uint32 pa, uint32 val, size_t size) switch (reg) { case IOF_ID: case IOF_VEC: - switch(cio[cid].sysgen_s) { + switch(cio[slot].sysgen_s) { case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ case CIO_INT0: /* We've seen an INT0 but not an INT1. */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT0) ID\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT0; + "[WRITE] [%s] (%d INT0) ID\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; break; case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT0) SYSGEN\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT0; - cio_sysgen(cid); + "[WRITE] [%s] (%d INT0) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; + cio_sysgen(slot); break; case CIO_SYSGEN: /* We've already sysgen'ed */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT0) EXPRESS JOB\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT0; - cio[cid].exp_handler(cid); + "[WRITE] [%s] (%d INT0) EXPRESS JOB\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT0; + cio[slot].exp_handler(slot); break; default: /* This should never happen */ stop_reason = STOP_ERR; sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", - R[NUM_PC], cid, cio[cid].sysgen_s); + "[WRITE] [%s] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); break; } return; case IOF_CTRL: - switch(cio[cid].sysgen_s) { + switch(cio[slot].sysgen_s) { case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */ case CIO_INT1: /* We've seen an INT1 but not an INT0 */ /* There's nothing to do in this instance */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT1) IGNORED\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; + "[WRITE] [%s] (%d INT1) IGNORED\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; break; case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT1) SYSGEN\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; - cio_sysgen(cid); + "[WRITE] [%s] (%d INT1) SYSGEN\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio_sysgen(slot); break; case CIO_SYSGEN: /* We've already sysgen'ed */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT1) FULL\n", - R[NUM_PC], cid); - cio[cid].sysgen_s |= CIO_INT1; - cio[cid].full_handler(cid); + "[WRITE] [%s] (%d INT1) FULL\n", + cio[slot].name, slot); + cio[slot].sysgen_s |= CIO_INT1; + cio[slot].full_handler(slot); break; default: /* This should never happen */ stop_reason = STOP_ERR; sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", - R[NUM_PC], cid, cio[cid].sysgen_s); + "[WRITE] [%s] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n", + cio[slot].name, slot, cio[slot].sysgen_s); break; } return; case IOF_STAT: sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] (%d RESET)\n", - R[NUM_PC], cid); - if (cio[cid].reset_handler) { - cio[cid].reset_handler(cid); + "[WRITE] [%s] (%d RESET)\n", + cio[slot].name, slot); + if (cio[slot].reset_handler) { + cio[slot].reset_handler(slot); } - cio[cid].sysgen_s = 0; + cio[slot].sysgen_s = 0; return; default: /* We should never reach here, but if we do, there's * nothing listening. */ sim_debug(CIO_DBG, &cpu_dev, - "[WRITE] [%08x] No card at cid=%d reg=%d\n", - R[NUM_PC], cid, reg); + "[WRITE] No card at slot=%d reg=%d\n", + slot, reg); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); return; @@ -760,27 +765,8 @@ void io_write(uint32 pa, uint32 val, size_t size) /* Not found. */ sim_debug(IO_DBG, &cpu_dev, - "[%08x] [io_write] ADDR=%08x: No device found.\n", - R[NUM_PC], pa); + "[io_write] ADDR=%08x: No device found.\n", + pa); CSRBIT(CSRTIMO, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } - - -/* For debugging only */ -void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type, - uint32 esize, cio_entry *entry, uint8 *app_data) -{ - char appl[64]; - uint32 i, c_offset; - - for (i = 0, c_offset=0; i < (esize - QESIZE); i++) { - snprintf(appl + c_offset, 3, "%02x", app_data[i]); - c_offset += 2; - } - - sim_debug(dbits, dev, - "*** %s ENTRY: byte_count=%04x, subdevice=%02x, opcode=%d, address=%08x, app_data=%s\n", - type, entry->byte_count, entry->subdevice, - entry->opcode, entry->address, appl); -} diff --git a/3B2/3b2_io.h b/3B2/3b2_io.h index d06e5b90..77a1306d 100644 --- a/3B2/3b2_io.h +++ b/3B2/3b2_io.h @@ -1,6 +1,6 @@ -/* 3b2_io.h: AT&T 3B2 Model 400 IO dispatch (Header) +/* 3b2_io.h: Common I/O (CIO) Feature Card Support - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -119,9 +119,18 @@ #define IO_BOTTOM 0x40000 #define IO_TOP 0x50000 +#if defined(REV3) +#define UBUS_BOTTOM 0x1c00000 +#define UBUS_TOP 0x2000000 +#endif + /* CIO area */ #define CIO_BOTTOM 0x200000 +#if defined(REV3) +#define CIO_TOP 0x1a00000 +#else #define CIO_TOP 0x2000000 +#endif #define IOF_ID 0 #define IOF_VEC 1 @@ -143,7 +152,7 @@ #define CIO_SYSGEN_OK 3 /* Map a physical address to a card ID */ -#define CID(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) +#define SLOT(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1) /* Map a card ID to a base address */ #define CADDR(bid) (((((bid) + 1) * 2) << 0x14)) @@ -160,15 +169,19 @@ #define CIO_INT1 2 #define CIO_SYSGEN 3 -#define CIO_SET_INT(slot) (cio_int_req |= (1 << slot)) +#define CIO_SET_INT(slot) if (cio[slot].populated && cio[slot].ivec >= 2) (cio_int_req |= (1 << slot)) #define CIO_CLR_INT(slot) (cio_int_req &= ~(1 << slot)) +#define CIO_NAME_LEN 8 + typedef struct { - uint16 id; /* Card ID */ - void (*exp_handler)(uint8 cid); /* Handler for express jobs */ - void (*full_handler)(uint8 cid); /* Handler for full jobs */ - void (*sysgen)(uint8 cid); /* Sysgen routine (optional) */ - void (*reset_handler)(uint8 cid); /* RESET request handler (optional) */ + t_bool populated; /* Populated? */ + uint16 id; /* CIO identifier */ + char name[CIO_NAME_LEN]; /* Device name */ + void (*exp_handler)(uint8 slot); /* Handler for express jobs */ + void (*full_handler)(uint8 slot); /* Handler for full jobs */ + void (*sysgen)(uint8 slot); /* Sysgen routine (optional) */ + void (*reset_handler)(uint8 slot); /* RESET request handler (optional) */ uint32 rqp; /* Request Queue Pointer */ uint32 cqp; /* Completion Queue Pointer */ uint8 rqs; /* Request queue size */ @@ -226,26 +239,32 @@ typedef struct { t_stat cio_reset(DEVICE *dptr); t_stat cio_svc(UNIT *uptr); -void cio_clear(uint8 cid); +t_stat cio_install(uint16 id, + CONST char *name, + uint8 ipl, + void (*exp_handler)(uint8 slot), + void (*full_handler)(uint8 slot), + void (*sysgen)(uint8 slot), + void (*reset_handler)(uint8 slot), + uint8 *slot); +void cio_remove(uint8 slot); +void cio_remove_all(uint16 id); uint32 cio_crc32_shift(uint32 crc, uint8 data); -void cio_cexpress(uint8 cid, uint32 esize, cio_entry *cqe, uint8 *app_data); -void cio_cqueue(uint8 cid, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data); -t_bool cio_cqueue_avail(uint8 cid, uint32 esize); -void cio_rexpress(uint8 cid, uint32 esize, cio_entry *rqe, uint8 *app_data); -t_stat cio_rqueue(uint8 cid, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data); -t_bool cio_rqueue_avail(uint8 cid, uint32 qnum, uint32 esize); -uint16 cio_r_lp(uint8 cid, uint32 qnum, uint32 esize); -uint16 cio_r_ulp(uint8 cid, uint32 qnum, uint32 esize); -uint16 cio_c_lp(uint8 cid, uint32 esize); -uint16 cio_c_ulp(uint8 cid, uint32 esize); -void cio_sysgen(uint8 cid); +void cio_cexpress(uint8 slot, uint32 esize, cio_entry *cqe, uint8 *app_data); +void cio_cqueue(uint8 slot, uint8 cmd_stat, uint32 esize, cio_entry *cqe, uint8 *app_data); +t_bool cio_cqueue_avail(uint8 slot, uint32 esize); +void cio_rexpress(uint8 slot, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_stat cio_rqueue(uint8 slot, uint32 qnum, uint32 esize, cio_entry *rqe, uint8 *app_data); +t_bool cio_rqueue_avail(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_r_lp(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_r_ulp(uint8 slot, uint32 qnum, uint32 esize); +uint16 cio_c_lp(uint8 slot, uint32 esize); +uint16 cio_c_ulp(uint8 slot, uint32 esize); +void cio_sysgen(uint8 slot); uint32 io_read(uint32 pa, size_t size); void io_write(uint32 pa, uint32 val, size_t size); -void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type, - uint32 esize, cio_entry *entry, uint8 *app_data); - extern uint16 cio_int_req; extern CIO_STATE cio[CIO_SLOTS]; diff --git a/3B2/3b2_iu.c b/3B2/3b2_iu.c index b9a67aa6..e3639eec 100644 --- a/3B2/3b2_iu.c +++ b/3B2/3b2_iu.c @@ -1,6 +1,6 @@ -/* 3b2_iu.c: SCN2681A Dual UART Implementation +/* 3b2_iu.c: SCN2681A Dual UART - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -39,61 +39,62 @@ #include "3b2_stddev.h" #include "3b2_timer.h" -#define SET_INT do { \ - CPU_SET_INT(INT_UART); \ - SET_CSR(CSRUART); \ - } while (0) - -#define CLR_INT do { \ - CPU_CLR_INT(INT_UART); \ - CLR_CSR(CSRUART); \ - } while (0) - -#if defined(REV3) -#define SET_DMA_INT do { \ - CPU_SET_INT(INT_UART_DMA); \ - SET_CSR(CSRDMA); \ - } while (0) - -#define CLR_DMA_INT do { \ - CPU_CLR_INT(INT_UART_DMA); \ - CLR_CSR(CSRDMA); \ - } while (0) -#else -#define SET_DMA_INT do { \ - CPU_SET_INT(INT_DMA); \ - SET_CSR(CSRDMA); \ - } while (0) - -#define CLR_DMA_INT do { \ - CPU_CLR_INT(INT_DMA); \ - CLR_CSR(CSRDMA); \ - } while (0) -#endif - -/* Static function declarations */ -static SIM_INLINE void iu_w_cmd(uint8 portno, uint8 val); - /* - * The 3B2/400 has two on-board serial ports, labeled CONSOLE and - * CONTTY. The CONSOLE port is (naturally) the system console. The - * CONTTY port serves as a secondary serial port for an additional - * terminal. + * The 3B2/400 and 3B2/700 both have two on-board serial ports, + * labeled CONSOLE and CONTTY. The CONSOLE port is the system console. + * The CONTTY port serves as a secondary serial port for one + * additional terminal. * * These lines are driven by an SCN2681A Dual UART, with two receivers * and two transmitters. * * In addition to the two TX/RX ports, the SCN27681A also has one - * programmable timer. + * programmable timer that is used in the 3B2 for various one-shot + * timing tasks. * * The SCN2681A UART is represented here by four devices: * - * - Console TTI (Input, port A) - * - Console TTO (Output, port A) - * - Contty (I/O, port B. Terminal multiplexer with one line) + * - Console TTI (Console Input, port A) + * - Console TTO (Console Output, port A) + * - CONTTY (I/O, port B. Terminal multiplexer with one line) * - IU Timer */ +#if defined(REV3) +#define DMA_INT INT_UART_DMA +#else +#define DMA_INT INT_DMA +#endif + +#define UPDATE_IRQ do { \ + if (iu_state.imr & iu_state.isr) { \ + CPU_SET_INT(INT_UART); \ + SET_CSR(CSRUART); \ + } else { \ + CPU_CLR_INT(INT_UART); \ + CLR_CSR(CSRUART); \ + } \ + } while (0) + +#define SET_DMA_INT do { \ + CPU_SET_INT(DMA_INT); \ + SET_CSR(CSRDMA); \ + } while (0) + +#define CLR_DMA_INT do { \ + CPU_CLR_INT(DMA_INT); \ + CLR_CSR(CSRDMA); \ + } while (0) + +#define LOOPBACK(P) (((P)->mode[1] & 0xc0) == 0x80) +#define TX_ENABLED(P) ((P).conf & TX_EN) +#define PORTNO(P) ((P) == &iu_console ? PORT_A : PORT_B) + +/* Static function declarations */ +static void iu_w_cmd(IU_PORT *port, uint8 val); +static t_stat iu_tx(IU_PORT *port, uint8 val); +static void iu_rx(IU_PORT *port, uint8 val); +static uint8 iu_rx_getc(IU_PORT *port); /* * Registers @@ -113,6 +114,8 @@ IU_TIMER_STATE iu_timer_state; t_bool iu_increment_a = FALSE; t_bool iu_increment_b = FALSE; +double iu_timer_multiplier = IU_TIMER_MULTIPLIER; + BITFIELD sr_bits[] = { BIT(RXRDY), BIT(FFULL), @@ -155,15 +158,17 @@ BITFIELD conf_bits[] = { /* TTI (Console) data structures */ +UNIT tti_unit = { UDATA(&iu_svc_tti, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }; + REG tti_reg[] = { - { HRDATADF(STAT, iu_console.stat, 8, "Status", sr_bits) }, - { HRDATADF(CONF, iu_console.conf, 8, "Config", conf_bits) }, - { BRDATAD(DATA, iu_console.rxbuf, 16, 8, IU_BUF_SIZE, "Data") }, + { HRDATADF(SRA, iu_console.sr, 8, "Status", sr_bits) }, + { HRDATADF(CONF, iu_console.conf, 8, "Config", conf_bits) }, + { BRDATAD(DATA, iu_console.rxbuf, 16, 8, IU_BUF_SIZE, "Data") }, + { DRDATAD(POS, tti_unit.pos, T_ADDR_W, "number of characters input"), PV_LEFT }, + { DRDATAD(TIME, tti_unit.wait, 24, "input polling interval"), PV_LEFT }, { NULL } }; -UNIT tti_unit = { UDATA(&iu_svc_tti, UNIT_IDLE, 0), TMLN_SPD_9600_BPS }; - DEVICE tti_dev = { "TTI", &tti_unit, tti_reg, NULL, 1, 8, 32, 1, 8, 8, @@ -174,17 +179,16 @@ DEVICE tti_dev = { /* TTO (Console) data structures */ +UNIT tto_unit = { UDATA(&iu_svc_tto, UNIT_IDLE|TT_MODE_8B, 0), SERIAL_OUT_WAIT }; + REG tto_reg[] = { - { HRDATADF(STAT, iu_console.stat, 8, "Status", sr_bits) }, - { HRDATADF(ISTAT, iu_state.istat, 8, "Interrupt Status", isr_bits) }, - { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, - { HRDATADF(ACR, iu_state.acr, 8, "Auxiliary Control Register", acr_bits) }, - { HRDATAD(DATA, iu_console.txbuf, 8, "Data") }, + { HRDATADF(SRA, iu_console.sr, 8, "Status Register", sr_bits) }, + { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, + { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, + { HRDATADF(ACR, iu_state.acr, 8, "Aux. Control Register", acr_bits) }, { NULL } }; -UNIT tto_unit = { UDATA(&iu_svc_tto, TT_MODE_8B, 0), SERIAL_OUT_WAIT }; - DEVICE tto_dev = { "TTO", &tto_unit, tto_reg, NULL, 1, 8, 32, 1, 8, 8, @@ -204,41 +208,47 @@ DEVICE tto_dev = { * served by a TMXR multiplexer. */ -TMLN *contty_ldsc = NULL; -TMXR contty_desc = { 1, 0, 0, NULL }; +TMLN contty_ldsc[1] = { 0 }; +TMXR contty_desc = { 1, 0, 0, contty_ldsc }; /* One fixed line */ + +UNIT contty_unit[2] = { + { UDATA(&iu_svc_contty, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0), SERIAL_IN_WAIT }, + { UDATA(&iu_svc_contty_xmt, UNIT_IDLE|UNIT_DIS, 0), SERIAL_OUT_WAIT } +}; REG contty_reg[] = { - { HRDATADF(STAT, iu_contty.stat, 8, "Status", sr_bits) }, + { HRDATADF(SRB, iu_contty.sr, 8, "Status Register", sr_bits) }, { HRDATADF(CONF, iu_contty.conf, 8, "Config", conf_bits) }, { BRDATAD(RXDATA, iu_contty.rxbuf, 16, 8, IU_BUF_SIZE, "RX Data") }, - { HRDATAD(TXDATA, iu_contty.txbuf, 8, "TX Data") }, - { HRDATADF(ISTAT, iu_state.istat, 8, "Interrupt Status", isr_bits) }, + { HRDATADF(ISR, iu_state.isr, 8, "Interrupt Status", isr_bits) }, { HRDATAD(IMR, iu_state.imr, 8, "Interrupt Mask") }, { HRDATADF(ACR, iu_state.acr, 8, "Auxiliary Control Register", acr_bits) }, + { DRDATAD(TIME, contty_unit[1].wait, 24, "output character delay"), PV_LEFT }, { NULL } }; +MTAB contty_mod[] = { + { UNIT_ATT, UNIT_ATT, "summary", NULL, + NULL, &tmxr_show_summ, (void *)&contty_desc, "Display a summary of line state" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL, + NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display current connection" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATISTICS", NULL, + NULL, &tmxr_show_cstat, (void *)&contty_desc, "Display CONTTY statistics" } +}; + CONST char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { - {NULL, "110", NULL, NULL, - "300", NULL, NULL, "1200", - "2400", "4800", NULL, "9600", - "38400", NULL, NULL, NULL}, - {NULL, "110", NULL, NULL, - "300", NULL, "1200", NULL, - NULL, "2400", "4800", "9600", - "19200", NULL, NULL, NULL} + {"50", "110", "134.5", "200", + "300", "600", "1200", "1050", + "2400", "4800", "7200", "9600", + "38400", NULL, NULL, NULL}, + {"75", "110", "134.5", "150", + "300", "600", "1200", "2000", + "2400", "4800", "1800", "9600", + "19200", NULL, NULL, NULL} }; CONST char *parity[3] = {"O", "E", "N"}; -UNIT contty_unit[2] = { - { UDATA(&iu_svc_contty_rcv, UNIT_ATTABLE, 0) }, - { UDATA(&iu_svc_contty_xmt, TT_MODE_8B, 0), SERIAL_OUT_WAIT } -}; - -UNIT *contty_rcv_unit = &contty_unit[0]; -UNIT *contty_xmt_unit = &contty_unit[1]; - DEBTAB contty_deb_tab[] = { {"EXEC", EXECUTE_MSG, "Execute"}, {"XMT", TMXR_DBG_XMT, "Transmitted Data"}, @@ -250,10 +260,9 @@ DEBTAB contty_deb_tab[] = { {0} }; - DEVICE contty_dev = { - "CONTTY", contty_unit, contty_reg, NULL, - 1, 8, 32, 1, 8, 8, + "CONTTY", contty_unit, contty_reg, contty_mod, + 2, 8, 32, 1, 8, 8, &tmxr_ex, &tmxr_dep, &contty_reset, NULL, &contty_attach, &contty_detach, NULL, DEV_DISABLE|DEV_DEBUG|DEV_MUX, @@ -265,33 +274,77 @@ DEVICE contty_dev = { /* IU Timer data structures */ +MTAB iu_timer_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MULT", "MULT={1|2|3|4}", + &iu_timer_set_mult, &iu_timer_show_mult, NULL, "Timer Multiplier" } +}; + REG iu_timer_reg[] = { { HRDATAD(CTR_SET, iu_timer_state.c_set, 16, "Counter Setting") }, { NULL } }; -UNIT iu_timer_unit = { UDATA(&iu_svc_timer, 0, 0) }; +UNIT iu_timer_unit = { UDATA(&iu_svc_timer, UNIT_IDLE, 0) }; DEVICE iu_timer_dev = { - "IUTIMER", &iu_timer_unit, iu_timer_reg, NULL, + "IUTIMER", &iu_timer_unit, iu_timer_reg, iu_timer_mod, 1, 8, 32, 1, 8, 8, NULL, NULL, &iu_timer_reset, NULL, NULL, NULL, NULL, DEV_DEBUG, 0, sys_deb_tab }; +t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, const void *desc) +{ + fprintf(st, "mult=%d", (int) iu_timer_multiplier); + return SCPE_OK; +} + +t_stat iu_timer_set_mult(UNIT *uptr, int32 val, const char *cptr, void *desc) +{ + t_stat r; + t_value v; + v = get_uint(cptr, 10, 8, &r); + if (r != SCPE_OK) { + return r; + } + if (v < 1 || v > 4) { + return SCPE_ARG; + } + iu_timer_multiplier = (uint32) v; + return SCPE_OK; + +} + uint8 brg_reg = 0; /* Selected baud-rate generator register */ uint8 brg_clk = 11; /* Selected baud-rate generator clock */ uint8 parity_sel = 1; /* Selected parity */ uint8 bits_per_char = 7; - t_stat contty_attach(UNIT *uptr, CONST char *cptr) { t_stat r; TMLN *lp; + char line_config[16]; - tmxr_set_modem_control_passthru(&contty_desc); + /* Set initial line speed */ + brg_reg = 0; + brg_clk = BRG_DEFAULT; + parity_sel = IU_PARITY_EVEN; + bits_per_char = 7; + + sprintf(line_config, "%s-%d%s1", + brg_rates[brg_reg][brg_clk], + bits_per_char, + parity[parity_sel]); + + tmxr_set_config_line(&contty_ldsc[0], line_config); + + if ((sim_switches & SWMASK('M'))) { + tmxr_set_modem_control_passthru(&contty_desc); + } + + tmxr_set_line_output_unit(&contty_desc, 0, &contty_unit[1]); r = tmxr_attach(&contty_desc, uptr, cptr); if (r != SCPE_OK) { @@ -333,85 +386,111 @@ void increment_modep_b() } } -void iu_txrdy_a_irq() { - if ((iu_state.imr & IMR_TXRA) && - (iu_console.conf & TX_EN) && - (iu_console.stat & STS_TXR)) { - sim_debug(EXECUTE_MSG, &tto_dev, - "[iu_txrdy_a_irq()] Firing IRQ after transmit of %02x (%c)\n", - (uint8) iu_console.txbuf, PCHAR(iu_console.txbuf)); - SET_INT; - } -} - -void iu_txrdy_b_irq() { - if ((iu_state.imr & IMR_TXRB) && - (iu_contty.conf & TX_EN) && - (iu_contty.stat & STS_TXR)) { - sim_debug(EXECUTE_MSG, &contty_dev, - "[iu_txrdy_b_irq()] Firing IRQ after transmit of %02x (%c)\n", - (uint8) iu_contty.txbuf, PCHAR(iu_contty.txbuf)); - SET_INT; - } -} - t_stat tti_reset(DEVICE *dptr) { memset(&iu_state, 0, sizeof(IU_STATE)); memset(&iu_console, 0, sizeof(IU_PORT)); - /* Input Port logic is inverted - 0 means set */ - iu_state.inprt = ~(IU_DCDA); + /* Input Port is active low */ + iu_state.inprt = ~IU_DCDA; + iu_state.ipcr = IU_DCDA_CH | (0xf & ~IU_DCDA); + + tmxr_set_console_units(&tti_unit, &tto_unit); /* Start the Console TTI polling loop */ - if (!sim_is_active(&tti_unit)) { - sim_activate_after(&tti_unit, tti_unit.wait); - } + sim_activate_after(&tti_unit, tti_unit.wait); return SCPE_OK; } t_stat contty_reset(DEVICE *dtpr) { - char line_config[16]; - - if (contty_ldsc == NULL) { - contty_desc.ldsc = - contty_ldsc = - (TMLN *)calloc(1, sizeof(*contty_ldsc)); - } + sim_set_uname(&contty_unit[0], "CONTTY-RCV"); + sim_set_uname(&contty_unit[1], "CONTTY-XMT"); tmxr_set_port_speed_control(&contty_desc); - tmxr_set_line_unit(&contty_desc, 0, contty_rcv_unit); - tmxr_set_line_output_unit(&contty_desc, 0, contty_xmt_unit); - tmxr_set_console_units(&tti_unit, &tto_unit); - memset(&iu_state, 0, sizeof(IU_STATE)); memset(&iu_contty, 0, sizeof(IU_PORT)); - /* DCD is off (inverted logic, 1 means off) */ - iu_state.inprt |= IU_DCDB; - - brg_reg = 0; - brg_clk = BRG_DEFAULT; - parity_sel = IU_PARITY_EVEN; - bits_per_char = 7; - - sprintf(line_config, "%s-%d%s1", - brg_rates[brg_reg][brg_clk], - bits_per_char, - parity[parity_sel]); - - tmxr_set_config_line(&contty_ldsc[0], line_config); - - /* Start the CONTTY polling loop */ - if (!sim_is_active(contty_rcv_unit)) { - sim_activate_after(contty_rcv_unit, contty_rcv_unit->wait); + if (contty_unit[0].flags & UNIT_ATT) { + sim_activate_after(&contty_unit[0], contty_unit[0].wait); + } else { + sim_cancel(&contty_unit[0]); } return SCPE_OK; } +static void iu_rx(IU_PORT *port, uint8 val) +{ + if (port->conf & RX_EN) { + if (!(port->sr & STS_FFL)) { + /* If not full, we're reading into the FIFO */ + port->rxbuf[port->w_p] = val; + port->w_p = (port->w_p + 1) % IU_BUF_SIZE; + if (port->w_p == port->r_p) { + port->sr |= STS_FFL; + } + } else { + /* FIFO is full, and now we're going to have to hold a + * character in the shift register until space is + * available */ + + /* If the register already had data, it's going to be + * overwritten, so we have to set the overflow flag */ + if (port->rxr_full) { + port->sr |= STS_OER; + } + + /* Save the character */ + port->rxr = val; + port->rxr_full = TRUE; + } + + port->sr |= STS_RXR; + iu_state.isr |= (port == &iu_console) ? ISTS_RXRA : ISTS_RXRB; + } + + UPDATE_IRQ; +} + +static uint8 iu_rx_getc(IU_PORT *port) +{ + uint8 val = 0; + + if (port->conf & RX_EN) { + val = port->rxbuf[port->r_p]; + port->r_p = (port->r_p + 1) % IU_BUF_SIZE; + /* No longer full */ + port->sr &= ~STS_FFL; + if (port->r_p == port->w_p) { + /* Empty FIFO, nothing left to read */ + port->sr &= ~STS_RXR; + iu_state.isr &= (port == &iu_console) ? ~ISTS_RXRA : ~ISTS_RXRB; + } + + if (port->rxr_full) { + /* Need to shift data from the Receive Shift Register into + the space that we just freed up */ + port->rxbuf[port->w_p] = port->rxr; + port->w_p = (port->w_p + 1) % IU_BUF_SIZE; + /* FIFO can logically never be full here, since we just + * freed up space above. */ + port->rxr_full = FALSE; + } + + if (!(port->mode[0] & 0x20)) { + /* Receiver is in "Char Error" mode, so reset SR error + bits on read */ + port->sr &= ~(STS_RXB|STS_FER|STS_PER); + } + } + + UPDATE_IRQ; + + return val; +} + t_stat iu_timer_reset(DEVICE *dptr) { memset(&iu_timer_state, 0, sizeof(IU_TIMER_STATE)); @@ -423,38 +502,15 @@ t_stat iu_timer_reset(DEVICE *dptr) t_stat iu_svc_tti(UNIT *uptr) { - int32 temp; + int32 c; tmxr_clock_coschedule(uptr, tmxr_poll); - /* TODO: - - - If there has been a change on IP0-IP3, set the corresponding - bits in IPCR, if configured to do so. We'll need to figure out - how these are wired (DCD pin, etc?) - - - Update the Output Port pins (which are logically inverted) - based on the contents of the OPR, OPCR, MR, and CR registers. - */ - - if ((temp = sim_poll_kbd()) < SCPE_KFLAG) { - return temp; + if ((c = sim_poll_kbd()) < SCPE_KFLAG) { + return c; } - if (iu_console.conf & RX_EN) { - if ((iu_console.stat & STS_FFL) == 0) { - iu_console.rxbuf[iu_console.w_p] = (temp & 0xff); - iu_console.w_p = (iu_console.w_p + 1) % IU_BUF_SIZE; - if (iu_console.w_p == iu_contty.w_p) { - iu_console.stat |= STS_FFL; - } - } - iu_console.stat |= STS_RXR; - iu_state.istat |= ISTS_RAI; - if (iu_state.imr & IMR_RXRA) { - SET_INT; - } - } + iu_rx(&iu_console, (uint8) c); return SCPE_OK; } @@ -462,60 +518,108 @@ t_stat iu_svc_tti(UNIT *uptr) t_stat iu_svc_tto(UNIT *uptr) { - /* If there's more DMA to do, do it */ - if (iu_console.dma && ((dma_state.mask >> DMA_IUA_CHAN) & 0x1) == 0) { + t_stat result; + dma_channel *chan = &dma_state.channels[DMA_IUA_CHAN]; + + /* Check for data in the transmitter shift register that's ready + * to go out to the TX line */ + if (iu_console.tx_state & T_XMIT) { + if (LOOPBACK(&iu_console)) { + /* Handle loopback mode if set */ + sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE is in loopback.\n"); + iu_console.tx_state &= ~T_XMIT; + + iu_rx(&iu_console, iu_console.txr); + + if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { + iu_console.sr |= STS_TXE; + } + } else { + /* Direct mode, no loopback */ + result = sim_putchar_s(iu_console.txr); + if (result == SCPE_STALL) { + sim_debug(EXECUTE_MSG, &tto_dev, "iu_svc_tto: CONSOLE PUTC STALL\n"); + sim_activate_after(uptr, 1000); + return SCPE_OK; + } else { + iu_console.tx_state &= ~T_XMIT; + if (TX_ENABLED(iu_console) && !(iu_console.tx_state & T_HOLD)) { + iu_console.sr |= STS_TXE; + } + } + } + } + + /* Check for data in the holding register that's ready to go + * out to the transmitter shift register */ + if (iu_console.tx_state & T_HOLD) { + iu_console.tx_state &= ~T_HOLD; + iu_console.tx_state |= T_XMIT; + iu_console.txr = iu_console.thr; + /* If the transmitter is currently enabled, we need to update + * the TxRDY and TxEMT flags */ + if (TX_ENABLED(iu_console)) { + iu_console.sr &= ~STS_TXE; + iu_console.sr |= STS_TXR; + iu_state.isr |= ISTS_TXRA; + /* DRQ is always tied to TxRDY */ + iu_console.drq = TRUE; + } + + sim_activate_after_abs(uptr, uptr->wait); + } + + UPDATE_IRQ; + + /* If we're done transmitting and there's more DMA to do, + * do it. */ + if (!iu_console.tx_state && + chan->wcount_c >= 0 && + ((dma_state.mask >> DMA_IUA_CHAN) & 0x1) == 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_tto: Triggering next DMA\n"); iu_dma_console(DMA_IUA_CHAN, IUBASE+IUA_DATA_REG); - } else { - /* The buffer is now empty, we've transmitted, so set TXR */ - iu_console.stat |= STS_TXR; - iu_state.istat |= 1; - iu_txrdy_a_irq(); } return SCPE_OK; } -t_stat iu_svc_contty_rcv(UNIT *uptr) +t_stat iu_svc_contty(UNIT *uptr) { - int32 temp, ln; + int32 c; if ((uptr->flags & UNIT_ATT) == 0) { return SCPE_OK; } - /* Check for connect */ - if ((ln = tmxr_poll_conn(&contty_desc)) >= 0) { - contty_ldsc[ln].rcve = 1; - iu_state.inprt &= ~(IU_DCDB); - iu_state.ipcr |= IU_DCDB; - SET_INT; + /* Check for connect of our single line */ + if (tmxr_poll_conn(&contty_desc) == 0) { + contty_ldsc[0].rcve = 1; + + iu_state.inprt &= ~IU_DCDB; + iu_state.ipcr &= ~IU_DCDB; + iu_state.ipcr |= IU_DCDB_CH; + + UPDATE_IRQ; } + tmxr_poll_tx(&contty_desc); + tmxr_poll_rx(&contty_desc); + /* Check for disconnect */ - if (!contty_ldsc[0].conn && (iu_state.inprt & IU_DCDB) == 0) { + if (!contty_ldsc[0].conn && contty_ldsc[0].rcve) { contty_ldsc[0].rcve = 0; iu_state.inprt |= IU_DCDB; - iu_state.ipcr |= IU_DCDB; - SET_INT; - } else if (iu_contty.conf & RX_EN) { - tmxr_poll_rx(&contty_desc); + iu_state.ipcr |= (IU_DCDB_CH|IU_DCDB); - if (contty_ldsc[0].conn) { - temp = tmxr_getc_ln(&contty_ldsc[0]); - if (temp && !(temp & SCPE_BREAK)) { - if ((iu_contty.stat & STS_FFL) == 0) { - iu_contty.rxbuf[iu_contty.w_p] = (temp & 0xff); - iu_contty.w_p = (iu_contty.w_p + 1) % IU_BUF_SIZE; - if (iu_contty.w_p == iu_contty.r_p) { - iu_contty.stat |= STS_FFL; - } - } - iu_contty.stat |= STS_RXR; - iu_state.istat |= ISTS_RBI; - if (iu_state.imr & IMR_RXRB) { - SET_INT; - } - } + UPDATE_IRQ; + } + + /* Check for RX */ + if ((iu_contty.conf & RX_EN) && contty_ldsc[0].conn) { + c = tmxr_getc_ln(&contty_ldsc[0]); + if (c && !(c & SCPE_BREAK)) { + iu_rx(&iu_contty, (uint8) c); } } @@ -527,17 +631,71 @@ t_stat iu_svc_contty_rcv(UNIT *uptr) t_stat iu_svc_contty_xmt(UNIT *uptr) { dma_channel *chan = &dma_state.channels[DMA_IUB_CHAN]; + TMLN *lp = &contty_ldsc[0]; + t_stat result; - tmxr_poll_tx(&contty_desc); + /* Check for data in the transmitter shift register that's ready + * to go out to the TX line */ + if (iu_contty.tx_state & T_XMIT) { + if (LOOPBACK(&iu_contty)) { + /* Handle loopback mode if set */ + sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY is in loopback.\n"); + iu_contty.tx_state &= ~T_XMIT; - /* If there's more DMA to do, do it */ - if (chan->wcount_c >= 0) { + iu_rx(&iu_contty, iu_contty.txr); + + if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { + iu_contty.sr |= STS_TXE; + } + } else { + /* Direct mode, no loopback */ + result = tmxr_putc_ln(lp, iu_contty.txr); + if (result == SCPE_STALL) { + sim_debug(EXECUTE_MSG, &contty_dev, "iu_svc_contty: CONTTY PUTC STALL: %d\n", result); + sim_activate_after(uptr, 1000); + return SCPE_OK; + } else { + tmxr_poll_tx(&contty_desc); + iu_contty.tx_state &= ~T_XMIT; + if (TX_ENABLED(iu_contty) && !(iu_contty.tx_state & T_HOLD)) { + iu_contty.sr |= STS_TXE; + } + } + } + } + + /* Check for data in the holding register that's ready to go + * out to the transmitter shift register */ + if (iu_contty.tx_state & T_HOLD) { + sim_debug(EXECUTE_MSG, &contty_dev, + "THRB->TXRB: 0x%02x (%c)\n", + iu_contty.thr, PCHAR(iu_contty.thr)); + iu_contty.tx_state &= ~T_HOLD; + iu_contty.tx_state |= T_XMIT; + iu_contty.txr = iu_contty.thr; + /* If the transmitter is currently enabled, we need to update + * the TxRDY and TxEMT flags */ + if (TX_ENABLED(iu_contty)) { + iu_contty.sr &= ~STS_TXE; + iu_contty.sr |= STS_TXR; + iu_state.isr |= ISTS_TXRB; + /* DRQ is always tied to TxRDY */ + iu_contty.drq = TRUE; + } + + sim_activate_after_abs(uptr, uptr->wait); + } + + UPDATE_IRQ; + + /* If we're done transmitting and there's more DMA to do, + * do it. */ + if (!iu_contty.tx_state && + chan->wcount_c >= 0 && + ((dma_state.mask >> DMA_IUB_CHAN) & 0x1) == 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_contty_xmt: Triggering next DMA\n"); iu_dma_contty(DMA_IUB_CHAN, IUBASE+IUB_DATA_REG); - } else { - /* The buffer is now empty, we've transmitted, so set TXR */ - iu_contty.stat |= STS_TXR; - iu_state.istat |= 0x10; - iu_txrdy_b_irq(); } return SCPE_OK; @@ -545,12 +703,13 @@ t_stat iu_svc_contty_xmt(UNIT *uptr) t_stat iu_svc_timer(UNIT *uptr) { - iu_state.istat |= ISTS_CRI; + iu_state.isr |= ISTS_CRI; - if (iu_state.imr & IMR_CTR) { - SET_INT; - } + sim_debug(EXECUTE_MSG, &iu_timer_dev, + "[iu_svc_timer] IMR=%02x ISR=%02x => %02x\n", + iu_state.imr, iu_state.isr, (iu_state.imr & iu_state.isr)); + UPDATE_IRQ; return SCPE_OK; } @@ -590,30 +749,18 @@ uint32 iu_read(uint32 pa, size_t size) iu_increment_a = TRUE; break; case SRA: - data = iu_console.stat; + data = iu_console.sr; break; case RHRA: - if (iu_console.conf & RX_EN) { - data = iu_console.rxbuf[iu_console.r_p]; - iu_console.r_p = (iu_console.r_p + 1) % IU_BUF_SIZE; - /* If the FIFO is not empty, we must cause another interrupt - * to continue reading */ - if (iu_console.r_p == iu_console.w_p) { - iu_console.stat &= ~(STS_RXR|STS_FFL); - iu_state.istat &= ~ISTS_RAI; - } else if (iu_state.imr & IMR_RXRA) { - SET_INT; - } - } + data = iu_rx_getc(&iu_console); break; case IPCR: data = iu_state.ipcr; - /* Reading the port resets it */ - iu_state.ipcr = 0; - CLR_INT; + /* Reading the port resets the top 4 bits */ + iu_state.ipcr &= 0xf; break; case ISR: - data = iu_state.istat; + data = iu_state.isr; break; case CTU: data = (iu_timer_state.c_set >> 8) & 0xff; @@ -627,37 +774,29 @@ uint32 iu_read(uint32 pa, size_t size) iu_increment_b = TRUE; break; case SRB: - data = iu_contty.stat; + data = iu_contty.sr; break; case RHRB: - if (iu_contty.conf & RX_EN) { - data = iu_contty.rxbuf[iu_contty.r_p]; - iu_contty.r_p = (iu_contty.r_p + 1) % IU_BUF_SIZE; - /* If the FIFO is not empty, we must cause another interrupt - * to continue reading */ - if (iu_contty.r_p == iu_contty.w_p) { - iu_contty.stat &= ~(STS_RXR|STS_FFL); - iu_state.istat &= ~ISTS_RBI; - } else if (iu_state.imr & IMR_RXRB) { - SET_INT; - } - } + data = iu_rx_getc(&iu_contty); break; case INPRT: data = iu_state.inprt; break; case START_CTR: data = 0; - iu_state.istat &= ~ISTS_CRI; - sim_activate_abs(&iu_timer_unit, (int32)(iu_timer_state.c_set * IU_TIMER_RATE)); + iu_state.isr &= ~ISTS_CRI; + sim_debug(EXECUTE_MSG, &iu_timer_dev, + "ACR=%02x : Activating IU Timer in %d ticks / %d microseconds\n", + iu_state.acr, iu_timer_state.c_set, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); + sim_activate_after(&iu_timer_unit, (int32)(iu_timer_state.c_set * iu_timer_multiplier)); break; case STOP_CTR: data = 0; - iu_state.istat &= ~ISTS_CRI; - CLR_INT; + iu_state.isr &= ~ISTS_CRI; + UPDATE_IRQ; sim_cancel(&iu_timer_unit); break; - case 17: /* Clear DMAC interrupt */ + case 17: /* Clear DMA interrupt */ data = 0; CLR_DMA_INT; break; @@ -684,25 +823,13 @@ void iu_write(uint32 pa, uint32 val, size_t size) iu_increment_a = TRUE; break; case CSRA: - if (brg_rates[brg_reg][brg_clk] != NULL) { - sprintf(line_config, "%s-%d%s1", - brg_rates[brg_reg][brg_clk], - bits_per_char, - parity[parity_sel]); - - sim_debug(EXECUTE_MSG, &contty_dev, - "Setting CONTTY line to %s\n", - line_config); - - tmxr_set_config_line(&contty_ldsc[0], line_config); - } + /* Nothing supported */ break; case CRA: /* Command A */ - iu_w_cmd(PORT_A, bval); + iu_w_cmd(&iu_console, bval); break; case THRA: /* TX/RX Buf A */ - iu_tx(PORT_A, bval); - sim_activate_abs(&tto_unit, tto_unit.wait); + iu_tx(&iu_console, bval); break; case ACR: /* Auxiliary Control Register */ iu_state.acr = bval; @@ -710,13 +837,7 @@ void iu_write(uint32 pa, uint32 val, size_t size) break; case IMR: iu_state.imr = bval; - sim_debug(EXECUTE_MSG, &tto_dev, - "[%08x] Write IMR = %x\n", - R[NUM_PC], bval); - CLR_INT; - /* Possibly cause an interrupt */ - iu_txrdy_a_irq(); - iu_txrdy_b_irq(); + UPDATE_IRQ; break; case CTUR: /* Counter/Timer Upper Preset Value */ /* Clear out high byte */ @@ -733,6 +854,7 @@ void iu_write(uint32 pa, uint32 val, size_t size) case MR12B: modep = iu_contty.modep; iu_contty.mode[modep] = bval; + sim_debug(EXECUTE_MSG, &tto_dev, "MR12B: Page %d Mode = %02x\n", modep, bval); iu_increment_b = TRUE; if (modep == 0) { if ((bval >> 4) & 1) { @@ -750,8 +872,8 @@ void iu_write(uint32 pa, uint32 val, size_t size) bits_per_char = (bval & 3) + 5; } break; - case CRB: /* Command B */ - iu_w_cmd(PORT_B, bval); + case CRB: + iu_w_cmd(&iu_contty, bval); break; case CSRB: brg_clk = (bval >> 4) & 0xf; @@ -770,183 +892,173 @@ void iu_write(uint32 pa, uint32 val, size_t size) } break; - case THRB: /* TX/RX Buf B */ - iu_tx(PORT_B, bval); - sim_activate_abs(contty_xmt_unit, contty_ldsc[0].txdeltausecs); + case THRB: + iu_tx(&iu_contty, bval); break; case OPCR: iu_state.opcr = bval; break; case SOPR: +#if defined (REV2) /* Bit 2 of the IU output register is used as a soft power * switch. When set, the machine will power down * immediately. */ if (bval & IU_KILLPWR) { stop_reason = STOP_POWER; } +#endif break; case ROPR: break; + case 17: /* Clear DMA interrupt */ + sim_debug(EXECUTE_MSG, &tto_dev, + "[WRITE] Clear DMA interrupt in UART\n"); + CLR_DMA_INT; + break; default: break; } } -t_stat iu_tx(uint8 portno, uint8 val) +/* + * Transmit a single character + */ +t_stat iu_tx(IU_PORT *port, uint8 val) { - IU_PORT *p = (portno == PORT_A) ? &iu_console : &iu_contty; - uint8 ists = (portno == PORT_A) ? ISTS_RAI : ISTS_RBI; - uint8 imr_mask = (portno == PORT_A) ? IMR_RXRA : IMR_RXRB; int32 c; - t_stat status = SCPE_OK; + uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; + UNIT *uptr = (port == &iu_console) ? &tto_unit : &contty_unit[1]; - if (p->conf & TX_EN) { - if ((p->mode[1] & 0xc0) == 0x80) { /* Loopback mode */ - p->txbuf = val; + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_tx PORT=%d CHAR=%02x (%c)\n", + PORTNO(port), val, PCHAR(val)); - /* This is also a Receive */ - if ((p->stat & STS_FFL) == 0) { - p->rxbuf[p->w_p] = val; - p->w_p = (p->w_p + 1) % IU_BUF_SIZE; - if (p->w_p == p->r_p) { - p->stat |= STS_FFL; - } - } - - p->stat |= STS_RXR; - if (iu_state.imr & imr_mask) { - iu_state.istat |= ists; - SET_INT; - } - - return SCPE_OK; - } else { /* Direct mode */ - c = sim_tt_outcvt(val, TTUF_MODE_8B); - - if (c >= 0) { - p->txbuf = c; - p->stat &= ~(STS_TXR|STS_TXE); - iu_state.istat &= ~(1 << (portno*4)); - - if (portno == PORT_A) { - /* Write the character to the SIMH console */ - sim_debug(EXECUTE_MSG, &tto_dev, - "[iu_tx] CONSOLE transmit %02x (%c)\n", - (uint8) c, PCHAR(c)); - status = sim_putchar_s(c); - } else { - sim_debug(EXECUTE_MSG, &contty_dev, - "[iu_tx] CONTTY transmit %02x (%c)\n", - (uint8) c, PCHAR(c)); - status = tmxr_putc_ln(&contty_ldsc[0], c); - } - } - } + if (!(port->conf & TX_EN) || !(port->sr & STS_TXR)) { + sim_debug(EXECUTE_MSG, &tto_dev, + ">>> IGNORING TRANSMIT, NOT ENABLED OR NOT READY!!!\n"); + return SCPE_INCOMP; } - return status; + c = sim_tt_outcvt(val, TTUF_MODE_8B); + + if (c >= 0) { + port->tx_state |= T_HOLD; + port->sr &= ~(STS_TXR|STS_TXE); + port->drq = FALSE; + iu_state.isr &= ~(tx_ists); + port->thr = c; + sim_activate_after(uptr, uptr->wait); + } + + return SCPE_OK; } -static SIM_INLINE void iu_w_cmd(uint8 portno, uint8 cmd) +static void iu_w_cmd(IU_PORT *port, uint8 cmd) { - - IU_PORT *p; - - if (portno == 0) { - p = &iu_console; - } else { - p = &iu_contty; - } + uint8 tx_ists = (port == &iu_console) ? ISTS_TXRA : ISTS_TXRB; + uint8 dbk_ists = (port == &iu_console) ? ISTS_DBA : ISTS_DBB; /* Enable or disable transmitter */ /* Disable always wins, if both are set */ if (cmd & CMD_DTX) { - p->conf &= ~TX_EN; - p->stat &= ~STS_TXR; - p->stat &= ~STS_TXE; - p->drq = FALSE; - p->dma = FALSE; + port->conf &= ~TX_EN; + port->sr &= ~(STS_TXR|STS_TXE); + port->drq = FALSE; + iu_state.isr &= ~tx_ists; + UPDATE_IRQ; + sim_debug(EXECUTE_MSG, &tto_dev, + "DISABLE TX, PORT %d\n", PORTNO(port)); } else if (cmd & CMD_ETX) { - p->conf |= TX_EN; - /* TXE and TXR are always set by an ENABLE */ - p->stat |= STS_TXR; - p->stat |= STS_TXE; - p->drq = TRUE; - iu_state.istat |= 1 << (portno*4); - if (portno == 0) { - iu_txrdy_a_irq(); - } else { - iu_txrdy_b_irq(); + if (!(port->conf & TX_EN)) { + /* TXE and TXR are always set by an ENABLE if prior state + was DISABLED */ + port->sr |= (STS_TXR|STS_TXE); + port->drq = TRUE; } + port->conf |= TX_EN; + iu_state.isr |= tx_ists; + UPDATE_IRQ; + sim_debug(EXECUTE_MSG, &tto_dev, + "ENABLE TX, PORT %d\n", PORTNO(port)); } /* Enable or disable receiver. */ /* Disable always wins, if both are set */ if (cmd & CMD_DRX) { - p->conf &= ~RX_EN; - p->stat &= ~STS_RXR; + port->conf &= ~RX_EN; + port->sr &= ~STS_RXR; } else if (cmd & CMD_ERX) { - p->conf |= RX_EN; + port->conf |= RX_EN; } /* Command register bits 6-4 have special meaning */ switch ((cmd >> CMD_MISC_SHIFT) & CMD_MISC_MASK) { - case 1: + case CR_RST_MR: /* Causes the Channel A MR pointer to point to MR1. */ - p->modep = 0; + port->modep = 0; break; - case 2: + case CR_RST_RX: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET RX\n", PORTNO(port)); /* Reset receiver. Resets the Channel's receiver as if a hardware reset had been applied. The receiver is disabled and the FIFO is flushed. */ - p->stat &= ~STS_RXR; - p->conf &= ~RX_EN; - p->w_p = 0; - p->r_p = 0; + port->sr &= ~STS_RXR; + port->conf &= ~RX_EN; + port->w_p = 0; + port->r_p = 0; break; - case 3: + case CR_RST_TX: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET TX\n", PORTNO(port)); /* Reset transmitter. Resets the Channel's transmitter as if a hardware reset had been applied. */ - p->stat &= ~STS_TXR; - p->stat &= ~STS_TXE; - p->conf &= ~TX_EN; - p->w_p = 0; - p->r_p = 0; + port->sr &= ~STS_TXR; + port->sr &= ~STS_TXE; + port->drq = FALSE; /* drq is tied to TXR */ + port->conf &= ~TX_EN; break; - case 4: + case CR_RST_ERR: /* Reset error status. Clears the Channel's Received Break, - Parity Error, and Overrun Error bits in the status register - (SRA[7:4]). Used in character mode to clear OE status - (although RB, PE and FE bits will also be cleared) and in - block mode to clear all error status after a block of data - has been received. */ - p->stat &= ~(STS_FER|STS_PER|STS_OER); + Parity Error, Framing Error, and Overrun Error bits in the + status register (SRn[7:4]). */ + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET ERROR\n", PORTNO(port)); + port->sr &= ~(STS_RXB|STS_FER|STS_PER|STS_OER); break; - case 5: + case CR_RST_BRK: /* Reset Channel's break change interrupt. Causes the Channel A break detect change bit in the interrupt status register (ISR[2] for Chan. A, ISR[6] for Chan. B) to be cleared to zero. */ - iu_state.istat &= ~(1 << (2 + portno*4)); + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: RESET BREAK IRQ\n", PORTNO(port)); + iu_state.isr &= ~dbk_ists; break; - case 6: - /* Start break. Forces the TxDA output LOW (spacing). If the - transmitter is empty the start of the break condition will - be delayed up to two bit times. If the transmitter is - active the break begins when transmission of the character - is completed. If a character is in the THR, the start of - the break will be delayed until that character, or any - other loaded subsequently are transmitted. The transmitter - must be enabled for this command to be accepted. */ - /* Not Implemented */ + case CR_START_BRK: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: START BREAK. loopback=%d\n", + PORTNO(port), LOOPBACK(port)); + if (LOOPBACK(port)) { + /* Set "Received Break" and "Parity Error" bits in + SRA/SRB */ + port->sr |= (STS_RXB|STS_PER); + /* Set "Delta Break" bit A or B in ISR */ + iu_state.isr |= dbk_ists; + } break; - case 7: - /* Stop break. The TxDA line will go HIGH (marking) within two - bit times. TxDA will remain HIGH for one bit time before - the next character, if any, is transmitted. */ - /* Not Implemented */ + case CR_STOP_BRK: + sim_debug(EXECUTE_MSG, &tto_dev, + "PORT %d Command: STOP BREAK. loopback=%d\n", + PORTNO(port), LOOPBACK(port)); + if (LOOPBACK(port)) { + /* Set "Delta Break" bit A or B in ISR */ + iu_state.isr |= dbk_ists; + } break; } + + UPDATE_IRQ; } /* @@ -958,45 +1070,39 @@ void iu_dma_console(uint8 channel, uint32 service_address) uint32 addr; t_stat status = SCPE_OK; dma_channel *chan = &dma_state.channels[channel]; - UNIT *uptr = &tto_unit; IU_PORT *port = &iu_console; - /* Immediate acknowledge of DMA */ - port->drq = FALSE; - - if (!port->dma) { - /* Set DMA transfer type */ - port->dma = 1u << ((dma_state.mode >> 2) & 0xf); + /* If we're doing DMA and we're done, end it */ + if (iu_console.dma && chan->wcount_c < 0) { + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_svc_tto: DMA Complete.\n"); + iu_console.dma = FALSE; + dma_state.mask |= (1 << DMA_IUA_CHAN); + dma_state.status |= (1 << DMA_IUA_CHAN); + SET_DMA_INT; + return; } - if (port->dma == DMA_READ) { - addr = dma_address(channel, chan->ptr, TRUE); - chan->addr_c = chan->addr + chan->ptr + 1; - data = pread_b(addr); - status = iu_tx(channel - 2, data); + /* Mark that IUA is in DMA */ + port->dma = TRUE; + + switch (DMA_XFER(DMA_IUA_CHAN)) { + case DMA_XFER_READ: + addr = dma_address(channel, chan->ptr); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + status = iu_tx(port, data); if (status == SCPE_OK) { chan->ptr++; chan->wcount_c--; } - - sim_activate_abs(uptr, uptr->wait); - - if (chan->wcount_c >= 0) { - /* Return early so we don't finish DMA */ - return; - } + break; + default: + sim_debug(EXECUTE_MSG, &tto_dev, + "iu_dma_console: Error, transfer type %d not supported\n", + DMA_XFER(DMA_IUA_CHAN)); + break; } - - /* Cancel any pending interrupts, we're done. */ - /* TODO: I THINK THIS BREAKS EVERYTHING!!!! */ - /* sim_cancel(uptr); */ - - /* Done with DMA */ - port->dma = DMA_NONE; - - dma_state.mask |= (1 << channel); - dma_state.status |= (1 << channel); - SET_DMA_INT; } void iu_dma_contty(uint8 channel, uint32 service_address) @@ -1005,41 +1111,37 @@ void iu_dma_contty(uint8 channel, uint32 service_address) uint32 addr; t_stat status = SCPE_OK; dma_channel *chan = &dma_state.channels[channel]; - UNIT *uptr = contty_xmt_unit; IU_PORT *port = &iu_contty; - uint32 wait = 0x7fffffff; - /* Immediate acknowledge of DMA */ - port->drq = FALSE; - - if (!port->dma) { - /* Set DMA transfer type */ - port->dma = 1u << ((dma_state.mode >> 2) & 0xf); + /* If we're doing DMA and we're done, end it */ + if (iu_contty.dma && chan->wcount_c < 0) { + sim_debug(EXECUTE_MSG, &contty_dev, + "iu_svc_contty_xmt: DMA Complete.\n"); + iu_contty.dma = FALSE; + dma_state.mask |= (1 << DMA_IUB_CHAN); + dma_state.status |= (1 << DMA_IUB_CHAN); + SET_DMA_INT; + return; } - if (port->dma == DMA_READ) { - addr = dma_address(channel, chan->ptr, TRUE); - chan->addr_c = chan->addr + chan->ptr + 1; - data = pread_b(addr); - status = iu_tx(channel - 2, data); + /* Mark that IUB is in DMA */ + port->dma = TRUE; + + switch (DMA_XFER(DMA_IUB_CHAN)) { + case DMA_XFER_READ: + addr = dma_address(channel, chan->ptr); + chan->addr_c = addr; + data = pread_b(addr, BUS_PER); + status = iu_tx(port, data); if (status == SCPE_OK) { - wait = MIN(wait, contty_ldsc[0].txdeltausecs); chan->ptr++; chan->wcount_c--; } - - tmxr_activate_after(uptr, wait); - - if (chan->wcount_c >= 0) { - /* Return early so we don't finish DMA */ - return; - } + break; + default: + sim_debug(EXECUTE_MSG, &contty_dev, + "iu_dma_contty: Error, transfer type %d not supported\n", + DMA_XFER(DMA_IUB_CHAN)); + break; } - - /* Done with DMA */ - port->dma = DMA_NONE; - - dma_state.mask |= (1 << channel); - dma_state.status |= (1 << channel); - SET_DMA_INT; } diff --git a/3B2/3b2_iu.h b/3B2/3b2_iu.h index 2a718ed0..2fb61887 100644 --- a/3B2/3b2_iu.h +++ b/3B2/3b2_iu.h @@ -1,6 +1,6 @@ -/* 3b2_iu.h: SCN2681A Dual UART Header +/* 3b2_iu.h: SCN2681A Dual UART - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -56,18 +56,22 @@ #define STS_FER 0x40 /* Framing error */ #define STS_RXB 0x80 /* Received break */ -#define ISTS_TAI 0x01 /* Transmitter ready A */ -#define ISTS_RAI 0x02 /* Receiver ready A */ -#define ISTS_CBA 0x04 /* Change in break A */ +#define ISTS_TXRA 0x01 /* Transmitter ready A */ +#define ISTS_RXRA 0x02 /* Receiver ready A */ +#define ISTS_DBA 0x04 /* Delta Break A */ #define ISTS_CRI 0x08 /* Counter ready */ -#define ISTS_TBI 0x10 /* Transmitter ready B */ -#define ISTS_RBI 0x20 /* Receiver ready B */ -#define ISTS_CBB 0x40 /* Change in break B */ +#define ISTS_TXRB 0x10 /* Transmitter ready B */ +#define ISTS_RXRB 0x20 /* Receiver ready B */ +#define ISTS_DBB 0x40 /* Delta Break B */ #define ISTS_IPC 0x80 /* Interrupt port change */ #define MODE_V_CHM 6 /* Channel mode */ #define MODE_M_CHM 0x3 +/* Transmitter State bits */ +#define T_HOLD 1 +#define T_XMIT 2 + /* Used by the DMAC */ #define IUA_DATA_REG 3 #define IUB_DATA_REG 11 @@ -81,7 +85,7 @@ #define CTL 7 #define SRB 9 #define RHRB 11 -#define INPRT 13 /* Input port data */ +#define INPRT 13 #define START_CTR 14 #define STOP_CTR 15 @@ -108,16 +112,14 @@ #define TX_EN 1 #define RX_EN 2 -#define UM_CTR_EXT 0 -#define UM_CTR_TXCA 1 -#define UM_CTR_TXCB 2 -#define UM_CTR_DIV16 3 -#define UM_TMR_EXT 4 -#define UM_TMR_EXT16 5 -#define UM_TMR_XTL 6 -#define UM_TMR_XTL16 7 -#define UM_MASK 0x70 -#define UM_SHIFT 4 +/* Control Register commands */ +#define CR_RST_MR 1 +#define CR_RST_RX 2 +#define CR_RST_TX 3 +#define CR_RST_ERR 4 +#define CR_RST_BRK 5 +#define CR_START_BRK 6 +#define CR_STOP_BRK 7 /* IMR bits */ #define IMR_TXRA 0x01 @@ -139,40 +141,57 @@ #define IU_BUF_SIZE 3 -#define IU_DCDA 0x01 +/* Data Carrier Detect inputs and input change bits */ +#if defined(REV3) +#define IU_DCDB_CH 0x80 +#define IU_DCDA_CH 0x40 +#define IU_DCDB 0x08 +#define IU_DCDA 0x04 +#else +#define IU_DCDB_CH 0x20 +#define IU_DCDA_CH 0x10 #define IU_DCDB 0x02 -#define IU_DTRA 0x01 -#define IU_DTRB 0x02 - -#define DMA_NONE 0 -#define DMA_VERIFY 1 -#define DMA_WRITE 2 -#define DMA_READ 4 +#define IU_DCDA 0x01 +#endif /* Default baud rate generator (9600 baud) */ #define BRG_DEFAULT 11 -#define IU_TIMER_RATE 2.114 /* microseconds per step */ +/* The 2681 DUART includes a 16-bit timer/counter that can be used to + * trigger an interrupt after a certain amount of time has passed. + * + * The 2681 uses a crystal with a frequency of 3.686400 MHz, and the + * timer/counter uses this frequency divided by 16, giving a final + * timer/counter frequency of 230,400 Hz. There are therefore 4.34 + * microseconds of wall time per tick of the timer. + * + * The multiplier defined below is a default that can be adjusted to + * make IU timing faster, but less accurate, if desired */ +#define IU_TIMER_MULTIPLIER 4 typedef struct iu_port { - uint8 stat; /* Port Status */ uint8 cmd; /* Command */ uint8 mode[2]; /* Two mode buffers */ uint8 modep; /* Point to mode[0] or mode[1] */ uint8 conf; /* Configuration bits */ - uint8 txbuf; /* Transmit Holding Register */ + uint8 sr; /* Status Register */ + uint8 thr; /* Transmit Holding Register */ + uint8 txr; /* Transmit Shift Register */ + uint8 rxr; /* Receive Shift Register */ uint8 rxbuf[IU_BUF_SIZE]; /* Receive Holding Register (3 bytes) */ - uint8 w_p; /* Buffer Write Pointer */ - uint8 r_p; /* Buffer Read Pointer */ - uint8 dma; /* Currently active DMA mode */ + uint8 w_p; /* Receive Buffer Write Pointer */ + uint8 r_p; /* Receive Buffer Read Pointer */ + uint8 tx_state; /* Transmitting state flags (HOLD, XMIT) */ + t_bool dma; /* DMA currently active */ t_bool drq; /* DMA request enabled */ + t_bool rxr_full; /* Receive Shift Register is full */ } IU_PORT; typedef struct iu_state { - uint8 istat; /* Interrupt Status */ + uint8 isr; /* Interrupt Status Register */ uint8 imr; /* Interrupt Mask Register */ - uint8 acr; + uint8 acr; /* Aux. Control Register */ uint8 opcr; /* Output Port Configuration */ uint8 inprt; /* Input Port Data */ uint8 ipcr; /* Input Port Change Register */ @@ -189,12 +208,13 @@ t_stat contty_detach(UNIT *uptr); t_stat tti_reset(DEVICE *dptr); t_stat contty_reset(DEVICE *dptr); t_stat iu_timer_reset(DEVICE *dptr); +t_stat iu_timer_set_mult(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat iu_timer_show_mult(FILE *st, UNIT *uptr, int val, CONST void *desc); t_stat iu_svc_tti(UNIT *uptr); t_stat iu_svc_tto(UNIT *uptr); -t_stat iu_svc_contty_rcv(UNIT *uptr); +t_stat iu_svc_contty(UNIT *uptr); t_stat iu_svc_contty_xmt(UNIT *uptr); t_stat iu_svc_timer(UNIT *uptr); -t_stat iu_tx(uint8 portno, uint8 val); uint32 iu_read(uint32 pa, size_t size); void iu_write(uint32 pa, uint32 val, size_t size); void iua_drq_handled(); diff --git a/3B2/3b2_rev2_mau.c b/3B2/3b2_mau.c similarity index 91% rename from 3B2/3b2_rev2_mau.c rename to 3B2/3b2_mau.c index 2b097794..fc14b27c 100644 --- a/3B2/3b2_rev2_mau.c +++ b/3B2/3b2_mau.c @@ -1,7 +1,6 @@ -/* 3b2_rev2_mau.c: AT&T 3B2 Rev 2 (Model 400) Math Acceleration - Unit (WE32106 MAU) Implementation +/* 3b2_mau.c: WE32106 and WE32206 Math Accelerator Unit - Copyright (c) 2019, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -30,11 +29,33 @@ --------------------------------------------------------------------- - This file is part of a simulation of the WE32106 Math Acceleration - Unit. The WE32106 MAU is an IEEE-754 compabitle floating point - hardware math accelerator that was available as an optional - component on the AT&T 3B2/310 and 3B2/400, and a standard component - on the 3B2/500, 3B2/600, and 3B2/1000. + This file is part of a simulation of the WE 32106 and WE 32206 Math + Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 + compabitle floating point hardware math accelerators that were + available as an optional component on the AT&T 3B2/310 and 3B2/400, + and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and + 3B2/1000. + + Unimplemented Features + ====================== + + All features of the WE 32106 MAU have been implemented, but there + remain some features of the WE 32206 that are not yet implemented. + Neither System V UNIX nor the Version 3 MAU diagnostics appear to + use these features in any way, but there is no guarantee that other + software does not use them. They are: + + - The FE, UW, and WF bits of the Auxiliary Status Register + - The new operand registers f4 through f7 + - The register bank select feature of the Command Register + - The RC and RCS bits of the Command Register + - The new WE 32206 instructions: + 1. ATAN + 2. COS + 3. PI + 4. SIN + + --------------------------------------------------------------------- Portions of this code are derived from the SoftFloat 2c library by John R. Hauser. Functions derived from SoftFloat 2c are clearly @@ -81,7 +102,7 @@ --------------------------------------------------------------------- */ -#include "3b2_rev2_mau.h" +#include "3b2_mau.h" #include @@ -123,8 +144,6 @@ static void sub_128(t_uint64 a0, t_uint64 a1, static void mul_64_to_128(t_uint64 a, t_uint64 b, t_uint64 *r_low, t_uint64 *r_high); static void mul_64_by_shifted_32_to_128(t_uint64 a, uint32 b, t_mau_128 *result); static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b); -static uint32 estimate_sqrt_32(int16 a_exp, uint32 a); - static uint32 round_pack_int(t_bool sign, t_uint64 frac, RM rounding_mode); static t_int64 round_pack_int64(t_bool sign, t_uint64 abs_0, t_uint64 abs_1, @@ -208,7 +227,13 @@ UNIT mau_unit = { UDATA(NULL, 0, 0) }; MAU_STATE mau_state; BITFIELD asr_bits[] = { +#if defined(REV3) + BIT(FE), + BITFFMT(VER,3,%d), + BIT(UW), +#else BITNCF(5), +#endif BIT(PR), BIT(QS), BIT(US), @@ -219,7 +244,11 @@ BITFIELD asr_bits[] = { BIT(UM), BIT(OM), BIT(IM), +#if defined(REV3) + BIT(WF), +#else BITNCF(1), +#endif BIT(UO), BIT(CSC), BIT(PS), @@ -279,7 +308,7 @@ DEVICE mau_dev = { #ifdef REV3 DEV_DEBUG, /* Rev 3 flags: Always required */ #else - DEV_DISABLE|DEV_DIS|DEV_DEBUG, /* Rev 2 flags */ + DEV_DISABLE|DEV_DEBUG, /* Rev 2 flags: Can be disabled */ #endif 0, /* debug control flags */ mau_debug, /* debug flag names */ @@ -314,10 +343,18 @@ XFP GEN_NONTRAPPING_NAN = { }; CONST char *mau_op_names[32] = { - "0x00", "0x01", "ADD", "SUB", "DIV", "REM", "MUL", "MOVE", /* 00-07 */ - "RDASR", "WRASR", "CMP", "CMPE", "ABS", "SQRT", "RTOI", "FTOI", /* 08-0F */ - "ITOF", "DTOF", "FTOD", "NOP", "EROF", "0x15", "0x16", "NEG", /* 10-17 */ - "LDR", "0x19", "CMPS", "CMPES", "0x1C", "0x1D", "0x1E", "0x1F" /* 18-1F */ + "0x00", "0x01", "ADD", "SUB", + "DIV", "REM", "MUL", "MOVE", + "RDASR", "WRASR", "CMP", "CMPE", + "ABS", "SQRT", "RTOI", "FTOI", + "ITOF", "DTOF", "FTOD", "NOP", + "EROF", "0x15", "0x16", "NEG", + "LDR", "0x19", "CMPS", "CMPES", +#if defined(REV3) + "SIN", "COS", "ATAN", "PI" +#else + "0x1C", "0x1D", "0x1E", "0x1F" +#endif }; CONST char *src_op_names[8] = { @@ -366,8 +403,8 @@ static SIM_INLINE void mau_case_div_zero(XFP *op1, XFP *op2, XFP *result) static SIM_INLINE void mau_exc(uint32 flag, uint32 mask) { sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_exc] asr=%08x flag=%08x mask=%08x\n", - R[NUM_PC], mau_state.asr, flag, mask); + "[mau_exc] asr=%08x flag=%08x mask=%08x\n", + mau_state.asr, flag, mask); mau_state.asr |= flag; @@ -427,8 +464,8 @@ static SIM_INLINE void abort_on_fault() stop_reason = STOP_EX; } sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [abort_on_fault] Aborting on un-maskable overflow fault. ASR=%08x\n", - R[NUM_PC], mau_state.asr); + "[abort_on_fault] Aborting on un-maskable overflow fault. ASR=%08x\n", + mau_state.asr); cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); } @@ -438,8 +475,8 @@ static SIM_INLINE void abort_on_fault() stop_reason = STOP_EX; } sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [abort_on_fault] Aborting on ECP fault. ASR=%08x\n", - R[NUM_PC], mau_state.asr); + "[abort_on_fault] Aborting on ECP fault. ASR=%08x\n", + mau_state.asr); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } @@ -475,7 +512,6 @@ static void clear_asr() */ static t_bool set_nz() { - switch(mau_state.opcode) { case M_NOP: case M_RDASR: @@ -490,6 +526,9 @@ static t_bool set_nz() t_stat mau_reset(DEVICE *dptr) { memset(&mau_state, 0, sizeof(MAU_STATE)); +#if defined(REV3) + mau_state.asr |= MAU_ASR_VER; /* Version 1 MAU */ +#endif return SCPE_OK; } @@ -846,44 +885,6 @@ static t_uint64 estimate_div_128_to_64(t_uint64 a0, t_uint64 a1, t_uint64 b) return z; } -/* - * Returns an approximation of the square root of the 32-bit - * value 'a'. - * - * Derived from the SoftFloat 2c package (see copyright notice above) - */ -static uint32 estimate_sqrt_32(int16 a_exp, uint32 a) -{ - static const uint16 sqrt_odd_adjust[] = { - 0x0004, 0x0022, 0x005D, 0x00B1, 0x011D, 0x019F, 0x0236, 0x02E0, - 0x039C, 0x0468, 0x0545, 0x0631, 0x072B, 0x0832, 0x0946, 0x0A67 - }; - - static const uint16 sqrt_even_adjust[] = { - 0x0A2D, 0x08AF, 0x075A, 0x0629, 0x051A, 0x0429, 0x0356, 0x029E, - 0x0200, 0x0179, 0x0109, 0x00AF, 0x0068, 0x0034, 0x0012, 0x0002 - }; - - int8 index; - uint32 z; - - index = (a >> 27) & 0xf; - - if (a_exp & 1) { - z = 0x4000 + (a >> 17) - sqrt_odd_adjust[index]; - z = ((a / z) << 14) + (z << 15); - a >>= 1; - } - else { - z = 0x8000 + (a >> 17) - sqrt_even_adjust[index]; - z = a / z + z; - z = (0x20000 <= z) ? 0xFFFF8000 : ( z<<15 ); - if ( z <= a ) return (uint32) (((int32) a) >> 1); - } - - return ((uint32) ((((t_uint64) a )<<31 ) / z)) + (z >> 1); -} - static uint32 approx_recip_sqrt_32(uint32 oddExpA, uint32 a) { int index; @@ -1833,9 +1834,8 @@ void xfp_to_decimal(XFP *a, DEC *d, RM rounding_mode) d->h |= (uint32)digits[15] << 8; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [xfp_to_decimal] " + "[xfp_to_decimal] " "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", - R[NUM_PC], digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], @@ -1855,8 +1855,8 @@ void mau_decimal_to_xfp(DEC *d, XFP *a) t_int64 signed_tmp; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_decimal_to_xfp] DEC input: %08x %08x %08x\n", - R[NUM_PC], d->h, (uint32)(d->l >> 32), (uint32)(d->l)); + "[mau_decimal_to_xfp] DEC input: %08x %08x %08x\n", + d->h, (uint32)(d->l >> 32), (uint32)(d->l)); sign = (d->l) & 15; digits[0] = (d->l >> 4) & 15; @@ -1879,9 +1879,8 @@ void mau_decimal_to_xfp(DEC *d, XFP *a) digits[17] = (d->h >> 8) & 15; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_decimal_to_xfp] " + "[mau_decimal_to_xfp] " "Digits: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d 0x%x\n", - R[NUM_PC], digits[17], digits[16], digits[15], digits[14], digits[13], digits[12], digits[11], digits[10], digits[9], digits[8], digits[7], digits[6], digits[5], digits[4], digits[3], digits[2], digits[1], digits[0], @@ -1906,14 +1905,14 @@ void mau_decimal_to_xfp(DEC *d, XFP *a) } sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_decimal_to_xfp] tmp val = %lld\n", - R[NUM_PC], signed_tmp); + "[mau_decimal_to_xfp] tmp val = %lld\n", + signed_tmp); mau_int64_to_xfp((t_uint64) signed_tmp, a); sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_decimal_to_xfp] XFP = %04x%016llx\n", - R[NUM_PC], a->sign_exp, a->frac); + "[mau_decimal_to_xfp] XFP = %04x%016llx\n", + a->sign_exp, a->frac); } @@ -2050,8 +2049,7 @@ static void xfp_add_fracs(XFP *a, XFP *b, t_bool sign, XFP *result, RM rounding_ int32 exp_diff; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [ADD_FRACS] a=%04x%016llx b=%04x%016llx\n", - R[NUM_PC], + "[ADD_FRACS] a=%04x%016llx b=%04x%016llx\n", a->sign_exp, a->frac, b->sign_exp, b->frac); @@ -2384,8 +2382,7 @@ static void xfp_mul(XFP *a, XFP *b, XFP *result, RM rounding_mode) t_uint64 a_frac, b_frac, r_frac_0, r_frac_1; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [MUL] op1=%04x%016llx op2=%04x%016llx\n", - R[NUM_PC], + "[MUL] op1=%04x%016llx op2=%04x%016llx\n", a->sign_exp, a->frac, b->sign_exp, b->frac); @@ -2469,8 +2466,8 @@ static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode) t_uint64 rem0, rem1, rem2, term0, term1, term2; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [DIV] op1=%04x%016llx op2=%04x%016llx\n", - R[NUM_PC], b->sign_exp, b->frac, a->sign_exp, a->frac); + "[DIV] op1=%04x%016llx op2=%04x%016llx\n", + b->sign_exp, b->frac, a->sign_exp, a->frac); a_sign = XFP_SIGN(a); a_exp = XFP_EXP(a); @@ -2525,7 +2522,7 @@ static void xfp_div(XFP *a, XFP *b, XFP *result, RM rounding_mode) } /* Divide by zero - SPECIAL CASE 4 */ sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [DIV] Divide by zero detected.\n", R[NUM_PC]); + "[DIV] Divide by zero detected.\n"); mau_case_div_zero(a, b, result); return; } @@ -2583,8 +2580,8 @@ static void xfp_sqrt(XFP *a, XFP *result, RM rounding_mode) t_mau_128 nan_128, rem, y, term; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [SQRT] op1=%04x%016llx\n", - R[NUM_PC], a->sign_exp, a->frac); + "[SQRT] op1=%04x%016llx\n", + a->sign_exp, a->frac); a_sign = XFP_SIGN(a); a_exp = XFP_EXP(a); @@ -2844,12 +2841,12 @@ static void load_src_op(uint8 op, XFP *xfp) xfp->frac = mau_state.f3.frac; break; case M_OP_MEM_SINGLE: - sfp = read_w(mau_state.src, ACC_AF); + sfp = read_w(mau_state.src, ACC_AF, BUS_PER); sfp_to_xfp(sfp, xfp); break; case M_OP_MEM_DOUBLE: - dfp = (t_uint64) read_w(mau_state.src + 4, ACC_AF); - dfp |= ((t_uint64) read_w(mau_state.src, ACC_AF)) << 32; + dfp = (t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER); + dfp |= ((t_uint64) read_w(mau_state.src, ACC_AF, BUS_PER)) << 32; sim_debug(TRACE_DBG, &mau_dev, "[load_src_op][DOUBLE] Loaded %016llx\n", dfp); @@ -2859,9 +2856,9 @@ static void load_src_op(uint8 op, XFP *xfp) xfp->sign_exp, xfp->frac); break; case M_OP_MEM_TRIPLE: - xfp->frac = (t_uint64) read_w(mau_state.src + 8, ACC_AF); - xfp->frac |= ((t_uint64) read_w(mau_state.src + 4, ACC_AF)) << 32; - xfp->sign_exp = (uint32) read_w(mau_state.src, ACC_AF); + xfp->frac = (t_uint64) read_w(mau_state.src + 8, ACC_AF, BUS_PER); + xfp->frac |= ((t_uint64) read_w(mau_state.src + 4, ACC_AF, BUS_PER)) << 32; + xfp->sign_exp = (uint32) read_w(mau_state.src, ACC_AF, BUS_PER); break; default: break; @@ -2877,9 +2874,9 @@ static void load_op1_decimal(DEC *d) switch (mau_state.op1) { case M_OP_MEM_TRIPLE: - low = read_w(mau_state.src + 8, ACC_AF); - mid = read_w(mau_state.src + 4, ACC_AF); - high = read_w(mau_state.src, ACC_AF); + low = read_w(mau_state.src + 8, ACC_AF, BUS_PER); + mid = read_w(mau_state.src + 4, ACC_AF, BUS_PER); + high = read_w(mau_state.src, ACC_AF, BUS_PER); d->l = low; d->l |= ((t_uint64) mid << 32); d->h = high; @@ -2911,7 +2908,7 @@ static void store_op3_int(uint32 val) mau_state.f3.frac = (t_uint64)val; break; case M_OP3_MEM_SINGLE: - write_w(mau_state.dst, val); + write_w(mau_state.dst, val, BUS_PER); break; default: /* Indeterminate output, unsupported */ @@ -2927,9 +2924,9 @@ static void store_op3_decimal(DEC *d) switch(mau_state.op3) { case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, d->h); - write_w(mau_state.dst + 4, (uint32)((t_uint64)d->l >> 32)); - write_w(mau_state.dst + 8, (uint32)d->l); + write_w(mau_state.dst, d->h, BUS_PER); + write_w(mau_state.dst + 4, (uint32)((t_uint64)d->l >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)d->l, BUS_PER); break; default: /* Unsupported */ @@ -2998,8 +2995,7 @@ static void store_op3(XFP *xfp) t_bool store_dr = FALSE; sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [store_op3] op3=%04x%016llx\n", - R[NUM_PC], + "[store_op3] op3=%04x%016llx\n", xfp->sign_exp, xfp->frac); @@ -3049,7 +3045,7 @@ static void store_op3(XFP *xfp) mau_state.asr |= MAU_ASR_Z; } } - write_w(mau_state.dst, (uint32)sfp); + write_w(mau_state.dst, (uint32)sfp, BUS_PER); break; case M_OP3_MEM_DOUBLE: if (mau_state.ntnan) { @@ -3074,18 +3070,18 @@ static void store_op3(XFP *xfp) mau_state.asr |= MAU_ASR_Z; } } - write_w(mau_state.dst, (uint32)(dfp >> 32)); - write_w(mau_state.dst + 4, (uint32)(dfp)); + write_w(mau_state.dst, (uint32)(dfp >> 32), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(dfp), BUS_PER); break; case M_OP3_MEM_TRIPLE: if (mau_state.ntnan) { - write_w(mau_state.dst, (uint32)(GEN_NONTRAPPING_NAN.sign_exp)); - write_w(mau_state.dst + 4, (uint32)(GEN_NONTRAPPING_NAN.frac >> 32)); - write_w(mau_state.dst + 8, (uint32)(GEN_NONTRAPPING_NAN.frac)); + write_w(mau_state.dst, (uint32)(GEN_NONTRAPPING_NAN.sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(GEN_NONTRAPPING_NAN.frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(GEN_NONTRAPPING_NAN.frac), BUS_PER); } else { - write_w(mau_state.dst, (uint32)(xfp->sign_exp)); - write_w(mau_state.dst + 4, (uint32)(xfp->frac >> 32)); - write_w(mau_state.dst + 8, (uint32)(xfp->frac)); + write_w(mau_state.dst, (uint32)(xfp->sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(xfp->frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(xfp->frac), BUS_PER); } if (set_nz()) { if (XFP_SIGN(xfp)) { @@ -3114,22 +3110,22 @@ static void mau_rdasr() switch (mau_state.op3) { /* Handled */ case M_OP3_MEM_SINGLE: - write_w(mau_state.dst, mau_state.asr); + write_w(mau_state.dst, mau_state.asr, BUS_PER); break; case M_OP3_MEM_DOUBLE: - write_w(mau_state.dst, mau_state.asr); - write_w(mau_state.dst + 4, mau_state.asr); + write_w(mau_state.dst, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); break; case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, mau_state.asr); - write_w(mau_state.dst + 4, mau_state.asr); - write_w(mau_state.dst + 8, mau_state.asr); + write_w(mau_state.dst, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 4, mau_state.asr, BUS_PER); + write_w(mau_state.dst + 8, mau_state.asr, BUS_PER); break; /* Unhandled */ default: sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_rdasr] WARNING: Unhandled source: %02x\n", - R[NUM_PC], mau_state.op3); + "[mau_rdasr] WARNING: Unhandled source: %02x\n", + mau_state.op3); break; } } @@ -3139,15 +3135,14 @@ static void mau_wrasr() switch (mau_state.op1) { /* Handled */ case M_OP_MEM_SINGLE: - mau_state.asr = read_w(mau_state.src, ACC_AF); + mau_state.asr = read_w(mau_state.src, ACC_AF, BUS_PER); sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [WRASR] Writing ASR with: %08x\n", - R[NUM_PC], mau_state.asr); + "[WRASR] Writing ASR with: %08x\n", + mau_state.asr); break; default: sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [mau_wrasr] WARNING: Unhandled source: %02x\n", - R[NUM_PC], + "[mau_wrasr] WARNING: Unhandled source: %02x\n", mau_state.op3); break; } @@ -3206,8 +3201,8 @@ static void mau_ldr() load_src_op(mau_state.op1, &xfp); sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [LDR] Loading DR with %04x%016llx\n", - R[NUM_PC], xfp.sign_exp, xfp.frac); + "[LDR] Loading DR with %04x%016llx\n", + xfp.sign_exp, xfp.frac); mau_state.dr.sign_exp = xfp.sign_exp; mau_state.dr.frac = xfp.frac; } @@ -3244,17 +3239,17 @@ static void mau_erof() return; case M_OP3_MEM_SINGLE: sfp = xfp_to_sfp(&(mau_state.dr), MAU_RM); - write_w(mau_state.dst, (uint32)sfp); + write_w(mau_state.dst, (uint32)sfp, BUS_PER); return; case M_OP3_MEM_DOUBLE: dfp = xfp_to_dfp(&(mau_state.dr), MAU_RM); - write_w(mau_state.dst + 4, (uint32)(dfp >> 32)); - write_w(mau_state.dst, (uint32)(dfp)); + write_w(mau_state.dst + 4, (uint32)(dfp >> 32), BUS_PER); + write_w(mau_state.dst, (uint32)(dfp), BUS_PER); return; case M_OP3_MEM_TRIPLE: - write_w(mau_state.dst, (uint32)(mau_state.dr.sign_exp)); - write_w(mau_state.dst + 4, (uint32)(mau_state.dr.frac >> 32)); - write_w(mau_state.dst + 8, (uint32)(mau_state.dr.frac)); + write_w(mau_state.dst, (uint32)(mau_state.dr.sign_exp), BUS_PER); + write_w(mau_state.dst + 4, (uint32)(mau_state.dr.frac >> 32), BUS_PER); + write_w(mau_state.dst + 8, (uint32)(mau_state.dr.frac), BUS_PER); return; default: sim_debug(TRACE_DBG, &mau_dev, @@ -3349,8 +3344,7 @@ static void mau_div() load_src_op(mau_state.op1, &a); load_src_op(mau_state.op2, &b); sim_debug(TRACE_DBG, &mau_dev, - "[%08x] [DIV OP2/OP1] OP2=0x%04x%016llx OP1=0x%04x%016llx\n", - R[NUM_PC], + "[DIV OP2/OP1] OP2=0x%04x%016llx OP1=0x%04x%016llx\n", b.sign_exp, b.frac, a.sign_exp, a.frac); xfp_div(&b, &a, &result, MAU_RM); @@ -3414,13 +3408,13 @@ static void mau_itof() mau_exc(MAU_ASR_IS, MAU_ASR_IM); return; case M_OP_MEM_SINGLE: - val = read_w(mau_state.src, ACC_AF); + val = read_w(mau_state.src, ACC_AF, BUS_PER); break; case M_OP_MEM_DOUBLE: - val = read_w(mau_state.src + 4, ACC_AF); + val = read_w(mau_state.src + 4, ACC_AF, BUS_PER); break; case M_OP_MEM_TRIPLE: - val = read_w(mau_state.src + 8, ACC_AF); + val = read_w(mau_state.src + 8, ACC_AF, BUS_PER); break; default: break; @@ -3593,5 +3587,9 @@ t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst) CONST char *mau_description(DEVICE *dptr) { - return "WE32106"; +#if defined(REV3) + return "WE 32106 MAU"; +#else + return "WE 32206 MAU"; +#endif } diff --git a/3B2/3b2_mau.h b/3B2/3b2_mau.h index fc5589a8..811c1748 100644 --- a/3B2/3b2_mau.h +++ b/3B2/3b2_mau.h @@ -1,6 +1,6 @@ -/* 3b2_mau.h: Common CSR header +/* 3b2_mau.h: WE32106 and WE32206 Math Accelerator Unit - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -26,12 +26,364 @@ not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. + + --------------------------------------------------------------------- + + This file is part of a simulation of the WE 32106 and WE 32206 Math + Acceleration Units. The WE 32106 and WE 32206 are IEEE-754 + compabitle floating point hardware math accelerators that were + available as an optional component on the AT&T 3B2/310 and 3B2/400, + and as a standard component on the 3B2/500, 3B2/600, 3B2/700, and + 3B2/1000. + + Portions of this code are derived from the SoftFloat 2c library by + John R. Hauser. Functions derived from SoftFloat 2c are clearly + marked in the comments. + + Legal Notice + ============ + + SoftFloat was written by John R. Hauser. Release 2c of SoftFloat + was made possible in part by the International Computer Science + Institute, located at Suite 600, 1947 Center Street, Berkeley, + California 94704. Funding was partially provided by the National + Science Foundation under grant MIP-9311980. The original version + of this code was written as part of a project to build a + fixed-point vector processor in collaboration with the University + of California at Berkeley, overseen by Profs. Nelson Morgan and + John Wawrzynek. + + THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable + effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS + THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS + SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND + WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE + TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE + INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE + EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER + SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL + LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND + CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A + DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. + + The following are expressly permitted, even for commercial + purposes: + + (1) distribution of SoftFloat in whole or in part, as long as this + and other legal notices remain and are prominent, and provided also + that, for a partial distribution, prominent notice is given that it + is a subset of the original; and + + (2) inclusion or use of SoftFloat in whole or in part in a + derivative work, provided that the use restrictions above are met + and the minimal documentation requirements stated in the source + code are satisfied. + --------------------------------------------------------------------- + + Data Types + ========== + + The WE32106 MAU stores values using IEEE-754 1985 types, plus a + non-standard Decimal type. + + - Decimal Type - 18 BCD digits long. Each digit is 4 bits wide. + Sign is encoded in byte 0. + + 3322 2222 2222 1111 1111 1100 0000 0000 + 1098 7654 3210 9876 5432 1098 7654 3210 + +-------------------+----+----+----+----+ + | unused | D18| D17| D16| D15| High Word + +----+----+----+----+----+----+----+----+ + | D14| D13| D12| D11| D10| D09| D08| D07| Middle Word + +----+----+----+----+----+----+----+----+ + | D06| D05| D04| D03| D02| D01| D00|sign| Low Word + +----+----+----+----+----+----+----+----+ + + Sign: 0: Positive Infinity 10: Positive Number + 1: Negative Infinity 11: Negative Number + 2: Positive NaN 12: Positive Number + 3: Negative NaN 13: Negative Number + 4-9: Trapping NaN 14-15: Positive Number + + - Extended Precision (80-bit) - exponent biased by 16383 + + 3 322222222221111 1 111110000000000 + 1 098765432109876 5 432109876543210 + +-----------------+-+---------------+ + | unused |S| Exponent | High Word + +-+---------------+-+---------------+ + |J| Fraction (high word) | Middle Word + +-+---------------------------------+ + | Fraction (low word) | Low Word + +-----------------------------------+ + + + - Double Precision (64-bit) - exponent biased by 1023 + + 3 3222222222 211111111110000000000 + 1 0987654321 098765432109876543210 + +-+----------+---------------------+ + |S| Exponent | Fraction (high) | High Word + +-+----------+---------------------+ + | Fraction (low) | Low Word + +----------------------------------+ + + + - Single Precision (32-bit) - exponent biased by 127 + + 3 32222222 22211111111110000000000 + 1 09876543 21098765432109876543210 + +-+--------+-----------------------+ + |S| Exp | Fraction | + +-+--------+-----------------------+ + */ -#ifndef _3B2_MAU_H_ -#define _3B2_MAU_H_ +#ifndef _3B2_REV2_MAU_H_ +#define _3B2_REV2_MAU_H_ -/* TODO: Update when rev3 mau is implemented */ -#include "3b2_rev2_mau.h" +#include "sim_defs.h" -#endif /* _3B2_MAU_H */ +#define SRC_LEN_INVALID 0 +#define SRC_LEN_SINGLE 1 +#define SRC_LEN_DOUBLE 2 +#define SRC_LEN_TRIPLE 3 + +#define MAU_ASR_RC_SHIFT 22 + +#define MAU_ASR_PR 0x20u /* Partial Remainder */ +#define MAU_ASR_QS 0x40u /* Divide By Zero Sticky */ +#define MAU_ASR_US 0x80u /* Underflow Sticky */ +#define MAU_ASR_OS 0x100u /* Overflow Sticky */ +#define MAU_ASR_IS 0x200u /* Invalid Operation Sticky */ +#define MAU_ASR_PM 0x400u /* Inexact Mask */ +#define MAU_ASR_QM 0x800u /* Divide by Zero Mask */ +#define MAU_ASR_UM 0x1000u /* Underflow Mask */ +#define MAU_ASR_OM 0x2000u /* Overflow Mask */ +#define MAU_ASR_IM 0x4000u /* Invalid Operation Mask */ + +#define MAU_ASR_UO 0x10000u /* Unordered */ +#define MAU_ASR_CSC 0x20000u /* Context Switch Control */ +#define MAU_ASR_PS 0x40000u /* Inexact Sticky */ +#define MAU_ASR_IO 0x80000u /* Integer Overflow */ +#define MAU_ASR_Z 0x100000u /* Zero Flag */ +#define MAU_ASR_N 0x200000u /* Negative Flag */ +#define MAU_ASR_RC 0x400000u /* Round Control */ + +#define MAU_ASR_NTNC 0x1000000u /* Nontrapping NaN Control */ +#define MAU_ASR_ECP 0x2000000u /* Exception Condition */ + +#define MAU_ASR_RA 0x80000000u /* Result Available */ + +#if defined(REV3) +#define MAU_ASR_FE 0x1u /* Feature Enable */ +#define MAU_ASR_VER 0x2u /* Version */ +#define MAU_ASR_UW 0x10u /* Unaligned Word */ +#define MAU_ASR_WF 0x8000u /* Write Fault Indicator */ +#define MAU_RC_RN 0 /* Round toward Nearest */ +#define MAU_RC_RP 1 /* Round toward Plus Infin. */ +#define MAU_RC_RM 2 /* Round toward Neg. Infin. */ +#define MAU_RC_RZ 3 /* Round toward Zero */ +#endif + +#define SFP_SIGN(V) (((V) >> 31) & 1) +#define SFP_EXP(V) (((V) >> 23) & 0xff) +#define SFP_FRAC(V) ((V) & 0x7fffff) + +#define DFP_SIGN(V) (((V) >> 63) & 1) +#define DFP_EXP(V) (((V) >> 52) & 0x7ff) +#define DFP_FRAC(V) ((V) & 0xfffffffffffffull) + +#define XFP_SIGN(V) (((V)->sign_exp >> 15) & 1) +#define XFP_EXP(V) ((V)->sign_exp & 0x7fff) +#define XFP_FRAC(V) ((V)->frac) + +#define XFP_IS_NORMAL(V) ((V)->frac & 0x8000000000000000ull) + +#define DEFAULT_XFP_NAN_SIGN_EXP 0xffff +#define DEFAULT_XFP_NAN_FRAC 0xc000000000000000ull + +#define SFP_IS_TRAPPING_NAN(V) (((((V) >> 22) & 0x1ff) == 0x1fe) && \ + ((V) & 0x3fffff)) +#define DFP_IS_TRAPPING_NAN(V) (((((V) >> 51) & 0xfff) == 0xffe) && \ + ((V) & 0x7ffffffffffffull)) +#define XFP_IS_NAN(V) ((((V)->sign_exp & 0x7fff) == 0x7fff) && \ + (t_uint64)((V)->frac << 1)) +#define XFP_IS_TRAPPING_NAN(V) ((((V)->sign_exp) & 0x7fff) && \ + ((((V)->frac) & ~(0x4000000000000000ull)) << 1) && \ + (((V)->frac) == ((V)->frac & ~(0x4000000000000000ull)))) +#define PACK_DFP(SIGN,EXP,FRAC) ((((t_uint64)(SIGN))<<63) + \ + (((t_uint64)(EXP))<<52) + \ + ((t_uint64)(FRAC))) +#define PACK_SFP(SIGN,EXP,FRAC) (((uint32)(SIGN)<<31) + \ + ((uint32)(EXP)<<23) + \ + ((uint32)(FRAC))) +#define PACK_XFP(SIGN,EXP,FRAC,V) do { \ + (V)->frac = (FRAC); \ + (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ + (V)->s = FALSE; \ + } while (0) + +#define PACK_XFP_S(SIGN,EXP,FRAC,S,V) do { \ + (V)->frac = (FRAC); \ + (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ + (V)->s = (S) != 0; \ + } while (0) + +#define MAU_RM ((RM)((mau_state.asr >> 22) & 3)) + +typedef enum { + M_ADD = 0x02, + M_SUB = 0x03, + M_DIV = 0x04, + M_REM = 0x05, + M_MUL = 0x06, + M_MOVE = 0x07, + M_RDASR = 0x08, + M_WRASR = 0x09, + M_CMP = 0x0a, + M_CMPE = 0x0b, + M_ABS = 0x0c, + M_SQRT = 0x0d, + M_RTOI = 0x0e, + M_FTOI = 0x0f, + M_ITOF = 0x10, + M_DTOF = 0x11, + M_FTOD = 0x12, + M_NOP = 0x13, + M_EROF = 0x14, + M_NEG = 0x17, + M_LDR = 0x18, + M_CMPS = 0x1a, + M_CMPES = 0x1b +} mau_opcodes; + +typedef enum { + M_OP3_F0_SINGLE, + M_OP3_F1_SINGLE, + M_OP3_F2_SINGLE, + M_OP3_F3_SINGLE, + M_OP3_F0_DOUBLE, + M_OP3_F1_DOUBLE, + M_OP3_F2_DOUBLE, + M_OP3_F3_DOUBLE, + M_OP3_F0_TRIPLE, + M_OP3_F1_TRIPLE, + M_OP3_F2_TRIPLE, + M_OP3_F3_TRIPLE, + M_OP3_MEM_SINGLE, + M_OP3_MEM_DOUBLE, + M_OP3_MEM_TRIPLE, + M_OP3_NONE +} op3_spec; + +/* Specifier bytes for Operands 1 and 2 */ +typedef enum { + M_OP_F0, + M_OP_F1, + M_OP_F2, + M_OP_F3, + M_OP_MEM_SINGLE, + M_OP_MEM_DOUBLE, + M_OP_MEM_TRIPLE, + M_OP_NONE +} op_spec; + +/* + * 128-bit value + */ +typedef struct { + t_uint64 low; + t_uint64 high; +} t_mau_128; + +/* + * Not-a-Number Type + */ +typedef struct { + t_bool sign; + t_uint64 high; + t_uint64 low; +} T_NAN; + +/* + * Extended Precision (80 bits). + * + * Note that an undocumented feature of the WE32106 requires the use + * of uint32 rather than uint16 for the sign and exponent components + * of the struct. Although bits 80-95 are "unused", several + * diagnostics actually expect these bits to be moved and preserved on + * word transfers. They are ignored and discarded by math routines, + * however. + * + * The 's' field holds the Sticky bit used by rounding. + */ +typedef struct { + uint32 sign_exp; /* Sign and Exponent */ + t_uint64 frac; /* Fraction/Significand/Mantissa */ + t_bool s; /* Sticky bit */ +} XFP; + +typedef struct { + uint32 h; + t_uint64 l; +} DEC; + +/* + * Supported rounding modes. + */ +typedef enum { + ROUND_NEAREST, + ROUND_PLUS_INF, + ROUND_MINUS_INF, + ROUND_ZERO +} RM; + +/* + * Double Precision (64 bits) + */ +typedef t_uint64 DFP; + +/* + * Single Precision (32 bits) + */ +typedef uint32 SFP; + +/* + * MAU state + */ + +typedef struct { + uint32 cmd; + /* Exception */ + uint32 exception; + /* Status register */ + uint32 asr; + t_bool trapping_nan; + /* Generate a Non-Trapping NaN */ + t_bool ntnan; + /* Source (from broadcast) */ + uint32 src; + /* Destination (from broadcast) */ + uint32 dst; + uint8 opcode; + uint8 op1; + uint8 op2; + uint8 op3; + /* Data Register */ + XFP dr; + /* Operand Registers */ + XFP f0; + XFP f1; + XFP f2; + XFP f3; +} MAU_STATE; + +t_stat mau_reset(DEVICE *dptr); +t_stat mau_attach(UNIT *uptr, CONST char *cptr); +t_stat mau_detach(UNIT *uptr); +t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); +CONST char *mau_description(DEVICE *dptr); +t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); + +#endif /* _3B2_REV2_MAU_H_ */ diff --git a/3B2/3b2_mem.c b/3B2/3b2_mem.c index 90db6946..1a776d45 100644 --- a/3B2/3b2_mem.c +++ b/3B2/3b2_mem.c @@ -1,6 +1,6 @@ -/* 3b2_mem.c: AT&T 3B2 memory access routines +/* 3b2_mem.c: Memory Map Access Routines - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -34,82 +34,111 @@ #include "3b2_csr.h" #include "3b2_io.h" #include "3b2_mmu.h" +#include "3b2_stddev.h" +#include "3b2_dmac.h" -t_bool addr_is_rom(uint32 pa) -{ - return (pa < rom_size); -} +#if defined(REV3) +static uint32 ecc_addr; /* ECC address */ +static t_bool ecc_err; /* ECC multi-bit error */ +#endif -t_bool addr_is_mem(uint32 pa) -{ - return (pa >= PHYS_MEM_BASE && - pa < (PHYS_MEM_BASE + MEM_SIZE)); -} - -t_bool addr_is_io(uint32 pa) +/* + * ECC is simulated just enough to pass diagnostics, and no more. + * + * Checking and setting of ECC syndrome bits is a no-op for Rev 2. + */ +static SIM_INLINE void check_ecc(uint32 pa, t_bool write, uint8 src) { #if defined(REV3) - return ((pa >= IO_BOTTOM && pa < IO_TOP) || - (pa >= CIO_BOTTOM && pa < CIO_TOP) || - (pa >= VCACHE_BOTTOM && pa < VCACHE_TOP) || - (pa >= BUB_BOTTOM && pa < BUB_TOP)); -#else - return ((pa >= IO_BOTTOM && pa < IO_TOP) || - (pa >= CIO_BOTTOM && pa < CIO_TOP)); + /* Force ECC Syndrome mode enables a diagnostic mode on the AM2960 + data correction ICs */ + if (write && !CSR(CSRFECC)) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "ECC Error on Write. pa=%08x\n", + pa); + ecc_addr = pa; + ecc_err = TRUE; + } else if (ecc_err && !write && pa == ecc_addr) { + sim_debug(EXECUTE_MSG, &mmu_dev, + "ECC Error detected on Read. pa=%08x psw=%08x cur_ipl=%d csr=%08x\n", + pa, R[NUM_PSW], PSW_CUR_IPL, csr_data); + flt[0] = ecc_addr & 0x3ffff; + flt[1] = MA_CPU_IO|MA_CPU_BU; + ecc_err = FALSE; + CSRBIT(CSRFRF, TRUE); /* Fault registers frozen */ + CSRBIT(CSRMBERR, TRUE); /* Multi-bit error */ + CPU_SET_INT(INT_MBERR); + /* Only abort if CPU is doing the read */ + if (src == BUS_CPU) { + cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + } + } #endif } /* Read Word (Physical Address) */ -uint32 pread_w(uint32 pa) +uint32 pread_w(uint32 pa, uint8 src) { - uint32 *m; - uint32 index; + uint8 *m; + uint32 index = 0; +#if defined(REV3) + if ((pa & 3) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { +#else if (pa & 3) { +#endif sim_debug(READ_MSG, &mmu_dev, - "[%08x] Cannot read physical address. ALIGNMENT ISSUE: %08x\n", - R[NUM_PC], pa); + "Cannot read physical address. ALIGNMENT ISSUE: %08x\n", + pa); CSRBIT(CSRALGN, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } - if (addr_is_io(pa)) { + if (IS_IO(pa)) { return io_read(pa, 32); } - if (addr_is_rom(pa)) { + if (IS_ROM(pa)) { m = ROM; - index = pa >> 2; - } else if (addr_is_mem(pa)) { + index = pa; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); m = RAM; - index = (pa - PHYS_MEM_BASE) >> 2; + index = (pa - PHYS_MEM_BASE); } else { return 0; } - return m[index]; + return ATOW(m, index); } /* * Write Word (Physical Address) */ -void pwrite_w(uint32 pa, uint32 val) +void pwrite_w(uint32 pa, uint32 val, uint8 src) { + uint32 index; + if (pa & 3) { sim_debug(WRITE_MSG, &mmu_dev, - "[%08x] Cannot write physical address. ALIGNMENT ISSUE: %08x\n", - R[NUM_PC], pa); + "Cannot write physical address. ALIGNMENT ISSUE: %08x\n", + pa); CSRBIT(CSRALGN, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } - if (addr_is_io(pa)) { + if (IS_IO(pa)) { io_write(pa, val, 32); return; } - if (addr_is_mem(pa)) { - RAM[(pa - PHYS_MEM_BASE) >> 2] = val; + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = (val >> 24) & 0xff; + RAM[index + 1] = (val >> 16) & 0xff; + RAM[index + 2] = (val >> 8) & 0xff; + RAM[index + 3] = val & 0xff; return; } } @@ -117,165 +146,158 @@ void pwrite_w(uint32 pa, uint32 val) /* * Read Halfword (Physical Address) */ -uint16 pread_h(uint32 pa) +uint16 pread_h(uint32 pa, uint8 src) { - uint32 *m; + uint8 *m; uint32 index; if (pa & 1) { sim_debug(READ_MSG, &mmu_dev, - "[%08x] Cannot read physical address. ALIGNMENT ISSUE %08x\n", - R[NUM_PC], pa); + "Cannot read physical address. ALIGNMENT ISSUE %08x\n", + pa); CSRBIT(CSRALGN, TRUE); cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } - if (addr_is_io(pa)) { + if (IS_IO(pa)) { return (uint16) io_read(pa, 16); } - if (addr_is_rom(pa)) { + if (IS_ROM(pa)) { m = ROM; - index = pa >> 2; - } else if (addr_is_mem(pa)) { + index = pa; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); m = RAM; - index = (pa - PHYS_MEM_BASE) >> 2; + index = pa - PHYS_MEM_BASE; } else { return 0; } - if (pa & 2) { - return m[index] & HALF_MASK; - } else { - return (m[index] >> 16) & HALF_MASK; - } + return ATOH(m, index); } /* * Write Halfword (Physical Address) */ -void pwrite_h(uint32 pa, uint16 val) +void pwrite_h(uint32 pa, uint16 val, uint8 src) { - uint32 *m; uint32 index; - uint32 wval = (uint32)val; +#if defined(REV3) + if ((pa & 1) && (R[NUM_PSW] & PSW_EA_MASK) == 0) { +#else if (pa & 1) { +#endif sim_debug(WRITE_MSG, &mmu_dev, - "[%08x] Cannot write physical address %08x, ALIGNMENT ISSUE\n", - R[NUM_PC], pa); + "Cannot write physical address %08x, ALIGNMENT ISSUE\n", + pa); CSRBIT(CSRALGN, TRUE); - cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); } - if (addr_is_io(pa)) { + if (IS_IO(pa)) { io_write(pa, val, 16); return; } - if (addr_is_mem(pa)) { - m = RAM; - index = (pa - PHYS_MEM_BASE) >> 2; - } else { + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = (val >> 8) & 0xff; + RAM[index + 1] = val & 0xff; return; } - - if (pa & 2) { - m[index] = (m[index] & ~HALF_MASK) | wval; - } else { - m[index] = (m[index] & HALF_MASK) | (wval << 16); - } } /* * Read Byte (Physical Address) */ -uint8 pread_b(uint32 pa) +uint8 pread_b(uint32 pa, uint8 src) { - uint32 data; - int32 sc = (~(pa & 3) << 3) & 0x1f; - - if (addr_is_io(pa)) { + if (IS_IO(pa)) { return (uint8)(io_read(pa, 8)); } - if (addr_is_rom(pa)) { - data = ROM[pa >> 2]; - } else if (addr_is_mem(pa)) { - data = RAM[(pa - PHYS_MEM_BASE) >> 2]; + if (IS_ROM(pa)) { + return ROM[pa]; + } else if (IS_RAM(pa)) { + check_ecc(pa, FALSE, src); + return RAM[pa - PHYS_MEM_BASE]; } else { return 0; } - - return (data >> sc) & BYTE_MASK; } /* Write Byte (Physical Address) */ -void pwrite_b(uint32 pa, uint8 val) +void pwrite_b(uint32 pa, uint8 val, uint8 src) { - uint32 *m; - int32 index; - int32 sc = (~(pa & 3) << 3) & 0x1f; - uint32 mask = 0xffu << sc; + uint32 index; - if (addr_is_io(pa)) { + if (IS_IO(pa)) { io_write(pa, val, 8); return; } - if (addr_is_mem(pa)) { - m = RAM; - index = (pa - PHYS_MEM_BASE) >> 2; - m[index] = (m[index] & ~mask) | (uint32) (val << sc); + if (IS_RAM(pa)) { + check_ecc(pa, TRUE, src); + index = pa - PHYS_MEM_BASE; + RAM[index] = val; return; } } +/* Write to ROM (used by ROM load) */ +void pwrite_b_rom(uint32 pa, uint8 val) { + if (IS_ROM(pa)) { + ROM[pa] = val; + } + } + /* Read Byte (Virtual Address) */ -uint8 read_b(uint32 va, uint8 r_acc) +uint8 read_b(uint32 va, uint8 r_acc, uint8 src) { - return pread_b(mmu_xlate_addr(va, r_acc)); + return pread_b(mmu_xlate_addr(va, r_acc), src); } /* Write Byte (Virtual Address) */ -void write_b(uint32 va, uint8 val) +void write_b(uint32 va, uint8 val, uint8 src) { - pwrite_b(mmu_xlate_addr(va, ACC_W), val); + pwrite_b(mmu_xlate_addr(va, ACC_W), val, src); } /* Read Halfword (Virtual Address) */ -uint16 read_h(uint32 va, uint8 r_acc) +uint16 read_h(uint32 va, uint8 r_acc, uint8 src) { - return pread_h(mmu_xlate_addr(va, r_acc)); + return pread_h(mmu_xlate_addr(va, r_acc), src); } /* Write Halfword (Virtual Address) */ -void write_h(uint32 va, uint16 val) +void write_h(uint32 va, uint16 val, uint8 src) { - pwrite_h(mmu_xlate_addr(va, ACC_W), val); + pwrite_h(mmu_xlate_addr(va, ACC_W), val, src); } /* Read Word (Virtual Address) */ -uint32 read_w(uint32 va, uint8 r_acc) +uint32 read_w(uint32 va, uint8 r_acc, uint8 src) { - return pread_w(mmu_xlate_addr(va, r_acc)); + return pread_w(mmu_xlate_addr(va, r_acc), src); } /* Write Word (Virtual Address) */ -void write_w(uint32 va, uint32 val) +void write_w(uint32 va, uint32 val, uint8 src) { - pwrite_w(mmu_xlate_addr(va, ACC_W), val); + pwrite_w(mmu_xlate_addr(va, ACC_W), val, src); } t_stat read_operand(uint32 va, uint8 *val) { uint32 pa; t_stat succ; - + succ = mmu_decode_va(va, ACC_IF, TRUE, &pa); if (succ == SCPE_OK) { - *val = pread_b(pa); + *val = pread_b(pa, BUS_CPU); } else { *val = 0; } @@ -291,8 +313,8 @@ t_stat examine(uint32 va, uint8 *val) succ = mmu_decode_va(va, 0, FALSE, &pa); if (succ == SCPE_OK) { - if (addr_is_rom(pa) || addr_is_mem(pa)) { - *val = pread_b(pa); + if (IS_ROM(pa) || IS_RAM(pa)) { + *val = pread_b(pa, BUS_CPU); return SCPE_OK; } else { *val = 0; @@ -312,8 +334,8 @@ t_stat deposit(uint32 va, uint8 val) succ = mmu_decode_va(va, 0, FALSE, &pa); if (succ == SCPE_OK) { - if (addr_is_mem(pa)) { - pwrite_b(pa, val); + if (IS_RAM(pa)) { + pwrite_b(pa, val, BUS_CPU); return SCPE_OK; } else { return SCPE_NXM; diff --git a/3B2/3b2_mem.h b/3B2/3b2_mem.h index b477bfb8..0f0fab3c 100644 --- a/3B2/3b2_mem.h +++ b/3B2/3b2_mem.h @@ -1,6 +1,6 @@ -/* 3b2_mem.h: AT&T 3B2 3B2 memory access routines +/* 3b2_mem.h: Memory Map Access Routines - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,30 +33,47 @@ #include "3b2_defs.h" -uint32 pread_w(uint32 pa); -void pwrite_w(uint32 pa, uint32 val); -uint8 pread_b(uint32 pa); -void pwrite_b(uint32 pa, uint8 val); -uint16 pread_h(uint32 pa); -void pwrite_h(uint32 pa, uint16 val); +#define IS_ROM(PA) ((PA) < ROM_SIZE) +#define IS_RAM(PA) (((PA) >= PHYS_MEM_BASE) && ((PA) < (PHYS_MEM_BASE + MEM_SIZE))) +#if defined(REV3) +#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ + ((PA >= CIO_BOTTOM) && (PA < CIO_TOP)) || \ + ((PA >= VCACHE_BOTTOM) && (PA < VCACHE_TOP)) || \ + ((PA >= BUB_BOTTOM) && (PA < BUB_TOP))) +#else +#define IS_IO(PA) (((PA >= IO_BOTTOM) && (PA < IO_TOP)) || \ + ((PA >= CIO_BOTTOM) && (PA < CIO_TOP))) +#endif -uint8 read_b(uint32 va, uint8 r_acc); -uint16 read_h(uint32 va, uint8 r_acc); -uint32 read_w(uint32 va, uint8 r_acc); -void write_b(uint32 va, uint8 val); -void write_h(uint32 va, uint16 val); -void write_w(uint32 va, uint32 val); +#define MA_BUB3 0x100 /* BUBUS slot 3 master on fault */ +#define MA_BUB2 0x200 /* BUBUS slot 2 master on fault */ +#define MA_BUB1 0x400 /* BUBUS slot 1 master on fault */ +#define MA_CPU_BU 0x2000 /* CPU access BUBUS peripheral */ +#define MA_BUB0 0x4000 /* BUBUS slot 0 master on fault */ +#define MA_CPU_IO 0x8000 /* CPU accessing I/O peripheral */ +#define MA_IO_NLY 0x10000 /* IO Bus Master on fault */ +#define MA_IO_BM 0x80000 /* IO Bus Master or BUBUS was master on fault */ + +#define BUS_PER 0 /* Read or Write is from peripheral */ +#define BUS_CPU 1 /* Read or Write is from CPU */ + +uint32 pread_w(uint32 pa, uint8 src); +void pwrite_w(uint32 pa, uint32 val, uint8 src); +uint8 pread_b(uint32 pa, uint8 src); +void pwrite_b(uint32 pa, uint8 val, uint8 src); +void pwrite_b_rom(uint32 pa, uint8 val); +uint16 pread_h(uint32 pa, uint8 src); +void pwrite_h(uint32 pa, uint16 val, uint8 src); + +uint8 read_b(uint32 va, uint8 r_acc, uint8 src); +uint16 read_h(uint32 va, uint8 r_acc, uint8 src); +uint32 read_w(uint32 va, uint8 r_acc, uint8 src); +void write_b(uint32 va, uint8 val, uint8 src); +void write_h(uint32 va, uint16 val, uint8 src); +void write_w(uint32 va, uint32 val, uint8 src); t_stat read_operand(uint32 va, uint8 *val); t_stat examine(uint32 va, uint8 *val); t_stat deposit(uint32 va, uint8 val); -t_bool addr_is_rom(uint32 pa); -t_bool addr_is_mem(uint32 pa); -t_bool addr_is_io(uint32 pa); - -t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); -void mmu_enable(); -void mmu_disable(); - #endif /* _3B2_MEM_H_ */ diff --git a/3B2/3b2_mmu.h b/3B2/3b2_mmu.h index 54192991..4283b230 100644 --- a/3B2/3b2_mmu.h +++ b/3B2/3b2_mmu.h @@ -1,6 +1,6 @@ /* 3b2_mmu.h: Common MMU header - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/3B2/3b2_ni.c b/3B2/3b2_ni.c index c17dccd6..36a74cfa 100644 --- a/3B2/3b2_ni.c +++ b/3B2/3b2_ni.c @@ -1,6 +1,6 @@ -/* 3b2_ni.c: AT&T 3B2 Model 400 "NI" feature card +/* 3b2_ni.c: CM195A Network Interface CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -97,26 +97,23 @@ #include "3b2_io.h" #include "3b2_mem.h" -#include "3b2_timer.h" +#include "3b2_stddev.h" /* State container for the card */ NI_STATE ni; +t_bool ni_conf = FALSE; + /* Static Function Declarations */ static void dump_packet(const char *direction, ETH_PACK *pkt); static void ni_enable(); static void ni_disable(); -static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp); -static t_stat ni_show_queue_common(FILE *st, UNIT *uptr, int32 val, - CONST void *desc, t_bool rq); -static t_stat ni_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -static t_stat ni_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp); /* - * When the NI card is pumped, its CRC depends on what slot it is - * installed in and what version of driver has been installed. + * A list of pumped code CRCs that will cause Force Function Call to + * respond with "Test Passed". Must be null-terminated. */ -#define NI_DIAG_CRCS_LEN 7 static const uint32 NI_DIAG_CRCS[] = { 0x795268a4, 0xfab1057c, @@ -125,6 +122,23 @@ static const uint32 NI_DIAG_CRCS[] = { 0x267b19a0, 0x123f36c0, 0xc04ca0ab, + 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ + 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ + 0, +}; + +/* + * A list of pumped code CRCs that will cause the sysgen routine to + * respond with a full completion request instead of an express + * completion request. Must be null-terminated. + */ +static const uint32 NI_PUMP_CRCS[] = { + 0xfab1057c, /* Rev 2 NI, SVR 3.x */ + 0xf6744bed, /* Rev 2 NI, SVR 3.x */ + 0x96d0506e, /* Rev 3 NI, SVR 3.2.3 */ + 0x9d3fafde, /* Rev 3 NI, SVR 3.2.3 */ + 0x3553230a, /* Rev 3 NI, SVR 3.2.3 */ + 0, }; /* @@ -151,10 +165,6 @@ MTAB ni_mod[] = { &ni_set_stats, &ni_show_stats, NULL, "Display or reset statistics" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "POLL", NULL, NULL, &ni_show_poll, NULL, "Display the current polling mode" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "RQUEUE=n", NULL, - NULL, &ni_show_rqueue, NULL, "Display Request Queue for card n" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL, - NULL, &ni_show_cqueue, NULL, "Display Completion Queue for card n" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, "MAC", "MAC=xx:xx:xx:xx:xx:xx", &ni_setmac, &ni_showmac, NULL, "MAC address" }, { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "FILTERS", NULL, @@ -291,10 +301,10 @@ static void ni_disable() sim_cancel(rq_unit); sim_cancel(cio_unit); sim_cancel(sanity_unit); - CIO_CLR_INT(ni.cid); + CIO_CLR_INT(ni.slot); } -static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp) +static void ni_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp) { int i, j; int32 delay; @@ -310,14 +320,14 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp centry.subdevice = rentry->subdevice; centry.address = rentry->address; - cio[cid].op = rentry->opcode; + cio[slot].op = rentry->opcode; delay = NI_INT_DELAY; switch(rentry->opcode) { case CIO_DLM: for (i = 0; i < rentry->byte_count; i++) { - ni.crc = cio_crc32_shift(ni.crc, pread_b(rentry->address + i)); + ni.crc = cio_crc32_shift(ni.crc, pread_b(rentry->address + i, BUS_PER)); } centry.address = rentry->address + rentry->byte_count; @@ -328,9 +338,9 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp centry.address, centry.subdevice, ni.crc); if (is_exp) { - cio_cexpress(cid, NIQESIZE, ¢ry, app_data); + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); } else { - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); } break; @@ -342,13 +352,13 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp /* If the currently running program is a diagnostics program, * we are expected to write results into memory at address * 0x200f000 */ - for (i = 0; i < NI_DIAG_CRCS_LEN; i++) { + for (i = 0; NI_DIAG_CRCS[i] != 0; i++) { if (ni.crc == NI_DIAG_CRCS[i]) { - pwrite_h(0x200f000, 0x1); /* Test success */ - pwrite_h(0x200f002, 0x0); /* Test Number */ - pwrite_h(0x200f004, 0x0); /* Actual */ - pwrite_h(0x200f006, 0x0); /* Expected */ - pwrite_b(0x200f008, 0x1); /* Success flag again */ + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ break; } } @@ -362,12 +372,12 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp * in the right state. */ ni_disable(); - cio[cid].sysgen_s = 0; + cio[slot].sysgen_s = 0; - if (cio[cid].ivec == 0 || cio[cid].ivec == 3) { - cio_cexpress(cid, NIQESIZE, ¢ry, app_data); + if (cio[slot].ivec == 0 || cio[slot].ivec == 3) { + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); } else { - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); } break; @@ -378,12 +388,12 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp /* The system wants us to write sub-device structures at the * supplied address */ - pwrite_h(rentry->address, 0x0); + pwrite_h(rentry->address, 0x0, BUS_PER); if (is_exp) { - cio_cexpress(cid, NIQESIZE, ¢ry, app_data); + cio_cexpress(slot, NIQESIZE, ¢ry, app_data); } else { - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); } break; @@ -393,7 +403,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp /* Try to read the mac from memory */ for (i = 0; i < MAC_SIZE_BYTES; i++) { - ni.mac_bytes[i] = pread_b(rentry->address + i); + ni.mac_bytes[i] = pread_b(rentry->address + i, BUS_PER); } snprintf(ni.mac_str, MAC_SIZE_CHARS, "%02x:%02x:%02x:%02x:%02x:%02x", @@ -406,7 +416,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp status = ni_setmac(ni_dev.units, 0, ni.mac_str, NULL); - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); break; case NI_TURNOFF: @@ -415,7 +425,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp ni_disable(); - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); break; case NI_TURNON: @@ -424,14 +434,14 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp ni_enable(); - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); break; case NI_STATS: sim_debug(DBG_TRACE, &ni_dev, "[ni_cmd] NI STATS Operation\n"); - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); break; case NI_SEND: @@ -471,25 +481,25 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp ni.wr_buf.oversize = NULL; /* Read the size of the header */ - hdrsize = pread_h(rentry->address + EIG_TABLE_SIZE); + hdrsize = pread_h(rentry->address + EIG_TABLE_SIZE, BUS_PER); /* Read out the packet frame */ for (i = 0; i < rentry->byte_count; i++) { - ni.wr_buf.msg[i] = pread_b(rentry->address + PKT_START_OFFSET + i); + ni.wr_buf.msg[i] = pread_b(rentry->address + PKT_START_OFFSET + i, BUS_PER); } /* Get a pointer to the buffer containing the protocol data */ prot_info_offset = 0; i = 0; do { - ni.prot.addr = pread_w(rentry->address + prot_info_offset); - ni.prot.size = pread_h(rentry->address + prot_info_offset + 4); - ni.prot.last = pread_h(rentry->address + prot_info_offset + 6); + ni.prot.addr = pread_w(rentry->address + prot_info_offset, BUS_PER); + ni.prot.size = pread_h(rentry->address + prot_info_offset + 4, BUS_PER); + ni.prot.last = pread_h(rentry->address + prot_info_offset + 6, BUS_PER); prot_info_offset += 8; /* Fill in the frame from this buffer */ for (j=0; j < ni.prot.size; i++, j++) { - ni.wr_buf.msg[hdrsize + i] = pread_b(ni.prot.addr + j); + ni.wr_buf.msg[hdrsize + i] = pread_b(ni.prot.addr + j, BUS_PER); } } while (!ni.prot.last); @@ -521,7 +531,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp centry.byte_count <<= 8; } - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); delay = 0; @@ -531,7 +541,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp "[ni_cmd] Opcode %d Not Handled Yet\n", rentry->opcode); - cio_cqueue(cid, CIO_STAT, NIQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_STAT, NIQESIZE, ¢ry, app_data); break; } @@ -595,8 +605,10 @@ t_stat ni_show_filters(FILE* st, UNIT* uptr, int32 val, CONST void* desc) return SCPE_OK; } -void ni_sysgen(uint8 cid) +void ni_sysgen(uint8 slot) { + int i; + t_bool pumped = FALSE; cio_entry cqe = {0}; uint8 app_data[4] = {0}; @@ -607,16 +619,21 @@ void ni_sysgen(uint8 cid) sim_debug(DBG_TRACE, &ni_dev, "[ni_sysgen] CIO SYSGEN. rqp=%08x, cqp=%08x, nrq=%d, rqs=%d cqs=%d\n", - cio[cid].rqp, cio[cid].cqp, cio[cid].no_rque, cio[cid].rqs, cio[cid].cqs); + cio[slot].rqp, cio[slot].cqp, cio[slot].no_rque, cio[slot].rqs, cio[slot].cqs); /* If the card has been successfully pumped, then we respond with * a full completion queue entry. Otherwise, an express entry is * used. */ - if (ni.crc == NI_PUMP_CRC1 || - ni.crc == NI_PUMP_CRC2) { - cio_cqueue(cid, CIO_STAT, NIQESIZE, &cqe, app_data); - } else { - cio_cexpress(cid, NIQESIZE, &cqe, app_data); + for (i = 0; NI_PUMP_CRCS[i] != 0; i++) { + if (NI_PUMP_CRCS[i] == ni.crc) { + cio_cqueue(slot, CIO_STAT, NIQESIZE, &cqe, app_data); + pumped = TRUE; + break; + } + } + + if (!pumped) { + cio_cexpress(slot, NIQESIZE, &cqe, app_data); } /* Now clear out the old CRC value, in case the card needs to be @@ -629,7 +646,7 @@ void ni_sysgen(uint8 cid) /* * Handler for CIO INT0 (express job) requests. */ -void ni_express(uint8 cid) +void ni_express(uint8 slot) { cio_entry rqe = {0}; uint8 app_data[4] = {0}; @@ -637,14 +654,14 @@ void ni_express(uint8 cid) sim_debug(DBG_TRACE, &ni_dev, "[ni_express] Handling express CIO request.\n"); - cio_rexpress(cid, NIQESIZE, &rqe, app_data); - ni_cmd(cid, &rqe, app_data, TRUE); + cio_rexpress(slot, NIQESIZE, &rqe, app_data); + ni_cmd(slot, &rqe, app_data, TRUE); } /* * Handler for CIO INT1 (full job) requests. */ -void ni_full(uint8 cid) +void ni_full(uint8 slot) { cio_entry rqe = {0}; uint8 app_data[4] = {0}; @@ -652,89 +669,49 @@ void ni_full(uint8 cid) sim_debug(DBG_TRACE, &ni_dev, "[ni_full] INT1 received. Handling full CIO request.\n"); - while (cio_cqueue_avail(cid, NIQESIZE) && - cio_rqueue(cid, GE_QUEUE, NIQESIZE, &rqe, app_data) == SCPE_OK) { - ni_cmd(cid, &rqe, app_data, FALSE); + while (cio_cqueue_avail(slot, NIQESIZE) && + cio_rqueue(slot, GE_QUEUE, NIQESIZE, &rqe, app_data) == SCPE_OK) { + ni_cmd(slot, &rqe, app_data, FALSE); } } /* * Handler for CIO RESET requests. */ -void ni_cio_reset(uint8 cid) +void ni_cio_reset(uint8 slot) { - UNUSED(cid); + UNUSED(slot); ni_disable(); } -t_stat ni_autoconfig() -{ - uint8 cid; - - /* Clear the CIO table of NI cards */ - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == NI_ID) { - cio[cid].id = 0; - cio[cid].ipl = 0; - cio[cid].ivec = 0; - cio[cid].exp_handler = NULL; - cio[cid].full_handler = NULL; - cio[cid].reset_handler = NULL; - cio[cid].sysgen = NULL; - } - } - - /* Find the first avaialable slot */ - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == 0) { - break; - } - } - - /* Do we have room? */ - if (cid >= CIO_SLOTS) { - return SCPE_NXM; - } - - /* Remember the card slot */ - ni.cid = cid; - - /* Set up the ni structure */ - cio[cid].id = NI_ID; - cio[cid].ipl = NI_IPL; - cio[cid].exp_handler = &ni_express; - cio[cid].full_handler = &ni_full; - cio[cid].reset_handler = &ni_cio_reset; - cio[cid].sysgen = &ni_sysgen; - - return SCPE_OK; -} - t_stat ni_reset(DEVICE *dptr) { - t_stat status; + t_stat r; + uint8 slot; char uname[16]; - sim_debug(DBG_TRACE, &ni_dev, - "[ni_reset] Resetting NI device\n"); - - /* Initial setup that should only ever be done once. */ - if (!(dptr->flags & DEV_DIS) && !ni.initialized) { - /* Autoconfiguration will select the correct backplane slot - * for the device, and enable CIO routines. This should only - * be done once. */ - status = ni_autoconfig(); - if (status != SCPE_OK) { - return status; - } - - /* Set an initial MAC address in the AT&T NI range */ - ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL); - - ni.initialized = TRUE; + if (dptr->flags & DEV_DIS) { + cio_remove_all(NI_ID); + ni_conf = FALSE; + return SCPE_OK; } + if (!ni_conf) { + r = cio_install(NI_ID, "NI", NI_IPL, + &ni_express, &ni_full, &ni_sysgen, &ni_cio_reset, + &slot); + if (r != SCPE_OK) { + return r; + } + /* Remember the card slot */ + ni.slot = slot; + ni_conf = TRUE; + } + + /* Set an initial MAC address in the AT&T NI range */ + ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL); + /* Set up unit names */ snprintf(uname, 16, "%s-RCV", dptr->name); sim_set_uname(rcv_unit, uname); @@ -756,9 +733,6 @@ t_stat ni_reset(DEVICE *dptr) * polling activity and interrupts are disabled. */ ni_disable(); - /* We make no attempt to autoconfig until the device - * is attached. */ - return SCPE_OK; } @@ -798,16 +772,16 @@ t_stat ni_rq_svc(UNIT *uptr) UNUSED(uptr); rq_taken = FALSE; - no_rque = cio[ni.cid].no_rque - 1; + no_rque = cio[ni.slot].no_rque - 1; for (i = 0; i < no_rque; i++) { - while (NI_CACHE_HAS_SPACE(i) && cio_rqueue(ni.cid, i+1, NIQESIZE, &rqe, slot) == SCPE_OK) { + while (NI_CACHE_HAS_SPACE(i) && cio_rqueue(ni.slot, i+1, NIQESIZE, &rqe, slot) == SCPE_OK) { sim_debug(DBG_CACHE, &ni_dev, "[cache - FILL] %s packet entry. lp=%02x ulp=%02x " "slot=%d addr=0x%08x\n", i == 0 ? "Small" : "Large", - cio_r_lp(ni.cid, i+1, NIQESIZE), - cio_r_ulp(ni.cid, i+1, NIQESIZE), + cio_r_lp(ni.slot, i+1, NIQESIZE), + cio_r_ulp(ni.slot, i+1, NIQESIZE), slot[3], rqe.address); wp = ni.job_cache[i].wp; ni.job_cache[i].req[wp].addr = rqe.address; @@ -835,11 +809,7 @@ t_stat ni_rq_svc(UNIT *uptr) if (ni.poll_rate == NI_QPOLL_FAST) { sim_activate_abs(rq_unit, NI_QPOLL_FAST); } else { - if (sim_idle_enab) { - sim_clock_coschedule(rq_unit, tmxr_poll); - } else { - sim_activate_abs(rq_unit, NI_QPOLL_SLOW); - } + sim_clock_coschedule(rq_unit, 1000); } return SCPE_OK; @@ -867,11 +837,9 @@ t_stat ni_sanity_svc(UNIT *uptr) "[ni_sanity_svc] Firing sanity timer.\n"); cqe.opcode = NI_SANITY; - cio_cqueue(ni.cid, CIO_STAT, NIQESIZE, &cqe, app_data); + cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, &cqe, app_data); - if (cio[ni.cid].ivec > 0) { - CIO_SET_INT(ni.cid); - } + CIO_SET_INT(ni.slot); sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); @@ -882,11 +850,9 @@ t_stat ni_cio_svc(UNIT *uptr) { UNUSED(uptr); - if (cio[ni.cid].ivec > 0) { - sim_debug(DBG_TRACE, &ni_dev, - "[ni_cio_svc] Handling a CIO service (Setting Interrupt) for board %d\n", ni.cid); - CIO_SET_INT(ni.cid); - } + sim_debug(DBG_TRACE, &ni_dev, + "[ni_cio_svc] Handling a CIO service (Setting Interrupt) for board %d\n", ni.slot); + CIO_SET_INT(ni.slot); return SCPE_OK; } @@ -924,13 +890,13 @@ void ni_process_packet() "[cache - DRAIN] %s packet entry. lp=%02x ulp=%02x " "slot=%d addr=0x%08x\n", que_num == 0 ? "Small" : "Large", - cio_r_lp(ni.cid, que_num+1, NIQESIZE), - cio_r_ulp(ni.cid, que_num+1, NIQESIZE), + cio_r_lp(ni.slot, que_num+1, NIQESIZE), + cio_r_ulp(ni.slot, que_num+1, NIQESIZE), slot, addr); /* Store the packet into main memory */ for (i = 0; i < len; i++) { - pwrite_b(addr + i, rbuf[i]); + pwrite_b(addr + i, rbuf[i], BUS_PER); } if (ni_dev.dctrl & DBG_DAT) { @@ -948,12 +914,10 @@ void ni_process_packet() capp_data[3] = slot; /* TODO: We should probably also check status here. */ - cio_cqueue(ni.cid, CIO_STAT, NIQESIZE, ¢ry, capp_data); + cio_cqueue(ni.slot, CIO_STAT, NIQESIZE, ¢ry, capp_data); /* Trigger an interrupt */ - if (cio[ni.cid].ivec > 0) { - CIO_SET_INT(ni.cid); - } + CIO_SET_INT(ni.slot); } t_stat ni_attach(UNIT *uptr, CONST char *cptr) @@ -1155,137 +1119,3 @@ const char *ni_description(DEVICE *dptr) return "NI 10BASE5 Ethernet controller"; } - -/* - * Useful routines for debugging request and completion queues - */ - -static t_stat ni_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ni_show_queue_common(st, uptr, val, desc, TRUE); -} - -static t_stat ni_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ni_show_queue_common(st, uptr, val, desc, FALSE); -} - -static t_stat ni_show_queue_common(FILE *st, UNIT *uptr, int32 val, - CONST void *desc, t_bool rq) -{ - uint8 cid; - char *cptr = (char *) desc; - t_stat result; - uint32 ptr, size, no_rque, i, j; - uint16 lp, ulp; - uint8 op, dev, seq, cmdstat; - - UNUSED(uptr); - UNUSED(val); - - if (cptr) { - cid = (uint8) get_uint(cptr, 10, 12, &result); - if (result != SCPE_OK) { - return SCPE_ARG; - } - } else { - return SCPE_ARG; - } - - /* If the card is not sysgen'ed, give up */ - if (cio[cid].sysgen_s != CIO_SYSGEN) { - fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid); - return SCPE_ARG; - } - - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Sysgen Block:\n"); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, " Request Queue Pointer: 0x%08x\n", cio[cid].rqp); - fprintf(st, " Completion Queue Pointer: 0x%08x\n", cio[cid].cqp); - fprintf(st, " Request Queue Size: 0x%02x\n", cio[cid].rqs); - fprintf(st, " Completion Queue Size: 0x%02x\n", cio[cid].cqs); - fprintf(st, " Interrupt Vector: %d (0x%02x)\n", cio[cid].ivec, cio[cid].ivec); - fprintf(st, " Number of Request Queues: %d\n", cio[cid].no_rque); - fprintf(st, "---------------------------------------------------------\n"); - - /* Get the top of the queue */ - if (rq) { - ptr = cio[cid].rqp; - size = cio[cid].rqs; - no_rque = cio[cid].no_rque; - } else { - ptr = cio[cid].cqp; - size = cio[cid].cqs; - no_rque = 0; /* Not used */ - } - - if (rq) { - fprintf(st, "Dumping %d Request Queues\n", no_rque); - } else { - fprintf(st, "Dumping Completion Queue\n"); - } - - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "EXPRESS ENTRY:\n"); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2)); - fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3)); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - - if (rq) { - for (i = 0; i < no_rque; i++) { - lp = pread_h(ptr); - ulp = pread_h(ptr + 2); - ptr += 4; - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "REQUEST QUEUE %d\n", i); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: 0x%04x (%d)\n", lp, (lp / NIQESIZE) + 1); - fprintf(st, "Unload Pointer: 0x%04x (%d)\n", ulp, (ulp / NIQESIZE) + 1); - fprintf(st, "---------------------------------------------------------\n"); - for (j = 0; j < size; j++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "REQUEST ENTRY %d (@ 0x%08x)\n", j + 1, ptr); - fprintf(st, " Byte Count: 0x%04x\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - } - } - } else { - lp = pread_h(ptr); - ulp = pread_h(ptr + 2); - ptr += 4; - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: 0x%04x (%d)\n", lp, (lp / NIQESIZE) + 1); - fprintf(st, "Unload Pointer: 0x%04x (%d)\n", ulp, (ulp / NIQESIZE) + 1); - fprintf(st, "---------------------------------------------------------\n"); - for (i = 0; i < size; i++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "COMPLETION ENTRY %d (@ 0x%08x)\n", i + 1, ptr); - fprintf(st, " Byte Count: 0x%04x\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - } - } - - return SCPE_OK; -} diff --git a/3B2/3b2_ni.h b/3B2/3b2_ni.h index b8c169a9..2e146ea8 100644 --- a/3B2/3b2_ni.h +++ b/3B2/3b2_ni.h @@ -1,6 +1,6 @@ -/* 3b2_ni.h: AT&T 3B2 Model 400 "NI" feature card +/* 3b2_ni.h: CM195A Network Interface CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -84,9 +84,6 @@ #define NI_QPOLL_FAST 100 #define NI_QPOLL_SLOW 50000 -#define NI_PUMP_CRC1 0xfab1057c -#define NI_PUMP_CRC2 0xf6744bed - #define EIG_TABLE_SIZE 40 #define PKT_HEADER_LEN_OFFSET EIG_TABLE_SIZE #define PKT_START_OFFSET (PKT_HEADER_LEN_OFFSET + 4) @@ -168,8 +165,7 @@ typedef struct { } ni_stat_info; typedef struct { - uint8 cid; - t_bool initialized; + uint8 slot; t_bool enabled; uint32 crc; uint32 poll_rate; @@ -197,18 +193,18 @@ t_stat ni_attach(UNIT *uptr, CONST char *cptr); t_stat ni_detach(UNIT *uptr); t_stat ni_setmac(UNIT *uptr, int32 val, CONST char *cptr, void *desc); t_stat ni_showmac(FILE* st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ni_try_job(uint8 cid); +t_stat ni_try_job(uint8 slot); t_stat ni_show_stats(FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat ni_set_stats(UNIT *uptr, int32 val, CONST char *cptr, void *desc); t_stat ni_show_poll(FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat ni_show_filters(FILE *st, UNIT *uptr, int32 val, CONST void *desc); const char *ni_description(DEVICE *dptr); t_stat ni_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); -void ni_cio_reset(uint8 cid); +void ni_cio_reset(uint8 slot); void ni_process_packet(); -void ni_int_ack(uint8 cid); -void ni_sysgen(uint8 cid); -void ni_express(uint8 cid); -void ni_full(uint8 cid); +void ni_int_ack(uint8 slot); +void ni_sysgen(uint8 slot); +void ni_express(uint8 slot); +void ni_full(uint8 slot); #endif /* _3B2_NI_H_ */ diff --git a/3B2/3b2_ports.c b/3B2/3b2_ports.c index b77db85b..3b5152c9 100644 --- a/3B2/3b2_ports.c +++ b/3B2/3b2_ports.c @@ -1,6 +1,6 @@ -/* 3b2_ports.c: AT&T 3B2 Model 400 "PORTS" feature card +/* 3b2_ports.c: CM195B 4-Port Serial CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -49,11 +49,9 @@ #include "3b2_cpu.h" #include "3b2_io.h" #include "3b2_mem.h" -#include "3b2_timer.h" +#include "3b2_stddev.h" -/* Static function declarations */ -static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val, - CONST void *desc, t_bool rq); +#define IO_SCHED 1000 /* Device and units for PORTS cards * -------------------------------- @@ -78,6 +76,8 @@ static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val, * */ +#define MAX_LINES 32 + #define PPQESIZE 12 #define DELAY_ASYNC 25 #define DELAY_DLM 100 @@ -100,16 +100,31 @@ static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val, #define PORTS_DIAG_CRC5 0x4be7bccc /* Used by SVR 2.0.5 */ #define PORTS_DIAG_CRC6 0x3197f6dd /* Used by SVR 2.0.5 */ -#define LN(cid,port) ((PORTS_LINES * ((cid) - ports_base_cid)) + (port)) -#define LCID(ln) (((ln) / PORTS_LINES) + ports_base_cid) -#define LPORT(ln) ((ln) % PORTS_LINES) +#define PORTS_DFLT_LINES 4 +#define PORTS_DFLT_CARDS 1 -int8 ports_base_cid; /* First cid in our contiguous block */ -uint8 ports_int_cid; /* Interrupting card ID */ +#define LN(slot,port) (ports_slot_ln[(slot)] + (port)) +#define LSLOT(ln) (ports_ln_slot[ln]) +/* #define LN(slot,port) ((PORTS_LINES * ((slot) - ports_base_slot)) + (port)) */ +/* #define LSLOT(ln) (((ln) / PORTS_LINES) + ports_base_slot) */ +#define LPORT(ln) ((ln) % PORTS_LINES) + +int8 ports_base_slot; /* First slot in our contiguous block */ +uint8 ports_int_slot; /* Interrupting card ID */ uint8 ports_int_subdev; /* Interrupting subdevice */ t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */ uint32 ports_crc; /* CRC32 of downloaded memory */ +/* Mapping of line number to CIO card slot. Up to 32 lines spread over + 8 slots are supported. */ +uint8 ports_ln_slot[MAX_LINES]; + +/* Mapping of slot number to base line number belonging to the card in + that slot. I.e., if there are two PORTS cards, one in slot 3 and + one in slot 5, index 3 will have starting line 0, index 5 will have + starting line 4. */ +uint32 ports_slot_ln[CIO_SLOTS]; + /* PORTS-specific state for each slot */ PORTS_LINE_STATE *ports_state = NULL; @@ -136,12 +151,8 @@ MTAB ports_mod[] = { { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL, "7 bit mode" }, { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL, "8 bit mode" }, { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL, "7 bit mode - non printing suppressed" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "RQUEUE=n", NULL, - NULL, &ports_show_rqueue, NULL, "Display Request Queue for card n" }, - { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL, - NULL, &ports_show_cqueue, NULL, "Display Completion Queue for card n" }, { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n", - &ports_setnl, &tmxr_show_lines, (void *) &ports_desc, "Display number of lines" }, + &ports_setnl, &tmxr_show_lines, (void *) &ports_desc, "Show or set number of lines" }, { 0 } }; @@ -189,9 +200,9 @@ DEVICE ports_dev = { }; -static void cio_irq(uint8 cid, uint8 dev, int32 delay) +static void cio_irq(uint8 slot, uint8 dev, int32 delay) { - ports_int_cid = cid; + ports_int_slot = slot; ports_int_subdev = dev & 0xf; sim_activate(&ports_unit[2], delay); } @@ -209,16 +220,20 @@ t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) return SCPE_ARG; } - newln = (int32) get_uint(cptr, 10, (MAX_PORTS_CARDS * PORTS_LINES), &r); + newln = (int32) get_uint(cptr, 10, (MAX_CARDS * PORTS_LINES), &r); if ((r != SCPE_OK) || (newln == ports_desc.lines)) { return r; } - if ((newln == 0) || LPORT(newln) != 0) { + if ((newln == 0) || LPORT(newln) != 0 || newln > MAX_LINES) { return SCPE_ARG; } + sim_debug(TRACE_DBG, &ports_dev, + "[ports_setnl] Setting line count to %d\n", + newln); + if (newln < ports_desc.lines) { for (i = newln, t = 0; i < ports_desc.lines; i++) { t = t | ports_ldsc[i].conn; @@ -235,10 +250,6 @@ t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) } /* completely reset line */ tmxr_detach_ln(&ports_ldsc[i]); - if (LPORT(i) == (PORTS_LINES - 1)) { - /* Also drop the corresponding card from the CIO array */ - cio_clear(LCID(i)); - } } } @@ -258,7 +269,7 @@ t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) } -static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) +static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data) { cio_entry centry = {0}; uint32 ln, i; @@ -267,37 +278,35 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) uint8 app_data[4] = {0}; centry.address = rentry->address; - cio[cid].op = rentry->opcode; - ln = LN(cid, rentry->subdevice & 0xf); + cio[slot].op = rentry->opcode; + ln = LN(slot, rentry->subdevice & 0xf); switch(rentry->opcode) { case CIO_DLM: for (i = 0; i < rentry->byte_count; i++) { - ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i)); + ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i, BUS_PER)); } centry.address = rentry->address + rentry->byte_count; sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] CIO Download Memory: bytecnt=%04x " + "[ports_cmd] CIO Download Memory: bytecnt=%04x " "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", - R[NUM_PC], rentry->byte_count, rentry->address, centry.address, centry.subdevice, ports_crc); /* We intentionally do not set the subdevice in * the completion entry */ - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_DLM); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DLM); break; case CIO_ULM: sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] CIO Upload Memory\n", - R[NUM_PC]); - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_ULM); + "[ports_cmd] CIO Upload Memory\n"); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_ULM); break; case CIO_FCF: sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] CIO Force Function Call (CRC=%08x)\n", - R[NUM_PC], ports_crc); + "[ports_cmd] CIO Force Function Call (CRC=%08x)\n", + ports_crc); /* If the currently running program is a diagnostics program, * we are expected to write results into memory at address @@ -308,11 +317,11 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) ports_crc == PORTS_DIAG_CRC4 || ports_crc == PORTS_DIAG_CRC5 || ports_crc == PORTS_DIAG_CRC6) { - pwrite_h(0x200f000, 0x1); /* Test success */ - pwrite_h(0x200f002, 0x0); /* Test Number */ - pwrite_h(0x200f004, 0x0); /* Actual */ - pwrite_h(0x200f006, 0x0); /* Expected */ - pwrite_b(0x200f008, 0x1); /* Success flag again */ + pwrite_h(0x200f000, 0x1, BUS_PER); /* Test success */ + pwrite_h(0x200f002, 0x0, BUS_PER); /* Test Number */ + pwrite_h(0x200f004, 0x0, BUS_PER); /* Actual */ + pwrite_h(0x200f006, 0x0, BUS_PER); /* Expected */ + pwrite_b(0x200f008, 0x1, BUS_PER); /* Success flag again */ } /* An interesting (?) side-effect of FORCE FUNCTION CALL is @@ -320,49 +329,46 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) * required in order for new commands to work. In fact, an * INT0/INT1 combo _without_ a RESET can sysgen the board. So, * we reset the command bits here. */ - cio[cid].sysgen_s = 0; - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_FCF); + cio[slot].sysgen_s = 0; + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_FCF); break; case CIO_DOS: sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] CIO Determine Op Status\n", - R[NUM_PC]); - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_DOS); + "[ports_cmd] CIO Determine Op Status\n"); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DOS); break; case CIO_DSD: /* Determine Sub-Devices. We have none. */ sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] Determine Sub-Devices.\n", - R[NUM_PC]); + "[ports_cmd] Determine Sub-Devices.\n"); /* The system wants us to write sub-device structures * at the supplied address */ - pwrite_h(rentry->address, 0x0); - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_DSD); + pwrite_h(rentry->address, 0x0, BUS_PER); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DSD); break; case PPC_OPTIONS: sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC Options Operation\n", - R[NUM_PC]); + "[ports_cmd] PPC Options Operation\n"); - opts.line = pread_h(rentry->address); - opts.iflag = pread_h(rentry->address + 4); - opts.oflag = pread_h(rentry->address + 6); - opts.cflag = pread_h(rentry->address + 8); - opts.lflag = pread_h(rentry->address + 10); - opts.cerase = pread_b(rentry->address + 11); - opts.ckill = pread_b(rentry->address + 12); - opts.cinter = pread_b(rentry->address + 13); - opts.cquit = pread_b(rentry->address + 14); - opts.ceof = pread_b(rentry->address + 15); - opts.ceol = pread_b(rentry->address + 16); - opts.itime = pread_b(rentry->address + 17); - opts.vtime = pread_b(rentry->address + 18); - opts.vcount = pread_b(rentry->address + 19); + opts.line = pread_h(rentry->address, BUS_PER); + opts.iflag = pread_h(rentry->address + 4, BUS_PER); + opts.oflag = pread_h(rentry->address + 6, BUS_PER); + opts.cflag = pread_h(rentry->address + 8, BUS_PER); + opts.lflag = pread_h(rentry->address + 10, BUS_PER); + opts.cerase = pread_b(rentry->address + 11, BUS_PER); + opts.ckill = pread_b(rentry->address + 12, BUS_PER); + opts.cinter = pread_b(rentry->address + 13, BUS_PER); + opts.cquit = pread_b(rentry->address + 14, BUS_PER); + opts.ceof = pread_b(rentry->address + 15, BUS_PER); + opts.ceol = pread_b(rentry->address + 16, BUS_PER); + opts.itime = pread_b(rentry->address + 17, BUS_PER); + opts.vtime = pread_b(rentry->address + 18, BUS_PER); + opts.vcount = pread_b(rentry->address + 19, BUS_PER); sim_debug(TRACE_DBG, &ports_dev, " PPC Options: iflag=%04x\n", opts.iflag); sim_debug(TRACE_DBG, &ports_dev, " PPC Options: oflag=%04x\n", opts.oflag); @@ -391,46 +397,45 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) centry.opcode = PPC_OPTIONS; centry.subdevice = rentry->subdevice; centry.address = rentry->address; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_OPTIONS); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_OPTIONS); break; case PPC_VERS: sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC Version\n", - R[NUM_PC]); + "[ports_cmd] PPC Version\n"); /* Write the version number at the supplied address */ - pwrite_b(rentry->address, PORTS_VERSION); + pwrite_b(rentry->address, PORTS_VERSION, BUS_PER); centry.opcode = CIO_ULM; /* TODO: It's unknown what the value 0x50 means, but this * is what a real board sends. */ app_data[0] = 0x50; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_VERS); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_VERS); break; case PPC_CONN: /* CONNECT - Full request and completion queues */ sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC CONNECT - subdevice = %02x\n", - R[NUM_PC], rentry->subdevice); + "[ports_cmd] PPC CONNECT - subdevice = %02x\n", + rentry->subdevice); ports_state[ln].conn = TRUE; centry.opcode = PPC_CONN; centry.subdevice = rentry->subdevice; centry.address = rentry->address; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_CONN); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_CONN); break; case PPC_XMIT: /* XMIT - Full request and completion queues */ /* The port being referred to is in the subdevice. */ sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n", - R[NUM_PC], rentry->subdevice, rentry->address, rentry->byte_count); + "[ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n", + rentry->subdevice, rentry->address, rentry->byte_count); /* Set state for xmit */ ports_state[ln].tx_addr = rentry->address; @@ -445,20 +450,20 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) /* DEVICE Control - Express request and completion queues */ /* The port being referred to is in the subdevice. */ sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC DEVICE - subdevice = %02x\n", - R[NUM_PC], rentry->subdevice); + "[ports_cmd] PPC DEVICE - subdevice = %02x\n", + rentry->subdevice); centry.subdevice = rentry->subdevice; centry.opcode = PPC_DEVICE; - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_DEVICE); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_DEVICE); break; case PPC_RECV: /* RECV - Full request and completion queues */ /* The port being referred to is in the subdevice. */ sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_cmd] PPC RECV - subdevice = %02x addr=%08x\n", - R[NUM_PC], rentry->subdevice, rentry->address); + "[ports_cmd] PPC RECV - subdevice = %02x addr=%08x\n", + rentry->subdevice, rentry->address); break; case PPC_DISC: @@ -466,8 +471,8 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) centry.subdevice = rentry->subdevice; centry.opcode = PPC_DISC; ports_ldsc[ln].rcve = 0; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_STD); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_STD); break; case PPC_BRK: case PPC_CLR: @@ -476,8 +481,8 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) ">>> Op %d Not Handled Yet\n", rentry->opcode); - cio_cexpress(cid, PPQESIZE, ¢ry, app_data); - cio_irq(cid, rentry->subdevice, DELAY_STD); + cio_cexpress(slot, PPQESIZE, ¢ry, app_data); + cio_irq(slot, rentry->subdevice, DELAY_STD); break; } } @@ -488,14 +493,14 @@ static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data) static void ports_update_conn(uint32 ln) { cio_entry centry = {0}; - uint8 cid; + uint8 slot; uint8 app_data[4] = {0}; - cid = LCID(ln); + slot = LSLOT(ln); /* If the card hasn't sysgened, there's no way to write a * completion queue entry */ - if (cio[cid].sysgen_s != CIO_SYSGEN) { + if (cio[slot].sysgen_s != CIO_SYSGEN) { return; } @@ -513,15 +518,13 @@ static void ports_update_conn(uint32 ln) centry.opcode = PPC_ASYNC; centry.subdevice = LPORT(ln); - cio_cqueue(cid, CIO_CMD, PPQESIZE, ¢ry, app_data); + cio_cqueue(slot, CIO_CMD, PPQESIZE, ¢ry, app_data); /* Interrupt */ - if (cio[cid].ivec > 0) { - CIO_SET_INT(cid); - } + CIO_SET_INT(slot); } -void ports_sysgen(uint8 cid) +void ports_sysgen(uint8 slot) { cio_entry cqe = {0}; uint8 app_data[4] = {0}; @@ -532,147 +535,98 @@ void ports_sysgen(uint8 cid) /* It's not clear why we put a response in both the express * and the full queue. */ - cio_cexpress(cid, PPQESIZE, &cqe, app_data); - cio_cqueue(cid, CIO_STAT, PPQESIZE, &cqe, app_data); + cio_cexpress(slot, PPQESIZE, &cqe, app_data); + cio_cqueue(slot, CIO_STAT, PPQESIZE, &cqe, app_data); - ports_int_cid = cid; + ports_int_slot = slot; sim_activate(&ports_unit[2], DELAY_STD); } -void ports_express(uint8 cid) +void ports_express(uint8 slot) { cio_entry rqe = {0}; uint8 app_data[4] = {0}; - cio_rexpress(cid, PPQESIZE, &rqe, app_data); - ports_cmd(cid, &rqe, app_data); + cio_rexpress(slot, PPQESIZE, &rqe, app_data); + ports_cmd(slot, &rqe, app_data); } -void ports_full(uint8 cid) +void ports_full(uint8 slot) { uint32 i; cio_entry rqe = {0}; uint8 app_data[4] = {0}; for (i = 0; i < PORTS_LINES; i++) { - if (cio_rqueue(cid, i, PPQESIZE, &rqe, app_data) == SCPE_OK) { - ports_cmd(cid, &rqe, app_data); + if (cio_rqueue(slot, i, PPQESIZE, &rqe, app_data) == SCPE_OK) { + ports_cmd(slot, &rqe, app_data); } } } t_stat ports_reset(DEVICE *dptr) { - int32 i; - uint8 cid, line, ln, end_slot; - TMLN *lp; + int32 i, j; + uint8 slot; + t_stat r; ports_crc = 0; - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] Resetting PORTS device\n"); + if (ports_ldsc == NULL) { + sim_set_uname(&ports_unit[0], "PORTS-RCV"); + sim_set_uname(&ports_unit[1], "PORTS-XMT"); + sim_set_uname(&ports_unit[2], "PORTS-CIO"); + + ports_desc.lines = PORTS_DFLT_LINES; + ports_desc.ldsc = ports_ldsc = + (TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc)); + } + + if (ports_state == NULL) { + ports_state = (PORTS_LINE_STATE *)calloc(ports_desc.lines, sizeof(*ports_state)); + memset(ports_state, 0, ports_desc.lines*sizeof(*ports_state)); + } + + tmxr_set_port_speed_control(&ports_desc); if ((dptr->flags & DEV_DIS)) { - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == PORTS_ID) { - cio[cid].id = 0; - cio[cid].ipl = 0; - cio[cid].ivec = 0; - cio[cid].exp_handler = NULL; - cio[cid].full_handler = NULL; - cio[cid].sysgen = NULL; - } - } - + cio_remove_all(PORTS_ID); ports_conf = FALSE; - } else if (!ports_conf) { + return SCPE_OK; + } + if (!ports_conf) { /* Clear out any old cards, we're starting fresh */ - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == PORTS_ID) { - cio[cid].id = 0; - cio[cid].ipl = 0; - cio[cid].ivec = 0; - cio[cid].exp_handler = NULL; - cio[cid].full_handler = NULL; - cio[cid].sysgen = NULL; + cio_remove_all(PORTS_ID); + + memset(ports_slot_ln, 0, sizeof(ports_slot_ln)); + memset(ports_ln_slot, 0, sizeof(ports_ln_slot)); + + /* Insert necessary cards into the backplane */ + j = 0; + for (i = 0; i < ports_desc.lines/PORTS_LINES; i++) { + r = cio_install(PORTS_ID, "PORTS", PORTS_IPL, + &ports_express, &ports_full, &ports_sysgen, NULL, + &slot); + if (r != SCPE_OK) { + return r; } - } - - /* Find the first avaialable slot */ - for (cid = 0; cid < CIO_SLOTS; cid++) { - if (cio[cid].id == 0) { - break; - } - } - - /* Do we have room? */ - if (cid >= CIO_SLOTS || cid > (CIO_SLOTS - (ports_desc.lines/PORTS_LINES))) { - return SCPE_NXM; - } - - /* Remember the base card slot */ - ports_base_cid = cid; - - end_slot = (cid + (ports_desc.lines/PORTS_LINES)); - - for (; cid < end_slot; cid++) { - /* Set up the ports structure */ - cio[cid].id = PORTS_ID; - cio[cid].ipl = PORTS_IPL; - cio[cid].exp_handler = &ports_express; - cio[cid].full_handler = &ports_full; - cio[cid].sysgen = &ports_sysgen; - - for (line = 0; line < PORTS_LINES; line++) { - ln = LN(cid, line); - - sim_debug(TRACE_DBG, &ports_dev, - ">>> Setting up lp %d (card %d, line %d)\n", - ln, cid, line); - - lp = &ports_ldsc[ln]; - tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + /* Remember the port assignments */ + ports_slot_ln[slot] = i * PORTS_LINES; + for (; j < (i * PORTS_LINES) + PORTS_LINES; j++) { + ports_ln_slot[j] = slot; } } ports_conf = TRUE; - - if (ports_ldsc == NULL) { - ports_desc.ldsc = ports_ldsc = - (TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc)); - } - - if (ports_state == NULL) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] calloc for ports_state...\n"); - ports_state = (PORTS_LINE_STATE *)calloc(ports_desc.lines, sizeof(*ports_state)); - } - - memset(ports_state, 0, ports_desc.lines*sizeof(*ports_state)); - - tmxr_set_port_speed_control(&ports_desc); - - for (i = 0; i < ports_desc.lines; i++) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] Setting up line %d...\n", i); - tmxr_set_line_unit(&ports_desc, i, &ports_unit[0]); - tmxr_set_line_output_unit(&ports_desc, i, &ports_unit[1]); - if (!ports_ldsc[i].conn) { - ports_ldsc[i].xmte = 1; - } - ports_ldsc[i].rcve = 0; - tmxr_set_config_line(&ports_ldsc[i], "9600-8N1"); - } } - if (!sim_is_active(&ports_unit[0])) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] starting receive polling...\n"); - sim_activate(&ports_unit[0], ports_unit[0].wait); + /* If attached, start polling for connections */ + if (ports_unit[0].flags & UNIT_ATT) { + sim_activate_after_abs(&ports_unit[0], ports_unit[0].wait); + } else { + sim_cancel(&ports_unit[0]); } - sim_debug(TRACE_DBG, &ports_dev, - "[ports_reset] returning scpe_ok\n"); return SCPE_OK; } @@ -680,20 +634,18 @@ t_stat ports_cio_svc(UNIT *uptr) { sim_debug(TRACE_DBG, &ports_dev, "[ports_cio_svc] IRQ for board %d device %d\n", - ports_int_cid, ports_int_subdev); + ports_int_slot, ports_int_subdev); - if (cio[ports_int_cid].ivec > 0) { - CIO_SET_INT(ports_int_cid); - } + CIO_SET_INT(ports_int_slot); - switch (cio[ports_int_cid].op) { + switch (cio[ports_int_slot].op) { case PPC_CONN: - cio[ports_int_cid].op = PPC_ASYNC; - ports_ldsc[LN(ports_int_cid, ports_int_subdev)].rcve = 1; + cio[ports_int_slot].op = PPC_ASYNC; + ports_ldsc[LN(ports_int_slot, ports_int_subdev)].rcve = 1; sim_activate(&ports_unit[2], DELAY_ASYNC); break; case PPC_ASYNC: - ports_update_conn(LN(ports_int_cid, ports_int_subdev)); + ports_update_conn(LN(ports_int_slot, ports_int_subdev)); break; default: break; @@ -704,7 +656,7 @@ t_stat ports_cio_svc(UNIT *uptr) t_stat ports_rcv_svc(UNIT *uptr) { - uint8 cid; + uint8 slot; int32 temp, ln; char c; cio_entry rentry = {0}; @@ -721,10 +673,8 @@ t_stat ports_rcv_svc(UNIT *uptr) ports_update_conn(ln); } - tmxr_poll_rx(&ports_desc); - for (ln = 0; ln < ports_desc.lines; ln++) { - cid = LCID(ln); + slot = LSLOT(ln); if (!ports_ldsc[ln].conn && ports_state[ln].conn) { ports_update_conn(ln); @@ -743,24 +693,27 @@ t_stat ports_rcv_svc(UNIT *uptr) c = 0xa; } - if (cio[cid].ivec > 0 && - cio_rqueue(cid, PORTS_RCV_QUEUE, + if (cio[slot].ivec > 0 && + cio_rqueue(slot, PORTS_RCV_QUEUE, PPQESIZE, &rentry, rapp_data) == SCPE_OK) { - CIO_SET_INT(cid); + CIO_SET_INT(slot); /* Write the character to the memory address */ - pwrite_b(rentry.address, c); + pwrite_b(rentry.address, c, BUS_PER); centry.subdevice = LPORT(ln); centry.opcode = PPC_RECV; centry.address = rentry.address; capp_data[3] = RC_TMR; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, capp_data); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, capp_data); } } } } + tmxr_poll_rx(&ports_desc); + tmxr_poll_tx(&ports_desc); + tmxr_clock_coschedule(uptr, tmxr_poll); return SCPE_OK; @@ -768,7 +721,7 @@ t_stat ports_rcv_svc(UNIT *uptr) t_stat ports_xmt_svc(UNIT *uptr) { - uint8 cid, ln; + uint8 slot, ln; char c; t_bool tx = FALSE; /* Did a tx ever occur? */ cio_entry centry = {0}; @@ -777,10 +730,10 @@ t_stat ports_xmt_svc(UNIT *uptr) /* Scan all lines for output */ for (ln = 0; ln < ports_desc.lines; ln++) { - cid = LCID(ln); + slot = LSLOT(ln); if (ports_ldsc[ln].conn && ports_state[ln].tx_chars > 0) { tx = TRUE; /* Even an attempt at TX counts for rescheduling */ - c = sim_tt_outcvt(pread_b(ports_state[ln].tx_addr), + c = sim_tt_outcvt(pread_b(ports_state[ln].tx_addr, BUS_PER), TT_GET_MODE(ports_unit[0].flags)); /* The PORTS card optionally handles NL->CRLF */ @@ -790,8 +743,8 @@ t_stat ports_xmt_svc(UNIT *uptr) if (tmxr_putc_ln(&ports_ldsc[ln], 0xd) == SCPE_OK) { wait = MIN(wait, ports_ldsc[ln].txdeltausecs); sim_debug(IO_DBG, &ports_dev, - "[%08x] [ports_xmt_svc] [LINE %d] XMIT (crlf): %02x (%c)\n", - R[NUM_PC], ln, 0xd, 0xd); + "[ports_xmt_svc] [LINE %d] XMIT (crlf): %02x (%c)\n", + ln, 0xd, 0xd); /* Indicate that we're in a CRLF translation */ ports_state[ln].crlf = TRUE; } @@ -806,21 +759,21 @@ t_stat ports_xmt_svc(UNIT *uptr) ports_state[ln].tx_chars--; ports_state[ln].tx_addr++; sim_debug(IO_DBG, &ports_dev, - "[%08x] [ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n", - R[NUM_PC], ln, c, c); + "[ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n", + ln, c, c); } if (ports_state[ln].tx_chars == 0) { sim_debug(TRACE_DBG, &ports_dev, - "[%08x] [ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n", - R[NUM_PC], cid, LPORT(ln)); + "[ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n", + slot, LPORT(ln)); centry.byte_count = ports_state[ln].tx_req_chars; centry.subdevice = LPORT(ln); centry.opcode = PPC_XMIT; centry.address = ports_state[ln].tx_req_addr; app_data[0] = RC_FLU; - cio_cqueue(cid, CIO_STAT, PPQESIZE, ¢ry, app_data); - CIO_SET_INT(cid); + cio_cqueue(slot, CIO_STAT, PPQESIZE, ¢ry, app_data); + CIO_SET_INT(slot); } } } @@ -836,11 +789,24 @@ t_stat ports_xmt_svc(UNIT *uptr) t_stat ports_attach(UNIT *uptr, CONST char *cptr) { + TMLN *lp; t_stat r; + int i; - sim_debug(TRACE_DBG, &ports_dev, "ports_attach()\n"); + if ((sim_switches & SWMASK('M'))) { + tmxr_set_modem_control_passthru(&ports_desc); + } - tmxr_set_modem_control_passthru(&ports_desc); + for (i = 0; i < ports_desc.lines; i++) { + sim_debug(TRACE_DBG, &ports_dev, + "[ports_reset] Setting up line %d...\n", i); + tmxr_set_line_output_unit(&ports_desc, i, &ports_unit[1]); + if (!ports_ldsc[i].conn) { + ports_ldsc[i].xmte = 1; + } + ports_ldsc[i].rcve = 0; + tmxr_set_config_line(&ports_ldsc[i], "9600-8N1"); + } r = tmxr_attach(&ports_desc, uptr, cptr); if (r != SCPE_OK) { @@ -848,6 +814,11 @@ t_stat ports_attach(UNIT *uptr, CONST char *cptr) return r; } + for (i = 0; i < ports_desc.lines; i++) { + lp = &ports_ldsc[i]; + tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); + } + return SCPE_OK; } @@ -861,129 +832,7 @@ t_stat ports_detach(UNIT *uptr) return r; } - if (sim_is_active(&ports_unit[0])) { - sim_debug(TRACE_DBG, &ports_dev, - "[ports_detach] Stopping receive polling...\n"); - sim_cancel(&ports_unit[0]); - } - tmxr_clear_modem_control_passthru(&ports_desc); return SCPE_OK; } - -/* - * Useful routines for debugging request and completion queues - */ - -t_stat ports_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ports_show_queue_common(st, uptr, val, desc, TRUE); -} - -t_stat ports_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc) -{ - return ports_show_queue_common(st, uptr, val, desc, FALSE); -} - - -static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val, - CONST void *desc, t_bool rq) -{ - uint8 cid; - char *cptr = (char *) desc; - t_stat result; - uint32 ptr, size, no_rque, i, j; - uint8 op, dev, seq, cmdstat; - - if (cptr) { - cid = (uint8) get_uint(cptr, 10, 12, &result); - if (result != SCPE_OK) { - return SCPE_ARG; - } - } else { - return SCPE_ARG; - } - - /* If the card is not sysgen'ed, give up */ - if (cio[cid].sysgen_s != CIO_SYSGEN) { - fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid); - return SCPE_ARG; - } - - /* Get the top of the queue */ - if (rq) { - ptr = cio[cid].rqp; - size = cio[cid].rqs; - no_rque = cio[cid].no_rque; - } else { - ptr = cio[cid].cqp; - size = cio[cid].cqs; - no_rque = 0; /* Not used */ - } - - if (rq) { - fprintf(st, "Dumping %d Request Queues\n", no_rque); - } else { - fprintf(st, "Dumping Completion Queue\n"); - } - - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "EXPRESS ENTRY:\n"); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2)); - fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3)); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - - if (rq) { - for (i = 0; i < no_rque; i++) { - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "REQUEST QUEUE %d\n", i); - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / 12); - fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / 12); - fprintf(st, "---------------------------------------------------------\n"); - ptr += 4; - for (j = 0; j < size; j++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "REQUEST ENTRY %d\n", j); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - } - } - } else { - fprintf(st, "---------------------------------------------------------\n"); - fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / 12); - fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / 12); - fprintf(st, "---------------------------------------------------------\n"); - ptr += 4; - for (i = 0; i < size; i++) { - dev = pread_b(ptr + 2); - op = pread_b(ptr + 3); - seq = (dev & 0x40) >> 6; - cmdstat = (dev & 0x80) >> 7; - fprintf(st, "COMPLETION ENTRY %d\n", i); - fprintf(st, " Byte Count: %d\n", pread_h(ptr)); - fprintf(st, " Subdevice: %d\n", dev & 0x3f); - fprintf(st, " Cmd/Stat: %d\n", cmdstat); - fprintf(st, " Seqbit: %d\n", seq); - fprintf(st, " Opcode: 0x%02x (%d)\n", op, op); - fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4)); - fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8)); - ptr += 12; - } - } - - return SCPE_OK; -} diff --git a/3B2/3b2_ports.h b/3B2/3b2_ports.h index fa9302b0..2e5d7114 100644 --- a/3B2/3b2_ports.h +++ b/3B2/3b2_ports.h @@ -1,6 +1,6 @@ -/* 3b2_ports.h: AT&T 3B2 Model 400 "PORTS" feature card +/* 3b2_ports.h: CM195B 4-Port Serial CIO Card - Copyright (c) 2018, Seth J. Morabito + Copyright (c) 2018-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -51,7 +51,8 @@ #define PORTS_IPL 10 #define PORTS_VERSION 1 -#define MAX_PORTS_CARDS 12 +#define MAX_CARDS 8 /* Up to 8 PORTS cards with 32 lines total + supported */ #define PORTS_LINES 4 #define PORTS_RCV_QUEUE 5 @@ -216,15 +217,13 @@ typedef struct { t_stat ports_reset(DEVICE *dptr); t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -t_stat ports_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -t_stat ports_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat ports_rcv_svc(UNIT *uptr); t_stat ports_xmt_svc(UNIT *uptr); t_stat ports_cio_svc(UNIT *uptr); t_stat ports_attach(UNIT *uptr, CONST char *cptr); t_stat ports_detach(UNIT *uptr); -void ports_sysgen(uint8 cid); -void ports_express(uint8 cid); -void ports_full(uint8 cid); +void ports_sysgen(uint8 slot); +void ports_express(uint8 slot); +void ports_full(uint8 slot); #endif /* _3B2_PORTS_H_ */ diff --git a/3B2/3b2_rev2_csr.c b/3B2/3b2_rev2_csr.c index aea82f1b..babc5d67 100644 --- a/3B2/3b2_rev2_csr.c +++ b/3B2/3b2_rev2_csr.c @@ -1,6 +1,6 @@ -/* 3b2_rev2_csr.c: AT&T 3B2 Rev 2 Control and Status Register +/* 3b2_rev2_csr.c: ED System Board Control and Status Register - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,8 +33,9 @@ #include "3b2_cpu.h" #include "3b2_sys.h" #include "3b2_timer.h" +#include "3b2_sys.h" -uint16 csr_data; +CSR_DATA csr_data; BITFIELD csr_bits[] = { BIT(IOF), @@ -94,8 +95,8 @@ uint32 csr_read(uint32 pa, size_t size) uint32 reg = pa - CSRBASE; sim_debug(READ_MSG, &csr_dev, - "[%08x] CSR=%04x\n", - R[NUM_PC], csr_data); + "CSR=%04x\n", + csr_data); switch (reg) { case 0x2: @@ -143,20 +144,15 @@ void csr_write(uint32 pa, uint32 val, size_t size) break; case 0x23: /* Set Inhibit Timers */ sim_debug(WRITE_MSG, &csr_dev, - "[%08x] SET INHIBIT TIMERS\n", R[NUM_PC]); + "SET INHIBIT TIMERS\n"); csr_data |= CSRITIM; + timer_gate(TIMER_INTERVAL, TRUE); break; case 0x27: /* Clear Inhibit Timers */ sim_debug(WRITE_MSG, &csr_dev, - "[%08x] CLEAR INHIBIT TIMERS\n", R[NUM_PC]); - - /* A side effect of clearing the timer inhibit bit is to cause - * a simulated "tick" of any active timers. This is a hack to - * make diagnostics pass. This is not 100% accurate, but it - * makes SVR3 and DGMON tests happy. - */ - timer_tick(); + "CLEAR INHIBIT TIMERS\n"); csr_data &= ~CSRITIM; + timer_gate(TIMER_INTERVAL, FALSE); break; case 0x2b: /* Set Inhibit Faults */ csr_data |= CSRIFLT; diff --git a/3B2/3b2_rev2_csr.h b/3B2/3b2_rev2_csr.h index cb873724..cd31ea2f 100644 --- a/3B2/3b2_rev2_csr.h +++ b/3B2/3b2_rev2_csr.h @@ -1,6 +1,6 @@ -/* 3b2_rev2_csr.h: AT&T 3B2 Rev 2 Control and Status Register +/* 3b2_rev2_csr.h: ED System Board Control and Status Register - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,6 +33,8 @@ #include "3b2_defs.h" +typedef uint16 CSR_DATA; + /* CSR */ t_stat csr_svc(UNIT *uptr); t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); @@ -41,6 +43,4 @@ t_stat csr_reset(DEVICE *dptr); uint32 csr_read(uint32 pa, size_t size); void csr_write(uint32 pa, uint32 val, size_t size); -extern uint16 csr_data; - #endif /* 3B2_REV2_CSR_H_ */ diff --git a/3B2/3b2_rev2_defs.h b/3B2/3b2_rev2_defs.h index 3448d92c..e2960257 100644 --- a/3B2/3b2_rev2_defs.h +++ b/3B2/3b2_rev2_defs.h @@ -1,6 +1,6 @@ -/* 3b2_rev2_defs.h: AT&T 3B2 Rev 2 (Model 400) Simulator Definitions +/* 3b2_rev2_defs.h: Version 2 (3B2/400) Common Definitions - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -83,7 +83,7 @@ #define INT_SERR 0x01 /* IPL 15 */ #define INT_CLOCK 0x02 /* IPL 15 */ #define INT_DMA 0x04 /* IPL 13 */ -#define INT_UART 0x04 /* IPL 13 */ +#define INT_UART 0x08 /* IPL 13 */ #define INT_DISK 0x10 /* IPL 11 */ #define INT_FLOPPY 0x20 /* IPL 11 */ #define INT_PIR9 0x40 /* IPL 9 */ diff --git a/3B2/3b2_rev2_mau.h b/3B2/3b2_rev2_mau.h deleted file mode 100644 index c486b3f4..00000000 --- a/3B2/3b2_rev2_mau.h +++ /dev/null @@ -1,383 +0,0 @@ -/* 3b2_rev2_mau.c: AT&T 3B2 Rev 2 (Model 400) Math Acceleration - Unit (WE32106 MAU) Header - - Copyright (c) 2019, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. - - --------------------------------------------------------------------- - - This file is part of a simulation of the WE32106 Math Acceleration - Unit. The WE32106 MAU is an IEEE-754 compabitle floating point - hardware math accelerator that was available as an optional - component on the AT&T 3B2/310 and 3B2/400, and a standard component - on the 3B2/500, 3B2/600, and 3B2/1000. - - Portions of this code are derived from the SoftFloat 2c library by - John R. Hauser. Functions derived from SoftFloat 2c are clearly - marked in the comments. - - Legal Notice - ============ - - SoftFloat was written by John R. Hauser. Release 2c of SoftFloat - was made possible in part by the International Computer Science - Institute, located at Suite 600, 1947 Center Street, Berkeley, - California 94704. Funding was partially provided by the National - Science Foundation under grant MIP-9311980. The original version - of this code was written as part of a project to build a - fixed-point vector processor in collaboration with the University - of California at Berkeley, overseen by Profs. Nelson Morgan and - John Wawrzynek. - - THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable - effort has been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS - THAT WILL AT TIMES RESULT IN INCORRECT BEHAVIOR. USE OF THIS - SOFTWARE IS RESTRICTED TO PERSONS AND ORGANIZATIONS WHO CAN AND - WILL TOLERATE ALL LOSSES, COSTS, OR OTHER PROBLEMS THEY INCUR DUE - TO THE SOFTWARE WITHOUT RECOMPENSE FROM JOHN HAUSER OR THE - INTERNATIONAL COMPUTER SCIENCE INSTITUTE, AND WHO FURTHERMORE - EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER - SCIENCE INSTITUTE (possibly via similar legal notice) AGAINST ALL - LOSSES, COSTS, OR OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND - CLIENTS DUE TO THE SOFTWARE, OR INCURRED BY ANYONE DUE TO A - DERIVATIVE WORK THEY CREATE USING ANY PART OF THE SOFTWARE. - - The following are expressly permitted, even for commercial - purposes: - - (1) distribution of SoftFloat in whole or in part, as long as this - and other legal notices remain and are prominent, and provided also - that, for a partial distribution, prominent notice is given that it - is a subset of the original; and - - (2) inclusion or use of SoftFloat in whole or in part in a - derivative work, provided that the use restrictions above are met - and the minimal documentation requirements stated in the source - code are satisfied. - --------------------------------------------------------------------- - - Data Types - ========== - - The WE32106 MAU stores values using IEEE-754 1985 types, plus a - non-standard Decimal type. - - - Decimal Type - 18 BCD digits long. Each digit is 4 bits wide. - Sign is encoded in byte 0. - - 3322 2222 2222 1111 1111 1100 0000 0000 - 1098 7654 3210 9876 5432 1098 7654 3210 - +-------------------+----+----+----+----+ - | unused | D18| D17| D16| D15| High Word - +----+----+----+----+----+----+----+----+ - | D14| D13| D12| D11| D10| D09| D08| D07| Middle Word - +----+----+----+----+----+----+----+----+ - | D06| D05| D04| D03| D02| D01| D00|sign| Low Word - +----+----+----+----+----+----+----+----+ - - Sign: 0: Positive Infinity 10: Positive Number - 1: Negative Infinity 11: Negative Number - 2: Positive NaN 12: Positive Number - 3: Negative NaN 13: Negative Number - 4-9: Trapping NaN 14-15: Positive Number - - - Extended Precision (80-bit) - exponent biased by 16383 - - 3 322222222221111 1 111110000000000 - 1 098765432109876 5 432109876543210 - +-----------------+-+---------------+ - | unused |S| Exponent | High Word - +-+---------------+-+---------------+ - |J| Fraction (high word) | Middle Word - +-+---------------------------------+ - | Fraction (low word) | Low Word - +-----------------------------------+ - - - - Double Precision (64-bit) - exponent biased by 1023 - - 3 3222222222 211111111110000000000 - 1 0987654321 098765432109876543210 - +-+----------+---------------------+ - |S| Exponent | Fraction (high) | High Word - +-+----------+---------------------+ - | Fraction (low) | Low Word - +----------------------------------+ - - - - Single Precision (32-bit) - exponent biased by 127 - - 3 32222222 22211111111110000000000 - 1 09876543 21098765432109876543210 - +-+--------+-----------------------+ - |S| Exp | Fraction | - +-+--------+-----------------------+ - -*/ - -#ifndef _3B2_REV2_MAU_H_ -#define _3B2_REV2_MAU_H_ - -#include "sim_defs.h" - -#define SRC_LEN_INVALID 0 -#define SRC_LEN_SINGLE 1 -#define SRC_LEN_DOUBLE 2 -#define SRC_LEN_TRIPLE 3 - -#define MAU_ASR_RC_SHIFT 22 - -#define MAU_ASR_PR 0x20u /* Partial Remainder */ -#define MAU_ASR_QS 0x40u /* Divide By Zero Sticky */ -#define MAU_ASR_US 0x80u /* Underflow Sticky */ -#define MAU_ASR_OS 0x100u /* Overflow Sticky */ -#define MAU_ASR_IS 0x200u /* Invalid Operation Sticky */ -#define MAU_ASR_PM 0x400u /* Inexact Mask */ -#define MAU_ASR_QM 0x800u /* Divide by Zero Mask */ -#define MAU_ASR_UM 0x1000u /* Underflow Mask */ -#define MAU_ASR_OM 0x2000u /* Overflow Mask */ -#define MAU_ASR_IM 0x4000u /* Invalid Operation Mask */ - -#define MAU_ASR_UO 0x10000u /* Unordered */ -#define MAU_ASR_CSC 0x20000u /* Context Switch Control */ -#define MAU_ASR_PS 0x40000u /* Inexact Sticky */ -#define MAU_ASR_IO 0x80000u /* Integer Overflow */ -#define MAU_ASR_Z 0x100000u /* Zero Flag */ -#define MAU_ASR_N 0x200000u /* Negative Flag */ -#define MAU_ASR_RC 0x400000u /* Round Control */ - -#define MAU_ASR_NTNC 0x1000000u /* Nontrapping NaN Control */ -#define MAU_ASR_ECP 0x2000000u /* Exception Condition */ - -#define MAU_ASR_RA 0x80000000u /* Result Available */ - -#define MAU_RC_RN 0 /* Round toward Nearest */ -#define MAU_RC_RP 1 /* Round toward Plus Infin. */ -#define MAU_RC_RM 2 /* Round toward Neg. Infin. */ -#define MAU_RC_RZ 3 /* Round toward Zero */ - -#define SFP_SIGN(V) (((V) >> 31) & 1) -#define SFP_EXP(V) (((V) >> 23) & 0xff) -#define SFP_FRAC(V) ((V) & 0x7fffff) - -#define DFP_SIGN(V) (((V) >> 63) & 1) -#define DFP_EXP(V) (((V) >> 52) & 0x7ff) -#define DFP_FRAC(V) ((V) & 0xfffffffffffffull) - -#define XFP_SIGN(V) (((V)->sign_exp >> 15) & 1) -#define XFP_EXP(V) ((V)->sign_exp & 0x7fff) -#define XFP_FRAC(V) ((V)->frac) - -#define XFP_IS_NORMAL(V) ((V)->frac & 0x8000000000000000ull) - -#define DEFAULT_XFP_NAN_SIGN_EXP 0xffff -#define DEFAULT_XFP_NAN_FRAC 0xc000000000000000ull - -#define SFP_IS_TRAPPING_NAN(V) (((((V) >> 22) & 0x1ff) == 0x1fe) && \ - ((V) & 0x3fffff)) -#define DFP_IS_TRAPPING_NAN(V) (((((V) >> 51) & 0xfff) == 0xffe) && \ - ((V) & 0x7ffffffffffffull)) -#define XFP_IS_NAN(V) ((((V)->sign_exp & 0x7fff) == 0x7fff) && \ - (t_uint64)((V)->frac << 1)) -#define XFP_IS_TRAPPING_NAN(V) ((((V)->sign_exp) & 0x7fff) && \ - ((((V)->frac) & ~(0x4000000000000000ull)) << 1) && \ - (((V)->frac) == ((V)->frac & ~(0x4000000000000000ull)))) -#define PACK_DFP(SIGN,EXP,FRAC) ((((t_uint64)(SIGN))<<63) + \ - (((t_uint64)(EXP))<<52) + \ - ((t_uint64)(FRAC))) -#define PACK_SFP(SIGN,EXP,FRAC) (((uint32)(SIGN)<<31) + \ - ((uint32)(EXP)<<23) + \ - ((uint32)(FRAC))) -#define PACK_XFP(SIGN,EXP,FRAC,V) do { \ - (V)->frac = (FRAC); \ - (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ - (V)->s = FALSE; \ - } while (0) - -#define PACK_XFP_S(SIGN,EXP,FRAC,S,V) do { \ - (V)->frac = (FRAC); \ - (V)->sign_exp = ((uint16)(SIGN) << 15) + (EXP); \ - (V)->s = (S) != 0; \ - } while (0) - -#define MAU_RM ((RM)((mau_state.asr >> 22) & 3)) - -typedef enum { - M_ADD = 0x02, - M_SUB = 0x03, - M_DIV = 0x04, - M_REM = 0x05, - M_MUL = 0x06, - M_MOVE = 0x07, - M_RDASR = 0x08, - M_WRASR = 0x09, - M_CMP = 0x0a, - M_CMPE = 0x0b, - M_ABS = 0x0c, - M_SQRT = 0x0d, - M_RTOI = 0x0e, - M_FTOI = 0x0f, - M_ITOF = 0x10, - M_DTOF = 0x11, - M_FTOD = 0x12, - M_NOP = 0x13, - M_EROF = 0x14, - M_NEG = 0x17, - M_LDR = 0x18, - M_CMPS = 0x1a, - M_CMPES = 0x1b -} mau_opcodes; - -typedef enum { - M_OP3_F0_SINGLE, - M_OP3_F1_SINGLE, - M_OP3_F2_SINGLE, - M_OP3_F3_SINGLE, - M_OP3_F0_DOUBLE, - M_OP3_F1_DOUBLE, - M_OP3_F2_DOUBLE, - M_OP3_F3_DOUBLE, - M_OP3_F0_TRIPLE, - M_OP3_F1_TRIPLE, - M_OP3_F2_TRIPLE, - M_OP3_F3_TRIPLE, - M_OP3_MEM_SINGLE, - M_OP3_MEM_DOUBLE, - M_OP3_MEM_TRIPLE, - M_OP3_NONE -} op3_spec; - -/* Specifier bytes for Operands 1 and 2 */ -typedef enum { - M_OP_F0, - M_OP_F1, - M_OP_F2, - M_OP_F3, - M_OP_MEM_SINGLE, - M_OP_MEM_DOUBLE, - M_OP_MEM_TRIPLE, - M_OP_NONE -} op_spec; - -/* - * 128-bit value - */ -typedef struct { - t_uint64 low; - t_uint64 high; -} t_mau_128; - -/* - * Not-a-Number Type - */ -typedef struct { - t_bool sign; - t_uint64 high; - t_uint64 low; -} T_NAN; - -/* - * Extended Precision (80 bits). - * - * Note that an undocumented feature of the WE32106 requires the use - * of uint32 rather than uint16 for the sign and exponent components - * of the struct. Although bits 80-95 are "unused", several - * diagnostics actually expect these bits to be moved and preserved on - * word transfers. They are ignored and discarded by math routines, - * however. - * - * The 's' field holds the Sticky bit used by rounding. - */ -typedef struct { - uint32 sign_exp; /* Sign and Exponent */ - t_uint64 frac; /* Fraction/Significand/Mantissa */ - t_bool s; /* Sticky bit */ -} XFP; - -typedef struct { - uint32 h; - t_uint64 l; -} DEC; - -/* - * Supported rounding modes. - */ -typedef enum { - ROUND_NEAREST, - ROUND_PLUS_INF, - ROUND_MINUS_INF, - ROUND_ZERO -} RM; - -/* - * Double Precision (64 bits) - */ -typedef t_uint64 DFP; - -/* - * Single Precision (32 bits) - */ -typedef uint32 SFP; - -/* - * MAU state - */ - -typedef struct { - uint32 cmd; - /* Exception */ - uint32 exception; - /* Status register */ - uint32 asr; - t_bool trapping_nan; - /* Generate a Non-Trapping NaN */ - t_bool ntnan; - /* Source (from broadcast) */ - uint32 src; - /* Destination (from broadcast) */ - uint32 dst; - uint8 opcode; - uint8 op1; - uint8 op2; - uint8 op3; - /* Data Register */ - XFP dr; - /* Operand Registers */ - XFP f0; - XFP f1; - XFP f2; - XFP f3; -} MAU_STATE; - -t_stat mau_reset(DEVICE *dptr); -t_stat mau_attach(UNIT *uptr, CONST char *cptr); -t_stat mau_detach(UNIT *uptr); -t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); -CONST char *mau_description(DEVICE *dptr); -t_stat mau_broadcast(uint32 cmd, uint32 src, uint32 dst); - -#endif /* _3B2_REV2_MAU_H_ */ diff --git a/3B2/3b2_rev2_mmu.c b/3B2/3b2_rev2_mmu.c index 9c9dc2b0..88383cb3 100644 --- a/3B2/3b2_rev2_mmu.c +++ b/3B2/3b2_rev2_mmu.c @@ -1,6 +1,6 @@ -/* 3b2_rev2_mmu.c: AT&T 3B2 Rev 2 (Model 400) MMU (WE32101) Implementation +/* 3b2_rev2_mmu.c: WE32101 MMU - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -32,6 +32,7 @@ #include "3b2_cpu.h" #include "3b2_mmu.h" #include "3b2_sys.h" +#include "3b2_mem.h" UNIT mmu_unit = { UDATA(NULL, 0, 0) }; @@ -260,13 +261,16 @@ static SIM_INLINE t_stat mmu_check_perm(uint8 flags, uint8 r_acc) case 0: /* No Access */ return SCPE_NXM; case 1: /* Exec Only */ - if (r_acc != ACC_IF && r_acc != ACC_IFAD) { + if (r_acc != ACC_IF && + r_acc != ACC_IFAD) { return SCPE_NXM; } return SCPE_OK; case 2: /* Read / Execute */ - if (r_acc != ACC_AF && r_acc != ACC_OF && - r_acc != ACC_IF && r_acc != ACC_IFAD && + if (r_acc != ACC_IF && + r_acc != ACC_IFAD && + r_acc != ACC_OF && + r_acc != ACC_AF && r_acc != ACC_MT) { return SCPE_NXM; } @@ -288,8 +292,8 @@ static SIM_INLINE void mmu_update_sd(uint32 va, uint32 mask) /* We go back to main memory to find the SD because the SD may have been loaded from cache, which is lossy. */ - sd0 = pread_w(SD_ADDR(va)); - pwrite_w(SD_ADDR(va), sd0|mask); + sd0 = pread_w(SD_ADDR(va), BUS_PER); + pwrite_w(SD_ADDR(va), sd0|mask, BUS_PER); /* There is no 'R' bit in the SD cache, only an 'M' bit. */ if (mask == SD_M_MASK) { @@ -310,8 +314,8 @@ static SIM_INLINE void mmu_update_pd(uint32 va, uint32 pd_addr, uint32 mask) /* We go back to main memory to find the PD because the PD may have been loaded from cache, which is lossy. */ - pd = pread_w(pd_addr); - pwrite_w(pd_addr, pd|mask); + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd|mask, BUS_PER); /* Update in the cache */ @@ -349,50 +353,50 @@ uint32 mmu_read(uint32 pa, size_t size) case MMU_SDCL: data = mmu_state.sdcl[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] [pa=%08x] MMU_SDCL[%d] = %08x\n", - R[NUM_PC], pa, offset, data); + "[pa=%08x] MMU_SDCL[%d] = %08x\n", + pa, offset, data); break; case MMU_SDCH: data = mmu_state.sdch[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_SDCH[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_SDCH[%d] = %08x\n", + offset, data); break; case MMU_PDCRL: data = mmu_state.pdcrl[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_PDCRL[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_PDCRL[%d] = %08x\n", + offset, data); break; case MMU_PDCRH: data = mmu_state.pdcrh[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_PDCRH[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_PDCRH[%d] = %08x\n", + offset, data); break; case MMU_PDCLL: data = mmu_state.pdcll[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_PDCLL[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_PDCLL[%d] = %08x\n", + offset, data); break; case MMU_PDCLH: data = mmu_state.pdclh[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_PDCLH[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_PDCLH[%d] = %08x\n", + offset, data); break; case MMU_SRAMA: data = mmu_state.sra[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_SRAMA[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_SRAMA[%d] = %08x\n", + offset, data); break; case MMU_SRAMB: data = mmu_state.srb[offset]; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_SRAMB[%d] = %08x\n", - R[NUM_PC], offset, data); + "MMU_SRAMB[%d] = %08x\n", + offset, data); break; case MMU_FC: data = mmu_state.fcode; @@ -403,14 +407,14 @@ uint32 mmu_read(uint32 pa, size_t size) case MMU_CONF: data = mmu_state.conf & 0x7; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_CONF = %08x\n", - R[NUM_PC], data); + "MMU_CONF = %08x\n", + data); break; case MMU_VAR: data = mmu_state.var; sim_debug(READ_MSG, &mmu_dev, - "[%08x] MMU_VAR = %08x\n", - R[NUM_PC], data); + "MMU_VAR = %08x\n", + data); break; } @@ -466,8 +470,8 @@ void mmu_write(uint32 pa, uint32 val, size_t size) mmu_state.sec[offset].addr = val & 0xffffffe0; /* We flush the entire section on writing SRAMA */ sim_debug(WRITE_MSG, &mmu_dev, - "[%08x] MMU_SRAMA[%d] = %08x\n", - R[NUM_PC], offset, val); + "MMU_SRAMA[%d] = %08x\n", + offset, val); flush_cache_sec((uint8) offset); break; case MMU_SRAMB: @@ -476,8 +480,8 @@ void mmu_write(uint32 pa, uint32 val, size_t size) mmu_state.sec[offset].len = (val >> 10) & 0x1fff; /* We do not flush the cache on writing SRAMB */ sim_debug(WRITE_MSG, &mmu_dev, - "[%08x] MMU_SRAMB[%d] = %08x (len=%06x)\n", - R[NUM_PC], offset, val, mmu_state.sec[offset].len); + "MMU_SRAMB[%d] = %08x (len=%06x)\n", + offset, val, mmu_state.sec[offset].len); break; case MMU_FC: mmu_state.fcode = val; @@ -518,20 +522,20 @@ t_stat mmu_get_sd(uint32 va, uint8 r_acc, t_bool fc, if (SSL(va) > SRAMB_LEN(va)) { MMU_FAULT(MMU_F_SDTLEN); sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", - R[NUM_PC], SRAMB_LEN(va), SSL(va), va); + "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", + SRAMB_LEN(va), SSL(va), va); return SCPE_NXM; } /* sd0 contains the segment descriptor, sd1 contains a pointer to the PDT or Segment */ - *sd0 = pread_w(SD_ADDR(va)); - *sd1 = pread_w(SD_ADDR(va) + 4); + *sd0 = pread_w(SD_ADDR(va), BUS_PER); + *sd1 = pread_w(SD_ADDR(va) + 4, BUS_PER); if (!SD_VALID(*sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Invalid Segment Descriptor. va=%08x sd0=%08x\n", - R[NUM_PC], va, *sd0); + "Invalid Segment Descriptor. va=%08x sd0=%08x\n", + va, *sd0); MMU_FAULT(MMU_F_INV_SD); return SCPE_NXM; } @@ -547,14 +551,14 @@ t_stat mmu_get_sd(uint32 va, uint8 r_acc, t_bool fc, if (!SD_PRESENT(*sd0)) { if (SD_CONTIG(*sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Segment Not Present. va=%08x", - R[NUM_PC], va); + "Segment Not Present. va=%08x", + va); MMU_FAULT(MMU_F_SEG_NOT_PRES); return SCPE_NXM; } else { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] PDT Not Present. va=%08x", - R[NUM_PC], va); + "PDT Not Present. va=%08x", + va); MMU_FAULT(MMU_F_PDT_NOT_PRES); return SCPE_NXM; } @@ -582,15 +586,15 @@ t_stat mmu_get_pd(uint32 va, uint8 r_acc, t_bool fc, /* Bounds checking on length */ if ((PSL(va) * 4) >= MAX_OFFSET(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] PDT Length Fault. " + "PDT Length Fault. " "PDT Offset=%08x Max Offset=%08x va=%08x\n", - R[NUM_PC], (PSL(va) * 4), + (PSL(va) * 4), MAX_OFFSET(sd0), va); MMU_FAULT(MMU_F_PDTLEN); return SCPE_NXM; } - *pd = pread_w(pd_addr); + *pd = pread_w(pd_addr, BUS_PER); /* Copy the access flags from the SD */ *pd_acc = SD_ACC(sd0); @@ -614,23 +618,21 @@ t_stat mmu_decode_contig(uint32 va, uint8 r_acc, /* Update R and M bits if configured */ if (SHOULD_UPDATE_SD_R_BIT(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Updating R bit in SD\n", - R[NUM_PC]); + "Updating R bit in SD\n"); mmu_update_sd(va, SD_R_MASK); } if (SHOULD_UPDATE_SD_M_BIT(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Updating M bit in SD\n", - R[NUM_PC]); + "Updating M bit in SD\n"); mmu_update_sd(va, SD_M_MASK); } /* Generate object trap if needed */ if (SD_TRAP(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Object Trap. va=%08x", - R[NUM_PC], va); + "Object Trap. va=%08x", + va); MMU_FAULT(MMU_F_OTRAP); return SCPE_NXM; } @@ -647,9 +649,9 @@ t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc, /* If the PD is not marked present, fail */ if (!PD_PRESENT(pd)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Page Not Present. " + "Page Not Present. " "pd=%08x r_acc=%x va=%08x\n", - R[NUM_PC], pd, r_acc, va); + pd, r_acc, va); MMU_FAULT(MMU_F_PAGE_NOT_PRES); return SCPE_NXM; } @@ -659,8 +661,8 @@ t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc, the 'W' bit is set, trigger a write fault */ if ((r_acc == ACC_W || r_acc == ACC_IR) && PD_WFAULT(pd)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Page Write Fault. va=%08x\n", - R[NUM_PC], va); + "Page Write Fault. va=%08x\n", + va); MMU_FAULT(MMU_F_PW); return SCPE_NXM; } @@ -668,16 +670,14 @@ t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc, /* If this is a write, modify the M bit */ if (SHOULD_UPDATE_PD_M_BIT(pd)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Updating M bit in PD\n", - R[NUM_PC]); + "Updating M bit in PD\n"); mmu_update_pd(va, PD_LOC(sd1, va), PD_M_MASK); } /* Modify the R bit and write it back */ if (SHOULD_UPDATE_PD_R_BIT(pd)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Updating R bit in PD\n", - R[NUM_PC]); + "Updating R bit in PD\n"); mmu_update_pd(va, PD_LOC(sd1, va), PD_R_MASK); } } @@ -717,29 +717,29 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) if (sd_cached == SCPE_OK && pd_cached != SCPE_OK) { if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Could not get PD (partial miss). r_acc=%d, fc=%d, va=%08x\n", - R[NUM_PC], r_acc, fc, va); + "Could not get PD (partial miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); return SCPE_NXM; } } else if (sd_cached != SCPE_OK && pd_cached == SCPE_OK) { if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Could not get SD (partial miss). r_acc=%d, fc=%d, va=%08x\n", - R[NUM_PC], r_acc, fc, va); + "Could not get SD (partial miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); return SCPE_NXM; } } else if (sd_cached != SCPE_OK && pd_cached != SCPE_OK) { if (mmu_get_sd(va, r_acc, fc, &sd0, &sd1) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Could not get SD (full miss). r_acc=%d, fc=%d, va=%08x\n", - R[NUM_PC], r_acc, fc, va); + "Could not get SD (full miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); return SCPE_NXM; } if (SD_PAGED(sd0) && mmu_get_pd(va, r_acc, fc, sd0, sd1, &pd, &pd_acc) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Could not get PD (full miss). r_acc=%d, fc=%d, va=%08x\n", - R[NUM_PC], r_acc, fc, va); + "Could not get PD (full miss). r_acc=%d, fc=%d, va=%08x\n", + r_acc, fc, va); return SCPE_NXM; } } @@ -747,18 +747,17 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) if (SD_PAGED(sd0)) { if (fc && mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] PAGED: NO ACCESS TO MEMORY AT %08x.\n" + "PAGED: NO ACCESS TO MEMORY AT %08x.\n" "\t\tcpu_cm=%d r_acc=%x pd_acc=%02x\n" "\t\tpd=%08x psw=%08x\n", - R[NUM_PC], va, CPU_CM, r_acc, pd_acc, + va, CPU_CM, r_acc, pd_acc, pd, R[NUM_PSW]); MMU_FAULT(MMU_F_ACC); return SCPE_NXM; } if (PD_LAST(pd) && (PSL_C(va) | POT(va)) >= MAX_OFFSET(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] PAGED: Segment Offset Fault.\n", - R[NUM_PC]); + "PAGED: Segment Offset Fault.\n"); MMU_FAULT(MMU_F_SEG_OFFSET); return SCPE_NXM; } @@ -766,20 +765,19 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) } else { if (fc && mmu_check_perm(SD_ACC(sd0), r_acc) != SCPE_OK) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] CONTIGUOUS: NO ACCESS TO MEMORY AT %08x.\n" + "CONTIGUOUS: NO ACCESS TO MEMORY AT %08x.\n" "\t\tsd0=%08x sd0_addr=%08x\n" "\t\tcpu_cm=%d acc_req=%x sd_acc=%02x\n", - R[NUM_PC], va, - sd0, SD_ADDR(va), + va, sd0, SD_ADDR(va), CPU_CM, r_acc, SD_ACC(sd0)); MMU_FAULT(MMU_F_ACC); return SCPE_NXM; } if (SOT(va) >= MAX_OFFSET(sd0)) { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] CONTIGUOUS: Segment Offset Fault. " + "CONTIGUOUS: Segment Offset Fault. " "sd0=%08x sd_addr=%08x SOT=%08x len=%08x va=%08x\n", - R[NUM_PC], sd0, SD_ADDR(va), SOT(va), + sd0, SD_ADDR(va), SOT(va), MAX_OFFSET(sd0), va); MMU_FAULT(MMU_F_SEG_OFFSET); return SCPE_NXM; @@ -807,16 +805,14 @@ uint32 mmu_xlate_addr(uint32 va, uint8 r_acc) void mmu_enable() { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Enabling MMU.\n", - R[NUM_PC]); + "Enabling MMU.\n"); mmu_state.enabled = TRUE; } void mmu_disable() { sim_debug(EXECUTE_MSG, &mmu_dev, - "[%08x] Disabling MMU.\n", - R[NUM_PC]); + "Disabling MMU.\n"); mmu_state.enabled = FALSE; } diff --git a/3B2/3b2_rev2_mmu.h b/3B2/3b2_rev2_mmu.h index 6090971e..30b1d116 100644 --- a/3B2/3b2_rev2_mmu.h +++ b/3B2/3b2_rev2_mmu.h @@ -1,6 +1,6 @@ -/* 3b2_rev2_mmu.c: AT&T 3B2 Rev 2 (Model 400) MMU (WE32101) Header +/* 3b2_rev2_mmu.c: WE32101 MMU - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -326,18 +326,6 @@ uint32 mmu_read(uint32 pa, size_t size); void mmu_write(uint32 pa, uint32 val, size_t size); CONST char *mmu_description(DEVICE *dptr); -/* Physical memory read/write */ -uint8 pread_b(uint32 pa); -uint16 pread_h(uint32 pa); -uint32 pread_w(uint32 pa); -uint32 pread_w_u(uint32 pa); -void pwrite_b(uint32 pa, uint8 val); -void pwrite_h(uint32 pa, uint16 val); -void pwrite_w(uint32 pa, uint32 val); - -/* TODO: REMOVE AFTER DEBUGGING */ -uint32 safe_read_w(uint32 va); - /* Virtual memory translation */ uint32 mmu_xlate_addr(uint32 va, uint8 r_acc); t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, @@ -361,19 +349,6 @@ t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc, #define SHOULD_UPDATE_PD_M_BIT(pd) \ (r_acc == ACC_W && !((pd) & PD_M_MASK)) -/* Dispatch to the MMU when enabled, or to physical RW when - disabled */ -uint8 read_b(uint32 va, uint8 r_acc); -uint16 read_h(uint32 va, uint8 r_acc); -uint32 read_w(uint32 va, uint8 r_acc); -void write_b(uint32 va, uint8 val); -void write_h(uint32 va, uint16 val); -void write_w(uint32 va, uint32 val); - -t_bool addr_is_rom(uint32 pa); -t_bool addr_is_mem(uint32 pa); -t_bool addr_is_io(uint32 pa); - t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa); void mmu_enable(); void mmu_disable(); diff --git a/3B2/3b2_rev2_sys.c b/3B2/3b2_rev2_sys.c index f869f4ec..c9e84b4e 100644 --- a/3B2/3b2_rev2_sys.c +++ b/3B2/3b2_rev2_sys.c @@ -1,6 +1,6 @@ -/* 3b2_rev2_sys.c: AT&T 3B2 Rev 2 (Model 400) system definition +/* 3b2_rev2_sys.c: Version 2 (3B2/400) System Definition - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/3B2/3b2_rev2_timer.c b/3B2/3b2_rev2_timer.c deleted file mode 100644 index 77c7363f..00000000 --- a/3B2/3b2_rev2_timer.c +++ /dev/null @@ -1,401 +0,0 @@ -/* 3b2_rev2_timer.c: 8253 Interval Timer - - Copyright (c) 2017, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. -*/ - -/* - * 8253 Timer. - * - * The 8253 Timer IC has three interval timers, which we treat here as - * three units. - * - * Note that this simulation is very specific to the 3B2, and not - * usable as a general purpose 8253 simulator. - * - */ - -#include "3b2_cpu.h" -#include "3b2_csr.h" -#include "3b2_defs.h" -#include "3b2_timer.h" - -#define SET_INT do { \ - CPU_SET_INT(INT_CLOCK); \ - SET_CSR(CSRCLK); \ - } while (0) - -#define CLR_INT do { \ - CPU_CLR_INT(INT_CLOCK); \ - CLR_CSR(CSRCLK); \ - } while (0) - -struct timer_ctr TIMERS[3]; - -int32 tmxr_poll = 16667; - -/* - * The three timers, (A, B, C) run at different - * programmatially controlled frequencies, so each must be - * handled through a different service routine. - */ - -UNIT timer_unit[] = { - { UDATA(&timer0_svc, 0, 0) }, - { UDATA(&timer1_svc, UNIT_IDLE, 0) }, - { UDATA(&timer2_svc, 0, 0) }, - { NULL } -}; - -UNIT *timer_clk_unit = &timer_unit[1]; - -REG timer_reg[] = { - { HRDATAD(DIVA, TIMERS[0].divider, 16, "Divider A") }, - { HRDATAD(STA, TIMERS[0].mode, 8, "Mode A") }, - { HRDATAD(DIVB, TIMERS[1].divider, 16, "Divider B") }, - { HRDATAD(STB, TIMERS[1].mode, 8, "Mode B") }, - { HRDATAD(DIVC, TIMERS[2].divider, 16, "Divider C") }, - { HRDATAD(STC, TIMERS[2].mode, 8, "Mode C") }, - { NULL } -}; - -MTAB timer_mod[] = { - { MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NC, 0, NULL, "SHUTDOWN", - &timer_set_shutdown, NULL, NULL, "Soft Power Shutdown" }, - { 0 } -}; - -DEVICE timer_dev = { - "TIMER", timer_unit, timer_reg, timer_mod, - 1, 16, 8, 4, 16, 32, - NULL, NULL, &timer_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab -}; - -t_stat timer_reset(DEVICE *dptr) { - int32 i, t; - - memset(&TIMERS, 0, sizeof(struct timer_ctr) * 3); - - for (i = 0; i < 3; i++) { - timer_unit[i].tmrnum = i; - timer_unit[i].tmr = (void *)&TIMERS[i]; - } - - /* Timer 1 gate is always active */ - TIMERS[1].gate = 1; - - if (!sim_is_running) { - t = sim_rtcn_init_unit(timer_clk_unit, TPS_CLK, TMR_CLK); - sim_activate_after(timer_clk_unit, 1000000 / t); - } - - return SCPE_OK; -} - -static void timer_activate(uint8 ctrnum) -{ - struct timer_ctr *ctr; - - ctr = &TIMERS[ctrnum]; - - switch (ctrnum) { - case TIMER_SANITY: - break; - case TIMER_INTERVAL: - if ((csr_data & CSRITIM) == 0) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] INTERVAL TIMER: Activating after %d ms\n", - R[NUM_PC], ctr->val); - sim_activate_after_abs(&timer_unit[ctrnum], ctr->val); - ctr->val--; - } else { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] INTERVAL TIMER: Currently disabled, not starting\n", - R[NUM_PC]); - } - break; - case TIMER_BUS: - break; - default: - break; - } -} - -t_stat timer_set_shutdown(UNIT *uptr, int32 val, CONST char* cptr, void* desc) -{ - struct timer_ctr *sanity = (struct timer_ctr *)timer_unit[0].tmr; - - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] Setting sanity timer to 0 for shutdown.\n", R[NUM_PC]); - - sanity->val = 0; - - CLR_INT; - - CPU_SET_INT(INT_SERR); - CSRBIT(CSRTIMO, TRUE); - - return SCPE_OK; -} - -void timer_enable(uint8 ctrnum) { - timer_activate(ctrnum); -} - -void timer_disable(uint8 ctrnum) -{ - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] Disabling timer %d\n", - R[NUM_PC], ctrnum); - sim_cancel(&timer_unit[ctrnum]); -} - -/* - * Sanity Timer - */ -t_stat timer0_svc(UNIT *uptr) -{ - struct timer_ctr *ctr; - int32 time; - - ctr = (struct timer_ctr *)uptr->tmr; - - time = ctr->divider * TIMER_STP_US; - - if (time == 0) { - time = TIMER_STP_US; - } - - sim_activate_after_abs(uptr, time); - - return SCPE_OK; -} - -/* - * Interval Timer - */ -t_stat timer1_svc(UNIT *uptr) -{ - struct timer_ctr *ctr; - int32 t; - - ctr = (struct timer_ctr *)uptr->tmr; - - if (ctr->enabled && !(csr_data & CSRITIM)) { - /* Fire the IPL 15 clock interrupt */ - SET_INT; - } - - t = sim_rtcn_calb(TPS_CLK, TMR_CLK); - sim_activate_after_abs(uptr, 1000000/TPS_CLK); - tmxr_poll = t; - - return SCPE_OK; -} - -/* - * Bus Timeout Timer - */ -t_stat timer2_svc(UNIT *uptr) -{ - struct timer_ctr *ctr; - int32 time; - - ctr = (struct timer_ctr *)uptr->tmr; - - time = ctr->divider * TIMER_STP_US; - - if (time == 0) { - time = TIMER_STP_US; - } - - sim_activate_after_abs(uptr, time); - - return SCPE_OK; -} - -uint32 timer_read(uint32 pa, size_t size) -{ - uint32 reg; - uint16 ctr_val; - uint8 ctrnum; - struct timer_ctr *ctr; - - reg = pa - TIMERBASE; - ctrnum = (reg >> 2) & 0x3; - ctr = &TIMERS[ctrnum]; - - switch (reg) { - case TIMER_REG_DIVA: - case TIMER_REG_DIVB: - case TIMER_REG_DIVC: - ctr_val = ctr->val; - - if (ctr_val != ctr->divider) { - sim_debug(READ_MSG, &timer_dev, - "[%08x] >>> ctr_val = %04x, ctr->divider = %04x\n", - R[NUM_PC], ctr_val, ctr->divider); - } - - switch (ctr->mode & CLK_RW) { - case CLK_LSB: - return ctr_val & 0xff; - case CLK_MSB: - return (ctr_val & 0xff00) >> 8; - case CLK_LMB: - if (ctr->lmb) { - ctr->lmb = FALSE; - return (ctr_val & 0xff00) >> 8; - } else { - ctr->lmb = TRUE; - return ctr_val & 0xff; - } - default: - return 0; - } - break; - case TIMER_REG_CTRL: - return ctr->mode; - case TIMER_CLR_LATCH: - /* Clearing the timer latch has a side-effect - of also clearing pending interrupts */ - CLR_INT; - return 0; - default: - /* Unhandled */ - sim_debug(READ_MSG, &timer_dev, - "[%08x] UNHANDLED TIMER READ. ADDR=%08x\n", - R[NUM_PC], pa); - return 0; - } -} - -void handle_timer_write(uint8 ctrnum, uint32 val) -{ - struct timer_ctr *ctr; - - ctr = &TIMERS[ctrnum]; - switch(ctr->mode & 0x30) { - case 0x10: - ctr->divider &= 0xff00; - ctr->divider |= val & 0xff; - ctr->val = ctr->divider; - ctr->enabled = TRUE; - ctr->stime = sim_gtime(); - sim_cancel(timer_clk_unit); - sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US); - break; - case 0x20: - ctr->divider &= 0x00ff; - ctr->divider |= (val & 0xff) << 8; - ctr->val = ctr->divider; - ctr->enabled = TRUE; - ctr->stime = sim_gtime(); - /* Kick the timer to get the new divider value */ - sim_cancel(timer_clk_unit); - sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US); - break; - case 0x30: - if (ctr->lmb) { - ctr->lmb = FALSE; - ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8)); - ctr->val = ctr->divider; - ctr->enabled = TRUE; - ctr->stime = sim_gtime(); - sim_debug(READ_MSG, &timer_dev, - "[%08x] Write timer %d val LMB (MSB): %02x\n", - R[NUM_PC], ctrnum, val & 0xff); - /* Kick the timer to get the new divider value */ - sim_cancel(timer_clk_unit); - sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US); - } else { - ctr->lmb = TRUE; - ctr->divider = (ctr->divider & 0xff00) | (val & 0xff); - ctr->val = ctr->divider; - } - break; - default: - break; - - } -} - -void timer_write(uint32 pa, uint32 val, size_t size) -{ - uint8 reg, ctrnum; - struct timer_ctr *ctr; - - reg = (uint8) (pa - TIMERBASE); - - switch(reg) { - case TIMER_REG_DIVA: - handle_timer_write(0, val); - break; - case TIMER_REG_DIVB: - handle_timer_write(1, val); - break; - case TIMER_REG_DIVC: - handle_timer_write(2, val); - break; - case TIMER_REG_CTRL: - /* The counter number is in bits 6 and 7 */ - ctrnum = (val >> 6) & 3; - if (ctrnum > 2) { - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] WARNING: Write to invalid counter: %d\n", - R[NUM_PC], ctrnum); - return; - } - ctr = &TIMERS[ctrnum]; - ctr->mode = (uint8) val; - ctr->enabled = FALSE; - ctr->lmb = FALSE; - break; - case TIMER_CLR_LATCH: - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] unexpected write to clear timer latch\n", - R[NUM_PC]); - break; - } -} - -void timer_tick() -{ - if (TIMERS[0].gate && TIMERS[0].enabled) { - TIMERS[0].val = TIMERS[0].divider - 1; - } - - if (TIMERS[1].gate && TIMERS[1].enabled) { - TIMERS[1].val = TIMERS[1].divider - 1; - } - - if (TIMERS[2].gate && TIMERS[2].enabled) { - TIMERS[2].val = TIMERS[2].divider - 1; - } -} diff --git a/3B2/3b2_rev2_timer.h b/3B2/3b2_rev2_timer.h deleted file mode 100644 index d30c0202..00000000 --- a/3B2/3b2_rev2_timer.h +++ /dev/null @@ -1,72 +0,0 @@ -/* 3b2_rev2_timer.h: 8253 Interval Timer - - Copyright (c) 2017, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. -*/ - -#ifndef _3B2_REV2_TIMER_H_ -#define _3B2_REV2_TIMER_H_ - -#include "sim_defs.h" - -#define TIMER_STP_US 1 -#define tmrnum u3 -#define tmr up7 - -#define TIMER_REG_DIVA 0x03 -#define TIMER_REG_DIVB 0x07 -#define TIMER_REG_DIVC 0x0b -#define TIMER_REG_CTRL 0x0f -#define TIMER_CLR_LATCH 0x13 - -#define CLK_RW 0x30 -#define CLK_LSB 0x10 -#define CLK_MSB 0x20 -#define CLK_LMB 0x30 - -struct timer_ctr { - uint16 divider; - uint16 val; - uint8 mode; - t_bool lmb; - t_bool enabled; - t_bool gate; - double stime; /* Most recent start time of counter */ -}; - -t_stat timer_reset(DEVICE *dptr); -uint32 timer_read(uint32 pa, size_t size); -void timer_write(uint32 pa, uint32 val, size_t size); -void timer_tick(); -t_stat timer0_svc(UNIT *uptr); -t_stat timer1_svc(UNIT *uptr); -t_stat timer2_svc(UNIT *uptr); -t_stat timer_set_shutdown(UNIT *uptr, int32 val, CONST char *cptr, void *desc); -void timer_disable(uint8 ctrnum); -void timer_enable(uint8 ctrnum); - -#endif /* _3B2_REV2_TIMER_H_ */ diff --git a/3B2/3b2_rev3_csr.c b/3B2/3b2_rev3_csr.c index 3b6afe57..148e23f7 100644 --- a/3B2/3b2_rev3_csr.c +++ b/3B2/3b2_rev3_csr.c @@ -1,6 +1,6 @@ -/* 3b2_rev3_csr.c: AT&T 3B2/600G Control and Status Register +/* 3b2_rev3_csr.c: CM518B System Board Control, Status & Error Register - Copyright (c) 2020, Seth J. Morabito + Copyright (c) 2020-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -32,8 +32,9 @@ #include "3b2_csr.h" #include "3b2_if.h" #include "3b2_timer.h" +#include "3b2_sys.h" -uint32 csr_data; +CSR_DATA csr_data; BITFIELD csr_bits[] = { BIT(UTIM), @@ -104,8 +105,14 @@ t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw) t_stat csr_reset(DEVICE *dptr) { - /* Accordig to the technical reference manual, the CSR is NOT - cleared on reset */ + CSRBIT(CSRFECC, TRUE); + CSRBIT(CSRTHERM, FALSE); + CSRBIT(CSRITIM, TRUE); + CSRBIT(CSRISTIM, TRUE); + CSRBIT(CSRIBUB, TRUE); + CSRBIT(CSRPWRSPDN, FALSE); + CSRBIT(CSRFLPMO, TRUE); + return SCPE_OK; } @@ -124,8 +131,8 @@ uint32 csr_read(uint32 pa, size_t size) return (csr_data >> 24) & 0xff; default: sim_debug(WRITE_MSG, &csr_dev, - "[%08x] CSR READ. Warning, unexpected register = %02x)\n", - R[NUM_PC], reg); + "CSR READ. Warning, unexpected register = %02x)\n", + reg); return 0; } } @@ -174,36 +181,15 @@ void csr_write(uint32 pa, uint32 val, size_t size) break; case 0x1c: CSRBIT(CSRITIM, val); - sim_debug(WRITE_MSG, &csr_dev, - "[%08x] CSR WRITE. Inhibit Interval Timer = %d\n", - R[NUM_PC], val); - if (csr_data & CSRITIM) { - timer_disable(TIMER_INTERVAL); - } else { - timer_enable(TIMER_INTERVAL); - } + timer_gate(TIMER_INTERVAL, CSR(CSRITIM)); break; case 0x20: CSRBIT(CSRISTIM, val); - sim_debug(WRITE_MSG, &csr_dev, - "[%08x] CSR WRITE. Inhibit Sanity Timer = %d\n", - R[NUM_PC], val); - if (csr_data & CSRISTIM) { - timer_disable(TIMER_SANITY); - } else { - timer_enable(TIMER_SANITY); - } + timer_gate(TIMER_SANITY, CSR(CSRISTIM)); break; case 0x24: CSRBIT(CSRITIMO, val); - sim_debug(WRITE_MSG, &csr_dev, - "[%08x] CSR WRITE. Inhibit Bus Timer = %d\n", - R[NUM_PC], val); - if (csr_data & CSRITIMO) { - timer_disable(TIMER_BUS); - } else { - timer_enable(TIMER_BUS); - } + timer_gate(TIMER_BUS, CSR(CSRITIMO)); break; case 0x28: CSRBIT(CSRICPUFLT, val); @@ -219,6 +205,9 @@ void csr_write(uint32 pa, uint32 val, size_t size) break; case 0x38: CSRBIT(CSRFECC, val); + sim_debug(WRITE_MSG, &csr_dev, + "CSR WRITE. Force ECC Syndrome = %d\n", + val); break; case 0x3c: CSRBIT(CSRTHERM, val); @@ -229,6 +218,10 @@ void csr_write(uint32 pa, uint32 val, size_t size) break; case 0x44: CSRBIT(CSRPWRSPDN, val); + if (!val) { + /* Stop the simulator - power down */ + stop_reason = STOP_POWER; + } break; case 0x48: CSRBIT(CSRFLPFST, val); @@ -290,6 +283,7 @@ void csr_write(uint32 pa, uint32 val, size_t size) break; case 0x7c: /* System reset request */ + full_reset(); cpu_boot(0, &cpu_dev); break; default: diff --git a/3B2/3b2_rev3_csr.h b/3B2/3b2_rev3_csr.h index 292efdd9..17078bed 100644 --- a/3B2/3b2_rev3_csr.h +++ b/3B2/3b2_rev3_csr.h @@ -1,6 +1,6 @@ -/* 3b2_rev3_csr.h: AT&T 3B2/600G Control and Status Register +/* 3b2_rev3_csr.h: CM518B System Board Control, Status & Error Register - Copyright (c) 2020, Seth J. Morabito + Copyright (c) 2020-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,6 +33,8 @@ #include "3b2_defs.h" +typedef uint32 CSR_DATA; + t_stat csr_svc(UNIT *uptr); t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw); t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw); @@ -40,6 +42,4 @@ t_stat csr_reset(DEVICE *dptr); uint32 csr_read(uint32 pa, size_t size); void csr_write(uint32 pa, uint32 val, size_t size); -extern uint32 csr_data; - #endif diff --git a/3B2/3b2_rev3_defs.h b/3B2/3b2_rev3_defs.h index 2c5289dd..31cd7006 100644 --- a/3B2/3b2_rev3_defs.h +++ b/3B2/3b2_rev3_defs.h @@ -1,6 +1,6 @@ - /* 3b2_rev3_defs.h: AT&T 3B2 Rev 3 (Model 600G) Simulator Definitions +/* 3b2_rev3_defs.h: Veresion 3 (3B2/700) Common Definitions - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -90,14 +90,6 @@ #define INT_MAP_LEN 0x2000 -/* Memory */ -#define MEMID_4M 6 -#define MEMID_16M 7 -#define MADDR_SLOT_0 0x4d000 -#define MADDR_SLOT_1 0x4d004 -#define MADDR_SLOT_2 0x4d008 -#define MADDR_SLOT_3 0x4d00c - #define IFCSRBASE 0x40000 #define IFCSRSIZE 0x100 #define TIMERBASE 0x41000 @@ -121,9 +113,9 @@ #define MMUBASE 0x4f000 #define MMUSIZE 0x1000 #define FLTLBASE 0x4c000 -#define FLTLSIZE 0x10 +#define FLTLSIZE 0x1000 #define FLTHBASE 0x4d000 -#define FLTHSIZE 0x10 +#define FLTHSIZE 0x1000 #define VCACHE_BOTTOM 0x1c00000 #define VCACHE_TOP 0x2000000 diff --git a/3B2/3b2_rev3_mau.c b/3B2/3b2_rev3_mau.c deleted file mode 100644 index 53fcc2b6..00000000 --- a/3B2/3b2_rev3_mau.c +++ /dev/null @@ -1,31 +0,0 @@ -/* 3b2_rev3_mau.c: WE32206 Math Accelration Unit Implementation - - Copyright (c) 2021, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. -*/ - -/* Stub file. Not Yet Implemented. */ diff --git a/3B2/3b2_rev3_mau.h b/3B2/3b2_rev3_mau.h deleted file mode 100644 index 9b54bb2b..00000000 --- a/3B2/3b2_rev3_mau.h +++ /dev/null @@ -1,36 +0,0 @@ -/* 3b2_rev3_mau.h: WE32206 Math Accelration Unit Header - - Copyright (c) 2021, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. -*/ - -#ifndef _3B2_REV3_MAU_H_ -#define _3B2_REV3_MAU_H_ - -/* Stub file. Not Yet Implemented. */ - -#endif diff --git a/3B2/3b2_rev3_mmu.c b/3B2/3b2_rev3_mmu.c index 898be53b..ccfc6eda 100644 --- a/3B2/3b2_rev3_mmu.c +++ b/3B2/3b2_rev3_mmu.c @@ -1,6 +1,6 @@ -/* 3b2_rev3_mmu.c: AT&T 3B2/600G MMU (WE32201) +/* 3b2_rev3_mmu.c: WE32201 MMU - Copyright (c) 2020, Seth J. Morabito + Copyright (c) 2020-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -188,8 +188,8 @@ static void put_sdce(uint32 va, uint32 sd_hi, uint32 sd_lo) mmu_state.sdcl[ci] = SD_TO_SDCL(sd_lo, va); sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] CACHED SD AT IDX %d. va=%08x sd_hi=%08x sd_lo=%08x sdc_hi=%08x sdc_lo=%08x\n", - R[NUM_PC], ci, va, sd_hi, sd_lo, mmu_state.sdch[ci], mmu_state.sdcl[ci]); + "CACHED SD AT IDX %d. va=%08x sd_hi=%08x sd_lo=%08x sdc_hi=%08x sdc_lo=%08x\n", + ci, va, sd_hi, sd_lo, mmu_state.sdch[ci], mmu_state.sdcl[ci]); } @@ -236,8 +236,8 @@ static t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc, uint32 *pdc_idx) *pd_acc = (mmu_state.pdcl[i] >> 24) & 0xff; *pdc_idx = i; sim_debug(MMU_TRACE_DBG, &mmu_dev, - "[%08x] PDC HIT. va=%08x idx=%d tag=%03x pd=%08x pdcl=%08x pdch=%08x\n", - R[NUM_PC], va, i, key_tag, *pd, + "PDC HIT. va=%08x idx=%d tag=%03x pd=%08x pdcl=%08x pdch=%08x\n", + va, i, key_tag, *pd, mmu_state.pdcl[i], mmu_state.pdch[i]); set_u_bit(i); return SCPE_OK; @@ -245,8 +245,8 @@ static t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc, uint32 *pdc_idx) } sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] PDC MISS. va=%08x tag=%03x\n", - R[NUM_PC], va, key_tag); + "PDC MISS. va=%08x tag=%03x\n", + va, key_tag); return SCPE_NXM; } @@ -259,8 +259,8 @@ static void put_pdce_at(uint32 va, uint32 sd_lo, uint32 pd, uint32 slot) mmu_state.pdcl[slot] = PD_TO_PDCL(pd, sd_lo); mmu_state.pdch[slot] = VA_TO_PDCH(va, sd_lo); sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Caching MMU PDC entry at index %d (pdc_hi=%08x pdc_lo=%08x va=%08x)\n", - R[NUM_PC], slot, mmu_state.pdch[slot], mmu_state.pdcl[slot], va); + "Caching MMU PDC entry at index %d (pdc_hi=%08x pdc_lo=%08x va=%08x)\n", + slot, mmu_state.pdch[slot], mmu_state.pdcl[slot], va); set_u_bit(slot); mmu_state.last_cached = slot; } @@ -279,8 +279,7 @@ static uint32 put_pdce(uint32 va, uint32 sd_lo, uint32 pd) */ if (mmu_state.flush_u) { sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing PDC U bits on all-set condition.\n", - R[NUM_PC]); + "Flushing PDC U bits on all-set condition.\n"); mmu_state.flush_u = FALSE; for (i = 0; i < MMU_PDCS; i++) { if (i != mmu_state.last_cached) { @@ -332,16 +331,14 @@ static void flush_pdc(uint32 va) target_tag = mmu_state.pdch[i] & PDC_TAG_MASK; if (target_tag == key_tag) { sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU PDC entry pdc_lo=%08x pdc_hi=%08x index %d (va=%08x)\n", - R[NUM_PC], + "Flushing MMU PDC entry pdc_lo=%08x pdc_hi=%08x index %d (va=%08x)\n", mmu_state.pdcl[i], mmu_state.pdch[i], i, va); if (mmu_state.pdch[i] & PDC_C_MASK) { sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU PDC entry: CONTIGUOUS\n", - R[NUM_PC]); + "Flushing MMU PDC entry: CONTIGUOUS\n"); /* If this PD came from a contiguous SD, we need to * flush ALL entries belonging to the same SD. All * pages within the same segment have the same upper @@ -361,8 +358,8 @@ static void flush_pdc(uint32 va) } sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU PDC entry: NOT FOUND (va=%08x key_tag=%08x)\n", - R[NUM_PC], va, key_tag); + "Flushing MMU PDC entry: NOT FOUND (va=%08x key_tag=%08x)\n", + va, key_tag); } @@ -374,8 +371,7 @@ static void flush_caches() uint32 i; sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU PDC and SDC\n", - R[NUM_PC]); + "Flushing MMU PDC and SDC\n"); for (i = 0; i < MMU_SDCS; i++) { mmu_state.sdch[i] &= ~SDC_G_MASK; @@ -399,13 +395,16 @@ static t_stat mmu_check_perm(uint8 flags, uint8 r_acc) case 0: /* No Access */ return SCPE_NXM; case 1: /* Exec Only */ - if (r_acc != ACC_IF && r_acc != ACC_IFAD) { + if (r_acc != ACC_IF && + r_acc != ACC_IFAD) { return SCPE_NXM; } return SCPE_OK; case 2: /* Read / Execute */ - if (r_acc != ACC_IF && r_acc != ACC_IFAD && - r_acc != ACC_AF && r_acc != ACC_OF && + if (r_acc != ACC_IF && + r_acc != ACC_IFAD && + r_acc != ACC_OF && + r_acc != ACC_AF && r_acc != ACC_MT) { return SCPE_NXM; } @@ -415,29 +414,6 @@ static t_stat mmu_check_perm(uint8 flags, uint8 r_acc) } } -/* - * Internally, the ID Number Cache is a fully associative cache with a - * tag consisting of the upper 29 bits of the segment address, plus a - * U bit indicating that the ID is usable. The value (the ID number) - * is the fixed index of the tag within the cache. - */ -static t_stat get_idnc(uint32 va, uint8 *id) -{ - uint32 i, tag, entry; - - tag = IDNC_TAG(va); - - for (i = 0; i < MMU_IDNCS; i++) { - entry = mmu_state.idnc[i]; - if ((IDNC_TAG(entry) == tag) && IDNC_U(entry)) { - *id = ((uint8)i & 0xff); - return SCPE_OK; - } - } - - return SCPE_NXM; -} - /* * Initialize the MMU device */ @@ -465,56 +441,56 @@ uint32 mmu_read(uint32 pa, size_t size) case MMU_SDCL: data = mmu_state.sdcl[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_SDCL[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_SDCL[%d] = %08x\n", + index, data); break; case MMU_SDCH: data = mmu_state.sdch[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_SDCH[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_SDCH[%d] = %08x\n", + index, data); break; case MMU_PDCL: data = mmu_state.pdcl[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_PDCL[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_PDCL[%d] = %08x\n", + index, data); break; case MMU_PDCH: data = mmu_state.pdch[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_PDCH[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_PDCH[%d] = %08x\n", + index, data); break; case MMU_SRAMA: data = mmu_state.sra[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_SRAMA[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_SRAMA[%d] = %08x\n", + index, data); break; case MMU_SRAMB: data = mmu_state.srb[index]; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_SRAMB[%d] = %08x\n", - R[NUM_PC], index, data); + "MMU_SRAMB[%d] = %08x\n", + index, data); break; case MMU_FC: data = mmu_state.fcode; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_FC = %08x\n", - R[NUM_PC], data); + "MMU_FC = %08x\n", + data); break; case MMU_FA: data = mmu_state.faddr; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_FA = %08x\n", - R[NUM_PC], data); + "MMU_FA = %08x\n", + data); break; case MMU_CONF: data = mmu_state.conf; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", - R[NUM_PC], data, + "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", + data, MMU_CONF_M, MMU_CONF_R, MMU_CONF_C, @@ -525,37 +501,36 @@ uint32 mmu_read(uint32 pa, size_t size) case MMU_VAR: data = mmu_state.var; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_VAR = %08x\n", - R[NUM_PC], data); + "MMU_VAR = %08x\n", + data); break; case MMU_IDC: /* TODO: Implement */ data = 0; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_IDC\n", R[NUM_PC]); + "MMU_IDC\n"); break; case MMU_IDNR: /* TODO: Implement */ data = 0; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_IDNR\n", R[NUM_PC]); + "MMU_IDNR\n"); break; case MMU_FIDNR: /* TODO: Implement */ data = 0; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_FIDNR\n", R[NUM_PC]); + "MMU_FIDNR\n"); break; case MMU_VR: - /* Simply not faulting here is good enough */ - data = 0; + data = MMU_REV3_VER; sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] MMU_VR\n", R[NUM_PC]); + "MMU_VR = 0x23\n"); break; default: sim_debug(MMU_READ_DBG, &mmu_dev, - "[%08x] Invalid MMU register: pa=%08x\n", - R[NUM_PC], pa); + "Invalid MMU register: pa=%08x\n", + pa); CSRBIT(CSRTIMO, TRUE); break; } @@ -600,15 +575,14 @@ void mmu_write(uint32 pa, uint32 val, size_t size) break; case MMU_FDCR: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_FDCR\n", - R[NUM_PC]); + "MMU_FDCR\n"); /* Data cache is not implemented */ break; case MMU_SRAMA: index = index & 3; sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_SRAMA[%d] = %08x\n", - R[NUM_PC], index, val); + "MMU_SRAMA[%d] = %08x\n", + index, val); mmu_state.sra[index] = val; mmu_state.sec[index].addr = val & 0xfffffffc; @@ -616,10 +590,9 @@ void mmu_write(uint32 pa, uint32 val, size_t size) for (i = 0; i < MMU_SDCS; i++) { if (((mmu_state.sdcl[i] >> 10) & 0x3) == index) { sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU SDC entry at index %d " + "Flushing MMU SDC entry at index %d " "(sdc_lo=%08x sdc_hi=%08x)\n", - R[NUM_PC], i, - mmu_state.sdcl[i], mmu_state.sdch[i]); + i, mmu_state.sdcl[i], mmu_state.sdch[i]); mmu_state.sdch[i] &= ~(SDC_G_MASK); } } @@ -637,8 +610,7 @@ void mmu_write(uint32 pa, uint32 val, size_t size) mmu_state.sec[index].len = (val >> 10) & 0x1fff; /* We do not flush the cache on writing SRAMB */ sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_SRAMB[%d] length=%04x (%d segments)\n", - R[NUM_PC], + "MMU_SRAMB[%d] length=%04x (%d segments)\n", index, mmu_state.sec[index].len, mmu_state.sec[index].len + 1); @@ -647,20 +619,20 @@ void mmu_write(uint32 pa, uint32 val, size_t size) /* Set a default value */ mmu_state.fcode = (((CPU_CM) << 5) | (0xa << 7)); sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_FC = %08x\n", - R[NUM_PC], mmu_state.fcode); + "MMU_FC = %08x\n", + mmu_state.fcode); break; case MMU_FA: mmu_state.faddr = val; sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_FADDR = %08x\n", - R[NUM_PC], val); + "MMU_FADDR = %08x\n", + val); break; case MMU_CONF: mmu_state.conf = val & 0x7f; sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", - R[NUM_PC], val, + "MMU_CONF = %02x (M=%d R=%d $=%d PS=%d MCE=%d DCE=%d)\n", + val, MMU_CONF_M, MMU_CONF_R, MMU_CONF_C, @@ -671,14 +643,13 @@ void mmu_write(uint32 pa, uint32 val, size_t size) case MMU_VAR: mmu_state.var = val; sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_VAR = %08x\n", - R[NUM_PC], val); + "MMU_VAR = %08x\n", val); if ((mmu_state.sdcl[SDC_IDX(val)] & SDC_VADDR_MASK) == ((val >> 20) & SDC_VADDR_MASK)) { sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Flushing MMU SDC entry at index %d " + "Flushing MMU SDC entry at index %d " "(sdc_lo=%08x sdc_hi=%08x)\n", - R[NUM_PC], SDC_IDX(val), + SDC_IDX(val), mmu_state.sdcl[SDC_IDX(val)], mmu_state.sdch[SDC_IDX(val)]); mmu_state.sdch[SDC_IDX(val)] &= ~SDC_G_MASK; @@ -687,28 +658,28 @@ void mmu_write(uint32 pa, uint32 val, size_t size) break; case MMU_IDC: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_IDC = %08x\n", - R[NUM_PC], val); + "MMU_IDC = %08x\n", + val); break; case MMU_IDNR: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_IDNR = %08x\n", - R[NUM_PC], val); + "MMU_IDNR = %08x\n", + val); break; case MMU_FIDNR: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_FIDNR = %08x\n", - R[NUM_PC], val); + "MMU_FIDNR = %08x\n", + val); break; case MMU_VR: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] MMU_VR = %08x\n", - R[NUM_PC], val); + "MMU_VR = %08x\n", + val); break; default: sim_debug(MMU_WRITE_DBG, &mmu_dev, - "[%08x] UNHANDLED WRITE (entity=0x%x, index=0x%x, val=%08x)\n", - R[NUM_PC], entity, index, val); + "UNHANDLED WRITE (entity=0x%x, index=0x%x, val=%08x)\n", + entity, index, val); break; } } @@ -726,8 +697,8 @@ static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool update_sdc = FALSE; } - sd_lo = pread_w(SD_ADDR(va)); - sd_hi = pread_w(SD_ADDR(va) + 4); + sd_lo = pread_w(SD_ADDR(va), BUS_PER); + sd_hi = pread_w(SD_ADDR(va) + 4, BUS_PER); if (MMU_CONF_M && r_acc == ACC_W && (mmu_state.sdcl[SDC_IDX(va)] & SDC_M_MASK) == 0) { if (update_sdc) { @@ -736,13 +707,12 @@ static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] MMU R&M Update Fault (M)\n", - R[NUM_PC]); + "MMU R&M Update Fault (M)\n"); MMU_FAULT(MMU_F_RM_UPD); return SCPE_NXM; } - pwrite_w(SD_ADDR(va), sd_lo | SD_M_MASK); + pwrite_w(SD_ADDR(va), sd_lo | SD_M_MASK, BUS_PER); } if (MMU_CONF_R && (mmu_state.sdcl[SDC_IDX(va)] & SDC_R_MASK) == 0) { @@ -752,13 +722,12 @@ static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] MMU R&M Update Fault (R)\n", - R[NUM_PC]); + "MMU R&M Update Fault (R)\n"); MMU_FAULT(MMU_F_RM_UPD); return SCPE_NXM; } - pwrite_w(SD_ADDR(va), sd_lo | SD_R_MASK); + pwrite_w(SD_ADDR(va), sd_lo | SD_R_MASK, BUS_PER); } if (!SD_CONTIG(sd_lo)) { @@ -766,14 +735,14 @@ static t_stat mmu_update_history(uint32 va, uint8 r_acc, uint32 pdc_idx, t_bool if (r_acc == ACC_W && (mmu_state.pdcl[pdc_idx] & PDC_M_MASK) == 0) { mmu_state.pdcl[pdc_idx] |= PDC_M_MASK; - pd = pread_w(pd_addr); - pwrite_w(pd_addr, pd | PD_M_MASK); + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd | PD_M_MASK, BUS_PER); } if ((mmu_state.pdcl[pdc_idx] & PDC_R_MASK) == 0) { mmu_state.pdcl[pdc_idx] |= PDC_R_MASK; - pd = pread_w(pd_addr); - pwrite_w(pd_addr, pd | PD_R_MASK); + pd = pread_w(pd_addr, BUS_PER); + pwrite_w(pd_addr, pd | PD_R_MASK, BUS_PER); } } @@ -821,8 +790,8 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, * checked because SSL out of bounds is a fatal error. */ if (SSL(va) > SRAMB_LEN(va)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", - R[NUM_PC], SRAMB_LEN(va), SSL(va), va); + "SDT Length Fault. sramb_len=%x ssl=%x va=%08x\n", + SRAMB_LEN(va), SSL(va), va); MMU_FAULT(MMU_F_SDTLEN); return SCPE_NXM; } @@ -836,17 +805,17 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, * memory. */ sdc_miss = TRUE; - sd_lo = pread_w(sd_ptr); /* Control Bits */ - sd_hi = pread_w(sd_ptr + 4); /* Address Bits */ + sd_lo = pread_w(sd_ptr, BUS_PER); /* Control Bits */ + sd_hi = pread_w(sd_ptr + 4, BUS_PER); /* Address Bits */ sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] SDC miss. Read sd_ptr=%08x sd_lo=%08x sd_hi=%08x va=%08x\n", - R[NUM_PC], sd_ptr, sd_lo, sd_hi, va); + "SDC miss. Read sd_ptr=%08x sd_lo=%08x sd_hi=%08x va=%08x\n", + sd_ptr, sd_lo, sd_hi, va); } if (!SD_VALID(sd_lo)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Invalid Segment Descriptor. va=%08x sd_hi=%08x sd_lo=%08x\n", - R[NUM_PC], va, sd_hi, sd_lo); + "Invalid Segment Descriptor. va=%08x sd_hi=%08x sd_lo=%08x\n", + va, sd_hi, sd_lo); MMU_FAULT(MMU_F_INV_SD); return SCPE_NXM; } @@ -854,8 +823,8 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, if (SD_INDIRECT(sd_lo)) { if (++indirect_count > MAX_INDIRECTS) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Max Indirects Fault. va=%08x sd_hi=%08x sd_lo=%08x\n", - R[NUM_PC], va, sd_hi, sd_lo); + "Max Indirects Fault. va=%08x sd_hi=%08x sd_lo=%08x\n", + va, sd_hi, sd_lo); MMU_FAULT(MMU_F_INDIRECT); return SCPE_NXM; } @@ -863,8 +832,8 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, /* Any permission failure at this point is actually an MMU_F_MISS_MEM */ if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] MMU Miss Processing Memory Fault (SD Access) (ckm=%d pd_acc=%02x r_acc=%02x)\n", - R[NUM_PC], CPU_CM, SD_ACC(sd_lo), r_acc); + "MMU Miss Processing Memory Fault (SD Access) (ckm=%d pd_acc=%02x r_acc=%02x)\n", + CPU_CM, SD_ACC(sd_lo), r_acc); MMU_FAULT(MMU_F_MISS_MEM); return SCPE_NXM; } @@ -872,8 +841,8 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, /* sd_hi is a pointer to a new segment descriptor */ sd_ptr = sd_hi; - sd_lo = pread_w(sd_ptr); - sd_hi = pread_w(sd_ptr + 4); + sd_lo = pread_w(sd_ptr, BUS_PER); + sd_hi = pread_w(sd_ptr + 4, BUS_PER); } else { /* If it's not an indirection, we're done. */ break; @@ -886,14 +855,14 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, fault; otherwise, it's a PDT NOT PRESENT fault. */ if (SD_CONTIG(sd_lo)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Segment Not Present. va=%08x\n", - R[NUM_PC], va); + "Segment Not Present. va=%08x\n", + va); MMU_FAULT(MMU_F_SEG_NOT_PRES); return SCPE_NXM; } else { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] PDT Not Present. va=%08x\n", - R[NUM_PC], va); + "PDT Not Present. va=%08x\n", + va); MMU_FAULT(MMU_F_PDT_NOT_PRES); return SCPE_NXM; } @@ -903,16 +872,16 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, if (SD_CONTIG(sd_lo)) { if (PSL(va) > SD_MAX_OFF(sd_lo)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Segment Offset Fault. va=%08x\n", - R[NUM_PC], va); + "Segment Offset Fault. va=%08x\n", + va); MMU_FAULT(MMU_F_SEG_OFFSET); return SCPE_NXM; } } else { if ((va & 0x1ffff) > MAX_SEG_OFF(sd_lo)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] PDT Length Fault. va=%08x max_seg_off=0x%x\n", - R[NUM_PC], va, MAX_SEG_OFF(sd_lo)); + "PDT Length Fault. va=%08x max_seg_off=0x%x\n", + va, MAX_SEG_OFF(sd_lo)); MMU_FAULT(MMU_F_PDTLEN); return SCPE_NXM; } @@ -923,8 +892,8 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, /* TODO: VERIFY */ if (mmu_check_perm(SD_ACC(sd_lo), r_acc) != SCPE_OK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] [AFTER DISCONTINUITY] Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", - R[NUM_PC], va, CPU_CM, SD_ACC(sd_lo), r_acc); + "[AFTER DISCONTINUITY] Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", + va, CPU_CM, SD_ACC(sd_lo), r_acc); MMU_FAULT(MMU_F_ACC); return SCPE_NXM; } @@ -936,31 +905,31 @@ t_stat mmu_pdc_miss(uint32 va, uint8 r_acc, t_bool fc, 1); /* P bit */ sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Contiguous Segment. Constructing PD. PSIZE=%d va=%08x sd_hi=%08x sd_lo=%08x pd=%08x\n", - R[NUM_PC], MMU_CONF_PS, va, sd_hi, sd_lo, *pd); + "Contiguous Segment. Constructing PD. PSIZE=%d va=%08x sd_hi=%08x sd_lo=%08x pd=%08x\n", + MMU_CONF_PS, va, sd_hi, sd_lo, *pd); } else { /* We can find the PD in main memory */ pd_addr = SD_SEG_ADDR(sd_hi) + (PSL(va) * 4); - *pd = pread_w(pd_addr); + *pd = pread_w(pd_addr, BUS_PER); sim_debug(MMU_CACHE_DBG, &mmu_dev, - "[%08x] Paged Segment. Loaded PD. va=%08x sd_hi=%08x sd_lo=%08x pd_addr=%08x pd=%08x\n", - R[NUM_PC], va, sd_hi, sd_lo, pd_addr, *pd); + "Paged Segment. Loaded PD. va=%08x sd_hi=%08x sd_lo=%08x pd_addr=%08x pd=%08x\n", + va, sd_hi, sd_lo, pd_addr, *pd); } if (r_acc == ACC_W && (*pd & PD_W_MASK)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Page Write Fault, pd=%08x va=%08x\n", - R[NUM_PC], *pd, va); + "Page Write Fault, pd=%08x va=%08x\n", + *pd, va); MMU_FAULT(MMU_F_PW); return SCPE_NXM; } if ((*pd & PD_P_MASK) != PD_P_MASK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Page Not Present Fault. pd=%08x va=%08x\n", - R[NUM_PC], *pd, va); + "Page Not Present Fault. pd=%08x va=%08x\n", + *pd, va); MMU_FAULT(MMU_F_PAGE_NOT_PRES); return SCPE_NXM; } @@ -1018,16 +987,16 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) if (get_pdce(va, &pd, &pd_acc, &pdc_idx) == SCPE_OK) { if (mmu_check_perm(pd_acc, r_acc) != SCPE_OK) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", - R[NUM_PC], va, CPU_CM, pd_acc, r_acc); + "Access to Memory Denied (va=%08x ckm=%d pd_acc=%02x r_acc=%02x)\n", + va, CPU_CM, pd_acc, r_acc); MMU_FAULT(MMU_F_ACC); return SCPE_NXM; } if (r_acc == ACC_W && (pd & PD_W_MASK)) { sim_debug(MMU_FAULT_DBG, &mmu_dev, - "[%08x] Page Write Fault, pd=%08x va=%08x\n", - R[NUM_PC], pd, va); + "Page Write Fault, pd=%08x va=%08x\n", + pd, va); MMU_FAULT(MMU_F_PW); return SCPE_NXM; } @@ -1053,8 +1022,8 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa) *pa = PD_ADDR(pd) + POT(va); sim_debug(MMU_TRACE_DBG, &mmu_dev, - "[%08x] XLATE DONE. r_acc=%d va=%08x pa=%08x\n", - R[NUM_PC], r_acc, va, *pa); + "XLATE DONE. r_acc=%d va=%08x pa=%08x\n", + r_acc, va, *pa); return SCPE_OK; } @@ -1189,8 +1158,8 @@ t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc) fprintf(st, "-------- -------- -------- -------- --- --------- ------\n"); for (i = 0; i < len; i++) { - sd_lo = pread_w(addr + (i * 8)) & SD_RES_MASK; - sd_hi = pread_w(addr + (i * 8) + 4); + sd_lo = pread_w(addr + (i * 8), BUS_PER) & SD_RES_MASK; + sd_hi = pread_w(addr + (i * 8) + 4, BUS_PER); base = (sec << 14 | i << 1) << 16; pages = ((sd_lo & SD_MAX_OFF_MASK) >> 18) + 1; diff --git a/3B2/3b2_rev3_mmu.h b/3B2/3b2_rev3_mmu.h index a946553a..e538d785 100644 --- a/3B2/3b2_rev3_mmu.h +++ b/3B2/3b2_rev3_mmu.h @@ -1,6 +1,6 @@ -/* 3b2_rev3_mmu.h: AT&T 3B2/600G MMU (WE32201) Header +/* 3b2_rev3_mmu.h: WE32201 MMU - Copyright (c) 2020, Seth J. Morabito + Copyright (c) 2020-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -28,8 +28,8 @@ from the author. */ -#ifndef _3B2_1000_MMU_H_ -#define _3B2_1000_MMU_H_ +#ifndef _3B2_REV3_MMU_H_ +#define _3B2_REV3_MMU_H_ #include "3b2_defs.h" @@ -55,6 +55,8 @@ #define MMU_FIDNR 14 /* Flush ID Number Register */ #define MMU_VR 15 /* Version Register */ +#define MMU_REV3_VER 0x23 /* Version byte returned by WE32201 MMU */ + #define MMU_CONF_M (mmu_state.conf & 0x1) #define MMU_CONF_R ((mmu_state.conf & 0x2) >> 1) #define MMU_CONF_C ((mmu_state.conf & 0x4) >> 1) @@ -353,4 +355,4 @@ t_stat mmu_show_sdt(FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat mmu_show_sdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); t_stat mmu_show_pdc(FILE *st, UNIT *uptr, int32 val, CONST void *desc); -#endif /* _3B2_1000_MMU_H_ */ +#endif /* _3B2_REV3_MMU_H_ */ diff --git a/3B2/3b2_rev3_sys.c b/3B2/3b2_rev3_sys.c index 8c32ba46..ce1645d2 100644 --- a/3B2/3b2_rev3_sys.c +++ b/3B2/3b2_rev3_sys.c @@ -1,6 +1,6 @@ -/* 3b2_rev3_sys.c: AT&T 3B2/600G system definition +/* 3b2_rev3_sys.c: Version 3 (3B2/700) System Definition - Copyright (c) 2020, Seth J. Morabito + Copyright (c) 2020-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -41,7 +41,7 @@ #include "3b2_stddev.h" #include "3b2_timer.h" -char sim_name[] = "AT&T 3B2/600G"; +char sim_name[] = "AT&T 3B2/700"; DEVICE *sim_devices[] = { &cpu_dev, diff --git a/3B2/3b2_rev3_timer.h b/3B2/3b2_rev3_timer.h deleted file mode 100644 index c4379763..00000000 --- a/3B2/3b2_rev3_timer.h +++ /dev/null @@ -1,79 +0,0 @@ -/* 3b2_rev2_stddev.h: 82C54 Interval Timer. - - Copyright (c) 2021, Seth J. Morabito - - 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 OR COPYRIGHT HOLDERS - 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 name of the author 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 author. -*/ - -#ifndef _3B2_REV3_TIMER_H_ -#define _3B2_REV3_TIMER_H_ - -#include "3b2_defs.h" - -/* Timer definitions */ -#define TIMER_STP_US 1 -#define tmrnum u3 -#define tmr up7 - -#define TIMER_REG_DIVA 0x03 -#define TIMER_REG_DIVB 0x07 -#define TIMER_REG_DIVC 0x0b -#define TIMER_REG_CTRL 0x0f -#define TIMER_CLR_LATCH 0x13 - -#define TIMER_MODE(ctr) (((ctr->ctrl) >> 1) & 7) -#define TIMER_RW(ctr) (((ctr->ctrl) >> 4) & 3) - -#define CLK_LATCH 0 -#define CLK_LSB 1 -#define CLK_MSB 2 -#define CLK_LMB 3 - -struct timer_ctr { - uint16 divider; - uint16 val; - uint8 ctrl_latch; - uint16 cnt_latch; - uint8 ctrl; - t_bool r_lmb; - t_bool w_lmb; - t_bool enabled; - t_bool r_ctrl_latch; - t_bool r_cnt_latch; -}; - -/* 82C54 Timer */ -t_stat timer_reset(DEVICE *dptr); -uint32 timer_read(uint32 pa, size_t size); -void timer_write(uint32 pa, uint32 val, size_t size); -t_stat timer0_svc(UNIT *uptr); -t_stat timer1_svc(UNIT *uptr); -t_stat timer2_svc(UNIT *uptr); -/* void timer_tick(uint8 ctrnum); */ -void timer_disable(uint8 ctrnum); -void timer_enable(uint8 ctrnum); - -#endif /* _3B2_REV3_TIMER_H_ */ diff --git a/3B2/3b2_stddev.c b/3B2/3b2_stddev.c index 6d2dba9f..14989333 100644 --- a/3B2/3b2_stddev.c +++ b/3B2/3b2_stddev.c @@ -1,6 +1,6 @@ -/* 3b2_stddev.c: AT&T 3B2 miscellaneous system board devices. +/* 3b2_stddev.c: Miscellaneous System Board Devices - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -33,14 +33,15 @@ following 3B2 devices: - nvram Non-Volatile RAM - - tod MM58174A Real-Time-Clock - - flt Fault Register + - tod MM58174A and MM58274C Real-Time-Clock + - flt Fault Register (Rev 3 only) */ #include "3b2_stddev.h" #include "3b2_cpu.h" #include "3b2_csr.h" +#include "3b2_timer.h" DEBTAB sys_deb_tab[] = { { "INIT", INIT_MSG, "Init" }, @@ -124,23 +125,19 @@ t_stat nvram_reset(DEVICE *dptr) const char *nvram_description(DEVICE *dptr) { - return "Non-volatile memory, used to store system state between boots.\n"; + return "Non-Volatile RAM.\n"; } t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { - fprintf(st, - "The NVRAM holds system state between boots. On initial startup,\n" - "if no valid NVRAM file is attached, you will see the message:\n" - "\n" - " FW ERROR 1-01: NVRAM SANITY FAILURE\n" - " DEFAULT VALUES ASSUMED\n" - " IF REPEATED, CHECK THE BATTERY\n" - "\n" - "To avoid this message on subsequent boots, attach a new NVRAM file\n" - "with the SIMH command:\n" - "\n" - " sim> ATTACH NVRAM \n"); + fprintf(st, "Non-Volatile RAM\n\n"); + fprintf(st, "The %s device is a small battery-backed, non-volatile RAM\n", dptr->name); + fprintf(st, "used by the 3B2 to hold system configuration and diagnostic data.\n\n"); + fprintf(st, "In order for the simulator to keep track of this data while not\n"); + fprintf(st, "running, the %s device may be attached to a file, e.g.\n\n", dptr->name); + fprintf(st, " sim> ATTACH NVRAM \n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); return SCPE_OK; } @@ -177,7 +174,6 @@ t_stat nvram_detach(UNIT *uptr) return r; } - uint32 nvram_read(uint32 pa, size_t size) { uint32 offset = pa - NVRBASE; @@ -229,25 +225,103 @@ void nvram_write(uint32 pa, uint32 val, size_t size) } /* - * MM58174A Time Of Day Clock + * MM58174A and MM58274C Time Of Day Clock. + * + * In addition to keeping track of time of day in tenths of seconds, + * this device is also used as the simulator's primary calibrated + * real-time clock. It operates at the speed of 100Hz, with every + * tenth step incrementing the time-of-day counter. */ +#define TOD_12H(TD) (((TD)->clkset & 1) == 0) +#define TOD_BCDH(V) (((V) / 10) & 0xf) +#define TOD_BCDL(V) (((V) % 10) & 0xf) +#define TOD_LYEAR(TD) ((TD)->lyear == 0) +#define TOD_LYEAR_INC(TD) \ + do { \ + (TD)->lyear = (((TD)->lyear + 1) & 0x3); \ + (TD)->clkset &= 3; \ + (TD)->clkset |= (TD)->lyear << 2; \ + } while(0) + +#define CTRL_DISABLE 0x4 +#define CLKSET_PM 0x2 +#define FLAG_DATA_CHANGED 0x4 +#define FLAG_INTERRUPT 0x1 +#define MIN_DIFF 5l +#define MAX_DIFF 157680000l + +#define CLK_DELAY 10000 /* 10 milliseconds per tick */ +#define CLK_TPS 100l /* 100 ticks per second */ + +static void tod_resync(UNIT *uptr); +static void tod_tick(UNIT *uptr); +static t_stat tod_svc(UNIT *uptr); +static t_bool tod_enabled; + +int32 tmr_poll = CLK_DELAY; +int32 tmxr_poll = CLK_DELAY; + UNIT tod_unit = { - UDATA(NULL, UNIT_FIX+UNIT_BINK, sizeof(TOD_DATA)) + UDATA(&tod_svc, UNIT_FIX|UNIT_BINK|UNIT_IDLE, sizeof(TOD_DATA)), CLK_DELAY +}; + +REG tod_reg[] = { + { DRDATAD(POLL, tmr_poll, 24, "Calibrated poll interval") }, + { 0 } }; DEVICE tod_dev = { - "TOD", &tod_unit, NULL, NULL, + "TOD", &tod_unit, tod_reg, NULL, 1, 16, 8, 4, 16, 32, NULL, NULL, &tod_reset, NULL, &tod_attach, &tod_detach, - NULL, 0, 0, sys_deb_tab, NULL, NULL, + NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, &tod_help, NULL, NULL, &tod_description }; +/* + * Attempt to re-sync the TOD by catching up (if lagging) and updating + * the current time stored in the TOD state. + * + * Because this process may be expensive when catching up following a + * very long time without the simulator running, the process will + * short-circuit if the delta is longer than 5 years, or if no + * previous time was recorded. + */ +static void tod_resync(UNIT *uptr) +{ + TOD_DATA *td; + time_t delta; + uint32 catchup_ticks; + + if (!(uptr->flags & UNIT_ATT) || uptr->filebuf == NULL) { + return; + } + + td = (TOD_DATA *)uptr->filebuf; + + if (td->time > 0) { + delta = time(NULL) - td->time; + if (delta > MIN_DIFF && delta < MAX_DIFF) { + catchup_ticks = (uint32) delta * CLK_TPS; + sim_debug(EXECUTE_MSG, &tod_dev, + "Catching up with a delta of %ld seconds (%d ticks).\n", + delta, catchup_ticks); + while (catchup_ticks-- > 0) { + tod_tick(&tod_unit); + } + } + } + + td->time = time(NULL); +} + t_stat tod_reset(DEVICE *dptr) { + int32 t; + if (tod_unit.filebuf == NULL) { tod_unit.filebuf = calloc(sizeof(TOD_DATA), 1); if (tod_unit.filebuf == NULL) { @@ -255,6 +329,14 @@ t_stat tod_reset(DEVICE *dptr) } } + /* We start in a running state */ + tod_enabled = TRUE; + + t = sim_rtcn_init_unit(&tod_unit, tod_unit.wait, TMR_CLK); + sim_activate_after(&tod_unit, 1000000/CLK_TPS); + tmr_poll = t; + tmxr_poll = t; + return SCPE_OK; } @@ -288,124 +370,191 @@ t_stat tod_detach(UNIT *uptr) return r; } -/* - * Re-set the tod_data registers based on the current simulated time. - */ -void tod_resync() +static t_stat tod_svc(UNIT *uptr) { - struct timespec now; - struct tm tm; - time_t sec; - TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf; + TOD_DATA *td = (TOD_DATA *)uptr->filebuf; + int32 t; - sim_rtcn_get_time(&now, TMR_CLK); - sec = now.tv_sec - td->delta; + /* Re-sync the recorded system time once every second */ + if (tod_enabled) { + tod_tick(uptr); - /* Populate the tm struct based on current sim_time */ - tm = *gmtime(&sec); - - td->tsec = 0; - td->unit_sec = tm.tm_sec % 10; - td->ten_sec = tm.tm_sec / 10; - td->unit_min = tm.tm_min % 10; - td->ten_min = tm.tm_min / 10; - td->unit_hour = tm.tm_hour % 10; - td->ten_hour = tm.tm_hour / 10; - /* tm struct stores as 0-11, tod struct as 1-12 */ - td->unit_mon = (tm.tm_mon + 1) % 10; - td->ten_mon = (tm.tm_mon + 1) / 10; - td->unit_day = tm.tm_mday % 10; - td->ten_day = tm.tm_mday / 10; - td->year = 1 << ((tm.tm_year - 1) % 4); -} - -/* - * Re-calculate the delta between real time and simulated time - */ -void tod_update_delta() -{ - struct timespec now; - struct tm tm = {0}; - time_t ssec; - TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf; - sim_rtcn_get_time(&now, TMR_CLK); - - /* Let the host decide if it is DST or not */ - tm.tm_isdst = -1; - - /* Compute the simulated seconds value */ - tm.tm_sec = (td->ten_sec * 10) + td->unit_sec; - tm.tm_min = (td->ten_min * 10) + td->unit_min; - tm.tm_hour = (td->ten_hour * 10) + td->unit_hour; - /* tm struct stores as 0-11, tod struct as 1-12 */ - tm.tm_mon = ((td->ten_mon * 10) + td->unit_mon) - 1; - tm.tm_mday = (td->ten_day * 10) + td->unit_day; - - /* We're forced to do this weird arithmetic because the TOD chip - * used by the 3B2 does not store the year. It only stores the - * offset from the nearest leap year. */ - switch(td->year) { - case 1: /* Leap Year - 3 */ - tm.tm_year = 85; - break; - case 2: /* Leap Year - 2 */ - tm.tm_year = 86; - break; - case 4: /* Leap Year - 1 */ - tm.tm_year = 87; - break; - case 8: /* Leap Year */ - tm.tm_year = 88; - break; - default: - break; + if (td->tsec == 0) { + tod_resync(uptr); + } } - ssec = mktime(&tm); - td->delta = (int32)(now.tv_sec - ssec); + t = sim_rtcn_calb(CLK_TPS, TMR_CLK); + sim_activate_after(uptr, 1000000/CLK_TPS); + tmr_poll = t; + tmxr_poll = t; + AIO_SET_INTERRUPT_LATENCY(tmr_poll * CLK_TPS); + return SCPE_OK; } +/* + * The MM58174 and MM58274 consist of a set of failry "dumb" roll-over + * counters. In an ideal world, we'd just look at the real system time + * and translate that into whatever read the host needs. + * Unfortunately, since the Day-of-Week and Leap Year registers are + * totally independent of whatever the "real" date and time should be, + * this doesn't map very well, and DGMON hardware diagnostics fail. + * + * Instead, we model the behavior of the chip accurately here. Each + * rollover is cascaded to the next highest register, using the same + * logic the chip uses. + */ +static void tod_tick(UNIT *uptr) +{ + TOD_DATA *td = (TOD_DATA *)uptr->filebuf; + + if (++td->tsec > 99) { + td->tsec = 0; + td->flags |= FLAG_DATA_CHANGED; + if (++td->sec > 59) { + td->sec = 0; + if (++td->min > 59) { + td->min = 0; + td->hour++; + + /* 12-hour clock cycles from 1-12, 24-hour clock cycles from 00-23 */ + if (TOD_12H(td)) { + if (td->hour == 12) { + td->clkset ^= CLKSET_PM; + } + if (td->hour > 12) { + td->hour = 1; + } + } else if (td->hour > 23) { + td->hour = 0; + } + + if ((TOD_12H(td) && td->hour == 12) || (!TOD_12H(td) && td->hour == 0)) { + /* Manage day-of-week */ + td->wday++; + if (td->wday > 7) { + td->wday = 1; + } + td->day++; + switch(td->mon) { + case 2: /* FEB */ + if (TOD_LYEAR(td)) { + if (td->day > 29) { + td->day = 1; + } + } else { + if (td->day > 28) { + td->day = 1; + } + } + break; + case 4: /* APR */ + case 6: /* JUN */ + case 9: /* SEP */ + case 11: /* NOV */ + if (td->day > 30) { + td->day = 1; + } + break; + case 1: /* JAN */ + case 3: /* MAR */ + case 5: /* MAY */ + case 7: /* JUL */ + case 8: /* AUG */ + case 10: /* OCT */ + case 12: /* DEC */ + if (td->day > 31) { + td->day = 1; + } + break; + } + if (td->day == 1) { + if (++td->mon > 12) { + td->mon = 1; + TOD_LYEAR_INC(td); + if (++td->year > 99) { + td->year = 0; + } + } + } + } + } + } + } +} + + uint32 tod_read(uint32 pa, size_t size) { - uint8 reg; + uint8 reg, val; TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); - tod_resync(); - - reg = pa - TODBASE; + reg = pa & 0xfc; switch(reg) { - case 0x04: /* 1/10 Sec */ - return td->tsec; - case 0x08: /* 1 Sec */ - return td->unit_sec; - case 0x0c: /* 10 Sec */ - return td->ten_sec; - case 0x10: /* 1 Min */ - return td->unit_min; - case 0x14: /* 10 Min */ - return td->ten_min; - case 0x18: /* 1 Hour */ - return td->unit_hour; - case 0x1c: /* 10 Hour */ - return td->ten_hour; - case 0x20: /* 1 Day */ - return td->unit_day; - case 0x24: /* 10 Day */ - return td->ten_day; - case 0x28: /* Day of Week */ - return td->wday; - case 0x2c: /* 1 Month */ - return td->unit_mon; - case 0x30: /* 10 Month */ - return td->ten_mon; - case 0x34: /* Year */ - return td->year; +#if defined(REV3) + case TOD_CTRL: + val = td->flags; + td->flags &= ~(FLAG_DATA_CHANGED); + break; +#endif + case TOD_TSEC: + val = TOD_BCDH(td->tsec); + break; + case TOD_1SEC: + val = TOD_BCDL(td->sec); + break; + case TOD_10SEC: + val = TOD_BCDH(td->sec); + break; + case TOD_1MIN: + val = TOD_BCDL(td->min); + break; + case TOD_10MIN: + val = TOD_BCDH(td->min); + break; + case TOD_1HOUR: + val = TOD_BCDL(td->hour); + break; + case TOD_10HOUR: + val = TOD_BCDH(td->hour); + break; + case TOD_1DAY: + val = TOD_BCDL(td->day); + break; + case TOD_10DAY: + val = TOD_BCDH(td->day); + break; + case TOD_1MON: + val = TOD_BCDL(td->mon); + break; + case TOD_10MON: + val = TOD_BCDH(td->mon); + break; + case TOD_WDAY: + val = td->wday; + break; + case TOD_1YEAR: +#if defined(REV3) + val = TOD_BCDL(td->year); +#else + val = td->lyear; +#endif + break; +#if defined(REV3) + case TOD_10YEAR: + val = TOD_BCDH(td->year); + break; + case TOD_SET_INT: + val = td->clkset; + break; +#endif default: + val = 0; break; } - return 0; + return val; } void tod_write(uint32 pa, uint32 val, size_t size) @@ -413,52 +562,83 @@ void tod_write(uint32 pa, uint32 val, size_t size) uint32 reg; TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf); - reg = pa - TODBASE; + /* reg = pa - TODBASE; */ + reg = pa & 0xfc; switch(reg) { - case 0x04: /* 1/10 Sec */ - td->tsec = (uint8) val; - break; - case 0x08: /* 1 Sec */ - td->unit_sec = (uint8) val; - break; - case 0x0c: /* 10 Sec */ - td->ten_sec = (uint8) val; - break; - case 0x10: /* 1 Min */ - td->unit_min = (uint8) val; - break; - case 0x14: /* 10 Min */ - td->ten_min = (uint8) val; - break; - case 0x18: /* 1 Hour */ - td->unit_hour = (uint8) val; - break; - case 0x1c: /* 10 Hour */ - td->ten_hour = (uint8) val; - break; - case 0x20: /* 1 Day */ - td->unit_day = (uint8) val; - break; - case 0x24: /* 10 Day */ - td->ten_day = (uint8) val; - break; - case 0x28: /* Day of Week */ - td->wday = (uint8) val; - break; - case 0x2c: /* 1 Month */ - td->unit_mon = (uint8) val; - break; - case 0x30: /* 10 Month */ - td->ten_mon = (uint8) val; - break; - case 0x34: /* Year */ - td->year = (uint8) val; - break; - case 0x38: - if (val & 1) { - tod_update_delta(); +#if defined(REV3) + case TOD_CTRL: + td->ctrl = (uint8) val; + if (val & CTRL_DISABLE) { + tod_enabled = FALSE; + td->tsec = 0; + } else { + tod_enabled = TRUE; } +#else + case TOD_TEST: + /* test mode */ +#endif + break; + case TOD_TSEC: + td->tsec = (uint8) val * 10; + break; + case TOD_1SEC: + td->sec = ((td->sec / 10) * 10) + (uint8) val; + break; + case TOD_10SEC: + td->sec = ((uint8) val * 10) + (td->sec % 10); + break; + case TOD_1MIN: + td->min = ((td->min / 10) * 10) + (uint8) val; + break; + case TOD_10MIN: + td->min = ((uint8) val * 10) + (td->min % 10); + break; + case TOD_1HOUR: + td->hour = ((td->hour / 10) * 10) + (uint8) val; + break; + case TOD_10HOUR: + td->hour = ((uint8) val * 10) + (td->hour % 10); + break; + case TOD_1DAY: + td->day = ((td->day / 10) * 10) + (uint8) val; + break; + case TOD_10DAY: + td->day = ((uint8) val * 10) + (td->day % 10); + break; + case TOD_1MON: + td->mon = ((td->mon / 10) * 10) + (uint8) val; + break; + case TOD_10MON: + td->mon = ((uint8) val * 10) + (td->mon % 10); + break; + case TOD_1YEAR: +#if defined(REV3) + td->year = ((td->year / 10) * 10) + (uint8) val; +#else + td->lyear = (uint8) val; +#endif + break; +#if defined(REV3) + case TOD_10YEAR: + td->year = ((uint8) val * 10) + (td->year % 10); + break; + case TOD_SET_INT: + td->clkset = (uint8) val; + if (!TOD_12H(td)) { + /* The AM/PM indicator is always 0 if not in 12H mode */ + td->clkset &= ~(CLKSET_PM); + } + td->lyear = (val >> 2) & 3; + break; +#else + case TOD_STARTSTOP: + tod_enabled = val & 1; + break; +#endif + case TOD_WDAY: + td->wday = (uint8)val & 0x7; break; default: break; @@ -467,27 +647,33 @@ void tod_write(uint32 pa, uint32 val, size_t size) const char *tod_description(DEVICE *dptr) { - return "Time-of-Day clock, used to store system time between boots.\n"; +#if defined(REV3) + return("MM58274C real time clock"); +#else + return("MM58174A real time clock"); +#endif } t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { - fprintf(st, - "The TOD is a battery-backed time-of-day clock that holds system\n" - "time between boots. In order to store the time, a file must be\n" - "attached to the TOD device with the SIMH command:\n" - "\n" - " sim> ATTACH TOD \n" - "\n" - "On a newly installed System V Release 3 UNIX system, no system\n" - "time will be stored in the TOD clock. In order to set the system\n" - "time, run the following command from within UNIX (as root):\n" - "\n" - " # sysadm datetime\n" - "\n" - "On subsequent boots, the correct system time will restored from\n" - "from the TOD.\n"); + char dname[10]; +#if defined(REV3) + snprintf(dname, 10, "MM58274C"); +#else + snprintf(dname, 10, "MM58174A"); +#endif + + fprintf(st, "%s Time-Of-Day Clock (%s)\n\n", dname, dptr->name); + fprintf(st, "The %s controller simulates a National Semiconductor %s\n", dptr->name, dname); + fprintf(st, "real time clock. This clock keeps track of the current system time\n"); + fprintf(st, "and date.\n\n"); + fprintf(st, "In order to preserve simulated calendar time between simulator runs,\n"); + fprintf(st, "the %s clock may be attached to a file which stores its state while\n", dptr->name); + fprintf(st, "the simulator is not running, e.g.:\n\n"); + fprintf(st, " sim> ATTACH TOD \n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); return SCPE_OK; } @@ -500,44 +686,130 @@ t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr * 0x4C000 and 0x4D000. These latch state of the last address to cause * a CPU fault. * - * Bits 00-25: Physical memory address bits 00-25 + * Bits 00-25: Physical memory address bits + * + * Fault Register 2 does double duty. It actually consists of four + * words, each of which maps to a memory slot on the system board. If + * occupied, it records the size of memory equipped in the slot, as + * well as information about any memory faults. + * + * + * Bits Active Purpose + * ---------------------------------------------------- + * 31-26 H Upper Halfword ECC Syndrome Bits + * 25-20 H Lower Halfword ECC Syndeome Bits + * 19 L I/O Bus or BUB master on Fault + * 18 H Invert low addr bit 2 (DWORD1) + * 17 H Decrement low addr by 4 + * 16 L I/O Bus Master on Fault + * 15 L CPU accessing I/O Peripheral + * 14 L BUB Slot 0 master on fault + * 13 L CPU Accessing BUB Peripheral + * 12-11 H BUB peripheral accessed by CPU + * 10 L BUB Slot 1 master on fault + * 9 L BUB Slot 2 master on fault + * 8 L BUB Slot 3 master on fault + * 7-3 N/A Not Used + * 2 L Memory Equipped + * 1-0 H Equipped Memory Size + * */ -uint32 flt_1 = 0; -uint32 flt_2 = 0; +uint32 flt[2] = {0, 0}; UNIT flt_unit = { - UDATA(NULL, UNIT_FIX+UNIT_BINK, 8) + UDATA(NULL, UNIT_FIX+UNIT_BINK, 64) }; REG flt_reg[] = { + { HRDATAD(FLT1, flt[0], 32, "Fault Register 1") }, + { HRDATAD(FLT2, flt[1], 32, "Fault Register 2") }, { NULL } }; DEVICE flt_dev = { "FLT", &flt_unit, flt_reg, NULL, - 1, 16, 8, 4, 16, 32, + 1, 16, 32, 1, 16, 32, NULL, NULL, NULL, NULL, NULL, NULL, NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL, - NULL, NULL, NULL, - NULL + &flt_help, NULL, NULL, + &flt_description }; +/* + * Return the configured memory size for a given backplane location. + */ +static uint32 mem_size(uint8 slot) { + switch(MEM_SIZE) { + case MSIZ_8M: + if (slot <= 1) { + return MEM_EQP|MEM_4M; + } else { + return 0; + } + case MSIZ_16M: + return MEM_EQP|MEM_4M; + case MSIZ_32M: + if (slot <= 1) { + return MEM_EQP|MEM_16M; + } else { + return 0; + } + case MSIZ_64M: + return MEM_EQP|MEM_16M; + default: + return 0; + } +} + uint32 flt_read(uint32 pa, size_t size) { - sim_debug(READ_MSG, &flt_dev, - "[%08x] Read from FLT Register at %x\n", - R[NUM_PC], pa); - return 0; + sim_debug(EXECUTE_MSG, &flt_dev, + "Read from FLT Register at %x\n", + pa); + + switch(pa) { + case FLTLBASE: + return flt[0]; + case FLTHBASE: + return (flt[1] & FLT_MSK) | mem_size(0); + case FLTHBASE + 4: + return (flt[1] & FLT_MSK) | mem_size(1); + case FLTHBASE + 8: + return (flt[1] & FLT_MSK) | mem_size(2); + case FLTHBASE + 12: + return (flt[1] & FLT_MSK) | mem_size(3); + default: + sim_debug(EXECUTE_MSG, &flt_dev, + "Read from FLT Register at %x: FAILURE, NO DATA!!!!\n", + pa); + return 0; + } } void flt_write(uint32 pa, uint32 val, size_t size) { - sim_debug(WRITE_MSG, &flt_dev, - "[%08x] Write to FLT Register at %x (val=%x)\n", - R[NUM_PC], pa, val); + sim_debug(EXECUTE_MSG, &flt_dev, + "Write to FLT Register at %x (val=%x)\n", + pa, val); + return; } +t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "Fault Register\n\n"); + fprintf(st, "The %s device is a pair of 32-bit registers that hold information about\n", dptr->name); + fprintf(st, "system memory faults.\n"); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + return SCPE_OK; +} + +const char *flt_description(DEVICE *dptr) +{ + return "Fault Register"; +} + #endif diff --git a/3B2/3b2_stddev.h b/3B2/3b2_stddev.h index c709fd00..266bdb5e 100644 --- a/3B2/3b2_stddev.h +++ b/3B2/3b2_stddev.h @@ -1,6 +1,6 @@ -/* 3b2_stddev.h: AT&T 3B2 miscellaneous system board devices. +/* 3b2_stddev.h: Miscellaneous System Board Devices - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -44,39 +44,95 @@ const char *nvram_description(DEVICE *dptr); t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); void nvram_write(uint32 pa, uint32 val, size_t size); -/* TOD */ typedef struct tod_data { - int32 delta; /* Delta between simulated time and real time (sec.) */ - uint8 tsec; /* 1/10 seconds */ - uint8 unit_sec; /* 1's column seconds */ - uint8 ten_sec; /* 10's column seconds */ - uint8 unit_min; /* 1's column minutes */ - uint8 ten_min; /* 10's column minutes */ - uint8 unit_hour; /* 1's column hours */ - uint8 ten_hour; /* 10's column hours */ - uint8 unit_day; /* 1's column day of month */ - uint8 ten_day; /* 10's column day of month */ - uint8 wday; /* Day of week (0-6) */ - uint8 unit_mon; /* 1's column month */ - uint8 ten_mon; /* 10's column month */ - uint8 year; /* 1, 2, 4, 8 shift register */ - uint8 pad[3]; /* Padding to 32 bytes */ + time_t time; /* System time */ + + uint8 ctrl; /* Control register (Rev 3 only) */ + uint8 flags; /* Data Changed & Interrutpt Flags (Rev 3 only) */ + uint8 clkset; /* Clock / Setting register (Rev 3 only) */ + + uint8 tsec; /* 1/100th seconds, 00-99 */ + uint8 sec; /* Seconds, 00-59 */ + uint8 min; /* Minutes, 00-59 */ + uint8 hour; /* Hours, 00-23 */ + uint8 day; /* Days, 00-27, 28, 29, or 30 */ + uint8 mon; /* Months, 00-11 */ + uint8 year; /* Years, 00-99 (Rev 3 only) */ + uint8 wday; /* Day of Week, 0-6 */ + uint8 lyear; /* Years since last leap year */ } TOD_DATA; -void tod_resync(); +#if defined(REV2) + +#define TOD_TEST 0x00 +#define TOD_TSEC 0x04 +#define TOD_1SEC 0x08 +#define TOD_10SEC 0x0c +#define TOD_1MIN 0x10 +#define TOD_10MIN 0x14 +#define TOD_1HOUR 0x18 +#define TOD_10HOUR 0x1c +#define TOD_1DAY 0x20 +#define TOD_10DAY 0x24 +#define TOD_WDAY 0x28 +#define TOD_1MON 0x2c +#define TOD_10MON 0x30 +#define TOD_1YEAR 0x34 +#define TOD_STARTSTOP 0x38 +#define TOD_INT 0x3c + +#else + +#define TOD_FLAG_CHG 0x08 +#define TOD_FLAG_IRQ 0x01 + +#define TOD_CTRL 0x00 +#define TOD_TSEC 0x04 +#define TOD_1SEC 0x08 +#define TOD_10SEC 0x0c +#define TOD_1MIN 0x10 +#define TOD_10MIN 0x14 +#define TOD_1HOUR 0x18 +#define TOD_10HOUR 0x1c +#define TOD_1DAY 0x20 +#define TOD_10DAY 0x24 +#define TOD_1MON 0x28 +#define TOD_10MON 0x2c +#define TOD_1YEAR 0x30 +#define TOD_10YEAR 0x34 +#define TOD_WDAY 0x38 +#define TOD_SET_INT 0x3c + +#endif + void tod_update_delta(); t_stat tod_reset(DEVICE *dptr); t_stat tod_attach(UNIT *uptr, CONST char *cptr); t_stat tod_detach(UNIT *uptr); -const char *tod_description(DEVICE *dptr); t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +const char *tod_description(DEVICE *dptr); uint32 tod_read(uint32 pa, size_t size); void tod_write(uint32, uint32 val, size_t size); +/* Global symbols */ + +extern int32 tmxr_poll; + #if defined(REV3) /* Fault Register */ + +#define FLT_MSK 0xffffff00 +#define MEM_EQP 0x4 +#define MEM_4M 0x2 +#define MEM_16M 0x3 + uint32 flt_read(uint32 pa, size_t size); void flt_write(uint32 pa, uint32 val, size_t size); -#endif +t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +const char *flt_description(DEVICE *dptr); + +extern uint32 flt[2]; + +#endif /* defined(REV3) */ #endif /* _3B2_STDDEV_H_ */ diff --git a/3B2/3b2_sys.c b/3B2/3b2_sys.c index e826d46c..68a619c6 100644 --- a/3B2/3b2_sys.c +++ b/3B2/3b2_sys.c @@ -1,6 +1,6 @@ -/* 3b2_sys.c: AT&T 3B2 common system definitions +/* 3b2_sys.c: Common System Definition - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -53,25 +53,57 @@ const char *sim_stop_messages[SCPE_BASE] = { "Simulator Error" }; +/* + * ROM and Binary loader + * + * -r load ROM + * -o for memory, specify origin + * + */ t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) { + t_stat r; int32 i; - uint32 addr = 0; + uint32 origin = 0, limit = 0; int32 cnt = 0; - if ((*cptr != 0) || (flag != 0)) { - return SCPE_ARG; + if (flag) { + return sim_messagef(SCPE_NOFNC, "Command not implemented."); } - addr = R[NUM_PC]; - - while ((i = getc (fileref)) != EOF) { - pwrite_b(addr, (uint8)i); - addr++; + if (sim_switches & SWMASK('R')) { + origin = ROM_BASE; + limit = ROM_BASE + ROM_SIZE; + } else { + origin = 0; + limit = (uint32) cpu_unit.capac; + if (sim_switches & SWMASK('O')) { + origin = (uint32) get_uint(cptr, 16, 0xffffffff, &r); + if (r != SCPE_OK) { + return SCPE_ARG; + } + } + } + + while ((i = Fgetc (fileref)) != EOF) { + if (origin >= limit) { + return SCPE_NXM; + } + if (sim_switches & SWMASK('R')) { + pwrite_b_rom(origin, (uint8)i); + } else { + pwrite_b(origin, (uint8)i, BUS_CPU); + } + origin++; cnt++; } - printf ("%d Bytes loaded.\n", cnt); + if (sim_switches & SWMASK('R')) { + rom_loaded = TRUE; + sim_messagef(SCPE_OK, "%d bytes loaded into ROM\n", cnt); + } else { + sim_messagef(SCPE_OK, "%d bytes loaded at address 0x%08x\n", cnt, origin - cnt); + } return SCPE_OK; } diff --git a/3B2/3b2_sys.h b/3B2/3b2_sys.h index d3fa72e2..7eb9f174 100644 --- a/3B2/3b2_sys.h +++ b/3B2/3b2_sys.h @@ -1,6 +1,6 @@ -/* 3b2_rev2_sys.h: AT&T 3B2 Rev 2 (Model 400) system header +/* 3b2_sys.h: Common System Definition - Copyright (c) 2017, Seth J. Morabito + Copyright (c) 2017-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/3B2/3b2_rev3_timer.c b/3B2/3b2_timer.c similarity index 55% rename from 3B2/3b2_rev3_timer.c rename to 3B2/3b2_timer.c index 085a7ffd..6c74b540 100644 --- a/3B2/3b2_rev3_timer.c +++ b/3B2/3b2_timer.c @@ -1,6 +1,6 @@ -/* 3b2_rev3_timer.c: 82C54 Interval Timer. +/* 3b2_timer.c: 8253/82C54 Interval Timer - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -29,9 +29,7 @@ */ /* - * 82C54 Timer. - * - * 82C54 (Rev3) Timer IC has three interval timers, which we treat + * The 8253/82C54 Timer IC has three interval timers, which we treat * here as three units. * * In the 3B2, the three timers are assigned specific purposes: @@ -64,10 +62,10 @@ * Implementaiton Notes * ==================== * - * In general, no attempt has been made to create an accurate - * emulation of the 82C54 timer. This implementation is truly built - * for the 3B2, and even more specifically for System V Unix, which is - * the only operating system ever to have been ported to the 3B2. + * In general, no attempt has been made to create a truly accurate + * simulation of the 8253/82C54 timer. This implementation is built + * for the 3B2, and even more specifically to pass System V timer + * "Sanity/Interval Timer" diagnostics. * * - The Bus Timeout Timer is not implemented other than a stub that * is designed to pass hardware diagnostics. The simulator IO @@ -77,9 +75,8 @@ * - The System Sanity Timer is also not implemented other than a * stub to pass diagnostics. * - * - The main Unix Interval Timer is implemented as a true SIMH clock - * when set up for the correct mode. In other modes, it likewise - * implements a stub designed to pass diagnostics. + * - The main Unix Interval Timer is more fully implemented, because + * it drives system interrupts in System V UNIX. */ #include "3b2_cpu.h" @@ -87,18 +84,50 @@ #include "3b2_defs.h" #include "3b2_timer.h" +#define MIN_DIVIDER 50 + +#if defined (REV3) +#define QUICK_DELAY 10 +#else +#define QUICK_DELAY 100 +#endif + +#define DELAY_US(C,N) ((TIMER_MODE(C) == 3) ? \ + (TIME_BASE[(N)] * (C)->divider) / 2 : \ + TIME_BASE[(N)] * (C)->divider) + +#if defined(REV3) +/* Microseconds per step (Version 3 system board): + * + * Timer 0: 10KHz time base + * Timer 1: 100KHz time base + * Timer 2: 500KHz time base + */ +static uint32 TIME_BASE[3] = { + 100, 10, 1 +}; +#else +/* Microseconds per step (Version 2 system board): + * + * Timer 0: 100Khz time base + * Timer 1: 100Khz time base + * Timer 2: 500Khz time base + */ +static uint32 TIME_BASE[3] = { + 10, 10, 2 +}; +#endif + struct timer_ctr TIMERS[3]; -int32 tmxr_poll = 16667; - UNIT timer_unit[] = { - { UDATA(&timer0_svc, 0, 0) }, - { UDATA(&timer1_svc, UNIT_IDLE, 0) }, - { UDATA(&timer2_svc, 0, 0) }, + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, + { UDATA(&tmr_svc, UNIT_IDLE, 0) }, { NULL } }; -UNIT *timer_clk_unit = &timer_unit[1]; +UNIT *tmr_int_unit = &timer_unit[3]; REG timer_reg[] = { { HRDATAD(DIV0, TIMERS[0].divider, 16, "Divider (0)") }, @@ -114,11 +143,32 @@ REG timer_reg[] = { }; DEVICE timer_dev = { - "TIMER", timer_unit, timer_reg, NULL, - 1, 16, 8, 4, 16, 32, - NULL, NULL, &timer_reset, - NULL, NULL, NULL, NULL, - DEV_DEBUG, 0, sys_deb_tab + "TMR", + timer_unit, + timer_reg, + NULL, + 3, + 16, + 8, + 4, + 16, + 32, + NULL, + NULL, + &timer_reset, + NULL, + NULL, + NULL, + NULL, + DEV_DEBUG, + 0, + sys_deb_tab, + NULL, + NULL, + &tmr_help, + NULL, + NULL, + &tmr_description }; t_stat timer_reset(DEVICE *dptr) { @@ -126,164 +176,111 @@ t_stat timer_reset(DEVICE *dptr) { memset(&TIMERS, 0, sizeof(struct timer_ctr) * 3); + /* Store the timer/counter number in the UNIT */ for (i = 0; i < 3; i++) { - timer_unit[i].tmrnum = i; - timer_unit[i].tmr = (void *)&TIMERS[i]; + timer_unit[i].u3 = i; } - /* TODO: I don't think this is right. Verify. */ - /* - if (!sim_is_running) { - t = sim_rtcn_init_unit(timer_clk_unit, TPS_CLK, TMR_CLK); - sim_activate_after_abs(timer_clk_unit, 1000000 / t); - } - */ - return SCPE_OK; } +/* + * Inhibit or allow a timer externally. + */ +void timer_gate(uint8 ctrnum, t_bool inhibit) +{ + struct timer_ctr *ctr = &TIMERS[ctrnum]; + + if (inhibit) { + ctr->gate = FALSE; + sim_cancel(&timer_unit[ctrnum]); + } else { + ctr->gate = TRUE; + if (ctr->enabled && !sim_is_active(&timer_unit[ctrnum])) { + sim_activate_after(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); + ctr->val--; + } + } +} + static void timer_activate(uint8 ctrnum) { - struct timer_ctr *ctr; + struct timer_ctr *ctr = &TIMERS[ctrnum]; - ctr = &TIMERS[ctrnum]; - - switch (ctrnum) { - case TIMER_SANITY: - if ((csr_data & CSRISTIM) == 0) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] SANITY TIMER: Activating after %d steps\n", - R[NUM_PC], ctr->val); - sim_activate_abs(&timer_unit[ctrnum], ctr->val); - ctr->val--; + if (ctr->enabled && ctr->gate) { + if (ctr->divider < MIN_DIVIDER) { + /* If the timer delay is too short, we need to force a + very quick activation */ + sim_activate_abs(&timer_unit[ctrnum], QUICK_DELAY); } else { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] SANITY TIMER: Currently disabled, not starting\n", - R[NUM_PC]); + /* Otherwise, use a computed time in microseconds */ + sim_activate_after_abs(&timer_unit[ctrnum], DELAY_US(ctr, ctrnum)); } - break; - case TIMER_INTERVAL: - if ((csr_data & CSRITIM) == 0) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] INTERVAL TIMER: Activating after %d ms\n", - R[NUM_PC], ctr->val); - sim_activate_after_abs(&timer_unit[ctrnum], ctr->val); - ctr->val--; - } else { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] INTERVAL TIMER: Currently disabled, not starting\n", - R[NUM_PC]); - } - break; - case TIMER_BUS: - if ((csr_data & CSRITIMO) == 0) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] BUS TIMER: Activating after %d steps\n", - R[NUM_PC], ctr->val); - sim_activate_abs(&timer_unit[ctrnum], (ctr->val - 2)); - ctr->val -= 2; - } else { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] BUS TIMER: Currently disabled, not starting\n", - R[NUM_PC]); - } - break; - default: - break; } } -void timer_enable(uint8 ctrnum) -{ - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] Enabling timer %d\n", - R[NUM_PC], ctrnum); - timer_activate(ctrnum); -} - -void timer_disable(uint8 ctrnum) -{ - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] Disabling timer %d\n", - R[NUM_PC], ctrnum); - sim_cancel(&timer_unit[ctrnum]); -} - /* - * Sanity Timer + * Sanity, Non-calibrated Interval, and Bus Timeout Timer service routine */ -t_stat timer0_svc(UNIT *uptr) +t_stat tmr_svc(UNIT *uptr) { - struct timer_ctr *ctr; + int32 ctr_num = uptr->u3; + uint32 usec_delay; + struct timer_ctr *ctr = &TIMERS[ctr_num]; - ctr = (struct timer_ctr *)uptr->tmr; - - if (ctr->enabled) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] TIMER 0 COMPLETION.\n", - R[NUM_PC]); - if (!(csr_data & CSRISTIM)) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] TIMER 0 NMI IRQ.\n", - R[NUM_PC]); - ctr->val = 0xffff; + if (ctr == NULL) { + return SCPE_SUB; + } + + /* If the timer isn't enabled, do nothing. */ + if (!ctr->enabled) { + return SCPE_OK; + } + + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] Handling timeout for ctr number %d\n", + ctr_num); + + switch (ctr_num) { + case TMR_SANITY: +#if defined (REV3) + if (!CSR(CSRISTIM) && TIMER_MODE(ctr) != 4) { cpu_nmi = TRUE; CSRBIT(CSRSTIMO, TRUE); CPU_SET_INT(INT_BUS_TMO); - } - } - - return SCPE_OK; -} - -/* - * Interval Timer - */ -t_stat timer1_svc(UNIT *uptr) -{ - struct timer_ctr *ctr; - int32 t; - - ctr = (struct timer_ctr *)uptr->tmr; - - if (ctr->enabled && !(csr_data & CSRITIM)) { - /* Fire the IPL 15 clock interrupt */ - CSRBIT(CSRCLK, TRUE); - CPU_SET_INT(INT_CLOCK); - } - - t = sim_rtcn_calb(TPS_CLK, TMR_CLK); - sim_activate_after_abs(uptr, 1000000/TPS_CLK); - tmxr_poll = t; - - return SCPE_OK; -} - -/* - * Bus Timeout Timer - */ -t_stat timer2_svc(UNIT *uptr) -{ - struct timer_ctr *ctr; - - ctr = (struct timer_ctr *)uptr->tmr; - - if (ctr->enabled && TIMER_RW(ctr) == CLK_LSB) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] TIMER 2 COMPLETION.\n", - R[NUM_PC]); - if (!(csr_data & CSRITIMO)) { - sim_debug(EXECUTE_MSG, &timer_dev, - "[%08x] TIMER 2 IRQ.\n", - R[NUM_PC]); ctr->val = 0xffff; + } +#endif + break; + case TMR_INT: + if (!CSR(CSRITIM)) { + CSRBIT(CSRCLK, TRUE); + CPU_SET_INT(INT_CLOCK); + if (ctr->enabled && ctr->gate) { + usec_delay = DELAY_US(ctr, TMR_INT); + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] Re-triggering TMR_INT in %d usec\n", usec_delay); + sim_activate_after(uptr, usec_delay); + } + ctr->val = 0xffff; + } + break; + case TMR_BUS: +#if defined (REV3) + /* Only used during diagnostics */ + if (TIMER_RW(ctr) == CLK_LSB) { + sim_debug(EXECUTE_MSG, &timer_dev, + "[tmr_svc] BUS TIMER FIRING. Setting memory fault and interrupt\n"); CSRBIT(CSRTIMO, TRUE); CPU_SET_INT(INT_BUS_TMO); - /* Also trigger a bus abort */ cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); + ctr->val = 0xffff; } +#endif + break; } + return SCPE_OK; } @@ -298,6 +295,9 @@ uint32 timer_read(uint32 pa, size_t size) ctrnum = (reg >> 2) & 0x3; ctr = &TIMERS[ctrnum]; + sim_debug(EXECUTE_MSG, &timer_dev, + "timer_read: reg=%x\n", reg); + switch (reg) { case TIMER_REG_DIVA: case TIMER_REG_DIVB: @@ -307,78 +307,60 @@ uint32 timer_read(uint32 pa, size_t size) switch (TIMER_RW(ctr)) { case CLK_LSB: retval = ctr_val & 0xff; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); break; case CLK_MSB: retval = (ctr_val & 0xff00) >> 8; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [MSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); break; case CLK_LMB: if (ctr->r_ctrl_latch) { ctr->r_ctrl_latch = FALSE; retval = ctr->ctrl_latch; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LATCH CTRL] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); } else if (ctr->r_cnt_latch) { if (ctr->r_lmb) { ctr->r_lmb = FALSE; retval = (ctr->cnt_latch & 0xff00) >> 8; ctr->r_cnt_latch = FALSE; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LATCH DATA MSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); } else { ctr->r_lmb = TRUE; retval = ctr->cnt_latch & 0xff; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LATCH DATA LSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); } } else if (ctr->r_lmb) { ctr->r_lmb = FALSE; retval = (ctr_val & 0xff00) >> 8; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LMB - MSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); } else { ctr->r_lmb = TRUE; retval = ctr_val & 0xff; - sim_debug(READ_MSG, &timer_dev, - "[%08x] [%d] [LMB - LSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, retval, retval); } break; default: retval = 0; } - return retval; + break; case TIMER_REG_CTRL: - return ctr->ctrl; + retval = ctr->ctrl; + break; case TIMER_CLR_LATCH: /* Clearing the timer latch has a side-effect of also clearing pending interrupts */ CSRBIT(CSRCLK, FALSE); CPU_CLR_INT(INT_CLOCK); - return 0; + /* Acknowledge a clock tick */ + sim_rtcn_tick_ack(1, TMR_CLK); + retval = 0; + break; default: /* Unhandled */ - sim_debug(READ_MSG, &timer_dev, - "[%08x] UNHANDLED TIMER READ. ADDR=%08x\n", - R[NUM_PC], pa); - return 0; + retval = 0; + break; } + + return retval; } void handle_timer_write(uint8 ctrnum, uint32 val) { struct timer_ctr *ctr; - UNIT *unit = &timer_unit[ctrnum]; ctr = &TIMERS[ctrnum]; ctr->enabled = TRUE; @@ -387,17 +369,13 @@ void handle_timer_write(uint8 ctrnum, uint32 val) case CLK_LSB: ctr->divider = val & 0xff; ctr->val = ctr->divider; - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] [%d] [LSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, val & 0xff, val & 0xff); + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d LSB=%02x\n", ctrnum, val & 0xff); timer_activate(ctrnum); break; case CLK_MSB: ctr->divider = (val & 0xff) << 8; ctr->val = ctr->divider; - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] [%d] [MSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, val & 0xff, val & 0xff); + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d MSB=%02x\n", ctrnum, val & 0xff); timer_activate(ctrnum); break; case CLK_LMB: @@ -405,17 +383,13 @@ void handle_timer_write(uint8 ctrnum, uint32 val) ctr->w_lmb = FALSE; ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8)); ctr->val = ctr->divider; - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] [%d] [LMB - MSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, val & 0xff, val & 0xff); + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) MSB=%02x\n", ctrnum, val & 0xff); timer_activate(ctrnum); } else { ctr->w_lmb = TRUE; ctr->divider = (ctr->divider & 0xff00) | (val & 0xff); ctr->val = ctr->divider; - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] [%d] [LMB - LSB] val=%d (0x%x)\n", - R[NUM_PC], ctrnum, val & 0xff, val & 0xff); + sim_debug(EXECUTE_MSG, &timer_dev, "TIMER_WRITE: CTR=%d (L/M) LSB=%02x\n", ctrnum, val & 0xff); } break; default: @@ -431,6 +405,9 @@ void timer_write(uint32 pa, uint32 val, size_t size) reg = (uint8) (pa - TIMERBASE); + sim_debug(EXECUTE_MSG, &timer_dev, + "timer_write: reg=%x val=%x\n", reg, val); + switch(reg) { case TIMER_REG_DIVA: handle_timer_write(0, val); @@ -444,9 +421,6 @@ void timer_write(uint32 pa, uint32 val, size_t size) case TIMER_REG_CTRL: ctrnum = (val >> 6) & 3; if (ctrnum == 3) { - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] READ BACK COMMAND. DATA=%02x\n", - R[NUM_PC], val); if (val & 2) { ctr = &TIMERS[0]; if ((val & 0x20) == 0) { @@ -481,9 +455,6 @@ void timer_write(uint32 pa, uint32 val, size_t size) } } } else { - sim_debug(WRITE_MSG, &timer_dev, - "[%08x] Timer Control Write: timer %d => %02x\n", - R[NUM_PC], ctrnum, val & 0xff); ctr = &TIMERS[ctrnum]; ctr->ctrl = (uint8) val; ctr->enabled = FALSE; @@ -495,12 +466,41 @@ void timer_write(uint32 pa, uint32 val, size_t size) break; case TIMER_CLR_LATCH: sim_debug(WRITE_MSG, &timer_dev, - "[%08x] unexpected write to clear timer latch\n", - R[NUM_PC]); + "unexpected write to clear timer latch\n"); break; default: sim_debug(WRITE_MSG, &timer_dev, - "[%08x] unknown timer register: %d\n", - R[NUM_PC], reg); + "unknown timer register: %d\n", + reg); } } + +CONST char *tmr_description(DEVICE *dptr) +{ +#if defined (REV3) + return "82C54 Programmable Interval Timer"; +#else + return "8253 Programmable Interval Timer"; +#endif +} + +t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ +#if defined (REV3) + fprintf(st, "82C54 Programmable Interval Timer (TMR)\n\n"); + fprintf(st, "The TMR device implements three programmable timers used by the 3B2/700\n"); +#else + fprintf(st, "8253 Programmable Interval Timer (TMR)\n\n"); + fprintf(st, "The TMR device implements three programmable timers used by the 3B2/400\n"); +#endif + fprintf(st, "to perform periodic tasks and sanity checks.\n\n"); + fprintf(st, "- TMR0: Used as a system sanity timer.\n"); + fprintf(st, "- TMR1: Used as a periodic 10 millisecond interval timer.\n"); + fprintf(st, "- TMR2: Used as a bus timeout timer.\n"); + + fprint_set_help(st, dptr); + fprint_show_help(st, dptr); + fprint_reg_help(st, dptr); + + return SCPE_OK; +} diff --git a/3B2/3b2_timer.h b/3B2/3b2_timer.h index 35df9b67..7d01e4ce 100644 --- a/3B2/3b2_timer.h +++ b/3B2/3b2_timer.h @@ -1,6 +1,6 @@ -/* 3b2_timer.h: Common TIMER header +/* 3b2_timer.h: 8253/82C54 Interval Timer - Copyright (c) 2021, Seth J. Morabito + Copyright (c) 2021-2022, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -31,12 +31,48 @@ #ifndef _3B2_TIMER_H_ #define _3B2_TIMER_H_ -#if defined(REV3) -#include "3b2_rev3_timer.h" -#else -#include "3b2_rev2_timer.h" -#endif +#include "3b2_defs.h" -extern int32 tmxr_poll; +#define TIMER_REG_DIVA 0x03 +#define TIMER_REG_DIVB 0x07 +#define TIMER_REG_DIVC 0x0b +#define TIMER_REG_CTRL 0x0f +#define TIMER_CLR_LATCH 0x13 + +#define TIMER_MODE(ctr) (((ctr->ctrl) >> 1) & 7) +#define TIMER_RW(ctr) (((ctr->ctrl) >> 4) & 3) + +#define CLK_LATCH 0 +#define CLK_LSB 1 +#define CLK_MSB 2 +#define CLK_LMB 3 + +#define TMR_SANITY 0 +#define TMR_INT 1 +#define TMR_BUS 2 + +struct timer_ctr { + uint16 divider; + uint16 val; + uint8 ctrl_latch; + uint16 cnt_latch; + uint8 ctrl; + t_bool r_lmb; + t_bool w_lmb; + t_bool enabled; + t_bool gate; + t_bool r_ctrl_latch; + t_bool r_cnt_latch; +}; + +t_stat timer_reset(DEVICE *dptr); +uint32 timer_read(uint32 pa, size_t size); +void timer_write(uint32 pa, uint32 val, size_t size); +void timer_gate(uint8 ctrnum, t_bool inhibit); + +t_stat tmr_svc(UNIT *uptr); +t_stat tmr_int_svc(UNIT *uptr); +CONST char *tmr_description(DEVICE *dptr); +t_stat tmr_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); #endif /* _3B2_TIMER_H_ */ diff --git a/3B2/README.md b/3B2/README.md index 96eb02f8..dc3fb474 100644 --- a/3B2/README.md +++ b/3B2/README.md @@ -3,26 +3,25 @@ AT&T 3B2 Simulator This module contains the source for two simulators: -1. A simulator for the AT&T 3B2 Model 400 computer (Rev. 2) -2. A simulator for the AT&T 3B2 Model 600 computer (Rev. 3) - -The 3B2/400 simulator is complete, usable, and robust. The 3B2/600 simulator -is not yet usable, however. It is under active development. +1. A simulator for the AT&T 3B2/400 computer (3b2-400 or 3B2-400.EXE) +2. A simulator for the AT&T 3B2/700 computer (3b2-700 or 3B2-700.EXE) Full documentation for the 3B2 simulator is available here: - https://loomcom.com/3b2/emulator.html -Devices -------- +3B2/400 Simulator Devices +------------------------- The following devices are simulated. The SIMH names for the simulated devices are given in parentheses: - - 3B2 Model 400 System Board with 1MB, 2MB, or 4MB RAM (CSR, NVRAM) + - 3B2 Model 400 System Board with 1MB, 2MB, or 4MB RAM + - Configuration and Status Register (CSR) - WE32100 CPU (CPU) - WE32101 MMU (MMU) - - PD8253 Interval Timer (TIMER) + - WE32106 Math Accelerator Unit (MAU) + - PD8253 Interval Timer (TMR) - AM9517 DMA controller (DMAC) - SCN2681A Integrated DUART (IU) - TMS2793 Integrated Floppy Controller (IFLOPPY) @@ -33,97 +32,23 @@ devices are given in parentheses: - CM195B 4-port Serial MUX (PORTS) - CM195H Cartridge Tape Controller (CTC) -Usage ------ +3B2/700 Simulator Devices +------------------------- -To boot the 3B2 simulator into firmware mode, simply type: +The following devices are simulated. The SIMH names for the simulated +devices are given in parentheses: - sim> BOOT - -You will be greeted with the message: - - FW ERROR 1-01: NVRAM SANITY FAILURE - DEFAULT VALUES ASSUMED - IF REPEATED, CHECK THE BATTERY - - FW ERROR 1-02: DISK SANITY FAILURE - EXECUTION HALTED - - SYSTEM FAILURE: CONSULT YOUR SYSTEM ADMINISTRATION UTILITIES GUIDE - -NVRAM and Time of Day can be saved between boots by attaching both -devices to files. - - sim> ATTACH NVRAM - sim> ATTACH TOD - -If you have no operating system installed on the hard drive, on -subsequent boots you will instead see the message - - SELF-CHECK - - - FW ERROR 1-02: DISK SANITY FAILURE - EXECUTION HALTED - - SYSTEM FAILURE: CONSULT YOUR SYSTEM ADMINISTRATION UTILITIES GUIDE - - -Once you see the `SYSTEM FAILURE` message, this is actually an -invisible prompt. To access firmware mode, type the default 3B2 -firmware password `mcp`, then press Enter or carriage return. - -You should then be prompted with: - - Enter name of program to execute [ ]: - -Here, you may type a question mark (?) and press Enter to see a list -of available firmware programs. - -Booting UNIX SVR3 ------------------ - -UNIX SVR3 is the only operating system available for the 3B2. To boot -UNIX, attach the first disk image from the 3B2 "Essential Utilities" -distribution. - - sim> ATTACH IFLOPPY - sim> BOOT - -Once you reach the `SYSTEM FAILURE` message, type `mcp` to enter -firmware mode. When prompted for the name of a program to boot, enter -`unix`, and confirm the boot device is `FD5` by pressing Enter or -carriage return. - - Enter name of program to execute [ ]: unix - Possible load devices are: - - Option Number Slot Name - --------------------------------------- - 0 0 FD5 - - Enter Load Device Option Number [0 (FD5)]: - -Installing SVR3 ---------------- - -To install SVR3 to the first hard disk, first, attach a new image -to the IDISK0 device: - - sim> ATTACH IDISK0 - -Then, boot the file `idtools` from the "3B2 Maintenance Utilities - -Issue 4.0" floppy diskette. - -From `idtools`, select the `formhard` option and low-level format -integrated disk 0. Parameters for the default 72MB hard disk are: - - Drive Id: 5 - Number cylinders: 925 - Number tracks/cyl: 9 - Number sectors/track: 18 - Number bytes/sector: 512 - -After low-level formatting integrated disk 0, boot the file `unix` -from the first diskette of the 3B2 "Essential Utilities" distribution, -and follow the prompts. + - 3B2 Model 700 System Board with 8MB, 16MB, 32MB, or 64MB RAM + - Configuration and Status Registers (CSR) + - WE32200 CPU (CPU) + - WE32201 MMU (MMU) + - WE32106 Math Accelerator Unit (MAU) + - PD8253 Interval Timer (TMR) + - AM9517 DMA controller (DMAC) + - SCN2681A Integrated DUART (IU) + - TMS2793 Integrated Floppy Controller (IFLOPPY) + - Non-Volatile Memory (NVRAM) + - MM58274C Time Of Day Clock (TOD) + - CM195W SCSI Host Adapter (SCSI) + - CM195A Ethernet Network Interface (NI) + - CM195B 4-port Serial MUX (PORTS) diff --git a/3B2/rom_rev2.bin b/3B2/rom_rev2.bin deleted file mode 100644 index 6ebc5390606fbaa069b7fe7a657588a1f2d80ddb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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 duDKuiHaJx5HU8T;avS~?_s}ukL diff --git a/3B2/rom_rev2_demon.bin b/3B2/rom_rev2_demon.bin deleted file mode 100644 index 222eb33e8cd5c87be535de3951a81610dfda13ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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�sK51?qiWj>i&lkTPhD|4EoM{qUd5r_&bUz z;u4moTX1gKriF-9pRo7L79SHAd2zbe<5iHMJs<1#9+nD7ob?YPj3 zz$_56i5(4c^9Zed}Q)#$^LyR@6oWqLXVKbVxDAh09Y6UE?U>wSutlu?c((wH~j|;%XuQYl0pX$`%^sK zA{{K2pY)vr(X1V(O7Y$BX6C!$+syl<3U-&RN0*B;57d=+&uMDFETOOw3#t*=h1OdaRFkA!QO$n8XwYy6ALcvvnNjbth z{Ubyca|N6^hx&1eHQkXots{luKZf^-l)&Kh{zlg;=>#-dK00z`VFsTel)+%+$~oNG zOpb4=(z!ksQ}nTt8u?E94W-W9vXlfZBqd6}**-Dx8Y$i3J9Sf%gq>kfSmpJG`deG+?|r#@ z-F1Dr`%3NAq41|fX=5cDD%QxQ2g)5M+XwIf(fvR)@1IgbZqgeF7kDA^hU>NeKl}Az zEJAEih&W1ZLG$GK0KobH@2>eeVupWPF;w@GD$MOH@ FzX0*WzPSJZ diff --git a/3B2/rom_rev3.bin b/3B2/rom_rev3.bin deleted file mode 100644 index 94afbfaf3b76085741fa78934796b1722b3d7243..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131072 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 HyY{~T_H*-9 diff --git a/3B2/tests/3b2_test.ini b/3B2/tests/3b2_test.ini deleted file mode 100644 index 39f6a980..00000000 --- a/3B2/tests/3b2_test.ini +++ /dev/null @@ -1,34 +0,0 @@ -:: 3b2-diag.ini -:: This script will run the available 3B2/400 core diagnostics. -:: -cd %~p0 -set runlimit 2 minutes -set on -on error ignore -on runtime echof "\r\n*** Test Runtime Limit %SIM_RUNLIMIT% %SIM_RUNLIMIT_UNITS% Exceeded ***\n"; exit 1 - -set env DIAG_QUIET_MODE=0 -if ("%1" == "-v") set console notelnet -else set -qu console telnet=localhost:65432,telnet=buffered; set env -a DIAG_QUIET_MODE=1 - -:: Set maximum memory size -set cpu 4M - -if not exist rev2_diags.dsk echof "\r\nMISSING - Diagnostic disk image '%~p0rev2_diags.dsk' is missing\n"; exit 1 -attach -rq ifloppy rev2_diags.dsk - -:: Initial setup -expect "UTILITIES GUIDE" send "mcp\r"; go -q -expect "Enter name of program to execute [ ]:" send "filledt\r"; go -q -expect "Enter Load Device Option Number [0 (FD5)]:" send "0\r"; go -q -expect "Enter name of program to execute [ ]:" send "dgmon\r"; go -q -expect "Enter Load Device Option Number [0 (FD5)]:" send "0\r"; go -q -expect "Did you boot filledt? [y or n] (n)" send "y\r"; go -q -expect "DGMON > " send "DGN SBD\r"; go -q -expect "SBD 0 (IN SLOT 0) DIAGNOSTICS PASSED" echof; echof "PASSED: 3B2 DGMON SBD Diagnostics."; exit 0 -expect [4] "FAIL" echof; echof "FAILED: 3B2 DGMON SBD Diagnostics."; exit 1 - -:: Run tests -if (DIAG_QUIET_MODE) echof "\nStarting 3B2 DGMON SBD Diagnostics." -boot -q CPU -return diff --git a/3B2/tests/rev2_diags.dsk b/3B2/tests/rev2_diags.dsk deleted file mode 100644 index 1fbfe90921e9c20c74ec7622d05884a5255af866..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737280 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
Iv>P+nO5QhR?ot z2HKFs1d1a(2u872lGuCu`l@Y{YufNBds?Kj?KUb-YmpkTZp$;7HKwE5G}{m1&{cL9;OxU-g&R%b*A+#d0 z1^hpbOgO@{$YDK#x?%aiIDB8@Uhrx+S+U32vZl%lFBNa6(vfzGPC&%Js9amxSp(OPn4KETT#|T?{(oU zxK`n;C{e<_E7}v@gmzbkyQBE`WKD#rOJYf7mBfk?B}u;7vO!I;3+(+~OG22sB$iZG zNvtSQl5An!*!6>kNj2{5wIqb8OJYf7mBfl-ktC{>tBGvEE3|S4m=@WuNA`7Y<$6Fo zeJjTtuEqk3%=Eml)AJI`vx~Qick5%IMi<^$hCkvpd9RtnA41VpVnN>w+&!C$c=H#9 zLd{>1*8J7ms0O8aJdH#-V!xZk;z;#liQd{q(K4m$6}t;XYs<$z_(N|HXEBx$9y{93Ix zV@THr!-KUbAg!cK-PA?wVxXY#be-%aN@-SGq_~D_71fFoCF&iPrq>bStYWfL@D6?PA#ftGL~k{ahc__h?kQYA5>2xw5&P%Iga(HxJXk}yz$f2EG!n8<{ z9zjzRJ(}zRvAcE_j<4d3%oJ)5U5yRq8JYZfZKvmY^O>8fK2@fZ8r2hxqpZc(N-bkw z&usw}=QXx#nrfr3Ib|GBH<4zQgs&H3RN51h)i&=Ss>c?T&|_E$Ifc%!ZP&i=Q;lPy zm~lynV}fUPG)FqGrj29ktSl*q4?weQa-%mIR%jSCEMNtm;b%ogM?|qaef|hvXBw5( z0MYQdhUqDiV$^H zQHe}e7L?FliRNMWbh3@rhVe}$xaN^gzfZ+$LF7`15`2)p?G+do2Nmn&|1J zBz-$c(gBM$cz}2B7Usi*)D_NMb$S;p(0cwa$T`1HlgSwnOO49DmaJ>`#l0inZ!VzVl`| zvb`~t&w?Jn#J)-WT+)XCNh=dVTlfMfS6J<;n)a6OS6xgya;GAmG@P!`pnkp<+j^(? zc1(LJ9Y_KllXf-ado<)lgpA#ohJw%2TPMHn&F0T*RqWDm_-073XVXfIGw5vcU5CZL z)$gx9J^)8SzeT$#bar<@JuWb7Sols0-^U`(0bs`!1%OS5R_neh6+krdZQA~+lW&jGvhs~QCe&C?1G_-MsnaXm%fbuI z{oC+HFs5CiR#V+>7P=Yg{xY_@y}G)~^@!WBaK5cN`OK1bRXTl*$p19055ihGw`5+~ zo769$Lrg|VLABam{0WAU^HDmMr=`W-rBD=JDYo-M!FD&1anlqt!XCW3RsAATVFOVd zF5lXqeu1N0YSmU=4cb!iER6As@GjaWwytGE?#3o_jE_^|8@HN|bl834>y~FGx%djd zJ|+0cFI;@bL3=iHqzP6);-H?rS$TA=4gVAI<8)v)YTe};WHYk1XVY8Ql|N1h;_)38 zx>G_f^po(-{Dhw}`IwsHjsy|=b||KUxw(>KY3I1E9i6UKJ0`WxdLl*ipeIt}8R$t9 zY;KR*QKHmA+TrTcwG(#4R?gE~piML=?=UU$mLAz2ZQ)n;tLJ9S{Z86a@k~{`gN*o8&qmzdqY+0?SlcZ|{laOC z_zn`r#5 zZ<#RYt}6PzW~aZ^?bu@3QJti2&@mCa8noU%8|z|&sNv&{CUJ|)cNsoEl=hifHwq=(=2 zUe|cJISkTAB8kXSSRT6-V>>uW_2UBx3^qddCUngFJUli!NgHoQyClKzfs#y2=uq1t zYHRo*T4_mq@IEd%Dk%vgdoJaNsyVpZ))*`anpvPUA6mq|-pZBM{*Y*Q!^0DCA{dP# z&#^oc->q2MY7KA83pePhGHA)3Ya7&~)Fo5aUlf<-G^ocqL7+gWk*K`IRc%Rs4Hwi{ zwZ{U1T*zGkCWzUVBIhpHJWBBO#T+7PU z7#b0Vny|ps8s5rJlWXC2(}Nu~`k?|XBRF>)dd*~`yh&7XM}<~`k|DJuV_E1m7M%b` z-XTGMk)VI*W~tlaJc(gZq~d|-^AayR{DB%b>lQ{%0N9YsQ3v5MTE200! zT32i6h6pkgEdu2LHm-@#05l`Vs4LT=^JB3Wk)KPVi7A2@22gNo3{RnQ_(gXdHKYqf z35i>2tfOt=Qb#U}gwe^R4&yp;D%;afv&h`%p0x_UuZ#3%%e}3KaOx3U1ev=rUYePuh4k!37-om6nGma=sZ$*o|Hq9J0<%K^! ztwmDX|4j=cdVZwSbD-1ndbAr_MdtLs+v(YVnrACM^);(4_@o%?Gs&~g@;(vI-?2Os z{^OQss}ZNN%v4s9#?$MyPG*@h!z}m%O?#PEyB6EzGnlT?`tKZw@V4X08Eo?E0}-!q z^QfOPlZ7sj^TNO4FC!=y-iYvly+#*jUA8vap0gIlUrtvTTGAmp`&rf|6Wt3>|8)ze z)}ky@Zs+IDh4YXFpIfcvxpU#rIKx^v^uD{oNn)8vtl}JAING|v4{?Bu9R6b$T~2{6 zaXhK|nXZ@y^)vKCuX~4Z{Zny>7M{SHX0ULLWhxYYwF^B_Hj${ZsZ$S1bp>!Lt2x9f z(pS|STqjjrt@%F8)O{ad8K{UDY2Oq*uD*e@r~1iQXjXeq$)J+1t&2V`(MH;-FsC-O zIoKQ~4JKHsAH_9oVUazmHT{3+KiV1-yGMARBlPJW;inv-uLYr*SWt!0TI_Fei~$ym zPC~dj!ax&&()SVR`>@ch{Pnr!>*3@ccp|e_VAQhEwH)ID3q~gnDIDRV?h!uYio2wH zgm(ym8wHJuBy_Y-@+AGDbxr$M5~ye`ofs%unwK9sSVjL;H7#%t#&ujR;ZJ$IVsN8H zJ;)n+Ck*fAH(J$dWSY9AS{9zn8ENH;G_**PL<=kG@$hBTmClWbwu3sZHtHM<8$!p0 zUJHwY+*S;*xD)FXX2{}x6Z~5CWF9s4WX}5Cj9b&hPRdNZ z+2g$?dZBl@*F=Bu7t4(!U2b>Oki?QTIcfBCoo4LY9kr}lxWvBQu?t@8dri8cosD}< zEH&&a+-qWJ>FnHVqL<7+)a{PPdAr)}y(T~TYPUP;S&enC3BTR(4t~3%h9>Scv6R(W zy4S=)@gL(}6B6i*?lmzkMvULeXxuv)eFGz|kBr~Ss80jpzi|!3H4xW8Tmx|p#5EAt zz`u+JjMW<4r5>;a=EzpBpI7z`di0N*@D2Ikqu3=*3FO$G5TF)-jth`IFej;NniD{1 z6$r^%nl^w=MT6P_>R_+640^QrHO97p+s9juuvd~<`|+n6@x;Har@de-ST)%bj94O* zHP{6P>>?v}3GNOjVf(|01u6Gz>|;{s+xTJ{kzL2|Juh*1@(}wW-+f5qZ%*P3w&@ZZ zBa!f=@J9x=l3-vBs{Y#<&Jh>;1$S!6-8hpRys~3 zRI4Qh=~RJF3_sE7bO5_ur*jGqZTdqi9!{8aCYp58b(GU4eu=H)LVO{uAB(6^&OsIm zOZeE`wZ4YGmhoYPk$sekUi=Q`onj`#N80?&N&5k^gns;?84viTol;P)UNhB*_3_o> z1z8dn%^whS4=`bSmuP$rFgkOhO#m;_4bn8<1eT^JjHDc3*pa=$v;Zv0(|WudJ|bWe z3T&+!3IE2B$QKDk7|#UY4Qd2NSR-DdJRB_aA`AUkljH;oplZH?O83W2NvMFyEcBRo zwFK{=21Sj&(0E89%}q44fu;Xz z!~;t5WF&lP_+la5uiGIkD23WdvY&#T_Yc_NGi#-_R?B)3Ca^hO3hEqd`+)OB(2!6Q z7Nl~6?-cxlqMHb#>53vo)+C3I@uUrE%0?-wSwShNDS?92hT{$I!?AF`_=hG&I}N6- zF_pKPwIHtW*m}eaSc5uPZyqlJrNM!MAu!NS?YmCx+P(jjZl>Cc)J`uhFf;!+J~OMy zPW3SCWi+`>n10>g+5d>DEyEmcrOzd#=+CN zz5x3r5FVtkSppx`wKivG3UYXhwP387jFoYrwO7vRl%^s;M!8`PoxOL;Km9SO2`ugElu{w;I&YDaQM`}iyM%N!MI29N2gN=BJ;ogvFquXiC zLyr~$A-l1V*L5w&un@nVsI!ImSucusb$3OKlXXOKT8lW*C}QcXsne<+ZM@QCU(Mk3 z&)q}e=nThm<~w&~AGrIkJ=b!9BRnX$!L4RdtrK)C;J7YEi`5%38taI+xIjjW;Tjn& zhRbBu&ttV|TElx6N<@p^&0KG2(IrgUXAI6XgjH8&pgrdZtF9b}=odU&3J9FppEf3* zf$tvnCp4*_;`ac;pAiniz56fl1!y9TG|{&ZWm4l~(#g`MKuhC`=c4NQw@c48sc*8{ znm6fiq?@G1aX5|b%~b~@?Bvn<)r}E>{>nu5%NNg;w|f%m-msjtm9RSilvv=uKKfx( z&Zy_k9<}D25zn#aoRQGpS(C6iXBg4E@@&#<${GD!hx%sd#~l&1DYUkOlxr=3A4*N? zA+#)S;#jXl9P71K4K?9z-6l1Ra0KBqR4*1NkVg%HvJabRwNTxCGZhO=<`X3}!Hn?wOq(E!X~fG&h<0nQiq8O=+oJ z=6UH(T0Y{bopO*xyo0(JtQf%3Te)JcM(EORlr=GyvQEpMif$&d%TZ#~zPeqK zaJtbGox#QM8L&60+oUkGyDf=IA7wpB-G<7wO#a^~`qSy!+8{LemtfR~u^dL04Pu-v z6kTIA*-!s2TCc5nORnh8i@wQz5FVMyMqEBy<~Tx_c97MF)!ki;3wYvIY74h zIcJU2u&$gi=d$pj=~*lC8L60gc5)XGjjlw^PyLr-M*Mp=q`?#!b)Yv+~Gx_b!OvcI3I_}|0mC@pN)s@G14f#FQ5-PF1f;`LquD-=_P#{s#e+++e~uzjF0(*(FU#w-&shXV>sz&s9EU;^&ofQ2UD zE)KZc1Qc<=5)-hL1HNGbmT>^SD%7*@=74e&;N^fy6R?5<9xwqbIiLmr>y{==&ve+T z5k6p%ly$n14TaZBC-hj`lMZZjHrt27_F^K_{-%yHAT|nZhUzH&JE5F9!Kq^NQ+z); z1sh9$!$uc2U~wCfwl`|-oa(WlpLQ4EuX29lzBE^(YA&84xfI$gt?$S74O;KQ|BU@o zII~&QA&39kr0xN?4-xhve1z~Z!YBHk(v{kl&w=`agE%X}*f-O+f(ULdgAeZmM>l>r z3kk06Yh27pn44&sk%)JAR2mnTe~lqO_Ix1!DYib1wG09wdNdZ)AG{=Lg`BUZ)5%+Z z1IwoHN3}J7lPb>T1b=A|w7~-kbPO8UyWPNIkhzCA8H?caqZh$nKmGRY>)mbN@}poAr`W6HmY0{6Pf}cE9#09aiYv3TXHQqARd|%eSS2qhy+t;L${cd<$| zvG89wkH2cR7cslQqIb3WJIV~-N7>q2JkLT0qhXsSmzjK`AB z#q;|IBfoY{n1xIV%}CU06VVZxlr(`JqyCwIbyFT=Ne_-i#n^(!j{_4PgWrPY+#u>% zvXNM%h}a)v;Ws$2b_&>_<~blGzTMZo0YmTEgzVISMsq?f{JE`rX4nx9*ff6dY0{WH z&d9Z*+=jZT3HdnF9V}X?hFV1L!O^0nrMorI zbRdE1XvN^yY-|;sLS-U8ofrO$SMc-ocPX4mpm{CGK=r1v>;w%zrff5Lk-`a$Qsw7MovP+JI^d6i+vHbVFtA%k&d60hO>*1RGgdULRwd2wgpdA z99UfV^1N`ML;YLm+;+~O11^;z-xoYi4PI!nh_}K#MK6uh+>J9o^uE~ION&E7^f#SG zf1GZUy^eHjL3QoFb2fWR|k(||roc2&X&YNrsEx-YTQPP%o_U99BeskfuXZXnGcXAQ? zx|1vn-HvH~Rj(9x<7WHthrrq)`FG&wbxim6Tw=Q84Y0GdD*T{&HyJ)V6U6$NhwB#} z;ohfW?%d798N;m;^#!fs^hFG}PGGn-tqzA~(Qxa8?42N-tn?DvWRws(a_XUYxxHdS zd4>B8y6{#8O*A3`A_<`vF#q3^V5%fqA29P?FAf~8FEF!ZEFE-S4}hm)qIYtu`c}ky zeRK}qHf3#4-=eV_S>B%vD6ewi{^X+f0(a4S_{*;~asFrO*Cy$cY|+$rnK_T7N&Ku? z&RH~HH~IpCiVjco2h`)m!kcaMF*=?;6F<|nDbePC&nUAPZGPfEQJYU4m^pwIZGP_8 zZS&2suPxMuwlu00{Re9Evy4)-hg`AS{2ODp`4?N;{DEh`&Hs6?+Wd8*&9Cp=<|m#> zo3Ay_`T}TF8%G}yaaa7#ZQL0)-6e*itxdPjg#N=f-QO~0wKUy7Frb#E`w(xsw|u>( z`=4k=^1sz`r=edzenz_rJJHyUF^UY!*T<#Mrh&Z~pf{!urZq-v(P^;{b~(zqH& zA{&-<)6V-3lv8u12*!z=zLH`N<&R4m=b@mB*dt^lkjfoDujj^Vk9YxXzDf@?y?70+sOG?lMfs z9x58ZwMQBWrQvk`zrrAKDtBz{Uf)gT>ARqm4)w1*)ssW9=XBJd8h))+{cEJ+D$eE-Nix{!W+W}_%j!1 z_&_Eq0Mt$les411NWwy5n^O^!yp~d<@*6by^d-{o_tiej2T zRV;!R#@6$My^IiiYNw3A*L4?}wSx3C-qcfg7#Alvg3Kx!>rMj;pTD6|8kXEhiR%n7 zE<@yVJAwucs6am1m(!(oSZ^fGZae&SR2!YopBkdm$bdFk60phgQWD=}nZUErY#*i< z+^Q*v$`4-3X#^$=V`7)aKaoAnQ6oSN|DJEcWOkEwIEOzuOm8HIvCY`&*|QcjOcKAP z%^tL~8{}@N#E@X2nf&)WO6osPi=9sWqIx&kd)E!dP(QA=BO|w3Hs8RqV*^Vl$$p~| zn`Fc$C}}QIuST(M1_sr99IlTCx`FV^sIpgDt%=Fs&>Wd)?-{ABb74EVgK!8p@~i#isw% z7n>eqvCF(q#W-Go=1j=exojUX%1DgY`X+LWE+#_d`J{>4nanox6>ni*7mP@ssfIp9 ztunffUi1l@gkj&q4ZE;wCWV-)kmkzhhMy$GwToNBy_sv-31a^saS zl)vb+m`)ujCK`{K{UC3`8_*A?AK+3@|GIe{`o@OG0i$aJ7nsr!n+YZxBZ1gyGjkUD|SaEl-~Q{5UmP~(^m|ldnYmQ z=PL+N_P|vY90X(6Hn;!9R+6|k9=>j$TUz2(W)~NiD!MBk9PHp>mpo@_iAQl`|DaeY zFNT+1d9g<+gcn}vXxalWCbvK@_K%B;;6OMlYfg5);-#5)l#r6#}4=X3UCO4d=H1-HayY`ExpoC*50V}HM8|HOi5?w(}wUH2%gIh1`~8k_=mx70Uf;7dRWAnwev zD#cakg$7F%O3}$Tz>-;w0C)E^_Ii}`NlJdnJxi4wY~+-9?^kXqD=J3uFGES{9E~j= z!-JvoPEc>CM0-zY6ecOR6c?2ga^H($#id)&={f7p6QpWVsMxtm7Ya1h+C^eLomv79 z*2408&9vkJL50~vVx0|Tw=*GKK$G*$)unsi_J*4K}sMT zMMy(Er?Fz1#;K(d<-?7pTm6XrLo=yxchNmfsPPc4C2%K!^9&@@d`mP>8r_;=DXANt z$yv*madY>;_&px9E@d!(s&=AoDs!^IXvSO^gxR{Fc_^h7*5LPVTxP+1;9~ zwNu&=b$LxjASL4He2K1%>*- zD~F0NrV2h!q;*|WTC1-&RPfu_uUm0aQ3S7FNYqLsN-tQp`%dEDQ#Igxwa$4K)~zUS zK{p0)!ciBGdp6G23omv}(F?S9HDwx`gqo7WYDF!2vXpHmCQ+3{h8R&bB$jXVREux) zb-dKf4Ng*s-LOuB?&Kh1vdpq?7Et;Xt!V(u$Zz=AEf@A5etM*^U z`n}EzMKAdVwPoWRVFV58UpLOx?V-S^%)Kpp+C0di%>!cm)yqEGFW$(6MXPQU(>116 zHG3ea*`!%@ogw_};rg8sC@6VOWOg zMcwJ0{??n>v*>TD3DpdVdZl)(<=|6=LSCP4kE&0%oJNbkH3?d@`2P&3Rg2%omBifs zV|%d2DyUi%Nne9lQk2&^t*`4I?1Xoj1&?U)w!-jW_qOv4Q|A%7{S=S$UZL%C9&bhG zb~ZfN1>;$m*BjaCePgUEtG5-BhAlbzkS11zU~bJ%w0DxSxjQG~=PMxb8Pk zGbiorJ;A?cS9_C%;cL&2=j-C1AA9bmO)YtnGi0aFi_V0dK9|v_d4DIr`mZoai6{9N z(ttMOyb#~4^QN4izwJQzu%mrtgK|~7O+P7CT&aIql8m2iScAgO%Y{bqt zVndAB1&EQv&GrtKhUS?`ThG$~vV`VXe|M?=E^5Y!w|h1v!z+YSNRmW;Uqog)(7Byv z#u+yHKRYMTWN$~`UYMlKI8T^Bo+nx#*2J!xGESdyBDa`v;@t4gU%Qe>L{TUtg2b3{ z60mj(*kHydoCkaM={iUFbR`>7h+KETVJ5U9V#)T!T!;u1#`AA}vSJS%vUABv zQz02s&l4QO+(xnSU5~Zu(FXOX#fPf4=4g7k9sehH$SKOf`sXMCys8J|CY7V%z($*`-@;&ITReul#2P`^Ra+RU6vXj}*FMJC8nWCD|7 z&7TeJT8x6&n5Pn0W&9!L&&M!-K1QXKiuv=gNJV$^=VLflxtsYj3H78)3Jjhl)vJf< z3l1B_o!qIo8wx_k{MlOa4eH^IgM|?^sNs$0>-I1N^JmIZlfAcPPxEKHg!wZiJDX*1 zI&N6Y{zIdnta`mQdakUMeXEL6&oedBsff(3B}ub{btpKCOOeI6T6DEgjiOe(*>00W z)-k_5W7{T1R~R|b^GDDd4X8DL_eoK6S*vYhF57J`iQsgTpe2`X18TMRWJxnPw{Q}O z`WkzFOhpolRR*!{=dy(N9~L~)q3FxMjWZJ(I@DvKzqE4>5jb$vsclkS!6_EH{EKe6 zzGR_B)J-?!ZaN{}>4eV@-TXOfwX5-zk=_;_6n-_coOuuUPG%M}@4j$L_$V*sR*VhA z1p0WSvW32}V@UNtjDuQEaJL=(P3KR*MU_g!1e#)J*cyv2xA!%YMqzdup>a_e9JM=* zy|9LalY6xjEOt7(pX2kL#lQPn04+0c#fbF)V=02B z@tq_PTh>+Dm-exz<1w~v1^MWOj&7;Ex=LBN){=R@H= zg>ac5{|9q_GJdVVztAN9e>^MNs)2&t>BF=|ePrKdv)Bs*C|H z?+uMDICIQA0LO;+5w9E>-#EbK;(^}(%;^03^KT7L$UND523Y%!S6qHVDLyq`;qnuF z8dF!ZpYF&SB?Z*Ofr2A88;*f{JvA_Mzx-<+OW(2UbMY@>SD@w5J+&Eh2hd4;LCMi) z=;&J#*_z+JcW}*byGhqzgX`ARNacPm;7L=8FfKyb-^kFDn=X>}FfKw7z^g5o){9Vn zCN4tx6Gq{GO7bk##OZH{lhD054Kx3g{#iF-B71ZT%AaULK{92U>^syyg&&Kk`$KPc z@K@Q!D;%AL!|(o#qC7+jJ(Tp|^_{eNs0ViVIP1?+L_HLKvsL|bq~d%|Jl7=73-%&l z;Co|@J__ttdI{(S)&mO^*jjvoMgkrr0S_iUIH!}s4sGc{VS$+kPcQ$$@H1R~Tn%{$ z$Am;G5AlpFH!?zBj7Z_x$Z2R2ok0<$qF1og^v8x2*lCX?!%2SIAPLohB>Q@&36R^- zEhRX959hkf7VGA>m6aC>zq?|7DhoVZ;P5qX6ujev^9+AXW*9p3`r;ejaazV2#&tK` zaVFLaD@?!DMqBNNyWnEu<{S@J{o%QX#eV(lKy%gp^k1>+XVK6Zf?duIB=4b9jV;n} z7I}0h{YKcXed6CQA7BY@1on;H`sh~e#7x`)tYBchd0ylg7TY#2z>ltuPgi}~64`k`{7c@Jeqc6|1a=3u2X+Qp0&S1(Uf;k{CA765W}LnI zE0!*qXX&n=#I{PSj$ru?6N1KvY_9d!v!H}4j&|uyqWzQRzX0eGBp|`12iZa zWCJKI8f0fD!e1VCgHN~28o>eAVkKdYB8QLjC%R1*XYkUCCcKck5TL0mW7{xCkm=rI zaT=|S0|3_nNgM#TDjm?D15!*tDhCWQ0cjk71tmSn5DvJ|1YFDkmzn^D1MtnQo@5vY zTxkMEaKK0tFp2}ln1FN+z^boKV>|~;FaZ-eV3G;Q;D9M6z`+62Oh66?s3stv18y<_ zGdN(T32<=$yt#Ba=5RoP37E$L3rxTr9I(&?+{FQRn}8w?SYiT}a=m^mz{>%ZCSU~zV5+JoS;+x40IbL0M($)X>5{AQ1X|7%<{$&-22Vz>h4p)pu5Mcr zPqi-Dl{IsE_9VsQEib$uzQb^EEw6BUVMv-0U#uOWU>n4H+L?(H(7Pal`t!nQnwMZ4gTP>)ylv<^T$g&iW(js{gJf^|o>PuI0QO$l|l*uC=HrZSmDcc?LpUffS{UH13^mA(;h78X!Nu+ zmb3*uEy0pDm!h+Sf&^1Smn(F{+gYLb&_Vlk2}SamQnATFaR~*6S!b4!I~+V-uvifX zbaHQc$2Sf#xOwE|y5j-tex>q1VU+)UTm8M|LjOz9b4<%WHpz2jb)RMx@@PoI#{o~f zi=uXITr_A_=Ze3 z^WT>_hxs4RyoLE+%PgYPgeI`jIQ)n5vJa`GyMYtvg%aN^r2XZy^DSD5DFf3K;PP$)?p7 zV8tRCqsdP4q|!Ob)4pnw7|mfVNT)Jb*V*LhSU>g1uU+iJ!FMaL)Q~!}EDw5>Um$PB^7Sy^&E*!|B?nbXwhEt?tl9_{gVb3RyN&5Z}M< zIn=*!<3{!zeU6pRbAr@brN9Wyce;r zMH(b1Fh}A{02V%VIv-OaPp8#LhSM{9G5?`VmH9u)yqf-vWd6T~QzeLPM8V{Ynk)G4 zge613;MSuyvYOH}TAVMA%wWN`@GhQre`0V)>+Y};+@TY+A!F3&F=NMFcm4PY*;8}e zx#&&j1d)-%b=&TEot{b(gP!Dd+t$5%$Lkx!``@JxM~&w(TMrwLO`hZ&2FfxRv*cxRtcTSvY1Yn6tcZ|!<}*So$%r>EZqCF%Q) ze0{#_4n%t0gn#+YKEWkQa7f%30BF2fX-tZWiLoXaUe3bjo5(~EWE@EA((Y?n)*D68 z`gZHPYhQA#eV0)ht)6sETXlYGNx#;T^tB$G5?_)IJAh82_QPH8?%KKQtqCRRE6=M> znBeJG=h}BD-0Nyjzl%%KbM}1zewQ;pDg$cdvJ1oK1>X(6gfJZTnPSyPi%#Wc!4i|u zet5Elp&!+=f6i%rd37H_ZgOiv7t}V+Q5qA7pe5{o*DX%#DUgKuxEx2y~u4AKYC{9X+1Oao*BBQXNKN0L+_cPV|`Pch+>-x z_sr0HX6QXL^qv{|f9_0Xw+!?ULo zPwyS;%Pl#`9wd3q>VwDnF1eVtWgO^Sa>2`W2X?p^3wRDFw3u=*E&ToqQ0!|7t5#_+ z*=fz+b>KG{$^(DVe!hhTTcsDR?mew;k5;!=t9u{&HZqj8+fuQ3Go(4o34`!tXyy0w z*v*K&JJ_%V0sBHrU^euAuw-we=iSB^-8zb0Xlr;jfM%Xd(Lce3z)c{RgWADL8=z9f|(`}qlRN|b39uOn zUrE89{*A8Sj$L~hmRV2*I@2{dX!)-1^69*_n5Z7?M6E;o_{TO6R6j zUj5*2|L5WNKKlE%GiW+`qV#Z}hXXwv=;1&Q2YNWr!+{^@JzE1{^k^5wL?vpVB%zZLOfVoe`2r&1_7y;%!86&{l zCu0Pd`(%uOmBv1qUbIgpg%p*N$Yk^~_sKvwWA2kdINvAJ>lFKBAjD_6Pll?;zjU7r zvEZ!s$pFFG?vv?0in#k^x{o66KAG;Lh`&z;IKa(QS>XUm3CG$e!#|vAp9}$= z41t;ZWQ?*h)`8A`pNxU=3$stA*XO!VrdP~;GQBpKTUC1Y$(TEV?0aIcp{Hk`%)i}S zs)yhn4xA1L^eN*&!!`k1%6H&cZ~Qw<1~W&FC0UL(*Q{j+PJ|?n=qJZ;|4SB zFqnaD159RCcP99jk)M*fPBNNNQ^;uM4Te|gO{+V?H>Q$bxs4;3ngXjCYzT+p%$G#G zX2gTJ%o{M5IT0EIbD3kolHzq#xmAx=*I}5t z&U?>!;?D*f^1TFFtUHYxqj)F!1D_B@_Dn-twT zEfr#f!Uh9_Z96>hRK3H~!j7$3-EvG@!fmil9Py2Ox$Xq*G=}QR+19#)1^m6ph^9Bp zbhtH5ur1&@!4273(!$%YNqA?l4N;qZaN=;_zV?Crh1JZ+O!w_)RRx>nbAGZm{* zw6sav42x@Pl!60YVI8$2@gQ?hO`w0B~y^>8sarjZgV7SWToy8jtt_5_FH=}j%4H#;h*uUeItl!gDNvP z+sPJkN8(-tQx*f1|K3Yh^I#3ZYqeCKc?WBL&CYv@2zh;MzrlF=ZZKYg<#x4??c?=d z2BG+m>_h!02~b^@(653U2K2y0$@xBulIB-z>95rs24PW@5Trqg6icQ=2C-4{pwlXZ zc$94GHYEgUP$I>WDUm^Jl%(1yA$BDAQq6+N3m=Wmrmfv3jvx)GKh`1Dy#Vn ztx}WDpxA z)2x;aY>HiA?{u3If;1?RV#$=qAT~<2usCwP$`q;Qo!zE{APq{SSTZFth(w94m0PWC z!6&qG`1@5E+ExoB7VM2#Ks7CMCt)ri363N~Bma zB{GPOl3yp7Ea8;2koUnBS!PT=C1H559s>@&q%7X7)e^-(LFMrx>539m35AiOEvqsv3AS6w zT_%PFe2^YTP;awk*eZsbD=8*O zg8@=3nIaj)#(;Ms#sIp=NFARkGA>`eNisHJ_0{Uyn>EnC%>BG9(G zgHE3_FWk!UM2HVhOYatx@W{N-N^J|!VPr6X=@0#l2Xv11`A-*s@kR!wSIs`8z$k2P zf*;zM!VfKfUmx+_XuWe$HIzH#i4sRSjgX74g__7-zMF#{_@nI5In_ox0u?ym8&3lP zf;S4$7Wc#?Pn*9V^*jwkY2(mFQc}!e zy)lrmQll?ARA?x*Fh~k%h94Ih9d1kV)cGTLBT-viD@6$;^ixI3pGfhls1#kkLVNxk zJbwgeBxQ?aB_)A`@~A1&GxwB8DkCbX1mEqkk|IDODO)5fDG4O>o`W<8Tn!!w!)qf_ z%_E+EpNdxkluB?F?GF`mp@!R0K+tg?w_4>EC_>1Ycqdqa|9t)nxe}zJtO;1B&lZo9 zcRo^;076sX-WD5$_t`0VApt~CueFBn zn3XxgiL_@imR*JyU4J$=je2ZzN$UmIQN|Ast zI`4-2^d4{C=gdlxmrRd0?;da7J>I-~ym@oy<2~NI#R_!K{~iu}{v0r^v6>l_lRFZj zdnHR69J?(%Dz!6ic$DB<$^kS0G(RfqgABtXMBnpRC+W=SEL#FGG(i&hW)6jeZ=rXS zK`8AM!9zcm*&DjNDv+XBc{po>3Y~XwsX6* zjx_R>2Bs1_b>Ia$@LU3hSz1%crx`7iUi9bi_w^o_tMTy7pkU8qH$r_yW71uR#NWDi z)*bJS6I5O!v$7M7B`rbE2?05ig=a|VeiMXb?BBI_z2{S?YzTAb~zYag35>+NB*IL8s<*SHXK|9t3{9-M)xVIvn~-cGco;hjuZ zJ4uM!yB1=c4KE34q!4Q;ELn)3J%te8PQ&QmFj}CE_Sx*? z`ccEnt-A}G0}1xUQR!Xt9HS!zVE_oPs>-SX*qqVK63xL;Xa3Pn&yl?FK8!K6?P%S3 z3BwAWxqM5uDCoReXd}gaZ~4bG55^K}wy-cah71YFQdk+a6(a#`?>LY5_*fJR-Q(=c z|1>Se;JuX4&!Nr@Q6H<&wS&Ej5KufdYAl?96P z7m4g~a;hMVuT3@GHy+MJX?%CS^iJt+#nNU=cw1h$$zYX z8W92pf{ldbHO^`&{?(jNeJLHZ1U*NHhFUkHZmx=_oS32 zM5bh|Q(nWzwHHV$m)BtEc09Bl3ydw{t$E@5ktSbMm*ghuy#XxHGXjqu$V(<0;ZLTD zJ0i3am>_1NupA1O1z%vzwg}h&gb1l3LTYG+BW`iFVy}W$yWAfA7@|YKcycV%+YwpB zwzLR1lVu6$tk4In8_=qNRzp9*T4zh>didr&W+#DiV3E)SFf+%fD^uo|>=gW3v zsz8PT6gFgqr%*ZkDqe~@&_%pN&@j+>mGVGYYzCsuRs<2@pw`?u^TQu9ZO)TF%2rFs57d{-X4bI*WPTjl2^z`dr=)JL?B+ zVUFMqcsI^yL5sXT-5NFJh2M{oKx*qFCG_i7#pLi`i+B%4yl+IiA)Apr6!G40igy{F z`kHkTJPO73PV{b&z9-=QLFt{~za_oPLY&MplUZ##k8jjEg=H!X^V4^A?q$kyEev}x zrP4Xt_~3qxw;hknWRuqI*ZjgTy>UuD3tgn-g@4OGYA6?8Z~4Gp#|wYiE!$mB%7yXq zsR~1nI>1Q2NNzIGy_}Q62}sQ1!VvKuSvdDf--+4sdt~9zI72QR`W~-v(pY91t396= zj@}_Y!Kh^Z9L38i;3Zs~d;V@Prpfbn^g}QD2XOvVaflY4f*->SdbKna3O^r(Pn3;j zE1R{R1CF&N2Vr+|kkw|aS$$xGqxQ-zsCF1v=Ns1AkPH!WqkUX`Jx`wMCu5DloW`QK2pY08!R4i4r>6sRIG})-J`LMS=vhx` z*x#9+7>C0vPoj_UMp%EuvHm0$){vT&SUWhah4HbbNm%LGz-{#Y z_j9c6j5uJUHB;xn00}ESTfA;6(f9yO&$Zy?a~#uevUlGD_z zKs)40cqqfKM#ri0zu!E-oG%6Uj@tUb*1}uFAAG477LMAA<2wt3+eRj`!r;MO`rp^y zcEYaGHMDQjhig7OrtRD>{!+GO?4OM&aN++-aA)vXuV?|6-qT0MbvE9gaM&|nuiZ)GY_u1Z{U zk+^nS;>KlcgVT()Ce?}UxfY6(q2EID48_x$K9gCpNNr7@<1CFe1NZJns#&mqjTNJ5 zrYqy`u?`?S_P`!)1;e&Ju;HcRh9oo!+`y0Rm zSgIavAX}h64Pq|+$;Iw6pHxBd<&#;fChL^!+&Z7LZq4P#*-;HjyU;jCJJ%Lo1eKkq zPp~8{VE~P@iwZX;h&kV8uzJs9$8;tjUaNMJ8$X#03%Z`*OE0VB5Et_&68=A0WtGhQ z8B*%PLoF|y@wUQA;e|ty9}-;HlnTB)v6s~j;5HNfRGvPRg;`2i*qDUAiVfF#`GFh< zs~u{J2$KC$#K(l@PAxn+{DvHiHPM9TlUjI~Eiiz<&$Muw8QA1jGWJ7FDorXOAXKoM zQb6UfBvL?&cRHbSy+`AE-Y2qHt&`SfIb?sMoSXVHmeJDs)@3giZrsiGI=oNv^$)!n7_1rvI{I zIu{b3%(8SbO_M~oL2w4La}m$Nvq)I3qNvexh z)n39A{m@Ra39}}Z1*8`T3ynz>=?0KlA>Py|wcA-OK+>4M+9b^qJkX)A+JRC9l2TAu zco>hWJl6v}P) zP*?`^Hi6cp25d1F~&Lo&50 zcq?rFC4RB6gL*_ImK;iV>O3fHejcBatZc&)#2LzVavXsv&oc*J*I0ctNC9VbtiO#B zHBm&}^jm6BX@RVL?do>t>U1vED-l6zC0enPrPT_7|8M&zvbJ*|rUSLc$ix3J%-HkKJ+ zwaa+2B7{1}h~CYAqs2ov#9DM!JN>q@jI;TuI;it*1-vmuolp;QP14PM0vEIg>i1M%(8Q~zf~>S9>2J+TRPMOmPEN8+Wd zk8R|UCbfF*fx+TIO~;$wfr0ow@ds}fI3wH4e9%2OSgc~XzOkg;3~Y4|1p(7r-IwBL zsu>JMsXFYvX0030}gkhQ$#J6i$U%Ge za^11@F3^GWyFEyGz&5&A-xXF|L=pd0g*$iO9XjF~{ z{=}y;Xar#hL{i(Cgxx*Z(lP8gmiZj>U&rS+E3YMDJ3#E^Yb!e-1)Tzm5tkzpHU`en zt&Z?-g(W#86met;{f1pKT0MPuGxp!C)sqfL@&jnmVUdUFlmALV$)yG*7uhH=5`T|{ zUx}CaEB3?%`gHUQFW05f7OXVBrKMk*mY*J~)I#4diMl^FQJ5wS7erlR5H%nMQA&7P z_| zH5z6Akjds?Y&N6pM+i0#Gl&^vBSw@xPru#~emP$H5v@{}#ea_$vScz2T`2x;)Accn zPuJSh5P^HJ%gd;yR;8;QDEM=M!Jia^KjA&`xbd7lRlyC?577Lc8~u;vVPq?mrY9vP za#WVP5nCGaZlsAZ0_<7{NT}8IiblukrLCTOkl9~H^rB~<(L#_)!+J^VVr0~9>*@Ne zH%4KR87FKbx|4BO39RlDQBYhLgBf0Jl+5VNB?B!k!$TZq>BW>~BIA}AnF{VesC+E34xbl0=by8iS#0 zSEoB_Q#k1ML|LJ@HHM6}HxrsYb~MOm+7;-`$m*<}T)ed_t70==lndz@;7ZQSrSZVL|3hkU3oBuTZW3x)W9{a>(C-1PC$_VDlu?}9vF2r3qQ6X2RDXi@SAU9KJ&mu>4C-(x=99v((E$l>QjnwG3VRf~UGsfdy=K1irUe3Vo0SjJ zcU)rSK;;G5W+xldVLmsZEM_&|g?vTmkfdzOEC+!qk+lJ24Rf(aHyDHehx9Mo6CaeG z9Qx4jL8h2b(Bko8rHO;}M45}60P*?cj>IRKpY2F|npL~F7zjR4N#g(#p%{5aXATck z^uTB3K$jl)oE(^}2mVA3Ow$7~faMJ5I8{CHXL4Yc9{6)P&``+0H<7h+foWeB^0$U- z?DfYf5z6~yG;aNbVqVvqiAi11!-6G>E1hd0#FP$uMPTpQ=plMzFQnFA?`aV)efUeOcuC_gZQ`XLfB96r&T5fe4iBYJ)L8{!yYR01#KJ+lz>+(f<>*hWkH4?8IHr3c(br0aX9PLPV%OsHSrjZ zd-6L^gpalJkBw7?3!Tu0vZ|Vz+G@Y*TUJ@7mY4cVRe$NcMc!*v<}346%w1Ai;a7)W zIZ^d_7kSJ4s=s2fx2o2E4S&eYSUT($m8sL+nZrgeO<^@&9coF1e<7gM@(N#BRb?e2 zMgw`v)p^U*ibadO3o3xM)bCXnR{8vFc45xk+@hj_BDJE@@8!g$u&b_8XXnhGu1=jZ zTP@0+om-6mIl0qIa^31xSEVquy2c9(wKZNT$rR@E*3`|VY)}TuN-MAMtMj}n<*;Uf z*KhxvQ!r~*cD{RV&aA?j6IGpvxwDJ2i`B|1zq;I8Qx$=5c5YG0^qkzeMY%V{jo@AC z_g0o8tBZXL)J5LP1^$KV6m?7ri}fK>%|KRW&LOrEJ8is~IS2dzr*rd*r+aR-(_UM7 zPi57TN_AeXPpw{9>hnfWT3MyeLoxcie!^k0RjmQ*ec*8ln_n@%s>WMdPK?rvSf!$` zs9d1Vs{+yUmifIt&I#}0YX34@ghi!3FcttpDyZPQj9a~6@!X~Jy*05C0~xv$)|6KI z0HpdsJm|z$j2O{I3SeYJqOBW%^J^C^V%1fP7R`+XY*617G4bNYrl_0H-6LyyesNJ@ zaWwgHgtmNf>0Bz?xmA_kxl~NC_^#uO2^$A56=9b+Q8CogGJi##mvsxaqB0(I913Pn zErO(1mmzzQZORq^)#>@Ov-5K!K5z)t=SQ8=X^H^wLDUy{RZ&~1A}uamMy-Y3etFAN zSW0!7uO@TYSX50UYStd#M3tews%LSdJ*cTcE*2P7zq+)h!oN&KDCz^srhqqp%obcQ zr&ulUP?nkBs4QhwwbW>;G9aU-ePK;iWtGu92)wp%6GOGPrlP96D~PK3^Qk4_v{7rV zE-bAnUs8&;R|K)1>l|}Hw_qBIkzObEd3MraeeXD$N7E^xw2`y*Nbp$m-UQ5g8g64x z8sjzqJ>h6h1Q6ft*?OdL-TRsx>DO1W-|S6LUcq$WrL>H#yV}GbHr@d+R`4ee`~|z1 z&pArB<8#2Nd=5CBp942*ubn`LhuM==6rU=H;JD>S#*FDWNSh{Ttv#nT%?`N*?>~N{pav&x#>)qC}<92s~ z^=*gyax$S9E}5ZORuvXH_h5ojX`zGWnj;27xDKVsp3Y3zM)$zR;z3yH%Tk)xUr@O5 z8dj+Aqc->Q9J#EV$+_x>;Lkf(IOvsZMNCR}xCc`Za&<0cKkz#TuX($|;&0tw*ICQl zjWY)}-X4=VYgX^YP=0`1k)_&?&Ci{ot)EFU^k-KL(7^MzaRZBA!e^M+I!>(xw5k@< z=~-qxeL5?~1KE0ag2dGSBLdZ}e0`xsYVGR1$$qSW?;TJ(`&>$Rkarl4Ru4(6GnAv8 zYvh5Ij(W6%ht7F7*Wz|){*rS|?sk_0XOR3~tz@E@Q$J^6;=ScbYUKu>Y<@1Guo>z& z!-lh&Yw>KIWg&-w`b>vOlv>8H(nZVoiej{kzB0QUyw}}9)O+y;QB7{gdd~-e)gRPj zzkdQLz`ot&`5|XA%mn())Pz-6z9y`? z=4#|^$Qi2EGJ7%q+VFQwX{tWgg&COasO`^t>cVEqoy1uZttP4~sh{yrhs_MSWX&Qi z2$5E?ztJ=`dD3vUx)?^nG8Gh`-Y~L{Jd6ag*!_Oc)Bii>aG=qBX=5>rn`}XY8;jMT zr(dun{q#nVoqS0hR`{A0hGlZZqBnfm(qKlq<^1;l-12MYwL1gnlrtDOpWxlV* z_;NleSagcJ(3Nz=>*lFpwldclrj=HnxiNZcO)_cqwOwHFU$LOJrqo|iRXH(*O?O|W4l7ruVF5?YE-$Ty z4w9vaK&BRX7gyDJX;q1-?h4;MDq{O-}RA9SQ9 zo|_AbX2papf(l$Ms9dxRG}O>iR(2W1Ro&jYin7>A&&HZjC5VhgZ$Y)cqO5e0It!Vr z0nYeXau$|WE&!IUK&lomuB`+7B$A4kfpcu;9PppAxU@4z4NwPzjDhTtnlmNg#Q=2ONj-4FE%>H3g5}pnBo7hg*$d&!pqoZSY#;d7>!*i z047HWNVEcAZiIjoD`!jRuasCEp4tx-40k>HJxATBIt$@3Dz&tCUoCnOe z0v7UsyRCplJfPAFsOAB%wlT`q&jaeLfTcWOxfQUI2dqYbJR~J^5J=(n#70vHFJ--L zJAF)$^%6*GBuG|bVe{iw@85nRA{82B=msQ%__#sqbBu|A+Vgn84w#onDI`axAm}6_ zEQ-Yw?NSm?LG$Tr=F`_7K9yL5}T#9CO<2TrIPJI=} z#V4cK-oJ}&O2B4m9*IMeM+DA*hb9(o{2gO1d_BoaCdxuF5?3Y*5U!0cvVes!5@<2o zU`sIa`$6$N#!QY_EYIXN==oNW?|rs>TiG@9r6(g+ug*=rO@JcG%wB7Uh#8fx%R7b&I?b%0$sq;)9uAO+`2p9ee!;o}%`A!5%AY4+$_K&+ zw|hRrs`_UMV97QYH{lHZ077yAd(T!MzzGkwC_khqA7jsIJb>E-z#17K-AJ^_b6`Ev zC6JH8bK{}l-#-rD5brx&>4S#w5TT+h)`W_3u}03!8=;@{{|L%S|L^pw{Uf7lubuKE zHX5fh{aMslYPQI;dpm=!{ZQUa^P3AIU(goMy$ zr3LZ)6J@Yw5~($oNHuxF)DuwX(`INQ8Wy4 zJ^o$Rs(v?$XYv=Ic+~&R;BaLqsbZrt1)w7m4N4O#cE*0ZZB~=u!slCfqeHH4vk3vAtT9n0n?nvlf@pBZ zPDl#vI8jFtZG#yth36zZ9?d|g4rdbZZu>_pG@zr?^I2%jNjb%h4pvRABXD=WOO=OPbq!ziWfXM%S-fl_o-_U{x{t5CpqonKr)=>3wH#!pCbxt`{>E*qfPS4|6Q`aNW@vYp+2^COHw@POjz}~ zrH^AmzsEIe)~IoIZa&6-)3fvP3uYHj&zX(gBE^}bRCmEl{GT}oKN+Vt3_TNsQ3J%- zL+Mz9XugVvp`b2HgTcoA-PB_ZI*0l@6IWMyEE6bFkEQno=Jzi8g8v6KMHhOmN@vWP zE9{zYa!PtGw-ZusuH-ka+t*z^*S@axTn8fTS@= z6QF+bXHYG4hRQp*VypYAgZ(zu!C7k#BB$G3XbB=g?G3NqhE7Ery0x2aXx2GwLrq*= zwV@_ZoHo?_9@&QO@VrQdSGrl#GNUE*-E9|{E!}Mfx5RA*S2`TEeKpUEG$i$q+6hh; zAT8ky5SH+oR?mwVXkXL36UwW*O!+;9r(F&WH#K@~aq)L}LbAQTZ@t5hM;ffDJA~5( z<5ZyLy+||Ff(oqesDZ8Zi)&ZE2#VGr)^W=za$W?qdOWfil~I0?LdUI9ctu zzD^1M4^MtrU+%}5($28}yFds3%SOIz=$^OGQJW<7UFV?CCOiE0|suB!V?gUJeXZw195!6S3QZPhoRwO_y5?aXa~XC z37ITf#kyoJ%~^nVM94J3*IDp-+e)GTZHDUUWhBRzX##%21m-eLK{llqwav$p;+H+n ziTK|s_;V!Fam)X^r&<27%1_dkiSqkpV%+je1E;OQCUlm(Z=}$%W|8RSSDhHU{0LYw zS)tyMf~q6qXOJ1CS#Rk%qV7_y{$HrOpKB4G`6z{WR&Vl5E{X4(p?XOiaK-8y2*J-x zP+pHHNZQ6Iq~USb>5NjAi|s4%TioRTol@(v#VtIG7WZsx9PxOc#dg7Cx0AP(P_#si z0#v~Fw&3*|N1<1np?W!Ya&w&MQ2+toU;^`!rXVWx`p6DmO?|xdwVLqz8Irx;G)?4 zq*-i6r6+d2W`^p*@CiIw#})-krC^FeXJqW38D34gF-LWV?!%6@&qDS8kZConapW*e zL2Saz-$$Bhlc&;H8V-u}6$=Xb120aAKOes}7(0LIC_O;s@st0{qSNZxdOC(oTMZ3? zY?VDgEJwi@IvLBD9myC!Ivqhz=Oj-btkJd6o{Wh8wx78-nm(OwXN%3ymW!PxPrq}p zc-!Yxv$zQ~`s z&Wcn_6m<<~$2+Wi1i`}$jQmPczWM$*P{EDEjF0*FSgICh7s5_1m;6kM{duvM3pv82 zu@(&@E$@nQZ=Rm-KD~9$IC1P6^_0R=KlUM1a>GE{o);(AvNhhU0(Y)DJ9lPoPBFF| zPS2Z$tme<2UVQ86(bs)6c9N~7;aT4Dic5)LHu+`N}K!-wmt8iR!FetTC(H zgK1dR$C^jkqJh`jYnr3 z+3GT>IC0`9kqv?GRcX1o4Qxy@y)?d%geix#B7FAl$l#|0lb+IZj+Z;pbB2{$Bs zWry0D{TIkYs{9j&DCeL?+Ht=-QJcabFP^fRAtWiq37V6oGA1=W+?O=d=FYe ztQ}|s`ePFmdXD`Zr={xFW6w)yuEt)ZN8ZHIyE}1`>idl)dwGax%mSJMXJYPd*p zt!cmWwc3Qt-go)!3m}xx0V^RvQ!4hJnj0-Ya2d<7*qAA8v&`T-GndnHEVelE|n#adub|u+IeM@=ZbCG=uc`_lg~sr&)}{zW~Kpc!tGD z`uVdh#z#yxVnvf0nMq?S_BSkesW1F+#gl=$;M53(i^bWJ68R-Fa_!V@J+` zjUA9v;f2dg*;fkP;4+$SN)twT&a3N5x;1(K+;u8quHP+MVyXqYoTY`)VRp&5(l2Zg1GxlMqeEWa8q*qd;h z{)1%G4k(mR5*Be`tc56Rkg(L3-??3C$ZwE0pR8_V44+wL%o4 zUAF~Vq32Jj6{3XRGqLKE$nPY?a#K}~ncSy1o5s`^?Tw=^qB+|3hUPByMFvb)`XYki z`l9Bp^hE}yuJlC)%)ha|2ts_OiiWe*7iB?4per+)0-wYG%v z5)-9eUxaAT7ZDP!FS2kJHd8c9xheETq3=;M5~|fmVRRv!W*Tn1+S=tLW10*SzDkP> zY zCRN-=d`y!n1I~{1v@ELJ(`U~(2NqSQW$x5nomp%JQFmh-x*}5USe8aT`nK3U* z3>FFv85uF2so_qPy6v=#Y`C3b>4H)iIFhxZWnDvthdoyh2YNWr!+{*mGiUU(=o5%XP=stW7zr7TV_IKE$kDvDncT<(x_QX~WibYlXI*+-|p%x3edepyzn7|%ESaOCV01WSgz1;Gra;6Dlyd03|%ONcEF39pw04&k{CK*hFTM?#L!MN)bg2< z7}{)xekkGnff=evp%3uTy`Wd(M0gpIK!GzY&~UjCXl-6j4C@CcKC2&`cp-Tu$1G!* zesJO#{ourG{oup`{ouqq^n(-c(GN~s#t%+x3_om%L7842A3}G)NPlxx4c~D?XGSfmqNA^jy(?o zKqeg`7I}DB~dpU3QqMcQc2I zkIp0bXI^#~&n?WM;+yLL{+X8@X2uleQ1LC{Zu~PZJ51r@%%S3&D*|RtKaux@{)*$a z;fxIcj03lI+3|d3ICH4@=m_ASdD&rZ*VA&;nE-gO!`wNTIaCUL6aUQ14s+LG=1}oX zc?bW@%MMe9@>TK8H5LEN%MP=A}3)exvKm)E)uf>p=^ecoGl)FRBH6!iny||E64r-`qX; z-+})`|Aphb2frKqA^1Ku-Ge`%^Ct}_c3D^a{I+xo{1_ifm*P12OCg_qS1QVRqeb0} zsK3g2w^%R(AuB2{q}vo8igL~SjBP+T;hAz9W1H~oBw;r%ds^AZr%K9nV69sJI>Z z_-9^rmUUQ>~Y2Wc`(4{CxvKm9`a1^iK!Me zxD^-wzP$oI!QDOhgUATsAJWtW`74ksK8h~tBGz@DET4)k!KhXXwv_*ZklQOyzt{prXrOgaYj>+3j=-gcrP zzYgQblE)Rc6$b^k`04kWavT+guOe(Ko(RT1Cd7NzHGc0>`XJ)9;F0iMzX(C{rCu}7 z=c53{vAvmYN>2vRp946yeau*ba~|V2W9MQ1mh@Z%Ec6*g*|Rv@$#TVcvOMCu@v3mT zati0DmyO0DYc-3ZWpn3Y6d&(W*vYX9hab~TcbWV0ru^NX8OIj62L5HKz3qh`=a^mncwblMO>_L4i z8qVd@f}Fh!#4L_YKHy6xD{gp>D)Sqhk!*vav#dC7U6kfoR8?KQOyy=qJe~QaW!~6% zQ5*{%Qaq*b8a1sH>Tez;8mpY&$__{FQa?PQz>SI(CtevVj&TRmsC-6N%^&H;vClbB zt=|)y7D`2Nd}nByg;w}2%30`*7l+!DzZZl56_w(I=)#4iI9PGyNbu9I<}US?)p9Mp zkpuDqGf_o@G8Z+ZplDY1%oIlCsl}!DKr@@Et{$cPZb@OI7(BVeyi)`McSY%f$||29 zqE2Qsi@fBsE4#Qb1-`DLAUvgz?s9yxT^2p4fwAcku4K9*hsfj!jzIx-O_B{lBFUly z?PhE`PQjOy>6N9`m~d3rRFuNESy|QMc}AJQDvp|P|1x+Iqc+1+QL}gn4vo~=gtp`q zRNEj-s(T+Lc}ZG_8+y*9@8Nz@f znPZ*9?p<2ut@c-dNq(wFm$2yQRz7VNpowfe2Ik!tks7ACHA4ThE09(i7Dl_F+dY!H!o zsVQATBW>ekUEW$-^u!dyxbz6xP!+&HTN%eB%LZYPWpYv?O2tssPa_m5EX6F0hzyW9 zrA7a#+jg0s#xu`=tqHbe;1cTYD34>gk2PMt-g5O43ip)OVnsZss@#k9ZGF@wlgRr<@2 zL@D_mHOd!84vUt8IQ>j$wWM?rn80OW(Ng`5C^dpWjLZ|0CFHa+0w*(@l&QRj&ab*g z#n{bgXHF3~y4#R3asI2Lr|6D2%WFy(&-H`zWu=u@_+hbv2@VFuUUji3Ctt;K@3mqC z1A7F^wB!<_eXvc!Xr_cwE#~y<^!(!7qU@aF=_R?5Vs8wUeJHG(vy1Yl=jV|{4Xj?c z41f~Q;e+kNGBv-1BO_A?%#(a7MhMwBHGElBty%_Ox22F9dR|iDTSzWxy?o$~Nc5Y=M%CyDD)HA-BUQibR$ z32QQFkms zBll*0;?A{W=14U}$TpNFiVZw8Ux=KyTS>NaDN~7F1$Na_(T(vA!{BiT-!EXbJw z!Sqwlb_weBt-KxsdY;#CdtFX-8e>GSA2k|tL#_kho~x6b=HVXlFCb$MNxD{jFJ(Z3B##{fHW@gdha=_f-`fr7#%Mp{G15E zIYFh@w^F^vofTzfE=yG*p=X8dTnf9WsiMYF6X)}n*7zY&QJ3PFbp-bb-0ccXo}gFm zgF7AfK-_$0HBlXX-FTemZE2_D56z^ZLP|s|sn4->CNzeL4;s;1VpncCYG-i71 zyo>a?(VWVADyx=M;!p+*t+|fhhZYQq_4$}?E#iPQ>p>S7aVd^sO>xZ0zi{taN~4XQ$u>|jhJ7?(ibB*?IiCVvu*Xs=bHHMwGp;WxV2oA&GhkW1&42aKSQjyVl8Br&L zVDjU;2V*Nb6a?)Djh^O<)g68?h~c{)(`J2)m)b_dyg;lp=MT)3gVi}{kSO21-f})8kriH$H)Fo9kD_&5g z(zfVZ(E=M|IsO%sOmVz}c_K?BgtxM+s+KA~ifMLk?hF-8KvhjwP&q|8rv{o?FuNF| z=;G|ypeS>_sp0(B$7e{@7@1+bNbA*bE;+x#yNI^02x)>Q9wTvc_Ugw<>OE>@?c#Z; zd}6f6Of_PRkCcEhIP_IOc^4;Y^f!~SVja%Lw&L7EacqdhwmFr24-G_H6oBy9854lJ z35fbm(gWR@4@(aiFzF6I=Nm&@qm%_Tek}Ci?1x%EcI#A7H*9>iBPoWuV}>F@= zhZZWSW*QZt8=$tDMzcZSjCnIQ_&9T(-*Ub)KQMiW8`Lz?9BBdM;Xlcdyzw9ZK# zkF<3O4a|I+1RJY41n%c6=G>Qr5zOQ3^GUOnQgcKSH2+^rYXPXKRSU6QXVd}oo1c}qShS^|A_lKcmF>ix2GHf)PWME$y8fXk6 zYiQ+Mm=eedidrtzD14TVfdh{NLk#Nn`LhvI^;ImWEL|kWpH29Q4_>Z|sjl)>@a5W~ zYj4*13KA{8!hx`Cq>oWc*+LpE@r5W|0-~d#-z<-Sorfm4#_O-GsRTJd zU4h=UhTFJssVfz0LNY4Ba5pW@$)##96=5e5|9gqIreZ$qV!FmsL{;DvFtdpv@e?xp ze!eZ#)U!~w%PY9dVJP`i+&{zpbKGPHi2XzCSGZ{(!>@7E{;1#JrhOd0#k~dh3%Ix9 z{vGZYaleFn8}65JZ^yj@_ba$x#r+!Y*Kxmr`}ert#Qg`{J8|#AP1^+C#{CZNKjMBD z_n&a@#{Fm9dvN~+_j|biiu--sAK>1L`)|1S;rZs~N-m(wt)${hdtw^h&H;xt5}$-alQLB(oLL4I*j!OWStMRFubqcXMDhXX*#kP*`x8D@6TO)sah!qCC&LFKUkP0x_|n$pSz zuokpN0!BepRhQ8aQphx)i_v@xOSRnW0SZ=sEj%$S!_ZL&fu_ZnMaTF`QTdoL#3g;#YN4H=9VeNJu?m}87qjkwmFmvc&&EE*RY)u?*mG9UMSfaUYD znu=Eonf#HS&e`CN)J_0;$9r> z63Ds~C(Y82rB&j~^N4666*LfOyygfCOX)k(lR$*L*$~(PAPJJKEgJQ>N+RD0=opdYrdQ>b=~ zdal=e`b|nsc`|_CUU!|hp5FUyP5lo24h!_A@g}{X&{fqDQQu@##5IsNnp%iA&_XC72)x!h$g&u*^{x*L9+Kt=5 z(~aL}pm!kL&mGp&=ti7)eEjjs?&J6gY@VMUsyHb9XMc(Gv3~RvbS^gB()*7W*}wf{ zvv~h;u@(MfnZHr_Ao-vRt!B3_%y+wYjT${>?6~W$A3q`E^LsKW8D(Ch&1;N#jWw@v z=5?KUU2k6F&1-^rjRbG5#%<*yduon5*OPIJ99BB74DB}biE*#27ZX0}``|^}C;roQ zMa2a})~0-zJ~wfa^Z7Y9e7Szm(J{YGJmxIQy|Uj2H(WH~n-{*M9IPHc_>(JN?epX~ zx3cOh=cNz3SWCV3OCyyFrvEkh)6}F3F1q$3#}iBbJZ|1~Bd)vh{Bp;X(#g(H-_9Yk zuG+-j$=^5b7mlx7a!;Rjc6XLU(SqHV{qd?R$6Wd4aoT?u-;n9fymHLj{T|V-cHhSC zydiwm|MYI^{9Wb2u4@Y*U%f+3{m+LW0x!$H%xzIU!HrX%Xl6*bB2xu&PJB^GlJO#rH`A> zY5A)8+id$ONl>*=dUi{RM+qL&^{^t#U z8+>c8IXA4*{^GgwqBZ9&zvTR3o!ai|Zz~J?-7$PnuLrevZtHjPy5ToYcrNKFZP&Cu z*M4upA1}FeM5Fd5Gi&*9y39B8VOpB~YA`Q7d=0-GF)jgcFTdcLAnKy;s>Hu9+M}X; z`7TBIQlX+;gxYOgdarE;z<&?F6eYcx?nK4>)bpTo0QoyU>j3LywP#=In?@11>ds0JuSU8IBu;DWHl6!)Mf{!266)KhJrb`as!?~USQgD zxt@nTIlCP>ISo@iKcyGKzlgH{jhu5>CE2xs$^tjBXCuKH0C9~y97f*Ub_XZHc6)puS$Kw|JE+mEY4cVb92fJu4G-*dpwR zMb=S^C@|?fl-$FP+~v<$h4=VX|2$FmE5`ZsViZ-p8aMFT)1c%U67>U_q94f=U7fqc zwITO^WV-fSbRDqh5`Z7usO$YOWW+OdL+cN@n^yKR5YP0=nC@4SaRO*|zI}}7od)dOYsbHw>ZIbjxZCOLX zW8U=Fa~d4(KyD!KUP|^2cWw@Z47DLIZ-e`r<<(P$l+QTKA)>$M8s8@u{P0h||0J*O z9Z`8Rk&e~gG!)#E^Ylm5R=SN;UWu6shv2E$zDudh+y2!}d3BW{m27(|EUSU@AiJUe z)QpQ7aQsr9ZE$GAk~+7T2iEg9QSu(6@wp`*Wls{VOmX#@Ys z8pOXeWQoAZ0)TkFr0--Alc4JpmhV*b7=xo?EG^+dOVZcvtKS0Xy;+kBe*YBwGa%Y` zjA%omqy1h)OTD40r2Ykw%H5(xnr_QNz|-K!X()ISmz(%FVSJ9e`VCjV8EACSL3lh9 zBi<)Pyi?ajWvZ6X*gm=7fp2*ycc6#N*=U|Az>um4jgQzmE4-8QlARkk86ZF&P9v-CR4Wp0i=qLy|4 z$5{vJlMz}yMdQD9|HTltzurY*lQsTZw}Ra(fC3`?elyZsjfrTtu=~we6iD&wa@e|k zW@rIz7!~1Hvvmi|a6Jubhi*kAs~wt8?a+KQDl6pR`O)nV&5$sTdPoA>RPeO))^PM8 zjI9=;#d*--Q1}bbZj&K+dqfpVuXY9RU-rAFa`tl3-;|@eC+E29JP!Qw@9w&ZqO;i! z99CO1w0;hAD9Z;smS60s<_v$$_B}w2-SxeOyX#J}6)6Gt@SK$!+)Fv&n>Ijzp-*UV zdB)bm8>oT^udx;91>BQ8xigIzZ`oqpTfY^=S7S^7?&plS{peQY7!{pD_hv1*;OVJ3 z_ky7b-<)4PrK)`Xj8DyPlMDXdNsWMO#e7c5yEeSN=mzJa`lK6rEn3oVQ^6iYHcD_s|4jvbw_ER0&oW91ZdfP^B|KGf&@mmC;fd{VD3kdY9$23|w0<{em|X9|2maLv+9?vU zr54~2)svAV3-B*X(gg*hO$t`@i==5dhpbzqp`jjH|68Y?<#(g<2EN^<97-+EYM47` zPJm~VztP0wkAYVxzvla#z#H_wu3Ye}YGJB$^M=ybr5FDbpl0%dL8UiA}o|7-;Q1v{iOY1M!3na0= zOfRkrJ7?>~B^A<=L6dpx`ZnYkeK3z+-+>pStf(VY%;YFHMsoc5oF(U4F>q7C`^dRb z?e0fYcK@wtB`;b_voDxmSf41^7{pP{K^wtliq?_FW49$3t)t$6OqqMMcl}HtUy?Lh z11}qH6?JvW+#jDO!V?2oE6!gxxA$HAwYtG!C7_QIkp!E$&WKQLtGPUVU4rkuwR*+X zsHrOkA4)`^DjV#o^?43$>_QTm(fx>E(@)YO18Grlcttj?eXS^d7*U}++aV0LJ zr=5tLxSXEeqo)=0bP!K^>Xdg*m!9_hY;P6@S5$u0_@zRnUtXQ9L@r!yRNU@KLP`M5 z$sI`!r7QFoc3Mb@YMv;G*E{rLh}Uj<;o{Xjq^rM-3={d2IRi|jdu62iO{7$cw{%UV zg^>7YB0MZ3JZ3|fWEX}=o?CofSF=BB#ppz{2vQv~W$(Rc&W=}30e51{8wb6N7Hf~;h!j&09Akcc4_1?x5KD*m?Q zV$n@d^Vj3$ z8TrgYvolrX^G6Bx4EDD&IA&#*E~MSg+gyS4ri7TxI|c6UdFJ3nM}wK0vRCuM~E0TC8+Tq zqKwU(nItnB>Whd#f;9?Jvz0beWgW_MdC|OP&}y41wYHnu*RI`Ss|~Ggmu|BayIP5j zns%`@Y-}UN7F!WiR4gO;eb2e?&D=Nh_^12(e14zLZ~aK#d-tAu&bjBFd+xdSo_oh? z*>c}OasWsbeDBg5wejRrsEw(gp0Eg1kJs;^7GC0a6~YPAIWcj3%|#po!`CXdeh=V3 zAriE^AJzAZ>i-BswOsuY-+@c4N}ohkJ))}D7r0i{73Yan>l-fB;uaO%)@!#iw>)=W z<4C5JgIKc-ifTUVb5`?&rGK1Gtaf71B2zU3m0e5DsoWC`>2f;X`h zL#X0ua0+0mP;(OF`L~DilsmBvo$1NZhGSKq&C7T4IU2RPo3M%8P=X^ba}@HrN=h;c z^VCW(M6H=s!4s3u_wu;03_KJbS1jYN&r?91^4~o<;`j*W&aYLQ>LZ{idvr#tHA^BF z?i^G7wUp0(-d-ue0W6P-?DKSq@x%7SGUKw*EF9$nsFSZ_9XUiaz9g~(wdF0@E?mZg zG}Q$=vh12_So(5|vn!+wQ@vIC;&JxbSv=0N0(=vh&M_6v{B{@~(HkWt=nx&#;D=?4 zcnTIIz2GUBWP86dKhF#$j0U}$YATI&$!UimhM$^>*Y6#Ls(W2OcM?>ORo6%IsM36{$r^h) zSGE0$48;+Ue}Xp>SGE0-;P4ny{;$I)W@pp&+@3E zsXL<1rl!`fvh=g&O(n_Tw;kEtnzq&c|alRp$7e?lO(Hn-REc!0J%{4EbwGQxJsFM+DRL}SMvF2*F*#4 z8sB;AB6daD_!37aXkK5)NILQkJ}~jo5_x9(4hxI!n5|HzqpPCXBUhSO^bE1x&Tk@zsW}t{dg89td@u z-_u`4tw}FI>xDKEX#sx-IYVV@6QQ=Xq$|Ii>-C9^1hRLBmun^Ke zJz?}u2P1c4jrpBf`J3QD(!4pT=e)efS9IyvV3*J?-*^u4K0?EGSK;G z4_P}cRJo&}UFU|@545wv?QDzNv&{b4%q{H!GsoX@qPBr8j(^J>uXjUTn1i~?M%|(x zB2m;H6Y)+)e7hTBUk>7Yo?rJmr0mLbha2jG9MoB!YnbQmVSa&8|XSuAqmwINB!{?!dHu`D*Z;VsI+3t0%fswb_(gdXU91&+9+aXF}eOS-g- z@!yk&pCuXETLu9G2H8?xEtj ztR*znMba19KbShCxEi-bv@0IOI<@Y3w}7?|czisl?|Daj(dtPx-f3s<`VREy4$`&) z7{v})jjkrLmu7z%Oc7+-8>cN2?OaM8oTDg%W&N|mTf@rW#Qt)68cP2HH1&d^MTwpQ z;OPcFYEtayy&zpyHD z*C%~9CdZ>TWUZY8N8VV>O4(<57(I4c5pp|f)v6jC}tnbDie_H}ZK8QbSS;~Rc4Sh(c zOt6Gk=1JJ9YnQ2BBdJ`?*pMx(mZg z)?@c{Uxn94+k=Az&?p!UY}-OZ(3Wk9oMeoUNM4)hK*Lw3yd%mEk4G5_BzBDTv<{Ud z27T+4o;EJXuWK2&yOqLpLIq6?FIMQD)AMLS-hhKpa)@DoqjYoxP`e;QEtuF*3TkD7 zT7OWKWqSQ(qCl47uU#oZO4GhwQ1NkRXiJekAxKO#wnvA#i!q)?X%Z&MN}u| zu!sp@Ia5pkDIaZlk}kKAH>H&u-XR!6?~tGV1fVcY_}7YmZHaDpMCEH6{+K~YEs70p zU{v(#0mPIUVf_MrM)XDejOrnN*6IpB>-1WF*6UDEz{m6@^z6_V(8E+sJZzuP3O?;% z!2Np=PN1)+_hB_XI_l+jNKH3H$sKaZ{hI+z&W~Dh7B+L~%i7>&r?0Wf-r=j{GLQ7G-<;$KQ;{S5geWS zJh}vb*xdpFTnOdXsYbO1GhUZ4Y7yUWM3zIs^@sqAzDEh+%H;{YaN*PfBbv#06B)*6 zI22u;EU{qU)y6M;8{Qii+>3m?E_~KUFM;}nzo3_e&+sSGH`y}MUY;i|AMbj?+hL0#^8V<;Z*xjG6kZPOZ+or!zfI&vnTVD0-ok$*NCEd;25#YN-b_6% z&8HU-^|-0-G%q9TTCRLjX*tacT3y~AU%mLgsSe+_d3#!(^RV=P4?KuT2JOqU~1$ z_0K&$Ewc+4^-s_XH<8L#@*`Pnb%Zc!H1>NSBD+hP>T1W=Ei67NY;xDI-LDO$UTr!? z3o8GRRAR&Xo{n%RLhW{mr>CSoR97E;d%e(Vq@5w9QI8M+@2%%vV1IPS7FKv*XcELS z`u{q9*6X*^6QgFacVV4pJu(_OBdjmuXGCAg&!~P4KWp{t_*tj7^0QuV<7Z4?OV9NN zmq*|?6%QL+I%05PbYUusE=)!13@%x3aKYI6gC9+DkAu`nioJKOU(;&Kb?5vG)1_{?>(b`RF8~qGT!xFivR=q&LIk-*( zuB(t?s}5EHfYaWW6r&M_4$qRq1$x1D41fsET!4ra%pwbfp{J13t0fHDSs_?{WD=zn z$o!0)88lU0`mWG@u>6FrG>dJs1R$^>)5i9QoC$2dkiN6nin7@LTLKW+kZEJvD`x`R zhw^tj@ijM#Y0`^XISEY2v@xA2X9815{yyDQuav(zaBAOnI8z9mdaa_n8=|sXPLjDb zWZ5~?ra_F6m~U*Bu$k(Uj<1&;U&VtG(-bMO0~k`uBR+qk3V-zdrWylC3hyIQm~Bn_ z7brJa)80f+xc@chAc)SpKZrDZ>Rw3?DANGMoxG zCb}uJ$4_t}M5f@Y;pLp775ewj2Hw&~JpM!qe@1==V8)^xE^S1p^CMn=qNWsoiHBzi zw)ohv#RTiulBvRD!xaiRwey%@z;srAH7;+2@wi-)Y|8sc3FL55Cp}CxDP_T1!j}ml zaQz_T6etT>AK+PVj!3lGwlop&P2$L>-|)vy(GV%ngQcmgY5yEGQ*llE6?i76APQV! z!SE?s;fB9_DA^qL@J%)Fo2+)2>N(O^%B!D7{t}*5^b`@`UOJN$2bkGv8zfULDEHI6 zwVY<47g)<+Up)^kHB~P~R>EutaJsk7pK5Q_2{-`{+*BLf(O|jYVDCO_3CRV`kpd?g zH3v-*anr5{cD^U!VWL+Z1)8ZQbD>w{K#wEbEge=}iW$ zwIJ28V3a#av%rtCI@7y<) z@9osjREXX4NcnbnZH1Pl02Z;Vg)|IOJxx8>7Zlb_V+ zHPp$F2PQPBp|bvoiBy~98A!JNoiRz}aSaB~A@_-1xQ$r$vbvDZOWb1#EYyCrqH`GX z>IbdyxHWIXUUC% z9F<(;UUGpJEVY@bdVia3Rm|S#Oq!|AL}GRc z#87=u60BtG*6C$LNeu-?!h?Zuc-Y$(NiE$S`AH<)72bns40o-6xe#3oSWav&9#W0f%)-E7>aI&ed8CsQFjXIW2&VMh95 zEB$;|bBG`zbWN{iWpfl&5V=(_)UaRMS| zb(h)@4v1$->Q*rXrhBjVq?D#aRa0_?siq~W@N^#=3$3}R#@m#{0gKcAJKLJb`|4PV zZAbc2XZjb$(r{H&wjevTI>%#L ztq0&QtxlHNeiFnPndI|fy#@m`=U%ZoIjK`5f=xKAi@ZMVeoWXTa;ExEguO|*AE!tZ znMS9+7!F_}r5E9GePS^`OVJEovT*|*I_y9g7wh8yKpAA!dLNfEO05ipN~U|inaMEK zJB~sVqW&fg>J!XDDsmHwXjhx+7mp6E`T*!$H3oS5bFPCg0=}3h>c`;(@ z)q8;m^x0vm&=+u8pG(^7E@@L=$}byoNxM!;!x=SvrE~@DM@^8@!`7QU)v~#G%u(v) z6D;+Cqtv|Lofxes;A|dhxu>K;d_e>=fazjKuO@B~ZE`Mu0x9llOPhBrD&ef0!O z{lrmf_ykLR=qOe7@`-V{1aMi&f8jW#v`?GrHytIOIbMk>Q~k44LYtNLDoVqY&?H4Z zVzU(nQl1aOlzNtQa(BnbL!BFs$>K3hJZ6cp@U@E>cPXV3c9FV#u}(BNm8ghRWwB1QIF+c0)cK2bqA5O3S*#Of1|ixa)nN`Y zW4@_UtuW0POY?(^XfAH$4HDqkn^$5Z~Re@8yf(UR0q9_IgG@983c0_8tN`3^zKCjqr{ zQ%N{#hfCo}rjjG<+sqPQ3Y)n^D#?Zqcim{E_a_rITC(K@$5AqBmnEco{?9Z)<>84}0!}1_92ryx`-5?uhu!i?L~|S+4~=h5%%uCAk}hacYDycElllrU z$9MJOXZrYT%=WurceKIlL-CH&EQW%%KdVwQuW)Qw)P#KzFA_Yn`nFJsIfRypyYjDgFg28cbEO6k1w@SZ3FKtNq5?(~zh=Z=( zZK4TIOZ|h8dTPicn!&`xG2MIckd0F=)G7+iP2G@NXd)GQ5{0zYa)BG|0ypIrm_!A> zOa;!pk6d9)Q9{KE>V}QbuHCKp@+r-+QEWnX?Z(;vkXLE09(AY#-$i2CB5uSg_HlGR z27PBWbrh)R{+)3Btd$FCErR_)+AJ=_erDHh1QK=azJ-}xbs*2y+2s&lgg9aBQr2U? zeMq*=&C*vYsQbFvWAfmk2apBtiQh~3J*XFp`~c+#MLxfm@w*ppY|9Z3Hm5gW!QRHU zno%SU*Ich)!ba#4nLPgFFkRq7{|OQE@YR|y(lpf zUljN1FOVP!fmO5it0fU1_A;b`{f4+CcuaTmX@34BXgVkYn?w@g5aO=3p0@aLX9HC; zMdA=|g#sc^9OH7u@w<}ah-0i%9E9K~;vh&japcileG)WNx ztfLt)RA#cC$jS0#v%Zm&B_VvM_^1dwbFw6aZ8=#5Op7C^&dQ-hSwV_VJo3q+ljvoKOGGcESv?G0yh`dWqSp)T`^V;CKWeYHd!Sst_oI~;wutJS%V=VN!u)5Z=fYIk8-o-H4(yhEN2 zMkRTkymrFQw_oQNKvd7D2SdpFsrG~;T?{OTTTG8r?`n{eX;52%y6f`+Wd%*Ox8#o% zbw$T%H+y{IcI$N+Z3<2b!@DjRo*I+KL$zyP4CpU7hM2>?9I^N^LV=81{_aBS>U>&G zJ(cS#4d`?10liE{V0l1?gVNyqGBvLvn(=VUft;4hHpt2MLZLT2CR<{V^+ryY^v0>S zPQ4LhouxMpz$}|Yw1r1ShhH9aF8 zEcAt&kM+gD8XCf{1^8~OLfo3D72Cbtb_g+J(f3g@QN_&ED^PRtQB4!i7A#j*r`f`> z+}fkLfjp(K96+6>6vuD|Pmb?lzy2_0PME~5K|GB<_+78BYK=Z2lk9Cw)OcGlFI8^^ zSq$gAbgIoms1Lr)dV9$pfhY8x4GP74kCx9JF`;$#s{K*C|93o^^7wP5%Hy|YCPeRG zpEQ~IN?DTuTGG{KrV26JJ2N!~e@{&n;qRt+9VQTUG=ZpV_@FbU ztw+Ksw%R=6HR=?RtEjx-8#6JY6ZAN5 zt6YAkUP~|7f8&&f4roy!g%d+rnyh$G?6wimh=SD!h9cLt8;fNy8uK>`OqKkI{)9L3 zO!Xp(Fq@SszKr!IDcMxNjIUuY7sUR6-JMca_pvT%&8%MXLlX_ zO31GyOHwU^n5qxbWZW4>i=_&rLX`0gsZO1DE^*95=bcRLEK~gt2*7N%D~<*002ZwO z8Y)s^uWc8hO|MjR52Hn`R!V!DDH3>z-D33O8o`@wG49Z`m1Hk^lRQ~gSXwhU*p33E zw=c6*10gN{lT{j1oiBZH5PcL0&XX9O`d(Bdk@D$p(6du7plwtudH_u}p&`}2PVCC3 zibXsI0<+2igH=7OS>dn%w5_$_5O&Iz71We6I$D0X1za)B`lHe;5Hvxhq`_)6G^#Km zLD7mSg!Q0RU5Nd~S2Ny0EQcSaefd#o`!lqQ8&WMVTdE-8C=hk(@YRfSkYT)692M^v zTaK(Ts9K-_%FKJy5^_smr}=8e3gmvRoe?iRD&j&LF>4?Y7ZGCK{LV-GoVO-mBSwJ7 za$vO$3kP^CKuCwFc1g*&C7np`)(=1p)qUeT{v1vn1bSzXkqYYn*@73mUJ7IRG0)TgWiK1W>|;%u2&dV2jJ|*y1_}@!QM3*>&YA7F%*cmn2QeewJf+Spw^SCyo=C z>WHIRCk$uXUf2ve8L38^Y@>kyybgru1QR&&qBZahtY@H1_EOnSy#xR_(l7|bt#G-9 zf~bTetp;h-=|5bF#a^I7{8cE>+VBKpoA1EpT%x7OAa)A-rzDO#-I;8;jV&pAHIV|r zXjn#*b>K?h%-eW+vrUP;M z{S|zYKUa{D3KZx?qC7uR?G5@kE?j`PmZwu^ZAj@8p%hJ- zpl%vN`GgVfUzX@1+T8Iq_LXe6LHx4xCB?(EC92ve9;O@F6xx>MvZnp>Z4?i4Wg7+d zY?g`}6!@~C#p|nSzom_0R+qNH73)STmBOHlz}7BYGpLu7r_iaNEglQ#k<#g*pGEJu zL~9;BMW3cac@MYRT^c5Ht^x(bvBFNh5A~w+bf)@WV8eN%6^$G!U6-5c*bXqgV311L z*EGZU-AHOnsR9jw)(B8StMkflx_41=g&sqd#BI0@TbbIgj!Vt!N{v#FN$t6@F4Ueo zE;XMkHA+1uwQJm{y^u!@?Na`-Pkx6m?kcWsA zx019G|H3tQ5Y`Uj7sbno=YO?}lYfd)3x~}K(e6A%AbYXIMb~LU7=I^MgmzHDDa$J_!Uph;6o8_dZ*g0=O~{xLpEvHsNg~$BdF(0Akn+W^`c-s~w6N zq__*jIgF8H^yj>)*$lVG^O*RnS0+W;W$WWxQro6<2NMs7s^u^dl9~AH(V1w=W8wy4 z!ml+rrH~*7{Mv<1;vxjKMAW;#P@76onWp+C=-G}fMJ>w)A!a?HS24^c@x2~+i+?Fk=*#BSHPlj{HfWpl{al4@}vDHbVDsLvhq`!re$#f zsMG=yPs=b%z8jBPsau7eC%>~obbye*t4X0F&hS1IwbZPp7Jm^(Hx+FjmhfnfO0fv~ zf1@lwJCiiNCQC%QJB!-m5{19WzG%?oLYO$Q5bLXOPJ3UK) z``IICGoG!UBchV3M(u<-?4j8`<(5pkj7pN1PS)R6dv0`&rCgKJ&xDeS@u{mopHDqe zQ%s!uPxt;JLp=m}^Wd1u)`ZOOVL&jRS@^ho^Z`+RK0lIuBvKcjyQ|nDGZlq6Jc)Ro z>MZ)8cM0e{>Yx|OP#}8uX1-kM z!7md|v%~kkRrj&$e2aM2v|pwrCz0p zr3(FDZ=`s}E0LaJ=S)aWT%=N=pLY}G-Yk`})9!Lf`=LwPZ(P#eb4fe(kJ<6WrV*3U za9I+*#2~$gTi3qqs{`N?04rZ=+*ola zsBFCIh}IpdRyM8~)i#A_;M+Lkhzf{*21Ka6QQ3Iy=!W*u4QoejEt2BpP4#h!dz=f! zmly|R?ZK?i0re6?(IiWTlI(-fd23>p@{!ju?7-lEHQNTA`jx_6CdG$0?(DOn%=Q`;lb~)t+e0$KK&Z|z0jv$NAC!*#MS|r<}k~wj)Tt1 zZu%_*{WLz!p?BYbxyLnn+ui&axyxGjU;kN5G6~0ht~mTD3kP+SW8nbmy+F6?gA#PY8Cjgw2XY9q36r!6(2-bVFEancBC$)!vM-Y|l~4(TKJSbS3m__6o~f7)W3 zGdu}5MqD;goVE-@U_? zX2hEDLMy^ils=X(}wWAdmnn!6xaJ`F+lN;A0 zs8z=n_mv20R>JyuM{8-I*YeS8pJsuO!Zocjv%+cijdIg}H^1qD=sY%}S?T7P5>0m5 zvVIFZrY`g!tn{D7q1bkpxgHyr6q?{RHQgfQE8ZA^})KEu(} zu9&oy>XNlVm3Y1g!yiN_IeI z#TtS?Aj&OuFXuW4(+CcnX$+(pELjDapH&bfN`t;j5o;`-l$#|D$-jn8z}Zp#zpUra zt>@#`^Q4Dt(0ci6Sh=N(*hJlzyVu?1I^LFX&hV zoQieSp0KzT`jaTO)5{5&jGY1TZBKx^37Y1}dJ=EsvV4_Nz=~yyYO=NniB9R~+l1UI zkV!6Rx(ZJErC{cMQK1?iKby}UwQ8u{EaY~rOYMMh(^uIl)*M3*j?amc_Ns2Dmalf9 zEO@%nB}cZ!wk>Cge|DYMchsR3H5u5otVVDR zebGiAwO4X(O1}@!5#`~LfHExn>~M+(Rh-SAZ+^nI{&`L4=yTElx_A_zmQ+u;0}6?K z#zkV}0o0IBayHz6R!n*n%TpqsYp-y!!ac3_z`@>E)?Im?8Dl?dRbN#k2`+tg*JT7MH{Rp z{CSdm7^)blC(VB31_c`@V1ka%Mu%twZS^q~0n`qPIp+E146xHXrx>2%0SqL)Yv|8U zXTdKkW}mUQfU^GK)XY%To!88(zE+_}*P}kxJc$}{y`vF(-<$&^50(P!Jhrg7&;bcL z(RzC#{*2Ay$Jaa40cde%5a4nG>?gp1Nz55uh_|>jqR6?;B}epUo)=-<5qF+Ww&rt6s^xn$ z7Xf2yn^wJHX+m&=v+d=1rPY2Fdio1T@65g5KE-9e#qMnH!;`t@d;mEfK=o!)rSA>m z&&Z5y&CjB&`{!n4cc=b2G9p?twtVOf{=Kld_BV`GW`=q;&s%z5F9MFA6Xq@n^M6z7 z#aYbv3CuPg+@C;`>5;IQIr9|Z-h7>EE*!1U%QdnZS;t*cunK(wGM!3x*KFHP2vK}P zC;^M&QsSzVc)ha}e_kRDvlM(DWo??1Cko7EPFNIw7y}$z39}|)-a)BnXE93%dG;W* zBUeE7tl>_mjcgz3lyMLPRqqb!N0xHmKqM|A!ataRKOauZ*6VWS>6A>pgbHtPQDJ$6 z&zPv*OJ+e|lUK@CO30UbPLq&rDZRgnpOx%=oXDR+f4(5`@GHt%kmW)9C#em!o1E>} zKQoK}gNdj}1kN6>9-->V^GznUjxg2}ou7@zpAv~!6J7_f}W(` zUj8ca>mT1KNZ@^TF(8V?CjGYvqGl>5uxYx765gJbsZ6$AzB>asOQ>vvu%*`%==p^G zUrVyse}Tnl8+#2wygDU|{cK9uEMX7gN*r{1I@oxyH$lRu3g03@ASc~>1fdnli?c+U zBqF=$2cd75i1bs!Iaw6hV)Llj`J(3kLQwx$fc_!=nTlEVH| z_F^jeBP!{YN?t)(Ph?AqroYpr=|xVLOHNke)wmlq-|uo7n+4R|=kp>$?;z%Pz27CC zrqG|ZGGtlC=HDo5R+cea^GM^iW;AX;_3$b{o+TCCKt-1j!Y64+{@sj>T!b>N9>W~) zj`kGinP6LfKqV)8Wy*AR)biST!5P~T3%C^lIbVaoawbz(~d)s*l6VbgBL+h&5j zSinx9tiKS3X8QA(RO;{eImzAuO0A(k%eg!?(k23$BfO=T28P%Rr6=HeTi7 zomMOQ)e4ohq7Ob#!*FIpLhKK_?C{VgiB@Y{*VxB^aFYT($dqBYQxH)XVJ!sAk- zy%yJI8Rz3{i!o2p&N8qlt7vB!rHXc{(VkSSyJg-Bja6cAZAT0)ZXtcWqI(dEXw%H> zO^V&fH_Gu5ZK^H^ggEefT3uNgHl&?nfDpcL^d4;6Yh7vCopx6`KV z$)dE=mgc453?nVw*>vyR4^1qr zI(;v?A0=Or{s}m8W%;m6+6yje`(4r|?9In_iA&mzE@^kTq-}9Y`;AN5doF3EuV;DD zG*hjWzi&3xHu-xM45s|uV5&cmzZaY8Z{=^?F?C4(w(pqwgd+g|keeOo&~GObrH$!l&}z$o3&jQr%Ea9Ib^^?L}IPioTAtAd^FWDur~vUo{Wn_zwMLhWLP zCuEb&3Nj>LJj^yf-GwQWwM%;#$H57!bvA;o#G&k}TWSCNeY^=II4xt&BovXuQi3E2b``G;V|Rz|X!0J`ip^69=d=TXYH|4w6(i_NwCQj4Ec$V=Fk@e=xJ@Z}wc z^R!u%wI!4FqfFK`;T+Uu0>hkxk+kQ)yYEd29Pdqz?G#(_H3dyLtlbonWKsaFgFh(bZ$B<9mm=(p6Z(my}Aenjtf8SFw+;UjvqV$$Q~O zT8gjN=?gb-;u3Yzei(WRk`T+BxDk!g$^~?V_<|lGN^vSF$p)gWm1tu$pSDG=?n$(v zS#1d!{?ozUO%!G85N9(@0MfnFD4ffY5w^s_+bWBBxbx}L#0#Q7rB;|`zngpk+0*Yk zR6FbanCiuUrGbLXg0Ss&yig;jQ&)7R-^%GEBWLOG(~mRo$$piHr#XRsH;;O+v)|Ps&cNzl^Ud~^ zaY~VHp-lIO{KEZ7kDyTFs$+`MX>f5K073qHm zXeOue=R9g|Yc}>cr4|x$YjXJAcjP|7=f44QGZmREW|kfAu+{y`muUrENI|U3oOT3| z_j*knR+MYKm4*osp~Pj_rbXX(B-(?2sX2{!J%vcn?G)shaI`$0xPXnnS6k0D))Utn zId@A8!4v;q9q4$#FG0S&3G?)SN;@w!KY6oC9!Xer0!|(YomC)uI>y++wiBzL5=9T>5IwiiYC91UKt2O1f>f>vIx6%nM z<<{gRa@F|m27zGGJ8Yh^DbhDt?u0xLW$J@4A5B#XaoWr^KA;Z9h4g4n5LkHYdU?j*_o(`xvIt0lj1b=EI*+mT10OocuJTZSKbN51zn z@1h!(0nqoe0jLJTql|g~DMqZ-tW)?YG)xKzI&aMTnmZgM`vTKTq#Ks{j5w(|XJHJn+Zn9WVa9VK&W{Zrf(?4D2gp3miVvN};234j?1nb}T0@{8|u`b!+Yi z)jQqAhBViW+7Kj+j3);B8@FzL$!f=trz5V-9xRAM?i3QOgm@m=cooTTMRz=;KaAA) zsz1uRV0{#Hm< zbg-fzV8Ys!QCn%mC(jV~JY(BV#A5e8NF(8Crt1G&b}0?@4!n&&+FNXi-bEB%NF9IO zwa@8ogabdAG)ICaC2ob#Ddv{7kz&R;7~IrNq>-E_9l`I*W$81R&B0XSV6Le~B%C1L z^%nkoaKO!8aQ#p*cE7Nwqt_OCJi^-@dRJ;E%hqROA+6oDlvsLrh-5*(JP&?(&W`7a zhC-&qvdy;%MMlz@-}~~|2N+Vm>!EGeNj1xanl^m%xS^(>7E2#uyhc2V70WoT^Lls= zU-)MVexRrz`z!%(13x;_qAW*0pdXF83v{zf|HIx^m_d7=;RJ$B*Wh=%ek`#r-DF_@ zvd5o^0tUY9)E_q)@Naiqxte~4_=bCF-Sf zAr@0DenYNv=DwwQRoBxY7=oyBg*X9uZYHJhiLuCzP#Z&8;(bSFrp_(K@sPM+qWmc6feQRN+@-Jr1Z-a zGcP8$BsCg?2upm`KUe4rnXzn`;L!>4bcLhPfb#b^XHJXfl~mk8^UK~Z>0d*2s3m#K zO6d>alP9{EfPJ0bc^tyQt_LkR9)?mZl99M}#mQGbPRHJJDqrZ;q^drq(SERzS=_kx ze$h0RVBtuR?NFMpIdWf&We^f}qi^kHty zolb8>@}D~6S{YqODCQ0wn<4eY{qdTj_^Q8E-Kwvt(#dY?)3CuNb(Bs{g@IU$M5{?@ zqp@o@&I5@y<=sa9HjzRtOQtWy%_@wb_>sblrz7Und^V1*(BegwYez-HuTEqp?zCt^ zeGHQZ5&4DIVQZem3IKJL@Rn5PD4djO^?A`cV21 z_wdVLXSvazZWE_F2NlefQR7zBK8M6PZe?dmWxYyr9F+x@k5JlYmp+2hHEX!{;-HcG z6&e}V$fKF5ewWf1LQbb!(P~;nKS#lTBOuI;vcn)&%()<00y&)vUN4LS3jPlPVHA-I z?&E^Ca@ivo7i?lpBUVQV2q%o>f)n=0k7J0=|cj6GY%d`nrJ*0v{AXSygHDa+12u& z_#Js%k5!SX#^el}bd9$$S<=O5 z(9>{|37oltZs3APcTzXoq(FAUm-yx@Ws%}d&a%^qh5Af}Prt-& zniQ_ewSiEz$p({v!?{0Awbmx$%oS*6671DQn%e@vv!u%#*G~InFurQGsXh#i?YhM} z39nZ^Li6|kfKcv>cg6Q8g9VE6$y+)q0F)dIF`aeEbM2f|#~H}jt^A}KX3R|nSPB5D z{5Irf&=>K;z@~|Ph1xc{Le2h7mH3|UU;&ir4kBV*uZFfL&!gT_?N@$+9mot5q%!#p zu@3Fs8}wwAJ_uit`yR<-TGwujR@nDnHLh!XysP?_gfsf6*+ZFpIK}Qj98A0n}p-P{bdH12?Vtd5jp=|ZgE=f>%+yjda zR{+4=NBo$CHs8P!ia&n1bbA4+3FgGAzYz~3 z^$>o8c@VbaaV*(&ZBQQA04_1;C!g=oA13xpeq7|&#+jF=OO{-|;VT~5fH`QdW7_Lv z+Eq5~U7oN365`Wn4&x5ntl^>@nfQR=Jqq=S4(jkRY+brDj@}3Ti)k!({CO72`yZuY zz9SHl=u#}L!f_j}A~(;@(y)NwTLj}h{uqqrBA{9Ib8f9o09N8P-dmFZL+foTg-BP3 zmfY!A#P_qL3dkf^f&rclSP3O|LWxLla&@TmS$|~EABlABj*LXM;s-?OK0~AiB7=ea z)RM?xNlt1A8%y%=meq-9Ps@OJiJ|U2h~J@K?0cppdG2ELRXi(+3?Q8==X10GilUuIlBZ~#BdeYsILus2~m1X_5rBAs!(GyX6 zl!2EMM)(>0^7qx@P&}-bn7E@St?zsabG?BBddGhty5EJQG4OER`pSq_l4``Mbfr7I zIg)(O2*=jbkBCI|rJ>WKG+AlSSTKA8N0A) zR4Yp)a?z1{LCdO)1ky^AEH5)+3aUlOFC^pxd+Yk1@pE;I7^*RRbu1E!M`Cjqx3@yf zXZ8%d;UZc?iDv5wlc7Wxx7R*XqE{{UqtGlP8rI9x(U@WsM`|LCxYAeI9FDz@pBH1{ zG(U;>rE&ny7#?^d>Vn?2IXhP*`dpbwBsyw41_lyBL_OZiKz`5{4ljbHkQBeV9cF6t zWgEVKWNWZidj)O0)nBW?ajm|REkpmvzz)o!YpsM#o zqajZ;woW@Iv8cLMzj9HqRxdNuJral(f-b8&_Gb&hghnGfz0v5Sh0(rf1pgaR9-jJ? zNKbWSbG;Gy%W`a|LX)D&8N?TqgDJmxBrB0Wmyw#xd@0JZEuYZ`l8{! zaD)b0JgonH^{G2=2P4Z9sXM3R`73XN3axlPG&Z_o25@y!>T(t^2oO^3)IOBzv`TS4 zN>K|~0Dbj!>AHcDXbj&v3P4E1i9V9l)2Y(wv_Q&6AA<%yl) zXf(M`6wqg+>*}LD;8rbl@VN+DW>+XwSD$(HhxHkY0xWGpE=zluuPz)IU)Ff1t%x+z3`PB)1On1FDCRKfPqPxyOrIP`w zr(Tshn^G#H>ZuG>(p}$Ow>cU+2nRC?n<*L(J+zk1QhnO91Vd}Y7}y$pci;(qJM=bL zXn3P(Z&ZmE-lHH8a*eWLdY>L5K?nn-n!9{tjbiy`|8BB|=7iTjr(`r2CsS$pP zKm;8!cpOUrKWoOXfm)_f$qH*`fy-t!`RTk2%4Z|GeF4dzMt-c z(uwy~M_?5!n`=b>GVpF<_}wM-hPO`G7(1`t@I*)()-3YX>X$6?*Xm2)AVl>U!x-~I zapkH@Q_EK*a1~^IcZ_uY316)?*(irv{bfahliuGjejoX{@s z;YXCZ#?#arP?d$k8YrR4NK5g;!S^s4Xm4iyk)nnD?`e~Ay%7V1^!diXPW(b&#&s!P z_|SXW6mXaeP4@6oV_-Of2_8%VlN!J920@A}cLqh#!c}h6>LU8-M!h1#o$mq%AEBUj zDqnM& znY%k+un@DicS;^iZO`;2#`?Iw8JAuR6qhyQD%8;;B{>DlK(l{|dALFwbcgf{`mpBV z?++@KjmfJjaSeP4<{BPP$51d?+p#MM|72H5A~Z2hoiJ{y!MCtgSxrBlU@t%RWx?sOX4VA?b%prPPm`OV4} z^|h~K=>ik;8=gN!c~n&*r5<2<#|u-~ny7rPOfT$djfXt(sJ|mV?2Xr_$JW=W`fD{g|{8tL>|= zRVVb++B9n8m38U`=Gx)9m{{PmfNQmxc{NT|KRue*TpOy5t$NRBso#xNm9+2?YR0#7LY)AsXRw$BX5}#> z29{ZfySTD*0vE>uMc32Cdr)2c_`t+L2%skxH|k^X;5+ITt(!}}-s{_-FA!Yqq1w8p z#G%^yx^Ay&*4o&dduo-P3Mor#0IRJPZ;IYBl`uf4zx;nH7Y2D5V^>{$@>hP$y_=pQu((8jRf_5}CnwOMLh?Es6 zqdo3TkIL1N*2czJb0Z_S1;$3M8bWneiY>24G#(xE;nYsxGcv zuU>R`(vA{3a+sV^xg(&Lg?0pX_>-qpu1wBYyrZPO7C)^98=lrf+i8a&8PH}{dGx6r zE|d4v4#(SZIfTM}Tfh>HayU{yR$ih_b<4Ic6tu8H_NsWLgjE5B`ehD4|%r zKJM}I9VLm%`iALmMC)Csb<}Hn^Ot@Eue8n(8dZ|>$SvEfJUS9p(#m=lB*qSZBps=% zSNlWtv8xJUcd^b>OTQX-P=ERvudlCuWum8khd)udGD=%8g~m#SSmtGh^bY?Ww2Va2 zu+b7hnU;V0>SM_|mYx0yuJR0XN5CwGICxArsCQ!k!G45QgqStlnCq#p-EiG#!tg6b zOQknleLV^7QAnsm;l0|J(Ht?>#rwSRH;N-MD4ZwqC*|AK%HHBw_{lKVN>{>ri6mde zqNTLlD!tOaE7meo$q0sj?X=7iw-Ls=6qaw2!OieoQP|q=*&L7l84YiLlAeMGt2`FN zu+l7HPeri66-JpuSb37tK~sGh%X)EbJj<+imZv9(eg5gRBDdHAMMeVI!bXrl$mxl7_(q7rlEo`7(v0u zG6XOru|iOVJG(jKwlhaX&Pvbbm5JyiqJpju>vE{KXjGe$Tc=c*%H>GR!CIzf(8PMu zmK}juI<~`KZ{QELbvqVUC{7GP1-5ZY?+CCgVY62rzgyRsaib}mddjkF>8JdnFh;XT zbZRDDtS3L^PiW&pS4N^^4CsEJe@SJ%dX6PyGbXGG%UG$`r)9+|{RAcHgY@b6puQTn zWv`!(+O^#2NjlW&aLaxVn)q?r;V0v{BY^XuWW9U;*(2<_#zIN1%M-`{>#~qkJ=<*D z$BCC1`0{foFbrtO7mLo%a5(6Tg^fD~VKHEg&|7geC)Vu3+z$AiB(o)e_~5i&iK&XA zAYYF?9zf+Y<%a=pwYk;BX{<@>jcgtC2}C&=c)Xn?CUy5r`N0Bsdg>PJCRy|YS?WLL zv6^h4yN1llq-Qk58j3S&Vh_#2&YK?CdBpEVJ8CQSCs604a=@!@!wY{U@Ik3MrC=__ z8QaA>r}y!@PD%O>dLJ%AY%cA1z!%afH*R?9w6gq>bJ0p~GKM2HafV75D^?~#F~*TB zMj|6KR>l^s@anG&`ru>HVnl3Jnf)GD$TlVa3#|~39Xhg-9KLuc7GJR?#O%bVb*S;Ke?Q4OuQUc`o||`D_`Y{V=5MXB{DLsIOM(gF}H$KuNlK=>ac;2smQ}SH)H~ zK#TMZzxvSD_p~2NwhvKlnf7H`fk-{yQ$UE;>Nn6bVdz;*aJV|117Sa*DL zee^w^iaUQdHy9t*2OyZ{IGbXfS_>m%iNUE#UvqYVHptc6L?*y?^^9hQg1Zr*%xWo% zygQGLkh-%?b1yd!D{zk6Kfjo-9 zmju3t1imd6K1VGw_`DK6{5I-4Kn4rKq1F6@gqPzd2u*d$ud~UYELa@{vwTaUpMNTo*&r8Kp)(;eB!WTF$ZaVdskheKGG2~sBEIDx6dT1uXG8fTju(BCR z1y<7OmFozKl1{afCLw78fQk=zMGBN8)ZW+es6zS!*<&$(0X!@%6CwcS~4Z32vV)+uWueK zK=ky8vI8p)LxIE&k9R#ke9d@}i@n2BKpEoIFJKDSQ3{tDlg%v3ym|xl+O->NB|tE~ zn7%4oMM#Lh1N^Q{gz)3;93sNs)JCxMAsESL;JIt(Q;EoG=oy;kanXW(@uAEm}as zhA9bZ2q7T;R=x()-_)=ViA6F*TKD8YSRyBR`-M&Q6w|S6$h3LLgNJ7w9+`$A+q;tC z_CE;t{sm-m4;T3Oi{8h=>XJeB1Z;y079lCY3ECPjFlT!0v)I5k)ad~j@I{nJb~}WZ z1!T!F#;OZEc&WupkSuweUi^4jLN5h)!RV+4eRx@hjYN{snm^Qn| zbPP>TbPUbLk()TN(V;J)2aKJt_WGW83Ve6!3MC!@yPoaLE?Pve-BUvKD8;W{K-3)a z>Up=oHyCUpqo0OOZo@-O!yEDHqWp3zXuBh^SPg-pofSKej1(= zCu`&V`s>!~#I#@N0e&{4vNiT#i0L!a$w{G>PCqlAED9C0(pKBUe=qR$F>jKLVf+yx zc_P(^VHQ(ws=jer{{;{*b#od{`^zzwcJx>C&%u5w)KWldzcmOLQod6U^k2|VJH0~{ z-0k@tz!--rSQp!sriNFj-}jdTDud8g?}n||-XR%ULeG=gr~LYd@3YK^T(!}pXmf=0 zScC~}rYuXXgklU|4Jlszul)N@(Luj{=^-jVSsUlqOYNT@Hcdb;g@H zktKSYc+HXMCW}!&{U{_2-A{l^dqme3V>CC0X2*@8>2S7wFJ+nPeyR?wGg*W8xB-0A zULJq5S%P^o`WFbB>OP*sD0!Cn^+_1ZiN-YJ8ZBpY=*k;2+UEJBJ@eJ^<2Js zY~Kt;6$hNI0Vcy^yXOhY|NqN>-h`)M*5Skdkg4zNNu}O#{1vp*e4gzd``_8E*RQ)C z8TPjglk-&0UU$R#bxt_KwkV`SxcAjnVY6#A-)7?biT>5p!PTaA@=eYy@ zo^V|{n4u$Hlx+X+R(zgrh3MtL&?oo*0A7})FIxiNQ{7zc@h$0T!ZHZGT_zrvi^ucW z-c{eY$m4q+uJ``Ua6oml+XIiA2NnHceplaU@c1|rz+LoCK0W+k8pjlzbRuUfIEWu1 z>*&}*CFqLplBQMr@uNvk6J*tX6OSZLK<>OFJNk8sOoM*mbB%p4iEb4gENsGEA2`-S zzY17#B^^M~Ylgi#AyEimr(baenu(IQi-JljDA}kCPEnN3?S;fpYX1n5N3bs2v}!Y4 zsfyJe+Bd?bEObm#zh(&-yGGw}qsJ$>CD-DudK_`18JTA`4%<@A#MfNM*Bt8$2PihP zym0WApE#t_lPz?L1=gv*Oo^TPka!H!1EdedJy`d{L=FoJXV{X%kAh1QGt@bc{_U~- z_n;yP?NcFNs|@(JqXAZAk-h7H+5d}uM+7p1Tit46ZgtVbve3o(TdCh5DbVf))C61esBp%;|otD!y;3<#%TFg0*Q_RZy%+!Ff5- zruN3BRd1S{Mwp*~1TtTG*-CE8+`8{~jtg{ICbz0g|bH|T}dGXF*|6?myO@KVVq zcb96_rg|&B{jCZ``qNnhS~+eEcjM60m9(2_`;^#r_G@Q4;i?8b$)`lKRf5s`abr$# zQ^R9&L(28ywHTZ)pRC}rE`w{3H5jlo8AIZ4-M@v@80$2ab+`qunrb3)F0DU$74?Ot zM6#IflKm%?3gHe^Kd#GG$-#m^%zlKsrS-s-1lT46dfSp?47l2uG-msJ>#8l_Y8rF3 z)J871Dg}xTBIk`boT@S&MLT*J}oFM3ohF%!I+IDM=IJF1IlEo4}#0V!dBZf z_LQK}rSE})Kx1wSj3+6EIAD&& z*XrCPSX#zLyE-Uk;rD9M&rNj~rybhg2SK8SMG%38gT34w$#M4!n~9%~2uf|*j4O28 zbh-`KZdmDhjEonIj=s5-jeFn*k1yY%UAHD<^5CE!vMnv5?g8xD+$%Z>Bo^i{P%=?z;%Qs?dL?62ps;Ve2H;fTgbreSjgp zf;5BsAJm}HR*QksAT_#61`Va84Ps^4RQn{H*dvg>Yzl87%~TKaKwwW`i@LIW*>I;L zEf;RF3tW?wW~v*cFB{W?Qktp$O!~4RevdSvZnn@&^-T$m#+^kD4+7nSl)Kzi$ET{un8!Lx7 z6Po}MBQ|rL#ExG3Rbtz2u9O6ayUV37yVQ$PnyJ1GDaqsA+$0rsUemGMi1d z(Vzi4#oqo%TVlX7J3DTSwI%_uo1TWOQ(?IT%j6+d*ro7b;gxwK>jKMWq2{L^#eCT_ z0onf3G~#~;E6rgl^s(f%uvUg?99@GV5f_CYv@8PT$@K2%0~QOW5YkiKKE_G_QlCSsQuhatRc zi7$3Av%Z-;$+Lcd9YY!oZ`l^&1|yZ>DNgH(Np_7|_IbANye%+F%EI%Zrfc^dN!@JC z^~9iq!x+bFC)rm#VSd2n(Z_gr6=O&g*cH!Sqm>y?2Me3+agckesg~R=tf8>HdPP3O z1P4TNB1?zIaKoo)SDETwjDNYsU>x<$G+nx;_Yx2JxH|nN(EpIrL&3VIgw#2&{D-G0 zN!I~>nxYXYl@-A@Mj4*Pz=_>p!3U$Y(}*;YS_V?~0Grkqt!Mu+XwvY+oC5jPhI;!$ zFdseb*DUc5JRCNrMGTzt?(yRW1Dy1Z9b7Q*u>SNvk)sWdJZKh3WoOvzB1(by&S91J zXJG;`*I{SCRA+xYUc)EKD+K4{)FDFVOW1RZuTxK!uQFMN75d$nKnQOgd(UmyKZbRl z_c!YPO-c>-iDc0FL|?_Zi*fh2CUC!VYs)aAsxYT%YnVJt%eKD#d*A|NnW9almjC4O zDoneTrrvYA)v$8o*b2k(Pu9LIZe+4u?n>*)5o!NM#6`z0y%HKe8|mdRkdGXl32~U7m)!<*#JDU6J{Y zis)xoU7sv7$B10&1LRd}Wn%J{EHR5D{dH=Ev}aE4*hOOG?BKzfcQj|ajinvpr|oI% z+@0fYWg-=*&(h-Gl1@zBJNswg-%Kn4y^MZ`*I9TS;aB}tdQhCi|N6R9hdwMwxGX;* zVvC$V+&=*Wsm)9c1;x5RT3-PBky3_&9GFpsV{@-n?edV5v#|+=rP)*mAbbwv+P@8Y zOK<7k+A&%ScIbHFNBf+;1f;d5#Ci&T(=s*I%56-Iv)=0IdM)ve5veRe2l#}Is_ZYz zjGh(xXRr16(ipiLeuGQEV*}g8ip|p%-PeH=gg8;z{1P}OR}Nk1eZ^lR7}FQm2+I0- zsF6M^hh~iPv6vwDArV0PhTNBcv_kpXq|%3sn{D+AEzl zqV9s4v!D&+L7=^$48MgaOqEta$-N|nfN{f&fE|Gq#g+WD;2~F>CK+otvW+uTAo0T; z#Em6dleMcQc*_;uL7&81&3v$l*}dpqcyh@{GU)~9`1ZOyr`W%Fw;-sOo^5bl>5H!T(0xNYcj zeA?71W``nriMq9-Y}Eh`JEyLgL2ra%MXB(}IJrf%jF2?_a1xtBQQ4~ga_YvHSxQQL z{15X%>oK`}eBUbzvl{HzG5a-C><=h5(vKf(5-pzUpDR{t>`HvbYpr!V8%mo9Az5qS zY?1_<8SAV~%vu0eFl}0>K&iiMRP^}EmC=fB1Ej0^XF-p~*|l2?UwMW;QK%&S&@9*4 z9x7f%0ko4nO3lOy6RASdf*)_g%bgy9C>05^5jrEiPMSt#ORV|ljaw{|5f;)uH)lSO4BaW zwwLE&{*C#VaLC$4*Rm!J@i98?GK6RZI85ql#!cjaEt|zg!%0M)ET+^3pWUI<6edz% zsP{g~J_U{*xWmDw)}#a?LcCw3iA!fm-%=9IPc0sxeV0g}6 z{ORNP`lgoBoiTe_pY)P@tG*tQxGFit>bnp6m3XHLAM!UCdphxIx^;x61<8FgpvUJX ztr?Zr9g7K+yl>M9w*6rvV0}CBqRI%g#Ks+%gj~&^QP?v90Bk+A3t_4Gwb|(^^gy(y z4xu=0Crh5}d{hDWfZya~gcL}+d@dAl5+)k>8Az}mQYf1X)#IH0hHw8(hkZYvc3je> z02iU(koZkpLodY9>I)&s&Wc(W%eU&Vr{^=EZs-q34u`Sc&K>^@v4^Xcm3Zz3S@`Na zRJ!69J1Ht##q1{P5)yyiojzVE?ifwP`OL!=gVgQ=Hsjxf)1gD0MO09DT3b;pA!<3Hvhdegs@~@jhaX zK+Rwk8H(HZJ9C12+5aG8j zkr^(dU#FSeb}CR^YJ88N_iezEWzf2x6dj15)EFEjF-yy%PEY}gYLPFeIk0R#4Fb*0 z#?7IU3)y4cWvpKu$Tu))&RSQyIMmO`-DU+hU_=<0H4N9F{ zsKzF&)Y{Z;wt?*-AvEP=VaM;A8hOkT^d}xxsAnck_dEiWYCI`Z!l!uz%uBhSKYL)! zD(`HwMul*K@q!+4hN0*1R8YZ8dLrAJpf!np9SWik@Elz2Jr`PIMCrVL4Li3NOCBlf zV;J8tw5F_2(9PM-3d(`3iqgW5IA+ojOI+vKS%xl`qjlsC^4pSadeTYsNQ?EfV7n7j z@E(jTW!AaKkZh;5U3 zk0+s-X#?fW!lHLD@y=esX_9|L1+oev2NB0+xms9@<21K)4`IO@Bpi>JX9nSpHpheJ znZb(Vy=cA}?894c3umVD-A!Y-#>l>X&T-Xi>~_0@I_@*rmW9^wHn!@1Y(MFcXC0u& z<=L!4lED8k3AlE$%-RMEET(p3=A_jG+&P;L`hdmV44Yn(qJ5yv!cMNntHW>@ z*qV&S>)YBMOIBFUY~VQZrP&zKJi{r5yguQy!lo%(TfcFLH)m6Ahg@Up*jm7x^8leN z>P~4Bg$WdPQJ6bfU8d!kO*nobr-qY6JHttf#wKxsRGWu&ZpU~#c8B$d9HDrqTB3$H zhZ4TzsZ#isAUi$a6*!(+&T^h1MxW72)1b5TF>sg2n|NILidB7P5|+4544M!0058>i zc(dY844He0A$~ibH$^d$Xk%pQ7Kf`qD)Kh_Dh-dDUgFp#OaM9 z(+8t!_cCXP6)O=`^70}nvExN=4<5mIg%$Q5{8-3;`w8WrZ*(rAQhSQbH3J?Xl7G0! zi*c?gDQI$FfSkiE73>a@K?- zp`s;gjKnzH1?W!Dt4%DmGk4pWt)5nc?)*p-_i(Mgw+4`e5IFwa9h*^G^QE2#?uG2l2pFyB=f*JM%x0q7 z3u-A;{SZ9EM7@n0#NJop4G^W-Mowr@tPmZsX57xF)vdGh z3n0GgewH*C&DP z6&_bxapr-t7A(OttYoqQ1~7dmq>uF*f6d-;waHP{iCSGV#`l#V4sYL%?Hj{4p2+1;~w?ct8`~?uI`}q-~%L1OLkhuGtt@zzM``~0AC7ACk-`y{_60k>iXZkP9_N} z(i$ikaAYNJL5oLG@$uy+&xEl*pO>R)3Ys`w22D1e!N+hYC~QrKw4%_bDBq1Nx2UEx z{~-kMSwG2*j(_&SfC`V*AvdI}(M441aQz6rfiuZr_>nQj{?i~0 z0KVSzB-X2*2t0XqEcryqlcCswvRc%o>c9lTzpVrg8--+HT;{bjPzEfmks~a zTOFQQy(E(t`xEv8<^y|KTXjaqB*zi{mUV?Mfj1E@L*oH+8tH|v4F6H>S!f!fo#ofp zI};7+iFRUOAPBx1miEZuXR!u?6UO{ZQ-LFg4^YVED!o`Ob<@*+hS;z94(-=`p7v`# zllX!1g;9P*pBW<=CR-|!<{@lL*Cc|y6|=?Bj$gG0Qcqd_pwF2rEqt`P&g*-E#I6IC+pBy%kAk6L|(tc&w zw9uq!!OSWEaa2cot1E0R1p3){FbYHbDE?M4ppEkT-j-DxbPXi(bZCz*JvdNG4jC3>7o`rapa}&)I}N!%60SQmEPDLV~I@ko+2NSmkqrhpYHhQb?ufjJGla&T+ z&N%otwTbx$0u_YLhqdc-I2_OPy#c(S#!#Vv)dX}6X9zhV_|WR4+9glyY{r88|0ONO z96@M(UnxF_WbCd%6D4&%CsPxldVB_WBDIoyI1mDYd_$&m@gy8>e;%~dW_K ztxE;0KcSbRbn2|CeLQnzZRZW*K;uWT&hMBo4gt^BBXO+nw;sdr5H>^Gr@h_~zv@8i zAe~BRFcRnVu+JR>l!@@W$pgm=w~0w)M-Gu4c`0!;aUPB=e?XD38{j~Jkg|Eo%9LVm z%=^wWpE{e~(&|7CRaLSP55Jr2%2S$Mk+Vyr-H|8ljHg0G<|BEc@P0z_sZ}+}r$V)5 z*5^SN0{9%d1(XUucH00MWTjIyseiZLQXY*8*BU199IK%_i3HYFudVJFlI@j+e~11sjcl=$(=%BWd8{3=DU~5dz$Z_YnvBo zB&efE5V>d9il%~K)Kvg0`yE`x=uAI?Peb!K!^>bDM zS^XFA;Hmzz)H08gC(HH!0mc8B>QCQd=Ps5d0P15)oyrGDRH*w@^ol;1r6hI~;hm?b zpQzgZlKY9MeT7^5X|m7cj3Q+11Oj{*AIU38l+w52Ft*0WlsEX|M~9X*80i;{Lk@b3 zNFNDzu8Ay}5boR-sX48$a{wz7DupszjM+`N;rOsiO>&{pyAWgaA9n-I)4(Scdl&ZB zlE)GM9eTWmhO6BYUlDwRao*M7Pf`u|YIfu5msb+Mf3H1L_{Zh*XUA18iqzDbP0foc znnJ#YNK;c)<+yRadb2syxV#>B)saPY95ilRNdCKW;T0h_TWG0Szo^-49OtX7!EIg8Cep#`DRE~Cc{Tc>KZP^!;E^fzKU)}_}9?5sJ=N=i(H|lRrSkVN_8_9 zH8m3fS&bB#u1HPpJTr+Vs;_Alr;efAxDz1OU^q0jzs;h&_H7{?h4>g;ir4clARehu$N~w}GG@3Pyk)?AXo8|RO>dl+#LyPd8O%leu ztru+#Rijkd{H`QWM7^m=Bh*~g)Z9cJoYObgf+Z3fu~J)Av4n(AB~(P}$q-a8s;|VK zx~l6TWOQ@KVu06W%Rwp>S!gykS5<0x>dZ*-d6w5#t!SvK00o`NsudMgvcC^sWd7_V zyT-=l4b3BQjBYmvdW*I}w^|NcCaO8Exwa~V`Ymc`0ACayYK}HkHH9LLRcKMwxkw`8 z&}NNMB@L3oqWT*nb!ZsQR=<4d!YbHDYMYC?W`}YJ=h6&8fAi6$(w5gZR#lia^^01l zv+5F)&7oRRwN6vCzB#fY1cM6}=b)BVjXIC8aaED#5Ex!wHO{9}sM>^15b|z!<8Gqz+P@BqI=`W@RKl5FAlytg2jIUl~CQ+Zsc-4Tk1ELtR8un;gajE25VRgF+o0Wx*zngS}g5Mj9(o zVP)~I57EflP*>GlMV-4TbVKN(4(O5Km<-_Z#;PW%yfBs_VG}7)sCm)SswSbeq_lrP|WUmq^m)AxM_ z*8{j7#Ptxaf5Y`KuCL(w8m@h~zKQFJfG>I)E?~e#&(?m+a8K7< z;=A5#TpFq4&h4wLA}2-Mvo(oSG%vcb%BN|luFs--LvAAQLe3}?z;GyoAwoRvjB-mT ziMqex29?1Lk$2Qe7hQqo6aSzH>IQ{#_eS4}hL%)CX%wN6V^RI`Dl{cpG+$d{T&h8* zmdZfYVCyZ15s-H^j5<$_$zRvGsjIVNJ&kWfXTzF}9qZdd8#*_`+J!c53UzgKcC$Sr zU8$JsQ|V)O-Vqxv_4@AhoA21TX?;j#C`?(U5INR#gf_3Av%af)&DxHQUG3}91uomL zu>)LnqvO!%M#EWM$aQY1>3x-TONU{nVnsu%*-%k=1C6t@L-e>X+FViJd;^*4d~&UZ z973B=_YIwQZbBM<-q;md6KdPIdHuTX&dqnpz>bZZZbvQe+|&(io`=3yk6dd|iCgdN zShG0<-q$10p|^hP`gM0w<($y2joa4CtT(OMylLa6TVV@`$kIr~l~>FTExi1i*&#H@ ziuu=s?pSl{#&sY!7xLNMjRvKWT6psr5N^SX8^K!gcfN9Az&U7o7vZCjFUb=T5J&7ulO zdP7&Nd-I$HU9BwJ5Zaimt4biArH*v2DXMk_Br+&3`rD#K1|HKpEZt)U+YGL9_+Jkbg@p*v!QbUfaLq;&C% z3)2x++z3K)pdL7n60cq=G= zq4!0Iw-_ok4gTlhHNr5~(j`==4Y)~JEBsW}awK66;^3D&CXY=`bxSWYO-H(qt`bB_ zZSyLvqDl+|x^%Xb1(MWhNfKo;9N(3pO(rTofi$|5WeQ-03BK3cPRPUK_wbjL#Y(6m_U-0Q58+~f#Ty$!qNO9 zn^bgb{;gbku}sW&m@waqeGc`Omko*9=BK$m1dd|ytPy7`=nN}%D+N#{sh2o~XZ1nG zxPSaHoN=qG4O}1m?_Cn=xl}8VaUmzkAFX-2ty-5umScS!B-yv7vPMB{Dl= zzxj$K;EzE)`+pww^DKwNX+!6uh7 zc5wV+Jyp_ppxKWzz$IU4_8T}c0%8Vk`S^6EG`g~%KA(jBt3NFE1$N-`Gtn)Yhp2B2;gcWM--qI3#zePUEEs(Z~TD2Q>}^IwClW z#2;nGh6;|9>&Zk>1fcwQVu~Ko1LmkX&O`O--K=B%?-JNHfWG%Ban;sN_bR#wz9A%QiB z!^9amO|@!BZ@HDzH8P@ljK3keHGADa- zgm&W)G{$`kPCFq2w2Lz9SrbEEkZrv2@^n@eY1mpEC6I=VUfKL1l4}riGG_!isS`gv zres;<$uT~d%i$8iB;y-Y!mK$kc-f?U=RX#VJn0)VQnK?n*=Zvso6gCCBPBbBlZ_oI z+1Z?|Br6%WWR@`dN;Lh}gwl5=cOs|yst<8*XNVCR*TaWt@HJXmk84lyGMx^AE8`^+ z%~og!4h8my<986g$tEyJqqy-htS}5i>R7c@uBR_rm%ZIh@^)jtgIKu>X%VCeJBYux(yD zOoi$6?L6|cf?F|0^;DKwH#Ni9)g&g*dM-$B`P3xF_jbq1FT&H|8T@&|n~@+fy0;}( z9s=^tAIrz$@9)cJ{38G?%x%w{mw&EIFno@phS%D1~cVaQ1CHVsIXaZZoU}|u@ISa8NBkzMJ@rVA^{~oh ze&=xd6F*fShGROaEciwG;rSf`j3J$N4v!K&lQ# z5gNiF5<00B>;ZYai_d{$J%Q6j;A~G|NCe*C2`m$V<2->gL?DXS3YyJ< zuI*RR)7I_peLeAg{!S;l%9igA;tV8>9fNhD+E7iXI)uHu&?5TsDcKZ0v6eWEK$i%Q z2|{x)tEl}rY^)xJBYKo>jvj_-28GSm!|){ngyC?e5(3%)rzk1i4W2-Jpnw9$xq-*p zqLD}1Bad`M9)TP4h`_Pka*OkwLdOJ zB~qvvZh2;2L%b9|BxC#F+tC73)R3n}ZDN6p#WN@@ow=L`18XMTIUhRV2c9J>BpN>U zM&&b07@9C8_JO1evZIR6AwAhc8t`j|Z~K))ICQ%GYWxjszien*_0WMJ9a^2Z`j)2} zET^mk)1t-!I4>{Z#MgmPG_q%zaiENUo|#JTR1ly+wnSf^(4~!Jwz)tXP;gqbohCF# z`@iWMDdzxvzR`Mw4xElyMnsk|LTVn89|4d7hFCCyFHaop51$AlFkDKjNT>J&ZaOhw zykfK%=K1Oljnhr{)P6_5_dF?ZVssByCJ=TADM@bOv4(lpfoQjZz{sIUOXR+5`G9od z3{T)Z4unaiP>gf1uO1G#x8MVqsK1R7Q>^viXdG}ou}*~-m02(X4+fui(Vohz4|0mD zdUz#4!WFIYJXPjNZy|+Re-OnfO8;0e<_j8CmHIJ+*ZKg<=hQ7Bj&^`B^%B zI@3&-UdK|dql=b=W%*G8R16~=zC(>y5T|Q6NUSX3c6vc&3h;g06^*u)&*Q^4o6-y@ zHGmffI^wh58#U(RrHc!d2ni1d12&d64-Z08cpnzhyrz|w`c>*ccIuMc)Z`;jE}bpk zSek|5#r{%)_#X6(=Q(K=#MoA~DrU1%sAbXJK4`vu_>u)a#XAfM)qw2FaSNte|1&6nhBRGq z8bdZjx!w?}nQ6S!+5{#R5~umq*Zkn+D&nQgTD-;=U?KsFrp*jGN8BnRqGjE-0z#&w zvX!X(1``)aB?*j=GV4sx%dQ`PyS%&uYcH5N3b>D_3p6WcWT$6QaB$bRPVZ-oA+lvYThQjnmRgSBH-;;Ek8c4HK`JKR;y{9nr1h(%68%9 z&h*)2Oz<0$znGEpbElzhV)(Y%=>#7?27eCtk+$_w%*77`TgV&Y#AhJMD2qYjTqKtR zqXgMt<`tS@IWsHvmlV_FD3ingC`_P2;fuUBd71G3qp^Xgu%*I+K7+hA>ldIl<^_E= zdfCy#6sp-}_6T)HoUuTs;nOyM87xpy@(5U#vc;6L^672@`1JTZ8e*wiK9z3{T~;+3un` zI6?VC+*Jr_W1)A1<<{L`>W_a z9kAxh-|P6-Z@rU#X-&otl>>etJe6aIr=fS}D@Q;zx&Vv$(~N?*igkm;YIk5OABDc} z$LGoT%QQ>Foo&(h0{b*H44)^-OUH}l$UtD}Hd@yDDqKRWEfA5`Pq@tBLm#71>sXSH zAXq`V=kI7x$@&JABQ_U#a*oUDEWv3o5el~+7>zF>i7&R$HX>S;%BA4e{l)S;>^Tzj zl+mC{^>QW=Wh9USV6n2s@1dob!wrmVRHHf`Q48hg!QjF3VJs+-U9yu3* zA&B*6Ude-hCGpEE$zw{I5264ubr6n}LBZ^F9UTB0Uuf3h-g|O&Nt{Tr0KJBDKwnWvo*8mS8HOz~(Iq z)j^HLvj^1J?HIc_c`Wg$a~L5uA|~fKKnKIL564SfOitI*=E3CDB8$W1d{(o)Wfql} z50kSL;R2KMOx%@OU+%_4r^b2`nHiIlZh-gWmhM3V3S%EcU~8dP8b}08&H}ph z=fLE2i!KI}b1xdBSWM0!X+RP<=PoAa42*FjVR9BMB6&iXoVAR}$&H{eIb|zghiVOD za=KOb^k9;@z~rRdeCd~h`JFlIjIINCZ~AHgUR_g7#GlEo)7*r9=-1LE0k^dhh$(u)sPE#s4nIUt_z%nRaq!dQeg$(ECi zF*wcvQE{L{eB%~Y)c`7npj8MqM&dm%V0zj2@8#>n9+6D9wf@A?$To-_w93(Lf z{|jq(`LHhtjs}o0t!-<%*D!X`Tzcw?uZhKcU7Z`c<7+mr58xJ+$|ePc!#G`8vBd-Kx!`gJmW5e?`&b&I4#8KXxu%A63e zq5xY*3`xkuag`ffJV0wd#i3a{m4!FZlHWanFh*tz_~&^hxCI$j)3I1F+mJ;_j<<1mt{ zp&DK_;!61S0AoV z;`$7(J-8mk^)I*{!u4fbU&RH#bJt_IzJcpWTu9E~IsS zk+zktAIOX1k!Q_S@~XivT_48v^MG${C9dBGd~NS4{#w1?_Xe><%YE#JEUK?IeGJa+ zi~6EfO}=_xy-AZ2`TrZk8U?~Ldnj;^|CVt_1HRjvaJ^4nz@NWezjhVlmo2&s(2~mq zR5jy?7L#e=a!(eav%8_QyKP*+qTT%;xc(CtuxNLG9oIftj<0EX19dPl3u$UH8+}B} z#}tXqaz^I$HBcaWK6zUw6_4_bjMXQK=cwaGo@idG@UU2rBqaQMv$&w+Br+`{m73Kd z0W3NAllaD7A!(;U%z?N99qb@zU!{8%ewE1t?!~d z^X6W|;L5aE(X~-8lG!aH*Bh+%#yhiE_LeSyQHzw!35a3S7QNxF=1Oi@b7x1l+bmbh zHpZlG{pQV`o8jS#HoY=*`DK^SyKMe-B3H5PmX|wOM$wI5fCz~xSEX{*t_W%8DqI2P z-kcuhQI}$r>`qPhg+l8gjPFRYD1ub3aPqKJpz66Y%G)l)$;M5cZP|TVA(|@_A#BN; zD{EL91-UCiPA5>;0yXd!vZ3p;uB(AUEvydU+=uDn1^LcxyaHvP0QX)t^IJwu`-B}Y zUS|r{C7*`m8&|TmhP7)$mi64y-I+ZG*%urfa%CV$)cI7R2$hvRjr|kRiuv@d zK+UpM&gGAA%1;7!`FZJpYm%kcP}w`Pb=_P0^&Nm5XHQ1-%mP))qmDVX6uQ#k;-$Z3 zHO=eTWb;F<-@blL+x2VacHSw!gdv)eLpR|=1HxgOFZA}-t~nbwt-F3*=N)%+ZlbRb ztZ#3VZjEqR1ToMO;aAMPCSS&`b(@@g^Ed(L+YCXIi7Ai^!13!h-6?!x;SY1JuDkA_ zCM}Su3m+s{*RiHcv^qv{sxrx6;>Zj6i*|j3{UE=losvK6NM8B7Yuay-`g{8?8c1|s zX{AoM!^3FYv1Z-oPSabnE&}mvwb-#+EEwKc%M>f5(+qTz|(^ zbKBN$fLRpNi4GO6vc>M2+l5$mQsMWX`gckK|Jx-n+$uzlukE!Lv97poFR~)ntrd}% z^H5U21QhfJ*T$T}Vf&awQSNY-7b(UuFK0#0E4-Ob)m5%n#ZI*=>9yv~pVDEtil_Q( z_A;+Nc^7WbDce-U%C}?oub3-l8>j5>T;Y#r3F4F;K4phPL5!UlxuF?-gs3Cv{6C1o}7weQdwkO z3-FgKf_&@Ox$Ts@fm$RVxdrCvQ&HQ(Z=&8s%gkHt;$@v%$G=g%Dxfq4mD*d)ytb@h z-U4Oi%Cft&EqmW9M}@Ll?hWgvw?Zdk+6q*sVD*c$2U(u|sd_oC*(o3X)EXI|b2_zd zlzj%}P1>CQk?RXZ`vAG)&tGgzIK?v^UVQ7$IS14MRU-lQ#jg5EQ;U1lp z2Uv4Y6p=IN^$#f|i+>K%E&3I-FZmbnBa5$75j8l)zi@p{V4w74%AdsVopDi!u4VH4 zgm>u7J3q|g>o204MQGe#gs&5wQ@#;vkf-JTCh9tJn^DcY68THmy#j5R#eVTNIjg@= zCAX|wv#vdR5|Hc17T~1dY{A=kPh>45t8&C+))|Xa^)2*<&WW~CZae2$FVhd$(it^s z5pVP6wavM-y_rr#CkoWd+d+x1g8G;{;*6|kMDUG`OMiyC=T<0B=YA{en5Vb`%~Y^a zirYOnnZkS{0x448Pszjkw%{pwc;#_2)i~Gi<*`%FIpYz|A9t+Z#OIH%l(wDo@tdIP zfUlIwT#r1n!$8h}#cy7SiScSZ+kk9!=zEyO+C@+dX z67b#pOu%=`bX;43t8vN6z`;0$c|q;BVJP6c^%{9C!moZUC*^1S`1`zvB~J9o;FsX9eE(bBpx8bB_V#EE*I^=?#h^ z=Tu29M@38P1VocPg@XD}J>OhWCyz$1%H*n55t7SQr~(yyb9$Di>AhQu9?)_VaGa+=e2pJVGkrQZ}T*2pBHt)D4 z_$?@OZ`smj7lH4P??6LdRe6K>4nps03>)z@Et^z@~A~lEk&CZ zh41htYnp{GkYDQ5!2~43DSU@)oRd@X@AATVoIHGuV(=Z_l=!_y{7vIKc*%Lt9_G!x zdhS&cey_j)UA$em3=5-lt(&_>p*?iY?dk~Ot=rI5bLWNT0sZZo%jRDby5ibvubUr= zG+*32r>R=ll@NE1fZSZC4kC z1^gktsJyH+^w=|(z;xDcqN25bPrjl(;5Oi+ub@z(y0riBHBw$ILV;19mza^&y<#NgAc zrMCIHmeDrP`MF-;JkQfQ&(pu)$u>);`9bd(e?~EB9{$>|j~~95nl0_O&NqV8e9wAv zO+BR3U+8&?d7k<_Pv7!9{l@cj)-QCL6`rS<=jr2~r^mIYERj9*Jbi)3j@st;IMFd{ z6+$2|eADQvCaU-k5H2%0k+Z_)ks+VYD#6!DPwVZd zCvV!ww8v&;+T)XwQ6sr;hB13pLt^GTGwrQCzBs-)-#0rSGZvWnTq8d8lxernkU!L_ zv+d5~>Kl#Jqwc33weta++T$f8vnQxF3r00nnH9ClhUP_$v_VN{t+CEbHIOD!G z#@FY`a0(kR_6H65Y@4V5(rtijg0m&TPbz|+^b$n)mmc_}Pw-X&LV`T$uh1$F!Sc?# zqVZWl;)=e(IALDUM_&m&diWVfpVw>5*yc{{DVS-{E1PMEPZL45c^@Y`X7!lKv9sL#K9#L9RGrCUWsg}OtYv%p4O1Ux|OL5 zJrKfzN@W_TE0Jgf%`(fs0@Q{wH^ydWZd`I+Ds$r@0|JVCzOy+JzGoWbWi%m2#Z6=h zZsU?04MZA){vM7kpuUoc4>A)sYE`6SrsXSU%jO&X>ONT|)?l-hlfR z;C=;hze4AJy5gSdXq%6Ex#w8798c$y+`quwukdicg1FBuhPeM37l1gIDzOFbbJK%+ zZf3}KXSA3W_$Jq3^nX*^pGXT_jU2&D(Je5a3pnX5Ky`$FNPim*gxA^TH$Yc?6HYT9#*dWw_=T20^smG%c@viY%B3U{ zj>yC>g2H`yrF-M^&-(Nk>mW-U6g(Kg?sJ!{E-I9??GxE7tb-c4XtexEA^ijV6_ zpx?j2vy)I7o{VG5R|s3)2wUD9gYmi%2L1e(-P*#&YESvP+bxJ!ql_}|4Q$>U$h@!7 zvX@zdE6Kb!G_apr`j1rQ4RL=&nJ&$LHzDI2QB}A6ng249pnJ@Ua7%3xD8+*aO zjcyjl67NWDW^0@3IUB|dQO@&u^t>1{p7e?<UASP9#5{1%i=f%gxL zHms=>WzchEZ^Q_XHk!cR#mLV|T&u;8&y*O(qx5NExNOa_*Kil7+vRBAhS-AAOhd)R z^sRB*P(#=;>&Sjm9V?iauuG;{@&cudpKqJ9a*Ls_D~@W#P?TA`Z3&ot3C!-u)MJ3I z2bY_93>x&`6GBa?KCMXdwVRO4~tiM&4&A$}w2NJA_u4Reo( zeg5p2bYfp1CoE&`8x`ACn3G$!4=ot8&7Al`U^BH-T`VyswHZH3GE|wbpfX|@DUofq zKhQ%}5u=F}{2?5`gtZ4Ds!`KD7vjl!fP!{fpQoR6sr3c^ooqcwzrX8@W$Hk|HYuY$ z8eW$55?di_nP?5!l&NjhG^tFT_>tQEO8@<^7HFUwL}L0tC+T~>hP2H_O|w*kE}k>Q zEqZ}%HdBj=Vo1YCeU9?%sK-cLvTIw6nsxEz$9HUt?b;G!wv=YJYV z9QD5=wxb=&-(GQ{6j=w?TT}gUyEws;pCJ|L{TFh{livRu_*$@*)&8S&3w4?x)Cr#h zKP|PnO%_C|bYL|No;0_$gGv^ncO->I?YXB6q7W^>ise+`{Z1biYNfid(5{_AsU%IQ zeqRKs7&KGp9sdlV;|opwUF$=>HWVcF?=(&Sf{F2>>Wh~{McT>F`~YgUV?!*p!RgLn z?#*nPI8q36c~--7cZbCvc_JzOBjJcYq#)=&a=%P#H(;I^pWxh){9Wl0uQ4aM$%}DrMrwTU}ll( zxdv^rHnsy@9o;qE6+tK_BlD@Hn(h+hQfrM}Yhx*cDsQ*Z%=n0P?|0dAnP!`x)Yx(h zWa@paTW(u2EmfTF;4{(V{-JVS?E*QZ?vwVFW88o)LejguZxI@cY}GZZAK82kOYORi+qF6*R=P?0TWB2;e&rrL0bFm# z^>khD@9Ih5q`jJ31%1`4gruih+}m%SDY=JQkQ}I{8+WW_1Gu*0a%#f+18@mNyAG{W zo>O9i2uZI)Q_JK_egM_b8eOBg@?S*5%f72t1!Sk)a}Uq)oTm`!AdMb8m^nJ4eH@&8 zfz?zSVs+G78vdTY)gAt1F_PfP8U7Sa;eCxb{B7q7u=|xg{4vWk{Eb>m!{5CdG5mQ1 zrwxA`$r&n~;ZJgsHTQKR789c1d~|{n}{sqq1ys?qTV0!TS`>AvDb${T2kCnzE!Fn;S@`X4FC9 zL^(rXhgBm-xm9YE6H3Nzjh8MAj%SoxB}O^2FPCGnQ!52t>G@tYmaWmU?YM4^NCO*$ zHF{dhZkxZ*p4c3zeFt8uosjZdPh(vT>x?_ZjWk(*;%sW=+**l!h(d&9j*25B8fS=#Q0Gtg>6>wC^F(Ti4S4PC)|nB0l^n!;ILPIg`a< zor{3eM}rLv2M=nL(Jtdj{-@yvw%u7z;4*luYKYcMW9!pd-}8t8pN9vkhN$@pMNf#< zoI$ZWdrBfQZbH;NoNoPt&zedhe0PR(cc!=-^jT-o9g(;yT62~vU>XHd0ar!MU++L~ zDp_knxj%3R_hl;WLiiq^Z|x1hd8|WX13AfY5JMvpm_Jhq(yjl`*Rz5MdP1T(AuZmJ zxkz!rIJ#L9t+^%@73z?-z6|A*O{K!R1|rY+BUpOq;Wi_3B~kOc*?R8w zMRX82u+MrrNSQUg(-(Lrcg#-1Lj3gKy5^G{T#C2N1}$`l^)Pjn1)U9iLj&nqoM;0Q zr8rF3W#N|ZjKA&}1Fy!9vB$EP9{celM@G*!Qr5-SU0+oW?DL< zi#2Y*=-qzB=q@I;;l`oN2|R@GH=Iz ztQLp5uJxg>H1A^hFMjWFv_kw++x#?%1vRscG_xjppjbvjra6@9d|GA*+_t#tIIXXs zxfj3vIO5*s)4K{;B`6Y3layMMdudn>$zORqL9;kKcTbuhjrs$a{+S8aVpw$#V{+?+%*R%-1!u5AqnjwRGY!-uonD%PT4OM)Jsiupv3`uNQdkMOA}vz!92&I!T#!sSpQvL`N=)0HqQLQgP+ zwQHEJ-a;8>alZIw+x$gb*GgPC#^S#j*DYj$PyIV3fm0GV`4XV50cYKh zZJ}IU47YxKqR~1Q-eM!R&UWIjZ5ps8WS$>lySv%*^q^~JB)xRkLQr3rq(z{KMv6*e`hYE`_AO=PHqkemtpOaZ{x;jRO%`%AOs(9xI>v#1jgm?F|&h zyBuWrRACeTK<6GCt+Ulg8SDv*r1WDXrm66NCtN2n8zf|^VmL7bdHV5Gs7@mzHf69o zPckA|%@&#H$CIfeCsXns5)#&8W~~iDe|QB2it_Nr0o;&wvyahgc}vv zKOVb`yy+OBsD=ASEnL{QFp?a4EEr2Z7DQnUa9Gq<6K{>>CqYqO66O$2sDBwWx>kPAw4DF3T&_)Zb51faN14&C!Gw*dc8gV3%m%ZVCv(6V_C zhf0~IQ9~*BaVdA%<~3UQP7$0_iW1;GBqNA~f2rT1nZ_?%n2PLpL`3kfC*rFzf__Qx zJ^xMtwt16QtjA}R{^HM%QRSVb-qXP{@9Ihx6N_gtuhznyBHT7#;=)<}xNDirHa(RLmE616A4Jd#IE2G*Cl$ zIzm&k60F)>hl;4(b=9IHbpfoCXD2#Y_xS>}8qzJN)*CsS)2+^QQueQ+B}v+9 zSR|8fDwwouoJ@LN;_WVhn^kDZ&UkFwbwk>^N*kOWoQc@1@r)zQo~LI$Pv;Ekbv3mo z&z6{Nc4|?!`LO5dx1Oizue&6fh;3fYL5?t{rCPW8+hLpDsU=M;kcn^fCO!izj5u1V zr<|JN+-+KzZN5u;+L>7uqw_1PsGt1q@2H8pwP;V(QU}{<{M?F_x`mR19;6LsRzZQd zh1kXgH?Tgv)c@*)lyO@uIZrLUZWxH{!4W`XLo5*&v_L5=DcxI$Qm~!L9qQl1P zi=RfdN?ge_;qNwQyoLZX|+8Eg;wBD z>*qAd=!sAbmE_s{Nu2m;$K57ZB;b`uJ1r0KubgX}bx6oom5d(Sa%)tk%7^%$>Yu+cJA}|lMpgz-}BMD=W3CnIM zf;yz`mb(Z3Y^Db~r7~m&R13R-?FA^esT7#)f#7rR05Z$)W|Yi#p1t=SS6<0X>q zCC9=>XWRA*>1X5iXI{roZ2Qow_<7g%-@TgnE;~b8Ui*V6j`pF1x2*5q3E!CChqTR! zKH6!Jb3FZUl=MrgINtH-W-5^rQ&+-EwSmsrDpT1rPmm z(BC_?7-oT(qc^T+bzp<(XOb(6Q#oA0%Dn$xxN^ilc1pdGVC6j;QMb6S`4b;>+caJ; zv+qIT9Cz@Q{+9ybQAWk-w)w9bv26`Qc^$;4q;)WtmUIYT>i>usKn~tEfPn?J66Bje z{x>@L-(mzqJfeix$Xzqb+rmo3+<;&8TGayE{vBD-4V}covqItuPRu>jN&I)mm44>8 z@EGWbkND_qtxn3~dJuBCJ!snn$gR)p<)F3jz(Stl({c;X@`qPnJ{Lx-zIAfBfKJ-; z@JFW&UjO^tAy|x!g;oYctUJtN`Dqzg*%!nz1}%~Bz~-SU)&&Yt70y!@)#CjpZ^mI< zsGNks*e(v^!(bsz`gwb@!-yqo zBfYihW&3@x-XBkhAvpdrf_LIMF&Yfw6)upN1-erZoPG{?95HavTdBqN)PzfFDo#g# z@2fG=^BQ8k6tLvCdcmLd6KPcNyC3icdMkIg#FArbX4EV>SLS*SXBNV>Eig~x5w{yr z6)*Nwi!_%a&0x(KI)!998Y||FW3=-(rxK^Jz2@;HV5lMwV|3&b*L&-iMRX3c!nVY=h9n@@(_;LxF;K; zF$x>2g*{@3usM0cX6J^@$_*;NK?|E7HLUG83dZI9`OA){v{fONLHWF{m3sZluj{%= zJrlDNhURs_?H@1_=azS_=~_8)sPiTnFk$FgdFMSh#a{U7oL_WP@iz6o`&3>Y1Uo<15& z;*YU+LS6EZVeBF0>xdobXu&U<3-}$FWk9h9tGm(fjhb+@w&@DH){fE;Mr_gBCNtXc z8dL4wYR*7i?O6Y>e2E{;Xs;O4S6!Rid*|uQ_H;I3w%<~6Z(EZ!GY5Kk7%mdY2 zL^q{Acsv>HjwPnrEnJ2E|2j^wNuwpU?V00tjZ+7^7ON%wZJ^B@Qq{MO#zNpx)dfiX zVRm`#*=3oZu)pt8E5e>0r0lrj;SjdWpt?-7s9_KAfOE!u#&nbSSfSc(}?}8}d?~ z{s`5kO^wB8fTPO`bCj=c`S)2o)rVH;^~y&D?J+_U=`Xw_YPeRb;RqQEY&Pi}O4>)| z3eJWYPfJ{3=i|8OdOQ0|w6L=mD`hU-+*WCCpz+|*k9k@J6Kq<}#S#ZK+2((um7wK+ zORa?0QU3v@(TwS7PK2-QGaB z6Yc-(QJqRKIVbp*n3!S26>e{6xtk9q=qtaPB|`#Ivu~;wI#x9w zl_IUrFf|^zaR-!X7JV6SfI{W-KgujhF`-#r2^{ST!`{S~Fxy;3WRXEaPfSD$|S=FnNq<2{^ z!B8nyoUE&yoV*F9a?*)C%K2}3asvIQsOk%5_|-Rmw4yFs#CXH7z!|crwiD$pk_I~9qZPl0Y>LJjg2Mllh*80W0V6~A)kW*TPI%##Q!W|GCl>(%)R zp#wZ0a==Z}@UIo13HHkp*e-i&@;dLHZ_^^TdeSIiTT%)$9Bqh?<-V4vWt~WcTo~ zewsQOX>uU@^r#T@PDYIqQJ9i@dE|A&aO*=ex?8d0?mG<|0^K$qRj(rj{~*voQhvPf zRNCD%d8gO}E3@8*(81DfxGJg zJgLS|s;dhpXvHZWmgo5*|-2=R7R)F zdg`;H2hlbcqadXYZlhU5>fm-UojKCK`1KiBX|T=uycu=hL@XXF^&E!e&evsKq#TA{ zvzM{pik3NAF?8PrR5PAs)H;w`UJ(xE%L$4tYRS>i>fSe0CpQnpQB}UG)2%@b#K}_law7Eu=N;5`h~M977jz9=}SnQP8JuBG;xWBi)5 z2$Q^p@1c!c9;;i=kx>|A&L_ecIzLTR=nN^-$&PS?)u42Lfl`-emr8HJ@Oy-<*UKn% zpzoQn29^9-jgk}P=cvyyQ}bo%PNbes@asfr`CQ$ST8Q^)x1tHvu$Zmrw3fXZ9S5gl zAJ&rD<}043%Sv1l*xBL^#^((90Rt$V8Za%I-!hv; ziw+Lna1vd%M5RW^X$g5y-FXFCN>yYOZIsgzPM+MBSeH`&a`ET2JUul@oS=SiV2TkoYG^&~%zo^IBmIQ$SH?{@8=3sH(ghN*`LyE-^;9)ZRNM~61!S8ZWi;qh*&Ujm#no; z=zHNQnN@TS=v&@u2)3YK)HE!`j~wl*DD*2csVrbodJ zF>NFUsT*8UHz`u9@{sa4GNrYBW{0rvZAhzz0ozP?p8j2XB3zCV-{C`We`?O8h6)BU zm?)Y)dkmplCZO~nXqdqp4&KPIc;)35ewp+Zl-62^GQET%3B)CVzULj9+q^Vsv|2UHX!UNb6u}$b zp7=L1fuGi5^407sh!3K>xYhvLh}ROZQ$zFBy;^obIGwq9fKxkm532*;Mlz9)=mmg3CZWfsy5+yqJi~=1S9r-DZhLD+?ZJMU(=J+H~ zMlyKQWXd48T5UWC6H7`>Z`u4m=Bzz$MP=otPbB&1W5tZj3TtdQ)1Fz;T7!jR#H+fv z91O$wA}xbpJeg_WF4m10?q5O^Z1bPAq&>Ca%!=Bk2Bj}B?SLIgXKoQ{b;LRku8FYC z)bM0d$x}1G02)b*H@z_bu9o#OwUrMK0_{Zg@Q1v`7gd>OH4bfa)Ff9H!OSakHbb1t zz`UkS*s740#5PUOQ`+qLqk9M%W{ru0#-t0^6ouZ>Hg2ne&BQ+G=fV0~#j6l#bZuZtbauFq1N? zGtK1l&LC~gtY9vXpZwdSR_$YCn5f-@U(Jo~{|b4q_;|+WLDb1hb>cH|QiBg=9gL%; zG3sELSdwxH0*#5p1#!I}x9LT`?<4Y4z;5XY)SzMjtaUgp~y-S3xt|BU&T zky(7N%FDNGXVg#eohMx$zL75v->x7;cQ^KoCea-lFz^kWEV0GmP``Z&%UO+iy)+|$ zmqgXcoX?BVRN~ydlyU8Vp?&_Mjdz*WVL=6ph;bU?f^C8k`NfO)Lfc$I zOj1Tyw?$gAHUx-HPkAI(X59f^Rb*9}`yuK*m+2;FE0_c_` zJ2wMDHrW{mIXT5q^PU8KnSeE8@o!#-e#EDLBC3Yy4`~cy(Q2}a@h&Cu8GN`5gn9c= zE5>%Q{zd-aLr@;E?&PA+Kj0yX&*;JC0A>%!a<%vY+Fs3$>eRt%j8xX+$Zj-U!-qKm zrkdpQpbWSN4pIkkAea&gTzXHQmETQTRQ51feAXirFW~sBy>u@i`K*0(|A;`#BL)O8 zUy{=pdpH_yn84{9^z$!03$#9F*%EktKs?r{2+Y2KMC=pkp5=In*_Rw8u=)mr4o!jC zXFdrolx$K5t*;>$j&e>z@5HOTyx?0UMV2~PCC+wUz_OBW8vvhBNJmAl{JBiOf0SW_ zhk8ir)+L2xswIm5gj=SMymEme7#^}HHyMX-?<3=YhD-W2)^HwQYbx4NNZ-?k9568W zDh;LQUGwygk5eF_z3gF8>rtO@qo{QRbuH_CDxM|MM@!(9se_f^M354#77?2%RU`#A zVCdw;N$gKhsj8o=&-qxy2>jNZ>|$p{WPfl#C;~_jb4U)F_q&0_QLI;pNOlMKh9oJm zI3c?O7KXp;=?;k-+|EN?B0KdTy{RV?A_{dm*&}maGvN1*%BXMZHdEPq&5F~8QSN~7 zC?o@0xW*HyEN&Y6Qajg)C~S#kjU^gw+H?Z#+%%H>`s{eOgblHsx5jp6Lb0Cq7#*5v zZ`#0vf%|ci{iE8+0N}8~%CZ7cZ4FJdWD$xbE-jd1B|ggpI8QEPu;Iqv1Dc{O3qAcC%^2!QKGQ!cDtRKo=%7{ zQ*8&4Z}vS-!rLWM=*pcC+_5)iv}b!1bO>RvgAOAA+toH_fI}3~AxA9X!cpxqrP(>1 zVNLi!g%Ztv`W5!y1p73ylbcPdd4VdVhqjYYWfC;65v3IhLAxe(um-~*0RuGg zh9w<^=`$Zjhlfe6;en5aMaRI3pp>CyT$zz9CRuz~^)7*Rte{G3M=QYij>;V{Z7_Py zuAM=y3#nxI3EJBvIZ9eQjvR>egee82D^|yqTraZCuR0=Q!$?k_T4gP>7J(cG_o5aR z5~76c4@{^VAnHK7)%3CgvGJ>K2{tsoSqgHOx||$?IKXUJVNyK{!Cd8txzhXmRAr1t zI`mkL10swfU3}-o@OIR28!_;-- zbAYmRZ8O)x6nwyGO*IoC)5z(c3qp_RfL1ph&^j3T1dY9@ga}o-iZAXx$nv2V3R8Hx zUr1=a5zz|YQA@{JYAf#K?5>5U4C+YQbHRrAC57XY`-0B}c`&@Xa3Z|f(;!}ODcvQC zT=D@(fQ~qM*TGmuM2FWmtfTb3M4(b1gE*Ai4jT}`r);OXAxZ2n%uT$FKA-rbu;I6U z9>zBmf9q0_D-Wk3=NMTkf7C(F)P~0(2IBApl%=Q%leKer#9^aQ1_^kEvzd2j{8Q8J z_^zW0Ne37BAGJib`5#P`HP`O(p{ps?u+8W6DB6T~qh8meXxipRh0dUQNrE)OxKR~) z6b&FMN~V_}*{<{dXYXAA<0`JZ@w=<_YyA?&fH5Xq zKn}7k*|HrH#}VkRSF1-XYlR-zYXitj+jjdGB3+J>l1(_?(ELT2I zOwPE5!ycu>!7)wJv}K@h9Hij9OKh-$iD7tc2wRzyH!XCQUl|O%j9~9&jNltDo)V+5OzI~fw=Jz5rV@jaoFahJmnSc z4Ie{BQ~GA`?N1aU1ml{+IK6@_wrNX*!4QXK+ADBGe>4bfM_3BuZj+Z)47}+q9~}^q zqayl*7#(|JL+JFh!BMrgcmC7{#;oHI*e^iL1P}7VbXJInZ_AS50a!w2u9>%3){C>J zKrnd<4dd9x;z2YO?=^ACAwrd}#D@4(7IE4w`jI&8*2GFUIr1w8C5VICop@s&LJC=> z!{~+z;&(*Mo-9_9Z_ncBj}5s1ACu?45TZZ zg~)a>Ru(pDfqjorr?XgLouG+@{k%#i)+M{lUC&@u)YHqcIY$+m?GQyr%;Q#AG6~>_ z{`dembwvLf{fL7r9!FDS5J>BA{`QN86cDf+>z%~=^s1x!f8baDI^~0uasEv8{USl) zJ2FJy8xUFJT7+(C8P3D!7U2hK@lwIGmfvHJ1$?QiN8Aa-`^O{A}iRvjk zRVbY6U=YEdx{3_2GbjG5V|@Q{Wia04$fQ?}$J%j@<^H@9j{ixFwt8GT9`jL37AfP{ z%!bc}S%@{9;rRXiv>PopocRZIxrlUZPbkz8SC?mICh>j8K~zW_Gm}q^hbR}$pmJ$#%D$is;_*A|pY(lRK6(fvj@x(4Sxp&KDo zfes`_k+K*ppIz4EG{xcVlI2%PR-5g{tgO33A9@aossDu%Sz|xHDaptE2n&GK$?}Mw zHP#V7+~qAfHNS)aM>9KURWb!i@p(rhc2|KcSqU^M5vzyS+ENKj3`3Svm1A09=SkGR zs2?zGBybq9ITWa!d!TXlda&{K!{<1OKjmlHe>bL44o}=SI`P~Q|nfp!>i1)SL{z)kwuYN__ruaS3wqfvX!@S%8@ZFn_fj& z_{)!z4*S%Rc#)~I!#XO#@{94bzL)@hS(Vm)*P&tF<{Qh2{^gWtS0k1_b?A;fUb3K_ zubrnTdBk*R)p&j(BUKjwG%ki-hwk#U&(iP&$Co^%(5B&r!%}gRu zUT$%UmZa0?0*(8hQna3>?_nSikpA8qLEC$hwA_4sr84JfJxfH1vG?ge$lQ>vP*sXu za8;SQLUuhKd!N=A(|8%=*=MC64!+@3Mq({Tb}+|guyGp~v0qX3%Rt;CicU-a*-H>_ z7V4WtJ7CTe%jP*OaN;xgeo4QKo*xw-e_S~6-CyNG6{uCTOwvHsS=@}J$Zsh{exHfl z*4ml387(Z?WWKYRRcgbW;eOac><$ zW(lWdMi%3|ulFb@zP`zk6fU7Ft39%i>Ij?h`UyH|PxNU5ttL>T1DZSm3D<$Hc<~8g zau`5xTpv-aLuZxSi}j3UIxh6)nF5tV9@7ntu~5Lj+-w*~f=wyGGm@Co-}xnsc@nJ2 zD^P5|L?@vK%m}BK6xTkjt0LDzOH}OzSf$y-0Br+9(%)f~GDd z{p!?hU7A^Ez-3$$+J>(5g_C=YWX|t_9%;B4?CnAGmGhtOg@7~kl=!_^C5#e>0`BPNe-}+1Y za~C49A)Ni~`eG!#Ly@G4|ASF=G#(13!XXuWNk(E(H8!ZGhQeyI@Ah85zoSQuCt?Q@ z!4Xw0L1LBPiwpxp!RUc-QXL6~!fG&qE2$2~hKFP6w+70EkK@f&&NZ%25(asDp{v2!W=8i4?#Eu$qc->p(EgYNA7dpb|YB z28l;ahU4J`M1&*+J;$t78Vwu_g&;b$(U`2;x;K#$Sq*CI(CDB^#t_Kp4~L^_B0Lg1 z1Zu3mGS#tO&i1YhlHRuV9<{66-`efjrTRNN0_tvm_s$-*#ow(qxA)-A>+kIJd3$}$ zYA;T#o3?ef?%1+vYePf**3Av|?`UXHdjdVZzFq1Lzo)yo=XyJpWhk+~p51+UB<;QG z?)HujwbPFq)CILt_cVJ|RH(<>(=KXt{q+Vp<&n&e028Vh$)&1Cx4R8JzdM{7O+=}G zcw#U8XRi;<-oYF} z<3d9Mk9_UveS5D;?b+o86(jM{6f{nI6kT|DIG7^kO4^`jESU9Q*ZlRlq;CeMR z78eO*e{H6t4JMXzl!qn@Y=cH2RtBj%ByREKR+>q7p~;8}bZF2s6ge=YCgWlBV+l*X z2(Xrd_K!g|4WytdO-Os`oT0J4{#JD)JR*=AaP=b=U^BZfMOMN9*&eFo%c(F$!O`JV(x6oOcor~tRq%J)C`4Lfz8h@~X(CI~q$=c+ z+^w`n4+V!KA(ApmU$DTfM8j#LutSjn5IKy3qoX7JXwrQRK(;n?r&`;vZo66vI@%u! zb0$0d=9>-pWbhD-74&X0JPeMbZ3Ossb#23z!z!rqwzcl?`+K*`7iyaMG#HEwhanOs zX@huHqrs7|T00tz941=mSMho}N%QRRsXdse-El1Bz_dLw1=M z=BkMebR-L?;35iXP+ElXO%Fu|hJ?U@D-fQ|TuaKnHe~XZ1yV2r+=pJ@)dqFE9dro3 z^ZQ72Fg9I}l_iFuj^GY)7TKhmO_fp5>hvHp**ye95VqKJNjUZr%w}PSf;trW2ePcK z&=ft~ZlYg%aq*x!{(_Sr4EMO#Jnxa#i<@QN}1TMk%AR zc0h6aqNG1aP`qQqktq9HMFJ?;?Dlnc`@6TBQd|UGOAWscthErfg@d6IX)cSbmz*jC zvt ziC-U7YVwkDN2Ba$L-=IJ6GA3B3F%F8Vx{Jc2Vrs&QMY1MtVifn`NqH@6-Y&}1jM4B z+S^by&16Z|f<+kI48#%%sH@>I(_Dqz&Hhf`0`rf|=sTwg_O(N9JC>O&`m~>n80`RNxS$nN(xQ0278`3M7gJQFsn( zVVyjo5c?p)5f}-^lTdDTYP3$hyB2169aP*Ht2no(AO0HU7Oen(m<+GmZAiE5k|gpp z(1XLb5wFRlZHMACIxb`~IIM;e5g1h}zXU+^AK3NL!QcSATIf{!Shix0Spmg|#*z^t zfT$F1k6W<>yor1T%PLl|iFF|Sda8L#`=0jBR#@%M_TGTn+wJl4(18X)R5ySCf}RQ) zP);wIfYWON16>hRi6W5iyM(48bpRitYd8qc2!6QeH|5lUrE1gnzEb%V{4SdqJljk0nernoWs z2xE*Py?vC>Z@K!RI^hue03xL2Y-`V(vWf~W~d8yC>}>Z zlgsf$3?U%$<%Y;-v_Rw3Z3cqzU_bg_DiThz_!MYkmKISxLSWPmOD+A~-55-F1kl~& zAH$;h#E$K1pJ{p|_sBTaQP_nHCHJ>TKZh`AKtrP*>U=~26*4Uj(H9W|OR?iic`OAc zN`nR^{VCOF1#H4Iy9Ho%i2fi*)i7=0CE%Ntq>@$yHR2^yTR4%385X^`T%n@a>zoxQ zgaC0XNtI9GJ5pm-{f1)j+(V-@QiV?l1Ca=)60ok)O1n+M_@$AYP~7&6)KiNyGDny- zhFUg}iYi05Te4L8E*_F2L8DCcl^qoEBTDURvAl)1E5JhiM;>N#*--)aO`9dM>vvSJ zP|ZT)kVukBwA)U@|D6WdlT40|$Xd`4QpNaV6kQBWQ&M!wB#3ftj_tbGR!TLjZF7U>j&2g8j(9JvU6KaB~p58H_|DNem;y$$>v>J5b+0iuPj0689xjI${UdK=Y7*T9eR% zCdkeXxVocpL!wCo561@B(Gc%x09guyg2UE(fHa$~PMQ3K1AjJ{lKiTV6c6$qsGQ#h zW9663Mx7F2gL0ZH2vXCM7Nx)L!dD^!ZhKz}VARj8NGT`Iu%3xwY-CFtjZ!L&Q``y- z8|p1)3h^{Em3?c^&SFs(3a&<+{0w-cQFUieJvlqvhKN%mXe8ytZTIgsB4|g`F38Gy z0>8n6tmtFNCumF#u`9{_Hk^d?xGAbQ93D)mqtR4sbYKWYqa?&=QnA=@(m-zH2XZot zafm1Bh}zvlVIWVqAw&4}_y)x}*pfM`|d|ppaV=jl*+stct}92GsSKyVJMZO8ijw+Kbiu;Q0)jnyNjrR6HF;kU#SQN0njn2Fc{t) zt{;HxV+d#UmWrJA*lUb_Ru3}+(HR|JFV;dJrm(2G=w|4WunD_8-JKXPY*%+-bc6EY zo7wXgn)z;!vBW_o3irl2u zMtEqmj`N1_972#Y6MbHL3ezV3N08LK$Bq3_88KY}b*psp;I|vm7p97+VHpAM z-S!Mn55usbshM-CwZq})fz%M9TDAgb%r9!|R*dksNf&BXE9Mq}(Rgb_UP|9cc`e8_ z8a;@)ahMCcZ-a4`XijjhVcTIm8emc)Wa_MdY&HFVi?v3B$aM%bc=d@8J*h=YEm>k- zr=okab)gP!^=i={WNy&}6uuJeNygRQ%VF$I0Vxq&_{Yt{zzRtu+l;w0^mRxf1cL@P zY^X_fVyqk^vnyQ|=`dLJxO7U;VW=OJPy>u&rViS2vlSIH{Z2TzNA0cfi_STCU< zMG9FNaw}=Rgr_CB``^tbp~qOo0U&51bXC@|{b5Y~M&VANN~k;kZ||716IUZAsT7;6 z&)BO?ePYuJrpOqYhum@_QFd$|`ci1h8smwEU|Y7`7{{UZ@HJ??N$sf=}#&dinxtQr1z4B89q!C zr1UF+X`sp1%~U$w-AgWv$~mWj!Acl_#lwaWOanR?Nk9&ytc*mGH01{~ZP+|^m`uVY zvG$)5!tF1M@9(98XfQgf@=y!ODRY!Y4L4gxm4hoLNIsV!tpLZ#bE!mW1EK=OtwY^T zh(;u8%@)Z9EK_55XZSD$6iu#3ZbCny6GEY)w?N4XKND^|{6viS5GaX>B`J8RxTX3N zgE<|Jt?;xc2g)vedPdWMUByUS;-4;kchR6Gph^?);5#g-y#JB zV^#D!QpREOrbPb+ZQxCkY9uQj9HtTTn3Q=;nxHzoCJdlEEKSG)p%4^jrhaT-5VE4c zd!uEfpo}KmFm44f_$T!Wl59+eTN_ld)GL8#Zd%0QcxEOHvQ81kZ1QaJH1nqJq1uqe5rr@Xu4xy>-uj!3Rl(4ou z=p{_e3WY2pQL(h@e=mydl$DSz#%rW6L^ne8i)w>q8ss-5)!sxfnjA!QA9JHHkTf+b z=4YGhcTt0sZve?r!40Dw+(k3q*8EAc$BRC)HfeT5Oqx^p+^tY25K|4xtbib|Vj>!P zVHXx*VoJb^wOoj*i_w0O6o(HZAcnP2hBkoefz{)XnbCs`uK-$KgmuP5*eI5&Ak)0O zmxin(5Mi3A;5I0DY6CgDOZ`Jl3r;mDf~oCl}n63ji`R9<}n37 zY^ooNYOAkt%`msWnOwc*J7xVaLy9>GD7q5BdQgbD!XQohlWCbl@ow1-7y&!^ENw=7 zh1dmrMt4VNp;ZvFrz83)#FjH-=oY8221##Gb{2+NaihErSU}zg`fgi$Yn$5B<-=lS zTI6P|Y_&h|yuR1p6#&)cJN27xnqb}B(2E0Dxli6y5SA2zCH(^t?t~&iV9U^)u#$$8 zLQC#bkr4zs6?cJsYhVv25^AV}_e0K3BqWvtLa517H8;0GM@FQC*s}_00X_jThd#+D zE%?$-DT@(SgClinE!?6?7Oz4bHX}osI@s+Jt1h!5=y!0bIE33=Y*tw{Zr<2H9eMLc zW)(u-ke`z3vx6ZpnXXnT)QpO&h)E{A^f$_=x}qGVY_-?CJZ~)xz+6w-*zQgy5Y3bF zMAib4iL{l%lhlX@5D?Nn2KSgI*3grus}n43dRqJ{rpf&+-sTW%7D zPGuSb@?ZpgM5~Ck?WnD_E|P0y4ld;CcR(PJ9i#x7G3hM1@Iz6b-=Q5Hs4UK(>N zJB{3E3}KFmYcpHD;W1!c??rI4R9Yu9Q#CL+$nScufeqD7-*maNmA8m zmj~D!Kzl&Iwo(99w7)S{0vLfuxYWcPJ4mNkO(6QVTq|G)k?#d-t+fJn5Tg_tOWRfp z0L1tNM4e{#9>f%m?5QPY5q3@c)hkT$S#SZovZWoa?Wv*!E@ut=_< z8y1bwi&|jDS$&jK0%J|=;}wEsr1AGlkfM|-&-7bivxumaKx2)HJ^O=fSJ1HhUJdM^yGb3 zQI@`h)1o?@N^@3RAKbGsg_tnj8fgMfdELw}Zq$u5iOp~y+~ZU}i0vr}SM!-7IBjEp zlE1xoDxYYki^?m4p~r0h=paHwC>Mt>)1@dH{Zssx-AJ>4h)7#6mNhC$_etxS?wpFb zf69gSjzG?TtU+;h91nDeyLR#OUh(toB{}~~?^2xKuFjo5=28^>3!lL5#OFUnw-55| z9rJMeFx~p^?@*i!mGg&o;+CL}=wGFGdJPKJAI0qv{Sp56EBF_us-FT34wRw?>|wD6 z$gr`-;wA?}rLWy^J`aF2wxH0@M6s3V653ho!g&>NVqXUhcjJ!pPXJ27;(V${KPb>W zg484Y4*45m*awTz(SFds(}JB%u%pIP${I~ShT_G;b@qq1iHDp+{}cf5bDVx+nw~e| zuN5Fi^p}u?O|zO{8SsDwGj6AYIJDn8cAi|zsE*h&i}TDfBdEM9|obB_C{yO z1{SXXc7us1Q~+`Q&wp}}5#_j%L_=^RaYN*E;v^3J#@@Yo>@9pjZyUyOB_=y-^haCq z?Egy}(z5MiZ@?TkDL3x;Jl*|C#o08Ue`s%e#^<2sqDBI)r$7KU6B0MT*`d7Ou5wN**RkeAf3Y}~+0=-pxvEgh)eh;@ zSft;;G`nKMQPW^}-tYJ@HN@-ZPof%xPVgvm1q=bTNZS2zZV?>3va+Z#_rB+1Z3tVp z87-c1n!N?Mu>Yt&08tiOe~s&2y7!!^_Rujg~%zj z3tFTXJ+-uppMo}(76{@s1XF0}X`Ev(T2pq}J8+U5s+&LmIta8nYc;36T9EcON(&e; zwfZpRRbP|$*HJn@<@GqruyJOkVM$e1C-&wdwFeVwa))kmrYd9d0ykq`1vl>x{#{bw*drH>DZ>f z4;)&kUk8r9Q^6rI)XJ~F%==-{nK9JBnJcsWo8x~em4HNBw#wb`a71&1OXztr9j~50 z16iW2umV~}KV7v{AZ%Q$=~q}!%j70st}Kv-GDD}8xniKyyI0@EB3H!%iLJ-YQ+U4KaS%Vs}z1a?~&HF)ALrEESYy4rwX-cnQMut zrKhi!MP` z`AwVzP=@7So1xqRVWi5HAiuOntdF#Kc|th*g#I8 zV;D*fh1fVNeI83PO8SEh__XK;h1kiUNH0h8qND(q1ZU?}TXky9SJ@DD#|(Kp8Od?~ zVNj?Y4`~~^^sRaSyx&rsXB{DQ)uPvQiU$=W>@={sGCkR$3C6Yau|~&G8spaMklScI z25G40`D;H~r$#Yt7d!P5W*`64hPreePEz`;~pT&?3Mi&ZHF~-)l(p$N@(5sVf zLS_+eXcRB~o}6=doJZ2KT2x0a1 z%W3JWv%_`|T-G+TqIOtc*60v0@VbX3G2~v98-dHzg3+BBy~%N8=RC!DY(xDc?CH(m z?^(higae{3T447IgR{*0Lm$2fAs0<{SKr0)gDhp`n%a3Ws}9?TkJy^j<^kT-THhX}^q&^V0;IiRCo&ZdF0NreO(i7sU??@Csvuj|beGr8l$ zyA%#7=(Jt!yByFRg!tM<|J(lg>q;QK9I00r8&9*7T2NDTk~N=2kHy%GT%NrDm>`mo z8)?Edxo~226>;u$y&44@?>;TzZE5-{U^L(|>kY`ymcdL_Gav;VwM^qEw@h`Oi~{ol zHR{dzRp8Zf9iHQ{;f~xjJ91sO9d~KRU7lk*T7O=5`k4Mm2GY*qmFj$zE$h_=GY~W) zt{A5&SymbTw$f3;i!YR+*xBG}0HQh2=Ii2N}uIuFD2#t^ly<4Is%`R7@zl_ z5{xrqL^l~4;W=}Zblk2IYV=p`L@e?-#Tr9Ar+5z=&b%!bMa>|?2IA&%NAS!%>plDK z;~akyhdSRlL&=%TCkjj5`C&T^=@z3#e_2o?edC5;dS!mTcuSp%LV%f-BJ7BbtQXk& zvMIvy3^b1B6poi}VHfm%_MxTIdQFj6dZbHwpg$@ckvwVok9E|dM<%ZspS^TzP~Q>l-)KlGP^|eer}#ZZ8j& za(gGMH+-iWrxSdqT6o+SPTaq6T>DP-1iwiCXv1>~`Frc$2btQN_y6kyic`kMUp@ag z1QL^6MwNF6C8K3HFH@X8-si@#^;R#o}hy_%dDUCJ!D}a1isLd|6dd{NpyIp$|9Iy=G`h9czd0oaOMhK6u@Y2eR#Dc{46eP9 z*Srb=IdRAQCPAG3?j}Kj-hle)R~Hs)(o%{HXS~)*j;sJN0HE11x+UhkI4yHNKfgx* zbQmo@S=;28ye3^ur2^ra8vV;iw$b_MJi_+la@ZOjlQsI4h4jk&a>nAs_W7A@6ZsJ?v{i*3jA=*4Eawz$m`4fo)fkD9JlP9aU0eX*Rtx&% zzDY&XuK+p}n%TJXX{fT9p<^>#4JycQQIv;xQ(IZN+}undxoX49>oX zINN$N74#|>L^H3$#bpcfAyOh{WP;I9#-f0SDWCU zBVAwxxy_jyZ9oWQnA(m+twvt}ZDQlnA3AL9NOHK3xz$Hx{Q_jUCRG_KDV4$T$s5@a z+tBTChru4TrU>dr~ z5yS*=+lHY28DycS#v?&4ZW9%yYA&8ZMec#LZzw~!BE;7c3!mA49*aHo0_zsTVY1;H1C4DU> zFsd6xDh^2rgfbXXQ8&>t%O(ge{ah|2&{!u2=k~TW#)54-mo&!iX=|N-Ja&$!5}d2r z7MiCEGmM8}_5$Xl4aPR7FzyJz>ksVyjUP84?rBD`28Hs_mrgkXm;zz7Kr?2Ie5dUu zH{ZX3Ri+6;dc=^=%LOnc3E&$F^b2xdhQS0Ey?>k!j*lrgi;^ZeT| zxR;Zn(+>6V@)LV`pkZbc1D??RldEv(*W@a1-M=+VuA;Cj2k>0_ng@(GD+=GFU%Y>E z^{tbuCRgu-0=xzktVd_M>f+>T+T*LZOT^AC%&)I1^P#h zVz}bC?#6{xcaAG?U5V=|T&r+hjcYZ;{jI;ZIPew+{txCrW;2b!cOd?q+y4oL6-ef7 zIlZ=7aRxAbxj<`u^^_Qqzf?2v67-VPIU8`8Ean386h3QOfQQ}phmJmvLq2hoZO<)y z_uvwmy=>b-vzIhEaE+0AHjwQ=Dw$_eSea!Hm|6EdX&~9hp=+4ILu0Twcj#UCkpb=E zxDQ9%HYC%u(u`Ez5!P~cOZ;+tSkzi>?ns?OdlJw zxN-uOofN8*wT_vl>1y;xkTxEiu^t45HXwTGwp_Vlk_q!vheRXVY7??*<9Q=$L!wt- zSd&VEV;E(!$)pB4=Dsrag!ZdE`&4wusBodh8fJ%nFQhr6uqWSU;CHlHfz&o~=(^%G zC)8%lQi;VdXyc&WJ)nL7pyHWlUA5;do;1v?DMn!lQAW4tG#lqSdus9IVTb*tIcfM# zTWY~%;kC9G#gl{+b}qcvIfVfPk9KOf4R}#2ug$G6nJ57)AR;OT4?rs#s7A1N~bRpOP!1jS9Jp3F8BkDs%x?kk(*BB(B2*f@md_Z8ZR$y1HQ`0#Wt8|DCH@SF`cF3WP;327n!47Lt8kdE zUwIiG9FEh~G`$9cT9o3N9A;-h4F2!e*N{en%d+?|0tZj7P6O-*Gk^)?+q76aLI39r z(Ak#8Wi8%%kh_kEtRVt&EuM)Mbf}!yGvOV-t&1QQ1A^;z4o^F;#M26bU&=M*7q3y) z<1N~eE@Hu=2`|0>Wwtrj?5k}ijdv6^Z+ueqk=>+w_<^gcoq1or6%LKQ?I?Hf>cToh z@d2i7o1OwAz$krDZojfn%UmN;{0iEF(_FNe04LmQ51*@sKu_LV+ngTEP2Q^=!>y+8 zJNo<{tm9j7yt*x;i8|Lwde1sxnOo>IU7T%yl}yCxx$|<%S|*4)JD;V0;P^IY<{>{2 zFEtUT|Hww#aggTAysaW>$2D*wy@+`5Ue^?E)tujjdTF^<53QrPtqY4Nu#|${q42lzuxzMZdl>@*JdDI`UpxD-4-l z$ud39mz}Ieq_gpqI$2HabPGIx*-n$yw20VjD7@T>m$vH9u4P(9lX`Ql+Obx&%%P*t z_n>85nIKoSIoG01w0yybZ!mJr+C=lXyDQt)xU9`vf7a>Mj`}# zf~s8;1iJH|j`ZgJSN%46lbFF1o#}YBQD*w17S(QQw8`qsbSuMXpMgLzE!`=%Vi+Q90KasyrR{i2sJ5-; zswp8jKKRz?p9uUZ9g^o0Ma=q!UYX~AP!X0b1X%x#G7z#={;td`u)Z3sSIV-UC_xhe zz{}mbaev&Q=?Ffci#P6X!~Jr(^s7b}uv1f0x_%h4dGz^jInNE_Uo4wAz7j4Y9z74% zl4A#TUVV0@m(r@wiG*ewdWc5XeFlQsVG@=1;(6N>HO};c+9y_$)zw4-3jP3(&OwZhFX?yzm3!gztMMHE(M~Su z$i1Lx7k~w|ZkY=v?h)Ea5=ozTicGIgw}j*3w=WHqD^H8LWsBvElWLToE|}7 zYhzW5AH8r3sH|rtw1}Enn?umF^xMw1+Kgob8_N?P z!Ezx&IyY=CExqlG1FF3gVdH$;iwR+vw}`T?pP$)^HE*tvx9*J7%#8WMymn?0R!aI6 z5G6eQ6F;AaMR2ar&S#-HkvdePKPfmtP5^P@^Y=3+=y7hemvEw1PixWt-I6=-4apGYx1~Ng9bECb46RMzY%q5V{4O=-* zNQAYnQcm0;%3A80i4(vfIq`!kixWs4%B*Z`aoqRX#l{xreLwtFV@uV2|8&8kdBMqw zUHYWGOQZKp!8I)C{FJ6>nfbYxu00cI^E~c>D#0g?Lr^8H82?nYbl!0Rt_{{Cw9r_g z`M96GSgaJV2v~Xa`KPXg-EPvJnTM}!v_NK~cqtUjW%BiPJSC7>E+3a`&r~sVBm2Su zYT=Lm7qoDIpDpyW?)3p%C*3Bv{z+&SLOM5WqJ`z;ge}f9n3Iq%WUt>Y z%G%&W3%4}7TBz5XFc=e4uWzAV4;-?EryO9e3xiAadZe~m^i`Y8eEd2sv(C;)_1QMFA?Qoi6W_%2#$9v9gjRj$83#q{jaFr3DaDpZ zEqWG4gcfL0NxPNICefy~FtJ8ft4$5KO^RsGvl;l*ufYoDQ#@YgVIsT$Io{?zgz=&n z2?p#j{><%IftI2*EU%s)qUi{9Bfm(>>o2ffefj)ZWTZidgHtk_>0@-uvR?3#S3Lv2&(8y(K0HQbDFfot-`4y1K?dU|lq0&N+lw0PP(jC(r!r@OC{ zUpST45nLaW*PZwsv^`V$A^EJ^(m&{KZuhiy`g?lYy*=1W(%b~kN>+FIu?xB%N0c_V z;#R#?RcJ#w3qa)Y`giT}bT;?UJAYSiyT6lf>9O>$+1KOkZf7XYeT(@ASb1NX%SbVS zZ@I;wx4E@bzTMaCyS?4(Q;pvnacDrduj`g3{PLe9+@}h_+Maa=5(B^d?P|NFsh+a+ zc{}iLkKeOXz4K0WUsrc~XYVch0!Zt;)8tr3ZF^7eI?5^uD8goY>;R+z)z#+d@u|I@ z9UVRsRZnf3e>bD5?ZoDud}qsUzwH5}>TB*b^S0IcI=W_;m+PUn0bbqZ>+Ca8&>8aL zZ^q&)q2TQ|+dEtQ>WnCSomBTt*fo@XRnDnWR+FCFYWvy+XJI93jIQf#Hufo?uX zuV;t98-xjfZeP8Rs6s#Ex0LGc_VsoL>?z>04UtqTvc0#|uWi^u`)4aorE5N})wq5i z*MGQ(Ofu=o7=NcQ`w?G=OId!Ms-d4R}RS&6X&eGckqDXK6=k;`w_HJqKY{owwI9;5? zQ>J$KJ@(Y{a^TzJ^$7)vLObhyd!SU%3Thu8SpjVB-Zpe6oWDnh^l-A;8|d=&sGe>g z?Nl!RsND9>+dUnqBIQ*(`*!WXfz(vVwK#yH1WPFboUQ|@0iwQ6obv2%?QGx6ec9IB zQZn2^v)_NXu$Xb~Yp#B_rksBNF8R|Rc-KFCv{kg0t=bZ$AnRCoA#cl&&u z*aDnzDa$vK*jj(ijfDx-yF7i?TSd8S&&Gn36+Jte?e8z&v#}t>)#L4Hw*kzNv%`kqs)8p)nI8_N_RGx%3CV(ul#op0SR%%`YFF0|6FF|K z2K|L8_P1}{zr}(7r#OIBzVmK;?exii`iANQM%ANPH9SHZTwNL239CIrF zatvwNA{crsv;U_P`+wGaxDeJU>a#0|b8bD@@O+rgu*HE0H*SVU)L95Co}9|lr<|qm zTPnl<{joCeTPwpq@#s%_!wH;zE{?S68VV-EYJI)hi9@D*hrKc;}EV(q}NWa zjaob9#(fcCXtqi<=!=RId%_`{D>V>~ry|%MK1J-oX|RRWE5K4%y>cQ{HqFL|t^X7F zVDMuuzeTl%2v_1=A$tk{%d+PKKggmlx1rx#`UhO1XQ2zqvaE8Vz!!Xj@!j)=;j2vf ztlpU4edPUTpnZ+`|GeM*P3!6Y{&?PhJrXcibM$$Q7QXfKgym7KKTh`|l>t*@iE6g4~ZSBmq7_}eg#PbpjPU4uK$vh~7^xIII; z8~Gh4(WHVcw*n1I6Mb^!M z35Wxu?L|xEP-BN^>p0!_{iDvc=ledL3A;gmyKMRSI(GKwX1nTQk0BT~X-KcZ{zfDR zPvM{Ra(s?Q52xuLqayx?9&(!HQRR*D`2LM6&y`u7_t)FH3_c}{(+c^_)kgy8Jh$6l zQ8HGH)tzD}}k{utfmk$L_-boVgbW$5m4+(|0TmFAL$EORlQ;~#|BnZtM_M$;pA zQLD78?lb%C?3#zyv+Q~rDq%JSU3Jlho@&D7Q|o_$9*ow1kRG`8=ZOBkYrze!QX{j+ zME{5#{i7y&n*8pcCFM5M#BUSzNjvIO1*kqZy&g@W&&=sT?#mUkNpU3@|yR5-Sz~r#C|eAklWBh zw9nur1QC*{9o1eCvkPK!Kz|nX0(6yQA;S7}pU0xKykA>Lrsee$D;M0azf8$k-+T%M zAAO!5v1)FOunCm^#*S*V4&@e2WHSsVJZ)8u?*9w{5s#?*tFSZB>@l9~cxe6`R1#rc z0nFtJ^Z&yPoIveqz73x#QSGmsUsCO>tZHw)S2Kiijy1*{5263Fd6lX>`%^rL!6-1r1*W(JQ+@`^ zT+}s|IA7i_SuyAOE3*DomFhnO@@cF3{mND4win94VaNl^2rGr$MN=NCp?RuB)nB76 z!3(v*rL@j8ssZcWSF${O*jb4mmr~-iO!}9~Oe*a(!ZBUKG`@%}3>8IF^z66LbzJ&0 z_|Y03F5%HtqR$Ysx892=MM+Pg-uV|(2AZ5eGqWc3|F9zEQ$n}r_=mg$ZfQZhgNfhZ zsKnqaEsb}BzZT?@M3j^3X}S92O0;DqE#!h?*)K+cRiprZhfd8Xl&#q-#S)2fWoq;O z^B}<(Jej^9wo+U5msUD^f;Za|*g-U`ukZvxD8BdGTWsoWEO~sHc=<9a<;auulr~WEc#b#vFSG|tvRxA7h>%!@tzeN4{2QROHPM=Rgf1yulH6e@J33U$nL7OPxl(mX__)$QLRAr= z)AMu7oT<8eZd57#r6LYpJ3UqJoHJEd&55e=18y*kT-j36cx)*ARdd8WN9BFZTHe+XvK;!Y_!ItCRvJSP#~gy$D{`El1v$2n zW+TIKF3X7BI0s4$Ip*@s(PVD__7(a$6h*eUe0y0On}4|V(l~b09O#sEQ1vn9;&INb zLc&>{{f8X?Q2m&%TtDT7ijD;pbJcxqs22GU7#UG6zNf$^VPsDQUwR*C)CrFWgzq@+ zr4fsZDu(Y{meB75BIbLE z$D`>B85g5GPX~>~jbpwp$IdJHAHWi8dNsjh7G=eaF%*th&*DU{8hzLEiqksBYr0dt z;+d#6?PqQkvDONV=X zGxMT2-0KFs6MI9I9_~f?AeV>>yg7$^-FXRzdl4&UbGnyn@?~0qkEOy3bDi2r7e3SR z1d{9zQ4*fFY~0EzgOS8YI+zHX$y+!%WUf=Ny#UAmZ!BjnESP2eE1(=m zCT#5d^>CFoW%N)YIAY?zncJ$g#KGX`aLOX%X3}17{ap$Nu>P;=#xbQQtSV|HX@l#`uKcviR+W&Mz% zy!Jj$wEj`?O`T^Ko}JG1Lv&fM3hta62Vx^5;izqW;_|bKMpIgq5$&U`}~Bqux%(4OrhSFzpIP zg9pOIC3}4fmr^?&H@!4Hd=Y=B{ielV#2=@tVS4yt{(Q2lx-%B7zdbgL1=qvj8JOgB zJn)3GJ(>z95~J~y+B23+g-42bXz+%9oyx^0oIOFTw;tQX*$zQ2XsLCgd?xN+-WcR3 zHmvrff~nD@+8sU+K`~`>I33-+)y<UI7LP-6Bgum*Gj%XH5Vk{E^>#WYs;&CYoYCoAtvS1f#*&c%@O0O% zKGho>7^;Ar+Shr*LaV>boRRdyT-NvF8!BTx|Jo3i>_dfYH&|059~D>h<0HlUuw1|5 zntrnstOvI*>t>Bry0U*-aN7!Rr8}6WPqI)vJ_6i!7#{{0En}jQ1n<+})&-h@5u0y9vqlv4Psz(GrA$30eKQE0{vJj&k+Y zdVJjxM>hzEwiCCu9IgXk(0HIv{GtP~1b38Ty}->>l)s?>0iFslczf{a=?;7Z5(=ld z%#Er_X0nZg;`B1l`4oAPeRJ$@ay z$Rnx3Maw*}y@E0y*8*G%aV^3{`4;2CY@V_d*D_qoaXFBaj&7KT918%w2#|{bwGaI@XBTQy8(X`9&W}T)^GO>;xCQA5906B_@i~z-@qTOX!~dU z{S<%L3vG&WQ%=f7IS85{35p<;%Wl}(+&7s1;HMw@#_50l=~U!I>-Vbv+oyWrmu+(@ zPhG$TpYf?XaNUcvMqC=M9VUmlub4`$LZeRKs)kCd+^EHkT<-bJY4|s?o~G`eTSecL zim^NGdYQyaB{^}DM2qKzQ{VsC6bZKC`gi)5-&gT_eqKoMB=m~wRqAYjY8Mv zE!bdW?cwb(im-^0kQz*+MuOxF%8%^KvWs+3fzc*6=Js$RNuRKdjA9VdA69S0z6E5E zh0%#b4-f(X)~SQB(P#)yGn8P{atawXLJAsP7{AeIw|V_CWA@QPKEg(HwbA{C?y6i&=++=?ksr9TC_kq86xZ z=YBmIaue6q!htAj@8(cbqZJ}il285+68=$ry^)@d;PY8gm?2N$kV=l9bSE(4OwmZg zmNY%5ZL$UKR8>_uoermix+VQNor@RXderG$5)}9JOV`pXa7`BeF8d9$j|h8n>HQND z0tR3!3X8RKVB>mxJZq9dy{)2`h@dun{1C>X#>mgwAf<&u-l|7*v@!tX2dfoy(zJ1a zX?x2SshN_ScI{AOgL0@Q%A)d&>rG&XTlRtiXW5rXAgaio`)gKZrooAO_9dw_`N9X~!u3MS%n1$f0nE{a^7$ zO=0*P8%<&JC>lEjhle8vq7+qeE5-HU1}IKU*4KB3Y3htLax#UXau~Y22f>YD6+_v9 zgOol9_Jv}j{dU4Ih5`lj-Ln`Ih>;k@Ib^N&z*)ywcrZ3R97_`xjs+yuG@*t~AuW#K zi(A>jF#^)qb*8AUJzNh4*P>Z=g%1S##{djAE09$si%S!Zu~vPVx&j|SU#_YS6`E_M zsyfvwm#WU2uP#9P6{@;WU4-4I7B5kk;#pm$s>|tD{y7|1D2|4eisS8Uc^hrTL8c3k$(C&P(OzBl@8oIxd9#Wy^+!>c(!OBy3HlXp3t`@bzG4NYrW>v8 zfQ(lPG2x?sbcGc959HVw4Jwqw(DsJvfi-ADB#7o)@s5;|p{?9X+ivrLOA;7422fWx z%%z}!ZD2bV4JBgOhX9==iSg&`O6alYk~Gh*7%z2)H>EJ2xo5z1En(GMDmk!!FmhlN z<6rn{Zbhb|v8+@>>zQwRhGU2cLCT=+?!kr*U^8!}B{NrgU949tt5gHIp-btRR0a0* zN(u$bYK1i%$Y3RBg$ke;?QS78ILVFD6B|s~P;i`xbmSZ{Ky}RYXVjEui|vG&@@;yl zaI!}Q3%5aqoADPSn*RFw9(rhMYHQy^QzM+wz`u~w7tC4|Z zIAO4ydc6@u$EXBWG9t_**=i}dLL#H#LkI!K#Z*c-bUplK)25Dyi3cHZBs|pFlHnAo z4vI;M$W*Zk0*3ZYezhOwSB#}tayP%*o?FNYrUV9tV54RH{l+bBYs)!|p6seCD5BQ1 zQbqOv&@gK(Hdd6CAd|mE+$f+x`h=EC0aaRogv~UzBAs1S!@{0gXNLH!W-D%aS(<9Z zCfUcgjr&ZWpMjP2-p{MgaX z80A8rMmfHzBCrTGnvB8WNk-sW!-+ME;C#Kk0hPQaGjAx8G_%7Oi>YI=QMfwEA;N_> z@UKS0P3pd}P1qAri7K7bk7;P{IT*9mj>MANI;QTsn<%?`G?Kb=yK=X3+uZ4ymY7)-`X*>7H|bUN zMz%QJ!dXtRVjg3+o;!Ls&!~k||7Z$|$5J`ypzhv8Fq#}8eld?oz05w3ZzP_YWjTg> z#6G*BG@E5~y?k(Ac>Yq;60Md>Bt2%TJP~cTqVaIhS*A%9x{Y#a*Z~2T&KrCH*3;$t zmgV=F^MBGVP1DQFA>gI=vfiOcQjPP5R*5j=6w3tGw9Rypa>6PbpwGWyB+>XMg}8N4<0mDSFo<9MGa79 z0romXfF+nbD0Cjs043=fPmi~~9dVstB8a);1bM&qOwq8&a4;H6t3$Y;g+ylZU}%wv znUx)mX*Fkg_dvKR*gF^0n8>VhjNs5PtzikN{bMS|_HJb;d>C%nKx8C1T!%54yukvc zk{Tt#aTp2^&4Xf8&STmMtGKeIsP;nKidjlBg>q;WOR&E`5k3@Q#C0kajwYbeh!=Mq zR!72c9;hlqv0+5CV`*#wNVSx#12AVoS`_A*R;^eR!L{|7tz;z!2dS0dg}@BLlF5wJ zBq8kM3)$#K8i8Oug`FQaD(<%MVX+_pBE)P`*KS1s$87PnTa$=@*6--{d3M&r%@J+| zJOiwtrhd)CD4@Z}BvD^1F7Q1${)p(?Z{qq8t`Fn-2(FLf`WUVWT)&0u0bC!)bqv>U z+`sNAJ-RfeG%6m;QB*c590bG zTo2*;V_Xm8`Vy`$<2r%s5nNxv^;KMdg6mOSe~RldTz`h^aa>=+^>tj|z;zPWf5Y|X zxc&myDO}&g^_RH5h3g4ie}(I>aXpC(Yxo?KxSq!K-*KIug?e}kTU&R5e|LH;8 z1d4}to*l*_9Ew1)?t|(LAvA)?Tg;P;DQ-*i7_J44Rxv6tHIkuJFcTn^7uFwD(q72f z8;c{h4dF3}n-pd{?&@Esg!yIw zHz>LGt`KgbcjG3!PN8v4?OlWGl>RUwIe?p_T1&&7WSo{X63(G@$`JBl*I#~&tW$;u z0sDS>!lZk65Eg!&a)?kLT&E1uyWw@pKmxxb1d|A-MiWsY3@P**!EbDx5<(=T_O3W? z1~%j7Zo1imn*`l##Z8iK-hrFcIwcCm*WNXX++$JP9HLtF<0g%gf|PO?DI@qD1IQ@7 zxd#c!0kxL?p=#^{ZK&H_cdSzm(C?k=lmz|W#cwyRQxJ`-y=y-|H33Ontz{md_H4Ld zy@zFwn>zha-&Wig`Dik5B-eQISen;`(x{jm9b+M3!5G(VZF@}9Ih)^UOmsprI*{y_^{04T|-tp#*033@_k@#0^xp8@g{0!+8<1 zp$y~((wn;lz->2>!$DCT%-mm66epo{m-o~}KSeMZLH0|&rtL5V%TA_+ESTb_fdsfK zTZ)$a7RRHxajmeT8J-pqMe!)si&%Ir5ed)dLW}V7V$v3HteOMYqwwx-Kjy^53q&$@ zcenTYEI}xS2xGnGD7kW=sK@8qDZ;|jL3z8qGXr(t;32iy)9Wb@%0mYp_;n)SXec_A zYB6`aimwx(c@Wp$C>g2n+6;RNDOOS69t4{ppE&CR0~NTzk*Meoun*V@+nhlVIHRK@ z{b)X3udwB?q8cIJa&Ik;_C<6+2 z;=3a@xt{al_)^>2R10zGzTE zq#dQL@Db<@+oX3XtX2GMKr2dBDPkKDVn%m%OqdbYhIcF+oTjm?noSS4dCR6P?@$N$ z)3y;>s9DnLLO4Y-`85bRCdk(i!Y03p!^naC9!nNY^$ZW)I1PohEKOINU(=D*nn@ww zxA|scD5FAwl+nl>GBs2(7vi@Uf8-GB^no=4BV%KdU7O@Mo3%nm(6!Yjy};VdTbNZk1Ra{zP9=mMO>T;y(iNU`?tMyzL@K6BVj zM`X&rM+al7D0~wwm4|_|E$OqBj>RFi<>f{XFm04rW=|EtN+d6>G?w=sy0@cV`e_FA@Bb%o{ep+ zv!Qq{gng9g{E{yseIgLg1vJ|sKiFyzhq;`64V!Jb^oG|JCpIL{>b06n!H#GR;^!UW zCr&@xbQM3Y^tIxqI=xcdsOiOG6R&FBbBWu6h2k}O<|69i|D$r0Is{cXd4S2nFVlrz zJ7Ky(m~IfJ8-%5QUBa;8crS*HdHWNq_HvhR7N7+)@1l*fm1=zrpn2nLrCQUMz+8uCLttbb$A@g^+mNg+Bw5S(-}e<% z7VvYQ*;ooxdb`bIYa#3bJ;W=}UR{pzBjpnB*HyBO2K8>c z0x+nDT_hk_%#}G<0#&hF0ydJWUSk?b75$ySv;wePh^dFg`RW|1JYK1DqHW4`LSl27 z^sF$4C2hk-lG}zd6}Qb}Zvdgra^xH-muS!k(@N$(?k1vNbliX2`KYPK8dh|66KJ4@RU`-knytVe`Ba} z|DVsLq05@q$E1FJY@(p8#>)a(oCOjvW41c{p67-v``; zL&^{1n1JO2j(u~*AuS2x(0~Kg`7E6%%jynZa8Z%cJ~6lo@TRjb?_uB?^*3RCqxYF9V zR@U)~^*vYYbdiC2PC+=KP~2mEKl zA{sY7GyWqpA+Kdhb+?IXenmDw7te&v9AQw$SV0}bA~n zxg9NErHAHc9nH;n_!D|)d)Cp`hKI-PU|+X`eZvm+s2%JvJJ_F@U?11^EkEwr0i@45 ze7<|KQ^&o{$D7+4+x!b&7+G*AfpZkUhziVhct2g`UiP)L9&hE^{fuw9Zm;ky*X(r* z;1?FaFD-yqEr9>90A8~IifZ$*qS}0+3Zbn_sZGw~%S{oJsKuzEj@w!LIXi2A*JLfg z&sqd$Tfb+KoNfKSAUfMhO$xFX9{2d}`D2UTkE{+JI`k@hzc0;+-s}!3TscomZpxeU z`UJfvTYSfTJDzpy*ny-!rw1WeUf*BPgAgpQ??Q_YO~i27){FeWaM{)=eqgw4tDv`q z=xrvkgtVOe3*`fcnnCYZ=0tB!^9h3O%ze<#+=uMU{bM_Gb1kh861gl<+18^itq;+o zBzU}~^^fV%MluS!=ZhAL7v1@V&O^Tim-A2OMzUYmr*w`Ov`Ko*FCN_CY~Q=u3Ik?Km$W;fa8MGwkrP#b}OK_l5h%%-fDv%#erZ)}OM9ZR*8VELW+QYi(-_t*BH|^8Y^1oY#HH3$?xX^S>saUsuTT2B>%Wmh_}xyz@39CMz##t{PV&d~pc^*S zb=PnCf?a`SS3k3+<1|#@`>22|H%SpmouVthcEuK(Kw178Hc>M4sx_Fnv81fZB2UK* zz8Yb3y4Mz{)dZ!@S7!+5PDd)qW=;9py2@uGnkSmBPsg_5LgrU7ZHLa1mwaGvv_68q zj`13cbw}y0W+UmEUo&`d;CdHuNcs9Np8I30;~CuHyamM%SF&o#O_2xxnTtEn z5nb6flDw79<+CS>*432HAAarubvCylKjf*L0)2=hLjr)h0&a8>n3SNFY`JEXCNR#- z!{}VjQOQcEgL43l23MZLcQ3edV@s8Zc2+*xa{{frwr#n3i74f*QeEj9LG98FnkS+u zbbejbYNepOBr-P=P3-kgTnO2WiMgTS(wy*RIbl*6tZo*RulZp}x&4lHA8F`V3yMBD z0*YX+dG`~)#l#Wqrk>VauhlnnV+_Z-V_GcU9p5Y^0`;4vKm-1wn{gi?<`+YG@n`$R zo z@vonEf4t-7l2na9Yv=6GI+3FQ#=@dV!tYq=$6kz?7cMY{j?D40zw0$C-Y8=R^u9YsLcFIwqy|w+ea9bt znje7~9603D1`yc8fnWHvlKKN`4F%+o8pTuY_z`rnFN~n1kU}S;s*eJ>qstdk=nPf0 zheDk`4B%Jsb_fIbF?@I?cdYZxYeMoqN^bPkHa#Y(@6h`dzS_2^dW2r3G@Or9MwsQweS!zpFBUJD&845T|r} z$D2_P6I_tsYWZEQBe))~2ysfs?a;Hn3XtK>9bM{6$dyY(>DGHk1L!*7NU`2KW`L46 zDTPW0CDwb#M5G*bq*(7A$Dm5xGXkot=~yiX)`C!{T7`T&zU9%>07cB}RGTS6M?@t> z)OM;GMSKua zuB>*1gpNk@yP*XrFHig71CfI~6YG&+G!%(;Z1LmC>CujjkYj6+!Ei@Pm3qmU9`BY* z{7@xt7y(|IbjC|YS4TVgeW@|&Xm#Y)?r5j3+%$qbkOTB`&EGIv-3=m;*DhabY&sfh zs9(gHwT&P%!_fijLCAlo7`6*Uo{7HV!Mcb(h8SKAI~8Q8VYF+}1OW-+P1O)=y5c*aT# zVPh4{R8Ojuz)(CZ6QvYrz3nGeVA;tRm>GG2d0k)}77>ZPKHtJ{&FTVm`qapa7*59v zpi~sf9oAW}T5-&$E=FIGLp~X682I~q78KYrizOk}uK(_eEiU`wJxY*lhh31M>DGkS z3X^UNM9$G?k!z_f2`|`dOTrn~_Wl~WN(@n5&4*xZH^I``shC`@1$o)2*I>}zKF#Cp z8=svj_sfcO9GjV%oUKe~*~)By{0_HRR&Xv!h5C_a59?R!^O!LwwK2kR|0@Mq|PYFVv!k0*Jft}H|)Gb;}| zDyi)$%T7sb!?Jq~)Vu5yITE3_KCdkI?7=;UGAifK27uvjrGG8>r|*Gt)REplc;Fag zRFBq3Mg|Li*b@LDRPnEXW7E~8;2^=R1RHckaj-s(%Ivcqa+Hn zCPLF%BmUI&NP9x0sq|vT$5S=BvV_>qRdcdFwJL;bG|!-GG`C)i|21208c6MUQQ#DqH~z4rIM0Od09 zoI9gTqtGbcpatRlvP2@#VEW0m$FW80ginke{*DG zpSM{)mOg}`cPskQiiEaec|u#hG7(y_QXQ)`VDkay|5YKu?&Kxajma~$d!*e0U_yVB zBsTz0fXzxxJ|-F2tdc`3`XrS$52ki=Sqtxk`==PHjjL`MfKrn3I+v-a0_#PFU+I%I ze&}4(Ey+rD^l%^c8_<=prA~1P?J)TP!f3^=jg~VAI|n)^e8aE9)CjmU(tbSkJggz5 zcMI*?pDMbIx{^y0J=mPX4QXxk>*NP)0X-2|=v3%*f?M!?6@HgmBNK}u`uZYnDqH_E z7z?p_*|8?+$SUv>Oj4vHeyK6Z-j|aQ+C`x&%!hZ)*e`KOeGDmA=*{TG#xq^70I3Xsh5Qu@x6 zmeI8kSRnX{S9Mm(DuwuReOYmot#)0_rzs9%6H z32-L5iZ#1n@q)7lbma-a*n>FZ$yO&) zmv6^^UHOeGNmnNR1fX$!r8&u9?0)_{>RuNi!5sLRY<2F80nci>u5c0g>33p6zv08A zq|n%4&eOmRSez}>%HY>sss&r9F^KuloFcO`P+@Rpk-Wb=v1hc@yg!iW^R~-9a;xz~ z4v_z-mw~acuS(s5a=JF5Px@q`#BggRD|q_knGty9V&uX{bcI*rmQFLb@~ho z-s{9y+GU~9LK7AZpcYcCLTz$uWP#DUP7eiJ7h*clJo_a`*8P6^SBA|YAP}4ViBMT< z1RL^5?(sL`uNi+WiEfe4!tdpkyiL_<9_y-5ze-PIDzxS;@=;fNC`DIx^r0j9&4~k} zY$hz|Swxzh$oe>A206TG3hh2&_#Qrl$+$UjKi3ZCo_7{y0LJs1)5PT5Qg}pIdISeXz*U8rXNv~oz?cP3U zxn@QjJ3!OW_i|!UuilHi4oKxqWNKSS))>zF7_hXxMT~(!HMt?QxMOO2J+xj~G04`H zcTOUY!y*rV>SMZc{3P&y(7-hKQx~Ecsdb53Zr+38P*}bOt2Cja4j^;8K~vMo$&lK` z?N-UE=!bsh1g+*S5gTXauU?=lR|CZC3A)lj2n=Qc5x0cr6Feszz|?osxgYvKlVaZ> zLy@&~Bj~Z{>C&PD{&teZF|D&fTNMw*;_7&uqbu0i``pDRn1jM1N8Qv5!z$0Tb4lZCAwLD`#{ zEA=9S!0ccIewcQO1p4zs)9BpKDI*Dk(I+h;YZI;06FbFsK4x3{#V1$dV+M^u4nwSx zziMx#WVC`)YB2Z3_f%!HCcGaV22;xJhcP?I>OLysBtNoZS5*w=-*MNWoOF&ddrewx zCAlKX_NbF}Wy14zmrJYH+o@hk{h%wg*-rISYOO1^#ZC=S>J_e3<0~#1KuzeW&1v;o zg4dOgI>2@slC{3Z1PUHKu1b$c2X=s7&o>B>Pz#dzBC!t)FB zlfN}Gzt7t$_o&x{a9LZ7Z^BxV{?zOPEsdBon!)bU|3ERVdSgGP5nbL_Nu~^}J9t2A z&5byNA&2*K(VYX zj2ohE4Bx&Y#8?;yT^I!`n>NT&Mh%&pJvR%A#}0{M%*2p#0;6I`3}fy!Fbs)xtRKCa zEO8Cn;$5%CVb$}(t*uHO`w?im9LDmoq{rK^E!OpF49R0S`NUk}>!f038tuavJ*9G= z?2#X?z+$~Vt8jKR4LXyS(LqQP zi9wMnB}GK7&IF>l01>l3Wa|)Fr5g5@R1TGk8=ycgJ^qHB7JFCScVeg26s!K>gsw~m zo6RviM6>U9#5j$;xvEmKqHB45rcE|P-iYO+gzv{q`6xkp3i(J;wtOTd){u|% znkOG;TWwbx6wMVZBonou)j$_n4dlkiX%id6&Gm0#kepYHqpeb2M*}Ubkx~a z*{alIGL)wx7&weTSC+XDj(eh9J$wwa6$$13ln|*eBzmgizgHms-3RPbbvvJaD>g#bVQo$)p1$@=DiOgb`oZ8-4rQMD;4N7$mTAFeQH1OR#!&HPRNLz~TtIcI8HG zKDRb;te-Z(RwXO}7(yqVVugf)xh4VEg4AS7teUaphEN+=b4~s6z#1N2m%`MAK2hsg zo5UW)2Y(@X<#A2I)G2xl zLSh9e?Dxi*ag3@(E!bZY>Ofs6x*}O_pud$00xg=4W~9FW6ui!<_0vmPY|b2dnR7V@ z5&x3U0GMt9?4g)Bubc6&a8P2bhd2_MoG@R0=$o@14xmJ+TsG$%pHW>$P1%%y%ooiSAf! zxalrQ6mgf7UxWp;!vNYb2vA#6Jq;CVOBTG>9h2@+>S`;k=h7+ zqq!zgn&sUik|3hB(Y9p*VwoPzhj3OU3O`cOGFn*eByphCskiboqNe#7RW*Lrt2_AF zpnjU4F}0hXof_kXHAbt`7z1TNhJli;9&-ISzr{-C}rCELez*<;BHvg1U%S(ea^x?$eZ9}kX=;bm#W+JO z6I90_If2#|Pe7>SC}umiW+GD}60ppYSixiSldc>hhWqulxDGexBdYdIROg~W&$^&P z)5r`RNCrrglROq1DxFzp)X^wjF!xa`PQ2oKf5|JE@YH1fHA`0GfB*{;{0e7oBcgb( zOg&dL2}j6%Wim4#i=SQOmuO~0OUL}jdk@N@ z`<7YVk!-H8DNxsDn=9EAxQ7QUkiF6*VFxAEm(zk)8u?yAx>c#!D?Q%5zHtWNC<9Qo z0ISqr5(Lid<7GUuN}jn_s<1+8V6S9iBaMq06nm2kyLqoP-p*_JUTKUSf+NgmJ9Oz@ zX{;TZu~#a$L!ii}0Tg*kjWmzEB8Q?>C3#p>uoEVESk!D?I}pkS)H1S7Gi=(byVZ>v zi*=*FN#8Igm`ZF0p^Xj0M+k-w+6A}{5?rW&uy5#alN*jS%pIje$vX1HGvwXHeef5S z4WVZ^c1@uJFN({spN95VW~bm-N03kMKdC5HBme`9FyG`>Yt@AV7N@TTr|lV))?rm@ z-PN>%Wr)Qt7XX%TG!Q&eD-Qz9bIA|sN-tpcVd`_7rb52V=6o-5Bs@rW#`6NN3SUeE zkB(j;U~_-cJbZ$TT4F;uwXX+Ss85m?-hAco24+4rwk3k_L=$w^{U$SXVwNj9wK44XXkB-U46waObl?$@$mxJ8^LXiHx#RHcQ$`&@T14LAQMm4T@ z>{g!!zdFR3) zJ%Kp0SW}DsCBq25dAHQMWhv?eiqH5qW8Uh467H)H~;&_LFxq_8B8 z*RF;ZE8eX%4mKvpA3Y-ea*rH)|j#FL=zm&_+!5&GO-Q*o|^M5%C~orgxJMs~)c!2CW`JjG%gkVjrIy_c)ovuvz_!hISqr&<#yg-pVeoJmXqy&!OqG8^C@JUGDiA<|81Gk=GY z98BL1{tjBN09s)%0F!QNt-mreM}u2@ABD~^Sn-*%AplJJGJj=G+%3B?nX;&9TW>7U zU6KY?FkYge%Vk(V9K;avCuc$PbF9ci5H2x=2DucJ%t7u%P=A)eSP?my`H+eg|1bHm za){=}#B_&@A6%jgk?JC>a9OqGuEK<{DO!D6=X3R0T^>pGEd-in&dp6*ix-RGV#N=yyN2AhF~ReDg5K8T zoP64va^@brsn8dUl!mvmD;!Fu^NW;I$ZF4Y;Ov z-6XwhlVlFbV)J`Cc0Aymu9FwIi4fz_nC!cAfYlekdl-O(9+pEc>;VOaIbDPXis&L@2<7%DcNY=V74IUPK4=#a5QBFSb1)CPi&zNxUBq3h zf?Y(AG*}lA)gH8qP&tN1H*^I$PAS*ecebeyg4Z8Z^^3Y>0vCV|%v6C6|%_>y6Q=MYm+MmRd8G1}XTazaR$w&I)? zlU#95i}a%T=Wv>!2j%o==5*>GiYVFs)Y?!RI80#m@znC7V4+YOI7?tl;wgOQRr?tss7%Oa)yDu)^j6yCZ2HF2&>eCFmCEf^?y1ohq`hVg&Qy!*h)D!j=3Et(Bq_v zYmvaJ8yi_oBR6HjKtXZ>QMwBNrBVzaGr*ID09FwI9k~Yw$zue-p}4#cyRG8j^f(tP z?DnbYoL^d^+oD1*+oobV`XGU@9yiGc1Nald8^xE+_L+yqc^D(GQ)?bfq1}{wg_*mq z>~)l`EC1~Zec%-qL2R?` zh6T}pf5`=}n%Nw1MZ;o^&hawh3`z`x^))b9zsr!O!TN%Oc8v;AeKn`&QN7d^O;j&O zOmV6+Zg_v{Tzq(pI2U(`R0Fq}Y zq~GNuZ66$m8G*0g3n8u_oWeKraWVmLaS+Z}OvY#J1+YhUdEb zk*EP|jt*e;1$_UQPZe1f#p=_juhsr8%c3CfjC_0{4ET!79IFhW6j~sQtj;Q$RRIIf z;8{(}&&udi_Ox2q;^SU+pQIr~)JOEuy>1h+VexT56flHv>aZ<71a0X4_{eJ*HFqrU zY|Dtd;(srDgNyd*`LtVlo4H;x*H3=YML)$(kn*EAx~U9)9vKcEWN%IQ$Y4kawE~}R z6v*4olU$S&a6l%{)dD$rojJ|Q&q*(^NY(A0HVl5-MibMHGpNyoj@KgVYpTL*!2fj% z&r852Tivfxw_}BZql;a!yrdHg%}*jBf1yc841|RyR--={90-d|0R3rDAY;LK?yEyC zIInXoIBx_vCUjmMr=pwymrblZzwAs9OH`aojf6eU1ccu}cz8?HM1VhYgfG>$xVB2p z?DoVii}{n6xXsPt`~d8~%1srSnCS6ANlu-)(}AQuM;Rn{<0FWqSPUN#->=bkYXwU2 z45t*h!lh1kl;RPk?DplAfU^hT5(Xdqmn5!;{~(laek-xwO0Bn1*4t?7Z4AAcE;H!! z2lFllH~PPZ6D%_|yYVdry#(=+NeYW8N0cA}{2OdcspHM8a5T_AfMh+_!fgS><7C?L=YKExN1X|rPo17AX-uTf%ar2(*_l!Je@3PZ|4+eL zzOle=uPj%~I2y0J{J(JcsVDC4c-v#Ojj<)`QKbw{wBQxCvER`^5h1xj9fierD(<5iBX6)% z5ej)?bOKP=0gr>Z5F)1PvhzPWV(&-2)NbOtM(uDvI+=Xjv)jXmfy|zMH~`7P6yZcl%E3tX84;K5_R!ZKd~TLMjVUv`6ldLV?wQ@b#zb{?se118 zMsi&3bP$>X;h%Jd+iK&)+C*)e+Sr7|yBvwS@~TxUs>vN5+s1>HrVBFgYt$dUg{Z&d zKi8kJj03@lk5}$KLEMBJ@Wns^2uu1Z^#pkPIl=f zj$Vk*!W3)TSF=lUCBFFXie=VzRmb?mDTeB*CK&%HfG=TwL(Dy<-u-VL@1scJWU^9= zGnZ(ZI-hlgCjHj#34S_vin;|m8B+kqVRO2}@F}9Naz&Hja|vSXLN#(R z{`wA>YX!XKf`_?QM0dHOg}HVI;tWbGb8W#%5g)uPrm?zR4SW|Ljc53A5MCkb1QDhO zhbK~4R}cQ4>)M+w*VRMD`}fQ7mE1PAo$uOy8`hls zeO)tZqWX|+&Mlv)93o%EQ6i$USfNs->C7UWl_kDuB3$uh*adcGV)A;Jp)G9s$gRJ3 zM!BdveGCRV(zqvOXj0GG<|5Toh*XMvZ%MsvY%Zw}4^8SbZc_0nUp`kU^1UVX=VNk7 z{m$T|Mmp@)5`9MW_V7;}a z;%t5zIT?l)T2_DWt};6XYoTIAn0a+^miVw#c9+S5taq&=vuy?uZHjm?h%_^zvE=2k zH02*@qWe*?lFwh04A`>dTV*MnI$=tdSIX@7&rlTuIzIgD@AZuqO{yzT70cEZNmB(P zj^OL>ea4V3xV^bJB9(5UTar3Jrm6nkPZ$}{5cRHE@FDER{G?b;UDXDQ_`y77L4x-B zw%=|Q1P?iaFkZ0y-B}np6JjApjo$CGGjTWiRj%$(DS8d(7dEpEAL36Z0(9#@fA7Pi zOol#RtTrZHnYL>!3bqub;HM={+FTUeX;jQjL5)Sh&xfYqR*QmTI6&7F^==+qQIrC% zIc=DeSj2D=&EsUp*GdIDx{7peyH|Qj&7RWV`&Nnl{=rgRd8t@MZ1hKofkvmEX%vdt zl83vU*-i&knE_?zv7p2ldUn)@1Oj*YUz8Y4%cIK7)a=(Nocr~+Oep2$p{Xu+RCKRD zpWgeaQOfBArT*TPC8^5ob&br~Lin5K16XJF zm@38g{B+i0Gn-Y`ke~^{)k3Hw@X(l}dz`9FSEEcvv^PCER#wLAHN>kKSOBG_I&jpV zATP=QRj^f4`-}t9js`PNB**KtF6sRbA2OaTI488Z%lN^P21kw=s-6(?_Yg3z$MDgY zag?%EcQF0yC+ODu{@%&~v~np?Y>EaXwwRIb4CqD)Zjl0mF3F%9x=P~b$JOOH`2pg} z(72{jIS-zoa_)?$=AfKhRg^^~$~|&|TwFM(vk+XuWB|u|emwb2TJZPx{sd>-(Xq_t zH76ELT@_UFRDBg`VaIx}m8`7F??g)S^&#=dgSEG6C_M92UC_7sFZlO|6RCfZ2 z;(jXZLVwMHZt;mYQKjDaSyHg(KvJTc8()kzA!IE{$T{j!vij;+ofyj$BWy0utJLQ? zm#wZ`?mo#}e(cD_%=tipxDy59s@|~cVdBm&5ZCUA!_}tUod#jFb;F6+=xTZE)Dx-y#L*;M{R62NLM5{L`Pi6{U*7tw6RD>{ zrSjGb2U0(v)B3jFaUwPa$NZ_EMtVTrde@26J)s&|{ioDd_^`gM*PlrJbLw-JaCgin z$|ImC!%~*`mEnm@a4zSt92zf6X^z#E*MURd|C&6X^9Q>H;U(w@nKLKMbBS+I(Z&vBD$id;#2J)Wb`ndY zQV)I_W6ZR2aHyaSWw^EzrJ5-)Myt8rQ7b>ghjpL$1f6|NYHbEpF|@l{p%ykFHi6i5 zJX9SgQ$-U3=AvufXkNaNfp-O>Ax^qvYq^+?+0_~IDK23mFQ?Q`qgYXvUB%}=X+V$` z=YlA>JQ~ls9K)rq6Xl+e+Z3yC7!n}^(>ukA)u8jr>j%228VkY$pA$N-QOS=^!@6oZ zSY)>>G{UP`BV6W+-i}y9Bc!thRWDd)nHsM;$P>OLvPo`MQlG`*l$Q8iI452^d@T&u zbn(a?P~PY$_Rr(V-^S$G3F_bkfn26!=mtqxm_no}6whZ6#%p`V1 z#i!~S$$Z1L-sI?Gox)|hIs}Z*NDTLh5%A_%nbDB&vnZp^nYzW;Ff|;`b;Wa}$T-O~ zy(kR2LpFv#b(XI9k&itZ(_)ZjJnIvwgxP1bR^!CqY(O0MYd~#ZZ&ul&YNjioVtrXVOF*td z=}O5T?V_RYIYR#XJY}r17<&yOh#Q9)#+v@lFyM6M7FPnzqe;ZrMe{tWegX5Snbo~6 z4Asu+epdqKQf#q2h&YQTV>Z1Ge%HK7m`LBZvMhnE-BEZ(W@66m!HcYc1>qh)G2%FM z)gSs;{q1(ZF8sO8D`y@w3m&)&hEJf~H!v!Z!9UukIQZ|#i?YQIw(mZv3<*-%LAjPR zVLA-rDC6uM9?o7T&i%u~x!-})wrn`0wJozLn>8GSS#*zX z=0wJG-?h~Jo_6(t)JL|9=s&umb>;Lwp{szcg3r(rnXRN58=J#ck9M|Fq?(&Tlc7K%oxPV^AW-0kOAqiqNOuHJrjKI# zd%sE<9mV2Xl!a5$N08AjQh+Ov!M7b5G>#wx73N=cEHc=Ll~a;lHXx_eQeC-j1i5@< zoPj)pkm0wn8pw8cGi(UXkA zrx0_CLYGpA*-4=p6k>i;=u8T6gHR|yA*u*FyO7P(WhArPbevJ?5VOD)!UrjofxZ|< z;L$L<^vZN*}KLen)$+ z7O-Um`vro1+yTptW9NIWfCW$bdp8nnw*!`&j$yI!ceM9iiFpPUslT_5V4EGV+^}}O zKM}BOt3H)r=Q?1y*%=mQ)T6y$CD=*&ILi08*i`5(2W(2!6z&jqo?8SQ=9~WB9}(Ok z32-Ykg}aIkceQ}S*}$kj!97lJ7ibE1BEWg*KGvhX=LUDjB( zN{eb!$rYE8S0;@t9B%KE$Wv3e7@Q$OvU&zgv*gHlIz5qF#pjXMwSA)GO=|wS6K3mD84YmT;FSO>f(P1{SB|<;~zED&$8iSh7r{9kBl1mJ=ptwu@FI z@x#&s+w!7$`EgK&QfEkz*(bo>pwj{`41< zI4XnXNkI&jDPJax-7XB4Dg`lEva}M$?Jf+KEd?=HzFbNe*SauR!sKGqdSvx1Y*zBP zAQ9Z(JJ1i*vt6hxaB@+Z%`9}DBaFlSP8P7($;EIN#xiIpp+4e5Whs=4%7w8cx}Gq$ zxiF~b6>6bLq?Gjc&LoV*z(}=H>#$TRkpCn)W3|6mBIt7ndLBWuY|4eEikx9_jRjTq zIM9A~JYP^*G9_=^k9)@+_n!o2m3q$o8296iF+*b@krOh<_DILQ$2IQF(ErkLPkOg8 z=r_M>vSs*#zV=^3TBd%b!i+hB4^xBaCNV7zGD?t(OL* z*BFpI`Zp44w+pr4=ywk<76Y>gb(srwxMJYgF`%CALLEX3JVh9#t>rQJi?d{jA4m^KfeR$GYEP_V&F%FcIcgaF_5r!*3B#0{CsoXs)sP- zUGpC@c=J!zN`a*Sf1+~tK|f#mw96yrR3RTuRw4|h3Bb6(v$oI#Of8|1CSWetgeG8y zhCjbk#lolQYT$6!U?WN;f|!mP&;?^Jp4E zqn&fw^tZv)E3ABIycX=t?$O7S+mW|`I?;t%xL3OdE>CX%MyS7d%cP!4E!L~gG~}Dm zwr?fGFI$LJ>J_Lwp%<>NCvOhFj54^+%Aja{F|Ar9b8WqZSmi=2TwCnJo{{kxJm%Uy zeH5s_If|}7f3TXK?yBidLVARdD$Sb>{qm|K{f1g@e!m0V8D?Q=QFXgId)rGeV6x2` z8&&PXcujY6PbJkJH}{@(BJYO#P4bY)5^lT=1qlHSEYNlg@S*atFV;B4ikTr!)))3{WH%+X0wx_p$_)p3YV zn|X}P$Yx|FrH*CHacTuUtJH?#g#p~T$6Ir7bCDpEv=pkVAU zC`jZ{Krx`8)S!T$xfF~lL;**-DHtUvpsN-P#x zXN~a;;Q6NX%w+l7&zvEDn<|Gp-`;#?(lcb}#4EujcY6}G_*^!b98bqBi+h7oqqmC} zf+#^Qx(DVK*fqrk#<^=+>z;cpCa&=|?z=zP>-gwC&xEEf#^dl&+AJ2=&zGr=tNG|^ zPrjOuUo3`@?hFj<2U%C;;f_4oX-<}Bn)-~Lu=nOdcG~gI5-xa5-MJjQ*Tzo8z@BSu<$TD?|Ihe%>RO;E3pE%$_J%`?n9lrv+pj$VX z7yM#a{wdKkeGI8yzKZ&4Lg||T{oGW;<|%`EjWJqoDG2WIe?Dq zN$_nW96L?GsWg3F_Xxhs!yP4C=#CQgOSqy${bB0$YFsgh%O8+tU3ZwABsl6xjxq7D zKL()~uk|%9tlT?lqAKmiYO!x3HoWC{YU17!cg)@i=v4%P+bcCWwpT(jn87=pAJ{8R zutQ|=yzZz!ChtN%(_Bdt`w{)9GB5y(6s4Bzis2q_lu<3q4C?!3}ku$kWpeX70g z+t@Al+)9scw~^l*K3)zr`8>@FCj&@p!4CT~fx0*AcvBH+)oS3+_^hWQQw#O4WFpred79PHp(YRRcO08(+9na$=uFd(>7}|I-j2w-akoE_8c(E5 zXvT$q2=$b;P-h-o?YEGmZacOR2WHPm#-@709{^mqC zM=tz5BHzgY>Q|*4AZx$`@D+DhYp+Xa;kXu-yTj8LCz8kR2xqRoD$!kM#rz4)VY$IV zP-^XAt+P(KaIO#6pv?7!lV_#ICA6y+gA&|o8h+eg7kRVCUl+rt8QsVM_*OgPMrG_?3G|`W^T=GMi^!X-l$7wrzS$`wUC9`sq}T~ zxjrwxN9Bm7Nx0jUP_uCV6}Wv9a9I(YHl$LL0NG%o&&Lr{%KQ`&0}s=!xR|?@JfGS; zUN~?3GjaeIH5%t;HhNQuM{mWH;Grqu4YdWssk#+T(nieD-UG0gt8w%%zlu|YymHOB zbPJlNWywW-XST?@vOz3MTEgTbBFD8>`Q8$_$4C9^-ccfQWdawZ0sflj{p#7EL#~-* zWKau4xd$20kt>Bz$>nGzT~eH_r2B}f)DT!=c##lVeFJ7dbPOl8dqW*Q|4EXiYgsH=V>3^BaF-G zK(L@xSH4uBbiaC@m)WRgD_17`>ct$IblgiBU=`uY;9m=5z$7!lc9WSEINJMRkM^{` zfsL(36=+?SQ-%8s3W&LW^}B+Cy;8|?7Ifu%C69P3qspXi#*umj=i3YScLDNb?LK+7~AWTiQGS8@uC(j%Tu^c~0~&#GkUt{}a%;{~2M7`TwL zkhd+kX9D91^$B6TWGf@})al?@nhivPN17XfzrQ>I>4vZWd;G15ZZmb^p#uFP3bQ0IXas1Z zU5~g#-*`rm`(;J?Gp099R&S;3obks*e464lMZ-hgA>K*;wkG-}h&(JAq_%Q0KhzfS zP7lgGLBV|!IF0E%tl#OdWvcv*y(qu_#YX2R<7sY%8VcZoQfVZ4p8&BmbaZ$#01$}!88<4fH; zh*)S|&;um6w^8sQHbWu*O2oiDOx04aCosSI7DrC1=2PvvQFQ7QIE9t6psQ{q6@?;c z_VP9={}f`*sO`W_o@LCrqqU~(e%j)qM@UvXDR%=8HSmiXNTx^@LQ0#4;e<+~g9`bF zo<*0Gkxqa<+8nt$l&xt_j@lIf996eXZj79BBs&!sGNHFta&HBEj~|Fc9nu%RO1)Kx zQkJQdWaPK-VS`u2_;Ze5l}0)E!%PByPT|N&sj+@FC|-;3z+b{Vz$2dr(!@foXiy}C zU8%f(anZ&DabI#0XExr$HgVq{l(;U@KzQU6w|2si#9;;*l(DFQAC3Vk01O&e;m5&rMS>>nrmow3reW#eKDUX$ME}(q)r}E!g7p<%FG-1VUO9XeAqbhTj4xD62(^ILEN9Y7F>9czsZ*`cLAkF~hUo#Z z?)m`&t1>D>^kdC)n5BQ7K)uwjUO1NPtvAxv+t0>3TQB6nX)%6v2}3XwK5D;i5U&Jt z0lGIyVscOM9i|yd4viuA6pv(tV5>BQY3dXRmy}_>gA?YXmhME4Ndl@ z2?dy+;YeowPVwqwzJu&f2(t0WAsbC_`>3HwwbbuEZ+12;8HNo)I|u6Z(6rB-Sb+8j zM>6eKiq|5ve^StnM?US(mlmSkxYAq0CJlxJCcL>ab~sRn{KEi;ApTsx`ny8JOOv?Q zr%6+&@T(Peut+6t6P3UtzY^n0HJ?g`vxHF@myO2VXAF9c^%Q3N8&2`^-NZ*~Sp}2# zRm!yDtmeCs`_0JlvW&|TsQM%W!~4dCjq99eFr2j8Kt7G(B(EFpSd%oxKpbr%Rux1X zZz5KhaKXe8v4X84vZ55Cg)N6<3ej4YLyIUxOI!}cDMV}GAmN25L`!52&83he?at1i z1zwaE*HkTePvuy7zbs^+RARqE&o^Uz_N&)^g#`=%ye7bK#B0-TX@4l;Gzy0jdqzvy zYQ$P_bB&UKZN4H;^A#nTuPA>m*#wwKwh7P?hA<~!Nh|Lwk>x$7gW9<`nnH9`cfU=| z@v8~6vMqX>QCX}v|B)jn6~-zi$I+v|Z)E0B>-_5TMrKBj{*@gp(xbgXqrfA-M_=Z1 zOjjR;QJ~N$E!eTqOQrX_@_X2oog-zDd!)qekz&50r<$+mzve4?wfTy^j#qP}#0N7& zRH5DNK5N*JKEjbK^`qj|*}b4He8o@~{HfX9d6vIbW?F*eUF5w9ou`8nGI03#(*c#6B@aiG5;fjR<9=;CRPi0 zIJPp_ zxtTVOrqCpfeID3750HfS9zF!iq?kBxHx6t&^d*zoUfU5ix0En1(gu{67nAr3@oSDm z&D)BN3jf(K8BbGJf=f$q^5qKv*yraUSbMC(d^0WUHd0rm`c+~rXvTEz9UgJ+wRbkz1L2LFy#a8f#3zs2Ly=Jq-Gd*eElG0~DYcLIK*{3vPDFWG59j6{-C8H=}iIC zV1~_cVSP0ej`aR1m~q}YgNL*j|Bwn(3{e1!dSX)q`=j>mKIIi{U=dB50QzDpNP7kPV)LU$lMM9{4^=#Ce zg_p@AHFRtOSuludo#F?``=mfTd!^*Zqf4kV=yv3yqzhEI5DkReUQf8R5y})OS^-?5 zi(n7u!0s3X_6H5_d_P?Pw%?PDQva<=XlFrT>&xQslDP;bC*0u-s&A&T9}@CpYlzG# zU{pVPt%=H5{lKD}s<9u6|JTIxs!MxdHF~y+_$fMUEeJ0Hm>HgPP2xnv6qDhM;qr163=>Mo=i2olQ;+Uc$}Jrt1f? zGjQth++@X(>es2Tt-aBSYD$Y_*yUWuw{ZB?g~15Lc6Rf^0o&u;3x_ ziIuyWpGTz0mt3l~Y&C7+yAf`1Aaom~FSuf$+u*>JrFa$1dgX;DLS7brR6s9{1f{z` zf5D)|2!bS%9-*ZVij&YamFTQ<@HNAc-nVI>`crM#?1T~Dk?@})VL2r*#&ZvR;s(Na zQY2hXSuuv0@PJ6D5<={8Py~ww?_3%(dR6ngnx_@_@WGKl0R2h;VS^Hso0AMpPH=-F z&msszV}8?>I{~7f67sgv5y+1tfR9DtjzFO-hj1WrAH9$i%sCLbLMRy4sQ`RJN%gJC zQmymWgmx=7y!s}rDlFpnO=uJwjD0wBg7zk{n4sNP*0nug4U8(L6enkuPJ4R+^S@e4 zZYuC5*biB`AG@5PY@ikApeetg+B6amTTSst8bD^B*{~{ z+7^K3uC{n~VxkM~73BGe0HQ9Zn2Qm!tvRu6ePUZnV%v?0ZLNuI8>vsye3CbB;D)&u zs7joS|ClAr7EN=}ubj&!D4HhvmBpZ5X*r+OE(|ErXrS7VWK(C5?;G~>5dpmhaFAs* zQ@jZ86N)Y?5IxxBj5!=wd3_T+4t>ZAf0kX-c;eQTw?Te4=~>tuwI^l3?Wo4qfZRyd z9HPpI>3UT@h-1Un91K4mhT;IdqL8{XT=ex=QR5hld)u0kXY;}Eg9nl6tJwg5!`K92 z@f_G!up&6(r6A#!CK9wY5z>3C>S&A98sfFaEGIuDs-4D>6ryV9RI&wBLhsL|a-xe$ z;vv?!*Fvvo!o{I~p%%}q63i}@zvzSD5)^~+T@iood}m4L$}EE_dX#ZiZc??lOJt+A~)YJLhb>}kK-2} zhR5%o%EY5h$N#btOV>)2+yHCw4(S*Y0E7Q>@`IXEg@l;4Le}ED;YqP#PkBO%uR#AI zQuhQBscD*0ZG#|n1!5f_}r3fwzJri3!Q3%9*y2IOy9jWqfwPNcPP8h?dmq|wK0BCVCvxHRK3scv%Z zQ}j<`kwtESO)I?&*&M!i488O;Iwwu5PGIWSNI}7H_!We8#{9~eauyaEc-N}pD=6BF zrw~!k#nX$3qRQFeR0Rg$5BwO1YG?ycJs5z$&SL~WCa8O^z6b{kxRAc|*IST&hbBCFoLr4<29){U0r7!_7C}Vl~0V%!MMIXJf~v)i*mp zhtIR&mGM89U;g-UcmBlYB-OiSrWi%Tt4^WL{CDYN`2|zV`qj6zw%dbTw6wdLe;3(8 zw5(gZ_U3hVR}x&!BXIcrp+JXy@5i%SFi|y%O4H_>S6kzxNaYBBgFWyJ@x0lV<09td z$6S7&F2JQhYvCgAz2|39?wwq@E-b3oF#7gzNf_?j@VBFZDpaKX-1S^bgIEYFp&Pin z?eNFcd(eN@=L~{8_PA-|&gdFB!{z(`Fh7F5^Ju>81>Fk8vk1Qi+0QWiWTVVYi_Yc#_ZwG2|0Br z(pxFID0#!zvBEQ!Lu4-G zAFs4qGXG{auWnzBSTk&Z|9umesAn$m+E% z>w?kPm2N0!$m<=m&r{#ozHVdly5``}h*X!~FI~6k<_#NHw{K|M7+l=4dh@#Ahd&&= zvaLN>-??t>Ee6L$DFgV~i=X@PvkX78@IxufBjHHAUSMAuyf7GCx^8p(r8xuymtx6( zY4A$r>MO#F#z;#y+`KNhboEW^HU=*bZoN3TdEMH!jm?|KNFQvL7O!q^$94i|6Ko)h zV6b^z`?|I5sMTs@T+{i%xt(LA`p$%iTfVyW7G%xQGlJo@%r9r;7%7Ns2m2dy{`uzz zo%KL9qSiJ<-LUb7V7R$?)4I)@Eh@JSNe z;5#FHeHyVVeeZ62-&|J?xI%9s1pgt!$07?a3hac!1r@$GuOcK8Y)=$?u8Vx--JTd- z=%H3hIOq$vU6t}}R|{|g2~vZ*J!;L9*eedj__<_SpEoXR>24Htm=k*7o#gKSp@So_zca6dg z1kSn=eAf1e6J62`a13Z5&%>oS%7iKK69sw$ISM>nnnPWgo%Zr5oaKuTYC0+9=`;j@t3xDm5BpY#yyE{?@d9tO%_S z`MC~xw3e8(HsKy)8Xh)ZuQ?f7sRPf!p(HlPhUy8E{7#;R1)X{Q;ySuLnmYM+aWOK{ zNk~6_{#Qq+)AacYSFYMb+GZe$$}MQdtu9R`Tf0h}t}^`EUF>t#?mq;fd9_pNzHzGE zet&-L{v?XaYq|v{(%lA`Zd8OHzU1g#^V4xZmpD#o>bJxxTGUQ~LF%kXDCQC4* z+QTtZt$D)1jHPfUXJ$;ytCN!%XNxQxh-VPuXlj3Ns@*QJa7i{J;wIWxy{XF$CR|mJ z34Zkg)CJygxVEtp>cLH53Hbu;f(ucqdOPC8)Q|52RD3pmtpL`Qe{sMWpL{sT%T@|s zxnqlh@n9_25UdZ*#}Rb)-MxKdg`@N+aVlIqgsYO{k%?Mt1&;oSVdACm!aq5V8yp(9 z{iJcD`=+$Z#xb!g65X+V0e7&>6*NzoB}kLf-A+n}tmhmmfV?^-Df~Usut2#t*7a(~ z!TeaWgr7J|uJ(I-ho zW!W)Y{VQ&qtfH(yh9l7LFj}u7h3*XxyJpYDgIxXbaTOvBe{G6nHCyw9yIu_om+e>C zfBQzmf4ddny$5j}j(V~=Y@{5X@~~Six%<)`mGp(%HBdNpW!1=P#hhfx^jAS8a7j;b&fOtDE{?xr$8F26{ThGjgW%at$*(qu(_IX)xW>_^ zQXe3(=vP|=gs$A~NHk<2GPy!>biRTD(Xu2P4&MF92l+}r#*nRf(CG5cuUN4%lJMPt z0k_x z&GQT2P3@~I^dLp&6&KYJg}OZ$JupJQK-<19S)%lrHP zZhpKUdY=RTD>;DiKlbb$yT4@-XODj~s*hfk)*M~JUGe^yzzQZdy)Pm>0s%9Hr^{Yj+v<-kxd&E;}_*Mq+6QEbUENvd=BD==0f-;d#{Ms60=y|>Txdhgul^*#icJqZ75s@MBp zNIz8L_5Nm;*ZXG3>y!GuzEdl`zOx_n`Yyoxf_+}!s@Dh4pX&Qp=I>i~%wD~VqoVdG zUaWkqJAtazGMw_59D@UW%JG6vX8rWg)V+X{GwHRG2Oo&`O_60070%oip{Q_QKpe-% zfG`f-=H=*Jbd`nrBo%_w-=D(sAWm$-!RYElBhT&WX@E?mKI%&MI?~VM^m(pyzaxD* zr=RIa_v55xLGkwVSV}eQ8wGsdEPl`#TKkw8G4%jFyVX7p{&iJNHZB~XaJ$}V?)=)8l`&VWySZ)C$Aj!4J-Ay*hs&+x>UFD~z8P~S z_|s}=GD=TJw1KF$s$OevNNDvjEy70WP^=;HHiGH8>J!_(hPC#{99lj@D5f0Ubxteu) zIIo%tc?=B$(5TqGbAFH07l=bB_&#r5!D)+Y@65TD$W6n}xq?d%7vEe+A#=FTT(p@6>3Ws&!=s@09l_8($n;557nlwX<$x;U7cs8yy~RG>AG|z9nGAx zB9WQ(G+Z#+qEw-_Wl#I4TD9SGxT(*{FQhw`Sy5M8H;dC}>2x$h&S%(7#19f-QB~KXlOV)+T>XL7y9`A(i zGWd_n9?Pen6fnE)Vr#gOkVr`E&o%j4=tecp+$k*dC1>m~-rwu|hEe2TAT-ahT^VP3 zV@0gKL6cpz+JZwNS<7g~3#Qsb6pd>UF6|f_^$efo`%Sao=8NiYj~If(HA|@ zC<5Q6K>S5dJdVJlPO$Ge!5(vhJ>mrWjuY&=7TD+HYbtuem}eSZ^wigHNe}cyqCL@g zD6Wh;bn~c=fAiVfmW_rp-{4g^Qr{5kiE$nN#IIa~H~E$6KV}2GYXcm&0gl=LZ`%Ox z*Z?^+e>R6^kI%_vEnE-Dh5FGPKs@eoa^=fTu6)Jf3cwH8RHS1s+jOL3uLw%gF=`Fa zQq~i$-|~P>#Ai-l-O+KYW53tQxOAP-`dYX_8;EGQaf_*XyrI6QzV1a&T^*u+KmpP6 zk@_bnAX+|B-`@b$EXHu@*a;3WTsk(u0ftM*1XT@0Rh0S-n)C4|q}w`P^Au@Lxn7Xv zWYoP*M&0jZ)B{dNX$`S^i70OJbgZi(c0UCTvN8>^2Po(uOq1)keBEZ(=o6XYWn~bei%6D{w{9~ntpg;>oN`TDpq8;ToUfMi)A9kfJHMuUZC&NF5zP}# z*QaCKK@kL!Qbhm`pe_Bmk(uWJO3|Qi58t zWo)S?FwV@w=v>ZG$x2MMrve)MR-Su6!ZA}NTlSS0XlLc4Jtxr0YulEqlSL_CFDX=q zbc5!JXuL*?5HLA68k)Q8l)$g93%x=-^X@FEA!^l1YKwBwMz`i==c zt)%{ddVo^oka`49xud_}Y#dS^!;jhwypET=Atj0*HBOjGYM?3!A zJFf}JX-a;`Tif)Qq<)Rw|J7TI^0 ze_o5o^$kQ&uJdbKM6SO?1WjD?3+Adm3D^kM(0JhVWqzttiOpXF9<@BO-;Ks#4z|*33n2REfyEAD^k7 zONr-7^L`vqr_k#eNTdqIYxk6^vnhtO0bD#AGr1+IcT&1RGjxOI0=j{@wlba`JiYky z;g6P?CHSMS7DwTaREW{|8-qXkBAeXI#^G-~{wnZ?4G!S>fl~_jGQf@jTsdIIBi-Zm z`AbSim5m-VwtU=pf|n-X=N$adH-j{8$#l3LKS})Tz|URyAuAz`aQepZKk@Si{LnXt zgiV-)MHmE4kOV~#(u8wnMVGBl?zrpz$M*i`9|kt`#$FD5|MU2GX7Lp4Io#{@o{K$% z^UCm7jX3NnEDqxD9MYAjVur5d(vJ$NCp2>#^Sko$v>!eYImmOY9tlQ6k!Z)8UOYKH z+VSgxvZYF02nx150}Zc00k9-{qL+-Wj&|JVO^r!Mt0T8|M>`8a0uK7C=5Ls->c|in zNpEUwIvQ&D|JZvQ__(U;Ui8dpY|FBYWn(~K2ab*02r#w*CvicIk7Ub|ZIcx8|_F8N2z1G@ajT81 zn%t?S@F-yO|6~`O2pmY$2I}WdZO8jshV7s8c*B}}1--n(eTmtm^R6Qoc8LDA*{%VF z(z#7`E#1lHUP=!#??48ZJQJH$wgGxAMcW*maMAdb-KNpUpD*&qe`5povyLiPRQ4Ak zVthkdM3*!xK%PR$#Kxb@Y^bl;zq4#Z{fbtJOd_*8j89RBTq?+cbdU^l&PmFibGD4# zFjgIou}oyjxv?6X=>n^yLr7eBGsW{M7gD_(Xh}mM#RlMX*Mi!!X558 z!r?sDwu$}QbM_a|pkc=AaRXo@Z-wih# z57%L<|M_@v?)+T$1@`5Z16r|c8F{os<)}r&`ApgDIU6vOcNp$k%;a^cj=i~gMdcoB zp+8^jqHMp8Gt0y=5HBs&Yf>0&IYvnVo5*w&PIExPS%u-Y;^Zh!uX+dvvF4MQ%9pgM)z91duvUGqxwW;sEwvwuz4u~OwJ8Vn{ArL>>|~&zkneo}N^Xx$VII z{jEKRZZ;}tY;pOAdOG{sP5y*s;v5%sfcmI{bs@0=1fS*-dlQ9mPRyHL7g9L3MC>R$ zs>)C)DnlY81+v>y9x0HYp_f{P{~LrO1@e3N=nCX-D5_5&=iJ~zj7A_aGv5&cDN?-C zfwLmN1mSen=lg`y~2GRrBGbiTg&J>Lm_n$YRMymRFVy{{cup-4)G$ z;Bax#Jbs1cZGi78)3PDvxLJ+Ie|wfx%&J7SBDgzk-)G(Ru-p0B3i=7Ns~?&Ns+=*xw`* zbK(U6%O9k@?q}hciyHeYx1etj{mp~fICN}<%ixO~JZU|}R=JzqDxB_g5<@VTV4Mth zT6%+HQ)EhxzUCHH)Kf(ssD;_G-EKBRvFr@$~$ke#N96I!dR_!WQmXyNhlv>|QVIUEbh5-eAfb?DYowy}^tg9OL|>+!km?4n0E( zQkQ(=tLJT-;Ot8Vah6+-pCNtGk|)xG8Tn&eFXH-VTrc7J39g^w`Wdd5as3?Ezu@{; zT$s}q`vtC7ah<|-8rLsz{R-D>xX$4EHLl;_`YkTZS&RJ}uHWH$9oIQr|BmYoT(Bw0 z^o_#?j^hEd%8WSF#5h#BIBeiJY~VO-;5cmHING^5Y~VOd#W-x>IBeiJY+9PQ7oUr3 z9 zs}|QaxUR)@9j>LguE(_u*K%AK^N!zu>qcDIhbO)g*L!fi7uWl6t-|&DxNgRE3$8j` ze}FCU{&tIla_t4ES%CnGF47# zXXrgzxJD98ghfY7gu`wgx-20}U6>Hmr3t~Sy$I;?gs-|lA*f3ff;V~NM#~gnb)iB~ zmnsBxu|iOnD+G1HLU5ZmTNbS#kyyqwBIz=RL|EwFEDa`z9ZRH!9fw^Omgz;DEJa6R zbyWeO&MdA=&ND1)bYkx-hi0H;#XM^GN{&K1spU}ryxjBEq}!J%-5z6KFTj5VgP`21 zE4P#uM$@ND(84Tmm+7bF>te)pB@~Tvfd-CQqq*Mwm4gX|kBU7$Mvy`&Y#T}V`Dbyy zAr-K{oYc(Bvdld7#ujgIKH^!A%{hc~sB7%qb+AK|SHB(t^C(y;GI z|0Vh4odoWl0-R|TA7Rbmp7fPKV&s$*S)?gYcyNW&9JQ3Q&w~R zbJKWX!Rsa-%fw?99%JG$)?9ylhIqujYR4sP0*?~59FGDvXCdkHMlW({$~ICiO)8fr zHy1prT=1xJX>xPHqsj%3Di=JeTpFK3!OoxGnLb{=&N{NwI?@OU*qP4lI~y*nO1 zq3Pr{IgY~h-Fs6#J=oAvq3)pD+uqklTS(#~?-04nylvZ7-X?OzN^FDaMpOl*Ld)_O zNMwylMC_%xw>`~!fOcc+PnDC|-_hP}w#$7sX~#_k<%OU)E)=j6bjN)kw!ds|GgImI z9^PV9_>ADwgs~I!@1wQCd7Eoc@n*PQqd%l@e2iWr_T_t3pOEGblUHcx6+YauJPzMg zcErSygSnCU*5L$#xe;?qdKmBOc1??om>XWEZ8>H-fb$$coU5&Y{fle}b8s4^UB^mp zp~tl}_#zTLS^vx|qdsfsU@=duP!E(RuI8D_m5D5fYQiTgQE`1om8e-bR<^Rst{lX` zbB%p*gX3??W{vDc7~XJVgFq#fMIbrT>n4jt5(U(7_JHYhKv^j0VpE_j`n$PaH+!4FRXB~eN~HFYz~ zD<>gQyl(PO_NBd^>C}#_?wDRJ!}j9A8K}Lf9Y9{mJ_DX4Mf7tUo#u>oW%8z=Uf5T2 z5(pMgN!0kfa)&d`-lD>u(LI4aY`jnYnK6f(LXQ6ieV01h3jHe zxK_Hj&eCw@luE)Cdstzc$I;^|ye^}{b*78!Obu5~DH2zQ{S0;%P7jtq!Wz3_mLQXF zQs0Wn-&#%udRkR4ydEU4+$wDklL$Pz;#gO#^N7*wG5h1F1g!Q{4$gGyS7=Sv$^zlw zkVHH;85)VueH0C&b3!FqqdS(WJk8u>lMOzNijH*V+FI@v5ke!CfrsPr&3Z3Q^RQ~{ zKcb#G+2Hwtom~+gd~x4NZQ9P@iycr!;l!xUC+{b4NBOL=KY>KV_vu#urP#F&-<2Oj z{|W~Td>KF0zGm(f@n+!2@qDrp?jRK}pInSkex!;r5?VwC&JsdmCMUB_114t)eW~Ct z`}HqSbw8zrv9bm2B_{2`Wl}`97=rGtomK59g{*T5pFzWc2Dl2=cf&W(b0FOp?H?dT zlILk??txyK34wzlO7RaA1}r#_$W=K~DrDq)<}=YD7wv7!+#@-J zcY^d)S6LMrEcD@q_0W!?D4Wq(fTk{3Av6feMYyl0b?-goD#01#eeL_hybrX;EiD$_ zIMAJb5IPSXl|ZvqvM~9E=H5k%%{%aKUndr)K4jdD|DQGP!T&ECUHE^@*w3-D_6fU+ zH5~NWa^CRE^oBJI`zsGr8M#yQF=-aoWbRZoJ($pix8Gl7@N4xO-cT>iGtE~ESFtJ~p&6VJ{!4z{I9(ub^*&8zpd(P{PT4#^~Z zp9u`f(AKaDY^WUzhhyYJ1O{j?==oC!?Mq+RPB34@v`8JhZ@JiAC$YO)!;YVUo%m)` zumE=6&tt%Dy~HMuvnR@pJeZU(%NzMQxG-hxCHn{WRT=VIY6q+vyB05UR&!lyN3)^; z(OSTu<+2}?0I&<1>CguvB}30Xvdh@f?9vv!+zjMtrIB21NMiY2k6}`^n<6Ja4A&Af_a6M5e6f& z8mUPSm5p4J9vHbcJv3+JIy@}Jb$xm$F|sU8%*r-pmL=}84mVjtTSjWJlDkIC!m^t) z%v_fn!%YNVH=}98cXLcUN*t6!(ql@5S^?zfAj&w#fouUBxGKkr!weJ$@t0xf&CFWz zhKnrtv#e;btH8D*r16)DI6VMH7l}4hBx#Spc=M57K3R#TE14Z~E82KGGRHi~L*!v6jc>YRwGUZ15_?w_qM zSE9jhOJC>dI|{c^MZU84n9vH(X?3M9D{3l4X#?#Vm3MjRjacFG7HY3V zdl%Jmx@-u~DNtUjOcC7OEqZ+|$y7SnDszs<)W{EE&mg|95O?)@n$`rejU~-6ndVJ| ziqh?Ga_FaWJmzf-js+TnxqAG$PJB2T;Oyjj$C+Zn3mJbQ3{g3arfe|&M41DHo7_^l z<%yIprQWkWCA#EMG0SbQyv*X77s)J9a|(ffyVwfpt0|jpm9Lhbt*%5VW|!!GbdL9 z(otmgp;I!>j(barmJ%q)#4PR|V#00qN&9^WT2IAjtiirwh=V6BGJ-H@1ILWpV-Cga z)fCk&r;NPH!fu_Om^AfJk69%%ErJZ_gmu6rqNHTmz8FZI#KK#$nCxbBE<-o)JN*p= zO=l8838r`pMejp7L6i3ESMU{sS(wv7#ifuWRN^gGTu&`3R8~(_G0HWTDXpQfG9h{3 zmv2w99<3`PS!{`^ItG?bnXW5op-j=+zd~-y?Ye4`+V1EkQb$NNdcHRXzHjq<-xl~D znLh3Qz`eJ~Da!V(Bb>-Nx?M7$7&uTQ@Wv9sfiZz^D-qm>Oz{>_syeAiItZ_80XlR0 znu?~g_#TgrOUD}ilm&Mc1vyM&o5 zGsV6S-RaCU44yd6v1$=j3n}{~bgGFmOn456EqYd%`8p2fa-{95drCm(Tk)yP6Q>M~;TUPP~ zL10G{G|bFT)10U2&eII%X{Pg3K~HLcM#aorF*L0uoq6}rbo^g5Gz0$^43*>mCD?eR zsp@FlvT7}yEIfP+{2z)nyQmE`Wm+Bi1bylVs5N_pKN1R-@ zi@z*WaC7M4IA+uz0em$FcxbHoQ%$Q|nr{DGmWPwwE#=DH^WWq?y=PcLwQ~Vp!VBK9 z7yAVn*ua(t4@=*`_B6%91gT%)9|LPrH+nI4_yB4{R=9q zXaek4=~?!-O7LjE0haR#k~^WW$xL!Dz*k3=W~g8E=KO+cfU71{=)M5asYA44R^~Qf zl=qvpGjh#V=KaVdGTI`KMB*4+c;g|Fh^997bk}{Lj$PPsR_!!)$LJr^0 z?}o7DPSv27!L(K0pNO_keY^`p_Vg0Z`TcmNeX}5xVric62IeQ^J;DF^BG(&ZHCtQ8eDUfIFiIy>@FoSSN?WQCy9aj=tD2cx- zA4@qTk@Cq|gbYR-D5;0Wl$1(inA2E0rZk3Dimk@o9%E?N|1xaime#@1=x*R0zc@)8 zqy=b*g*B?g!yF#L&S+K+`C=Y(V`31wV9ZosD7wU0Kf3;RHoX`dy55+hK-byHT_^7e z!zSK)@O?G-j)7c!Pq;)nQG`gon6J7qUo8?dC=6wOwh#DUz|x*U^8p8Ve|_``Cbbz^ zyM}K1=63YHe<607r$s0Wd8Ounh$=({s_hbhu)|`Z*fN9CfPi_xWI$#Y)>I6INJ$y9 zU!ysa@iDG&q1uvbmb}dOp z)?P|CMBq-tO#;UNRnZre9#a{!B2debXllIbBOoo5085M9V1>ev79mZNP^xG_$;OBmOY!}tN`Ab zjZ&PQj*4=YX>$29*ZLs>xx&E5G~@RV-hE0aL^j>5!7kpEo}aPUk+-FnfhxAZIgmYy zjp;FaGAMx+epw5)ts%l!5fn3@+_@Dq*CsSGObBLeP1LE?u?~-YFO*l(1xZib_*$YL z(+2|mY-+~~dY?rrpJ5o}EY{`@1{IL4^f-=@3RF#q3S!fwQBh%LkP27=Sb{*H?IS8+9s8JsA>mMgkp>lztvo_) zj8vcof~dd^X>L?hC=XJhyZ{wmH|>w2f?0KAm4UcZdPWwdM|J}O^D&m5H93@iFSRnG zEB%aM>1X&$Urr5U=A*E$sF`K^s!DGJGJA?QhB2qqj3i2pY@|gdOO2I#$mY^odpk8? zA_O%XzSz3KJ*Py^VbjeTY-2T>pL1QTWsBzyRH zkQ{e2r?@8NxSKh}@|5Fl#;Yf{MCi%79+Cr&Xoso8VLgP(gYH@M3d|%7oK#~OQNi;E z@26N!M-(`JQ1nl9&M^iQ_45ba4?aCaeF*9yB+yR1%8?aHWr_iOKF{xHBV4`9l z`Mn{phAnpuz*(7?95UhES>A) z!cxeAb#nV`$?YoE-o2g}sij>)`ezT$avBt!-FiVOKewQC?$D?TO3@>b4-}LVcVQ_s z{(@c;ET}VPs5jYB#izrR+~U*mOQR}2MR$utA*V=z;#1--EQK7{G4na3_*%8r`Aqq_ z1tm;JRZxl^fqbB#l(-8^p-CxdD4$EJ`m!q?uopyEedhO zu6R&roZ?Zu(4nFdMG6vFmlUGjjDr6Z-7Ox4oZ2OoFI zB?Q9QpVZ63+p_jSVxdwKBN%$A!_dJphn=q{>_dpl8@A0JtaRQx4E!_6z+LB2qG+?j zS3aGCxzs4i7skO4t4>f9GfH5oe1e}^`Cx$Dhw{1o4Mtl-O8H-9DO=8mbE1&&Dr?$T zJry^gf?usva479D{G^UEQyLsji}C{L>mO3Uetn%{x@{7Loa&aj%Hb`#QF|^|OWF@d zbf``S|JNv0^bpl8yqnS#I;XI+Wbvr6CTxAWoVT!x3Kk%szEOG+w3zh5jz$)OTAV{3 zM;1urGb9V14%|x~k_BBqDNufv1pzy8vfxu+JVsl%M7S-0EB+)ATv|sW3KW?nBDxrX z9=iZV#wHP?F0zyhGzTFrH>Z6}vN|uCXxVNDx?XC@Xuehr=AAh$GG_ORmKw(c%o~j3 zeU95hsLh$X!dor3Why0C>hMLbnVH<-B+GI09QA9pb{B5!ukN7muzf$> zN^Z;SEPP)!RN=3FnC`RuvBxSz#)B_*pS0{nPI|N;<=hi}p8PXa&S%c9E=lpx0bjy1xbLD@n(x z8k{ROKo3trj1E)|UaV4(B}0D){R8>b-HJr!4qk+&;nq=+rWt1~XKtcx`gzc_0;Ir=bG%u#*#S5+SrbH6il?9Am9N2b_2LF{F6 z4wTB^xq`ve;6Xfyzec=y!z+2*sJ&DCJvCz-{G}8}R2S8pnU!mn?W@S_SJ`IO$|r}R zqc$l~fb=!tNT8!_DbPu z#vV0;^U=vR2jUB+eY->fqh2@Ih(I8Ifm5b50sG@=%4Z3Ll=A_+2ILuOo+z=H5XP%9 zt?{Wd{c}m3$;BK^MAL|bRZ@M{q!E0~%+b1n%%$orrVBeAgmj%c{V3N+x0k5nZq>3E zP$wX2t&cb1c2HNMu!WG|v$bFcvAM;eQdLgborN&?a$@l1I)lGd=4C7(&Vc1J$j9QW zx%2n%8aZhqgj6sWkbaUQPzQY9~*jsJ6BVUeRHW;ku`oVWcr+Oq%lRv5`eQ zxlz-To$^_{$eYeVtUp|D4O(|Q#SnVCYHqXfpsn9yb_%NCp(vL^ zBh1nfG;)0n{(xxpO@~14$5uNQ2HnYUb zMIvhD;A~+xWyG2>3v0&Mp_8-erq+z{GuBPBD^CW5lTQY!#i}v?QY~$R4QIX9>BK>p zLTYLnc0O=zYB)psgp0sgQZjW;W+NXLnhu*YI`wXf0#g*2qQDddrYJB)fpuz4)F(CA#)mL$a&@`q`yq>`thY zkZa!{Id?cFD!JfrOo8Ow;h3o8g2OQdk~`)M$3!I;9FB=f4n`au==#LZXXAkDdu9tG zT^KK9&rt@hc~!b~iBqk3Rv?@+w~V~2T)8X=3%@BL78=VD7y|He&u5;6r687`NGq5C zV0~qsnca8Ew{yd60doW3WN@Ne9_1oy%h?_c)uhFc&tl*)J)c=@SI- zdYq&T-pzS~m!K`ffH1Axp)Zmi;qBRZsmPeqK&Ahd7)Kv{9;3x0G{CnI+AfdmQ8$6< ziehAsr!jiQ^ z1IfrC4<}z`IiF7Sg^iaG|I{eSJKys$Rl+F>Oi^Hp0#g*2qQDdd3Q>Uc2eD?SCH>%@ z^n-2bY)3jPCV9veJNZ34Lcq=mY-R|}@i#FsRT5T3cEn6)=95B|N{wwblj85?ElNfzt1-rz)Wr zb*1YnyWs4I(MFXow1)VVQ9?g5BF~v|wH(Etr<@Y*L56XT)SGo=unq~U>bFUXR`X&b zF{O=2dwt#W)z2@)PC;{^5kJ2W*7Nh#9c)5F-@TG3%@g$SuUgUkJG{@_E!by{IhV8P zruLcRXY4aaEAGT|2hY+G&zQ1PnO+?W*%;2zGqd)iBoB^$%b^^<4AXDFK)^B%>9-UX z`mL4>^xHr(Qd!kymvG>{=}LBf;E0)BUo`A<=jW0Mw1?J??7{+{`SLl{Vb7F{urps$ z+Oat2q#7A-e6TBxHDFn?rXkz1I^5Qd61j(kywn!lR;^1;tV490HatV#d_RelR*h68 zjgy99U-A^+@ro9r)!3ijs~eDR64+i4#|!fI>1Df-mYS}J`z#u!!+nmtUo7vJ$vdyE z!}qHwJYZifZtoVi>m<-}dFTBr0nEF3;?7$~;Lf|V;=V@0u>%WX9vsA-{d2f)liztK zNQ7^v@PLh-BN#{CDiN>ml417lAmwe-_ZQJyWp`ds- zWSpKs;%M?ZOA(ov1A>+~xQw#L5kca{gd8Y9N4Xik+`CZjf+|Hz?hNa!a%2L^#s1FV zDpODHoH^j3owL9Ce%j3?+;zk~S zi56Z0{Ib0eku^|C$f%%0h3q;Y?D?{*%%t~;u#9mIwwAMfH|@ zFV#QToUhN@x8Y!synP4%-;Mu+b{qb`1Vr=m@;m8Vywi7)KOLvhB}U{KVAuq{3m6V7wXtnUzaDQTe}4asxn#NVqoZ7|^ib{$ymWZbJn+|1Lp&sOzc`+5QyVbi}wx#Tnl-p%MT3H+bxVt~F*LfeaT$?4J1 z_jR7Hy&u5kNV?qW2XI*kaJ5MKzPj^uF&CPWP85K?h@kCqM(28LLqOzRl1t8WqIk-h zka_&z4Is3e3VgeWV#@T}}U`UafLlrN7X!RIg>NSX?My z&=1N9RU`kDH`a6=#f@q2s^wDC51R^iMbR^DWZ#xrt9LQf@jZ#|Kz;P|KmL!q^_X^V zX`NGupfm?)ysE|MpRuY1C-df#Rc@QqB57&*Xo;@Z*T337!8=dSYX6$;$-;d zQJT`}^!DDq?gRVHKQNcHRghjZ-Y}&x+8^4JDzby2j^*!G+S0wSc_SpOO)6bkfC$x! zfU!*(jQ=(^{Bhwpo!TFoG(OhekK9|1h9i;UCB<^qILCN&D~;>?Pw zm!PsSqaqii8)?t3HXBhAJujk9ED;HKl(<+^QmSL7Pbq@56Tb^4NMFM*H1lHImJK@H~voIP>-}|9=Gk2kp<`|4UTqFBS&ReC$U$ z_+l3sJVFOh=1x~S^HoO|rMP789LAmOou#>Z|svUj56MA$nb0_+{ZiOXdUW3hMqGFAsBjakOVZSVL9 zJ~1K|V(<9!kzzphPKk_(uy?}Ky^rjj5~XA`_Ku%-8w0#6Sv8~WhYQ;~Tr{?KSm+Aa zJ3b;T64DuJ?~q?7*VjDEjhf!n-4Q5?m^uWnW~?6bw|E{=OM1AzuDQOc@|ZELg%@`VTFRo2VRT$2#FTY& zpZ{7Z@+$!10*4xKDa*CqNG@IQpfTe6?pSYIFm2Wok8#=^uJ?Lz!qz>~daLpm%y3Lc zuiooRKw58$O3KZ#(E_=ZPDW|@79^aDJ6mx+!ojyfWpk~W@b9FVj~s3EzH&o(HS! z({!(~Ww&8CC7iel5Yf5{ieqk5j!BBm0dvWZ7A^xhU=-9TZz6Co<8`_Ll+q2gRPh&GFxB2G0O^D>V#maLFH^{9PyBg;USL35S zV17DR z7e7ineToT%%1;;mQTpk`$D)2ZVbPGE&QIf~12hi!>AdVSeY8aX=gh0vj=eT(Eqz{v z@JR&bRY*JrUv&1(q;(0WV|`Hzdab9#-aXgnlJ6$hZy1-QIpBD)owd<*m|72K^JPv% z=L;qYiT3hpDU?s^6F}trj|2`VFU6n8a5C*GgPbFBODiSOi~OSVIT^ma zP5h!&nCI07FDJQ14?6QPHe!K_R7r)u!f@s`+=frW)F+@&m#A+}%U7IPFfv)6bHMlA zaSm9R_T}az8miN}*U%SH&c`5&UU=<%-=h;|!;YO<>}~YLlWwV_;Vv03J@f|Zpfi_; z%Uz~acf(I+iyTaH)>|NN{%D03NcqdMWnk9%kKsT5e2lW;;dI+ffs(xg@1)mQAmf{% z-9eslyNmyma17dM{AVlT5@AKWg84?#Gqk1sz;cE83Vn!7_Cvx+FreEVhM}$Ohl2p~ z*bnq#0_+DW70*|og3){hDpbUL1fiBJ2kd0rrF7#N{%FvDgoO8JiC# z<+(*0gN%*ae((`|Vni&&e(>cZ#enRG5*ZU=KZK{t_CtwMQkwlB=4bnPR})misJ4k2- zWC!_aygfkUfF0yzpXsAzNT06FSCD;r=zN77vM$BGV7Q!3H^;uAtdE)I0 zo)}K(LN6bt`!Kg|qtMGArdX8BBb*H1-X?nad|Lar0%!{jIF3bnQX%)RwNrBys+@Iv z1RScE>&kPVqg~6ieOkHiyQ4gxb!M9DI7N(rdU4XFc7-lIR_0 z^ChhhuDUvK0O%-<+FcW_$@_pcwb_TPqdW(Yg|hA$lJ*i&8gMk8zc$0=(?DhAWIgo?pK zh4~_YTsa~}pacoL)}*Kwx-}^(gwL8>*i9k#2V5E1!2zydQfITTVakd#*U3g#>Q<=`UpNTA{Js#`tp%tK=x#b zjES%(!_#GZvP3BvjXmk--H8D2CeWVbqOm>6LRY|^^bui^kj}#Pq`xSy1d1|7dy`8CL1tMaIJ?Y2%TmbVD>`6&nX-^7V7rVGd*pr;~1+ynzC8h)2na`dK z|0wNA@v*2qDZ60UlYSakqV@E4gaLcf%RbXb%b;eikE_XEJ=C78qdqTWrLcegJHzE{ zx;geF2c$hIo`pTh&y!$yf#BQ3`_;py>Vlt^`-|OV}(QIYOL}t2%<>;){_2wv=JF%KQQUde^(_v{lc3&WE z#z}JmgiEAykocSt5x|;#{}hI8HEuaJm*uY&2GNGOAxx==q~*X|tJn4m2iAFYV3DDjm z-%51zB86*`s>kKoTpsbFqB)JA&w=%(wLp3q z3NVGdo-R|U`F1*8*CFba2QB3qdp{cf$ZkTOTJebJ`(6L9uR}%Wt9)S|jX?D>gh)ew zN+lz^GtpqZp>`GwC#*a?W%Gy6d$8V!kqkf9{VQO@8W$N2b8Dw&>C-ySoiCqfYVDQBz z^2DCqXf~To<~oxO(^jbnzii`RtSNJ(YTe=aSgqfb+qq8I7Fa$IVqIu*kXh=N0a9gN zg@+)wPSgpQW<*wT5R1{A{I1I+qV_s}bNO+^bwRQKX(|NbYu)VqZrr1Y>+h3ElU;i3 zWm_0N_G$$kP#x_W^~t9@+Otsn8v7R5i{$Xp&UW`xqW=S`HmqytHv?ans;^BA_C1B_ zO6IdA*AqHXPpc;TeYNDi_gV?%!v$qmu-j{;yDlpKdj z`St26q`a$;l;5}b{Dg`5KR6Oul5l^)0S^lI!`e4bgZw~`5N;1g)H*9R)G@C06#6#Rf{e5vR&!`E4n`rwRfiN4!rl|sb5O2myA z*Yag_nFNj)*9wP@7}xRvSBs>h#R0+^RDt|f^p$F&5mi(OnJ#3pTFh+M=g%7$3|Y z3!1W8FBblBH}y(qq*tTpxR%teF|OsO@go5m2gbF$>@$6|46&88d6Lpr3LV#a8d;Yz zuElUUn{Ljy76;_GmUtH9TKqgY<66%Ux-g!O(|wp*w^5919ivz@uJs%z!?(A^xK`8Q zYMLi`2E$RpfBy}vxsk4DsIm0tj9xwGEXN_B@X@PgckE)7GeMESg%Nua>VG=3L(SuR z#O4>sk1T;^A@tsOveqg{XY}MN&dg3qT{=yYU+~o~Z(2GIcd8WixoEM7vPyy^+rvE^ zUcyLRHsle@K(WMMv7~+b)pSY2*_*T|9VSRdQCSfdfJ9MkgC{Q~+?FuE4@7iPzLW>`9nnKzS3DgRL_){0X2fb1DK!aF@llpPi#?IR)x zoO$yJYk^j3SzLfM{Lu=la-${OD5y+Li##>uj5AM}n;gfNalgMQVnlaGAX=~#_;^9< z%C$L6HHS;~C2IAQMA$ELXrcWI-D_+r{ROK{Og_1Aa-t=~zDHLc1iS4I;hnVW1*7do z;b?7muqoeweJel|Y{J6-sc#r$GO>fTS~yrc;0crtR_EYk;Z$uPr|MUfPk?(C_ykTP z74ivWoiqbJfognGb6v^%beRN>@Ck%NNB9JMz||t@D4#$Cw9hBN%_ktc^-!OHfw7TNd;$yynFYAx6X1aK35aLm z6X55`@d+ddUHAkl=|0S@+bDbjb*@@&bHN>~JZOeJKhwc;lXpGZ_KVdIsAXCYV*l7`p5_OijgSEJNX zWKVxFZQm5Z2>Sz46x2~Je01drntWrQ|DgRc{Gu!g4*LQB1G-lYUMl_mG@d>BJ-wI!{hkWOYuTu5w3dy^6|t6$vUK!&j+nFQ z9n*!@vT@u9{oa?+WfC|-zYm9w(C>Y~)gtLA{XPQPr{6Pqm444rJY`MDJUse6(?aR@ z(QzX5dl3Qpz2L;GvfvCPKdt zPnY%k5~ZYc{XWYr!G$|}(QU8K2G3p$|0wl4@v*3WCmUJlcYYy#JRqb2{f;>Sy}w<{L%N%Ssu|Oe8u%RS!(uNXpvA9sa zq}Up~DM*uDPWNGM9Y&!aCny#rQ_0EjZL;-aV+=p5_UdcyY47R2KAr06ySevJ@AAGw z>Gs}b9W!rOvFyFeZdi7MK_L4&d-nIY_O#P`yu5MQP5ip^fb-%;@nWCvMem+A=Z%Qd z+u7&55s?n=>2%(#={LI1r@w1mXK;w%ct7I=fwc;XUEHS|o3SLKxoD zO%SO{)$M}W1=P<{SQTED66(UF=I_^dgZ+MJe-NNRuym!@oe-fJg^UDH3DT<+_V@@f z&VD5l{|Ul2NNz$nqzqv-vILo%AT&b|3Su5o58u=_t8~*Rh&xez!sM+4zC_AZu+^j)F96L4CO<20F@D^>4 zCb3l}+IZz?iUy-?liz;q3G0hP^?_0JLz75_P(SlqLQW^O(80PnULH;9oW0B6Ot}ea z{oK~>P3_;G>gG|kw!6BW&g+I1+@D3(xycl^iI=s>#J1%2E69KgLft8RC(sNJGYLwy zZ{Nc5Fx4)F4KSD3E=_7<6U1SPSL5JSWD6XuoRiAdcR-sFst*Rq7)kLd`pRibG?isa z9wyFJUSc0T)sBv%9i7z5JJsifI9x-dI!LF`HaW5d4mQ@4%GRki?oDswLggXI_DBht zl81LzA3fFQjib+dyVaXXt-Qg$XG*rlAzP6xa8T$bm90~K^i&_MmeWkxrCb}{?D{s< zj*g=pom6H`^?6fvsb{HSWUoD`VmQ@DPxaC7us(XK&ztJ=!j^akt)HnmEMl#@v)qFjdzV4|$Z>rCm>hn}1B==#_wpbbS&Mai6hm4j^&9Ru82k9tF zQ@+h9-{$hS+5GoObt+ByHm7`>Q@%~wMQ*%y^psv7rhNt5m$#d}_Kv`JQu`2^$2|3W zXHp=RHl{6l=&x^bO1E&{oY*W1)f_gA>YQQ3R>h5@M%?(@s<`p<$Csr$?(1vagD|~2 z3b<)i_;(hU{cJEZ}dQT1Aza5AKoRh7nVYCk@Z0)Yu_vdwwSH3&%y|R&Ju!iQKraG>EpuerXvuf3<|Kw9I3f~7m~`HLza{%a4kc9}csw{F(~!^e(&CPA+Rzs;7>?+#-|84@OgfZ#D8OIFA7_b`h@@U zM;-hHe*-w^L{!ho!ueVwgco%caxQ{7qyfIkv|ztQ~Q zfzG}6fJnW)t#`F^g?8xkk5>lZZ!$NhdiJC6!3c5VA0H0DuQa!|_qO+$x3>3m?(5vE z;{^EM1>oNU_|`TP0^VmfCbyW4Jv}KY+HvDVM*#l4CJBVO?m$-;i^rDMJ_tD%8}NT0 zfPbI4b#wii4Gha^n+=_PqSSHYhie1yt4x*+4Go8)fd318rS__x$^PIr)QUjW`F=wa-@d&&nlF zeA9?OTZJ=Oz5od8__X7Ob=V%J=VN7+TW}(#{T!te_5Ju2mUWnF*mMH@fdHkOH5AL1 z88IWh^o}Rm68Tiy(qrdN)lM@Egr7^)#VuqWw`%O`MvQolBT|g_$ryj(gUFZVr{OG% zC;od`UCgqtK^nt;h~(oxH{z7Z5g=b<$B}OBP8(-*zW*CWe8@OyJ!4#C*>!{=4((XY z_iDVIRxaPt;T-X}eUi(NG-7yuiJp(g`#=(Uv<6>1`o>A?T&%DDc)Y&;FvHdlzW6us zll5Pzw+3ZG_--AKgD(8t%5AYN&W&-HBY{waZ7GDYzQtlMey;d>@i| zp=!uFX=Sd9guGcxE^B4ti4o^`$TXb^$vxu6to;MPr8768jWEzg7|oeQwfxSWEt`#) zmAQsaHWD>g9Dlpvhq;~BBRkWzR?{;Q%-}raL1WF*uZ`CSy{{J#=zsFQn*sJv!T3Zow@kStRdAaWOf+^xF?%IF z55?_O^8WjDAFyxcV9dUS?ztqzuz@u|6{=}4c`lgzg_Tw|yPd#5fLQZ}SF?7WLX^F~ z%uDY$^$ZF>WVNJ+T25N46IOpsfRTTx6@>)AR~FC@h!`7lr_LZL`xQp;+vhAI_Lg+5 zeFD+@D{?XWBpz_q0CQ1glHH3I^rUsX+Oou}`k{}lp;z_CtLp2I)T_6HFMce}uj!R# zzf76nY=nQo{iL!`^F5(`zmC6OseMn_RZi-& z=qm`f9i4+@s3SuVwd;t|EUgeE03>rT;Y$F+t;{(N_(LjsIup{ zf%*LvS=8!^?-}tE*zd;y_c ziq`k{0A4RATmd8EN z|L%k4n3YS9o%WRM##1hK5I=}u8?{3|8c~j1N`W3 z>9LpXs=tNpK+2XPkM5=-f2=YmwpHEUKpX_3Yv!}j-ceR#% zg?iuZdEdVvpG@OTex~Z#b7Z9sejl%7LZA0Ua1;7d&-?yF=5idINl9@$mE3(h&gYYV z!=S=c%_m>;gu-yIK1*c{;(mqaeLt$lLcHPNiB=m*KC{zC|#hpE5uhmi?@TC46LCnoVxpXKvn_BHs zQa?j+FB-D@wA7b)QeSqCAUvrfR3!Bvf2s@dXEWt7XUKk3!{WatbP9fqAYvZ=%qhwr ziADNHM!W?k;PWt?wZ7KVpMRdxpNszdYc3Dp<-y>e31XTDgSka9kUT8=cf_5i>PkM@ zj4*n3EwBGkMx_;XDWBX*@UuPgGOwV#c*@d4u`hO0`vT|IrscNAliObq{O@^kySVh+ zzDKd=ySY`oL1lYH%kBGdy=?RGJ)hd-$!>n(>@=-tCB?qP&FxOk?P)EyyF9twMevt- zGP|U3W_s&2M6oY*bNip1+Ziple~am5`x}D4&Xe1vrRVlz#?{TO`aDsx=4n?VKk3PB zF}~+h+dbK#s8sEv`caLN4HUcD&Fy2H+jcFtcX@LAB*8E9WL8}`Go6xOq}Z3cxqXjw z%W1hiVd#`RLGTa@txdbU^xV!->?_dgXz3?8eWjgW3kQ6D*MmlW*BLs{TWIL)zRq7k z?+OvoE!R*l+LLQ@$=%S^t<3v!$qs&4mP_{W15Q@HpC1;Yf2^*vV)pmzthoJRomFK& zS7({_k25c8J}DZo**GwANBY5$-RbN|8?FwhFWU*%DwlRc6rSa3p|pi{g0tY~JZM{G@ZZ5*{Zk`8 z0Qdgysi++9B)nu_JoT+FTF^&D!ui{BQH60|)Eii)fAqUmV$e zvi>j)Z=ldJ;annHo-N0r-U7vE78*$-4HFHootMWJ7#xP4ge;#pI)nY1ad_Q8wA@Gs zu%b9t|IESI$cNM5j!4Xj7XU1Okl?(<%_ScPX_;c_ti7G+0CLxWpopF(zv594vzEW~ z-E=e!IQ$fbV3B=aHAH&ZBPA?qrOx0jQ=R!F3K+6xGv}jOj z?T>+=dLk5*~x3dW=|1#K;O@Bt2VjY@{olx%dRq1T;5k zsG40EblLthx8`m^o9Wh83iV&7_#Mk$td#`A#J6xT8N=^+r0uVJvR;jCECfRWP7|pR z7;8#&#I^O5Y1Nq|qjA~Lw4geOMn@72FUvxsz_kI_MqHS77T=7EYVj6aA4JWY`b|+_iURLI3b18FHc75S zXu;60y!OP<$Q_zj#kDYZDoa33qj2pe%r4;mL)ku!a!!J9f9_=W=aAK$UHAH~tkG;e zH^Z`CPg%|N&rRb=2CtiVEEA7ac#Mh1SabdH8R9XHM+uw2ql7KTqkzp>Ncz0d3nir~ z+eo=Isa%@eT=1xJ!K2Eh$;}0iDi=JeT=1xJX?zNeEYiitVuB%_av2ksMp?z7K{Lto&$@;ay9nk#n<(l z*Ce-co*M338@FvqZr;}DDUiZ@`}$3d$sOB8!dsf_w>4r0XmTs1ee3#q6mRpknJXy_1%TIK7gwa*XJq}>9%yV z_O_c#+Dx5>%u-Pdo{hix!2Uh$J%Fk()_3ns_3-rSUd)Zhw>?dmo875Chbm@&M|-!~evnE1aiMsk zcPic9!xO2^eUNgB?9A*hn1!4hEhdUn1rhlQ3~n}O5{FhDH0ifh^6q8|i1&If1ySVr z-i3-PW`q|?L(caX6SPYPER<(MD~UFea@GT=brZTo?(5u_GSgxTxw)?=wcl*p-)cJa zCN3#zxrCu7jbf6Fdb0b)3WO39R&3a+c;GOmzOAhX^Yk@_s7hEghX_H{$}^U1SsY6& z7HFihC`K<@Sg|H`fR?Mc?TQkEu`+=YZIuLc8mqA-+T7aH*58WWr4P$pQV06X{?=Zz zt-a5w%B2g5l=OC5=D^925F<6W%4;ktNaG7>@&*5`cn`y**Eo?eXaMP zLmFKY_2`kYY>igG^tN}g`18t|w$9$xJ-+URDt+MwKx3;ze6WF5!;KCtEujx?+T6HY z@{^X=jV>W-Gav=~JG)!aNt>;Ex_R+a*P+oRvyB9SWRxoR*xCtFd_|Y7cSi-)w=MPC zwm~C>hF4KRTIkwFQo0R;)!%w3-nTXyo0D5N)x*Q2RCR)*tXLhqne;){7H&4zKvsLO z>}yL;ik8u#lI*}bGknFqN`ULFFjzz6R-59@WKl(Yp}#?Ad%LIVtm$;tM3!87qE1I= zubE~gyQlr$1JFcJ@9ga$Akt|&yAQOl zHg7+)y!%em=r%SR1%+pGa=Y1huzl|V$o(QILx+qOV@Ef-%+y_IagaRrx9`}xS*W``Sp9d%y!~Fn8f5Cb^Je2-w*@7Pv%whXI3#3SnAo4NG*ZvP9QF>d|cV zvX#d)bkXvmL=CeC8I>kj09DkVPH1Dsi93s+N7FcjCg)otU202mmDQ1oSkP;xVaHMky+mZiwyd}grx6VM2?QCuLFy|d6oafMR86f@MR`&C1flLMdgrXDC+a1=LwHzd3H$_=#9b{v{shIUdTAk!rlOJ- zD}E78p}re~N?54JUOiSQz_49e6&9*&FLgQB!lTSC=PIap7)!)}{6JB4}y{U8;coZuPfj#MTT`4%?h$~#< z-BTUR^nv~9eQ-%qxjpRZqkUlZvA0q;eGJ}JabIW8e)7aPyG@F%Ox<*y4>vMA?`cjt z^3A=6dY6apGZh&(BCm*z)16EvY?HFd+)%2-J;NeM(0RZ|=FWY-7ZZXzsx;vlfkY(V zygZ)}c1!>sp|Up#s1n73TlAL5??{?P@E}+hCW!HbZ!qx)EG95dFGE6kHmOKZxg(iS z0&){Vif|E9UF_6-iULyPLc~3%l-ds?60>J*PJ6_ufNl^apP;J z)7bp4s~xifo&EpHPGe%O>BJWgmbf6dqcAhglE&; z|1Y&W9!u>eynHYe%s&K4W^76H_nRm3&Y>& z!vC(ig!FH6;a|5(fM4mtpX*@wO|3AJ$;|Jf@qgul_hC;I`uE|Y@DBC_|He~A;T`M= z{`^S+FNU=@wRU6YINIScl$_vKY(;T+7a`1o%@&7u5sJskju(e_$O(L=)iS*64cvAJ zQ&;vYf_udW@UyyNByXEqF&MQCgl~_46cOXGtNJv2me+T6-qp?IS=V}?t53Q%X+DS$ zxOlAkFvH*4+0#e+Ev_NwesmV`*ySe~A7@0OaKw0QK@F$BrQ;AhMnyA-#}>AE(=Qw` z9(&g#oW2^e52=GXNvVGQV!IqwBQgjZ`ugj!wIDYA<3R_}qKQ})L9u-$K6OkhnZoYyIXX__Ez($k7XD5szn>wJ@Yt z9=v7|QXqN%RR^xmitDvSapSejl3qP&ZM@O7yiqV3o8g8^4Vo=lT48M3v}4`I?Q6E0 ze<02ErKWgQufJ-5yg4TH%7fmbX!*G^9Gagu9+8hWV}(cjdh9APZf8)MhNWO? zlkb6xZ5SW>ah5m`kENfayZTW$9y{oHr@l?-9r?%O4}B{hJ5;TwOW}Ad<9VllpRi}j z-*^q%b$Roma6ESZo0b1>E^PkT=H-Hx|4hXN&VT*|&i~2_oPX^F&i}>>oImy-ypZ+( z%(@Gl|CS4!|IQ1Xf7=Dlzxx8`fA9k5|KJ7A|KSUmKkVf{+yOo8g52ZA*P-`*>t$TH zAH)3^?%%}yo47xV`?I)@;68%;Gq^v4`!{g^2JT>vyR;nRIEv$)xzolS###RM?*YlOa24%fg7Buq!r;gz;H!gJn8ioo-Y=P z2SbTzc?5-Dcxq$^PvNU`aEiW49zq?l7esuE%kC7eV`&yjplhh}d!mIvxi6RWk|!@5 zD}Rd?4yCtea=^srxSuTCu{xk+isC6ES@S{P#q`qWKw0{fX?>d~t zph?M6v!u;5=o&2p+zizOZF3K1$LmXJ7TuzG;~{NY$eqXCxRsYd(1dUkGsejIzUm-A z3*qC@6G&HhJN+CHHa4f6r5a*YD-BABaT84G#cX?;W4FjyTCtIiU2+LdVk5SWB0VlCC9Ayz4RmOH?vq)JT6M0Bq%_&!yim*23dq2xLp-njUi zPfd7aUERmSc52-gR^RY~f>j4Gn4{K0n3(r}0JB6{sAxq*E2d)#2`f>@RD;=a8n?(= zks(4QzEP2(f@_wlVd6gOiY80znRY7X2X8yDe}8Mwp_`Y|!j;yxyi9LpMinnpeOoIwsK7#AHG)smVv4a#ieTZ^*IyG<6IH18 zjJ75gFCWjlaOYu6NXc9*o&InXdhQJdnv>8AGp$136MWzo!<#s+GF;fy&X|U4I<6VG zXzTu&xTyc3(_<@f&Bk>Rt~t13h*^eM(-30@pl1Sd7NBMW0y_@I%M#P3&nTZ+F{^U+ zMFelm#cwfwH{rJyKiXY&AAbG#J%rz9@uMwNzl`58{CyL4L4q~uOvzxRw}GzbOh#QJ`cB z6lrhtwx`r*?o5mXrUqkvFSXn87@kD9{QN)thx63Y7zv)5%-_rSJBP~&o;o2Tafbez z=dleRbx20y*CKrWtWBMhk@%GeP)g2Vqt;TWYy9iNf-9f$F6#wD}7~&+;pky zV1@g*6wV|&lET4^0d zJuCMHCOl$wB5yJVu~=H+*rk+$v_vs=n5UO&uZ*&?vN-13#)vFaztSiWkMI0iJbwHB zc>Ipb#53Lb8i~j6ydTfn{lm+}tlu@MHf+?P7Hhk6KgHL2xiM7$fVnR4Qaq2klT$}5 zaCbd8(JnJb%qSYUY-o=V-8r`Mph26axoK@||44h6GskujPm(=|F+i^lR#DuI5aC5& zM@bPDxJuN-`DTi=qi&X}vPot+-X2xioEPqjT;&4N@_-Kc(G^t8xbe!<`cv! z@{Kda#){b2r>m>|F3j$x513I*4Bgjx*8#Cr;G)21ERgS|+1E=BE`yKH2drewExeqh zjCspTu;qs{V|fWpO;*tA?#5EiByR2YVG%Fw$=TXx(nf2bAEVx+&FbqmjMck^*8$45}rkR*k^P?)5}d*dL5@Km*=+( zQ+UI#6i~SKMmJtf-5N@QlHDOJ*?+|HENP zv=Ow}=Kggpgd=edVI$wqOGKppMjSKfA?=@dLSLm+ z?WZUQ`z!d50|JiXc9M@V5GNOBa4_O;J(1#&7R4*Ui)cLPl-T@32HzeQw_$O6MBF|j zZinfX%jXPm`sFRTminCa`MlvtJ#%+XaDgH7hLyR~N2BE$V)^9F2;~KtlLFoDBOvYKGG&Dg;#sK+dq|ABx5A`%@z~M?8Jj$Xz6!{)3U5EuKDZ zmYQPxl(R3h{K8k(()=?lf}c;%S?an<1Xo8@cJ?X|<7?CZ677-~<)U_c|k2 zCY~1kf9$;tU{uwaH-7KrCK-|d14IcLVSuPn5F=lrj^xhwK#+;@m4G#X0fPpTLd-k|a=AMt|Jm-0y^PJ~=pCf4&;hHMpGIbt1UeavBl>tR$-6%K>@49Q{ z>?k=q71qVSRu-qPll9QY#g5U(&3ftMVSV)RvQzZ&vD5VNvorJwuygcT#?I4cIqSy< z5Q$FdRd(wBQ3rDghI~}b-=CJYa-Q=SV1IK*w#>b z3xM!IeKnjVaOM_QhcdO28sROwvGk4;iht(moLHech@U|XOiW-BjZ-=yf{QY;^F#W6hH zj<>pCfn)iR+BwJbSwUMZ2i9Ww&bC;7%f00Y4V}?y)wWs($Z=mKaM{ZfL1kxkthQKD z*rv&`0xbuwEX^7#qMYnJCwpkJCfByG8p^B~f(TbT02;cgXGRR~xJ+opZyRxG4`-5? zYKvobHqVh)>%K{e6|3#F>Z!h1QOxdad4f=sALPC^4wenDvY*qBO;A^=GYYZZ!*iLu zKUU}{sR6fK61S9V;Dy3!)!@}4>=9F#|JOX#M2Fp!Bs`S|jz=;B%aPhx;r`k(Arp}4 zY_D}fWczE~XB~2N=w>~aUb&VmeDIpIob+)2Tj7l&=7drnUgM~%Q}-Ob;J?*T_n^9G z@df{Nj=G?_=U*=Pa~*XabYi2(vrFAG z<3jk5qpnHaQ+px&PvK|6x&{D$TLTod@u5HNt0)NI@_n+`Hqzf{Fbc6oVbYA(82=mo zgn_FW>2CfO6$blROgX6Cncb4w&f3Twx8!yzssoR=T-Q?Br*>bXs{E~kER=9HYBRN+ z;cI!+RU#@X^C;1bYG?NYORW}MJx;c8H|zto0J@2dR!4(J?evgLKP$%j>UgeA3E!gB zaDzR-&Y)>`$TgvBb;}qH$~|V@!qXBFH>N~r)rpqBmS@}Y2)*%u*2*NOa!Qhsl+zdB z|0|Z$)7M{yoc=k5Hz32uMVBF`Nk297W|GtGDG{TR(?&{?m6X%7@c$L-Q{G*dA*aq1-b`|O zBPC)~a?1M|>XhUoOEaaPWj_u?c;VMTvJL*f;`%9VxD0tdk;2GcUd0DwSJM+@tKbE(I@1-YzQ7Y64A@=dP6Gqm*YmCCW?6^L63(o{PQc z_-T5}mbCFx%w`Or3VED)0EM`bMo@bzn=4P@x#`W3nrQHTVq|=>jK2)OQO42Wci?+U z2S3HfRKiy6Wd)DRmadHP6Uy+^w2j6}xtM5-%2A14UO&&4OyHAe1l6a8a+~GNMJzvC zw)88ksaLY7m}O$Q%@#crh@>(+^!>e2WEYL$ZO)2~DW(WBD};Qc?6Z-5c)K}kOwMi& zZxGq04JZkHLn?YDTry-tIo)U=fGblTxn6qcR!rT|s8yd~9ZCBtjhXDL zhYr-k`NNFR`4IVbWTTIYFNJ?36bl3jJ_9jU9Q0x_O(lJ*X-YDq(@DwlFujo!vJ6;9?7#ExcCUo58&uG+oE+NUL!?{Pn;kY$GDA zY%pjq0$fp&iBC#s(*XQZcb!kV}*A#lJ8ciTNg9G1pL57!6h;ROoAzJq|rJp7o6e zKW2i*s=+U%B1UM(DqXTCOPB*6YyxY9ms_|v8jOs<8@RDadJ|x1BiAF{An2s%Yf9Ah8#r5x<^lx|j9T#9 z_+=V$)`Ija)=jebEIpw!&(R$w5+)=~!8`C9b~{p|ojnEpVI?Ezh-hy9Jkxbm-P!34 z^^q+p*8AcUc9VZ;X4Eu*S&|ZwY=Vbtd;Plg!(?Fz^aP7AD!sRT{%kkI^nTSjsZtHI zc*b}pAwYHH;yLl8Z-UA&L$8LF2u)0eYvR&G0avWD{J5kig2xQyGDBSzm$P)#vRY*Z zyEcM}`bB6g9je444%No~!J(G0qv8L?j5V6IYY?@BsZh$Qh!mQlYh)s)rK0Zy)GCV#$)o@x0{Cpmj zTl?Y$HgoR@tC;Dn)*AS9D;eH}&|c9*H}6zGx3l@Aimn7?B2UCftcevipFCmhLlJ1z zJW2D(owVvP1F~ANzmr?6%n16=ZJPx zCWDs&_zg=mT!(fv#%GjWklw0+8P(9_nl5=N??7JlE9``pH#eW$+=u0K=u2{?94iI% z@;fV<%*XN2qn}NpqL_vxsNEcz(vfh{H@+B(yeX+DuDg5`EDgoX%SZ8!K%s=!MuXR1 ze)7ZloCzg7wME{9CLI`(r5IkiZ&NWZ-(XQRxDs(jiAIBu6C{sImigbNq(*7TW%Vta zjLv8M;x!o&b_6gHw>Id})_b)EpXT*z?se^o-{JSU--9pWDqhE?M*UHDbR7*LicpcU zv`fh}j{*2gN;c?nc-DpT^tA7UXCRShNFqJ@r$_7w(hNIR1 z$m0NWjPP09>*Bw^!O}2CQp#h2P03jNHFWNXO4Yh}J9-eD-~@|7+P~`3TwSHh-zJ>I;%U{sn0W zas#AQ2WcG&yf z5qHGXGHYGSf|uo%IuCfLTk~=Ts4R8P;zc)W2=+FA%Q{!7YXOg55Ot-MEJeFZ-E|&8 zohRxZ2EbG5saqoe)!FXI9mA(d;|ZJ^PvFvc0=LE!cr>2CtMLRryhV-#{-%h_;U1|{w&(dBKEJ!KNz_Nq z|1h;M0fovg-0w9}eKy?GHuAAfi_ChFXu!ZrK91T1T zS$G$j_mKrY(faS>ar-!Kktzwt=VRg-=$7=MWku(X+=|JaNA72HR0_AkE%sm3kT>5H zWlKcWQdFTSsvr>+vr!z+>9CcCSM0N=gHfE%!TibTstQXjAwQi{L!Wkf+&xJr?+0LT z@p%coOtf4BQ_;uC>{GkY1>+c7Fe>F92eM0eh+>0s5l#FgqKRsXrmu)5o)XbSIYrY~ zL=#_$XkvsYOq5@OJ8T3XHa3WyG*t7xjIpJm+O(zNPOy?>%``&Vm=a-R zBSxQDT8-G(@DN(*f@rWg6_!GU0#f`o20D0a3Up$}P0GB- z{RA^5DZekm|3S&`vFk2RerXy=Vwx=CXs~nS ztc)B|a>c%Ef~a<(NvjXu^_nv?XA?fn#2yY zSbow}bE9msC$>{4--*fcy$}9G`HtD13~kpwsJ(g*rwQv~d-Wbp;q9VpbZrEhw&wYz zl;p$LqGTJ_PxVFFj9y@DH9ph|ALX*_XV!Os+@i!*i|GVb@LQT?sKgu#KtGkrgPFTmE0m%GGeHjOY?jrt%-yOI%-nq! zrC6*~W$wOB*^s$=F8$o7Jd(M47EeKQZODTX$|51av(ux{Nh6#OWJ$Z<&M=Wi$?PJo zEOU2vhR)kd;4L}rfrAHp+M|-rAS7o_&JWAFM*KQ8Cx)1A&F#@VUd`*%e16RzaHxUC z?HS#&s=AeMuXaIgRh_L)SF6k2>h`pHyschetIyx+545U*R;Rz!v^gBSd9SA{T?ougiKK|M@}XacoE)ej|^c zUSS?w(w(XJA-#K}G;C zB>+T`pXEjF5HNKder26(;})Cl<}ueg=l>G${X=T{EX0Y#>!JadNecKH7odnot)eRHd$SK4+p`;Eie7>z zucQP=X84@f=Wq9Oj(^LYoZsJZC+D_53Gjy`!23yn^GSerlK}4}0n#}9Od5x88s_?q z4}e3>3EN4+q)9F+&~|eXe92q{UrrPOzUwO4{u!w4-uDMPMXuF4Wixg3LcL5yzO4sG1=up(D%tBl+x|}0eOT{y1fHF z%-i}oT*QBYdpKOgKgc~CF5(wF_=pD&sWde0$Pc6^8mkPe`|B&=K~ueg33DMlYc7PZ znG50T=0Z4aE`+zth48j01S$f-0J1R`!nlt-v$F9^hPnOi6)_NGY>*{O!fR5dgJwRy zY3AdQnU6C`K7O6#;%t%!6hSW3s9pB%{$^5>p1t{<^Nq(1i~a7EF~JQS%rU6fEMB8F zJY#0ypUe!*@#0jK_p4?$P9@oRBguvUd^3fa@$FDppR3KgJDOx?&3!-G+?b=A^{f%C zc*2Hj!GoW&SeS>_^OrLReU>p59}G2khm}Sg(O}*i7d7r$@E^A1|G(&*cp~12f3H~M za?DUy6~;YYH}oC#*za%iJe7#qaGC>;2zeazq^ z?t`zxPN8?1N@~}Lq;g<0sVSgoq;7ucqfvk3PpmqrldphObgp^HIJK)?CDSys*HM0N zW1Y2K!HH()eiu{1=k)xpzJmOMO=}lU7P=68`s^!TX93a&{;L*>aFzQUBAT=&G|?fF z5iG_XC$jYf)6)|)t>q!1e4Kq<4&Xl35q#zA5+<56cRypKnlU8RDX^qUTD!W+wZ!eIk1LbQVB*m?@cHgc^WT-`r(TPpt9Et63y$EY z_iwqw*Z7}VI*rrEP9yeU{NWRC(NL(3)U}O> z(YsUyo_4pl-9ucU_O)XG6`XkTpZ5nESEYjahJfY$`inmZG%iR5^9})uC7EO9UTPn%^*jb?pDF=#1Qa1&qjE2eX0}Z{ub~Q{c(xo;8X#2u}z11QadP z+sg{bqdM4JeAUJUM(*>*p1UJRR{*jo5H`M+;Rw=YflMRcs~M={KZ5HN>iGA*hw^NE zA!BJ3qK{DYw=+tsz9+F?k^5^IrC7o|M$Rv1=n|QK1vIf!Ky~&U;R|MH>@@k#;K#vM zkntdY6}P#1Jq*0i{i>mI4kVfI1q>bwMq?0un@D zMdKtabJPeD*6AcHTUsMX@aZHhE3FYEEJ6Tva+c{NxUPT%CLys~W3AwAm4km+Iq5|= z7Su_le9i3ph+Dgqe}rm1hd9zBPYXMaxYecnBlPKMNvFzpB~($N_#7`emMW z#EQvz2ePIDaxeYibhep7Zj_e((8gAivk;L~Sb@^6JXTL3Mqe^tFEm|nBlaxCnu?eo zNa;eZ05=zzAJBr-n-D#Y;4f!C|8;jCZ}_8byT{@7G`^)a%l|Z|t~e;>~mWxMEM^-AXto;wg4N-sWl2@va>^USP#ALQ{MTS;6htROM;> zMMgL`;&J%AE0P(kAA1ISSv7=?5irdFYQb78IPiOOuza>3NAqFCDIS|6GY`7lNgozI z&fUp}2xRCU7<>NON>+gkw0qe7_|VqOjra)GabJa!6N3pJ7JMNGj^e1m(WDmz0G~k2^BhcVirX* z%pvgP>=}4aBX-ib++t)zQqVKv&JQr7OIAIaC*ynmgr@GTT&h&MZ{53jX~t&v%(wC? zv3H(IQElFQ%Gqsk(pqPv{3(hqd+jL#E_>A^lcpKEOtKy$R})fZ?IxE-t}waU;KCS} z^2IV5?S-cb7U7c7_%CWd7n;U@$w&1!nak8Yi5faOdGjM5d)3k8GWCK9H<8ctrm}pT zJVseY$ivI>a&Yg#&EdkbIJiI(xLeJ*pG@GU-FQ`(mdw-(CcGrx1m68-yicd#o1Qp5AJ5~UixBU!HDTaqOko|!D$@KrGSep{!7UxQ{XYI__tGr1pA zxACT~7#5;T8wZ{Sbn3C$;aAcltONXL!3^1ojqY$pt0ZeQs9Jd+{7_xC==Kb0FAIgG zr^K^YU~?W%D+`44=5u-{0Sf)CNF)Z z?*F+g$v_f5>MRrL zae+ODLL&oTAW9ltf=+}$I(A5Bra}X@L$1sr(wS*FPaOhMEXzCOB88c99VrYMBXiea zG`bR$WJRK^ex{5dsj*~uX_@`QsAU%KeL&T#6g_#t#po^U5Oq_+bH%{yCS8)>0ddG?*bOctPlqLX za=50Jn#g4L!6c%?6L{P?Fl31|s2$u#g+MK<=mHMuBY-MX$H4mL6|9I#oeAs4om7Ak zUbAtq2X3N-S_+{KIEbzf5>2hZXKo&9>1aNu3&~84c4%rpc(h489BTvQ<_Awciz}6M zIp>a@6+xhVSOOou3}t;U#!EN_lY!&c0Ft zhp#o4qm&ny;EiJ^5K0+wo0XEZ6esG;hO`gO2k#wg>=Zd^uuSQesVoj>gmovBd00+O z{cPNBAo|(D0JkHd=!O%b`JX`>Y^tIr>q`x=$_~TAK07${|3R>G z>lLsQl=`-(m){Mk<##U0@Ocx%pM2)>87}x>7+!Ei469Y?4CkjZ+@EB4z{Ie0$K^Ae z7#Loe!rp#G3`<3+{7Jy3S4f4NdWDoAhRdyxIPUvFXz*QE#NQAN?%UpZQBEEJ)zr(k z@Xj0YJSwwMdWBSW@=QhJ4FgDIUrgTf$3~_5il~$}r&kVG^NUfrcx+VquY}6sCM6(H z>ddE;%7@E*VJA5!F_tEy>&Wm0Tu82Sy;*@00IqiXC_>ME7pSmXi7OCHZrAJD`-82mp(4uuTz>p^}? zMIWbjB=Wngv3zkSs3VrIy6>XfLA$Z?(cYy%GPO!AQY&=$TdY!c;o8%4hqj~RjHec^ z6U!Wwuq@VY167SX2QeXYL=Ml7;Mbs|0dz!zpQGQ}Xz;7_#cAp9(-*fg^@-VSCf|)}2xv`y?S$luR64M}H>cZDsrEgNBTie)7cS4F2WsR^(&4ouD-) z;XzGP5`C@*I(3Y63VUG>qBNA)Lei&&V0|HE77g}*z-D=C^VZ}M=VVDNG%FG`@EN0?_j|h2_n2$lsPmM;( z2~vt!2CWXBe)cQm zp2*$Z<70*C;%EnKTdYv5Id_UR=TF~U9m<1%-XQ^n@%)bTJH5;x|Cz&yL@z`L_O+)- z2FsN}Az)lC!V}wyxvGZ73AA`OMS2%Fi#d}dv7Hbs)NHB6U_w)m7bhg57Y>TZR5)w2 znm}BVj^oZ6tRw1$qvtal_Kru|c%%}p)oQsvPxs&7uGD~PygyWn?e}_lmmNY-u~9#I zM_X`QYRlbZUV7wkVFbShjy~XsR?x*s{9GdpNlL}yhD6_=gQaoQ|7xuX*A@J%iZ|5I zP?8kY7@HxBY|UB|&cs_yx@D|U-Sb*tXS=W@a6GSToK$_>hO78+&tk3k#B6QVx-qO# z%YItK-U4!3d+yFqg*oLVP1=>87JOd8nGq-*N-3k#ePf4wJYS0ysLfkDEjsRe{aZkJ z0I2L2;azD+pgY6i{_R+BPsRGTD(!jWf?Vdo2!G&)4MRIP7#>!9Qzw3Sdc!io;s#&Y~#13)QYvLsjs! z6q1+y5LqSnbX3wKR^ozb)7e&uY;NShfV6LOb4I1cUk?No@g3M4NiPYa#ez&Lc*7M9 z_QQZTSE?u=HLWd=RiKs|P5EAy|1slUYuragH^xG3>S-B=BCy3Ww}q~(o+m|Q1iMabuW^}9brRg(CteC&uHj==U(GdZGA$u*DWgS*v7cIw&(UK|= z>*$C{Xj3ki2;EVAX^B9aRMI9jMVrKfdLs!ZZ*=rPIG{ueE+2z%MoCtxoNS|`DMc!i$o|AeF3a7z8{Q zr<0ToW573(_DkWdkpunG`?9zneyVO33P7B(cp3J*Z$Yd@4b9SH{Y1K9b;xLm<}Q4# z9{(`nqr4UwQMiznY+)2v1end7ZbrqPDQ-tu0Zf@VWeT!-g9C$uGQU0=Mie%-neHFJ zwaKuk<>L0_V4I0lx*Vn4WJaH&YYBiSiloF&>qEwcsReN92%aKt0H_jl^GW@()h1+yQv{~_-6kZ>}Z!s5ZaHWp>OBWHD4)Ov( zqCAyRgz2)SvNX%uC`*3#XfKi{uUF-xO`LKb=I+t^aSsp1H5~pTcaNIDQtlq5bhz%6 z?jB9c^5S=on(>Wv_ox{r_3lwUSGs#NCD+Tid(?z{q`OClf~4I&It)PS-J>Re^t(rO z7M{o5qbV#zMt}FH3Hxw&kETTry?ZnbVCdbWDFEqrj|v8G_h=ufnv0#HkDHyQkB6P1 zkC&aJkB^GAzio;cI%A8+s8QZUyT=k z@qy77&k)nCp#{bZ(-WI{D0MG!A}9${Wkuhi!06JYLhWPpfHgQW^^FI?SaN&%C9i@q zGdu$I)_n}pd_Fwdg{u^kQ10#?>POIP`q6`8FHqa}B z{PPX@=j!qg-)@Eo5QP3Fcmm=oA@_ZZUJQqFp#8T~`>(53HhAKv4os>wT$61Y6VaBA z8q8ij#m|0e+u3+O$rzoa0S6D`HEzU24jddN*1%BHd~z#Y zI#y#`vR9>3)kRdD#2Ypyu~^dcFtBV*A?;-1?!K)CR%)0J%3&Pf0Wp{%Na0Jgq9*xj zKOVrPx8o<4KgCCWNvYD9Zyz5S8hS8cAEU1QK9+bu2B+ZHz!JIt6p;kG^g6;oKs#O>fi5dx$oZkHdi8qUbk0bXI9t)NJR6WHVev4pI_91 z1y;r4#~2zDm6)iiq$yTDfgP#AQ@243;ilaSCD-S#`!O@pMkDA{8Kz8N^SQ>FsEq&6 zT@!PS#WkS`M#%X?@6da@G%cyH&*RlHwPbvx+M!ghy0v?HHNTiH%Yr$MYC2ef`MT4Y zHEMf?dOBMVsAwbL4X-M!hbNvQ5-&jt{unS450k1^tH5*&=G1A$#)c_RfhFEXY_6O+ z7{Oc<(#32X8=Wm02Oxv*fl=^nA|Cm3Dd=;qh7`n%kE|XqNX-4vi!nkHUxAn*2_jQr z-{hVt@6UQUgRXmHxsjMnkZn*T{r9CyLSgUfNMp7Ng=O4FIZS!qLP#;WKNP7l3Zx@V zB~xLaKXi?(y{!27wayY}l&+Gd>6s*>pX6ldjDB9w6qhpB@;Sb|xFic#HdqdCHrTa} zf-;4@uA}Dc{?}x|gR>~E zHX6g7&!XuUgz51#Dw$h{#j9b~9I#NpttIwE0?yruLT-<6(7? zwCrVWqw!A7{gi8>uHO$XSTBAetOCEt_V-wW=!fBlvmed@;e=6Q3LnB*-@`Nm*s)0aj5J(q0}*e`p&ELDwIOK zHXBPYTn%7|V1;7&Fhp3jsdoDvV4ymb)nOz1427t4Pd^tcz#17e!bTbq%cn_kP=vU$ zH%KGMlyN*zcS2J3?`E-dV)dpcH#S%8%Fk7g({hQ5MAq0`XAjmN5a{ew^gx?A(5eR( zm;>`h5b8{YHLNuWl}lD(tvqZBN%FJ8If4k4OfEuvhKlfG`NJuInMEpjVTH2P<4Z;+ zaq2n~iF&!u;bEM_#lkrPiOr&v@fk|ui7BHe5$5qCB>vklB>p%_;_-`;*dj>8XDErc zPcfD!pqvLP^i@LAN>j@{M5uh#Lb)i#wZKJ9nYl7Wco-Mz-w3B!sN)?~tx9@D`TXSJ zL`Vh#wx*)Js=ykn5DjUzdb~oz*a`nSjU~^70T{XP^DBqaS;>zfUrxtS5e_jKI*R z1w;6xF=V#+a%FL)8|rdZ6&8)F9eI4=bOJmuY;W2w&2@1lo2cQb$H)q_KgTP=S0y~S zXg&)MAwnL*^9#Tt_PYm=Ew84Ue3n|CA-4Pug}u$$93QQuL)jUVOls(mD?{TImbZb+ zfo#y!q;r;V63nfr7}N%YrsFLCS!xMYM~)h@#brtNVQJrJ6Rs+*3{BD#7=cAB3o@Z8 zF<^;|Na^45EOkmNZ5jvXwMM7(+ez}CPdei%lydBsLn%*?N~w@(lsD!iwMX$rqj9Q6 zZzLTG^W4wtixBMB2!!_oR8F=|aSeyz^=DVoxkorZK-mtV?}pI7^OXbgjC~2DtyvX33V8bfDtyvX36hf<@!Am7;PRh+v`79*YZC$4wcYrJd8`|*TQL*kg4hZ zxKMh0(o`l@W>a&9lUXY7!$^=uyD4qhpGzjMZ5k?*_jnkWiDffIkJS2qLdXQ4G?_qo zCXBmWLFgly-k_8rOJXoG3*#GoU?T=G1*Snva`JvdZyxA8D8IfrnLpiQ3? zlEY`HpjKP#bl5B}N3SER7}tA~oOo`+3|ppQ2}VS5t35rp`J5F$Q9h4`5)yn;pZle#Q* zoHbDVFO0k1Z?ONr9%y(vu8Zt%LlMx z0+TxRq|-Qpz(bQQ z+_M@Unt|b-a(HOB8cqDhJn+!m4fm+8uZAjfe ztYK=GG`1RMuDGjHDsFkA;_5C`BHe|GrMpnsbQdaad7@I5kD!~F>)Qx@NmdLof!e^s zcoWKm)2x-~vVK|bvMS+RG<}bwi3{JRu&)c)2oHz`Y0^3x?4b6K&k#lVY^yO3AI(Z_ z6S4H>*Js>ey0F&I{vgn4X9rMQIR@=4O?h)PPw^RHTK=@rd^cb7WD+@f0QVx#Qpf2r<@EtwirYu=^2Sg9&suGtAZvP0g;F=UeB)9s9j0vErcgUQUeTH z_?z6qaMJWYRwpVk-Lsxg_q>IbyU1~?R-<-jHsJ=e2G_S-&Tjc}`h2U->1=U!X7D-d zva123)i}E|eq?j~&h>l-kJ4Mx2`yy%GbdwOK8zREl1+itAy>0;jw%_G-Z?E*d$CtM zY;CFfA-=^8pkjzG|0unS6;YW!335M zNS-+Cfk)%0NjR=xpRhR9aSkC^>M+F*Fv%QGvc!6djn=%iK`i%bRekCKN$$?Y4@wGq z)AnKs9%#@}Np5e0f#kS!)o}j`)R4A-@2e%S!BtN{>ZTGtyvALJlyQ6-ZcluNo zynep?oeXTx#^E5$EgV{ocIi}u#ga%84blcNbcYYMW{G8Uf#zXKaaA#L!J(r4NNLkk zwo#sCpH@V1th&2dV(|#ANs&Jd}UXH zLt$0U$!TRt7Hg31dnM!ajSRtnsTf;6_m9s}Nxdx_d{By4j1=M26lut#K_hv;kzl!= zK(M@JP|r$Fs2A}gAD94MGMieW{H&BlBL!ZdYiUUU2Q4EC;4N%tO4{={r5J}4yDi~0 zsXQ29N#@@-fbX${iv{D`(-~*K0Hv7PYLO58K?+~H55IbIiZf;mt2}=c@m=y%Tu^P~ z2&g{ns*Vob0gVnMwe`GT)t$SZ?<%rVcMPwbIdH#Yz&%OxufrDRHsLM%o#=C3Dow*> zr%`(f8r1J${s#+V>)QN1wiKV$?0X+jApvHPb(E8JDP9&5R=OcllAQmBgP7{Nc5J3T z+1NKE(7@Y0D!dD{b*zZ@r0G|6__37{`*-aW z(E{p}7Vu6fx>-ht^h&ypd9vPhJk*4H9>x9|R1fU_nG14bmpN(oOb zdcnq@x-9x72WmKq9?yCgh+@I|CrU%KOF43Q;=Thpvcf(g!UTvxDPk_t7U50+-WH+t zX$oL6ph0bGz}DZk^)2_7!zMQcFc}b_{lF=3*CpXf?=5H9cu8}Qe0+S0 zk7hlEa#itupT4=sbiayk_+P-Ynwq zS%BAo21#Iz52K`pNyg2q*gJ&*saTuPO=aY}&!OC(_?T{%FzW%55hkY9vdD>BBQR@k zNU0i9YNN|OQg>OWkU7F(gNe=G1)+y!A;@O)Ne@^DDwDkcFfytcMMfYEJ42(vg|L3; z@3FqWv7kT7mKr!}fdi!k7&TfRWwfKzC`}w_AxoWGVG1*dduntHLiUs+sQ{Z|94;hb8F)T73OojecZS9A zcb9?TaQ<*Kd}$OI&;s@GhQCwRW*j&scjKR|E>W?KIW%Q|Y5@C9Z%?!CDu$p z6i$_wGUa-6XsaofE@=S4J)Gcw1;Ilpx!nY=^J_-30OkPRHGDh)}LgP6D+nkTpsEPtEsfXp}Z_l&sQ zrcw~A0Rs@40rm-iC2a<|%PR-l3|3cO2e)2MD>q&c#^lG~1r3WP*M^lytq41*hrw(| z(iVla+5;W@luAl%G|qY^&hqg7DLN8*6Yz{EiD$TUo|Sth!S1PInGKgvP+OzWpok8@ z(DgDjF^Q>3!}w_kSG9)3eoV)KXUX2OHO1>g*;>PQpOGE(!xrk@mA{92N6E4_L=kN2 zc7q_Z2~ooZ?N-3GTN`wgp+jQeK$G8ezNLuTGr>zl>QykfQTPCi$0?%27Y)z_Ac?O3fe5TyqKOa(2hoO3)Db-|2*6f;9jQt(!%pys@m66rG&H3_ZUXo;GfMEQL|mlt#y>jcBM8w^*u8wP$F zI`d)-gEWQc1JHJTIvtdc^9Wk78tnk9-h|fLJo6;_?)P0+{@C zyHyd>T9f?(bQ%aKWC4@O&eF={OCSArC|47%}xQqO5H`S)0z@f&_a8D?b<&E3{ot!tsWD z(Xf~q8cT6~a7^xKKDi^E$y?Ma1xI4!N;FuNk>Tru!u|tsFjaj|3cakF0fcvv5OyzCTxeC#xR{Ok;U0_+@p zma+5nS{my^_~iti59EoEuTCl(5{5YUI&6tzNA&&!=_T{90#zK-^Yp4K_{6vu}E2%g!CQ)Yt87xO3NIyKZfGtbWU`c{Ss1E19==-fi=4lL%zj=4uLpfBw7$ zJR~(y{jQKmGJm+p2RDmIfot3L?T;pqE#mO?n|5y6o(LN*gRQ%6-L)tIRx(edOC@6G zmWQ_0?GRZl0-=JI`ki$TCQyr@E!7QaA)D(tiJZUri#bgQGI%zmu=&wMkb&JCvXH0S zx^3t7(59VR(sHQYw&|fAb-Nn2nTf$1(CFkp0@C4&MXr}JW*6S@zer#CU*W8YcmbCF zXv+=%vq+ti@o!%BriM)jHT*hA`bVADu%)RX6^`(cc{l&r=WX7#ivoZzkN%k>P40+U z4^818IzN=ahZ49F30#B6Rm^{ud8q-{-sg3C?(rr-3hQ?^Y~Qjy;s4O_p#(mZ!2hul z$XQ*p#g1;zL+DiDOI6!myz|i=_N_bXw%gaNTvlPP-O{+F*1l<%eP>-=!%gbdqsw=LP=bh$q=*S={-H3tOpx}A@S zc>AtxySMB@@TLYkdO-FEAKkfg%Z`TH$Lw1^`slX$`YqM-K-;!mBEPyFhzQl~e8gV2 z!``@M=Pq>K>u26HFDGYZUBi|;?cfb@6u)!JrdnX&Xy)5> zI(F68HS8jUo9vXlZl`_G{Q34`VySxDuKLx?r-zn=RM zCP@TG-;_VQF;1W~^iSZTZ_1x)^#3ERbL#0I<(C2>Jdt@1)@|Po`;Zz(@a8{@WLs8W zPh;@tdaScV)ne>3Xva~$-z%N;+56p7$-bVejXtGxqk#Y6<)YM){m zAy`=%V7-Xbkjm;T+0;bi4+w+S9?k&=?gjq?KDa0;!`>Rfd~_Z@wOTMzf^`Y~(T!M5 zb6p@-8@;nHCRUW$xr|7pSasW@!HSzeNAM~nqN${IgwsNm$n*WGb5d-h+M;5SCmLML zVFp+LFf9n_Q^T91!Fx@iT5tn=xSVQ3H27dj_;wNQjs_c3!XqMlPBi$5l<=>J@F}<* zoqf@v+E|A}Enzh^?7eA7q1%(PfkY(aDF!mM*&$4P&qBr{3@}vM~k2K9(hqA zPBj0EO79xK%Moz-ZFkjGqv#6S<7cSsG}WDC3OC~58JRlIi+OC$T?(MC)!a^IA8`9s zEyq>rDrY%u>VBvHy|6BcrwSsGyX*kW>5ZRp6FyO1`_>&1=uy@YPb3}}ytOR@KGMY{ z&>!Ode_3a?M)oh;efC0qeTU5x?ge~@;tBWA*X{|&>076I!au}U2BN<3cbCYX(6kPV zhg9uH1MyStuRa++6+iVHl6_GR>vMmWKtH`C(-X?>z&qleBPDJ~%Xydb#7B!-BW&i8 z5@)-c{_oCkyW5>^PM;ASdJTEtZZUVfZ};*+WCho$-RfX{y|%)u(JC_K>~>Gpg2liV z@6qIV{7JXF*zK{}*#>ZUE^xVBJ+j+ zo4+`5M0K^7xc2)r*PCm@)4+eRJ2aVi-m~A=QnI#)Eg27MQTL#`Y7sCTtgCi+TijSY zbaiJq>7)N|@wMzdiu~5L?A?gZOEM){_aBaN?sWw~w2uPU5?ZJrVGPeWf+YJy5+3`K zY>GbO^A339eQrO%?*cd^(t89_>9BhA0JCI#n+BK98r)q*5(|1D1Fx3t(lVUkLikI_ zAI<}3M%+Y~rZ`axJGB*tY@vB8yp;c~kZtdNJtHC%a#IhZW_neyQNFgv>2`;HEr8k7 z0k6;94l;w|O$0810vT*NyuO4}ak7FXRu5gzLx@e{?X|@_QL2`BTtorc5h5l_%Zm54 z!&LpKKmHsk-(fBOoHL%2?{qn;2D4p+YT7uM|M=mMuRG7_3aLFaoGv!OtSF8+wqi_n z#E;Z@al|fI3vu{xw|uaM9GcJDp5cX!5E)-GwGbv!3n^#D$-Y30lzC5n{II*j<=*q{ z3mQaB@>QI>U${U7kPyC*14W8IVnSTDy0#pl$H z_+d4asmbmE*{!-WpTt`Xbq}iRu0CRMwM0m*?`w&W(!U^wi;iTtT1xKw?0rYD_RXQ* z6(BFjI*bLWP@5;EVz;);LF%lQTV9a0_>nj7(_HcHBQ8)9?>gdyisDQpY6I!g2+AVI zpV|E%e?$ohQ9B&*WA5x|{8(6tI+B9Uwj*j*rJXf>)K6tR0KoxxgUj4wpYi zD|r(l4x0q*;sU0(nkZ(!%ioU@ars4wJRnNM65ICZ!>s1yX#$B5}C;~mam zK8%h#6P*o_16qjwVwbrfhvp*+Qm zybrR^-47771zCSQ0*&L&YBX5&dOM)G-;BT0`_2lVCVPc3OvL#jE>eY!ODrC?WQpQo z%h1tqvw~iduz6>)1r-x}S9YIg2MeS+hf--oyzMg!J$J>3dJ26A*AR8zA>| zy~jbF{`0UCEo;#FuSR+H!vm(A%um^5Q#Q8^_yf=z=c?kCHS5r5dx6gBe+Th`%SaQfdxG~sCkrLH4h*O7P}|DvwomL*jRP~Qyvp=q$2p*$FW zRDs@bS$#WwJh14l_)$~?@Nqk=JJo*GQS7S9S^VU?Xsp7&Huq>g{_fBojfTwF4O+ZM zHA8L~2GS9aW`VN(0onff;y4dxaFNM}a!@!}pli}Hu`!4l*JnSwle$1R>?&3N< zM{|A;4T}c`hRxC9-JHF67y9=)e@osOjz21iJ)eC?iyuXXrqZ}@KCLuz7r%FY_rvdi z=X4qEUQMAmV^gJBXw_F6^cM$0w?u6E;mgk>{8cAcGNz2A>8i(Gtq`JhOqFeXSIt@o=@k zW9`XbPy08s)gx6Ga0PUgWk3yGO*HlKTaP>QBas2QS`AN401gmfEPnvk5v$JzU;jKd zd1I}8%PZHZ2ZO3>qNEN~{Z3Y^i+_~QGTW;I#R0bx3G~VV-@rIjf3Fu^o^ja7=mmk} zyaUxfwQoHJG~T{iNPwE}=O$4(IeUZcAk?Quol&rgli>YKyB5T#s4uGLh|d`u-|p0N zaR!RL!CBGTUaucL0Rt3Ba7jSP^Mm`H4yS*^dz!~<$fY;n^=p1#>7un|)6rL@&ep7! z+g=>p?``8KvjG@V_WSp()2dJ8X7yt-m|`R=v&Bo(C=SYHI>LGXiSA zte&v-)?=Po~%VY#z3m zc0gp_)2=txsHc5zylA1J^lASa7$%Y<@WzW-+~J}q0#In&O#oTk0RXG3*M$z*1Q))l z3oyWhc!3G=9225_y*|~47(^qKNB2zd`Fs7U9|3FBo(cXyZ$J%v>BXE=HQ`C!V5{99 z(0qQ)MNLh^3}T>j3giJnc>BFBPruja>i7D6{a(Q{IQ1T$A=o;6<7n9Oys^d!-e0tE zX8wHaaTX4H{fE82!(P`wVGU`y|C%Sgo*qxY^Q2epR%La+dKk}4PVk0jc6$SAx7Y9K znc(sCdQ?x37a|B8Hl$PBL_?SFKve)30HD9y>+^McT__bl=>+7A5A=dY{XRYOfXIKK z$!3;VO8HP>^1`;Dn4C!OA6i@&3SZ3O)aQci9yNe^PvrKKwmQ4LPG`>qK0l+Ws1+Wn z71a8`7Bx6U2S#;uU>Gq2BMsHXt`lPoe^*XE*$k={t`k$besHC~_X@tk%y`5jIE z4cQL2e-*U{ZmXk9>9TIv01Qe9mySq^f5XZaq=1=PPshH`vkq8N3^5#jAItley(_QS z2P5Y@)93b4dXXnhIXDK@&=l%J?N<->xzz!+iBDy8DHx=BFoyC4Uv>EWt1@AWqFx~P z5prrNj$jwKuS+SeaA%>fkf~K*f(k@C2fCCeXp+i_K9f67WJI$(M|}R!BF-`^=5!YT zUk-TV_V7GA2%MOF(^C*>c%BlZw)@ua-igsK-i-(@!L(T*Jf0&xG*!Y|yAIYw6Ji_q zQ8KixiDuO*S}){OwNGLXQ;sYv;tzYYfFB-9$QD@#y61~S^8YH|^)f^`6~7iXsrY3p z!wxi;<7Mm1%DP`6&@K+5Dd-HTej#XTu#bW-Ox5P0CD$yh8W8BuIwD`+QbILjYOZ8e zbEtk%&xAx!MEpJ{ymb;Vq{9k9A}$6B>Babd)maJB{+&fd?d-A|TmPCQUgV+sh3wWC zr`9{r{n6AeD{1bMZ&|zY1UVuR7cZ{{yv{KPfKlPc^E|V^ zRR=LZXd*>-h9BLl$O4!VTbC1e}9T^@YLxE-j#jUZ;%dOszZO zaa8rczn)GY^c6zRY&8lpbk{jKSOEw3!A*gT0x;4e=SJ@2q4$%M2e2AAd$ZU3dipFr zpT;OAfN94Y9X#4kGj)`QgQE)=-Zrm8_O6?O}`TJbQqn;z_*+sloDj z+c9yJOiR6O+~BZbrsj2H05UMhyj3%?zyt@`XUd&SplDN%2mmpKZ$w=+;32C-&`$sw zrG$BYKhIb5pCzKH?L6X92NZRknT>HBGLOb}5(K3Sn+NSadZF9mQ_=b4vUKoYe^tJA zmPE^{@uO96_c@uzRX74X$Iy5oeeUU10-Trt2hrA5I0RXE_gX+}75*jbWY$M{wbK-a zf5Sw>9Z>JvGuVFtZw~~Zyiv~yq07*L(?&ruH9RXwcvKec|>u!&$la>!ovY{ z)OH+O$E+Ss->Ft!7h6J3qy|Uf>=tJ^7IR$g-xCd}e`ITlO}56cKLu%F{EaL&wVl<1 z0h5}coy^ljAYRUb#0gEQOXyoy*H3S3{$X~7W>*c>b{ zS?T2&?P>@cInWw5Un{@0dQ0Q2kI)9b`F8s~uE!d-NJ|XILc@XGGS&7iJ9lD}3+;}1 zuwh%{7W<<+wl&#zG-97nj+C>?w9`l5u(Ro*EqCVRNaNi1(9R!cj&z%jUk`C1WN~Vc z+Z^=hjz@OXg?8A@n}}wb0CMgTyNAT~AU!wtrX4pn*z4-I?66no?-;^5 z^zg&@b_;hFR;RNttA43}m&2#EOyJJ_nx3CQ!x+BVpBp`nyv^WaHP)t)f6n8PNbWx) z?Po+-f`0f7cXo5q&E>X7__KE;!?XWB={C|Ozs4>SvDfR;za^vnyKVl0g^L#7zT}RQ zKbhfK%{Luwo1!Z`XnhK+G6@k_!5qQE%i@J{ORFO@IQH&KLN0p z|5=9lqj)=L`3wKy9JZ2~m2DlDlbbhw!bIDo$y4&Lx_WBCv}>*{yl(mo`^;Ihi>|-n z#^Rf9o-_BBdABAde&=0|yO)-#LdXx*YOZ=3SHq)?p{B=nZ@B*>4^(Xw z1>+AaTVA%}o|VD!d+%Gddd=E(6_xA5kAG|rJze@2{=;#2$sM@Qy~4R_m9%NsgWI-A zx7KdkeCvZXJ0Gmwv}@O`7oksmA@lW#pPTm5o$n|&ZksY=U(Rd!Ratjs99dI(9V(W!&ezY4Wd1r`_??)E`<-*Dsm=o10=2zP89?`Phcs{OhkB%)9lfTP#zS{lfbD zyzD8{Zhc)o5IVkS^WwRSZ<1@jRnQkbFN)D zzi@Ei8~40BEuMevRiC`;z<_&CWa{KQ9m+?GeeWOJ`jz}y*W7-?mzM5*e}{kk9k(yB zbD|b~^w6Rk#tZsxTmL&{dXaF?3%pSHx=py}G(4RD_rfXN_>q~5PwQ@gTE3{eKm2?s zfe$6{f0+c9KQY6uvph4j=z4?u>#8154EHrF%5*CtNmF-EHiD%oo06ybY%A@F@bMo@ zIHhsbNq1Ix(w(6u-O|>r)4p&6{i_)qxcBXu{y87d{${3q@b}J7jQ>x^!!y2r-E)KQ z*8F_d+u64HKf3De!QZYrxu|u?iHX)ppBOyrzV`O$E#ZQnT=VMSnMbPT|J|)~7JhuX zeem>}Hzu6So1O8+rIEqcJ%@8okNek~%5wj1@Z|R2%{ad3?6qH?asS{i*S%BvU(+kc ztttK3;Lm*zOnV}C_smJxUl=@A|9MNz%%aR)Zp%3G&)|;t&$p0Xtrw#Z2pTCjTg-ib0 z_)DJUzMNy(V>$0EjrRQVFlWApKf63O+J`+{O-HN8#MCIyWdB(h81|_8WuHqQbvb>I zJPlLki39v2UL^2=p7S5`ss0g;+YhT|e-YWH>2)d%bpEI4)vu3B*rmkz|2sP+Df5&h zxk@aOCnEka)46^TCP@T0^7$WmnxaNX4gSo2@v{EtQYcoF@h{LBEldzkY-;~cmj zI_*3Vo&RYM(j@{bw6pusgJH!V$ITXJpC;#Aax&zAJ4DGDZ1zbO4y6SP$VpII@a*^h z=fc@1;f&PmV?_Ym$F9dO?r!NsAg(1NPki8PVWvc@p1GwmhYH;==WG-Jp=r^e$^=LU z{FsE@SdDjNvT<=ohs00$oK=yuMLv5J(IOFDceXk;Q6~~1ZG59-p-aQ3h*g#1`*|o~ zMC4pQkE9K~H99_rTobw`iL}OqG+d;IPY`E~2v-;xr&4<;SE6+Qnt8^~DlW<3^n-7I zgNsDt@)R=T@)R;-#JJkhDfj6!-N!cJf)s2h8#rqRt9)I-z=j)UNvII#YD8&6l9q3l zEIOStb)KSp1(L{3N}uqilg=zYk3+=KU@I5&xwBN{qoxvfa(je?yFuaEXOR{+DB@1B zP7qj)AT4klZN`EDIVuou`Jt`Vc0fJk>h}(>ypYx*GxV zLE}_>c88hZ*>QnZOw5@5*7$G()-7}z2A26P@z1c-hMpDq&HfqPVy}gz6)@4gxqQ+fPJBybD#?L+x@1T=_@fy!Ug+t?hf}c`3_7n%CtG> zLrB3KpcKj|`~_b9bkjY$q=RXgpHhVk%BPM)*aS~Hj^bk)So0z(wePO5b=X|HEy-xH zS(dne4x!jYu! zu8oouCE*|p4`paF7w|n?gugu$4XkNH)x{%7v=ifoeTJiQhrB+4q~qyeJ?mg>GCD9$ zMhC1_T?s{DM({p{YOyu*g>xR4vB3Hc8O677%2s^kwYM<_j&f!!Vek zT^=#^WpH8trcl)U^Ac!ccebo4qb)#%S|`Ejc63Xtc0RZZg_iGV=CpVUKB2Vc4_JNK zJ-oDt%rKb7&vh45&7NU%wRx=kz(eU=T|u=!BG$%`gNGh?O}H5HJt~M(d*P?368?*gk8&DMNl+_E*G^bR2b&BYJGA)7v_Z-aUkct&>R$=U=Hjuc-;;^A#;j zdK^l4{O#PBY*azXGBFPhO7sCQEm68Wth&Il4O~B&+Zto2JC44cO9drSLMDl;B;sK! zc!-IqdxH9RU{=iIgB<&XY%Ep4cc4zGP{Q}4>t^+2QH3j(pWwhOcBM8?!W5{Yw8uO- zp6#(6vBA%d7zc83e3^$W(*--t!WjJ(S&(sk1&# zJ_owlZZW>R#(Gh2fxaUY3pGq=fv(r7`b4U5s}yY1nx*`}C`t&v_R<3`v7HL%b6CtL z+;$1cQUix=R5?FvquP#gh0l3=oFj;6#z~_+_%_(WcmojY!uLFj^!^L8JOE?QiDwmfjPke?Dq(oo*Z4jd4UqdY)hm`D$Ut=7~>HK zP1!EAsYnUWN1XA`xq0p6Ox4opGY=5QG3msX|LfZau1`dw3a$4f{P|%SoLeD@nza73 zXR0oje@EkKqLvL~NpZiJYl&V9x{(|C_f zR9z>FeNeh;pV@|EL%C8JOwKoFa>`ouOKB3I-dDcwrI?KCMAdP0T$2m2b zeoA*cXOn)+JJ9vQrr@Oohs{(`53ELY>nNUo*c_NeLCC@3&+2Zb5Z~#1UfoDE#g@ZT zU`}x+a8~gG;I!g}z>MPC4@0^-DRD34@%u{&qAQ@eF|(Jl(5L&;K&QSiUbOf=nM+( zJd9Bl9Ijd_JjD5q8IF>fW_0Zwa%Z6n=Z|GLR-O?vt))y!{AsHsV|VGUHNkX8zGLN; zTAag@%UZSuuU=WZ*6hNAk`wrxnklXa#RW$?)fFU;Dnh?;(1?#OZ1_4tR+#>suz16U z;-(!?mAk16fdMNEiP-{uRXI>;ADwVyAL>MMMKRR5JP7PchTLB*5@z!5A8M&P+4gixPUKgQ2pQ= zo5(oO8c07V1$JO1VsR!en&@I7E;iD|0$kjOivs+urGVRUfzKG^;w)U0bRU-Jni9B` zE+~OTbU_KsrwdA8E-om6SrkACOr;AX;M*59ilmXv)1C${SCglx;TRtvOshXpJxCSB zh2FI@ikEp;XNL;C7`hme{Z|z?)p^(Egql`s0okEjwX5)s)HhMwV1Nc^(6JiC2Z!3= z2KCM`0J7x6=>tpTL9;A>INfXe9!ifqP}n(8NHxMw+^B-^cPd`Q^}pJXSMfk0&On)t z;ek?A>3M-4gA_M1Zp1;cH;``Uz!L7>1+JniwPc zlodotQ=1Vo5aXB0a(i2vWRd(=SY@fTDrA)ntu;VnYh~zD)gkz=)tU3a{RjHYfpBgJ z47zQ{_s#9YDFklY3;QhUpFH`ESu#A_lR#m^*2b~!D)b(YnNAm?7pajw{|=tx z)Eyn0si~P7ZH3w8b1w;>q)rDjDo3>U&>rihawN3f8RS2m{vjD3ZJw0Z z96LC&H3*RR;v3Fmvg|BO{tOCfrl(XFkNoO~sO$XLtX`u(7M~XB9bM;4*_nUDjaJD& zWJ9LMY~f68oT<(B?5#NY+U37)0;)oy>UP7}yPNTrgumP)5ypm#0uNyL)EvOE-3_?V zMwoE47>_Jz12H@#8>vV5Hr#i`x#h4;&l(t$2TxI=^8!D3071^T*k~8DG1are9U6D0 zSnwG2nRJTT2`Jp4(BDYa4D`57NuN9+q^eW@Gr|)18F%>-gsUT;+=d2*n!tjbh=^(s zqS1$q^92ELe3@{kv+?Tx1U5Q(=3)pV>wMAU97@a;7pBCLb2sp#Q%`3-bE0Y~j=kxR z|6jG;_{e98L7Sozje&M@Zy$mePMur*}x^eu~U*yajT=PC;nB;yNS6Id0z^^pns@V%otu z=-wFGOp;-WKR0A>3*C`OtD)z+Ojr3QCZ%$`K<5SS!%S&$x)-C0`klb7%19l(n5)&Z zZ7}VSq>qqb`0Z{Qj!wjYORjB%5p9vxWkmeDtm8ah9@C=0xQHL~%Z35o3~RcL}cUpBtM5Gt5rp4~Q|t z#+fIEQhH)Z!t6~9n^Z5{B7}0&j0p@(%gO7m5+3lFGrbdVsr<45!xiDl-LgB4A^rvx z>}RccYkQSYMDaIwwJ2*nI%iF?|?8Z(w~mdW-JqWek^ATy`1*viiqH4`MRylmjd1 zsUc8;KlJ)VN3uy+7D|2E*>E;7Z{2H>h~C*|z^fR(%b%sU9El=dD+WfqsS_0`MfP8s z4sJ#5+vc`5dE7M8(bfPV!cAGRDPV#;zNwuCkNS-0sQ&N~-4t-i6JH`jx`@|^fdZEX z^>y>on4~5mq7-$~yA$nTUf>-}(&GdkuyucVC_>Vi7BIn<>SBq28JC^-{FtQ?`O=w9 zs#4V+|237a>zVFey)>fD0#%u2|J7D{y!X??wE+f!|B`eG|WL;kO6BZ{znJ{Ju-=ZR%f211Sys z|EB?`bQjkJm5feC7vtTGRgBe)HH@{4s~A@^-oxl-T*J7Q@m|LJ81H9X$M^ta9pie& zdPWc92F8ty4>C3|Zesjf#)lXi88tX8a@L8OA>`jxhe2@hsyx#!<$aWw;FV$I)F7VG`4yNmD#Y=XLFb1UCkT8WfNi$Y*I?VtbZiBK-HzH`LYUU6#5o7f zHdL*cEG)qu-5pr4TTmUiuGmr?$S%&T4x|?^uJ&EgU4pvG@rL*Z-wO6ev6&PC+Hf(OwR)C>h@)pN4W=| z8!axT*9F$F>r)0c<4(#NHjF~#Owz%f<`6C|LFcz&VBY%|E8A(y9~&08)5>;? zHf?~Cv*54T>yI`;rgiX+j(hqqSNAQ;^JD!*b;}Pf#ygYmI=D&zXRRhnJP2>r224xv z`2_j&EcWR%dmBzMUL#xkECab@r<*ats@Vx^_%ZP{63`5Vhb4F@h(9^YI8>A3tPU9q zE95{+$I)t7#_X?SQA@G=a<}rl9ka!l)$cCt%yPArS*MkiEIeW?p$()8T!D}A6Wr52 z+VD@T;$TvXtD8tyzS&;W<6KFOPla8dtlN$4Fx5PLPer)SBBS9vnt9_`EuxY;|2)>? zbRuOM@Sx8pun(6o!`NXAtbskDFd)4wlaa*HK*Yd_(Sg!_8kr%a@$NFdBc!_uz9XbN z7u`8aJ2CQp#K0rtRDE#(%_uj}j55u6TUuvd>=`v+FDUp+&jn2B=CyH|@h`;KP#X#f z6(GL_Hj>rL%ltSx#}If2bLWBIT4htWnRrs_A(jk!^q}Id9QdP;Ks$be*^W0ns zQrvK04)*MIJ}D@JDyrvOsCqjvJ5s%I_V}!#$N8lMwD|`tl+5=MJi>`Z2O1s00~TzJ zOk2G^i=?58zn6@5XaIjO5w9*^3Ogmw`4bBf15a3CSj%K*daz9K7hXantcy80WTy2o zG~>L>4M&~>@1o86>?p!^lw~d4_g>J^5VSWQiy@yKY>>Mff{nKBM!2v}d~&Z+wp;P! zmnm0-RL7yc#BfHl`kh#FnKi4dozT)&m zWJ%wh>GsbN-8(&q8WgP?R))JHbQy!04o7sw8YUc+9S-cyR$}%~-RmgZg&Z6``3lyz zJNFBQQyC%VE+{$r2}MDd!)j7!XZktd65BayS9yUiehTe@gI$~g+jhXb>TQIniLFO< zzVGTI4xw=TkP6@Ep1+%-CVFY9;Uw##K?6(~pP+S$@Ss~Qh(A*Ex@L<1MttyK_LMBZ zA~ZenZV!kCGqD2BSqi4q%4pA@)39#V>{`}REPg4`UwA2!5LQ8OITD&H{qvmx*+C-W_8k#1C%@Iu?G@=Qr3`rBapcwcB-Ci2+ zahmv=%H6|eHPK5;Es-X)1oO@Dn)te!SB`liO&|?T6TdS>G=b0t|17IwXgxP(RgA5F zxM)>Ot#4k4@SMIkX_4M57HO285SoIh!cSq6?4J^B3dnD`%WS$6CGsJ1mTw8!&!s?|STyPYn- zVUnrJv~t_$CJVPb0t>tOK9c;TOTAktq_3)4|3{;m=%uBmS{Q4G;uBPigP7~p9%!Y0 zpPE;J5f)agW`#<9mQgUn1 zE_d6pB8vV5OKs>=p}k3Z11@uvmA~ZGlxTM({Hy5M5#csHI2D_ni%g~mv#2@A#6IYB zj3HIMRe!;TlsHua1};MvB@%m*mYs8?ENlo`ZMA2glowDgIVD*2mO%qeFVY$Y@WxBG z_B2aEAX)SX8WFW8F}i29K}7o3B7l% zf6Zk`|MmfGv@jp%Uxkf$n`!am$f*XJ=0!1bszE)0*SK9hed-7bA0A-dmqPrD#RNzm zH34n)Bodg0b&-L+RyjTJy#LqvZtUys%)pC7WX&3_6`{RJv4`*0pskFzCM|Gn^^NYb zbyneUyA|P(GuTGdJaDqYQf8r*{TxT#+tWO7yaHia_R<=QQeIJEQEcTE_EI`lHfx35 zQC`WR<&_Q#D6j)3(N))$l-R4QmD-95x242+m#e~FLs8~?rP@mSJuZ6*ZY|}NHTJ5i zS{GtTlIOuKi?L6{I!|45ouriB%mIri#Otfux>fQvZEWTfb#c?UslKk64%*#_Jv7I8 zGQSj?YT|$ChE>k;(i>v_l$A=@Jg}t9Ub2!{$KkwSL$cppTkdj!yHfkA@)EnH#%olJ+n(O84H0Un71`;r9Z5KQ&4Zd=-CxW0dZ@55HqZ>Hc=3w9bOxM*I-| zfXvBMyQ^#LcUj7-p;Mfw2Gy5?YcNNARyiwFrmO5VwN;gt8mHwh8|fZfAVNT$#8u^V zRN3w#HK_bMOG;|1sz?*HRb*Z*6VNN=j@l}gg0iqxRyxTwDoa@=bt6+Pw8Ci`4bcG$?R%dJbiy1b^0+*0?$O50ub1yBhzo;;TNu)I<)atV_LkuJSz zXwnw~$p;_!Poq>fNBb?rU-5TsyrI~>ngYQVlw7{pX<1QQ9kJYGBCV*6iei-7590R* ze(&NpjNc#eJA>b!@Eg(ckg984E@xGZ1+Ggz@2rw29f?z;vRun9A-O0J+-<=|yLC80 zd)tQkW*qkokt;TBdB{@Vw0%pn<#tPU=FRIs!f1_o|;w6QPi zEhiBvf$PLcsoJrzsqQ1RLt;s{ZKk(`7`bBkp-Q0vX9?U^x2S15wGK-cEm2L$p<5a^ zdDS+fi0$Kj(7SNcmipW3n>KH5+On8jsllT;fm%DNi6JdDeCeWF5=HdZZwbdMWCD)2 z4T`3^JV`8c7B+0zzL`Z*>jaMFeRwmOG)W{cdN{miwHA$YTVLnhgaY>8=V;OX0|Qa+ zg^RROa>)({r`=pvzpW|ax~lwtjU$rjv8Q*v+G%)j!l}_p;d->K4=?iKDSe5V`uEXlAiVwmE#d9|Zw+t%UliW{e;K_yr}qE< zNOYgt{~u~h?f?HjWB>ou{@%?Gh77?f-p=3QLivG?3CjN&_hkq%@GyKuQBC z4Wu-X(m+ZBDGj7FkkUXB4LtKDse8s_C+A50$^{94Z^QbPGXy8c03V#5-mi?(?RiOh zto8iq>AowsO=)W#9rJzc=PjfCSSQ$~j2gY>cIABT@NfH-F@x0JI+o^rIHX+YY8k6M z^zM%>GoN}V^!;B*xE1a~EgythPKH{}gj!BNvp}-UY=13#r*ig*zUIr+c)f`CR#)&B z(oSXM#Eph7<#gAoGdq>zJ6FBerJU?q^}$Z%^v+dhx|HKxtKQqGoZPwUgOD;d{1`Hh zundNDzL%+ddzZXok-F|5>-t&OTU|d}^7^^6U1tuxt7SGQL8zVPh)9%TU$0Se^-7ZK znNcYhY?>1$|FD+)C&x{GXUDrCWYJ}YsLOHPeC_z;%E<7tbIMpKwA}=`;xta|wGnId z3$+K_U}#@8CJi4G<4ghNw60aT%U8|vWfSF0Z`!K{Y1s5?D5O5wsa)8(YOG6{ z*|lm`*RKx!&bZHOYX50&;lQE4sQ;M`byZ$~5tbj`ZeG5>`IeuzTp*R4U#`q9`nnx>a*=w(!rkVDsKzcNAv;H)(ISnJu-A>Ykp>5&h-3xD1+@@aG7 zvH2m%GsI@{S^*Y`*-Sv|Jf$Hl4W5v?SCXGI?7#t-dn)HY za+Yp3f+M&-Gj<#~oY)o$>x0ao8l?C6iETk7x-A59(H0^}lr6qxASL9Awh)p?W(zSn zfqcRo$re_FV2iNC;R}e~+s3}w`$A&T+oaw$-6@Df7hND184*dMME{;dqG!SWNRgh9 z{o$9})5t>%G1(_{05R=L$^IFdWMj4$VE zGp^NADD5(r_8|n5h;1Z)j<1!2LsNzzd^QVjka`b9>z*RQU!OasE1sU~iWg{K2I1l7 zj&k`INhto8kD~mO2UyGzr++j=oUkQkcq5$j?vKj9J!H@si{peSbxwp>A}1nA6epiI zMU0S)lh-EA2~p~t2(d&?M3N{@vZFX5!=!n$!-jDTpGvHzzDYAjlsa=lERi{pB#ODF zNcDN*(NGfR#`C7bQEakUQQkz)b>;+GkvWkhin-U-`h=^{WHx*&D*y4ki70hWgjgac zB1se{Ws#Z$#7vnEcN)yr*X+>5= zk|zQwBZ-R9HWX%1} z$UVEN{;G@@NsQI>E)dOkmTJaK@9HjP6t6~_rbaPH1Wg%-i|DPA=yfAT#T{9$j5g1t zax9RX_#8$;&Z99LJv21@eD9*D%$O6((d4a;&e6;@I8p0a2{GsApjwXPCQzNoqlyJt zAqGbp$Z_sO~9{IR^e7etnn8WkJ+1nTm^y?cNj2| z7FNZJjuWlu>|y-pRg=`NzHy~6rGb @@ -26,7 +26,7 @@ @@ -222,15 +222,7 @@ > - - - - - - + + + + + + @@ -565,6 +565,10 @@ RelativePath="..\3B2\3b2_cpu.h" > + + @@ -573,10 +577,6 @@ RelativePath="..\3B2\3b2_defs.h" > - - @@ -598,21 +598,17 @@ > - - - - + + @@ -621,22 +617,18 @@ RelativePath="..\3B2\3b2_ports.h" > - - - - - - + + + + diff --git a/Visual Studio Projects/3B2-600.vcproj b/Visual Studio Projects/3B2-700.vcproj similarity index 91% rename from Visual Studio Projects/3B2-600.vcproj rename to Visual Studio Projects/3B2-700.vcproj index 09106192..f5642cbe 100644 --- a/Visual Studio Projects/3B2-600.vcproj +++ b/Visual Studio Projects/3B2-700.vcproj @@ -2,9 +2,9 @@ @@ -26,7 +26,7 @@ @@ -213,6 +213,10 @@ RelativePath="..\3B2\3b2_iu.c" > + + @@ -225,10 +229,6 @@ RelativePath="..\3B2\3b2_ports.c" > - - @@ -241,10 +241,6 @@ RelativePath="..\3B2\3b2_rev3_sys.c" > - - @@ -257,6 +253,10 @@ RelativePath="..\3B2\3b2_sys.c" > + + @@ -566,7 +566,7 @@ > + + + + @@ -605,30 +613,6 @@ RelativePath="..\3B2\3b2_ports.h" > - - - - - - - - - - - - @@ -637,6 +621,10 @@ RelativePath="..\3B2\3b2_sys.h" > + + diff --git a/Visual Studio Projects/Simh.sln b/Visual Studio Projects/Simh.sln index 3a213c5a..b3a3b940 100755 --- a/Visual Studio Projects/Simh.sln +++ b/Visual Studio Projects/Simh.sln @@ -228,7 +228,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "scelbi", "scelbi.vcproj", " {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3B2", "3B2.vcproj", "{56178F08-8783-4ADA-820C-20C06412678E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3B2-400", "3B2-400.vcproj", "{56178F08-8783-4ADA-820C-20C06412678E}" ProjectSection(ProjectDependencies) = postProject {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection @@ -373,7 +373,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tt2500", "tt2500.vcproj", " {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3B2-600", "3B2-600.vcproj", "{A7AE7747-DFA0-49F5-9D6C-9094657A8EE3}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3B2-700", "3B2-700.vcproj", "{A7AE7747-DFA0-49F5-9D6C-9094657A8EE3}" ProjectSection(ProjectDependencies) = postProject {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection diff --git a/makefile b/makefile index d8129485..f6044897 100644 --- a/makefile +++ b/makefile @@ -2147,27 +2147,27 @@ KS10_OPT = -DKS=1 -DUSE_INT64 -I $(KS10D) -I $(PDP11D) ${NETWORK_OPT} ATT3B2D = ${SIMHD}/3B2 ATT3B2M400 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_sys.c \ - ${ATT3B2D}/3b2_rev2_sys.c ${ATT3B2D}/3b2_rev2_mmu.c \ - ${ATT3B2D}/3b2_rev2_mau.c ${ATT3B2D}/3b2_rev2_csr.c \ - ${ATT3B2D}/3b2_rev2_timer.c ${ATT3B2D}/3b2_stddev.c \ - ${ATT3B2D}/3b2_mem.c ${ATT3B2D}/3b2_iu.c \ - ${ATT3B2D}/3b2_if.c ${ATT3B2D}/3b2_id.c \ - ${ATT3B2D}/3b2_dmac.c ${ATT3B2D}/3b2_io.c \ - ${ATT3B2D}/3b2_ports.c ${ATT3B2D}/3b2_ctc.c \ + ${ATT3B2D}/3b2_rev2_sys.c ${ATT3B2D}/3b2_rev2_mmu.c \ + ${ATT3B2D}/3b2_mau.c ${ATT3B2D}/3b2_rev2_csr.c \ + ${ATT3B2D}/3b2_timer.c ${ATT3B2D}/3b2_stddev.c \ + ${ATT3B2D}/3b2_mem.c ${ATT3B2D}/3b2_iu.c \ + ${ATT3B2D}/3b2_if.c ${ATT3B2D}/3b2_id.c \ + ${ATT3B2D}/3b2_dmac.c ${ATT3B2D}/3b2_io.c \ + ${ATT3B2D}/3b2_ports.c ${ATT3B2D}/3b2_ctc.c \ ${ATT3B2D}/3b2_ni.c \ $(NETWORK_DEPS) ATT3B2M400_OPT = -DUSE_INT64 -DUSE_ADDR64 -DREV2 -I ${ATT3B2D} ${NETWORK_OPT} -ATT3B2M600 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_sys.c \ - ${ATT3B2D}/3b2_rev3_sys.c ${ATT3B2D}/3b2_rev3_mmu.c \ - ${ATT3B2D}/3b2_rev2_mau.c ${ATT3B2D}/3b2_rev3_csr.c \ - ${ATT3B2D}/3b2_rev3_timer.c ${ATT3B2D}/3b2_stddev.c \ - ${ATT3B2D}/3b2_mem.c ${ATT3B2D}/3b2_iu.c \ - ${ATT3B2D}/3b2_if.c ${ATT3B2D}/3b2_dmac.c \ - ${ATT3B2D}/3b2_io.c ${ATT3B2D}/3b2_ports.c \ +ATT3B2M700 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_sys.c \ + ${ATT3B2D}/3b2_rev3_sys.c ${ATT3B2D}/3b2_rev3_mmu.c \ + ${ATT3B2D}/3b2_mau.c ${ATT3B2D}/3b2_rev3_csr.c \ + ${ATT3B2D}/3b2_timer.c ${ATT3B2D}/3b2_stddev.c \ + ${ATT3B2D}/3b2_mem.c ${ATT3B2D}/3b2_iu.c \ + ${ATT3B2D}/3b2_if.c ${ATT3B2D}/3b2_dmac.c \ + ${ATT3B2D}/3b2_io.c ${ATT3B2D}/3b2_ports.c \ ${ATT3B2D}/3b2_scsi.c ${ATT3B2D}/3b2_ni.c \ $(NETWORK_DEPS) -ATT3B2M600_OPT = -DUSE_INT64 -DUSE_ADDR64 -DREV3 -I ${ATT3B2D} ${NETWORK_OPT} +ATT3B2M700_OPT = -DUSE_INT64 -DUSE_ADDR64 -DREV3 -I ${ATT3B2D} ${NETWORK_OPT} SIGMAD = ${SIMHD}/sigma SIGMA = ${SIGMAD}/sigma_cpu.c ${SIGMAD}/sigma_sys.c ${SIGMAD}/sigma_cis.c \ @@ -2239,7 +2239,7 @@ ALL = pdp1 pdp4 pdp7 pdp8 pdp9 pdp15 pdp11 pdp10 \ nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri \ i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \ swtp6800mp-a swtp6800mp-a2 tx-0 ssem b5500 intel-mds \ - scelbi 3b2 i701 i704 i7010 i7070 i7080 i7090 \ + scelbi 3b2 3b2-700 i701 i704 i7010 i7070 i7080 i7090 \ sigma uc15 pdp10-ka pdp10-ki pdp10-kl pdp10-ks pdp6 i650 \ imlac tt2500 sel32 @@ -2546,12 +2546,12 @@ b5500 : ${B5500} ${SIM} $(MAKEIT) OPTS="$(B5500_OPT)" INDIR1="$(B5500D)" -3b2 : ${ATT3B2M400} ${SIM} ${BUILD_ROMS} - $(MAKEIT) OPTS="$(ATT3B2M400_OPT)" INDIR1="$(ATT3B2D)" +3b2 : ${ATT3B2M400} ${SIM} + $(MAKEIT) OPTS="$(ATT3B2M400_OPT)" INDIR1="$(ATT3B2D)" ALTNAME=3b2-400 -3b2-600 : ${ATT3B2M600} ${SIM} ${SCSI} ${BUILD_ROMS} - $(MAKEIT) OPTS="$(ATT3B2M600_OPT)" INDIR1="$(ATT3B2D)" +3b2-700 : ${ATT3B2M700} ${SCSI} ${SIM} + $(MAKEIT) OPTS="$(ATT3B2M700_OPT)" INDIR1="$(ATT3B2D)" i7090 : ${I7090} ${SIM} diff --git a/sim_BuildROMs.c b/sim_BuildROMs.c index 06d89f7d..f4eb0fc7 100644 --- a/sim_BuildROMs.c +++ b/sim_BuildROMs.c @@ -70,9 +70,6 @@ struct ROM_File_Descriptor { {"PDP11/dazzledart/dazzle.lda", "PDP11/pdp11_dazzle_dart_rom.h", 6096, 0xFFF83848, "dazzle_lda"}, {"PDP11/11logo/11logo.lda", "PDP11/pdp11_11logo_rom.h", 26009, 0xFFDD77F7, "logo_lda"}, {"swtp6800/swtp6800/swtbugv10.bin", "swtp6800/swtp6800/swtp_swtbugv10_bin.h", 1024, 0xFFFE4FBC, "swtp_swtbugv10_bin"}, - {"3B2/rom_rev2.bin", "3B2/rom_rev2_bin.h", 32768, 0xFFD55762, "rom_rev2_bin"}, - {"3B2/rom_rev2_demon.bin", "3B2/rom_rev2_demon_bin.h", 65536, 0xFFB345BB, "rom_rev2_demon_bin"}, - {"3B2/rom_rev3.bin", "3B2/rom_rev3_bin.h", 131072, 0xFFDC0EB8, "rom_rev3_bin"}, };