3b2: Initial release of an AT&T 3B2 model 400 emulator.

For information on usage, please see the file 3B2/README.md
This commit is contained in:
Seth Morabito 2017-11-20 18:21:49 -08:00 committed by Mark Pizzolato
parent 7246e74004
commit 804ea8e322
28 changed files with 12528 additions and 1 deletions

3402
3B2/3b2_cpu.c Normal file

File diff suppressed because it is too large Load diff

490
3B2/3b2_cpu.h Normal file
View file

@ -0,0 +1,490 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 CPU (WE32100) Header
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_CPU_H_
#define _3B2_CPU_H_
#include "3b2_defs.h"
#include "3b2_io.h"
/* Execution Modes */
#define EX_LVL_KERN 0
#define EX_LVL_EXEC 1
#define EX_LVL_SUPR 2
#define EX_LVL_USER 3
/* Processor Version Number */
#define WE32100_VER 0x1A
/*
*
* Mode Syntax Mode Reg. Bytes Notes
* ----------------------------------------------------------------------
* Absolute $expr 7 15 5
* Abs. Deferred *$expr 14 15 5
* Byte Disp. expr(%rn) 12 0-10,12-15 2
* Byte Disp. Def. *expr(%rn) 13 0-10,12-15 2
* Halfword Disp. expr(%rn) 10 0-10,12-15 3
* Halfword Disp. Def. *expr(%rn) 11 0-10,12-15 3
* Word Disp. expr(%rn) 8 0-10,12-15 5
* Word Disp. Def. *expr(%rn) 9 0-10,12-15 5
* AP Short Offset so(%ap) 7 0-14 1 1
* FP Short Offset so(%fp) 6 0-14 1 1
* Byte Immediate &imm8 6 15 2 2,3
* Halfword Immediate &imm16 5 15 3 2,3
* Word Immediate &imm32 4 15 5 2,3
* Positive Literal &lit 0-3 0-15 1 2,3
* Negative Literal &lit 15 0-15 1 2,3
* Register %rn 4 0-14 1 1,3
* Register Deferred (%rn) 5 0-10,12-14 1 1
* Expanded Op. Type {type}opnd 14 0-14 2-6 4
*
* Notes:
*
* 1. Mode field has special meaning if register field is 15; see
* absolute or immediate mode.
* 2. Mode may not be used for a destination operand.
* 3. Mode may not be used if the instruction takes effective address
* of the operand.
* 4. 'type' overrides instruction type; 'type' determines the operand
* type, except that it does not determine the length for immediate
* or literals or whether literals are signed or unsigned. 'opnd'
* determines actual address mode. For total bytes, add 1 to byte
* count for address mode determined by 'opnd'.
*
*/
/*
* Opcodes
*/
typedef enum {
SPOPRD = 0x02,
SPOPD2 = 0x03,
MOVAW = 0x04,
SPOPRT = 0x06,
SPOPT2 = 0x07,
RET = 0x08,
MOVTRW = 0x0C,
SAVE = 0x10,
SPOPWD = 0x13,
EXTOP = 0x14,
SPOPWT = 0x17,
RESTORE = 0x18,
SWAPWI = 0x1C,
SWAPHI = 0x1E,
SWAPBI = 0x1F,
POPW = 0x20,
SPOPRS = 0x22,
SPOPS2 = 0x23,
JMP = 0x24,
CFLUSH = 0x27,
TSTW = 0x28,
TSTH = 0x2A,
TSTB = 0x2B,
CALL = 0x2C,
BPT = 0x2E,
WAIT = 0x2F,
EMB = 0x30, // Multi-byte
SPOP = 0x32,
SPOPWS = 0x33,
JSB = 0x34,
BSBH = 0x36,
BSBB = 0x37,
BITW = 0x38,
BITH = 0x3A,
BITB = 0x3B,
CMPW = 0x3C,
CMPH = 0x3E,
CMPB = 0x3F,
RGEQ = 0x40,
BGEH = 0x42,
BGEB = 0x43,
RGTR = 0x44,
BGH = 0x46,
BGB = 0x47,
RLSS = 0x48,
BLH = 0x4A,
BLB = 0x4B,
RLEQ = 0x4C,
BLEH = 0x4E,
BLEB = 0x4F,
RGEQU = 0x50,
BGEUH = 0x52, // Also BCCH
BGEUB = 0x53, // Also BCCB
RGTRU = 0x54,
BGUH = 0x56,
BGUB = 0x57,
BLSSU = 0x58, // Also BCS
BLUH = 0x5A, // Also BCSH
BLUB = 0x5B, // Also BCSB
RLEQU = 0x5C,
BLEUH = 0x5E,
BLEUB = 0x5F,
RVC = 0x60,
BVCH = 0x62,
BVCB = 0x63,
RNEQU = 0x64,
BNEH_D = 0x66, // Duplicate of 0x76
BNEB_D = 0x67, // Duplicate of 0x77
RVS = 0x68,
BVSH = 0x6A,
BVSB = 0x6B,
REQLU = 0x6C,
BEH_D = 0x6E, // Duplicate of 0x7E
BEB_D = 0x6F, // Duplicate of 0x7F
NOP = 0x70,
NOP3 = 0x72,
NOP2 = 0x73,
BNEQ = 0x74,
RNEQ = 0x74,
BNEH = 0x76,
BNEB = 0x77,
RSB = 0x78,
BRH = 0x7A,
BRB = 0x7B,
REQL = 0x7C,
BEH = 0x7E,
BEB = 0x7F,
CLRW = 0x80,
CLRH = 0x82,
CLRB = 0x83,
MOVW = 0x84, /* done */
MOVH = 0x86, /* done */
MOVB = 0x87, /* done */
MCOMW = 0x88,
MCOMH = 0x8A,
MCOMB = 0x8B,
MNEGW = 0x8C,
MNEGH = 0x8E,
MNEGB = 0x8F,
INCW = 0x90,
INCH = 0x92,
INCB = 0x93,
DECW = 0x94,
DECH = 0x96,
DECB = 0x97,
ADDW2 = 0x9C,
ADDH2 = 0x9E,
ADDB2 = 0x9F,
PUSHW = 0xA0,
MODW2 = 0xA4,
MODH2 = 0xA6,
MODB2 = 0xA7,
MULW2 = 0xA8,
MULH2 = 0xAA,
MULB2 = 0xAB,
DIVW2 = 0xAC,
DIVH2 = 0xAE,
DIVB2 = 0xAF,
ORW2 = 0xB0,
ORH2 = 0xB2,
ORB2 = 0xB3,
XORW2 = 0xB4,
XORH2 = 0xB6,
XORB2 = 0xB7,
ANDW2 = 0xB8,
ANDH2 = 0xBA,
ANDB2 = 0xBB,
SUBW2 = 0xBC,
SUBH2 = 0xBE,
SUBB2 = 0xBF,
ALSW3 = 0xC0,
ARSW3 = 0xC4,
ARSH3 = 0xC6,
ARSB3 = 0xC7,
INSFW = 0xC8,
INSFH = 0xCA,
INSFB = 0xCB,
EXTFW = 0xCC,
EXTFH = 0xCE,
EXTFB = 0xCF,
LLSW3 = 0xD0,
LLSH3 = 0xD2,
LLSB3 = 0xD3,
LRSW3 = 0xD4,
ROTW = 0xD8,
ADDW3 = 0xDC,
ADDH3 = 0xDE,
ADDB3 = 0xDF,
PUSHAW = 0xE0,
MODW3 = 0xE4,
MODH3 = 0xE6,
MODB3 = 0xE7,
MULW3 = 0xE8,
MULH3 = 0xEA,
MULB3 = 0xEB,
DIVW3 = 0xEC,
DIVH3 = 0xEE,
DIVB3 = 0xEF,
ORW3 = 0xF0,
ORH3 = 0xF2,
ORB3 = 0xF3,
XORW3 = 0xF4,
XORH3 = 0xF6,
XORB3 = 0xF7,
ANDW3 = 0xF8,
ANDH3 = 0xFA,
ANDB3 = 0xFB,
SUBW3 = 0xFC,
SUBH3 = 0xFE,
SUBB3 = 0xFF,
MVERNO = 0x3009,
ENBVJMP = 0x300d,
DISVJMP = 0x3013,
MOVBLW = 0x3019,
STREND = 0x301f,
INTACK = 0x302f,
STRCPY = 0x3035,
RETG = 0x3045,
GATE = 0x3061,
CALLPS = 0x30ac,
RETPS = 0x30c8
} opcode;
typedef enum {
DECODE_SUCCESS,
DECODE_ERROR
} decode_result;
/* Addressing Mode enum */
typedef enum {
ABS, // Absolute
ABS_DEF, // Absolute Deferred
BYTE_DISP, // Byte Displacement
BYTE_DISP_DEF, // Byte Displacement Deferred
HFWD_DISP, // Halfword Displacement
HFWD_DISP_DEF, // Halfword Displacement Deferred
WORD_DISP, // Word Displacement
WORD_DISP_DEF, // Word Displacement Deferred
AP_SHORT_OFF, // AP Short Offset
FP_SHORT_OFF, // FP Short Offset
BYTE_IMM, // Byte Immediate
HFWD_IMM, // Halfword Immediate
WORD_IMM, // Word Immediate
POS_LIT, // Positive Literal
NEG_LIT, // Negative Literal
REGISTER, // Register
REGISTER_DEF, // Register Deferred
EXP // Expanded-operand type
} addr_mode;
/*
* Each instruction expects operands of a certain type.
*
* The large majority of instructions expect operands that have a
* descriptor as the first byte. This descriptor carries all the
* information necessary to compute the addressing mode of the
* operand.
*
* e.g.:
*
* MOVB 6(%r1),%r0
* +------+------+------+------+
* | 0x87 | 0xc1 | 0x06 | 0x40 |
* +------+------+------+------+
* ^^^^^^
* Descriptor byte. mode = 13 (0x0c), register = 1 (0x01)
*
*
* Branch instructions have either an 8-bit or a 16-bit signed
* displacement value, and lack a descriptor byte.
*
* e.g.:
*
* BCCB 0x03
* +------+------+
* | 0x53 | 0x03 | 8-bit displacement
* +------+------+
*
* BCCH 0x01ff
* +------+------+------+
* | 0x52 | 0xff | 0x01 | 16-bit displacement
* +------+------+------+
*
*
* TODO: Describe coprocessor instructions
*
*/
typedef enum {
OP_NONE, /* NULL type */
OP_DESC, /* Descriptor byte */
OP_BYTE, /* 8-bit signed value */
OP_HALF, /* 16-bit signed value */
OP_COPR /* Coprocessor instruction */
} op_mode;
/* Describes a mnemonic */
typedef struct _mnemonic {
uint16 opcode;
int8 op_count; /* Number of operands */
op_mode mode; /* Dispatch mode */
int8 dtype; /* Default data type */
char mnemonic[8];
int8 src_op1;
int8 src_op2;
int8 src_op3;
int8 dst_op;
} mnemonic;
/*
* Structure that describes each operand in a decoded instruction
*/
typedef struct _operand {
uint8 mode; /* Embedded data addressing mode */
uint8 reg; /* Operand register (0-15) */
int8 dtype; /* Default type for the operand */
int8 etype; /* Expanded type (-1 if none) */
union {
uint32 w;
uint16 h;
uint8 b;
} embedded; /* Data consumed as part of the instruction
stream, i.e. literals, displacement,
etc. */
uint32 data; /* Data either read or written during
instruction execution */
} operand;
/*
* An inst is a combination of a decoded instruction and
* 0 to 4 operands. Also used for history record keeping.
*/
typedef struct _instr {
mnemonic *mn;
uint32 psw;
uint32 sp;
uint32 pc;
t_bool valid;
operand operands[4];
} instr;
/* Function prototypes */
t_stat cpu_svc(UNIT *uptr);
t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw);
t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw);
t_stat cpu_reset(DEVICE *dptr);
t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat cpu_set_halt(UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_clear_halt(UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_boot(int32 unit_num, DEVICE *dptr);
t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs);
void cpu_register_name(uint8 reg, char *buf, size_t len);
void cpu_show_operand(FILE *st, operand *op);
void fprint_sym_m(FILE *st, instr *ip);
instr *cpu_next_instruction(void);
uint8 decode_instruction(instr *instr);
t_bool cpu_on_interrupt(uint8 ipl);
static uint32 cpu_effective_address(operand * op);
static uint32 cpu_read_op(operand * op);
static void cpu_write_op(operand * op, t_uint64 val);
static void cpu_set_nz_flags(t_uint64 data, operand * op);
static SIM_INLINE uint8 cpu_ipl();
static SIM_INLINE void cpu_on_normal_exception(uint8 isc);
static SIM_INLINE void cpu_on_stack_exception(uint8 isc);
static SIM_INLINE void cpu_on_process_exception(uint8 isc);
static SIM_INLINE void cpu_on_reset_exception(uint8 isc);
static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2);
static SIM_INLINE t_bool mem_exception();
static SIM_INLINE void clear_exceptions();
static SIM_INLINE void set_psw_tm(t_bool val);
static SIM_INLINE void clear_instruction(instr *inst);
static SIM_INLINE int8 op_type(operand *op);
static SIM_INLINE t_bool op_signed(operand *op);
static SIM_INLINE t_bool is_byte_immediate(operand * oper);
static SIM_INLINE t_bool is_halfword_immediate(operand * oper);
static SIM_INLINE t_bool is_word_immediate(operand * oper);
static SIM_INLINE t_bool is_positive_literal(operand * oper);
static SIM_INLINE t_bool is_negative_literal(operand * oper);
static SIM_INLINE t_bool invalid_destination(operand * oper);
static SIM_INLINE uint32 sign_extend_n(uint8 val);
static SIM_INLINE uint32 sign_extend_b(uint8 val);
static SIM_INLINE uint32 zero_extend_b(uint8 val);
static SIM_INLINE uint32 sign_extend_h(uint16 val);
static SIM_INLINE uint32 zero_extend_h(uint16 val);
static SIM_INLINE t_bool cpu_z_flag();
static SIM_INLINE t_bool cpu_n_flag();
static SIM_INLINE t_bool cpu_c_flag();
static SIM_INLINE t_bool cpu_v_flag();
static SIM_INLINE void cpu_set_z_flag(t_bool val);
static SIM_INLINE void cpu_set_n_flag(t_bool val);
static SIM_INLINE void cpu_set_c_flag(t_bool val);
static SIM_INLINE void cpu_set_v_flag(t_bool val);
static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op);
static SIM_INLINE uint8 cpu_execution_level();
static SIM_INLINE void cpu_set_execution_level(uint8 level);
static SIM_INLINE void cpu_push_word(uint32 val);
static SIM_INLINE uint32 cpu_pop_word();
static SIM_INLINE void irq_push_word(uint32 val);
static SIM_INLINE uint32 irq_pop_word();
static SIM_INLINE void cpu_context_switch_1(uint32 pcbp);
static SIM_INLINE void cpu_context_switch_2(uint32 pcbp);
static SIM_INLINE void cpu_context_switch_3(uint32 pcbp);
static SIM_INLINE t_bool op_is_psw(operand *op);
static SIM_INLINE t_bool op_is_sp(operand *op);
static SIM_INLINE uint32 cpu_next_pc();
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);
/* Helper macros */
#define MOD(A,B,OP1,OP2,SZ) { \
if (op_signed(OP1) && !op_signed(OP2)) { \
result = (SZ)(B) % (A); \
} else if (!op_signed(OP1) && op_signed(OP2)) { \
result = (B) % (SZ)(A); \
} else if (op_signed(OP1) && op_signed(OP2)) { \
result = (SZ)(B) % (SZ)(A); \
} else { \
result = (B) % (A); \
} \
}
#define DIV(A,B,OP1,OP2,SZ) { \
if (op_signed(OP1) && !op_signed(OP2)) { \
result = (SZ)(B) / (A); \
} else if (!op_signed(OP1) && op_signed(OP2)) { \
result = (B) / (SZ)(A); \
} else if (op_signed(OP1) && op_signed(OP2)) { \
result = (SZ)(B) / (SZ)(A); \
} else { \
result = (B) / (A); \
} \
}
#endif

362
3B2/3b2_defs.h Normal file
View file

@ -0,0 +1,362 @@
/* 3b2_defs.h: AT&T 3B2 Model 400 Simulator Definitions
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_DEFS_H_
#define _3B2_DEFS_H_
#include "sim_defs.h"
#include "sim_tmxr.h"
#include <setjmp.h>
#define FALSE 0
#define TRUE 1
#if defined (__GNUC__)
#define noret void __attribute__((noreturn))
#else
#define noret void
#endif
#if defined(__GLIBC__) && !defined(__cplusplus)
/* use glibc internal longjmp to bypass fortify checks */
noret __libc_longjmp (jmp_buf buf, int val);
#define longjmp __libc_longjmp
#endif
/* -v flag for examine routine */
#define EX_V_FLAG 1 << 21
#define MAX_HIST_SIZE 1000000
#define MIN_HIST_SIZE 64
#define MAXMEMSIZE (1 << 22) /* 4 MB */
#define MEM_SIZE (cpu_unit.capac) /* actual memory size */
#define UNIT_V_MSIZE (UNIT_V_UF)
#define UNIT_MSIZE (1 << UNIT_V_MSIZE)
#define WD_MSB 0x80000000
#define HW_MSB 0x8000
#define BT_MSB 0x80
#define WORD_MASK 0xffffffff
#define HALF_MASK 0xffffu
#define BYTE_MASK 0xff
/*
*
* Physical memory in the 3B2 is arranged as follows:
*
* 0x00000000 - 0x0000FFFF: 64KB ROM (32K used)
* 0x00040000 - 0x0004FFFF: IO
* 0x02000000 - 0x023FFFFF: 4MB RAM ("Mainstore"),
*
*/
#define PHYS_MEM_BASE 0x2000000
#define ROM_BASE 0
#define IO_BASE 0x40000
#define IO_SIZE 0x10000
#define IOB_BASE 0x200000
#define IOB_SIZE 0x1E00000
/* Register numbers */
#define NUM_FP 9
#define NUM_AP 10
#define NUM_PSW 11
#define NUM_SP 12
#define NUM_PCBP 13
#define NUM_ISP 14
#define NUM_PC 15
#define CPU_CM (cpu_km ? L_KERNEL : ((R[NUM_PSW] >> PSW_CM) & 3))
/* Simulator stop codes */
#define STOP_RSRV 1
#define STOP_IBKPT 2 /* Breakpoint encountered */
#define STOP_OPCODE 3 /* Invalid opcode */
#define STOP_IRQ 4 /* Interrupt */
#define STOP_EX 5 /* Exception */
#define STOP_ESTK 6 /* Exception stack too deep */
#define STOP_MMU 7 /* Unimplemented MMU Feature */
/* Exceptional conditions handled within the instruction loop */
#define ABORT_EXC 1 /* CPU exception */
#define ABORT_TRAP 2 /* CPU trap */
/* Contexts for aborts */
#define C_NONE 0 /* No context. Normal handling. */
#define C_NORMAL_GATE_VECTOR 1
#define C_PROCESS_GATE_PCB 2
#define C_PROCESS_OLD_PCB 3
#define C_PROCESS_NEW_PCB 4
#define C_RESET_GATE_VECTOR 5
#define C_RESET_INT_STACK 6
#define C_RESET_NEW_PCB 7
#define C_RESET_SYSTEM_DATA 8
#define C_STACK_FAULT 9
/* Debug flags */
#define READ_MSG 0x01
#define WRITE_MSG 0x02
#define DECODE_MSG 0x04
#define EXECUTE_MSG 0x08
#define INIT_MSG 0x10
#define IRQ_MSG 0x20
#define IO_D_MSG 0x40
#define TRACE_MSG 0x80
/* Data types operated on by instructions. NB: These integer values
have meaning when decoding instructions, so this is not just an
enum. Please don't change them. */
#define UW 0 /* Unsigned Word */
#define UH 2 /* Unsigned Halfword */
#define BT 3 /* Unsigned Byte */
#define WD 4 /* Signed Word */
#define HW 6 /* Signed Halfword */
#define SB 7 /* Signed Byte */
#define NA -1
/*
* Exceptions are described on page 2-66 of the WE32100 manual
*/
/* Exception Types */
#define RESET_EXCEPTION 0
#define PROCESS_EXCEPTION 1
#define STACK_EXCEPTION 2
#define NORMAL_EXCEPTION 3
/* Reset Exceptions */
#define OLD_PCB_FAULT 0
#define SYSTEM_DATA_FAULT 1
#define INTERRUPT_STACK_FAULT 2
#define EXTERNAL_RESET 3
#define NEW_PCB_FAULT 4
#define GATE_VECTOR_FAULT 6
/* Processor Exceptions */
#define GATE_PCB_FAULT 1
/* Stack Exceptions */
#define STACK_BOUND 0
#define STACK_FAULT 1
#define INTERRUPT_ID_FETCH 3
/* Normal Exceptions */
#define INTEGER_ZERO_DIVIDE 0
#define TRACE_TRAP 1
#define ILLEGAL_OPCODE 2
#define RESERVED_OPCODE 3
#define INVALID_DESCRIPTOR 4
#define EXTERNAL_MEMORY_FAULT 5
#define N_GATE_VECTOR 6
#define ILLEGAL_LEVEL_CHANGE 7
#define RESERVED_DATATYPE 8
#define INTEGER_OVERFLOW 9
#define PRIVILEGED_OPCODE 10
#define BREAKPOINT_TRAP 14
#define PRIVILEGED_REGISTER 15
#define PSW_ET 0
#define PSW_TM 2u
#define PSW_ISC 3u
#define PSW_I 7
#define PSW_R 8
#define PSW_PM 9
#define PSW_CM 11
#define PSW_IPL 13
#define PSW_TE 17
#define PSW_C 18
#define PSW_V 19
#define PSW_Z 20
#define PSW_N 21
#define PSW_OE 22
#define PSW_CD 23
#define PSW_QIE 24
#define PSW_CFD 25
/* Access Request types */
#define ACC_MT 0 /* Move Translated */
#define ACC_SPW 1 /* Support processor write */
#define ACC_SPF 3 /* Support processor fetch */
#define ACC_IR 7 /* Interlocked read */
#define ACC_AF 8 /* Address fetch */
#define ACC_OF 9 /* Operand fetch */
#define ACC_W 10 /* Write */
#define ACC_IFAD 12 /* Instruction fetch after discontinuity */
#define ACC_IF 13 /* Instruction fetch */
#define L_KERNEL 0
#define L_EXEC 1
#define L_SUPER 2
#define L_USER 3
#define PSW_ET_MASK 3u
#define PSW_TM_MASK (1u << PSW_TM)
#define PSW_ISC_MASK (15u << PSW_ISC)
#define PSW_I_MASK (1u << PSW_I)
#define PSW_R_MASK (1u << PSW_R)
#define PSW_PM_MASK (3u << PSW_PM)
#define PSW_CM_MASK (3u << PSW_CM)
#define PSW_IPL_MASK (15u << PSW_IPL)
#define PSW_TE_MASK (1u << PSW_TE)
#define PSW_C_MASK (1u << PSW_C)
#define PSW_V_MASK (1u << PSW_V)
#define PSW_N_MASK (1u << PSW_N)
#define PSW_Z_MASK (1u << PSW_Z)
#define PSW_OE_MASK (1u << PSW_OE)
#define PSW_CD_MASK (1u << PSW_CD)
#define PSW_QIE_MASK (1u << PSW_QIE)
#define PSW_CFD_MASK (1u << PSW_CFD)
#define PSW_CUR_IPL (((R[NUM_PSW] & PSW_IPL_MASK) >> PSW_IPL) & 0xf)
#define TODBASE 0x41000
#define TODSIZE 0x40
#define TIMERBASE 0x42000
#define TIMERSIZE 0x20
#define NVRAMBASE 0x43000
#define NVRAMSIZE 0x1000
#define CSRBASE 0x44000
#define CSRSIZE 0x100
#define CSRTIMO 0x8000 /* Bus Timeout Error */
#define CSRPARE 0x4000 /* Memory Parity Error */
#define CSRRRST 0x2000 /* System Reset Request */
#define CSRALGN 0x1000 /* Memory Alignment Fault */
#define CSRLED 0x0800 /* Failure LED */
#define CSRFLOP 0x0400 /* Floppy Motor On */
#define CSRRES 0x0200 /* Reserved */
#define CSRITIM 0x0100 /* Inhibit Timers */
#define CSRIFLT 0x0080 /* Inhibit Faults */
#define CSRCLK 0x0040 /* Clock Interrupt */
#define CSRPIR8 0x0020 /* Programmed Interrupt 8 */
#define CSRPIR9 0x0010 /* Programmed Interrupt 9 */
#define CSRUART 0x0008 /* UART Interrupt */
#define CSRDISK 0x0004 /* Floppy Interrupt */
#define CSRDMA 0x0002 /* DMA Interrupt */
#define CSRIOF 0x0001 /* I/O Board Fail */
#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
/* Clock state bitmasks */
#define CLK_MD 0x0E /* Mode mask */
#define CLK_RW 0x30 /* RW mask */
#define CLK_SC 0xC0 /* SC mask */
#define CLK_LAT 0x00
#define CLK_LSB 0x10
#define CLK_MSB 0x20
#define CLK_LMB 0x30
#define CLK_MD0 0x00
#define CLK_MD1 0x02
#define CLK_MD2 0x04
#define CLK_MD3 0x06
#define CLK_MD4 0x08
#define CLK_MD5 0x0a
/* Timer definitions */
#define TMR_CLK 0 /* The clock responsible for IPL 15 interrupts */
#define TMR_TOD 1 /* The Time-of-Day clock */
#define TPS_CLK 100
#define TPS_TOD 10
/* TIMING SECTION */
/* ----------------------------------------------- */
/* Calculate delays (in simulator steps) for times */
/* System clock runs at 10MHz; 100ns period. */
#define US_PER_INST 0.9
#define INST_PER_MS (1000.0 / US_PER_INST)
#define DELAY_US(us) ((uint32)((us) / US_PER_INST))
#define DELAY_MS(ms) ((uint32)(((ms) * 1000) / US_PER_INST))
/* global symbols from the CPU */
extern jmp_buf save_env;
extern uint32 *ROM;
extern uint32 *RAM;
extern uint32 R[16];
extern REG cpu_reg[];
extern DEVICE cpu_dev;
extern UNIT cpu_unit;
extern uint8 fault;
extern DEBTAB sys_deb_tab[];
extern t_bool cpu_km;
/* Generic callback function */
typedef void (*callback)(void);
/* global symbols from the DMAC */
extern DEVICE dmac_dev;
/* global symbols from the CSR */
extern uint16 csr_data;
/* global symbols from the IU */
extern t_bool iu_increment_a;
extern t_bool iu_increment_b;
extern void increment_modep_a();
extern void increment_modep_b();
/* global symbols from the MMU */
extern t_bool mmu_enabled();
extern void mmu_enable();
extern void mmu_disable();
extern uint8 read_b(uint32 va, uint8 acc);
extern uint16 read_h(uint32 va, uint8 acc);
extern uint32 read_w(uint32 va, uint8 acc);
extern void write_b(uint32 va, uint8 val);
extern void write_h(uint32 va, uint16 val);
extern void write_w(uint32 va, uint32 val);
/* Globally scoped CPU functions */
void cpu_abort(uint8 et, uint8 isc);
void cpu_set_irq(uint8 ipl, uint8 id, uint16 csr_flags);
void cpu_clear_irq(uint8 ipl, uint16 csr_flags);
/* Globally scoped IO functions */
uint32 io_read(uint32 pa, size_t size);
void io_write(uint32 pa, uint32 val, size_t size);
#endif

443
3B2/3b2_dmac.c Normal file
View file

@ -0,0 +1,443 @@
/* 3b2_dmac.c: AT&T 3B2 Model 400 AM9517A DMA Controller Implementation
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.
*/
#include "3b2_dmac.h"
DMA_STATE dma_state;
UNIT dmac_unit[] = {
{ UDATA (NULL, 0, 0), 0, 0 },
{ UDATA (NULL, 0, 0), 0, 1 },
{ UDATA (NULL, 0, 0), 0, 2 },
{ UDATA (NULL, 0, 0), 0, 3 },
{ NULL }
};
REG dmac_reg[] = {
{ NULL }
};
DEVICE dmac_dev = {
"DMAC", dmac_unit, dmac_reg, NULL,
1, 16, 8, 4, 16, 32,
NULL, NULL, &dmac_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
dmac_drq_handler dmac_drq_handlers[] = {
{DMA_ID_CHAN, IDBASE+ID_DATA_REG, &id_drq, id_drq_handled},
{DMA_IF_CHAN, IFBASE+IF_DATA_REG, &if_state.drq, if_drq_handled},
{DMA_IUA_CHAN, IUBASE+IUA_DATA_REG, &iu_state.drqa, iua_drq_handled},
{DMA_IUB_CHAN, IUBASE+IUB_DATA_REG, &iu_state.drqb, iub_drq_handled},
{0, 0, NULL, NULL }
};
t_stat dmac_reset(DEVICE *dptr)
{
int i;
memset(&dma_state, 0, sizeof(dma_state));
for (i = 0; i < 4; i++) {
dma_state.channels[i].page = 0;
dma_state.channels[i].addr = 0;
dma_state.channels[i].wcount = 0;
dma_state.channels[i].addr_c = 0;
dma_state.channels[i].wcount_c = 0;
}
return SCPE_OK;
}
uint32 dmac_read(uint32 pa, size_t size)
{
uint8 reg, base, data;
base =(uint8) (pa >> 12);
reg = pa & 0xff;
switch (base) {
case DMA_C: /* 0x48xxx */
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);
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);
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);
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);
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);
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);
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);
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);
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);
dma_state.status = 0;
break;
default:
sim_debug(READ_MSG, &dmac_dev,
"[%08x] DMAC READ %lu B @ %08x\n",
R[NUM_PC], 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);
return 0;
}
}
/*
* Program the DMAC
*/
void dmac_program(uint8 reg, uint8 val)
{
uint8 channel_id, i, chan_num;
dma_channel *channel;
if (reg < 8) {
switch (reg) {
case 0:
case 1:
chan_num = 0;
break;
case 2:
case 3:
chan_num = 1;
break;
case 4:
case 5:
chan_num = 2;
break;
case 6:
case 7:
chan_num = 3;
break;
}
channel = &dma_state.channels[chan_num];
if (channel == NULL) {
/* This should never happen */
return;
}
switch (reg & 1) {
case 0: /* Address */
channel->addr &= ~(0xff << dma_state.bff * 8);
channel->addr |= (val & 0xff) << (dma_state.bff * 8);
channel->addr_c = channel->addr;
sim_debug(WRITE_MSG, &dmac_dev,
"Set address channel %d byte %d = %08x\n",
chan_num, dma_state.bff, channel->addr);
break;
case 1: /* Word Count */
channel->wcount &= ~(0xff << dma_state.bff * 8);
channel->wcount |= (val & 0xff) << (dma_state.bff * 8);
channel->wcount_c = channel->wcount;
sim_debug(WRITE_MSG, &dmac_dev,
"Set word count channel %d byte %d = %08x\n",
chan_num, dma_state.bff, channel->wcount);
break;
}
/* Toggle the byte flip-flop */
dma_state.bff ^= 1;
/* Handled. */
return;
}
/* If it hasn't been handled, it must be one of the following
registers. */
switch (reg) {
case 8: /* Command */
dma_state.command = val;
sim_debug(WRITE_MSG, &dmac_dev,
"[%08x] Command: val=%02x\n",
R[NUM_PC], val);
break;
case 9: /* Request */
sim_debug(WRITE_MSG, &dmac_dev,
"[%08x] Request set: val=%02x\n",
R[NUM_PC], val);
dma_state.request = val;
break;
case 10: /* Write Single Mask Register Bit */
channel_id = val & 3;
/* "Clear or Set" is bit 2 */
if ((val >> 2) & 1) {
dma_state.mask |= (1 << channel_id);
} else {
dma_state.mask &= ~(1 << channel_id);
}
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);
break;
case 11: /* Mode */
sim_debug(WRITE_MSG, &dmac_dev,
"[%08x] Mode Set. val=%02x\n",
R[NUM_PC], val);
dma_state.mode = val;
break;
case 12: /* Clear Byte Pointer Flip/Flop */
dma_state.bff = 0;
break;
case 13: /* Master Clear */
dma_state.bff = 0;
dma_state.command = 0;
dma_state.status = 0;
for (i = 0; i < 4; i++) {
dma_state.channels[i].addr = 0;
dma_state.channels[i].wcount = 0;
dma_state.channels[i].page = 0;
}
break;
case 15: /* Write All Mask Register Bits */
sim_debug(WRITE_MSG, &dmac_dev,
"[%08x] Clear DMAC Interrupt. val=%02x\n",
R[NUM_PC], 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);
break;
default:
sim_debug(WRITE_MSG, &dmac_dev,
"[%08x] Unhandled DMAC write. reg=%x val=%02x\n",
R[NUM_PC], reg, val);
break;
}
}
void dmac_page_update(uint8 base, uint8 reg, uint8 val)
{
uint8 shift = 0;
/* Sanity check */
if (reg > 3) {
return;
}
/* The actual register is a 32-bit, byte-addressed register, so
that address 4x000 is the highest byte, 4x003 is the lowest
byte. */
shift = -(reg - 3) * 8;
switch (base) {
case DMA_ID:
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 0 = %x\n", val);
dma_state.channels[DMA_ID_CHAN].page &= ~(0xff << shift);
dma_state.channels[DMA_ID_CHAN].page |= (val << shift);
break;
case DMA_IF:
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 1 = %x\n", val);
dma_state.channels[DMA_IF_CHAN].page &= ~(0xff << shift);
dma_state.channels[DMA_IF_CHAN].page |= (val << shift);
break;
case DMA_IUA:
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 2 = %x\n", val);
dma_state.channels[DMA_IUA_CHAN].page &= ~(0xff << shift);
dma_state.channels[DMA_IUA_CHAN].page |= (val << shift);
break;
case DMA_IUB:
sim_debug(WRITE_MSG, &dmac_dev, "Set page channel 3 = %x\n", val);
dma_state.channels[DMA_IUB_CHAN].page &= ~(0xff << shift);
dma_state.channels[DMA_IUB_CHAN].page |= (val << shift);
break;
}
}
void dmac_write(uint32 pa, uint32 val, size_t size)
{
uint8 reg, base;
base = (uint8) (pa >> 12);
reg = pa & 0xff;
switch (base) {
case DMA_C: /* 0x48xxx */
dmac_program(reg, (uint8) val);
break;
case DMA_ID: /* 0x45xxx */
case DMA_IUA: /* 0x46xxx */
case DMA_IUB: /* 0x47xxx */
case DMA_IF: /* 0x4Exxx */
dmac_page_update(base, reg, (uint8) val);
break;
}
}
static SIM_INLINE uint32 dma_address(uint8 channel, uint32 offset, t_bool r) {
uint32 addr;
addr = (PHYS_MEM_BASE +
dma_state.channels[channel].addr +
offset);
/* The top bit of the page address is a R/W bit, so we mask it here */
addr |= (uint32) (((uint32)dma_state.channels[channel].page & 0x7f) << 16);
return addr;
}
void dmac_transfer(uint8 channel, uint32 service_address)
{
uint8 data;
int32 i;
uint16 offset;
uint32 addr;
dma_channel *chan = &dma_state.channels[channel];
/* TODO: This does not handle decrement-mode transfers,
which don't seem to be used in SVR3 */
switch ((dma_state.mode >> 2) & 0xf) {
case DMA_MODE_VERIFY:
sim_debug(EXECUTE_MSG, &dmac_dev,
"[%08x] [dmac_transfer channel=%d] unhandled VERIFY request.\n",
R[NUM_PC], channel);
break;
case DMA_MODE_WRITE:
sim_debug(EXECUTE_MSG, &dmac_dev,
"[%08x] [dmac_transfer channel=%d] write: %d bytes from %08x\n",
R[NUM_PC], channel,
chan->wcount + 1,
dma_address(channel, 0, TRUE));
offset = 0;
for (i = chan->wcount; i >= 0; i--) {
addr = dma_address(channel, offset, TRUE);
chan->addr_c = dma_state.channels[channel].addr + offset;
offset++;
data = pread_b(service_address);
write_b(addr, data);
}
break;
case DMA_MODE_READ:
sim_debug(EXECUTE_MSG, &dmac_dev,
"[%08x] [dmac_transfer channel=%d] read: %d bytes to %08x\n",
R[NUM_PC], channel,
chan->wcount + 1,
dma_address(channel, 0, TRUE));
offset = 0;
for (i = chan->wcount; i >= 0; i--) {
addr = dma_address(channel, offset++, TRUE);
chan->addr_c = dma_state.channels[channel].addr + offset;
data = pread_b(addr);
write_b(service_address, data);
}
break;
}
/* End of Process must set the IF channel's mask bit */
dma_state.mask |= (1 << channel);
dma_state.status |= (1 << channel);
}
/*
* Service pending DRQs
*/
void dmac_service_drqs()
{
dmac_drq_handler *h;
for (h = &dmac_drq_handlers[0]; h->drq != NULL; h++) {
/* Only trigger if the channel has a DRQ set and its channel's
mask bit is 0 */
if (*h->drq && ((dma_state.mask >> h->channel) & 0x1) == 0) {
dmac_transfer(h->channel, h->service_address);
*h->drq = FALSE; /* Immediately clear DRQ state */
if (h->handled_callback != NULL) {
h->handled_callback();
}
}
}
}

113
3B2/3b2_dmac.h Normal file
View file

@ -0,0 +1,113 @@
/* 3b2_dmac.h: AT&T 3B2 Model 400 AM9517A DMA Controller Header
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_DMAC_H_
#define _3B2_DMAC_H_
#include "3b2_sysdev.h"
#include "3b2_id.h"
#include "3b2_if.h"
/* DMA Controller */
#define DMACBASE 0x48000
#define DMACSIZE 0x11
/* DMA integrated disk page buffer */
#define DMAIDBASE 0x45000
#define DMAIDSIZE 0x5
/* DMA integrated uart A page buffer */
#define DMAIUABASE 0x46000
#define DMAIUASIZE 0x5
/* DMA integrated uart B page buffer */
#define DMAIUBBASE 0x47000
#define DMAIUBSIZE 0x5
/* DMA integrated floppy page buffer */
#define DMAIFBASE 0x4E000
#define DMAIFSIZE 0x5
#define DMA_ID_CHAN 0
#define DMA_IF_CHAN 1
#define DMA_IUA_CHAN 2
#define DMA_IUB_CHAN 3
#define DMA_ID 0x45
#define DMA_IUA 0x46
#define DMA_IUB 0x47
#define DMA_C 0x48
#define DMA_IF 0x4E
#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_IF_READ (IFBASE + IF_DATA_REG)
typedef struct {
uint8 page;
uint16 addr; /* Original addr */
uint16 wcount; /* Original wcount */
uint16 addr_c; /* Current addr */
uint16 wcount_c; /* Current word-count */
} dma_channel;
typedef struct {
/* Byte (high/low) flip-flop */
uint8 bff;
/* Address and count registers for channels 0-3 */
dma_channel channels[4];
/* DMAC programmable registers */
uint8 command;
uint8 mode;
uint8 request;
uint8 mask;
uint8 status;
} DMA_STATE;
typedef struct {
uint8 channel;
uint32 service_address;
t_bool *drq;
void (*handled_callback)();
} dmac_drq_handler;
/* DMAC */
t_stat dmac_reset(DEVICE *dptr);
uint32 dmac_read(uint32 pa, size_t size);
void dmac_write(uint32 pa, uint32 val, size_t size);
void dmac_service_drqs();
void dmac_transfer(uint8 channel, uint32 service_address);
#endif

844
3B2/3b2_id.c Normal file
View file

@ -0,0 +1,844 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 Hard Disk (uPD7261) Implementation
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.
*/
/*
* The larger of the two hard drive options shipped with the AT&T 3B2
* was a 72MB Wren II ST-506 MFM hard disk. That's what we emulate
* here, as a start.
*
* Formatted Capacity: 76,723,200 Bytes
*
* Cylinders: 925
* Sectors/Track: 18
* Heads: 9
* Bytes/Sector: 512
* Avg. seek: 28 ms
* Seek/track: 5 ms
*
* Drive Information from the 3B2 FAQ:
*
* Drive type drv id cyls trk/cyl sec/trk byte/cyl abbrev
* --------------- ------ ---- ------- ------- -------- ------
* Wren II 30MB 3 697 5 18 512 HD30
* Wren II 72MB 5 925 9 18 512 HD72
* Fujitsu M2243AS 8 754 11 18 512 HD72C
* Micropolis 1325 5 1024 8 18 512 HD72
* Maxtor 1140 4* 918= 15 18 512 HD120
* Maxtor 1190 11 1224+ 15 18 512 HD135
*
*/
#include <assert.h>
#include "3b2_id.h"
/* Wait times, in CPU steps, for various actions */
/* Each step is 50 us in buffered mode */
#define ID_SEEK_WAIT 100 /* us */
#define ID_SEEK_BASE 700 /* us */
#define ID_RECAL_WAIT 6000 /* us */
/* Reading data takes about 8ms per sector, plus time to seek if not
on cylinder */
#define ID_RW_WAIT 8000 /* us */
/* Sense Unit Status completes in about 200 us */
#define ID_SUS_WAIT 200 /* us */
/* Specify takes a bit longer, 1.25 ms */
#define ID_SPEC_WAIT 1250 /* us */
/* Sense Interrupt Status is about 142 us */
#define ID_SIS_WAIT 142 /* us */
/* The catch-all command wait time is about 140 us */
#define ID_CMD_WAIT 140 /* us */
/* State. The DP7261 supports four MFM (winchester) disks connected
simultaneously. There is only one set of registers, however, so
commands must be completed for one unit before they can begin on
another unit. */
/* Data FIFO pointer - Read */
uint8 id_dpr = 0;
/* Data FIFO pointer - Write */
uint8 id_dpw = 0;
/* Selected unit */
uint8 id_sel = 0;
/* Controller Status Register */
uint8 id_status = 0;
/* Unit Interrupt Status */
uint8 id_int_status;
/* Last command received */
uint8 id_cmd = 0;
/* DMAC request */
t_bool id_drq = FALSE;
/* 8-byte FIFO */
uint8 id_data[ID_FIFO_LEN] = {0};
/* INT output pin */
t_bool id_irq = FALSE;
/* Special flag for seek end SIS */
t_bool id_seek_sis = FALSE;
/* State of each drive */
/* Cylinder the drive is positioned on */
uint16 id_cyl[ID_NUM_UNITS] = {0};
/* DTLH byte for each drive */
uint8 id_dtlh[ID_NUM_UNITS] = {0};
/* Arguments of last READ, WRITE, VERIFY ID, or READ ID command */
/* Ending Track Number (from Specify) */
uint8 id_etn = 0;
/* Ending Sector Number (from Specify) */
uint8 id_esn = 0;
/* Physical sector number */
uint8 id_psn = 0;
/* Physical head number */
uint8 id_phn = 0;
/* Logical cylinder number, high byte */
uint8 id_lcnh = 0;
/* Logical cylinder number, low byte */
uint8 id_lcnl = 0;
/* Logical head number */
uint8 id_lhn = 0;
/* Logical sector number */
uint8 id_lsn = 0;
/* Number of sectors to transfer, decremented after each sector */
uint8 id_scnt = 0;
/* Sector buffer */
uint8 id_buf[ID_SEC_SIZE];
/* Buffer pointer */
size_t id_buf_ptr = 0;
uint8 id_idfield[ID_IDFIELD_LEN];
uint8 id_idfield_ptr = 0;
/*
* TODO: Macros used for debugging timers. Remove when debugging is complete.
*/
double id_start_time;
#define ID_START_TIME() { id_start_time = sim_gtime(); }
#define ID_DIFF_MS() ((sim_gtime() - id_start_time) / INST_PER_MS)
UNIT id_unit[] = {
{UDATA (&id_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK, ID_DSK_SIZE), 0, 0 },
{UDATA (&id_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK, ID_DSK_SIZE), 0, 1 },
{ NULL }
};
/* The currently selected drive number */
UNIT *id_sel_unit = &id_unit[0];
REG id_reg[] = {
{ HRDATAD(CMD, id_cmd, 8, "Command") },
{ HRDATAD(STAT, id_status, 8, "Status") },
{ BRDATAD(CYL, id_cyl, 8, 8, ID_NUM_UNITS, "Track") },
{ NULL }
};
DEVICE id_dev = {
"ID", id_unit, id_reg, NULL,
ID_NUM_UNITS, 16, 32, 1, 16, 8,
NULL, NULL, &id_reset,
NULL, &id_attach, &id_detach, NULL,
DEV_DEBUG|DEV_SECTORS, 0, sys_deb_tab,
NULL, NULL, &id_help, NULL, NULL,
&id_description
};
/* Function implementation */
static SIM_INLINE void id_activate(uint32 delay)
{
ID_START_TIME();
sim_activate_abs(id_sel_unit, (int32) delay);
}
static SIM_INLINE void id_clear_fifo()
{
id_dpr = 0;
id_dpw = 0;
}
t_stat id_svc(UNIT *uptr)
{
/* Complete the last command */
id_status = ID_STAT_CEH;
switch (CMD_NUM) {
case ID_CMD_SEEK: /* fall-through */
case ID_CMD_RECAL:
/* SRQ is only set in polling mode (POL bit is 0) */
if ((id_dtlh[UNIT_NUM] & ID_DTLH_POLL) == 0) {
id_status |= ID_STAT_SRQ;
}
if (uptr->flags & UNIT_ATT) {
id_int_status = ID_IST_SEN|(uint8)uptr->ID_UNIT_NUM;
} else {
id_int_status = ID_IST_NR|(uint8)uptr->ID_UNIT_NUM;
}
break;
case ID_CMD_SIS:
if (!id_seek_sis) {
id_status = ID_STAT_CEL;
}
id_seek_sis = FALSE;
id_data[0] = id_int_status;
id_status &= ~ID_STAT_SRQ;
break;
case ID_CMD_SUS:
if ((id_sel_unit->flags & UNIT_ATT) == 0) {
/* If no HD is attached, SUS puts 0x00 into the data
buffer */
id_data[0] = 0;
} else {
/* Put Unit Status into byte 0 */
id_data[0] = (ID_UST_DSEL|ID_UST_SCL|ID_UST_RDY);
if (id_cyl[UNIT_NUM] == 0) {
id_data[0] |= ID_UST_TK0;
}
}
break;
default:
break;
}
sim_debug(EXECUTE_MSG, &id_dev,
"[%08x] \tINTR\t\tDELTA=%f ms\n",
R[NUM_PC], ID_DIFF_MS());
id_irq = TRUE;
return SCPE_OK;
}
t_stat id_reset(DEVICE *dptr)
{
id_clear_fifo();
return SCPE_OK;
}
t_stat id_attach(UNIT *uptr, CONST char *cptr)
{
return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, "HD72", 0, 0);
}
t_stat id_detach(UNIT *uptr)
{
return sim_disk_detach(uptr);
}
/* Return the logical block address of the given sector */
static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec)
{
return((ID_SEC_CNT * ID_HEADS * cyl) +
(ID_SEC_CNT * head) +
sec);
}
/* At the end of each sector read or write, we update the FIFO
* with the correct return parameters. */
static void SIM_INLINE id_end_rw(uint8 est) {
sim_debug(EXECUTE_MSG, &id_dev,
">>> ending R/W with status: %02x\n",
est);
id_dpr = 0;
id_dpw = 0;
id_data[0] = est;
id_data[1] = id_phn;
id_data[2] = ~(id_lcnh);
id_data[3] = id_lcnl;
id_data[4] = id_lhn;
id_data[5] = id_lsn;
id_data[6] = id_scnt;
}
/* The controller wraps id_lsn, id_lhn, and id_lcnl on each sector
* read, so that they point to the next C/H/S */
static void SIM_INLINE id_update_chs() {
sim_debug(EXECUTE_MSG, &id_dev,
">>> id_update_chs(): id_esn=%02x id_etn=%02x\n",
id_esn, id_etn);
if (id_lsn++ >= id_esn) {
sim_debug(EXECUTE_MSG, &id_dev,
">>> id_update_chs(): id_lsn reset to 0. id_lhn is %02x\n",
id_lhn);
id_lsn = 0;
if (id_lhn++ >= id_etn) {
sim_debug(EXECUTE_MSG, &id_dev,
">>> id_update_chs(): id_lhn reset to 0. id_lcnl is %02x\n",
id_lcnl);
id_lhn = 0;
if (id_lcnl == 0xff) {
id_lcnl = 0;
id_lcnh++;
} else {
id_lcnl++;
}
}
}
}
uint32 id_read(uint32 pa, size_t size) {
uint8 reg;
uint16 cyl;
t_lba lba;
uint32 data;
t_seccnt sectsread;
reg = (uint8) (pa - IDBASE);
switch(reg) {
case ID_DATA_REG: /* Data Buffer Register */
/* If we're in a DMA transfer, we need to be reading data from
* the disk buffer. Otherwise, we're reading from the FIFO. */
if (id_drq) {
/* If the drive isn't attached, there's really nothing we
can do. */
if ((id_sel_unit->flags & UNIT_ATT) == 0) {
id_end_rw(ID_EST_NR);
return 0;
}
/* We could be in one of these commands:
* - Read Data
* - Read ID
*/
if (CMD_NUM == ID_CMD_RDATA) {
/* If we're still in DRQ but we've read all our sectors,
* 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]);
id_end_rw(ID_EST_OVR);
return 0;
}
/* If the disk buffer is empty, fill it. */
if (id_buf_ptr == 0 || id_buf_ptr >= ID_SEC_SIZE) {
/* It's time to read a new sector into our sector buf */
id_buf_ptr = 0;
cyl = (uint16) (((uint16)id_lcnh << 8)|(uint16)id_lcnl);
id_cyl[UNIT_NUM] = cyl;
lba = id_lba(cyl, id_lhn, id_lsn);
if (sim_disk_rdsect(id_sel_unit, lba, id_buf, &sectsread, 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);
}
sim_debug(READ_MSG, &id_dev,
"[%08x] \tRDATA\tCYL=%d PHN=%d LCNH=%02x "
"LCNL=%02x LHN=%d LSN=%d SCNT=%d LBA=%04x\n",
R[NUM_PC], cyl, id_phn, id_lcnh, id_lcnl,
id_lhn, id_lsn, id_scnt-1, lba);
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]);
id_end_rw(ID_EST_DER);
return 0;
}
}
data = id_buf[id_buf_ptr++];
sim_debug(READ_MSG, &id_dev,
"[%08x]\tSECTOR DATA\t%02x\t(%c)\n",
R[NUM_PC], data, (data >= 0x20 && data < 0x7f) ? data : '.');
/* Done with this current sector, update id_scnt */
if (id_buf_ptr >= ID_SEC_SIZE) {
if (--id_scnt == 0) {
id_end_rw(0);
}
}
} else if (CMD_NUM == ID_CMD_RID) {
/* We have to return the ID bytes for the current C/H/S */
if (id_idfield_ptr == 0 || id_idfield_ptr >= ID_IDFIELD_LEN) {
id_idfield[0] = ~(id_lcnh);
id_idfield[1] = id_lcnl;
id_idfield[2] = id_lhn;
id_idfield[3] = id_lsn;
id_idfield_ptr = 0;
}
data = id_idfield[id_idfield_ptr++];
sim_debug(READ_MSG, &id_dev,
"[%08x]\tID DATA\t%02x\n",
R[NUM_PC], data);
if (id_idfield_ptr >= ID_IDFIELD_LEN) {
if (id_scnt-- > 0) {
/* Another sector to ID */
id_idfield_ptr = 0;
} else {
/* All done, set return codes */
id_dpr = 0;
id_dpw = 0;
id_data[0] = 0;
id_data[1] = id_scnt;
}
}
} else {
assert(0); // cmd not Read Data or Read ID
}
return data;
} 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]);
return id_data[id_dpr++];
} else {
sim_debug(READ_MSG, &id_dev,
"[%08x] ERROR\tFIFO OVERRUN\n",
R[NUM_PC]);
return 0;
}
}
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);
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);
return 0;
}
void id_write(uint32 pa, uint32 val, size_t size)
{
uint8 reg;
uint16 cyl;
t_lba lba;
t_seccnt sectswritten;
reg = (uint8) (pa - IDBASE);
switch(reg) {
case ID_DATA_REG:
/* If we're in a DMA transfer, we need to be writing data to
* the disk buffer. Otherwise, we're writing to the FIFO. */
if (id_drq) {
/* If we're still in DRQ but we've written all our sectors,
* 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]);
id_end_rw(ID_EST_OVR);
return;
}
/* Write to the disk buffer */
if (id_buf_ptr < ID_SEC_SIZE) {
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tSECTOR DATA\t%02x\t(%c)\n",
R[NUM_PC], val, (val >= 0x20 && val < 0x7f) ? val : '.');
id_buf[id_buf_ptr++] = (uint8)(val & 0xff);
} else {
sim_debug(WRITE_MSG, &id_dev,
"[%08x] ERROR\tWDATA OVERRUN\n",
R[NUM_PC]);
id_end_rw(ID_EST_OVR);
return;
}
/* If we've hit the end of a sector, flush it */
if (id_buf_ptr >= ID_SEC_SIZE) {
/* It's time to start the next sector, and flush the old. */
id_buf_ptr = 0;
cyl = (uint16) (((uint16) id_lcnh << 8)|(uint16)id_lcnl);
id_cyl[UNIT_NUM] = cyl;
lba = id_lba(cyl, id_lhn, id_lsn);
if (sim_disk_wrsect(id_sel_unit, lba, id_buf, &sectswritten, 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);
}
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tWDATA\tCYL=%d PHN=%d LCNH=%02x "
"LCNL=%02x LHN=%d LSN=%d SCNT=%d LBA=%04x\n",
R[NUM_PC], cyl, id_phn, id_lcnh, id_lcnl,
id_lhn, id_lsn, id_scnt, lba);
id_update_chs();
if (--id_scnt == 0) {
id_end_rw(0);
}
} else {
/* Uh-oh! */
sim_debug(WRITE_MSG, &id_dev,
"[%08x] ERROR\tWDATA WRITE ERROR. lba=%04x\n",
R[NUM_PC], lba);
id_end_rw(ID_EST_DER);
return;
}
}
return;
} else {
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tDATA\t%02x\n",
R[NUM_PC], 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]);
}
}
return;
case ID_CMD_STAT_REG:
id_handle_command((uint8) val);
return;
default:
return;
}
}
void id_handle_command(uint8 val)
{
uint8 cmd, aux_cmd, sec, pattern;
uint16 cyl;
uint32 time;
t_lba lba;
/* Save the full command byte */
id_cmd = val;
/* Reset the FIFO pointer */
id_dpr = 0;
id_dpw = 0;
/* Writing a command always de-asserts INT output, UNLESS
the SRQ bit is set. */
if ((id_status & ID_STAT_SRQ) != ID_STAT_SRQ) {
id_irq = FALSE;
}
/* Is this an aux command or a full command? */
if ((val & 0xf0) == 0) {
aux_cmd = val & 0x0f;
id_status &= ~(ID_STAT_CB);
if (aux_cmd & ID_AUX_CLCE) {
sim_debug(WRITE_MSG, &id_dev,
"[%08x] \tCOMMAND\t%02x\tAUX:CLCE\n",
R[NUM_PC], val);
id_status &= ~(ID_STAT_CEL|ID_STAT_CEH);
sim_cancel(id_sel_unit);
}
if (aux_cmd & ID_AUX_HSRQ) {
sim_debug(WRITE_MSG, &id_dev,
"[%08x] \tCOMMAND\t%02x\tAUX:HSRQ\n",
R[NUM_PC], val);
id_status &= ~ID_STAT_SRQ;
sim_cancel(id_sel_unit);
}
if (aux_cmd & ID_AUX_CLB) {
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tAUX:CLBUF\n",
R[NUM_PC], 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);
sim_cancel(id_sel_unit);
id_clear_fifo();
}
/* Just return early */
return;
}
/* Now that we know it's not an aux command, get the unit number
this command is for */
id_sel_unit = &id_unit[UNIT_NUM];
cmd = (id_cmd >> 4) & 0xf;
/* If this command is anything BUT a sense interrupt status, set
* the seek flag to false.
*/
if (cmd != ID_CMD_SIS) {
id_seek_sis = FALSE;
}
id_status = ID_STAT_CB;
switch(cmd) {
case ID_CMD_SIS:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSense Int. Status - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_activate(DELAY_US(ID_SIS_WAIT));
break;
case ID_CMD_SPEC:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSpecify - %d - ETN=%02x ESN=%02x\n",
R[NUM_PC], val, UNIT_NUM, id_data[3], id_data[4]);
id_dtlh[UNIT_NUM] = id_data[1];
id_etn = id_data[3];
id_esn = id_data[4];
id_activate(DELAY_US(ID_SPEC_WAIT));
break;
case ID_CMD_SUS:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSense Unit Status - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_activate(DELAY_US(ID_SUS_WAIT));
break;
case ID_CMD_DERR:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tDetect Error - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_status |= ID_STAT_CEH;
id_activate(DELAY_US(ID_CMD_WAIT));
break;
case ID_CMD_RECAL:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tRecalibrate - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_cyl[UNIT_NUM] = 0;
time = id_cyl[UNIT_NUM];
id_activate(DELAY_US(ID_RECAL_WAIT + (time * ID_SEEK_WAIT)));
id_seek_sis = TRUE;
break;
case ID_CMD_SEEK:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSeek - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_lcnh = id_data[0];
id_lcnl = id_data[1];
cyl = id_lcnh << 8 | id_lcnl;
time = (uint32) abs(id_cyl[UNIT_NUM] - cyl);
id_activate(DELAY_US(ID_SEEK_BASE + (ID_SEEK_WAIT * time)));
id_cyl[UNIT_NUM] = cyl;
id_seek_sis = TRUE;
break;
case ID_CMD_FMT:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tFormat - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_phn = id_data[0];
id_scnt = id_data[1];
pattern = id_data[2];
/* Format scnt sectors with the given pattern, if attached */
if (id_sel_unit->flags & UNIT_ATT) {
/* Formatting soft-sectored disks always begins at sector 0 */
sec = 0;
while (id_scnt-- > 0) {
/* Write one sector of pattern */
for (id_buf_ptr = 0; id_buf_ptr < ID_SEC_SIZE; id_buf_ptr++) {
id_buf[id_buf_ptr] = pattern;
}
lba = id_lba(id_cyl[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);
} 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);
break;
}
}
id_data[0] = 0;
} else {
/* Not attached */
id_data[0] = ID_EST_NR;
}
id_data[1] = id_scnt;
id_activate(DELAY_US(ID_CMD_WAIT));
break;
case ID_CMD_VID:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tVerify ID - %d\n",
R[NUM_PC], val, UNIT_NUM);
id_data[0] = 0;
id_data[1] = 0x05; /* What do we put here? */
id_activate(DELAY_US(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, UNIT_NUM);
if (id_sel_unit->flags & UNIT_ATT) {
id_drq = TRUE;
/* Grab our arguments */
id_phn = id_data[0];
id_scnt = id_data[1];
/* Compute logical values used by ID verification */
id_lhn = id_phn;
id_lsn = 0;
} else {
sim_debug(EXECUTE_MSG, &id_dev,
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ ID.\n",
R[NUM_PC], UNIT_NUM);
}
id_activate(DELAY_US(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, UNIT_NUM);
id_activate(DELAY_US(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, UNIT_NUM);
if (id_sel_unit->flags & UNIT_ATT) {
id_drq = TRUE;
id_buf_ptr = 0;
/* Grab our arguments */
id_phn = id_data[0];
id_lcnh = ~(id_data[1]);
id_lcnl = id_data[2];
id_lhn = id_data[3];
id_lsn = id_data[4];
id_scnt = id_data[5];
} else {
sim_debug(EXECUTE_MSG, &id_dev,
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ DATA.\n",
R[NUM_PC], UNIT_NUM);
}
time = (uint32) abs(id_cyl[UNIT_NUM] - ((id_lcnh<<8)|id_lcnl));
if (time == 0) {
time++;
}
time = time * ID_SEEK_WAIT;
id_activate(DELAY_US(time + 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, UNIT_NUM);
id_activate(DELAY_US(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, UNIT_NUM);
id_activate(DELAY_US(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, UNIT_NUM);
id_activate(DELAY_US(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, UNIT_NUM);
if (id_sel_unit->flags & UNIT_ATT) {
id_drq = TRUE;
id_buf_ptr = 0;
/* Grab our arguments */
id_phn = id_data[0];
id_lcnh = ~(id_data[1]);
id_lcnl = id_data[2];
id_lhn = id_data[3];
id_lsn = id_data[4];
id_scnt = id_data[5];
} else {
sim_debug(EXECUTE_MSG, &id_dev,
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT WRITE.\n",
R[NUM_PC], UNIT_NUM);
}
time = (uint32) abs(id_cyl[UNIT_NUM] - ((id_lcnh<<8)|id_lcnl));
if (time == 0) {
time++;
}
time = time * ID_SEEK_WAIT;
id_activate(DELAY_US(time + ID_RW_WAIT));
break;
}
}
void id_drq_handled()
{
id_status &= ~ID_STAT_DRQ;
id_drq = FALSE;
}
CONST char *id_description(DEVICE *dptr)
{
return "72MB MFM Hard Disk";
}
t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf(st, "71MB MFM Integrated Hard Disk (ID)\n\n");
fprintf(st,
"The ID controller implements the integrated MFM hard disk controller\n"
"of the 3B2/400. Up to four drives are supported on a single controller.\n");
return SCPE_OK;
}

146
3B2/3b2_id.h Normal file
View file

@ -0,0 +1,146 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 Hard Disk (uPD7261) Header
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_ID_H__
#define __3B2_ID_H__
#include "3b2_defs.h"
#include "3b2_sysdev.h"
#include "sim_disk.h"
/* Command Codes (bits 3-7 of command byte) */
#define ID_DATA_REG 0
#define ID_CMD_STAT_REG 1
#define ID_CMD_AUX 0x00 /* Auxiliary Command */
#define ID_CMD_SIS 0x01 /* Sense int. status */
#define ID_CMD_SPEC 0x02 /* Specify */
#define ID_CMD_SUS 0x03 /* Sense unit status */
#define ID_CMD_DERR 0x04 /* Detect Error */
#define ID_CMD_RECAL 0x05 /* Recalibrate */
#define ID_CMD_SEEK 0x06 /* Seek */
#define ID_CMD_FMT 0x07 /* Format */
#define ID_CMD_VID 0x08 /* Verify ID */
#define ID_CMD_RID 0x09 /* Read ID */
#define ID_CMD_RDIAG 0x0A /* Read Diagnostic */
#define ID_CMD_RDATA 0x0B /* Read Data */
#define ID_CMD_CHECK 0x0C /* Check */
#define ID_CMD_SCAN 0x0D /* Scan */
#define ID_CMD_VDATA 0x0E /* Verify Data */
#define ID_CMD_WDATA 0x0F /* Write Data */
#define ID_AUX_RST 0x01
#define ID_AUX_CLB 0x02
#define ID_AUX_HSRQ 0x04
#define ID_AUX_CLCE 0x08
#define ID_STAT_DRQ 0x01
#define ID_STAT_NCI 0x02
#define ID_STAT_IER 0x04
#define ID_STAT_RRQ 0x08
#define ID_STAT_SRQ 0x10
#define ID_STAT_CEL 0x20
#define ID_STAT_CEH 0x40
#define ID_STAT_CB 0x80
#define ID_IST_SEN 0x80
#define ID_IST_RC 0x40
#define ID_IST_SER 0x20
#define ID_IST_EQC 0x10
#define ID_IST_NR 0x08
#define ID_UST_DSEL 0x10
#define ID_UST_SCL 0x08
#define ID_UST_TK0 0x04
#define ID_UST_RDY 0x02
#define ID_UST_WFL 0x01
#define ID_EST_ENC 0x80
#define ID_EST_OVR 0x40
#define ID_EST_DER 0x20
#define ID_EST_EQC 0x10
#define ID_EST_NR 0x08
#define ID_EST_ND 0x04
#define ID_EST_NWR 0x02
#define ID_EST_MAM 0x01
#define ID_DTLH_POLL 0x10
/* Geometry */
#define ID_CYL 925
#define ID_SEC_SIZE 512 /* Bytes per sector */
#define ID_SEC_CNT 18 /* Sectors per track */
#define ID_HEADS 9
#define ID_CYL_SIZE 512 * 18
/* Unit, Register, Device descriptions */
#define ID_FIFO_LEN 8
#define ID_IDFIELD_LEN 4
#define ID_NUM_UNITS 2
/* Unit number field in UNIT structure */
#define ID_UNIT_NUM u3
extern DEVICE id_dev;
extern DEBTAB sys_deb_tab[];
extern t_bool id_drq;
extern t_bool id_irq;
#define IDBASE 0x4a000
#define IDSIZE 0x2
/* Total disk size, in sectors */
#define ID_DSK_SIZE ID_CYL * ID_SEC_CNT * ID_HEADS
#define CMD_NUM ((id_cmd >> 4) & 0xf)
#define UNIT_NUM (id_cmd & 1) /* We intentionally ignore the top unit address bit */
/* Function prototypes */
t_stat id_svc(UNIT *uptr);
t_stat id_reset(DEVICE *dptr);
t_stat id_attach(UNIT *uptr, CONST char *cptr);
t_stat id_detach(UNIT *uptr);
uint32 id_read(uint32 pa, size_t size);
void id_write(uint32 pa, uint32 val, size_t size);
CONST char *id_description(DEVICE *dptr);
t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
void id_handle_data(uint8 val);
void id_handle_command(uint8 val);
static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec);
void id_drq_handled();
#endif

526
3B2/3b2_if.c Normal file
View file

@ -0,0 +1,526 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 Floppy (TMS2797NL) Implementation
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.
*/
#include "3b2_if.h"
#include <assert.h>
/*
* TODO: Macros used for debugging timers. Remove when debugging is complete.
*/
double if_start_time;
#define IF_START_TIME() { if_start_time = sim_gtime(); }
#define IF_DIFF_MS() ((sim_gtime() - if_start_time) / INST_PER_MS)
#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif
#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif
/*
* Disk Format:
* ------------
*
* - 80 Tracks
* - 9 Sectors per track
* - 2 heads
* - 512 bytes per sector
*
* 80 * 9 * 2 * 512 = 720KB
*
* The clock on pin 24 runs at 1.000 MHz, meaning that each
* step is 6ms and head settling time is 30ms.
*/
#define IF_STEP_DELAY 6 /* ms */
#define IF_R_DELAY 85 /* ms */
#define IF_W_DELAY 90 /* ms */
#define IF_VERIFY_DELAY 30 /* ms */
#define IF_HLD_DELAY 80 /* ms */
#define IF_HSW_DELAY 60 /* ms */
UNIT if_unit = {
UDATA (&if_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
UNIT_MUSTBUF+UNIT_BINK, IF_DSK_SIZE)
};
REG if_reg[] = {
{ NULL }
};
DEVICE if_dev = {
"IF", &if_unit, if_reg, NULL,
1, 16, 8, 1, 16, 8,
NULL, NULL, &if_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
IF_STATE if_state;
uint32 if_sec_ptr;
t_bool if_irq;
/* Function implementation */
static SIM_INLINE void if_set_irq()
{
if_irq = TRUE;
csr_data |= CSRDISK;
}
static SIM_INLINE void if_clear_irq()
{
if_irq = FALSE;
csr_data &= ~CSRDISK;
}
static SIM_INLINE void if_activate(uint32 delay)
{
IF_START_TIME();
sim_activate_abs(&if_unit, (int32) DELAY_MS(delay));
}
static SIM_INLINE void if_cancel_pending_irq()
{
sim_cancel(&if_unit);
}
t_stat if_svc(UNIT *uptr)
{
if_state.status &= ~(IF_BUSY);
switch(if_state.cmd & 0xf0) {
case IF_RESTORE:
if_state.status = (IF_TK_0|IF_HEAD_LOADED);
break;
case IF_SEEK:
if_state.status = IF_HEAD_LOADED;
if (if_state.track == 0) {
if_state.status |= IF_TK_0;
}
break;
}
if_state.cmd = 0;
/* Request an interrupt */
sim_debug(IRQ_MSG, &if_dev, "\tINTR\t\tDELTA=%f ms\n", IF_DIFF_MS());
if_set_irq();
return SCPE_OK;
}
t_stat if_reset(DEVICE *dptr)
{
if_state.status = IF_TK_0;
if_state.track = 0;
if_state.sector = 1;
if_sec_ptr = 0;
return SCPE_OK;
}
uint32 if_read(uint32 pa, size_t size) {
uint8 reg, data;
uint32 pos, pc;
UNIT *uptr;
uint8 *fbuf;
uptr = &(if_dev.units[0]);
reg = (uint8)(pa - IFBASE);
pc = R[NUM_PC];
fbuf = (uint8 *)uptr->filebuf;
switch (reg) {
case IF_STATUS_REG:
data = if_state.status;
/* If there's no image attached, we're not ready */
if ((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) {
data |= IF_NRDY;
}
/* Reading the status register always de-asserts the IRQ line */
if_clear_irq();
sim_debug(READ_MSG, &if_dev, "\tSTATUS\t%02x\n", data);
break;
case IF_TRACK_REG:
data = if_state.track;
sim_debug(READ_MSG, &if_dev, "\tTRACK\t%02x\n", data);
break;
case IF_SECTOR_REG:
data = if_state.sector;
sim_debug(READ_MSG, &if_dev, "\tSECTOR\t%02x\n", data);
break;
case IF_DATA_REG:
if_state.status &= ~IF_DRQ;
if (((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) ||
((if_state.cmd & 0xf0) != IF_READ_SEC &&
(if_state.cmd & 0xf0) != IF_READ_SEC_M)) {
/* Not attached, or not a read command */
switch (if_state.cmd & 0xf0) {
case IF_READ_ADDR:
/* Special state machine. */
switch (if_state.read_addr_ptr++) {
case 0:
if_state.data = if_state.track;
break;
case 1:
if_state.data = if_state.side;
break;
case 2:
if_state.data = if_state.sector;
break;
case 3:
if_state.data = 2; /* 512 byte */
break;
case 4:
/* TODO: Checksum */
if_state.data = 0;
break;
case 5:
/* TODO: Checksum */
if_state.data = 0;
if_state.read_addr_ptr = 0;
break;
}
}
sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", if_state.data);
return if_state.data;
}
pos = if_buf_offset();
data = fbuf[pos + if_sec_ptr++];
sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", data);
if (if_sec_ptr >= IF_SECTOR_SIZE) {
if_sec_ptr = 0;
}
break;
default:
data = 0xffu; // Compiler warning
break;
}
return data;
}
/* Handle the most recently received command */
void if_handle_command()
{
uint32 delay_ms = 0;
uint32 head_switch_delay = 0;
uint32 head_load_delay = 0;
if_sec_ptr = 0;
/* We're starting a new command. */
if_state.status = IF_BUSY;
/* Clear read addr state */
if_state.read_addr_ptr = 0;
switch(if_state.cmd & 0xf0) {
case IF_RESTORE:
case IF_SEEK:
case IF_STEP:
case IF_STEP_T:
case IF_STEP_IN:
case IF_STEP_IN_T:
case IF_STEP_OUT:
case IF_STEP_OUT_T:
if_state.cmd_type = 1;
if (if_state.cmd & IF_H_FLAG) {
head_load_delay = IF_HLD_DELAY;
}
break;
case IF_READ_SEC:
case IF_READ_SEC_M:
case IF_WRITE_SEC:
case IF_WRITE_SEC_M:
if_state.cmd_type = 2;
if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) {
head_switch_delay = IF_HSW_DELAY;
if_state.side = (if_state.cmd & IF_U_FLAG) >> 1;
}
break;
case IF_READ_ADDR:
case IF_READ_TRACK:
case IF_WRITE_TRACK:
if_state.cmd_type = 3;
if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) {
head_switch_delay = IF_HSW_DELAY;
if_state.side = (if_state.cmd & IF_U_FLAG) >> 1;
}
break;
case IF_FORCE_INT:
if_state.cmd_type = 4;
break;
}
switch(if_state.cmd & 0xf0) {
case IF_RESTORE:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRestore\n", if_state.cmd);
/* Reset HLT */
if_state.status &= ~IF_HEAD_LOADED;
/* If head should be loaded immediately, do so now */
if (if_state.cmd & IF_H_FLAG) {
if_state.status |= IF_HEAD_LOADED;
}
if (if_state.track == 0) {
if_state.status |= IF_TK_0;
if_state.track = 1; /* Kind of a gross hack */
}
if (if_state.cmd & IF_V_FLAG) {
delay_ms = (IF_STEP_DELAY * if_state.track) + IF_VERIFY_DELAY;
} else {
delay_ms = IF_STEP_DELAY * if_state.track;
}
if_activate(delay_ms);
if_state.data = 0;
if_state.track = 0;
break;
case IF_STEP:
case IF_STEP_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep\n", if_state.cmd);
if_activate(IF_STEP_DELAY);
if_state.track = (uint8) min(max((int) if_state.track + if_state.step_dir, 0), 0x4f);
break;
case IF_STEP_IN:
case IF_STEP_IN_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep In\n", if_state.cmd);
if_state.step_dir = IF_STEP_IN_DIR;
if_state.track = (uint8) max((int) if_state.track + if_state.step_dir, 0);
if_activate(IF_STEP_DELAY);
break;
case IF_STEP_OUT:
case IF_STEP_OUT_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep Out\n", if_state.cmd);
if_state.step_dir = IF_STEP_OUT_DIR;
if_state.track = (uint8) min((int) if_state.track + if_state.step_dir, 0x4f);
if_activate(IF_STEP_DELAY);
break;
case IF_SEEK:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tSeek\n", if_state.cmd);
/* Reset HLT */
if_state.status &= ~IF_HEAD_LOADED;
/* If head should be loaded immediately, do so now */
if (if_state.cmd & IF_H_FLAG) {
if_state.status |= IF_HEAD_LOADED;
}
/* Save the direction for stepping */
if (if_state.data > if_state.track) {
if_state.step_dir = IF_STEP_IN_DIR;
} else if (if_state.data < if_state.track) {
if_state.step_dir = IF_STEP_OUT_DIR;
}
/* The new track is in the data register */
if (if_state.data > IF_TRACK_COUNT-1) {
if_state.data = IF_TRACK_COUNT-1;
}
if (if_state.data == 0) {
if_state.status |= IF_TK_0;
} else {
if_state.status &= ~(IF_TK_0);
}
delay_ms = (uint32) abs(if_state.data - if_state.track);
if (delay_ms == 0) {
delay_ms++;
}
if (if_state.cmd & IF_V_FLAG) {
if_activate((IF_STEP_DELAY * delay_ms) + IF_VERIFY_DELAY + head_load_delay);
} else {
if_activate((IF_STEP_DELAY * delay_ms) + head_load_delay);
}
if_state.track = if_state.data;
break;
case IF_READ_SEC:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Sector %d/%d/%d\n",
if_state.cmd, if_state.track, if_state.side, if_state.sector);
/* We set DRQ right away to request the transfer. */
if_state.drq = TRUE;
if_state.status |= IF_DRQ;
if (if_state.cmd & IF_E_FLAG) {
if_activate(IF_R_DELAY + IF_VERIFY_DELAY + head_switch_delay);
} else {
if_activate(IF_R_DELAY + head_switch_delay);
}
break;
case IF_READ_SEC_M:
assert(0);
case IF_WRITE_SEC:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Sector %d/%d/%d\n",
if_state.cmd, if_state.track, if_state.side, if_state.sector);
/* We set DRQ right away to request the transfer. */
if_state.drq = TRUE;
if_state.status |= IF_DRQ;
if (if_state.cmd & IF_E_FLAG) {
if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay);
} else {
if_activate(IF_W_DELAY + head_switch_delay);
}
break;
case IF_WRITE_SEC_M:
assert(0);
break;
case IF_READ_ADDR:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Address\n", if_state.cmd);
if_state.drq = TRUE;
if_state.status |= IF_DRQ;
if_activate(IF_R_DELAY);
break;
case IF_READ_TRACK:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Track\n", if_state.cmd);
assert(0); /* NOT YET IMPLEMENTED */
break;
case IF_WRITE_TRACK:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Track\n", if_state.cmd);
assert(0); /* NOT YET IMPLEMENTED */
break;
case IF_FORCE_INT:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tForce Interrupt\n", if_state.cmd);
if_state.status = 0;
if (if_state.track == 0) {
if_state.status |= (IF_TK_0|IF_HEAD_LOADED);
}
if ((if_state.cmd & 0xf) == 0) {
if_cancel_pending_irq();
if_clear_irq(); /* TODO: Confirm this is right */
} else if ((if_state.cmd & 0x8) == 0x8) {
if_state.status |= IF_DRQ;
if_set_irq();
}
break;
}
}
void if_write(uint32 pa, uint32 val, size_t size)
{
UNIT *uptr;
uint8 reg;
uint32 pos;
uint8 *fbuf;
val = val & 0xff;
uptr = &(if_dev.units[0]);
reg = (uint8) (pa - IFBASE);
fbuf = (uint8 *)uptr->filebuf;
switch (reg) {
case IF_CMD_REG:
if_state.cmd = (uint8) val;
/* Writing to the command register always de-asserts the IRQ line */
if_clear_irq();
if_handle_command();
break;
case IF_TRACK_REG:
if_state.track = (uint8) val;
sim_debug(WRITE_MSG, &if_dev, "\tTRACK\t%02x\n", val);
break;
case IF_SECTOR_REG:
if_state.sector = (uint8) val;
sim_debug(WRITE_MSG, &if_dev, "\tSECTOR\t%02x\n", val);
break;
case IF_DATA_REG:
if_state.data = (uint8) val;
sim_debug(WRITE_MSG, &if_dev, "\tDATA\t%02x\n", val);
if (uptr->fileref == NULL ||
((if_state.cmd & 0xf0) != IF_WRITE_SEC &&
(if_state.cmd & 0xf0) != IF_WRITE_SEC_M)) {
/* Not attached, or not a write command */
break;
}
/* Find the right offset, and update the value. */
pos = if_buf_offset();
fbuf[pos + if_sec_ptr++] = (uint8) val;
if (if_sec_ptr >= IF_SECTOR_SIZE) {
if_sec_ptr = 0;
}
break;
default:
break;
}
}
/*
* Compute the offset of the currently selected C/H/S
*/
SIM_INLINE uint32 if_buf_offset()
{
uint32 pos;
pos = IF_TRACK_SIZE * if_state.track * 2;
if (if_state.side == 1) {
pos += IF_TRACK_SIZE;
}
pos += IF_SECTOR_SIZE * (if_state.sector - 1);
return pos;
}
void if_drq_handled()
{
if_state.status &= ~IF_DRQ;
}

138
3B2/3b2_if.h Normal file
View file

@ -0,0 +1,138 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 Floppy (TMS2797NL) Header
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_IF_H__
#define __3B2_IF_H__
#include "3b2_defs.h"
#include "3b2_sysdev.h"
#include "3b2_sys.h"
typedef struct {
uint8 data;
uint8 cmd;
uint8 cmd_type;
uint8 status;
uint8 track;
uint8 sector;
uint8 side;
uint8 read_addr_ptr;
int8 step_dir;
t_bool drq;
} IF_STATE;
extern DEVICE if_dev;
extern DEBTAB sys_deb_tab[];
extern IF_STATE if_state;
extern t_bool if_irq;
#define IFBASE 0x4d000
#define IFSIZE 0x10
#define IF_STATUS_REG 0
#define IF_CMD_REG 0
#define IF_TRACK_REG 1
#define IF_SECTOR_REG 2
#define IF_DATA_REG 3
/* Status Bits */
#define IF_BUSY 0x01
#define IF_DRQ 0x02
#define IF_INDEX 0x02
#define IF_TK_0 0x04
#define IF_LOST_DATA 0x04
#define IF_CRC_ERR 0x08
#define IF_SEEK_ERR 0x10
#define IF_RNF 0x10
#define IF_HEAD_LOADED 0x20
#define IF_RECORD_TYPE 0x20
#define IF_WP 0x40
#define IF_NRDY 0x80
/* Type I Commands */
#define IF_RESTORE 0x00
#define IF_SEEK 0x10
#define IF_STEP 0x20
#define IF_STEP_T 0x30
#define IF_STEP_IN 0x40
#define IF_STEP_IN_T 0x50
#define IF_STEP_OUT 0x60
#define IF_STEP_OUT_T 0x70
/* Type II Commands */
#define IF_READ_SEC 0x80
#define IF_READ_SEC_M 0x90
#define IF_WRITE_SEC 0xA0
#define IF_WRITE_SEC_M 0xB0
/* Type III Commands */
#define IF_READ_ADDR 0xC0
#define IF_READ_TRACK 0xE0
#define IF_WRITE_TRACK 0xF0
/* Type IV Command */
#define IF_FORCE_INT 0xD0
/* Command flags */
#define IF_C_FLAG 0x02
#define IF_V_FLAG 0x04
#define IF_E_FLAG 0x04
#define IF_U_FLAG 0x02
#define IF_H_FLAG 0x08
#define IF_S_FLAG 0x10
/* Constants */
#define IF_SIDES 2
#define IF_TRACK_SIZE 4608
#define IF_SECTOR_SIZE 512
#define IF_TRACK_COUNT 80
#define IF_STEP_IN_DIR 1
#define IF_STEP_OUT_DIR -1
#define IF_DSK_SIZE (IF_SIDES * IF_TRACK_SIZE * IF_TRACK_COUNT)
/* Function prototypes */
static SIM_INLINE void if_set_irq();
static SIM_INLINE void if_clear_irq();
static SIM_INLINE void if_cancel_pending_irq();
t_stat if_svc(UNIT *uptr);
t_stat if_reset(DEVICE *dptr);
uint32 if_read(uint32 pa, size_t size);
void if_write(uint32 pa, uint32 val, size_t size);
void if_drq_handled();
void if_handle_command();
uint32 if_buf_offset();
#endif

135
3B2/3b2_io.c Normal file
View file

@ -0,0 +1,135 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 IO dispatch implemenation
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.
*/
#include "3b2_io.h"
struct iolink iotable[] = {
{ MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write },
{ IFBASE, IFBASE+IFSIZE, &if_read, &if_write },
{ IDBASE, IDBASE+IDSIZE, &id_read, &id_write },
{ TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write },
{ NVRAMBASE, NVRAMBASE+NVRAMSIZE, &nvram_read, &nvram_write },
{ CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write },
{ IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write },
{ DMAIDBASE, DMAIDBASE+DMAIDSIZE, &dmac_read, &dmac_write },
{ DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write },
{ DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write },
{ DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write },
{ DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write },
{ TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write },
{ 0, 0, NULL, NULL}
};
uint32 io_read(uint32 pa, size_t size)
{
struct iolink *p;
/* Special devices */
if (pa == 0x4c003) {
/* MEMSIZE register */
/* It appears that the following values map to memory sizes:
0x00: 512KB ( 524,288 B)
0x01: 2MB (2,097,152 B)
0x02: 1MB (1,048,576 B)
0x03: 4MB (4,194,304 B)
*/
switch(MEM_SIZE) {
case 0x80000: /* 512KB */
return 0;
case 0x100000: /* 1MB */
return 2;
case 0x200000: /* 2MB */
return 1;
case 0x400000: /* 4MB */
return 3;
default:
return 0;
}
}
/* IO Board Area - Unimplemented */
if (pa >= 0x200000 && pa < 0x2000000) {
sim_debug(IO_D_MSG, &cpu_dev, "[%08x] [IO BOARD READ] ADDR=%08x\n", R[NUM_PC], pa);
/* When we implement boards, register them here
N.B.: High byte of board ID is read at 0xnnnnn0,
low byte at 0xnnnnn1 */
/* Since we have no cards in our system, there's nothing
to read. We indicate that our bus read timed out with
CSRTIMO, then abort.*/
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
for (p = &iotable[0]; p->low != 0; p++) {
if ((pa >= p->low) && (pa < p->high) && p->read) {
return p->read(pa, size);
}
}
/* Not found. */
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [io_read] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
void io_write(uint32 pa, uint32 val, size_t size)
{
struct iolink *p;
/* IO Board Area - Unimplemented */
if (pa >= 0x200000 && pa < 0x2000000) {
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] ADDR=%08x, DATA=%08x\n",
R[NUM_PC], pa, val);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return;
}
for (p = &iotable[0]; p->low != 0; p++) {
if ((pa >= p->low) && (pa < p->high) && p->write) {
p->write(pa, val, size);
return;
}
}
/* Not found. */
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [io_write] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}

48
3B2/3b2_io.h Normal file
View file

@ -0,0 +1,48 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 IO dispatch (Header)
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_IO_H_
#define _3B2_IO_H_
#include "3b2_sysdev.h"
#include "3b2_iu.h"
#include "3b2_if.h"
#include "3b2_id.h"
#include "3b2_dmac.h"
#include "3b2_mmu.h"
struct iolink {
uint32 low;
uint32 high;
uint32 (*read)(uint32 pa, size_t size);
void (*write)(uint32 pa, uint32 val, size_t size);
};
#endif

546
3B2/3b2_iu.c Normal file
View file

@ -0,0 +1,546 @@
/* 3b2_iu.c: SCN2681A Dual UART Implementation
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.
*/
#include "3b2_iu.h"
/*
* Registers
*/
/* The IU state */
IU_STATE iu_state;
t_bool iu_increment_a = FALSE;
t_bool iu_increment_b = FALSE;
extern uint16 csr_data;
UNIT iu_unit[] = {
{ UDATA(&iu_svc_tti, UNIT_IDLE, 0), TMLN_SPD_9600_BPS },
{ UDATA(&iu_svc_tto, TT_MODE_8B, 0), SERIAL_OUT_WAIT },
{ UDATA(&iu_svc_timer, 0, 0) },
{ NULL }
};
BITFIELD sr_bits[] = {
BIT(RXRDY),
BIT(FFULL),
BIT(TXRDY),
BIT(TXEMT),
BIT(OVRN_E),
BIT(PRTY_E),
BIT(FRM_E),
BIT(BRK),
ENDBITS
};
BITFIELD isr_bits[] = {
BIT(TXRDYA),
BIT(RXRDY_FFA),
BIT(DLTA_BRKA),
BIT(CTR_RDY),
BIT(TXRDYB),
BIT(RXRDY_FFB),
BIT(DLTA_BRKB),
BIT(IPC),
ENDBITS
};
BITFIELD acr_bits[] = {
BIT(BRG_SET),
BITFFMT(TMR_MODE,3,%d),
BIT(DLTA_IP3),
BIT(DLTA_IP2),
BIT(DLTA_IP1),
BIT(DLTA_IP0),
ENDBITS
};
BITFIELD conf_bits[] = {
BIT(TX_EN),
BIT(RX_EN),
ENDBITS
};
REG iu_reg[] = {
{ 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(CTR, iu_state.c_set, 16, "Counter Setting") },
{ HRDATAD(IP, iu_state.inprt, 8, "Input Port") },
{ HRDATADF(STAT_A, iu_state.port[0].stat, 8, "Status (Port A)", sr_bits) },
{ HRDATAD(DATA_A, iu_state.port[0].buf, 8, "Data (Port A)") },
{ HRDATADF(CONF_A, iu_state.port[0].conf, 8, "Config (Port A)", conf_bits) },
{ HRDATADF(STAT_B, iu_state.port[1].stat, 8, "Status (Port B)", sr_bits) },
{ HRDATAD(DATA_B, iu_state.port[1].buf, 8, "Data (Port B)") },
{ HRDATADF(CONF_B, iu_state.port[1].conf, 8, "Config (Port B)", conf_bits) },
{ NULL }
};
DEVICE iu_dev = {
"IU", iu_unit, iu_reg, NULL,
3, 8, 32, 1, 8, 8,
NULL, NULL, &iu_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
void increment_modep_a()
{
iu_increment_a = FALSE;
iu_state.port[PORT_A].modep++;
if (iu_state.port[PORT_A].modep > 1) {
iu_state.port[PORT_A].modep = 0;
}
}
void increment_modep_b()
{
iu_increment_b = FALSE;
iu_state.port[PORT_B].modep++;
if (iu_state.port[PORT_B].modep > 1) {
iu_state.port[PORT_B].modep = 0;
}
}
void iu_txrdy_irq(uint8 portno) {
uint8 irq_mask = (uint8) (1u << (portno * 4));
if ((iu_state.imr & irq_mask) &&
(iu_state.port[portno].conf & TX_EN) &&
(iu_state.port[portno].stat & STS_TXR)) {
sim_debug(EXECUTE_MSG, &iu_dev,
"Firing IU TTY IRQ 13 ON TX/State Change\n");
csr_data |= CSRUART;
}
}
t_stat iu_reset(DEVICE *dptr)
{
uint8 portno;
memset(&iu_state, 0, sizeof(struct iu_state));
iu_state.opcr = 0;
if (!sim_is_active(&iu_unit[UNIT_CONSOLE_TTI])) {
iu_unit[UNIT_CONSOLE_TTI].wait = IU_TTY_DELAY;
sim_activate(&iu_unit[UNIT_CONSOLE_TTI],
iu_unit[UNIT_CONSOLE_TTI].wait);
}
for (portno = 0; portno < 2; portno++) {
iu_state.port[portno].buf = 0;
iu_state.port[portno].modep = 0;
iu_state.port[portno].conf = 0;
iu_state.port[portno].stat = 0;
}
return SCPE_OK;
}
t_stat iu_svc_tti(UNIT *uptr)
{
int32 temp;
sim_clock_coschedule_tmr_abs(uptr, TMR_CLK, 2);
/* 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 (iu_state.port[PORT_A].conf & RX_EN) {
iu_state.port[PORT_A].buf = (temp & 0xff);
iu_state.port[PORT_A].stat |= STS_RXR;
iu_state.istat |= ISTS_RAI;
if (iu_state.imr & 0x02) {
sim_debug(EXECUTE_MSG, &iu_dev,
"Firing IU TTY IRQ 13 ON RECEIVE (%c)\n",
(temp & 0xff));
csr_data |= CSRUART;
}
}
return SCPE_OK;
}
t_stat iu_svc_tto(UNIT *uptr)
{
sim_debug(EXECUTE_MSG, &iu_dev,
"Calling iu_txrdy_irq on iu_svc_tto\n");
iu_txrdy_irq(PORT_A);
return SCPE_OK;
}
t_stat iu_svc_timer(UNIT *uptr)
{
iu_state.istat |= ISTS_CRI;
if (iu_state.imr & 0x08) {
csr_data |= CSRUART;
}
return SCPE_OK;
}
/*
* Reg | Name (Read) | Name (Write)
* -----+-------------------------+----------------------------
* 0 | Mode Register 1/2 A | Mode Register 1/2 A
* 1 | Status Register A | Clock Select Register A
* 2 | BRG Test | Command Register A
* 3 | Rx Holding Register A | Tx Holding Register A
* 4 | Input Port Change Reg. | Aux. Control Register
* 5 | Interrupt Status Reg. | Interrupt Mask Register
* 6 | Counter/Timer Upper Val | C/T Upper Preset Val.
* 7 | Counter/Timer Lower Val | C/T Lower Preset Val.
* 8 | Mode Register B | Mode Register B
* 9 | Status Register B | Clock Select Register B
* 10 | 1X/16X Test | Command Register B
* 11 | Rx Holding Register B | Tx Holding Register B
* 12 | *Reserved* | *Reserved*
* 13 | Input Ports IP0 to IP6 | Output Port Conf. Reg.
* 14 | Start Counter Command | Set Output Port Bits Cmd.
* 15 | Stop Counter Command | Reset Output Port Bits Cmd.
*/
uint32 iu_read(uint32 pa, size_t size)
{
uint8 reg, modep;
uint32 data, delay;
reg = (uint8) (pa - IUBASE);
switch (reg) {
case MR12A:
modep = iu_state.port[PORT_A].modep;
data = iu_state.port[PORT_A].mode[modep];
iu_increment_a = TRUE;
break;
case SRA:
data = iu_state.port[PORT_A].stat;
break;
case RHRA:
data = iu_state.port[PORT_A].buf;
iu_state.port[PORT_A].stat &= ~STS_RXR;
iu_state.istat &= ~ISTS_RAI;
csr_data &= ~CSRUART;
break;
case IPCR:
data = iu_state.ipcr;
/* Reading the port resets the upper four bits */
iu_state.ipcr &= 0x0f;
csr_data &= ~CSRUART;
break;
case ISR:
data = iu_state.istat;
break;
case CTU:
data = (iu_state.c_set >> 8) & 0xff;
break;
case CTL:
data = iu_state.c_set & 0xff;
break;
case MR12B:
modep = iu_state.port[PORT_B].modep;
data = iu_state.port[PORT_B].mode[modep];
iu_increment_b = TRUE;
break;
case SRB:
data = iu_state.port[PORT_B].stat;
break;
case RHRB:
data = iu_state.port[PORT_B].buf;
iu_state.port[PORT_B].stat &= ~STS_RXR;
iu_state.istat &= ~ISTS_RBI;
break;
case INPRT:
/* TODO: Correct behavior for DCD on contty */
/* For now, this enables DCD/DTR on console only */
data = 0x8e;
break;
case START_CTR:
data = 0;
iu_state.istat &= ~ISTS_CRI;
delay = (uint32) (IU_TIMER_STP * iu_state.c_set);
sim_activate_abs(&iu_unit[UNIT_IU_TIMER], (int32) DELAY_US(delay));
break;
case STOP_CTR:
data = 0;
iu_state.istat &= ~ISTS_CRI;
csr_data &= ~CSRUART;
sim_cancel(&iu_unit[UNIT_IU_TIMER]);
break;
case 17: /* Clear DMAC interrupt */
data = 0;
iu_state.drqa = FALSE;
iu_state.drqb = FALSE;
csr_data &= ~CSRDMA;
break;
default:
data = 0;
break;
}
return data;
}
void iu_write(uint32 pa, uint32 val, size_t size)
{
uint8 reg;
uint8 modep;
reg = (uint8) (pa - IUBASE);
switch (reg) {
case MR12A:
modep = iu_state.port[PORT_A].modep;
iu_state.port[PORT_A].mode[modep] = val & 0xff;
iu_increment_a = TRUE;
break;
case CSRA:
/* Set baud rate - not implemented */
break;
case CRA: /* Command A */
iu_w_cmd(PORT_A, (uint8) val);
break;
case THRA: /* TX/RX Buf A */
/* Loopback mode */
if ((iu_state.port[PORT_A].mode[1] & 0xc0) == 0x80) {
iu_state.port[PORT_A].buf = (uint8) val;
iu_state.port[PORT_A].stat |= STS_RXR;
iu_state.istat |= ISTS_RAI;
} else {
iu_tx(PORT_A, (uint8) val);
}
csr_data &= ~CSRUART;
break;
case ACR: /* Auxiliary Control Register */
iu_state.acr = (uint8) val;
break;
case IMR:
iu_state.imr = (uint8) val;
csr_data &= ~CSRUART;
/* Possibly cause an interrupt */
sim_debug(EXECUTE_MSG, &iu_dev,
">>> calling iu_txrdy_irq() on IMR write.\n");
iu_txrdy_irq(PORT_A);
iu_txrdy_irq(PORT_B);
break;
case CTUR: /* Counter/Timer Upper Preset Value */
/* Clear out high byte */
iu_state.c_set &= 0x00ff;
/* Set high byte */
iu_state.c_set |= (val & 0xff) << 8;
break;
case CTLR: /* Counter/Timer Lower Preset Value */
/* Clear out low byte */
iu_state.c_set &= 0xff00;
/* Set low byte */
iu_state.c_set |= (val & 0xff);
break;
case MR12B:
modep = iu_state.port[PORT_B].modep;
iu_state.port[PORT_B].mode[modep] = val & 0xff;
iu_increment_b = TRUE;
break;
case CRB: /* Command B */
iu_w_cmd(PORT_B, (uint8) val);
break;
case CSRB:
break;
case THRB: /* TX/RX Buf B */
/* Loopback mode */
if ((iu_state.port[PORT_B].mode[1] & 0xc0) == 0x80) {
iu_state.port[PORT_B].buf = (uint8) val;
iu_state.port[PORT_B].stat |= STS_RXR;
iu_state.istat |= ISTS_RAI;
} else {
iu_tx(PORT_B, (uint8) val);
}
break;
case OPCR:
iu_state.opcr = (uint8) val;
break;
case SOPR:
break;
case ROPR:
break;
default:
break;
}
}
void iua_drq_handled()
{
sim_debug(EXECUTE_MSG, &iu_dev,
"Firing IU TTY IRQ 13 On DRQ Handled\n");
csr_data |= CSRDMA;
}
void iub_drq_handled()
{
sim_debug(EXECUTE_MSG, &iu_dev,
">>> DRQB handled.\n");
}
static SIM_INLINE void iu_tx(uint8 portno, uint8 val)
{
struct port *p;
p = &iu_state.port[portno];
p->buf = val;
if (p->conf & TX_EN) {
sim_debug(EXECUTE_MSG, &iu_dev,
"[%08x] TRANSMIT: %02x (%c)\n",
R[NUM_PC], val, val);
p->stat &= ~(STS_TXR|STS_TXE);
iu_state.istat &= ~(1 << (portno*4));
/* Write the character to the SIMH console */
sim_putchar(p->buf);
/* The buffer is now empty, we've transmitted, so set TXR */
p->stat |= STS_TXR;
iu_state.istat |= (1 << (portno*4));
/* Possibly cause an interrupt */
sim_activate_abs(&iu_unit[UNIT_CONSOLE_TTO],
iu_unit[UNIT_CONSOLE_TTO].wait);
}
}
static SIM_INLINE void iu_w_cmd(uint8 portno, uint8 cmd)
{
/* Enable or disable transmitter */
/* Disable always wins, if both are set */
if (cmd & CMD_DTX) {
iu_state.port[portno].conf &= ~TX_EN;
iu_state.port[portno].stat &= ~STS_TXR;
iu_state.port[portno].stat &= ~STS_TXE;
iu_state.drqa = FALSE;
sim_debug(EXECUTE_MSG, &iu_dev,
">>> Disabling transmitter.\n");
} else if (cmd & CMD_ETX) {
iu_state.port[portno].conf |= TX_EN;
/* TXE and TXR are always set by an ENABLE */
iu_state.port[portno].stat |= STS_TXR;
iu_state.port[portno].stat |= STS_TXE;
iu_state.istat |= 1 << (portno*4);
iu_state.drqa = TRUE;
sim_debug(EXECUTE_MSG, &iu_dev,
">>> Calling iu_txrdy_irq() on TX Enable\n");
iu_txrdy_irq(portno);
}
/* Enable or disable receiver. */
/* Disable always wins, if both are set */
if (cmd & CMD_DRX) {
iu_state.port[portno].conf &= ~RX_EN;
iu_state.port[portno].stat &= ~STS_RXR;
} else if (cmd & CMD_ERX) {
iu_state.port[portno].conf |= RX_EN;
}
/* Command register bits 6-4 have special meaning */
switch ((cmd >> CMD_MISC_SHIFT) & CMD_MISC_MASK) {
case 1:
/* Causes the Channel A MR pointer to point to MR1. */
iu_state.port[portno].modep = 0;
break;
case 2:
/* Reset receiver. Resets the Channel's receiver as if a
hardware reset had been applied. The receiver is disabled
and the FIFO is flushed. */
iu_state.port[portno].stat &= ~STS_RXR;
iu_state.port[portno].conf &= ~RX_EN;
iu_state.port[portno].buf = 0;
break;
case 3:
/* Reset transmitter. Resets the Channel's transmitter as if a
hardware reset had been applied. */
iu_state.port[portno].stat &= ~STS_TXR;
iu_state.port[portno].stat &= ~STS_TXE;
iu_state.port[portno].conf &= ~TX_EN;
iu_state.port[portno].buf = 0;
break;
case 4:
/* 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. */
iu_state.port[portno].stat &= ~(STS_FER|STS_PER|STS_OER);
break;
case 5:
/* 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));
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 */
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 */
break;
}
}

192
3B2/3b2_iu.h Normal file
View file

@ -0,0 +1,192 @@
/* 3b2_iu.h: SCN2681A Dual UART Header
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_IU_H__
#define __3B2_IU_H__
#include "3b2_defs.h"
#include "3b2_sysdev.h"
#define CMD_ERX 0x01 /* Enable receiver */
#define CMD_DRX 0x02 /* Disable receiver */
#define CMD_ETX 0x04 /* Enable transmitter */
#define CMD_DTX 0x08 /* Disable transmitter */
#define CMD_MISC_SHIFT 4 /* Command */
#define CMD_MISC_MASK 0x7
#define STS_RXR 0x01 /* Receiver ready */
#define STS_FFL 0x02 /* FIFO full */
#define STS_TXR 0x04 /* Transmitter ready */
#define STS_TXE 0x08 /* Transmitter empty */
#define STS_OER 0x10 /* Overrun error */
#define STS_PER 0x20 /* Parity error */
#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_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_IPC 0x80 /* Interrupt port change */
#define MODE_V_CHM 6 /* Channel mode */
#define MODE_M_CHM 0x3
#define PORT_A 0
#define PORT_B 1
/* Used by the DMAC */
#define IUA_DATA_REG 3
#define IUB_DATA_REG 11
/* Registers - Read */
#define SRA 1
#define RHRA 3
#define IPCR 4
#define ISR 5
#define CTU 6
#define CTL 7
#define SRB 9
#define RHRB 11
#define INPRT 13 /* Input port data */
#define START_CTR 14
#define STOP_CTR 15
/* Registers - Write */
#define CSRA 1
#define CRA 2
#define THRA 3
#define ACR 4
#define IMR 5
#define CTUR 6
#define CTLR 7
#define CSRB 9
#define CRB 10
#define THRB 11
#define OPCR 13
#define SOPR 14
#define ROPR 15
#define UNIT_CONSOLE_TTI 0
#define UNIT_CONSOLE_TTO 1
#define UNIT_IU_TIMER 2
/* Registers - R/W */
#define MR12A 0
#define MR12B 8
/* Port configuration */
#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
#define IU_MODE(x) ((x & UM_MASK) >> UM_SHIFT)
extern DEVICE iu_dev;
#define IU_TTY_DELAY 25000
#define IUBASE 0x49000
#define IUSIZE 0x100
/* The UART is driven by a 3.6864 MHz crystal. This is divided by 16
to clock the timer. (One peculiarity: 3.6864 MHz /16 is 230400 Hz,
but the SVR3 source code claims the /16 clock is actually 230525
Hz. So, we'll go with 230525 Hz until proven otherwise.)
UART clock period = 4338ns
System clock period = 100ns
That means the system ticks 43.3792 times for every one tick of the
UART clock.
But this is a simulated system, where each simulator step is
CYCLES_PER_INST long. So we take that into account.
*/
#define IU_TIMER_STP 4.33792
struct 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 buf; /* Character data */
};
typedef struct iu_state {
uint8 istat; /* Interrupt Status */
uint8 imr; /* Interrupt Mask Register */
uint16 c_set; /* Timer / Counter Setting */
int32 c_val; /* Timer / Counter Value */
t_bool c_en; /* Counter Enabled */
t_bool drqa; /* Port A DRQ */
t_bool drqb; /* Port B DRQ */
uint8 acr;
uint8 opcr; /* Output Port Configuration */
uint8 inprt; /* Input Port Data */
uint8 ipcr; /* Input Port Change Register */
struct port port[2]; /* Port A and B */
} IU_STATE;
extern IU_STATE iu_state;
/* Function prototypes */
t_stat iu_reset(DEVICE *dptr);
t_stat iu_svc_tti(UNIT *uptr);
t_stat iu_svc_tto(UNIT *uptr);
t_stat iu_svc_timer(UNIT *uptr);
uint32 iu_read(uint32 pa, size_t size);
void iu_write(uint32 pa, uint32 val, size_t size);
void iua_drq_handled();
void iub_drq_handled();
static SIM_INLINE void iu_tx(uint8 portno, uint8 val);
static SIM_INLINE void iu_w_buf(uint8 portno, uint8 val);
static SIM_INLINE void iu_w_cmd(uint8 portno, uint8 val);
static SIM_INLINE void iu_update_rxi(uint8 c);
static SIM_INLINE void iu_update_txi();
#endif

882
3B2/3b2_mmu.c Normal file
View file

@ -0,0 +1,882 @@
/* 3b2_mmu.c: AT&T 3B2 Model 400 MMU (WE32101) Implementation
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.
*/
#include "3b2_mmu.h"
UNIT mmu_unit = { UDATA(NULL, 0, 0) };
MMU_STATE mmu_state;
REG mmu_reg[] = {
{ HRDATAD (ENABLE, mmu_state.enabled, 1, "Enabled?") },
{ HRDATAD (CONFIG, mmu_state.conf, 32, "Configuration") },
{ HRDATAD (VAR, mmu_state.var, 32, "Virtual Address") },
{ HRDATAD (FCODE, mmu_state.fcode, 32, "Fault Code") },
{ HRDATAD (FADDR, mmu_state.faddr, 32, "Fault Address") },
{ BRDATA (SDCL, mmu_state.sdcl, 16, 32, MMU_SDCS) },
{ BRDATA (SDCR, mmu_state.sdch, 16, 32, MMU_SDCS) },
{ BRDATA (PDCLL, mmu_state.pdcll, 16, 32, MMU_PDCS) },
{ BRDATA (PDCLH, mmu_state.pdclh, 16, 32, MMU_PDCS) },
{ BRDATA (PDCRL, mmu_state.pdcrl, 16, 32, MMU_PDCS) },
{ BRDATA (PDCRH, mmu_state.pdcrh, 16, 32, MMU_PDCS) },
{ BRDATA (SRAMA, mmu_state.sra, 16, 32, MMU_SRS) },
{ BRDATA (SRAMB, mmu_state.srb, 16, 32, MMU_SRS) },
{ NULL }
};
DEVICE mmu_dev = {
"MMU", &mmu_unit, mmu_reg, NULL,
1, 16, 8, 4, 16, 32,
NULL, NULL, &mmu_init,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
t_stat mmu_init(DEVICE *dptr)
{
flush_caches();
return SCPE_OK;
}
uint32 mmu_read(uint32 pa, size_t size)
{
uint32 offset;
uint32 data = 0;
offset = (pa >> 2) & 0x1f;
switch ((pa >> 8) & 0xf) {
case MMU_SDCL:
data = mmu_state.sdcl[offset];
sim_debug(READ_MSG, &mmu_dev,
"MMU_SDCL[%d] = %08x\n",
offset, data);
break;
case MMU_SDCH:
data = mmu_state.sdch[offset];
sim_debug(READ_MSG, &mmu_dev,
"MMU_SDCH[%d] = %08x\n",
offset, data);
break;
case MMU_PDCRL:
data = mmu_state.pdcrl[offset];
sim_debug(READ_MSG, &mmu_dev,
"MMU_PDCRL[%d] = %08x\n",
offset, data);
break;
case MMU_PDCRH:
data = mmu_state.pdcrh[offset];
sim_debug(READ_MSG, &mmu_dev,
"MMU_PDCRH[%d] = %08x\n",
offset, data);
break;
case MMU_PDCLL:
data = mmu_state.pdcll[offset];
sim_debug(READ_MSG, &mmu_dev,
"MMU_PDCLL[%d] = %08x\n",
offset, data);
break;
case MMU_PDCLH:
data = mmu_state.pdclh[offset];
sim_debug(READ_MSG, &mmu_dev,
"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);
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);
break;
case MMU_FC:
data = mmu_state.fcode;
break;
case MMU_FA:
data = mmu_state.faddr;
break;
case MMU_CONF:
data = mmu_state.conf & 0x7;
sim_debug(READ_MSG, &mmu_dev,
"[%08x] MMU_CONF = %08x\n",
R[NUM_PC], data);
break;
case MMU_VAR:
data = mmu_state.var;
sim_debug(READ_MSG, &mmu_dev,
"[%08x] MMU_VAR = %08x\n",
R[NUM_PC], data);
break;
}
return data;
}
void mmu_write(uint32 pa, uint32 val, size_t size)
{
uint32 offset;
offset = (pa >> 2) & 0x1f;
switch ((pa >> 8) & 0xf) {
case MMU_SDCL:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_SDCL[%d] = %08x\n",
offset, val);
mmu_state.sdcl[offset] = val;
break;
case MMU_SDCH:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_SDCH[%d] = %08x\n",
offset, val);
mmu_state.sdch[offset] = val;
break;
case MMU_PDCRL:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_PDCRL[%d] = %08x\n",
offset, val);
mmu_state.pdcrl[offset] = val;
break;
case MMU_PDCRH:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_PDCRH[%d] = %08x\n",
offset, val);
mmu_state.pdcrh[offset] = val;
break;
case MMU_PDCLL:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_PDCLL[%d] = %08x\n",
offset, val);
mmu_state.pdcll[offset] = val;
break;
case MMU_PDCLH:
sim_debug(WRITE_MSG, &mmu_dev,
"MMU_PDCLH[%d] = %08x\n",
offset, val);
mmu_state.pdclh[offset] = val;
break;
case MMU_SRAMA:
offset = offset & 3;
mmu_state.sra[offset] = val;
mmu_state.sec[offset].addr = val & 0xffffffe0;
/* We flush the entire section on writing SRAMA */
flush_cache_sec((uint8) offset);
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] MMU_SRAMA[%d] = %08x (addr=%08x)\n",
R[NUM_PC], offset, val, mmu_state.sec[offset].addr);
break;
case MMU_SRAMB:
offset = offset & 3;
mmu_state.srb[offset] = val;
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);
break;
case MMU_FC:
mmu_state.fcode = val;
break;
case MMU_FA:
mmu_state.faddr = val;
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] MMU_FAULT_ADDR = %08x\n",
R[NUM_PC], val);
break;
case MMU_CONF:
mmu_state.conf = val & 0x7;
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] MMU_CONF = %08x\n",
R[NUM_PC], val);
break;
case MMU_VAR:
mmu_state.var = val;
flush_sdce(val);
flush_pdce(val);
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] MMU_VAR = %08x\n",
R[NUM_PC], val);
break;
}
}
t_bool addr_is_rom(uint32 pa)
{
return (pa < BOOT_CODE_SIZE);
}
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)
{
return ((pa >= IO_BASE && pa < IO_BASE + IO_SIZE) ||
(pa >= IOB_BASE && pa < IOB_BASE + IOB_SIZE));
}
/*
* Raw physical reads and writes.
*
* The WE32100 is a BIG-endian machine, meaning that words are
* arranged in increasing address from most-significant byte to
* least-significant byte.
*/
/*
* Read Word (Physical Address)
*/
uint32 pread_w(uint32 pa)
{
uint32 *m;
uint32 index;
if (pa & 3) {
sim_debug(READ_MSG, &mmu_dev,
"[%08x] Cannot read physical address. ALIGNMENT ISSUE: %08x\n",
R[NUM_PC], pa);
csr_data |= CSRALGN;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}
if (addr_is_io(pa)) {
return io_read(pa, 32);
}
if (addr_is_rom(pa)) {
m = ROM;
index = pa >> 2;
} else if (addr_is_mem(pa)) {
m = RAM;
index = (pa - PHYS_MEM_BASE) >> 2;
} else {
return 0;
}
return m[index];
}
/*
* Write Word (Physical Address)
*/
void pwrite_w(uint32 pa, uint32 val)
{
if (pa & 3) {
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] Cannot write physical address. ALIGNMENT ISSUE: %08x\n",
R[NUM_PC], pa);
csr_data |= CSRALGN;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}
if (addr_is_io(pa)) {
io_write(pa, val, 32);
return;
}
if (addr_is_mem(pa)) {
RAM[(pa - PHYS_MEM_BASE) >> 2] = val;
return;
}
}
/*
* Read Halfword (Physical Address)
*/
uint16 pread_h(uint32 pa)
{
uint32 *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);
csr_data |= CSRALGN;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}
if (addr_is_io(pa)) {
return (uint16) io_read(pa, 16);
}
if (addr_is_rom(pa)) {
m = ROM;
index = pa >> 2;
} else if (addr_is_mem(pa)) {
m = RAM;
index = (pa - PHYS_MEM_BASE) >> 2;
} else {
return 0;
}
if (pa & 2) {
return m[index] & HALF_MASK;
} else {
return (m[index] >> 16) & HALF_MASK;
}
}
/*
* Write Halfword (Physical Address)
*/
void pwrite_h(uint32 pa, uint16 val)
{
uint32 *m;
uint32 index;
uint32 wval = (uint32)val;
if (pa & 1) {
sim_debug(WRITE_MSG, &mmu_dev,
"[%08x] Cannot write physical address %08x, ALIGNMENT ISSUE\n",
R[NUM_PC], pa);
csr_data |= CSRALGN;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}
if (addr_is_io(pa)) {
io_write(pa, val, 16);
return;
}
if (addr_is_mem(pa)) {
m = RAM;
index = (pa - PHYS_MEM_BASE) >> 2;
} else {
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)
{
uint32 data;
int32 sc = (~(pa & 3) << 3) & 0x1f;
if (addr_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];
} else {
return 0;
}
return (data >> sc) & BYTE_MASK;
}
/*
* Write Byte (Physical Address)
*/
void pwrite_b(uint32 pa, uint8 val)
{
uint32 *m;
int32 index;
int32 sc = (~(pa & 3) << 3) & 0x1f;
uint32 mask = 0xffu << sc;
if (addr_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);
return;
}
}
/* Helper functions for MMU decode. */
/*
* Get the Segment Descriptor for a virtual address on a cache miss.
*
* Returns SCPE_OK on success, SCPE_NXM on failure.
*
* If SCPE_NXM is returned, a failure code and fault address will be
* set in the appropriate registers.
*
* As always, the flag 'fc' may be set to FALSE to avoid certain
* typses of fault checking.
*
*/
t_stat mmu_get_sd(uint32 va, uint8 r_acc, t_bool fc,
uint32 *sd0, uint32 *sd1)
{
/* We immediately do some bounds checking (fc flag is not checked
* because this is a fatal error) */
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);
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);
if (!SD_VALID(*sd0)) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] Invalid Segment Descriptor. va=%08x sd0=%08x\n",
R[NUM_PC], va, *sd0);
MMU_FAULT(MMU_F_INV_SD);
return SCPE_NXM;
}
/* TODO: Handle indirect lookups. */
if (SD_INDIRECT(*sd0)) {
stop_reason = STOP_MMU;
return SCPE_NXM;
}
/* If the segment descriptor isn't present, we need to
* fail out */
if (!SD_PRESENT(*sd0)) {
if (SD_CONTIG(*sd0)) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] Segment Not Present. va=%08x",
R[NUM_PC], 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);
MMU_FAULT(MMU_F_PDT_NOT_PRES);
return SCPE_NXM;
}
}
if (SHOULD_CACHE_SD(*sd0)) {
put_sdce(va, *sd0, *sd1);
}
return SCPE_OK;
}
/*
* Load a page descriptor from memory
*/
t_stat mmu_get_pd(uint32 va, uint8 r_acc, t_bool fc,
uint32 sd0, uint32 sd1,
uint32 *pd, uint8 *pd_acc)
{
uint32 pd_addr;
/* Where do we find the page descriptor? */
pd_addr = SD_SEG_ADDR(sd1) + (PSL(va) * 4);
/* Bounds checking on length */
if ((PSL(va) * 4) > MAX_OFFSET(sd0)) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] PDT Length Fault. "
"PDT Offset=%08x Max Offset=%08x va=%08x\n",
R[NUM_PC], (PSL(va) * 4),
MAX_OFFSET(sd0), va);
MMU_FAULT(MMU_F_PDTLEN);
return SCPE_NXM;
}
*pd = pread_w(pd_addr);
/* Copy the access flags from the SD */
*pd_acc = SD_ACC(sd0);
/* Cache it */
if (SHOULD_CACHE_PD(*pd)) {
put_pdce(va, sd0, *pd);
}
return SCPE_OK;
}
/*
* Decode an address from a contiguous segment.
*/
t_stat mmu_decode_contig(uint32 va, uint8 r_acc,
uint32 sd0, uint32 sd1,
t_bool fc, uint32 *pa)
{
if (fc) {
/* Verify permissions */
if (mmu_check_perm(SD_ACC(sd0), r_acc) != SCPE_OK) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] SEGMENT: NO ACCESS TO MEMORY AT %08x.\n"
"\t\tcpu_cm=%d acc_req=%x sd_acc=%02x\n",
R[NUM_PC], va, CPU_CM, r_acc, SD_ACC(sd0));
MMU_FAULT(MMU_F_ACC);
return SCPE_NXM;
}
}
/* Do max segment offset check outside any 'fc' checks because we
want this to fail even if fc is false. */
if (SOT(va) > MAX_OFFSET(sd0)) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] CONTIGUOUS: Segment Offset Fault. "
"sd0=%08x SOT=%08x len=%08x va=%08x\n",
R[NUM_PC], sd0, SOT(va),
(SD_MAX_OFF(sd0) * 8), va);
MMU_FAULT(MMU_F_SEG_OFFSET);
return SCPE_NXM;
}
/* TODO: It's possible to have BOTH a segment offset violation AND
an access violation. We need to cover that instance. */
if (fc) {
/* 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]);
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]);
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);
MMU_FAULT(MMU_F_OTRAP);
return SCPE_NXM;
}
}
*pa = SD_SEG_ADDR(sd1) + SOT(va);
return SCPE_OK;
}
t_stat mmu_decode_paged(uint32 va, uint8 r_acc, t_bool fc,
uint32 sd1, uint32 pd,
uint8 pd_acc, uint32 *pa)
{
if (fc && mmu_check_perm(pd_acc, r_acc) != SCPE_OK) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] PAGE: 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,
pd, R[NUM_PSW]);
MMU_FAULT(MMU_F_ACC);
return SCPE_NXM;
}
/* If the PD is not marked present, fail */
if (!PD_PRESENT(pd)) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] Page Not Present. "
"pd=%08x r_acc=%x va=%08x\n",
R[NUM_PC], pd, r_acc, va);
MMU_FAULT(MMU_F_PAGE_NOT_PRES);
return SCPE_NXM;
}
if (fc) {
/* If this is a write or interlocked read access, and
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);
MMU_FAULT(MMU_F_PW);
return SCPE_NXM;
}
/* 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]);
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]);
mmu_update_pd(va, PD_LOC(sd1, va), PD_R_MASK);
}
}
*pa = PD_ADDR(pd) + POT(va);
return SCPE_OK;
}
/*
* Translate a virtual address into a physical address.
*
* If "fc" is false, this function will bypass:
*
* - Access flag checks
* - Cache insertion
* - Setting MMU fault registers
* - Modifying segment and page descriptor bits
*/
t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa)
{
uint32 sd0, sd1, pd;
uint8 pd_acc;
t_stat sd_cached, pd_cached;
if (!mmu_enabled()) {
*pa = va;
return SCPE_OK;
}
/* We must check both caches first to determine what kind of miss
processing to do. */
sd_cached = get_sdce(va, &sd0, &sd1);
pd_cached = get_pdce(va, &pd, &pd_acc);
/* Now, potentially, do miss processing */
if (sd_cached != SCPE_OK && pd_cached != SCPE_OK) {
/* Full miss processing. We have to load both the SD and PD
* from main memory, and potentially cache them. */
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);
return SCPE_NXM;
}
if (!SD_CONTIG(sd0)) {
if (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);
return SCPE_NXM;
}
}
} else if (sd_cached == SCPE_OK && pd_cached != SCPE_OK && !SD_CONTIG(sd0)) {
/* Partial miss processing - SDC hit and PDC miss - but only
* if segment is paged. */
if (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);
return SCPE_NXM;
}
} else if (sd_cached != SCPE_OK && pd_cached == SCPE_OK) {
/* Partial miss processing - SDC miss and PDC hit. This is
* always paged translation */
/* First we must bring the SD into cache so that the SD
* R & M bits may be updated, if needed. */
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);
return SCPE_NXM;
}
/* If the 'L' bit is set in the page descriptor, we need to
* do some bounds checking */
if (PD_LAST(pd)) {
if ((PD_ADDR(pd) + POT(va)) > (SD_SEG_ADDR(sd1) + MAX_OFFSET(sd0))) {
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] PAGED: Segment Offset Fault.\n",
R[NUM_PC]);
MMU_FAULT(MMU_F_SEG_OFFSET);
return SCPE_NXM;
}
}
return mmu_decode_paged(va, r_acc, fc, sd1, pd, pd_acc, pa);
}
if (SD_CONTIG(sd0)) {
return mmu_decode_contig(va, r_acc, sd0, sd1, fc, pa);
} else {
return mmu_decode_paged(va, r_acc, fc, sd1, pd, pd_acc, pa);
}
}
t_stat examine(uint32 va, uint8 *val) {
uint32 pa;
t_stat succ;
succ = mmu_decode_va(va, 0, FALSE, &pa);
if (succ == SCPE_OK) {
if (addr_is_rom(pa) || addr_is_io(pa) || addr_is_mem(pa)) {
*val = pread_b(pa);
return SCPE_OK;
} else {
*val = 0;
return SCPE_NXM;
}
} else {
*val = 0;
return succ;
}
}
t_stat deposit(uint32 va, uint8 val) {
uint32 pa;
t_stat succ;
succ = mmu_decode_va(va, 0, FALSE, &pa);
if (succ == SCPE_OK) {
if (addr_is_mem(pa) || addr_is_io(pa)) {
pwrite_b(pa, val);
return SCPE_OK;
} else {
return SCPE_NXM;
}
} else {
return succ;
}
}
t_stat read_operand(uint32 va, uint8 *val) {
uint32 pa;
t_stat succ;
succ = mmu_decode_va(va, ACC_OF, TRUE, &pa);
if (succ == SCPE_OK) {
*val = pread_b(pa);
} else {
*val = 0;
}
return succ;
}
uint32 mmu_xlate_addr(uint32 va, uint8 r_acc)
{
uint32 pa;
t_stat succ;
succ = mmu_decode_va(va, r_acc, TRUE, &pa);
if (succ == SCPE_OK) {
mmu_state.var = va;
return pa;
} else {
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
}
SIM_INLINE t_bool mmu_enabled()
{
return mmu_state.enabled;
}
void mmu_enable()
{
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] Enabling MMU.\n",
R[NUM_PC]);
mmu_state.enabled = TRUE;
}
void mmu_disable()
{
sim_debug(EXECUTE_MSG, &mmu_dev,
"[%08x] Disabling MMU.\n",
R[NUM_PC]);
mmu_state.enabled = FALSE;
}
/*
* MMU Virtual Read and Write Functions
*/
uint8 read_b(uint32 va, uint8 r_acc)
{
return pread_b(mmu_xlate_addr(va, r_acc));
}
uint16 read_h(uint32 va, uint8 r_acc)
{
return pread_h(mmu_xlate_addr(va, r_acc));
}
uint32 read_w(uint32 va, uint8 r_acc)
{
return pread_w(mmu_xlate_addr(va, r_acc));
}
void write_b(uint32 va, uint8 val)
{
pwrite_b(mmu_xlate_addr(va, ACC_W), val);
}
void write_h(uint32 va, uint16 val)
{
pwrite_h(mmu_xlate_addr(va, ACC_W), val);
}
void write_w(uint32 va, uint32 val)
{
pwrite_w(mmu_xlate_addr(va, ACC_W), val);
}

612
3B2/3b2_mmu.h Normal file
View file

@ -0,0 +1,612 @@
/* 3b2_mmu.c: AT&T 3B2 Model 400 MMU (WE32101) Header
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_MMU_H
#define _3B2_MMU_H
#include "3b2_defs.h"
/************************************************************************
*
* Vocabulary
* ----------
*
* PD: Page Descriptor (in main memory)
* PDT: Page Descriptor Table (in main memory)
* POT: Page Offset. Bits 0-10 of a Paged virtual address.
* PSL: Page Select. Bits 11-16 of a Paged virtual address.
* SD: Segment Descriptor (in main memory)
* SDT: Segment Descriptor Table (in main memory)
* SID: Section ID. Bits 30-31 of all virtual addresses
* SOT: Segment Offset. Bits 0-16 of a Contiguous virtual address.
* SSL: Segment Select. Bits 17-29 of all virtual addresses.
*
*
* The WE32101 MMU divides the virtual address space into four
* Sections with 8K Segments per section. Virtual address bits 30 and
* 31 determine the section, bits 17-29 determine the Segment within
* the section.
*
* There are two kinds of address translation: Contiguous Translation
* and Paged Translation. Contiguous Translation just uses an offset
* (bits 0-16 of the virtual address) into each Segment to find an
* address, allowing for 128K bytes per Segment. Paged translation
* further break Segments down into 64 Pages of 2K each.
*
* Details about how to do translation are held in main memory in
* Segment Descriptors and Page Descriptors. These are located in
* Segment Descriptor Tables and Page Descriptor Tables set up by the
* computer before enabling the MMU.
*
* In addition to details in main memory, the MMU has a small cache
* of both Segment Descriptors and Page Descriptors. This is NOT just
* used for performance reasons! Various features of the cache,
* such as updating R and M bits in Segment and Page Descriptors,
* are used by various operating system features.
*
*
* Virtual Address Fields
* ----------------------
*
* 31 30 29 17 16 0
* +-----+-------------------+-----------------------------+
* Contig: | SID | SSL | SOT |
* +-----+-------------------+-----------------------------+
*
* 31 30 29 17 16 11 10 0
* +-----+-------------------+---------+-------------------+
* Paged: | SID | SSL | PSL | POT |
* +-----+-------------------+---------+-------------------+
*
*
* Segment Descriptor Fields
* -------------------------
*
* 31 24 23 10 9 8 7 6 5 4 3 2 1 0
* +-------+---------+-----+---+---+---+---+---+---+---+---+
* sd0: | Acc | Max Off | Res | I | V | R | T | $ | C | M | P |
* +-------+---------+-----+---+---+---+---+---+---+---+---+
*
* +-----------------------------------------------+-------+
* sd1: | Address (high-order 27 or 29 bits) | Soft |
* +-----------------------------------------------+-------+
*
*
* Segment Descriptor Cache Entry
* ------------------------------
*
* 31 24 23 10 9 0
* +-------+-------------------------+---------------------+
* Low: | Acc | Max Off | Tag |
* +-------+-------------------------+---------------------+
*
* 31 5 4 3 2 1 0
* +-----------------------------------+---+---+---+---+---+
* High: | Address | T | $ | C | M | G |
* +-----------------------------------+---+---+---+---+---+
*
*
* Page Descriptor Fields
* ----------------------
*
* 31 11 10 8 7 6 5 4 3 2 1 0
* +----------------+------+-----+---+---+-----+---+---+---+
* | Page Address | Soft | Res | R | W | Res | L | M | P |
* +----------------+------+-----+---+---+-----+---+---+---+
*
*
* Page Descriptor Cache Entry
* ---------------------------
*
* 31 24 23 16 15 0
* +-----+------------------+------------------------------+
* Low: | Acc | Res | Tag |
* +-----+------------------+------------------------------+
*
* 31 11 10 7 6 5 4 3 2 1 0
* +---------------------+-----+---+---+---+---+---+---+---+
* High: | Address | Res | U | R | W | $ | L | M | G |
* +---------------------+-----+---+---+---+---+---+---+---+
*
* "U" is only set in the left cache entry, and indicates
* which slot (left or right) was most recently updated.
*
***********************************************************************/
#define MMUBASE 0x40000
#define MMUSIZE 0x1000
#define MMU_SRS 0x04 /* Section RAM array size (words) */
#define MMU_SDCS 0x20 /* Segment Descriptor Cache H/L array size
(words) */
#define MMU_PDCS 0x20 /* Page Descriptor Cache H/L array size
(words) */
/* Register address offsets */
#define MMU_SDCL 0
#define MMU_SDCH 1
#define MMU_PDCRL 2
#define MMU_PDCRH 3
#define MMU_PDCLL 4
#define MMU_PDCLH 5
#define MMU_SRAMA 6
#define MMU_SRAMB 7
#define MMU_FC 8
#define MMU_FA 9
#define MMU_CONF 10
#define MMU_VAR 11
#define MMU_CONF_M (mmu_state.conf & 0x1)
#define MMU_CONF_R (mmu_state.conf & 0x2)
/* Caching */
#define NUM_SEC 4u /* Number of memory sections */
#define NUM_SDCE 8 /* SD cache entries per section */
#define NUM_PDCE 8 /* PD cache entries per section per side (l/r) */
#define SET_SIZE 2 /* PDs are held in a 2-way associative set */
/* Cache Tag for SDs */
#define SD_TAG(vaddr) ((vaddr >> 20) & 0x3ff)
/* Cache Tag for PDs */
#define PD_TAG(vaddr) (((vaddr >> 13) & 0xf) | ((vaddr >> 14) & 0xfff0))
/* Index of entry in the SD cache */
#define SD_IDX(vaddr) ((vaddr >> 17) & 7)
/* Index of entry in the PD cache */
#define PD_IDX(vaddr) (((vaddr >> 11) & 3) | ((vaddr >> 15) & 4))
/* Shift and mask the flag bits for the current CPU mode */
#define MMU_PERM(f) ((f >> ((3 - CPU_CM) * 2)) & 3)
#define ROM_SIZE 0x10000
#define BOOT_CODE_SIZE 0x8000
/* Codes set in the MMU Fault register */
#define MMU_F_SDTLEN 0x03
#define MMU_F_PW 0x04
#define MMU_F_PDTLEN 0x05
#define MMU_F_INV_SD 0x06
#define MMU_F_SEG_NOT_PRES 0x07
#define MMU_F_OTRAP 0x08
#define MMU_F_PDT_NOT_PRES 0x09
#define MMU_F_PAGE_NOT_PRES 0x0a
#define MMU_F_ACC 0x0d
#define MMU_F_SEG_OFFSET 0x0e
/* Pluck out Virtual Address fields */
#define SID(va) (((va) >> 30) & 3)
#define SSL(va) (((va) >> 17) & 0x1fff)
#define SOT(va) (va & 0x1ffff)
#define PSL(va) (((va) >> 11) & 0x3f)
#define POT(va) (va & 0x7ff)
/* Get the maximum length of an SSL from SRAMB */
#define SRAMB_LEN(va) (mmu_state.sec[SID(va)].len + 1)
/* Pluck out Segment Descriptor fields */
#define SD_PRESENT(sd0) ((sd0) & 1)
#define SD_MODIFIED(sd0) (((sd0) >> 1) & 1)
#define SD_CONTIG(sd0) (((sd0) >> 2) & 1)
#define SD_CACHE(sd0) (((sd0) >> 3) & 1)
#define SD_TRAP(sd0) (((sd0) >> 4) & 1)
#define SD_REF(sd0) (((sd0) >> 5) & 1)
#define SD_VALID(sd0) (((sd0) >> 6) & 1)
#define SD_INDIRECT(sd0) (((sd0) >> 7) & 1)
#define SD_SEG_ADDR(sd1) ((sd1) & 0xffffffe0)
#define SD_MAX_OFF(sd0) (((sd0) >> 10) & 0x3fff)
#define SD_ACC(sd0) (((sd0) >> 24) & 0xff)
#define SD_R_MASK 0x20
#define SD_M_MASK 0x2
#define SD_GOOD_MASK 0x1u
#define SDCE_TAG(sdcl) ((sdcl) & 0x3ff)
#define SD_ADDR(va) (mmu_state.sec[SID(va)].addr + (SSL(va) * 8))
/* Convert from sd to sd cache entry */
#define SD_TO_SDCL(va,sd0) ((sd0 & 0xfffffc00)|SD_TAG(va))
#define SD_TO_SDCH(sd0,sd1) (SD_SEG_ADDR(sd1)|(sd0 & 0x1e)|1)
/* Note that this is a lossy transform. We will lose the state of the
I and R flags, as well as the software flags. We don't need them.
The V and P flags can be inferred as set. */
#define SDCE_TO_SD0(sdch,sdcl) ((sdcl & 0xfffffc00)|0x40|(sdch & 0x1e)|1)
#define SDCE_TO_SD1(sdch) (sdch & 0xffffffe0)
/* Maximum size (in bytes) of a segment */
#define MAX_OFFSET(sd0) ((SD_MAX_OFF(sd0) + 1) << 3)
#define PD_PRESENT(pd) (pd & 1)
#define PD_MODIFIED(pd) ((pd >> 1) & 1)
#define PD_LAST(pd) ((pd >> 2) & 1)
#define PD_WFAULT(pd) ((pd >> 4) & 1)
#define PD_REF(pd) ((pd >> 5) & 1)
#define PD_ADDR(pd) (pd & 0xfffff800) /* Address portion of PD */
#define PD_USED_MASK 0x40
#define PD_R_MASK 0x20
#define PD_M_MASK 0x2
#define PD_GOOD_MASK 0x1u
#define PDCXL_TAG(pdcxl) (pdcxl & 0xffff)
#define PD_LOC(sd1,va) SD_SEG_ADDR(sd1) + (PSL(va) * 4)
/* Page Descriptor Cache Entry
*
*/
/* Convert from pd to pd cache entry. Alwasy sets "Good" bit. */
#define SD_TO_PDCXL(va,sd0) ((sd0 & 0xff000000)|PD_TAG(va))
#define PD_TO_PDCXH(pd,sd0) ((pd & 0xfffff836)|(sd0 & 0x8)|1)
/* Always set 'present' to true on conversion */
#define PDCXH_TO_PD(pdch) ((pdch & 0xfffff836)|1)
#define PDCXL_TO_ACC(pdcl) (((pdcl & 0xff000000) >> 24) & 0xff)
#define PDCLH_USED_MASK 0x40u
/* Fault codes */
#define MMU_FAULT(f) { \
if (fc) { \
mmu_state.fcode = ((((uint32)r_acc)<<7)|(((uint32)(CPU_CM))<<5)|f); \
mmu_state.faddr = va; \
} \
}
typedef struct _mmu_sec {
uint32 addr;
uint32 len;
} mmu_sec;
typedef struct _mmu_state {
t_bool enabled; /* Global enabled/disabled flag */
uint32 sdcl[MMU_SDCS]; /* SDC low bits (0-31) */
uint32 sdch[MMU_SDCS]; /* SDC high bits (32-63) */
uint32 pdcll[MMU_PDCS]; /* PDC low bits (left) (0-31) */
uint32 pdclh[MMU_PDCS]; /* PDC high bits (left) (32-63) */
uint32 pdcrl[MMU_PDCS]; /* PDC low bits (right) (0-31) */
uint32 pdcrh[MMU_PDCS]; /* PDC high bits (right) (32-63) */
uint32 sra[MMU_SRS]; /* Section RAM A */
uint32 srb[MMU_SRS]; /* Section RAM B */
mmu_sec sec[MMU_SRS]; /* Section descriptors decoded from
Section RAM A and B */
uint32 fcode; /* Fault Code Register */
uint32 faddr; /* Fault Address Register */
uint32 conf; /* Configuration Register */
uint32 var; /* Virtual Address Register */
} MMU_STATE;
extern MMU_STATE mmu_state;
extern volatile int32 stop_reason;
extern DEVICE mmu_dev;
t_stat mmu_init(DEVICE *dptr);
uint32 mmu_read(uint32 pa, size_t size);
void mmu_write(uint32 pa, uint32 val, size_t size);
/* 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);
/* Virtual memory translation */
uint32 mmu_xlate_addr(uint32 va, uint8 r_acc);
t_stat mmu_decode_vaddr(uint32 vaddr, uint8 r_acc,
t_bool fc, uint32 *pa);
#define SHOULD_CACHE_PD(pd) \
(fc && PD_PRESENT(pd))
#define SHOULD_CACHE_SD(sd) \
(fc && SD_VALID(sd) && SD_PRESENT(sd))
#define SHOULD_UPDATE_SD_R_BIT(sd) \
(MMU_CONF_R && !((sd) & SD_R_MASK))
#define SHOULD_UPDATE_SD_M_BIT(sd) \
(MMU_CONF_M && r_acc == ACC_W && !((sd) & SD_M_MASK))
#define SHOULD_UPDATE_PD_R_BIT(pd) \
(!((pd) & PD_R_MASK))
#define SHOULD_UPDATE_PD_M_BIT(pd) \
(r_acc == ACC_W && !((pd) & PD_M_MASK))
/*
* Find an SD in the cache.
*/
static SIM_INLINE t_stat get_sdce(uint32 va, uint32 *sd0, uint32 *sd1)
{
uint32 tag, sdch, sdcl;
uint8 ci;
ci = (SID(va) * NUM_SDCE) + SD_IDX(va);
tag = SD_TAG(va);
sdch = mmu_state.sdch[ci];
sdcl = mmu_state.sdcl[ci];
if ((sdch & SD_GOOD_MASK) && SDCE_TAG(sdcl) == tag) {
*sd0 = SDCE_TO_SD0(sdch, sdcl);
*sd1 = SDCE_TO_SD1(sdch);
return SCPE_OK;
}
return SCPE_NXM;
}
/*
* Find a PD in the cache. Sets both the PD and the cached access
* permissions.
*/
static SIM_INLINE t_stat get_pdce(uint32 va, uint32 *pd, uint8 *pd_acc)
{
uint32 tag, pdcll, pdclh, pdcrl, pdcrh;
uint8 ci;
ci = (SID(va) * NUM_PDCE) + PD_IDX(va);
tag = PD_TAG(va);
/* Left side */
pdcll = mmu_state.pdcll[ci];
pdclh = mmu_state.pdclh[ci];
/* Right side */
pdcrl = mmu_state.pdcrl[ci];
pdcrh = mmu_state.pdcrh[ci];
/* Search L and R to find a good entry with a matching tag. */
if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) {
*pd = PDCXH_TO_PD(pdclh);
*pd_acc = PDCXL_TO_ACC(pdcll);
return SCPE_OK;
} else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) {
*pd = PDCXH_TO_PD(pdcrh);
*pd_acc = PDCXL_TO_ACC(pdcrl);
return SCPE_OK;
}
return SCPE_NXM;
}
static SIM_INLINE void put_sdce(uint32 va, uint32 sd0, uint32 sd1)
{
uint32 tag;
uint8 ci;
tag = SD_TAG(va);
ci = (SID(va) * NUM_SDCE) + SD_IDX(va);
mmu_state.sdcl[ci] = SD_TO_SDCL(va, sd0);
mmu_state.sdch[ci] = SD_TO_SDCH(sd0, sd1);
}
static SIM_INLINE void put_pdce(uint32 va, uint32 sd0, uint32 pd)
{
uint32 tag, pdclh, pdcrh;
uint8 ci;
tag = PD_TAG(va);
ci = (SID(va) * NUM_PDCE) + PD_IDX(va);
/* Left side contains the 'U' bit we care about */
pdclh = mmu_state.pdclh[ci];
/* Right side */
pdcrh = mmu_state.pdcrh[ci];
/* Pick the least-recently-replaced side */
if (pdclh & PDCLH_USED_MASK) { /* Right side replaced more recently */
/* Add to left side of cache without the U bit set */
mmu_state.pdcll[ci] = SD_TO_PDCXL(va, sd0);
mmu_state.pdclh[ci] = PD_TO_PDCXH(pd, sd0);
mmu_state.pdcll[ci] &= ~PDCLH_USED_MASK;
} else { /* Left side replaced more recently */
/* Add to right side of cache and set the U bit on the left side */
mmu_state.pdcrl[ci] = SD_TO_PDCXL(va, sd0);
mmu_state.pdcrh[ci] = PD_TO_PDCXH(pd, sd0);
mmu_state.pdcll[ci] |= PDCLH_USED_MASK;
}
}
static SIM_INLINE void flush_sdce(uint32 va)
{
uint32 tag;
uint8 ci;
ci = (SID(va) * NUM_SDCE) + SD_IDX(va);
tag = SD_TAG(va);
if (mmu_state.sdch[ci] & SD_GOOD_MASK) {
mmu_state.sdch[ci] &= ~SD_GOOD_MASK;
}
}
static SIM_INLINE void flush_pdce(uint32 va)
{
uint32 tag, pdcll, pdclh, pdcrl, pdcrh;
uint8 ci;
ci = (SID(va) * NUM_PDCE) + PD_IDX(va);
tag = PD_TAG(va);
/* Left side */
pdcll = mmu_state.pdcll[ci];
pdclh = mmu_state.pdclh[ci];
/* Right side */
pdcrl = mmu_state.pdcrl[ci];
pdcrh = mmu_state.pdcrh[ci];
/* Search L and R to find a good entry with a matching tag. */
if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) {
mmu_state.pdclh[ci] &= ~PD_GOOD_MASK;
} else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) {
mmu_state.pdcrh[ci] &= ~PD_GOOD_MASK;
}
}
static SIM_INLINE void flush_cache_sec(uint8 sec)
{
int i;
for (i = 0; i < MMU_SDCS; i++) {
mmu_state.sdch[(sec * NUM_SDCE) + i] &= ~SD_GOOD_MASK;
}
for (i = 0; i < MMU_PDCS; i++) {
mmu_state.pdclh[(sec * NUM_PDCE) + i] |= PD_USED_MASK;
mmu_state.pdclh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK;
mmu_state.pdcrh[(sec * NUM_PDCE) + i] &= ~PD_GOOD_MASK;
}
}
static SIM_INLINE void flush_caches()
{
uint8 i;
for (i = 0; i < NUM_SEC; i++) {
flush_cache_sec(i);
}
}
static SIM_INLINE t_stat mmu_check_perm(uint8 flags, uint8 r_acc)
{
switch(MMU_PERM(flags)) {
case 0: /* No Access */
return SCPE_NXM;
case 1: /* Exec Only */
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 &&
r_acc != ACC_MT) {
return SCPE_NXM;
}
return SCPE_OK;
default:
return SCPE_OK;
}
}
/*
* Update the M (modified) or R (referenced) bit the SD and cache
*/
static SIM_INLINE void mmu_update_sd(uint32 va, uint32 mask)
{
uint32 sd0, tag;
uint8 ci;
tag = SD_TAG(va);
ci = (SID(va) * NUM_SDCE) + SD_IDX(va);
/* 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);
/* There is no 'R' bit in the SD cache, only an 'M' bit. */
if (mask == SD_M_MASK) {
mmu_state.sdch[ci] |= mask;
}
}
/*
* Update the M (modified) or R (referenced) bit the PD and cache
*/
static SIM_INLINE void mmu_update_pd(uint32 va, uint32 pd_addr, uint32 mask)
{
uint32 pd, tag, pdcll, pdclh, pdcrl, pdcrh;
uint8 ci;
tag = PD_TAG(va);
ci = (SID(va) * NUM_PDCE) + PD_IDX(va);
/* 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);
/* Update in the cache */
/* Left side */
pdcll = mmu_state.pdcll[ci];
pdclh = mmu_state.pdclh[ci];
/* Right side */
pdcrl = mmu_state.pdcrl[ci];
pdcrh = mmu_state.pdcrh[ci];
/* Search L and R to find a good entry with a matching tag, then
update the appropriate bit */
if ((pdclh & PD_GOOD_MASK) && PDCXL_TAG(pdcll) == tag) {
mmu_state.pdclh[ci] |= mask;
} else if ((pdcrh & PD_GOOD_MASK) && PDCXL_TAG(pdcrl) == tag) {
mmu_state.pdcrh[ci] |= mask;
}
}
/* Special functions for reading operands and examining memory
safely */
t_stat read_operand(uint32 va, uint8 *val);
t_stat examine(uint32 va, uint8 *val);
t_stat deposit(uint32 va, uint8 val);
/* 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);
#endif

180
3B2/3b2_sys.c Normal file
View file

@ -0,0 +1,180 @@
/* 3b2_defs.h: AT&T 3B2 Model 400 system-specific logic implementation
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.
*/
#include "3b2_sys.h"
#include "3b2_cpu.h"
#include "3b2_iu.h"
#include "3b2_if.h"
#include "3b2_id.h"
#include "3b2_mmu.h"
#include "3b2_sysdev.h"
char sim_name[] = "AT&T 3B2 Model 400";
REG *sim_PC = &cpu_reg[0];
/* All opcodes are 1 or 2 bytes. Operands may be up to 6 bytes, and
there may be up to 3 operands, for a maximum of 20 bytes */
int32 sim_emax = 20;
extern instr cpu_instr;
DEVICE *sim_devices[] = {
&cpu_dev,
&mmu_dev,
&timer_dev,
&tod_dev,
&nvram_dev,
&csr_dev,
&iu_dev,
&dmac_dev,
&if_dev,
&id_dev,
NULL
};
const char *sim_stop_messages[] = {
"Unknown error",
"Reserved Instruction",
"Breakpoint",
"Invalid Opcode",
"IRQ",
"Exception/Trap",
"Exception Stack Too Deep",
"Unimplemented MMU Feature"
};
t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag)
{
int32 i;
uint32 addr = 0;
int32 cnt = 0;
if ((*cptr != 0) || (flag != 0)) {
return SCPE_ARG;
}
addr = R[NUM_PC];
while ((i = getc (fileref)) != EOF) {
pwrite_b(addr, (uint8)i);
addr++;
cnt++;
}
printf ("%d Bytes loaded.\n", cnt);
return SCPE_OK;
}
t_stat parse_sym(CONST char *cptr, t_addr exta, UNIT *uptr, t_value *val, int32 sw)
{
DEVICE *dptr;
t_stat r;
int32 k, num, vp;
int32 len = 4;
if (sw & (int32) SWMASK ('B')) {
len = 1;
} else if (sw & (int32) SWMASK ('H')) {
len = 2;
} else if (sw & (int32) SWMASK ('W')) {
len = 4;
}
// Parse cptr
num = (int32) get_uint(cptr, 16, WORD_MASK, &r);
if (r != SCPE_OK) {
return r;
}
if (uptr == NULL) {
uptr = &cpu_unit;
}
dptr = find_dev_from_unit(uptr);
if (dptr == NULL) {
return SCPE_IERR;
}
vp = 0;
for (k = len - 1; k >= 0; k--) {
val[vp++] = (num >> (k * 8)) & 0xff;
}
return -(vp - 1);
}
t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw)
{
uint32 len = 4;
int32 k, vp, num;
unsigned int c;
num = 0;
vp = 0;
if (sw & (int32) SWMASK ('B')) {
len = 1;
} else if (sw & (int32) SWMASK ('H')) {
len = 2;
} else if (sw & (int32) SWMASK ('W')) {
len = 4;
}
if (sw & (int32) SWMASK('M')) {
fprint_sym_m(of, &cpu_instr);
return SCPE_OK;
}
if (sw & (int32) SWMASK('C')) {
len = 16;
for (k = (int32) len - 1; k >= 0; k--) {
c = (unsigned int)val[vp++];
if (c >= 0x20 && c < 0x7f) {
fprintf(of, "%c", c);
} else {
fprintf(of, ".");
}
}
return -(vp - 1);
}
for (k = len - 1; k >= 0; k--) {
num = num | (((int32) val[vp++]) << (k * 8));
}
fprint_val(of, (uint32) num, 16, len * 8, PV_RZRO);
return -(vp - 1);
}

47
3B2/3b2_sys.h Normal file
View file

@ -0,0 +1,47 @@
/* 3b2_sys.h: AT&T 3B2 Model 400 system-specific logic headers
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_SYS_H
#define _3B2_SYS_H
#include "3b2_defs.h"
extern uint32 R[16];
extern char sim_name[];
extern REG *sim_PC;
extern int32 sim_emax;
extern DEVICE *sim_devices[];
t_stat sim_load (FILE *fileref, CONST char *cptr, CONST char *fnam, int flag);
t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val,
int32 sw);
t_stat fprint_sym (FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw);
#endif

711
3B2/3b2_sysdev.c Normal file
View file

@ -0,0 +1,711 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 System Devices implementation
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.
*/
/*
This file contains system-specific registers and devices for the
following 3B2 devices:
- timer 8253 interval timer
- nvram Non-Volatile RAM
- csr Control Status Registers
- tod MM58174A Real-Time-Clock
*/
#include "3b2_sysdev.h"
#include "3b2_iu.h"
DEBTAB sys_deb_tab[] = {
{ "INIT", INIT_MSG, "Init" },
{ "READ", READ_MSG, "Read activity" },
{ "WRITE", WRITE_MSG, "Write activity" },
{ "EXECUTE", EXECUTE_MSG, "Execute activity" },
{ "IRQ", IRQ_MSG, "Interrupt activity"},
{ "TRACE", TRACE_MSG, "Detailed activity" },
{ NULL, 0 }
};
uint32 *NVRAM = NULL;
extern DEVICE cpu_dev;
/* CSR */
uint16 csr_data;
BITFIELD csr_bits[] = {
BIT(IOF),
BIT(DMA),
BIT(DISK),
BIT(UART),
BIT(PIR9),
BIT(PIR8),
BIT(CLK),
BIT(IFLT),
BIT(ITIM),
BIT(FLOP),
BIT(NA),
BIT(LED),
BIT(ALGN),
BIT(RRST),
BIT(PARE),
BIT(TIMO),
ENDBITS
};
UNIT csr_unit = {
UDATA(&csr_svc, UNIT_FIX, CSRSIZE)
};
REG csr_reg[] = {
{ HRDATADF(DATA, csr_data, 16, "CSR Data", csr_bits) }
};
DEVICE csr_dev = {
"CSR", &csr_unit, csr_reg, NULL,
1, 16, 8, 4, 16, 32,
&csr_ex, &csr_dep, &csr_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
t_stat csr_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw)
{
return SCPE_OK;
}
t_stat csr_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw)
{
return SCPE_OK;
}
t_stat csr_reset(DEVICE *dptr)
{
csr_data = 0;
return SCPE_OK;
}
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);
switch (reg) {
case 0x2:
if (size == 8) {
return (csr_data >> 8) & 0xff;
} else {
return csr_data;
}
case 0x3:
return csr_data & 0xff;
default:
return 0;
}
}
/* TODO: Remove once we confirm we don't need it */
t_stat csr_svc(UNIT *uptr)
{
return SCPE_OK;
}
void csr_write(uint32 pa, uint32 val, size_t size)
{
uint32 reg = pa - CSRBASE;
switch (reg) {
case 0x03: /* Clear Bus Timeout Error */
csr_data &= ~CSRTIMO;
break;
case 0x07: /* Clear Memory Parity Error */
csr_data &= ~CSRPARE;
break;
case 0x0b: /* Set System Reset Request */
iu_reset(&iu_dev);
cpu_reset(&cpu_dev);
cpu_boot(0, &cpu_dev);
break;
case 0x0f: /* Clear Memory Alignment Fault */
csr_data &= ~CSRALGN;
break;
case 0x13: /* Set Failure LED */
csr_data |= CSRLED;
break;
case 0x17: /* Clear Failure LED */
csr_data &= ~CSRLED;
break;
case 0x1b: /* Set Floppy Motor On */
csr_data |= CSRFLOP;
break;
case 0x1f: /* Clear Floppy Motor On */
csr_data &= ~CSRFLOP;
break;
case 0x23: /* Set Inhibit Timers */
csr_data |= CSRITIM;
break;
case 0x27: /* Clear Inhibit Timers */
csr_data &= ~CSRITIM;
break;
case 0x2b: /* Set Inhibit Faults */
csr_data |= CSRIFLT;
break;
case 0x2f: /* Clear Inhibit Faults */
csr_data &= ~CSRIFLT;
break;
case 0x33: /* Set PIR9 */
csr_data |= CSRPIR9;
break;
case 0x37: /* Clear PIR9 */
csr_data &= ~CSRPIR9;
break;
case 0x3b: /* Set PIR8 */
csr_data |= CSRPIR8;
break;
case 0x3f: /* Clear PIR8 */
csr_data &= ~CSRPIR8;
break;
default:
break;
}
}
/* NVRAM */
UNIT nvram_unit = {
UDATA(NULL, UNIT_FIX+UNIT_BINK, NVRAMSIZE)
};
REG nvram_reg[] = {
{ NULL }
};
DEVICE nvram_dev = {
"NVRAM", &nvram_unit, nvram_reg, NULL,
1, 16, 8, 4, 16, 32,
&nvram_ex, &nvram_dep, &nvram_reset,
NULL, &nvram_attach, &nvram_detach,
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
NULL, NULL, NULL,
&nvram_description
};
t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;
if ((vptr == NULL) || (addr & 03)) {
return SCPE_ARG;
}
if (addr >= NVRAMSIZE) {
return SCPE_NXM;
}
*vptr = NVRAM[addr >> 2];
return SCPE_OK;
}
t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw)
{
uint32 addr = (uint32) exta;
if (addr & 03) {
return SCPE_ARG;
}
if (addr >= NVRAMSIZE) {
return SCPE_NXM;
}
NVRAM[addr >> 2] = (uint32) val;
return SCPE_OK;
}
t_stat nvram_reset(DEVICE *dptr)
{
if (NVRAM == NULL) {
NVRAM = (uint32 *)calloc(NVRAMSIZE >> 2, sizeof(uint32));
memset(NVRAM, 0, sizeof(uint32) * NVRAMSIZE >> 2);
nvram_unit.filebuf = NVRAM;
}
if (NVRAM == NULL) {
return SCPE_MEM;
}
return SCPE_OK;
}
const char *nvram_description(DEVICE *dptr)
{
return "Non-volatile memory";
}
t_stat nvram_attach(UNIT *uptr, CONST char *cptr)
{
t_stat r;
/* If we've been asked to attach, make sure the ATTABLE
and BUFABLE flags are set on the unit */
uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE);
r = attach_unit(uptr, cptr);
if (r != SCPE_OK) {
/* Unset the ATTABLE and BUFABLE flags if we failed. */
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
} else {
uptr->hwmark = (uint32) uptr->capac;
}
return r;
}
t_stat nvram_detach(UNIT *uptr)
{
t_stat r;
r = detach_unit(uptr);
if ((uptr->flags & UNIT_ATT) == 0) {
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
}
return r;
}
uint32 nvram_read(uint32 pa, size_t size)
{
uint32 offset = pa - NVRAMBASE;
uint32 data;
uint32 sc = (~(offset & 3) << 3) & 0x1f;
switch(size) {
case 8:
data = (NVRAM[offset >> 2] >> sc) & BYTE_MASK;
break;
case 16:
if (offset & 2) {
data = NVRAM[offset >> 2] & HALF_MASK;
} else {
data = (NVRAM[offset >> 2] >> 16) & HALF_MASK;
}
break;
case 32:
data = NVRAM[offset >> 2];
break;
}
return data;
}
void nvram_write(uint32 pa, uint32 val, size_t size)
{
uint32 offset = pa - NVRAMBASE;
uint32 index = offset >> 2;
uint32 sc, mask;
switch(size) {
case 8:
sc = (~(pa & 3) << 3) & 0x1f;
mask = (uint32) (0xff << sc);
NVRAM[index] = (NVRAM[index] & ~mask) | (val << sc);
break;
case 16:
if (offset & 2) {
NVRAM[index] = (NVRAM[index] & ~HALF_MASK) | val;
} else {
NVRAM[index] = (NVRAM[index] & HALF_MASK) | (val << 16);
}
break;
case 32:
NVRAM[index] = val;
break;
}
}
/*
* 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.
*
*/
struct timer_ctr TIMERS[3];
/*
* 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 }
};
REG timer_reg[] = {
{ HRDATAD(DIVA, TIMERS[0].divider, 16, "Divider A") },
{ HRDATAD(STA, TIMERS[0].mode, 16, "Mode A") },
{ HRDATAD(DIVB, TIMERS[1].divider, 16, "Divider B") },
{ HRDATAD(STB, TIMERS[1].mode, 16, "Mode B") },
{ HRDATAD(DIVC, TIMERS[2].divider, 16, "Divider C") },
{ HRDATAD(STC, TIMERS[2].mode, 16, "Mode C") },
{ NULL }
};
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
};
#define TIMER_STP_US 10 /* 10 us delay per timer step */
#define tmrnum u3
#define tmr up7
#define DECR_STEPS 400
/*
* This is a hack to make diagnostics pass. If read immediately after
* being set, a counter should always return the initial value. If a
* certain number of steps have passed, it should have decremented a
* little bit, so we return a value one less than the initial value.
* This is not 100% accurate, but it makes SVR3 and DGMON tests happy.
*/
static SIM_INLINE uint16 timer_current_val(struct timer_ctr *ctr)
{
if ((sim_gtime() - ctr->stime) > DECR_STEPS) {
return ctr->divider - 1;
} else {
return ctr->divider;
}
}
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 = &TIMERS[i];
}
/* Timer 1 gate is always active */
TIMERS[1].gate = 1;
if (!sim_is_running) {
t = sim_rtcn_init_unit(&timer_unit[1], TPS_CLK, TMR_CLK);
sim_activate_after(&timer_unit[1], 1000000 / t);
}
return SCPE_OK;
}
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_abs(uptr, (int32) DELAY_US(time));
return SCPE_OK;
}
t_stat timer1_svc(UNIT *uptr)
{
struct timer_ctr *ctr;
int32 t, ticks;
ctr = (struct timer_ctr *)uptr->tmr;
if (ctr->enabled && !(csr_data & CSRITIM)) {
/* Fire the IPL 15 clock interrupt */
csr_data |= CSRCLK;
}
ticks = ctr->divider / TIMER_STP_US;
if (ticks == 0) {
ticks = TPS_CLK;
}
t = sim_rtcn_calb(ticks, TMR_CLK);
sim_activate_after(uptr, (uint32) (1000000 / ticks));
return SCPE_OK;
}
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_abs(uptr, (int32) DELAY_US(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:
if (ctr->enabled && ctr->gate) {
ctr_val = timer_current_val(ctr);
} else {
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 */
csr_data &= ~CSRCLK;
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->enabled = TRUE;
ctr->stime = sim_gtime();
break;
case 0x20:
ctr->divider &= 0x00ff;
ctr->divider |= (val & 0xff) << 8;
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
break;
case 0x30:
if (ctr->lmb) {
ctr->lmb = FALSE;
ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8));
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
} else {
ctr->lmb = TRUE;
ctr->divider = (ctr->divider & 0xff00) | (val & 0xff);
}
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;
}
}
/*
* MM58174A Real-Time-Clock
*/
UNIT tod_unit = { UDATA(&tod_svc, UNIT_IDLE+UNIT_FIX, 0) };
uint32 tod_reg = 0;
DEVICE tod_dev = {
"TOD", &tod_unit, NULL, NULL,
1, 16, 8, 4, 16, 32,
NULL, NULL, &tod_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
};
t_stat tod_reset(DEVICE *dptr)
{
int32 t;
if (!sim_is_running) {
t = sim_rtcn_init_unit(&tod_unit, TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / TPS_TOD);
}
return SCPE_OK;
}
t_stat tod_svc(UNIT *uptr)
{
int32 t;
t = sim_rtcn_calb(TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / TPS_TOD);
tod_reg++;
return SCPE_OK;
}
uint32 tod_read(uint32 pa, size_t size)
{
uint32 reg;
reg = pa - TODBASE;
sim_debug(READ_MSG, &tod_dev,
"[%08x] READ TOD: reg=%02x\n",
R[NUM_PC], reg);
switch(reg) {
case 0x04: /* 1/10 Sec */
case 0x08: /* 1 Sec */
case 0x0c: /* 10 Sec */
case 0x10: /* 1 Min */
case 0x14: /* 10 Min */
case 0x18: /* 1 Hour */
case 0x1c: /* 10 Hour */
case 0x20: /* 1 Day */
case 0x24: /* 10 Day */
case 0x28: /* Day of Week */
case 0x2c: /* 1 Month */
case 0x30: /* 10 Month */
default:
break;
}
return 0;
}
void tod_write(uint32 pa, uint32 val, size_t size)
{
uint32 reg;
reg = pa - TODBASE;
sim_debug(WRITE_MSG, &tod_dev,
"[%08x] WRITE TOD: reg=%02x val=%d\n",
R[NUM_PC], reg, val);
}

84
3B2/3b2_sysdev.h Normal file
View file

@ -0,0 +1,84 @@
/* 3b2_cpu.h: AT&T 3B2 Model 400 System Devices (Header)
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_SYSDEV_H_
#define _3B2_SYSDEV_H_
#include "3b2_defs.h"
#include "3b2_sys.h"
#include "3b2_cpu.h"
extern DEVICE nvram_dev;
extern DEVICE timer_dev;
extern DEVICE csr_dev;
extern DEVICE tod_dev;
extern DEBTAB sys_deb_tab[];
struct timer_ctr {
uint16 divider;
uint8 mode;
t_bool lmb;
t_bool enabled;
t_bool gate;
double stime; /* Most recent start time of counter */
};
/* NVRAM */
t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw);
t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw);
t_stat nvram_reset(DEVICE *dptr);
uint32 nvram_read(uint32 pa, size_t size);
t_stat nvram_attach(UNIT *uptr, CONST char *cptr);
t_stat nvram_detach(UNIT *uptr);
const char *nvram_description(DEVICE *dptr);
void nvram_write(uint32 pa, uint32 val, size_t size);
/* 8253 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);
/* CSR */
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);
t_stat csr_reset(DEVICE *dptr);
uint32 csr_read(uint32 pa, size_t size);
void csr_write(uint32 pa, uint32 val, size_t size);
/* TOD */
t_stat tod_svc(UNIT *uptr);
t_stat tod_reset(DEVICE *dptr);
uint32 tod_read(uint32 pa, size_t size);
void tod_write(uint32, uint32 val, size_t size);
#endif

112
3B2/README.md Normal file
View file

@ -0,0 +1,112 @@
AT&T 3B2 Simulator
==================
This module contains a simulator for the AT&T 3B2 Model 400 microcomputer.
*CAUTION*: The simulator is under active and heavy development. It is
usable today, but please consider this emulator to be a beta.
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)
- WE32100 CPU (CPU)
- WE32101 MMU (MMU)
- PD8253 Interval Timer (TIMER)
- AM9517 DMA controller (DMAC)
- SCN2681A Integrated DUART (IU)
- TMS2793 Integrated Floppy Controller (IF)
- uPD7261A Integrated MFM Fixed Disk Controller (ID)
Usage
-----
To boot the 3B2 simulator into firmware mode, simply type:
sim> BOOT CPU
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 can be saved between boots by attaching it to a file.
sim> ATTACH NVRAM <file>
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 for the 3B2 partially boots. To boot UNIX, attach the first
disk image from the 3B2 "Essential Utilities" distribution.
sim> ATTACH IF <disk-image>
sim> BOOT CPU
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
sim> ATTACH ID0 <disk-image>
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 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.

BIN
3B2/rom_400.bin Normal file

Binary file not shown.

2061
3B2/rom_400_bin.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,8 @@
### New Simulators ### New Simulators
#### Seth Morabito has implemented a AT&T 3B2 simulator.
#### Leonid Broukhis and Serge Vakulenko have implemented a simulator for the Soviet mainframe BESM-6 computer. #### Leonid Broukhis and Serge Vakulenko have implemented a simulator for the Soviet mainframe BESM-6 computer.
#### Matt Burke has implemented new VAX model simulators: #### Matt Burke has implemented new VAX model simulators:

View file

@ -0,0 +1,377 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9.00"
Name="3B2"
ProjectGUID="{56178F08-8783-4ADA-820C-20C06412678E}"
RootNamespace="3B2"
Keyword="Win32Proj"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="..\BIN\NT\$(PlatformName)-$(ConfigurationName)"
IntermediateDirectory="..\BIN\NT\Project\simh\$(ProjectName)\$(PlatformName)-$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="0"
>
<Tool
Name="VCPreBuildEventTool"
Description="Check for required build dependencies &amp; git commit id"
CommandLine="Pre-Build-Event.cmd LIBPCRE ROM"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="./;../;../3B2/;&quot;../../windows-build/PCRE/include/&quot;"
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCREPOSIX_H;PCRE_STATIC;USE_INT64;USE_ADDR64"
KeepComments="false"
MinimalRebuild="true"
BasicRuntimeChecks="0"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
CompileAs="1"
ShowIncludes="false"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="wsock32.lib winmm.lib pcrestaticd.lib pcreposixstaticd.lib"
LinkIncremental="2"
AdditionalLibraryDirectories="../../windows-build/PCRE/lib/"
GenerateDebugInformation="true"
SubSystem="1"
StackReserveSize="10485760"
StackCommitSize="10485760"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="..\BIN\NT\$(PlatformName)-$(ConfigurationName)"
IntermediateDirectory="..\BIN\NT\Project\simh\$(ProjectName)\$(PlatformName)-$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="0"
>
<Tool
Name="VCPreBuildEventTool"
Description="Check for required build dependencies &amp; git commit id"
CommandLine="Pre-Build-Event.cmd LIBPCRE ROM"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
InlineFunctionExpansion="1"
OmitFramePointers="true"
AdditionalIncludeDirectories="./;../;../3B2/;&quot;../../windows-build/PCRE/include/&quot;"
PreprocessorDefinitions="_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCREPOSIX_H;PCRE_STATIC;USE_INT64;USE_ADDR64"
StringPooling="true"
RuntimeLibrary="0"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
DebugInformationFormat="3"
CompileAs="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="wsock32.lib winmm.lib pcrestatic.lib pcreposixstatic.lib"
LinkIncremental="1"
AdditionalLibraryDirectories="../../windows-build/PCRE/lib/"
GenerateDebugInformation="false"
SubSystem="1"
StackReserveSize="10485760"
StackCommitSize="10485760"
OptimizeReferences="2"
EnableCOMDATFolding="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm"
>
<File
RelativePath="..\3B2\3b2_cpu.c"
>
</File>
<File
RelativePath="..\3B2\3b2_dmac.c"
>
</File>
<File
RelativePath="..\3B2\3b2_id.c"
>
</File>
<File
RelativePath="..\3B2\3b2_if.c"
>
</File>
<File
RelativePath="..\3B2\3b2_io.c"
>
</File>
<File
RelativePath="..\3B2\3b2_iu.c"
>
</File>
<File
RelativePath="..\3B2\3b2_mmu.c"
>
</File>
<File
RelativePath="..\3B2\3b2_sys.c"
>
</File>
<File
RelativePath="..\3B2\3b2_sysdev.c"
>
</File>
<File
RelativePath="..\scp.c"
>
</File>
<File
RelativePath="..\sim_console.c"
>
</File>
<File
RelativePath="..\sim_disk.c"
>
</File>
<File
RelativePath="..\sim_ether.c"
>
</File>
<File
RelativePath="..\sim_fio.c"
>
</File>
<File
RelativePath="..\sim_serial.c"
>
</File>
<File
RelativePath="..\sim_sock.c"
>
</File>
<File
RelativePath="..\sim_tape.c"
>
</File>
<File
RelativePath="..\sim_timer.c"
>
</File>
<File
RelativePath="..\sim_tmxr.c"
>
</File>
<File
RelativePath="..\sim_video.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc"
>
<File
RelativePath="..\3B2\3b2_cpu.h"
>
</File>
<File
RelativePath="..\3B2\3b2_defs.h"
>
</File>
<File
RelativePath="..\3B2\3b2_dmac.h"
>
</File>
<File
RelativePath="..\3B2\3b2_id.h"
>
</File>
<File
RelativePath="..\3B2\3b2_if.h"
>
</File>
<File
RelativePath="..\3B2\3b2_io.h"
>
</File>
<File
RelativePath="..\3B2\3b2_iu.h"
>
</File>
<File
RelativePath="..\3B2\3b2_mmu.h"
>
</File>
<File
RelativePath="..\3B2\3b2_sys.h"
>
</File>
<File
RelativePath="..\3B2\3b2_sysdev.h"
>
</File>
<File
RelativePath="..\scp.h"
>
</File>
<File
RelativePath="..\sim_console.h"
>
</File>
<File
RelativePath="..\sim_defs.h"
>
</File>
<File
RelativePath="..\sim_disk.h"
>
</File>
<File
RelativePath="..\sim_ether.h"
>
</File>
<File
RelativePath="..\sim_fio.h"
>
</File>
<File
RelativePath="..\sim_rev.h"
>
</File>
<File
RelativePath="..\sim_serial.h"
>
</File>
<File
RelativePath="..\sim_sock.h"
>
</File>
<File
RelativePath="..\sim_tape.h"
>
</File>
<File
RelativePath="..\sim_timer.h"
>
</File>
<File
RelativePath="..\sim_tmxr.h"
>
</File>
<File
RelativePath="..\sim_video.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View file

@ -254,6 +254,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "scelbi", "scelbi.vcproj", "
{D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8}
EndProjectSection EndProjectSection
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3B2", "3B2.vcproj", "{56178F08-8783-4ADA-820C-20C06412678E}"
ProjectSection(ProjectDependencies) = postProject
{D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8}
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32 Debug|Win32 = Debug|Win32
@ -472,6 +477,10 @@ Global
{1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Debug|Win32.Build.0 = Debug|Win32 {1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Debug|Win32.Build.0 = Debug|Win32
{1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Release|Win32.ActiveCfg = Release|Win32 {1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Release|Win32.ActiveCfg = Release|Win32
{1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Release|Win32.Build.0 = Release|Win32 {1E92CC4B-9ED5-4CD4-BD35-061F25126523}.Release|Win32.Build.0 = Release|Win32
{56178F08-8783-4ADA-820C-20C06412678E}.Debug|Win32.ActiveCfg = Debug|Win32
{56178F08-8783-4ADA-820C-20C06412678E}.Debug|Win32.Build.0 = Debug|Win32
{56178F08-8783-4ADA-820C-20C06412678E}.Release|Win32.ActiveCfg = Release|Win32
{56178F08-8783-4ADA-820C-20C06412678E}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -16,6 +16,7 @@
# This build script will accept the following build options. # This build script will accept the following build options.
# #
# ALL Just Build "Everything". # ALL Just Build "Everything".
# 3B2 Just Build The AT&T 3B2.
# ALTAIR Just Build The MITS Altair. # ALTAIR Just Build The MITS Altair.
# ALTAIRZ80 Just Build The MITS Altair Z80. # ALTAIRZ80 Just Build The MITS Altair Z80.
# BESM6 Just Build The BESM-6. # BESM6 Just Build The BESM-6.
@ -306,6 +307,17 @@ PCAP_SIMH_INC = /INCL=($(PCAP_DIR))
@ IF (("$(BUILDING_ROMS)".EQS."").AND.(F$SEARCH("$(BIN_DIR)BuildROMs-$(ARCH).EXE").EQS."")) THEN $(MMS) BUILDROMS/MACRO=(BUILDING_ROMS=1$(NEST_DEBUG)) @ IF (("$(BUILDING_ROMS)".EQS."").AND.(F$SEARCH("$(BIN_DIR)BuildROMs-$(ARCH).EXE").EQS."")) THEN $(MMS) BUILDROMS/MACRO=(BUILDING_ROMS=1$(NEST_DEBUG))
# AT&T 3B2 Simulator Definitions.
#
ATT3B2_DIR = SYS$DISK:[.3B2]
ATT3B2_LIB = $(LIB_DIR)ATT3B2-$(ARCH).OLB
ATT3B2_SOURCE = $(ATT3B2_DIR)3B2_CPU.C,$(ATT3B2_DIR)3B2_DMAC.C,\
$(ATT3B2_DIR)3B2_ID.C,$(ATT3B2_DIR)3B2_IF.C,\
$(ATT3B2_DIR)3B2_IO.C,$(ATT3B2_DIR)3B2_IU.C,\
$(ATT3B2_DIR)3B2_MMU.C,$(ATT3B2_DIR)3B2_SYS.C,\
$(ATT3B2_DIR)3B2_SYSDEV.C
ALTAIR_OPTIONS = /INCL=($(SIMH_DIR),$(ATT3B2_DIR))/DEF=($(CC_DEFS))
# MITS Altair Simulator Definitions. # MITS Altair Simulator Definitions.
# #
ALTAIR_DIR = SYS$DISK:[.ALTAIR] ALTAIR_DIR = SYS$DISK:[.ALTAIR]
@ -1033,6 +1045,17 @@ $(SIMH_LIB64) : $(SIMH_SOURCE)
$ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;* $ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;*
.ENDIF .ENDIF
$(ATT3B2_LIB) : $(ATT3B2_SOURCE)
$!
$! Building The $(ATT3B2_LIB) Library.
$!
$ $(CC)$(ATT3B2_OPTIONS) -
/OBJ=$(BLD_DIR) $(MMS$CHANGED_LIST)
$ IF (F$SEARCH("$(MMS$TARGET)").EQS."") THEN -
LIBRARY/CREATE $(MMS$TARGET)
$ LIBRARY/REPLACE $(MMS$TARGET) $(BLD_DIR)*.OBJ
$ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;*
$(ALTAIR_LIB) : $(ALTAIR_SOURCE) $(ALTAIR_LIB) : $(ALTAIR_SOURCE)
$! $!
$! Building The $(ALTAIR_LIB) Library. $! Building The $(ALTAIR_LIB) Library.
@ -1698,6 +1721,33 @@ $(I7094_LIB) :
# #
# Individual Simulator Builds. # Individual Simulator Builds.
# #
#
# If Not On VAX, Build The AT&T 3B2 Simulator.
#
.IFDEF ALPHA_OR_IA64
ATT3B2 : $(BIN_DIR)ATT3B2-$(ARCH).EXE
$! ATT3B2 aka 3B2 done
.ELSE
#
# Else We Are On VAX And Tell The User We Can't Build On VAX
# Due To The Use Of INT64.
#
ATT3B2 :
$! Sorry, Can't Build $(BIN_DIR)ATT3B2-$(ARCH).EXE Simulator
$! Because It Requires The Use Of INT64.
.ENDIF
$(BIN_DIR)ATT3B2-$(ARCH).EXE : $(SIMH_MAIN) $(SIMH_NONET_LIB) $(ATT3B2_LIB)
$!
$! Building The $(BIN_DIR)ATT3B2-$(ARCH).EXE Simulator.
$!
$ $(CC)$(ATT3B2_OPTIONS)/OBJ=$(BLD_DIR) SCP.C
$ LINK $(LINK_DEBUG)/EXE=$(BIN_DIR)ATT3B2-$(ARCH).EXE -
$(BLD_DIR)SCP.OBJ,$(ATT3B2_LIB)/LIBRARY,$(SIMH_NONET_LIB)/LIBRARY
$ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;*
$ COPY $(BIN_DIR)ATT3B2-$(ARCH).EXE $(BIN_DIR)3B2-$(ARCH).EXE
ALTAIR : $(BIN_DIR)ALTAIR-$(ARCH).EXE ALTAIR : $(BIN_DIR)ALTAIR-$(ARCH).EXE
$! ALTAIR done $! ALTAIR done

View file

@ -1547,6 +1547,14 @@ PDQ3D = PDQ-3
PDQ3 = ${PDQ3D}/pdq3_cpu.c ${PDQ3D}/pdq3_sys.c ${PDQ3D}/pdq3_stddev.c \ PDQ3 = ${PDQ3D}/pdq3_cpu.c ${PDQ3D}/pdq3_sys.c ${PDQ3D}/pdq3_stddev.c \
${PDQ3D}/pdq3_mem.c ${PDQ3D}/pdq3_debug.c ${PDQ3D}/pdq3_fdc.c ${PDQ3D}/pdq3_mem.c ${PDQ3D}/pdq3_debug.c ${PDQ3D}/pdq3_fdc.c
PDQ3_OPT = -I ${PDQ3D} -DUSE_SIM_IMD PDQ3_OPT = -I ${PDQ3D} -DUSE_SIM_IMD
ATT3B2D = 3B2
ATT3B2 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_mmu.c \
${ATT3B2D}/3b2_iu.c ${ATT3B2D}/3b2_if.c \
${ATT3B2D}/3b2_id.c ${ATT3B2D}/3b2_dmac.c \
${ATT3B2D}/3b2_sys.c ${ATT3B2D}/3b2_io.c \
${ATT3B2D}/3b2_sysdev.c
ATT3B2_OPT = -I ${ATT3B2D} -DUSE_INT64 -DUSE_ADDR64
# #
# Build everything (not the unsupported/incomplete or experimental simulators) # Build everything (not the unsupported/incomplete or experimental simulators)
# #
@ -1555,7 +1563,7 @@ ALL = pdp1 pdp4 pdp7 pdp8 pdp9 pdp15 pdp11 pdp10 \
nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri \ nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri \
i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \ i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \
swtp6800mp-a swtp6800mp-a2 tx-0 ssem b5500 isys8010 isys8020 \ swtp6800mp-a swtp6800mp-a2 tx-0 ssem b5500 isys8010 isys8020 \
isys8030 isys8024 imds-225 scelbi isys8030 isys8024 imds-225 scelbi 3b2
all : ${ALL} all : ${ALL}
@ -1933,6 +1941,12 @@ ${BIN}b5500${EXE} : ${B5500} ${SIM}
${MKDIRBIN} ${MKDIRBIN}
${CC} ${B5500} ${SIM} ${B5500_OPT} $(CC_OUTSPEC) ${LDFLAGS} ${CC} ${B5500} ${SIM} ${B5500_OPT} $(CC_OUTSPEC) ${LDFLAGS}
3b2 : $(BIN)3b2$(EXE)
${BIN}3b2${EXE} : ${ATT3B2} ${SIM} ${BUILD_ROMS}
${MKDIRBIN}
${CC} ${ATT3B2} ${SIM} ${ATT3B2_OPT} $(CC_OUTSPEC) ${LDFLAGS}
# Front Panel API Demo/Test program # Front Panel API Demo/Test program
frontpaneltest : ${BIN}frontpaneltest${EXE} frontpaneltest : ${BIN}frontpaneltest${EXE}

View file

@ -48,6 +48,7 @@ struct ROM_File_Descriptor {
{"VAX/vmb.exe", "VAX/vax_vmb_exe.h", 44544, 0xFFC014BB, "vax_vmb_exe"}, {"VAX/vmb.exe", "VAX/vax_vmb_exe.h", 44544, 0xFFC014BB, "vax_vmb_exe"},
{"PDP11/lunar11/lunar.lda", "PDP11/pdp11_vt_lunar_rom.h", 13824 , 0xFFF15D00, "lunar_lda"}, {"PDP11/lunar11/lunar.lda", "PDP11/pdp11_vt_lunar_rom.h", 13824 , 0xFFF15D00, "lunar_lda"},
{"swtp6800/swtp6800/swtbug.bin", "swtp6800/swtp6800/swtp_swtbug_bin.h", 1024, 0xFFFE4FBC, "swtp_swtbug_bin"}, {"swtp6800/swtp6800/swtbug.bin", "swtp6800/swtp6800/swtp_swtbug_bin.h", 1024, 0xFFFE4FBC, "swtp_swtbug_bin"},
{"3B2/rom_400.bin", "3B2/rom_400_bin.h", 32768, 0xFFD55762, "rom_400_bin"},
}; };